blob: bfe9492435db0a3fe556c5d8f0827c3175084a8b [file] [log] [blame]
// Copyright (c) 2016, 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/kernel_binary_flowgraph.h"
#include "vm/closure_functions_cache.h"
#include "vm/compiler/ffi/callback.h"
#include "vm/compiler/ffi/recognized_method.h"
#include "vm/compiler/frontend/flow_graph_builder.h" // For dart::FlowGraphBuilder::SimpleInstanceOfType.
#include "vm/compiler/frontend/prologue_builder.h"
#include "vm/compiler/jit/compiler.h"
#include "vm/kernel_binary.h"
#include "vm/kernel_loader.h"
#include "vm/object_store.h"
#include "vm/resolver.h"
#include "vm/stack_frame.h"
namespace dart {
namespace kernel {
#define Z (zone_)
#define H (translation_helper_)
#define T (type_translator_)
#define I Isolate::Current()
#define IG IsolateGroup::Current()
#define B (flow_graph_builder_)
Class& StreamingFlowGraphBuilder::GetSuperOrDie() {
Class& klass = Class::Handle(Z, parsed_function()->function().Owner());
ASSERT(!klass.IsNull());
klass = klass.SuperClass();
ASSERT(!klass.IsNull());
return klass;
}
FlowGraph* StreamingFlowGraphBuilder::BuildGraphOfFieldInitializer() {
FieldHelper field_helper(this);
field_helper.ReadUntilExcluding(FieldHelper::kInitializer);
// Constants are directly accessed at use sites of Dart code. In C++ - if
// we need to access static constants - we do so directly using the kernel
// evaluation instead of invoking the initializer function in Dart code.
//
// If the field is marked as @pragma('vm:entry-point') then the embedder might
// invoke the getter, so we'll generate the initializer function.
ASSERT(!field_helper.IsConst() ||
Field::Handle(Z, parsed_function()->function().accessor_field())
.VerifyEntryPoint(EntryPointPragma::kGetterOnly) ==
Error::null());
Tag initializer_tag = ReadTag(); // read first part of initializer.
if (initializer_tag != kSomething) {
UNREACHABLE();
}
B->graph_entry_ = new (Z) GraphEntryInstr(*parsed_function(), B->osr_id_);
auto normal_entry = B->BuildFunctionEntry(B->graph_entry_);
B->graph_entry_->set_normal_entry(normal_entry);
Fragment body(normal_entry);
body += B->CheckStackOverflowInPrologue(field_helper.position_);
body += SetupCapturedParameters(parsed_function()->function());
body += BuildExpression(); // read initializer.
body += Return(TokenPosition::kNoSource);
PrologueInfo prologue_info(-1, -1);
if (B->IsCompiledForOsr()) {
auto result = B->graph_entry_->FindOsrEntry(Z, B->last_used_block_id_ + 1);
flow_graph_builder_->RelinkToOsrEntry(result);
}
return new (Z) FlowGraph(
*parsed_function(), B->graph_entry_, B->last_used_block_id_,
prologue_info,
FlowGraph::CompilationModeFrom(flow_graph_builder_->optimizing()));
}
void StreamingFlowGraphBuilder::SetupDefaultParameterValues() {
intptr_t optional_parameter_count =
parsed_function()->function().NumOptionalParameters();
if (optional_parameter_count > 0) {
ZoneGrowableArray<const Instance*>* default_values =
new ZoneGrowableArray<const Instance*>(Z, optional_parameter_count);
AlternativeReadingScope alt(&reader_);
FunctionNodeHelper function_node_helper(this);
function_node_helper.ReadUntilExcluding(
FunctionNodeHelper::kPositionalParameters);
if (parsed_function()->function().HasOptionalNamedParameters()) {
// List of positional.
intptr_t list_length = ReadListLength(); // read list length.
for (intptr_t i = 0; i < list_length; ++i) {
SkipVariableDeclaration(); // read ith variable declaration.
}
// List of named.
list_length = ReadListLength(); // read list length.
ASSERT(optional_parameter_count == list_length);
ASSERT(!parsed_function()->function().HasOptionalPositionalParameters());
for (intptr_t i = 0; i < list_length; ++i) {
Instance* default_value;
// Read ith variable declaration
VariableDeclarationHelper helper(this);
helper.ReadUntilExcluding(VariableDeclarationHelper::kInitializer);
Tag tag = ReadTag(); // read (first part of) initializer.
if (tag == kSomething) {
// This will read the initializer.
default_value = &Instance::ZoneHandle(
Z, constant_reader_.ReadConstantExpression());
} else {
default_value = &Instance::ZoneHandle(Z, Instance::null());
}
default_values->Add(default_value);
}
} else {
// List of positional.
intptr_t list_length = ReadListLength(); // read list length.
ASSERT(list_length == function_node_helper.required_parameter_count_ +
optional_parameter_count);
ASSERT(parsed_function()->function().HasOptionalPositionalParameters());
for (intptr_t i = 0; i < function_node_helper.required_parameter_count_;
++i) {
SkipVariableDeclaration(); // read ith variable declaration.
}
for (intptr_t i = 0; i < optional_parameter_count; ++i) {
Instance* default_value;
// Read ith variable declaration
VariableDeclarationHelper helper(this);
helper.ReadUntilExcluding(VariableDeclarationHelper::kInitializer);
Tag tag = ReadTag(); // read (first part of) initializer.
if (tag == kSomething) {
// This will read the initializer.
default_value = &Instance::ZoneHandle(
Z, constant_reader_.ReadConstantExpression());
} else {
default_value = &Instance::ZoneHandle(Z, Instance::null());
}
default_values->Add(default_value);
}
// List of named.
list_length = ReadListLength(); // read list length.
ASSERT(list_length == 0);
}
parsed_function()->set_default_parameter_values(default_values);
}
}
Fragment StreamingFlowGraphBuilder::BuildFieldInitializer(
const Field& field,
bool only_for_side_effects) {
ASSERT(Error::Handle(Z, H.thread()->sticky_error()).IsNull());
if (PeekTag() == kNullLiteral) {
SkipExpression(); // read past the null literal.
if (H.thread()->IsDartMutatorThread()) {
ASSERT(field.IsOriginal());
LeaveCompilerScope cs(H.thread());
field.RecordStore(Object::null_object());
} else {
ASSERT(field.is_nullable_unsafe());
}
return Fragment();
}
Fragment instructions;
if (!only_for_side_effects) {
instructions += LoadLocal(parsed_function()->receiver_var());
}
// All closures created inside BuildExpression will have
// field.RawOwner() as its owner.
closure_owner_ = field.RawOwner();
instructions += BuildExpression();
closure_owner_ = Object::null();
if (only_for_side_effects) {
instructions += Drop();
} else {
instructions += flow_graph_builder_->StoreFieldGuarded(
field, StoreFieldInstr::Kind::kInitializing);
}
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildLateFieldInitializer(
const Field& field,
bool has_initializer) {
if (has_initializer && PeekTag() == kNullLiteral) {
SkipExpression(); // read past the null literal.
if (H.thread()->IsDartMutatorThread()) {
LeaveCompilerScope cs(H.thread());
field.RecordStore(Object::null_object());
} else {
ASSERT(field.is_nullable_unsafe());
}
return Fragment();
}
Fragment instructions;
instructions += LoadLocal(parsed_function()->receiver_var());
instructions += flow_graph_builder_->Constant(Object::sentinel());
instructions += flow_graph_builder_->StoreField(
field, StoreFieldInstr::Kind::kInitializing);
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildInitializers(
const Class& parent_class) {
ASSERT(Error::Handle(Z, H.thread()->sticky_error()).IsNull());
Fragment instructions;
// Start by getting the position of the constructors initializer.
intptr_t initializers_offset = -1;
{
AlternativeReadingScope alt(&reader_);
SkipFunctionNode(); // read constructors function node.
initializers_offset = ReaderOffset();
}
bool is_redirecting_constructor = false;
// Field which will be initialized by the initializer with the given index.
GrowableArray<const Field*> initializer_fields(5);
// Check if this is a redirecting constructor and collect all fields which
// will be initialized by the constructor initializer list.
{
AlternativeReadingScope alt(&reader_, initializers_offset);
const intptr_t list_length =
ReadListLength(); // read initializers list length.
initializer_fields.EnsureLength(list_length, nullptr);
bool has_field_initializers = false;
for (intptr_t i = 0; i < list_length; ++i) {
if (PeekTag() == kRedirectingInitializer) {
is_redirecting_constructor = true;
} else if (PeekTag() == kFieldInitializer) {
has_field_initializers = true;
ReadTag();
ReadBool();
ReadPosition();
const NameIndex field_name = ReadCanonicalNameReference();
const Field& field =
Field::Handle(Z, H.LookupFieldByKernelField(field_name));
initializer_fields[i] = &field;
SkipExpression();
continue;
}
SkipInitializer();
}
ASSERT(!is_redirecting_constructor || !has_field_initializers);
}
// These come from:
//
// class A {
// var x = (expr);
// }
//
// We don't want to do that when this is a Redirecting Constructors though
// (i.e. has a single initializer being of type kRedirectingInitializer).
if (!is_redirecting_constructor) {
// Sort list of fields (represented as their kernel offsets) which will
// be initialized by the constructor initializer list. We will not emit
// StoreField instructions for those initializers though we will
// still evaluate initialization expression for its side effects.
GrowableArray<intptr_t> constructor_initialized_field_offsets(
initializer_fields.length());
for (auto field : initializer_fields) {
if (field != nullptr) {
constructor_initialized_field_offsets.Add(field->kernel_offset());
}
}
constructor_initialized_field_offsets.Sort(
[](const intptr_t* a, const intptr_t* b) {
return static_cast<int>(*a) - static_cast<int>(*b);
});
constructor_initialized_field_offsets.Add(-1);
auto& kernel_data = TypedDataView::Handle(Z);
Array& class_fields = Array::Handle(Z, parent_class.fields());
Field& class_field = Field::Handle(Z);
intptr_t next_constructor_initialized_field_index = 0;
for (intptr_t i = 0; i < class_fields.Length(); ++i) {
class_field ^= class_fields.At(i);
if (!class_field.is_static()) {
const intptr_t field_offset = class_field.kernel_offset();
// Check if this field will be initialized by the constructor
// initializer list.
// Note that both class_fields and the list of initialized fields
// are sorted by their kernel offset (by construction) -
// so we don't need to perform the search.
bool is_constructor_initialized = false;
const intptr_t constructor_initialized_field_offset =
constructor_initialized_field_offsets
[next_constructor_initialized_field_index];
if (constructor_initialized_field_offset == field_offset) {
next_constructor_initialized_field_index++;
is_constructor_initialized = true;
}
kernel_data = class_field.KernelLibrary();
ASSERT(!kernel_data.IsNull());
AlternativeReadingScopeWithNewData alt(&reader_, &kernel_data,
field_offset);
FieldHelper field_helper(this);
field_helper.ReadUntilExcluding(FieldHelper::kInitializer);
const Tag initializer_tag = ReadTag();
if (class_field.is_late()) {
if (!is_constructor_initialized) {
instructions += BuildLateFieldInitializer(
Field::ZoneHandle(Z, class_field.ptr()),
initializer_tag == kSomething);
}
} else if (initializer_tag == kSomething) {
EnterScope(field_offset);
// If this field is initialized in constructor then we can ignore the
// value produced by the field initializer. However we still need to
// execute it for its side effects.
instructions += BuildFieldInitializer(
Field::ZoneHandle(Z, class_field.ptr()),
/*only_for_side_effects=*/is_constructor_initialized);
ExitScope(field_offset);
}
}
}
}
// These to come from:
// class A {
// var x;
// var y;
// A(this.x) : super(expr), y = (expr);
// }
{
AlternativeReadingScope alt(&reader_, initializers_offset);
intptr_t list_length = ReadListLength(); // read initializers list length.
for (intptr_t i = 0; i < list_length; ++i) {
Tag tag = ReadTag();
bool isSynthetic = ReadBool(); // read isSynthetic flag.
switch (tag) {
case kInvalidInitializer:
UNIMPLEMENTED();
return Fragment();
case kFieldInitializer: {
ReadPosition(); // read position.
ReadCanonicalNameReference();
instructions += BuildFieldInitializer(
Field::ZoneHandle(Z, initializer_fields[i]->ptr()),
/*only_for_size_effects=*/false);
break;
}
case kAssertInitializer: {
instructions += BuildStatement();
break;
}
case kSuperInitializer: {
TokenPosition position = ReadPosition(); // read position.
NameIndex canonical_target =
ReadCanonicalNameReference(); // read target_reference.
instructions += LoadLocal(parsed_function()->receiver_var());
// TODO(jensj): ASSERT(init->arguments()->types().length() == 0);
Array& argument_names = Array::ZoneHandle(Z);
intptr_t argument_count;
instructions += BuildArguments(
&argument_names, &argument_count,
/* positional_parameter_count = */ nullptr); // read arguments.
argument_count += 1;
Class& parent_klass = GetSuperOrDie();
const Function& target = Function::ZoneHandle(
Z, H.LookupConstructorByKernelConstructor(
parent_klass, H.CanonicalNameString(canonical_target)));
instructions += StaticCall(
isSynthetic ? TokenPosition::kNoSource : position, target,
argument_count, argument_names, ICData::kStatic);
instructions += Drop();
break;
}
case kRedirectingInitializer: {
TokenPosition position = ReadPosition(); // read position.
NameIndex canonical_target =
ReadCanonicalNameReference(); // read target_reference.
instructions += LoadLocal(parsed_function()->receiver_var());
// TODO(jensj): ASSERT(init->arguments()->types().length() == 0);
Array& argument_names = Array::ZoneHandle(Z);
intptr_t argument_count;
instructions += BuildArguments(
&argument_names, &argument_count,
/* positional_parameter_count = */ nullptr); // read arguments.
argument_count += 1;
const Function& target = Function::ZoneHandle(
Z, H.LookupConstructorByKernelConstructor(canonical_target));
instructions += StaticCall(
isSynthetic ? TokenPosition::kNoSource : position, target,
argument_count, argument_names, ICData::kStatic);
instructions += Drop();
break;
}
case kLocalInitializer: {
// The other initializers following this one might read the variable.
// This is used e.g. for evaluating the arguments to a super call
// first, run normal field initializers next and then make the actual
// super call:
//
// The frontend converts
//
// class A {
// var x;
// A(a, b) : super(a + b), x = 2*b {}
// }
//
// to
//
// class A {
// var x;
// A(a, b) : tmp = a + b, x = 2*b, super(tmp) {}
// }
//
// (This is strictly speaking not what one should do in terms of the
// specification but that is how it is currently implemented.)
LocalVariable* variable =
LookupVariable(ReaderOffset() + data_program_offset_);
// Variable declaration
VariableDeclarationHelper helper(this);
helper.ReadUntilExcluding(VariableDeclarationHelper::kInitializer);
ASSERT(!helper.IsConst());
Tag tag = ReadTag(); // read (first part of) initializer.
if (tag != kSomething) {
UNREACHABLE();
}
instructions += BuildExpression(); // read initializer.
instructions += StoreLocal(TokenPosition::kNoSource, variable);
instructions += Drop();
break;
}
default:
ReportUnexpectedTag("initializer", tag);
UNREACHABLE();
}
}
}
return instructions;
}
Fragment StreamingFlowGraphBuilder::DebugStepCheckInPrologue(
const Function& dart_function,
TokenPosition position) {
if (!NeedsDebugStepCheck(dart_function, position)) {
return {};
}
// Place this check at the last parameter to ensure parameters
// are in scope in the debugger at method entry.
const int parameter_count = dart_function.NumParameters();
TokenPosition check_pos = TokenPosition::kNoSource;
if (parameter_count > 0) {
const LocalVariable& parameter =
*parsed_function()->ParameterVariable(parameter_count - 1);
check_pos = parameter.token_pos();
}
if (!check_pos.IsDebugPause()) {
// No parameters or synthetic parameters.
check_pos = position;
ASSERT(check_pos.IsDebugPause());
}
return DebugStepCheck(check_pos);
}
Fragment StreamingFlowGraphBuilder::TypeArgumentsHandling(
const Function& dart_function) {
Fragment prologue = B->BuildDefaultTypeHandling(dart_function);
if (dart_function.IsClosureFunction() &&
dart_function.NumParentTypeArguments() > 0) {
LocalVariable* closure = parsed_function()->ParameterVariable(0);
LocalVariable* fn_type_args = parsed_function()->function_type_arguments();
ASSERT(fn_type_args != nullptr && closure != nullptr);
if (dart_function.IsGeneric()) {
prologue += LoadLocal(fn_type_args);
prologue += LoadLocal(closure);
prologue += LoadNativeField(Slot::Closure_function_type_arguments());
prologue += IntConstant(dart_function.NumParentTypeArguments());
prologue += IntConstant(dart_function.NumTypeArguments());
const auto& prepend_function =
flow_graph_builder_->PrependTypeArgumentsFunction();
prologue += StaticCall(TokenPosition::kNoSource, prepend_function, 4,
ICData::kStatic);
prologue += StoreLocal(TokenPosition::kNoSource, fn_type_args);
prologue += Drop();
} else {
prologue += LoadLocal(closure);
prologue += LoadNativeField(Slot::Closure_function_type_arguments());
prologue += StoreLocal(TokenPosition::kNoSource, fn_type_args);
prologue += Drop();
}
}
return prologue;
}
Fragment StreamingFlowGraphBuilder::CheckStackOverflowInPrologue(
const Function& dart_function) {
if (dart_function.is_native()) return {};
return B->CheckStackOverflowInPrologue(dart_function.token_pos());
}
Fragment StreamingFlowGraphBuilder::SetupCapturedParameters(
const Function& dart_function) {
Fragment body;
const LocalScope* scope = parsed_function()->scope();
if (scope->num_context_variables() > 0) {
body += flow_graph_builder_->PushContext(scope);
LocalVariable* context = MakeTemporary();
// Copy captured parameters from the stack into the context.
LocalScope* scope = parsed_function()->scope();
intptr_t parameter_count = dart_function.NumParameters();
const ParsedFunction& pf = *flow_graph_builder_->parsed_function_;
const Function& function = pf.function();
for (intptr_t i = 0; i < parameter_count; ++i) {
LocalVariable* variable = pf.ParameterVariable(i);
if (variable->is_captured()) {
LocalVariable& raw_parameter = *pf.RawParameterVariable(i);
ASSERT((function.MakesCopyOfParameters() &&
raw_parameter.owner() == scope) ||
(!function.MakesCopyOfParameters() &&
raw_parameter.owner() == nullptr));
ASSERT(!raw_parameter.is_captured());
// Copy the parameter from the stack to the context.
body += LoadLocal(context);
body += LoadLocal(&raw_parameter);
body += flow_graph_builder_->StoreNativeField(
Slot::GetContextVariableSlotFor(thread(), *variable),
StoreFieldInstr::Kind::kInitializing);
}
}
body += Drop(); // The context.
}
return body;
}
Fragment StreamingFlowGraphBuilder::InitSuspendableFunction(
const Function& dart_function,
const AbstractType* emitted_value_type) {
Fragment body;
if (dart_function.IsAsyncFunction()) {
ASSERT(emitted_value_type != nullptr);
auto& type_args = TypeArguments::ZoneHandle(Z, TypeArguments::New(1));
type_args.SetTypeAt(0, *emitted_value_type);
type_args = Class::Handle(Z, IG->object_store()->future_class())
.GetInstanceTypeArguments(H.thread(), type_args);
body += TranslateInstantiatedTypeArguments(type_args);
body += B->Call1ArgStub(TokenPosition::kNoSource,
Call1ArgStubInstr::StubId::kInitAsync);
body += Drop();
} else if (dart_function.IsAsyncGenerator()) {
ASSERT(emitted_value_type != nullptr);
auto& type_args = TypeArguments::ZoneHandle(Z, TypeArguments::New(1));
type_args.SetTypeAt(0, *emitted_value_type);
type_args = Class::Handle(Z, IG->object_store()->stream_class())
.GetInstanceTypeArguments(H.thread(), type_args);
body += TranslateInstantiatedTypeArguments(type_args);
body += B->Call1ArgStub(TokenPosition::kNoSource,
Call1ArgStubInstr::StubId::kInitAsyncStar);
body += Drop();
body += NullConstant();
body += B->Suspend(TokenPosition::kNoSource,
SuspendInstr::StubId::kYieldAsyncStar);
body += Drop();
} else if (dart_function.IsSyncGenerator()) {
ASSERT(emitted_value_type != nullptr);
auto& type_args = TypeArguments::ZoneHandle(Z, TypeArguments::New(1));
type_args.SetTypeAt(0, *emitted_value_type);
type_args = Class::Handle(Z, IG->object_store()->iterable_class())
.GetInstanceTypeArguments(H.thread(), type_args);
body += TranslateInstantiatedTypeArguments(type_args);
body += B->Call1ArgStub(TokenPosition::kNoSource,
Call1ArgStubInstr::StubId::kInitSyncStar);
body += Drop();
body += NullConstant();
body += B->Suspend(TokenPosition::kNoSource,
SuspendInstr::StubId::kSuspendSyncStarAtStart);
body += Drop();
// Clone context if there are any captured parameter variables, so
// each invocation of .iterator would get its own copy of parameters.
const LocalScope* scope = parsed_function()->scope();
if (scope->num_context_variables() > 0) {
body += CloneContext(scope->context_slots());
}
} else {
ASSERT(emitted_value_type == nullptr);
}
return body;
}
Fragment StreamingFlowGraphBuilder::ShortcutForUserDefinedEquals(
const Function& dart_function,
LocalVariable* first_parameter) {
// The specification defines the result of `a == b` to be:
//
// a) if either side is `null` then the result is `identical(a, b)`.
// b) else the result is `a.operator==(b)`
//
// For user-defined implementations of `operator==` we need therefore
// implement the handling of a).
//
// The default `operator==` implementation in `Object` is implemented in terms
// of identical (which we assume here!) which means that case a) is actually
// included in b). So we just use the normal implementation in the body.
Fragment body;
if ((dart_function.NumParameters() == 2) &&
(dart_function.name() == Symbols::EqualOperator().ptr()) &&
(dart_function.Owner() != IG->object_store()->object_class())) {
TargetEntryInstr* null_entry;
TargetEntryInstr* non_null_entry;
body += LoadLocal(first_parameter);
body += BranchIfNull(&null_entry, &non_null_entry);
// The argument was `null` and the receiver is not the null class (we only
// go into this branch for user-defined == operators) so we can return
// false.
Fragment null_fragment(null_entry);
null_fragment += Constant(Bool::False());
null_fragment += Return(dart_function.end_token_pos());
body = Fragment(body.entry, non_null_entry);
}
return body;
}
Fragment StreamingFlowGraphBuilder::BuildFunctionBody(
const Function& dart_function,
LocalVariable* first_parameter,
bool constructor) {
Fragment body;
// TODO(27590): Currently the [VariableDeclaration]s from the
// initializers will be visible inside the entire body of the constructor.
// We should make a separate scope for them.
if (constructor) {
body += BuildInitializers(Class::Handle(Z, dart_function.Owner()));
}
if (body.is_closed()) return body;
FunctionNodeHelper function_node_helper(this);
function_node_helper.ReadUntilExcluding(FunctionNodeHelper::kBody);
const bool has_body = ReadTag() == kSomething; // read first part of body.
if (dart_function.is_old_native()) {
body += B->NativeFunctionBody(dart_function, first_parameter);
} else if (dart_function.is_ffi_native()) {
body += B->FfiNativeFunctionBody(dart_function);
} else if (dart_function.is_external()) {
body += ThrowNoSuchMethodError(TokenPosition::kNoSource, dart_function,
/*incompatible_arguments=*/false);
ASSERT(body.is_closed());
} else if (has_body) {
body += BuildStatement();
}
if (body.is_open()) {
if (parsed_function()->function().IsSyncGenerator()) {
// Return false from sync* function to indicate the end of iteration.
body += Constant(Bool::False());
} else {
body += NullConstant();
}
body += Return(dart_function.end_token_pos());
}
return body;
}
Fragment StreamingFlowGraphBuilder::BuildRegularFunctionPrologue(
const Function& dart_function,
TokenPosition token_position,
LocalVariable* first_parameter) {
Fragment F;
F += CheckStackOverflowInPrologue(dart_function);
F += DebugStepCheckInPrologue(dart_function, token_position);
F += B->InitConstantParameters();
F += SetupCapturedParameters(dart_function);
F += ShortcutForUserDefinedEquals(dart_function, first_parameter);
return F;
}
Fragment StreamingFlowGraphBuilder::ClearRawParameters(
const Function& dart_function) {
const ParsedFunction& pf = *flow_graph_builder_->parsed_function_;
Fragment code;
for (intptr_t i = 0; i < dart_function.NumParameters(); ++i) {
LocalVariable* variable = pf.ParameterVariable(i);
if (!variable->is_captured()) continue;
// Captured 'this' is immutable, so within the outer method we don't need to
// load it from the context. Therefore we don't reset it to null.
if (pf.function().HasThisParameter() && pf.has_receiver_var() &&
variable == pf.receiver_var()) {
ASSERT(i == 0);
continue;
}
variable = pf.RawParameterVariable(i);
code += NullConstant();
code += StoreLocal(TokenPosition::kNoSource, variable);
code += Drop();
}
return code;
}
UncheckedEntryPointStyle StreamingFlowGraphBuilder::ChooseEntryPointStyle(
const Function& dart_function,
const Fragment& implicit_type_checks,
const Fragment& regular_function_prologue,
const Fragment& type_args_handling) {
ASSERT(!dart_function.IsImplicitClosureFunction());
if (!dart_function.MayHaveUncheckedEntryPoint() ||
implicit_type_checks.is_empty()) {
return UncheckedEntryPointStyle::kNone;
}
// Record which entry-point was taken into a variable and test it later if
// either:
//
// 1. There is a non-empty PrologueBuilder-prologue.
//
// 2. The regular function prologue has more than two instructions
// (DebugStepCheck and CheckStackOverflow).
//
if (!PrologueBuilder::HasEmptyPrologue(dart_function) ||
!type_args_handling.is_empty()) {
return UncheckedEntryPointStyle::kSharedWithVariable;
}
Instruction* instr = regular_function_prologue.entry;
if (instr != nullptr && instr->IsCheckStackOverflow()) {
instr = instr->next();
}
if (instr != nullptr && instr->IsDebugStepCheck()) {
instr = instr->next();
}
if (instr != nullptr) {
return UncheckedEntryPointStyle::kSharedWithVariable;
}
return UncheckedEntryPointStyle::kSeparate;
}
FlowGraph* StreamingFlowGraphBuilder::BuildGraphOfFunction(
bool is_constructor) {
const Function& dart_function = parsed_function()->function();
LocalVariable* first_parameter = nullptr;
TokenPosition token_position = TokenPosition::kNoSource;
const AbstractType* emitted_value_type = nullptr;
{
AlternativeReadingScope alt(&reader_);
FunctionNodeHelper function_node_helper(this);
function_node_helper.ReadUntilExcluding(
FunctionNodeHelper::kPositionalParameters);
{
AlternativeReadingScope alt2(&reader_);
intptr_t list_length = ReadListLength(); // read number of positionals.
if (list_length > 0) {
intptr_t first_parameter_offset = ReaderOffset() + data_program_offset_;
first_parameter = LookupVariable(first_parameter_offset);
}
}
token_position = function_node_helper.position_;
if (dart_function.IsSuspendableFunction()) {
function_node_helper.ReadUntilExcluding(
FunctionNodeHelper::kEmittedValueType);
if (ReadTag() == kSomething) {
emitted_value_type = &T.BuildType(); // read emitted value type.
} else {
UNREACHABLE();
}
}
}
auto graph_entry = flow_graph_builder_->graph_entry_ =
new (Z) GraphEntryInstr(*parsed_function(), flow_graph_builder_->osr_id_);
auto normal_entry = flow_graph_builder_->BuildFunctionEntry(graph_entry);
graph_entry->set_normal_entry(normal_entry);
PrologueInfo prologue_info(-1, -1);
BlockEntryInstr* instruction_cursor =
flow_graph_builder_->BuildPrologue(normal_entry, &prologue_info);
const Fragment regular_prologue = BuildRegularFunctionPrologue(
dart_function, token_position, first_parameter);
// TODO(#34162): We can remove the default type handling (and
// shorten the prologue type handling sequence) for non-dynamic invocations of
// regular methods.
const Fragment type_args_handling = TypeArgumentsHandling(dart_function);
Fragment implicit_type_checks;
if (dart_function.NeedsTypeArgumentTypeChecks()) {
B->BuildTypeArgumentTypeChecks(
TypeChecksToBuild::kCheckCovariantTypeParameterBounds,
&implicit_type_checks);
}
Fragment explicit_type_checks;
Fragment implicit_redefinitions;
if (dart_function.NeedsArgumentTypeChecks()) {
B->BuildArgumentTypeChecks(&explicit_type_checks, &implicit_type_checks,
&implicit_redefinitions);
}
// The RawParameter variables should be set to null to avoid retaining more
// objects than necessary during GC.
const Fragment body =
ClearRawParameters(dart_function) +
InitSuspendableFunction(dart_function, emitted_value_type) +
BuildFunctionBody(dart_function, first_parameter, is_constructor);
auto extra_entry_point_style =
ChooseEntryPointStyle(dart_function, implicit_type_checks,
regular_prologue, type_args_handling);
Fragment function(instruction_cursor);
FunctionEntryInstr* extra_entry = nullptr;
switch (extra_entry_point_style) {
case UncheckedEntryPointStyle::kNone: {
function += regular_prologue + type_args_handling + implicit_type_checks +
explicit_type_checks + body;
break;
}
case UncheckedEntryPointStyle::kSeparate: {
ASSERT(instruction_cursor == normal_entry);
ASSERT(type_args_handling.is_empty());
const Fragment prologue_copy = BuildRegularFunctionPrologue(
dart_function, token_position, first_parameter);
extra_entry = B->BuildSeparateUncheckedEntryPoint(
normal_entry,
/*normal_prologue=*/regular_prologue + implicit_type_checks,
/*extra_prologue=*/prologue_copy,
/*shared_prologue=*/explicit_type_checks,
/*body=*/body);
break;
}
case UncheckedEntryPointStyle::kSharedWithVariable: {
Fragment prologue(normal_entry, instruction_cursor);
prologue += regular_prologue;
prologue += type_args_handling;
prologue += explicit_type_checks;
extra_entry = B->BuildSharedUncheckedEntryPoint(
/*shared_prologue_linked_in=*/prologue,
/*skippable_checks=*/implicit_type_checks,
/*redefinitions_if_skipped=*/implicit_redefinitions,
/*body=*/body);
break;
}
}
if (extra_entry != nullptr) {
B->RecordUncheckedEntryPoint(graph_entry, extra_entry);
}
auto flow_graph = new (Z) FlowGraph(
*parsed_function(), graph_entry, flow_graph_builder_->last_used_block_id_,
prologue_info,
FlowGraph::CompilationModeFrom(flow_graph_builder_->optimizing()));
// When compiling for OSR, use a depth first search to find the OSR
// entry and make graph entry jump to it instead of normal entry.
// Include enclosing try blocks with corresponding catch blocks.
if (flow_graph_builder_->IsCompiledForOsr()) {
auto result = graph_entry->FindOsrEntry(
Z, flow_graph_builder_->last_used_block_id_ + 1);
flow_graph_builder_->RelinkToOsrEntry(result);
flow_graph = new (Z) FlowGraph(
*parsed_function(), graph_entry,
flow_graph_builder_->last_used_block_id_, prologue_info,
FlowGraph::CompilationModeFrom(flow_graph_builder_->optimizing_));
}
return flow_graph;
}
FlowGraph* StreamingFlowGraphBuilder::BuildGraph() {
ASSERT(Error::Handle(Z, H.thread()->sticky_error()).IsNull());
ASSERT(flow_graph_builder_ != nullptr);
const Function& function = parsed_function()->function();
// Setup an [ActiveClassScope] and an [ActiveMemberScope] which will be used
// e.g. for type translation.
const Class& klass =
Class::Handle(zone_, parsed_function()->function().Owner());
Function& outermost_function =
Function::Handle(Z, function.GetOutermostFunction());
ActiveClassScope active_class_scope(active_class(), &klass);
ActiveMemberScope active_member(active_class(), &outermost_function);
FunctionType& signature = FunctionType::Handle(Z, function.signature());
ActiveTypeParametersScope active_type_params(active_class(), function,
&signature, Z);
ParseKernelASTFunction();
switch (function.kind()) {
case UntaggedFunction::kRegularFunction:
case UntaggedFunction::kGetterFunction:
case UntaggedFunction::kSetterFunction:
case UntaggedFunction::kClosureFunction:
case UntaggedFunction::kConstructor: {
if (FlowGraphBuilder::IsRecognizedMethodForFlowGraph(function)) {
return B->BuildGraphOfRecognizedMethod(function);
}
return BuildGraphOfFunction(function.IsGenerativeConstructor());
}
case UntaggedFunction::kImplicitGetter:
case UntaggedFunction::kImplicitStaticGetter:
case UntaggedFunction::kImplicitSetter: {
return B->BuildGraphOfFieldAccessor(function);
}
case UntaggedFunction::kFieldInitializer:
return BuildGraphOfFieldInitializer();
case UntaggedFunction::kDynamicInvocationForwarder:
return B->BuildGraphOfDynamicInvocationForwarder(function);
case UntaggedFunction::kMethodExtractor:
return flow_graph_builder_->BuildGraphOfMethodExtractor(function);
case UntaggedFunction::kNoSuchMethodDispatcher:
return flow_graph_builder_->BuildGraphOfNoSuchMethodDispatcher(function);
case UntaggedFunction::kInvokeFieldDispatcher:
return flow_graph_builder_->BuildGraphOfInvokeFieldDispatcher(function);
case UntaggedFunction::kImplicitClosureFunction:
return flow_graph_builder_->BuildGraphOfImplicitClosureFunction(function);
case UntaggedFunction::kFfiTrampoline:
return flow_graph_builder_->BuildGraphOfFfiTrampoline(function);
case UntaggedFunction::kRecordFieldGetter:
return flow_graph_builder_->BuildGraphOfRecordFieldGetter(function);
case UntaggedFunction::kIrregexpFunction:
break;
}
UNREACHABLE();
return nullptr;
}
void StreamingFlowGraphBuilder::ParseKernelASTFunction() {
const Function& function = parsed_function()->function();
if (!function.IsNoSuchMethodDispatcher() &&
!function.IsInvokeFieldDispatcher() &&
!function.IsFfiCallbackTrampoline()) {
const intptr_t kernel_offset = function.kernel_offset();
ASSERT(kernel_offset >= 0);
SetOffset(kernel_offset);
}
// Mark forwarding stubs.
switch (function.kind()) {
case UntaggedFunction::kRegularFunction:
case UntaggedFunction::kImplicitClosureFunction:
case UntaggedFunction::kGetterFunction:
case UntaggedFunction::kSetterFunction:
case UntaggedFunction::kClosureFunction:
case UntaggedFunction::kConstructor:
case UntaggedFunction::kDynamicInvocationForwarder:
ReadForwardingStubTarget(function);
break;
default:
break;
}
set_scopes(parsed_function()->EnsureKernelScopes());
switch (function.kind()) {
case UntaggedFunction::kRegularFunction:
case UntaggedFunction::kGetterFunction:
case UntaggedFunction::kSetterFunction:
case UntaggedFunction::kClosureFunction:
case UntaggedFunction::kConstructor:
case UntaggedFunction::kImplicitClosureFunction:
ReadUntilFunctionNode();
SetupDefaultParameterValues();
break;
case UntaggedFunction::kImplicitGetter:
case UntaggedFunction::kImplicitStaticGetter:
case UntaggedFunction::kImplicitSetter:
case UntaggedFunction::kFieldInitializer:
case UntaggedFunction::kMethodExtractor:
case UntaggedFunction::kNoSuchMethodDispatcher:
case UntaggedFunction::kInvokeFieldDispatcher:
case UntaggedFunction::kFfiTrampoline:
case UntaggedFunction::kRecordFieldGetter:
break;
case UntaggedFunction::kDynamicInvocationForwarder:
if (PeekTag() != kField) {
ReadUntilFunctionNode();
SetupDefaultParameterValues();
}
break;
case UntaggedFunction::kIrregexpFunction:
UNREACHABLE();
break;
}
}
void StreamingFlowGraphBuilder::ReadForwardingStubTarget(
const Function& function) {
if (PeekTag() == kProcedure) {
AlternativeReadingScope alt(&reader_);
ProcedureHelper procedure_helper(this);
procedure_helper.ReadUntilExcluding(ProcedureHelper::kFunction);
if (procedure_helper.IsForwardingStub() && !procedure_helper.IsAbstract()) {
const NameIndex target_name =
procedure_helper.concrete_forwarding_stub_target_;
ASSERT(target_name != NameIndex::kInvalidName);
const String& name = function.IsSetterFunction()
? H.DartSetterName(target_name)
: H.DartProcedureName(target_name);
const Function* forwarding_target =
&Function::ZoneHandle(Z, H.LookupMethodByMember(target_name, name));
ASSERT(!forwarding_target->IsNull());
parsed_function()->MarkForwardingStub(forwarding_target);
}
}
}
Fragment StreamingFlowGraphBuilder::BuildStatementAt(intptr_t kernel_offset) {
SetOffset(kernel_offset);
return BuildStatement(); // read statement.
}
Fragment StreamingFlowGraphBuilder::BuildExpression(TokenPosition* position) {
++num_ast_nodes_;
uint8_t payload = 0;
Tag tag = ReadTag(&payload); // read tag.
switch (tag) {
case kInvalidExpression:
return BuildInvalidExpression(position);
case kVariableGet:
return BuildVariableGet(position);
case kSpecializedVariableGet:
return BuildVariableGet(payload, position);
case kVariableSet:
return BuildVariableSet(position);
case kSpecializedVariableSet:
return BuildVariableSet(payload, position);
case kInstanceGet:
return BuildInstanceGet(position);
case kDynamicGet:
return BuildDynamicGet(position);
case kInstanceTearOff:
return BuildInstanceTearOff(position);
case kFunctionTearOff:
// Removed by lowering kernel transformation.
UNREACHABLE();
break;
case kInstanceSet:
return BuildInstanceSet(position);
case kDynamicSet:
return BuildDynamicSet(position);
case kAbstractSuperPropertyGet:
// Abstract super property getters must be converted into super property
// getters during mixin transformation.
UNREACHABLE();
break;
case kAbstractSuperPropertySet:
// Abstract super property setters must be converted into super property
// setters during mixin transformation.
UNREACHABLE();
break;
case kSuperPropertyGet:
return BuildSuperPropertyGet(position);
case kSuperPropertySet:
return BuildSuperPropertySet(position);
case kStaticGet:
return BuildStaticGet(position);
case kStaticSet:
return BuildStaticSet(position);
case kInstanceInvocation:
return BuildMethodInvocation(position, /*is_dynamic=*/false);
case kDynamicInvocation:
return BuildMethodInvocation(position, /*is_dynamic=*/true);
case kLocalFunctionInvocation:
return BuildLocalFunctionInvocation(position);
case kFunctionInvocation:
return BuildFunctionInvocation(position);
case kEqualsCall:
return BuildEqualsCall(position);
case kEqualsNull:
return BuildEqualsNull(position);
case kAbstractSuperMethodInvocation:
// Abstract super method invocations must be converted into super
// method invocations during mixin transformation.
UNREACHABLE();
break;
case kSuperMethodInvocation:
return BuildSuperMethodInvocation(position);
case kStaticInvocation:
return BuildStaticInvocation(position);
case kConstructorInvocation:
return BuildConstructorInvocation(position);
case kNot:
return BuildNot(position);
case kNullCheck:
return BuildNullCheck(position);
case kLogicalExpression:
return BuildLogicalExpression(position);
case kConditionalExpression:
return BuildConditionalExpression(position);
case kStringConcatenation:
return BuildStringConcatenation(position);
case kIsExpression:
return BuildIsExpression(position);
case kAsExpression:
return BuildAsExpression(position);
case kTypeLiteral:
return BuildTypeLiteral(position);
case kThisExpression:
return BuildThisExpression(position);
case kRethrow:
return BuildRethrow(position);
case kThrow:
return BuildThrow(position);
case kListLiteral:
return BuildListLiteral(position);
case kSetLiteral:
// Set literals are currently desugared in the frontend and will not
// reach the VM. See http://dartbug.com/35124 for discussion.
UNREACHABLE();
break;
case kMapLiteral:
return BuildMapLiteral(position);
case kRecordLiteral:
return BuildRecordLiteral(position);
case kRecordIndexGet:
return BuildRecordFieldGet(position, /*is_named=*/false);
case kRecordNameGet:
return BuildRecordFieldGet(position, /*is_named=*/true);
case kFunctionExpression:
return BuildFunctionExpression();
case kLet:
return BuildLet(position);
case kBlockExpression:
return BuildBlockExpression();
case kBigIntLiteral:
return BuildBigIntLiteral(position);
case kStringLiteral:
return BuildStringLiteral(position);
case kSpecializedIntLiteral:
return BuildIntLiteral(payload, position);
case kNegativeIntLiteral:
return BuildIntLiteral(true, position);
case kPositiveIntLiteral:
return BuildIntLiteral(false, position);
case kDoubleLiteral:
return BuildDoubleLiteral(position);
case kTrueLiteral:
return BuildBoolLiteral(true, position);
case kFalseLiteral:
return BuildBoolLiteral(false, position);
case kNullLiteral:
return BuildNullLiteral(position);
case kConstantExpression:
case kFileUriConstantExpression:
return BuildConstantExpression(position, tag);
case kInstantiation:
return BuildPartialTearoffInstantiation(position);
case kLoadLibrary:
return BuildLibraryPrefixAction(position, Symbols::LoadLibrary());
case kCheckLibraryIsLoaded:
return BuildLibraryPrefixAction(position, Symbols::CheckLoaded());
case kAwaitExpression:
return BuildAwaitExpression(position);
case kFileUriExpression:
return BuildFileUriExpression(position);
case kConstStaticInvocation:
case kConstConstructorInvocation:
case kConstListLiteral:
case kConstSetLiteral:
case kConstMapLiteral:
case kSymbolLiteral:
case kListConcatenation:
case kSetConcatenation:
case kMapConcatenation:
case kInstanceCreation:
case kStaticTearOff:
case kSwitchExpression:
case kPatternAssignment:
// These nodes are internal to the front end and
// removed by the constant evaluator.
default:
ReportUnexpectedTag("expression", tag);
UNREACHABLE();
}
return Fragment();
}
Fragment StreamingFlowGraphBuilder::BuildStatement(TokenPosition* position) {
++num_ast_nodes_;
Tag tag = ReadTag(); // read tag.
switch (tag) {
case kExpressionStatement:
return BuildExpressionStatement(position);
case kBlock:
return BuildBlock(position);
case kEmptyStatement:
return BuildEmptyStatement();
case kAssertBlock:
return BuildAssertBlock(position);
case kAssertStatement:
return BuildAssertStatement(position);
case kLabeledStatement:
return BuildLabeledStatement(position);
case kBreakStatement:
return BuildBreakStatement(position);
case kWhileStatement:
return BuildWhileStatement(position);
case kDoStatement:
return BuildDoStatement(position);
case kForStatement:
return BuildForStatement(position);
case kSwitchStatement:
return BuildSwitchStatement(position);
case kContinueSwitchStatement:
return BuildContinueSwitchStatement(position);
case kIfStatement:
return BuildIfStatement(position);
case kReturnStatement:
return BuildReturnStatement(position);
case kTryCatch:
return BuildTryCatch(position);
case kTryFinally:
return BuildTryFinally(position);
case kYieldStatement:
return BuildYieldStatement(position);
case kVariableDeclaration:
return BuildVariableDeclaration(position);
case kFunctionDeclaration:
return BuildFunctionDeclaration(position);
case kForInStatement:
case kAsyncForInStatement:
case kIfCaseStatement:
case kPatternSwitchStatement:
case kPatternVariableDeclaration:
// These nodes are internal to the front end and
// removed by the constant evaluator.
default:
ReportUnexpectedTag("statement", tag);
UNREACHABLE();
}
return Fragment();
}
Fragment StreamingFlowGraphBuilder::BuildStatementWithBranchCoverage(
TokenPosition* position) {
TokenPosition pos = TokenPosition::kNoSource;
Fragment statement = BuildStatement(&pos);
if (position != nullptr) *position = pos;
Fragment covered_statement = flow_graph_builder_->RecordBranchCoverage(pos);
covered_statement += statement;
return covered_statement;
}
void StreamingFlowGraphBuilder::ReportUnexpectedTag(const char* variant,
Tag tag) {
if ((flow_graph_builder_ == nullptr) || (parsed_function() == nullptr)) {
KernelReaderHelper::ReportUnexpectedTag(variant, tag);
} else {
const auto& script = Script::Handle(Z, Script());
H.ReportError(script, TokenPosition::kNoSource,
"Unexpected tag %d (%s) in %s, expected %s", tag,
Reader::TagName(tag),
parsed_function()->function().ToQualifiedCString(), variant);
}
}
Tag KernelReaderHelper::ReadTag(uint8_t* payload) {
return reader_.ReadTag(payload);
}
Tag KernelReaderHelper::PeekTag(uint8_t* payload) {
return reader_.PeekTag(payload);
}
Nullability KernelReaderHelper::ReadNullability() {
return reader_.ReadNullability();
}
Variance KernelReaderHelper::ReadVariance() {
return reader_.ReadVariance();
}
void StreamingFlowGraphBuilder::loop_depth_inc() {
++flow_graph_builder_->loop_depth_;
}
void StreamingFlowGraphBuilder::loop_depth_dec() {
--flow_graph_builder_->loop_depth_;
}
void StreamingFlowGraphBuilder::catch_depth_inc() {
++flow_graph_builder_->catch_depth_;
}
void StreamingFlowGraphBuilder::catch_depth_dec() {
--flow_graph_builder_->catch_depth_;
}
void StreamingFlowGraphBuilder::try_depth_inc() {
++flow_graph_builder_->try_depth_;
}
void StreamingFlowGraphBuilder::try_depth_dec() {
--flow_graph_builder_->try_depth_;
}
intptr_t StreamingFlowGraphBuilder::block_expression_depth() {
return flow_graph_builder_->block_expression_depth_;
}
void StreamingFlowGraphBuilder::block_expression_depth_inc() {
++flow_graph_builder_->block_expression_depth_;
}
void StreamingFlowGraphBuilder::block_expression_depth_dec() {
--flow_graph_builder_->block_expression_depth_;
}
void StreamingFlowGraphBuilder::synthetic_error_handler_depth_inc() {
++synthetic_error_handler_depth_;
}
void StreamingFlowGraphBuilder::synthetic_error_handler_depth_dec() {
--synthetic_error_handler_depth_;
}
intptr_t StreamingFlowGraphBuilder::CurrentTryIndex() {
return flow_graph_builder_->CurrentTryIndex();
}
intptr_t StreamingFlowGraphBuilder::AllocateTryIndex() {
return flow_graph_builder_->AllocateTryIndex();
}
LocalVariable* StreamingFlowGraphBuilder::CurrentException() {
return flow_graph_builder_->CurrentException();
}
LocalVariable* StreamingFlowGraphBuilder::CurrentStackTrace() {
return flow_graph_builder_->CurrentStackTrace();
}
CatchBlock* StreamingFlowGraphBuilder::catch_block() {
return flow_graph_builder_->catch_block_;
}
ActiveClass* StreamingFlowGraphBuilder::active_class() {
return active_class_;
}
ScopeBuildingResult* StreamingFlowGraphBuilder::scopes() {
return flow_graph_builder_->scopes_;
}
void StreamingFlowGraphBuilder::set_scopes(ScopeBuildingResult* scope) {
flow_graph_builder_->scopes_ = scope;
}
ParsedFunction* StreamingFlowGraphBuilder::parsed_function() {
return flow_graph_builder_->parsed_function_;
}
TryFinallyBlock* StreamingFlowGraphBuilder::try_finally_block() {
return flow_graph_builder_->try_finally_block_;
}
SwitchBlock* StreamingFlowGraphBuilder::switch_block() {
return flow_graph_builder_->switch_block_;
}
BreakableBlock* StreamingFlowGraphBuilder::breakable_block() {
return flow_graph_builder_->breakable_block_;
}
Value* StreamingFlowGraphBuilder::stack() {
return flow_graph_builder_->stack_;
}
void StreamingFlowGraphBuilder::set_stack(Value* top) {
flow_graph_builder_->stack_ = top;
}
void StreamingFlowGraphBuilder::Push(Definition* definition) {
flow_graph_builder_->Push(definition);
}
Value* StreamingFlowGraphBuilder::Pop() {
return flow_graph_builder_->Pop();
}
Tag StreamingFlowGraphBuilder::PeekArgumentsFirstPositionalTag() {
// read parts of arguments, then go back to before doing so.
AlternativeReadingScope alt(&reader_);
ReadUInt(); // read number of arguments.
SkipListOfDartTypes(); // Read list of types.
// List of positional.
intptr_t list_length = ReadListLength(); // read list length.
if (list_length > 0) {
return ReadTag(); // read first tag.
}
UNREACHABLE();
return kNothing;
}
const TypeArguments& StreamingFlowGraphBuilder::PeekArgumentsInstantiatedType(
const Class& klass) {
// read parts of arguments, then go back to before doing so.
AlternativeReadingScope alt(&reader_);
ReadUInt(); // read argument count.
intptr_t list_length = ReadListLength(); // read types list length.
return T.BuildInstantiatedTypeArguments(klass, list_length); // read types.
}
intptr_t StreamingFlowGraphBuilder::PeekArgumentsCount() {
return PeekUInt();
}
TokenPosition StreamingFlowGraphBuilder::ReadPosition() {
TokenPosition position = KernelReaderHelper::ReadPosition();
if (synthetic_error_handler_depth_ > 0 && position.IsReal()) {
position = TokenPosition::Synthetic(position.Pos());
}
return position;
}
LocalVariable* StreamingFlowGraphBuilder::LookupVariable(
intptr_t kernel_offset) {
return flow_graph_builder_->LookupVariable(kernel_offset);
}
LocalVariable* StreamingFlowGraphBuilder::MakeTemporary(const char* suffix) {
return flow_graph_builder_->MakeTemporary(suffix);
}
Fragment StreamingFlowGraphBuilder::DropTemporary(LocalVariable** variable) {
return flow_graph_builder_->DropTemporary(variable);
}
Function& StreamingFlowGraphBuilder::FindMatchingFunction(
const Class& klass,
const String& name,
int type_args_len,
int argument_count,
const Array& argument_names) {
// Search the superclass chain for the selector.
ArgumentsDescriptor args_desc(
Array::Handle(Z, ArgumentsDescriptor::NewBoxed(
type_args_len, argument_count, argument_names)));
return Function::Handle(
Z, Resolver::ResolveDynamicForReceiverClassAllowPrivate(klass, name,
args_desc));
}
bool StreamingFlowGraphBuilder::NeedsDebugStepCheck(const Function& function,
TokenPosition position) {
return flow_graph_builder_->NeedsDebugStepCheck(function, position);
}
bool StreamingFlowGraphBuilder::NeedsDebugStepCheck(Value* value,
TokenPosition position) {
return flow_graph_builder_->NeedsDebugStepCheck(value, position);
}
void StreamingFlowGraphBuilder::InlineBailout(const char* reason) {
flow_graph_builder_->InlineBailout(reason);
}
Fragment StreamingFlowGraphBuilder::DebugStepCheck(TokenPosition position) {
return flow_graph_builder_->DebugStepCheck(position);
}
Fragment StreamingFlowGraphBuilder::LoadLocal(LocalVariable* variable) {
return flow_graph_builder_->LoadLocal(variable);
}
IndirectGotoInstr* StreamingFlowGraphBuilder::IndirectGoto(
intptr_t target_count) {
return flow_graph_builder_->IndirectGoto(target_count);
}
Fragment StreamingFlowGraphBuilder::Return(TokenPosition position) {
return flow_graph_builder_->Return(position,
/*omit_result_type_check=*/false);
}
Fragment StreamingFlowGraphBuilder::RethrowException(TokenPosition position,
int catch_try_index) {
return flow_graph_builder_->RethrowException(position, catch_try_index);
}
Fragment StreamingFlowGraphBuilder::ThrowNoSuchMethodError(
TokenPosition position,
const Function& target,
bool incompatible_arguments) {
return flow_graph_builder_->ThrowNoSuchMethodError(position, target,
incompatible_arguments);
}
Fragment StreamingFlowGraphBuilder::Constant(const Object& value) {
return flow_graph_builder_->Constant(value);
}
Fragment StreamingFlowGraphBuilder::IntConstant(int64_t value) {
return flow_graph_builder_->IntConstant(value);
}
Fragment StreamingFlowGraphBuilder::LoadStaticField(const Field& field,
bool calls_initializer) {
return flow_graph_builder_->LoadStaticField(field, calls_initializer);
}
Fragment StreamingFlowGraphBuilder::RedefinitionWithType(
const AbstractType& type) {
return flow_graph_builder_->RedefinitionWithType(type);
}
Fragment StreamingFlowGraphBuilder::CheckNull(TokenPosition position,
LocalVariable* receiver,
const String& function_name) {
return flow_graph_builder_->CheckNull(position, receiver, function_name);
}
Fragment StreamingFlowGraphBuilder::StaticCall(TokenPosition position,
const Function& target,
intptr_t argument_count,
ICData::RebindRule rebind_rule) {
if (!target.AreValidArgumentCounts(0, argument_count, 0, nullptr)) {
Fragment instructions;
instructions += DropArguments(argument_count, /*type_args_count=*/0);
instructions += ThrowNoSuchMethodError(position, target,
/*incompatible_arguments=*/true);
return instructions;
}
return flow_graph_builder_->StaticCall(position, target, argument_count,
rebind_rule);
}
Fragment StreamingFlowGraphBuilder::StaticCall(
TokenPosition position,
const Function& target,
intptr_t argument_count,
const Array& argument_names,
ICData::RebindRule rebind_rule,
const InferredTypeMetadata* result_type,
intptr_t type_args_count,
bool use_unchecked_entry) {
if (!target.AreValidArguments(type_args_count, argument_count, argument_names,
nullptr)) {
Fragment instructions;
instructions += DropArguments(argument_count, type_args_count);
instructions += ThrowNoSuchMethodError(position, target,
/*incompatible_arguments=*/true);
return instructions;
}
return flow_graph_builder_->StaticCall(
position, target, argument_count, argument_names, rebind_rule,
result_type, type_args_count, use_unchecked_entry);
}
Fragment StreamingFlowGraphBuilder::StaticCallMissing(
TokenPosition position,
const String& selector,
intptr_t argument_count,
InvocationMirror::Level level,
InvocationMirror::Kind kind) {
Fragment instructions;
instructions += DropArguments(argument_count, /*type_args_count=*/0);
instructions += flow_graph_builder_->ThrowNoSuchMethodError(
position, selector, level, kind);
return instructions;
}
Fragment StreamingFlowGraphBuilder::InstanceCall(
TokenPosition position,
const String& name,
Token::Kind kind,
intptr_t argument_count,
intptr_t checked_argument_count) {
const intptr_t kTypeArgsLen = 0;
return flow_graph_builder_->InstanceCall(position, name, kind, kTypeArgsLen,
argument_count, Array::null_array(),
checked_argument_count);
}
Fragment StreamingFlowGraphBuilder::InstanceCall(
TokenPosition position,
const String& name,
Token::Kind kind,
intptr_t type_args_len,
intptr_t argument_count,
const Array& argument_names,
intptr_t checked_argument_count,
const Function& interface_target,
const Function& tearoff_interface_target,
const InferredTypeMetadata* result_type,
bool use_unchecked_entry,
const CallSiteAttributesMetadata* call_site_attrs,
bool receiver_is_not_smi,
bool is_call_on_this) {
return flow_graph_builder_->InstanceCall(
position, name, kind, type_args_len, argument_count, argument_names,
checked_argument_count, interface_target, tearoff_interface_target,
result_type, use_unchecked_entry, call_site_attrs, receiver_is_not_smi,
is_call_on_this);
}
Fragment StreamingFlowGraphBuilder::ThrowException(TokenPosition position) {
return flow_graph_builder_->ThrowException(position);
}
Fragment StreamingFlowGraphBuilder::BooleanNegate() {
return flow_graph_builder_->BooleanNegate();
}
Fragment StreamingFlowGraphBuilder::TranslateInstantiatedTypeArguments(
const TypeArguments& type_arguments) {
return flow_graph_builder_->TranslateInstantiatedTypeArguments(
type_arguments);
}
Fragment StreamingFlowGraphBuilder::StrictCompare(TokenPosition position,
Token::Kind kind,
bool number_check) {
return flow_graph_builder_->StrictCompare(position, kind, number_check);
}
Fragment StreamingFlowGraphBuilder::AllocateObject(TokenPosition position,
const Class& klass,
intptr_t argument_count) {
return flow_graph_builder_->AllocateObject(position, klass, argument_count);
}
Fragment StreamingFlowGraphBuilder::AllocateContext(
const ZoneGrowableArray<const Slot*>& context_slots) {
return flow_graph_builder_->AllocateContext(context_slots);
}
Fragment StreamingFlowGraphBuilder::LoadNativeField(
const Slot& field,
InnerPointerAccess loads_inner_pointer) {
return flow_graph_builder_->LoadNativeField(field, loads_inner_pointer);
}
Fragment StreamingFlowGraphBuilder::StoreLocal(TokenPosition position,
LocalVariable* variable) {
return flow_graph_builder_->StoreLocal(position, variable);
}
Fragment StreamingFlowGraphBuilder::StoreStaticField(TokenPosition position,
const Field& field) {
return flow_graph_builder_->StoreStaticField(position, field);
}
Fragment StreamingFlowGraphBuilder::StringInterpolate(TokenPosition position) {
return flow_graph_builder_->StringInterpolate(position);
}
Fragment StreamingFlowGraphBuilder::StringInterpolateSingle(
TokenPosition position) {
return flow_graph_builder_->StringInterpolateSingle(position);
}
Fragment StreamingFlowGraphBuilder::LoadInstantiatorTypeArguments() {
return flow_graph_builder_->LoadInstantiatorTypeArguments();
}
Fragment StreamingFlowGraphBuilder::LoadFunctionTypeArguments() {
return flow_graph_builder_->LoadFunctionTypeArguments();
}
Fragment StreamingFlowGraphBuilder::InstantiateType(const AbstractType& type) {
return flow_graph_builder_->InstantiateType(type);
}
Fragment StreamingFlowGraphBuilder::CreateArray() {
return flow_graph_builder_->CreateArray();
}
Fragment StreamingFlowGraphBuilder::StoreIndexed(intptr_t class_id) {
return flow_graph_builder_->StoreIndexed(class_id);
}
Fragment StreamingFlowGraphBuilder::CheckStackOverflow(TokenPosition position) {
return flow_graph_builder_->CheckStackOverflow(
position, flow_graph_builder_->GetStackDepth(),
flow_graph_builder_->loop_depth_);
}
Fragment StreamingFlowGraphBuilder::CloneContext(
const ZoneGrowableArray<const Slot*>& context_slots) {
return flow_graph_builder_->CloneContext(context_slots);
}
Fragment StreamingFlowGraphBuilder::TranslateFinallyFinalizers(
TryFinallyBlock* outer_finally,
intptr_t target_context_depth) {
// TranslateFinallyFinalizers can move the readers offset.
// Save the current position and restore it afterwards.
AlternativeReadingScope alt(&reader_);
// Save context.
TryFinallyBlock* const saved_finally_block = B->try_finally_block_;
TryCatchBlock* const saved_try_catch_block = B->CurrentTryCatchBlock();
const intptr_t saved_context_depth = B->context_depth_;
const ProgramState state(B->breakable_block_, B->switch_block_,
B->loop_depth_, B->try_depth_, B->catch_depth_,
B->block_expression_depth_);
Fragment instructions;
// While translating the body of a finalizer we need to set the try-finally
// block which is active when translating the body.
while (B->try_finally_block_ != outer_finally) {
ASSERT(B->try_finally_block_ != nullptr);
// Adjust program context to finalizer's position.
B->try_finally_block_->state().assignTo(B);
// Potentially restore the context to what is expected for the finally
// block.
instructions += B->AdjustContextTo(B->try_finally_block_->context_depth());
// The to-be-translated finalizer has to have the correct try-index (namely
// the one outside the try-finally block).
bool changed_try_index = false;
intptr_t target_try_index = B->try_finally_block_->try_index();
while (B->CurrentTryIndex() != target_try_index) {
B->SetCurrentTryCatchBlock(B->CurrentTryCatchBlock()->outer());
changed_try_index = true;
}
if (changed_try_index) {
JoinEntryInstr* entry = BuildJoinEntry();
instructions += Goto(entry);
instructions = Fragment(instructions.entry, entry);
}
intptr_t finalizer_kernel_offset =
B->try_finally_block_->finalizer_kernel_offset();
B->try_finally_block_ = B->try_finally_block_->outer();
instructions += BuildStatementAt(finalizer_kernel_offset);
// We only need to make sure that if the finalizer ended normally, we
// continue towards the next outer try-finally.
if (!instructions.is_open()) break;
}
if (instructions.is_open() && target_context_depth != -1) {
// A target context depth of -1 indicates that the code after this
// will not care about the context chain so we can leave it any way we
// want after the last finalizer. That is used when returning.
instructions += B->AdjustContextTo(target_context_depth);
}
// Restore.
B->try_finally_block_ = saved_finally_block;
B->SetCurrentTryCatchBlock(saved_try_catch_block);
B->context_depth_ = saved_context_depth;
state.assignTo(B);
return instructions;
}
Fragment StreamingFlowGraphBuilder::BranchIfTrue(
TargetEntryInstr** then_entry,
TargetEntryInstr** otherwise_entry,
bool negate) {
return flow_graph_builder_->BranchIfTrue(then_entry, otherwise_entry, negate);
}
Fragment StreamingFlowGraphBuilder::BranchIfEqual(
TargetEntryInstr** then_entry,
TargetEntryInstr** otherwise_entry,
bool negate) {
return flow_graph_builder_->BranchIfEqual(then_entry, otherwise_entry,
negate);
}
Fragment StreamingFlowGraphBuilder::BranchIfNull(
TargetEntryInstr** then_entry,
TargetEntryInstr** otherwise_entry,
bool negate) {
return flow_graph_builder_->BranchIfNull(then_entry, otherwise_entry, negate);
}
Fragment StreamingFlowGraphBuilder::CatchBlockEntry(const Array& handler_types,
intptr_t handler_index,
bool needs_stacktrace,
bool is_synthesized) {
return B->CatchBlockEntry(handler_types, handler_index, needs_stacktrace,
is_synthesized);
}
Fragment StreamingFlowGraphBuilder::TryEntry(int try_handler_index) {
return B->TryEntry(try_handler_index);
}
Fragment StreamingFlowGraphBuilder::Drop() {
return flow_graph_builder_->Drop();
}
Fragment StreamingFlowGraphBuilder::DropArguments(intptr_t argument_count,
intptr_t type_args_count) {
Fragment instructions;
for (intptr_t i = 0; i < argument_count; i++) {
instructions += Drop();
}
if (type_args_count != 0) {
instructions += Drop();
}
return instructions;
}
Fragment StreamingFlowGraphBuilder::DropTempsPreserveTop(
intptr_t num_temps_to_drop) {
return flow_graph_builder_->DropTempsPreserveTop(num_temps_to_drop);
}
Fragment StreamingFlowGraphBuilder::MakeTemp() {
return flow_graph_builder_->MakeTemp();
}
Fragment StreamingFlowGraphBuilder::NullConstant() {
return flow_graph_builder_->NullConstant();
}
JoinEntryInstr* StreamingFlowGraphBuilder::BuildJoinEntry() {
return flow_graph_builder_->BuildJoinEntry();
}
JoinEntryInstr* StreamingFlowGraphBuilder::BuildJoinEntry(intptr_t try_index) {
return flow_graph_builder_->BuildJoinEntry(try_index);
}
Fragment StreamingFlowGraphBuilder::Goto(JoinEntryInstr* destination) {
return flow_graph_builder_->Goto(destination);
}
Fragment StreamingFlowGraphBuilder::CheckArgumentType(
LocalVariable* variable,
const AbstractType& type) {
return flow_graph_builder_->CheckAssignable(
type, variable->name(), AssertAssignableInstr::kParameterCheck);
}
Fragment StreamingFlowGraphBuilder::RecordCoverage(TokenPosition position) {
return flow_graph_builder_->RecordCoverage(position);
}
Fragment StreamingFlowGraphBuilder::EnterScope(
intptr_t kernel_offset,
const LocalScope** scope /* = nullptr */) {
return flow_graph_builder_->EnterScope(kernel_offset, scope);
}
Fragment StreamingFlowGraphBuilder::ExitScope(intptr_t kernel_offset) {
return flow_graph_builder_->ExitScope(kernel_offset);
}
TestFragment StreamingFlowGraphBuilder::TranslateConditionForControl() {
// Skip all negations and go directly to the expression.
bool negate = false;
while (PeekTag() == kNot) {
SkipBytes(1);
ReadPosition();
negate = !negate;
}
TestFragment result;
if (PeekTag() == kLogicalExpression) {
// Handle '&&' and '||' operators specially to implement short circuit
// evaluation.
SkipBytes(1); // tag.
ReadPosition();
TestFragment left = TranslateConditionForControl();
LogicalOperator op = static_cast<LogicalOperator>(ReadByte());
TestFragment right = TranslateConditionForControl();
result.entry = left.entry;
if (op == kAnd) {
left.CreateTrueSuccessor(flow_graph_builder_)->LinkTo(right.entry);
result.true_successor_addresses = right.true_successor_addresses;
result.false_successor_addresses = left.false_successor_addresses;
result.false_successor_addresses->AddArray(
*right.false_successor_addresses);
} else {
ASSERT(op == kOr);
left.CreateFalseSuccessor(flow_graph_builder_)->LinkTo(right.entry);
result.true_successor_addresses = left.true_successor_addresses;
result.true_successor_addresses->AddArray(
*right.true_successor_addresses);
result.false_successor_addresses = right.false_successor_addresses;
}
} else {
// Other expressions.
TokenPosition position = TokenPosition::kNoSource;
Fragment instructions = BuildExpression(&position); // read expression.
// Check if the top of the stack is already a StrictCompare that
// can be merged with a branch. Otherwise compare TOS with
// true value and branch on that.
BranchInstr* branch;
if (stack()->definition()->IsStrictCompare() &&
stack()->definition() == instructions.current) {
StrictCompareInstr* compare = Pop()->definition()->AsStrictCompare();
if (negate) {
compare->NegateCondition();
negate = false;
}
branch =
new (Z) BranchInstr(compare, flow_graph_builder_->GetNextDeoptId());
branch->condition()->ClearTempIndex();
ASSERT(instructions.current->previous() != nullptr);
instructions.current = instructions.current->previous();
} else {
if (NeedsDebugStepCheck(stack(), position)) {
instructions = DebugStepCheck(position) + instructions;
}
instructions += Constant(Bool::True());
Value* right_value = Pop();
Value* left_value = Pop();
StrictCompareInstr* compare = new (Z) StrictCompareInstr(
InstructionSource(), negate ? Token::kNE_STRICT : Token::kEQ_STRICT,
left_value, right_value, false,
flow_graph_builder_->GetNextDeoptId());
branch =
new (Z) BranchInstr(compare, flow_graph_builder_->GetNextDeoptId());
negate = false;
}
instructions <<= branch;
result = TestFragment(instructions.entry, branch);
}
return result.Negate(negate);
}
const TypeArguments& StreamingFlowGraphBuilder::BuildTypeArguments() {
ReadUInt(); // read arguments count.
intptr_t type_count = ReadListLength(); // read type count.
return T.BuildTypeArguments(type_count); // read types.
}
Fragment StreamingFlowGraphBuilder::BuildArguments(Array* argument_names,
intptr_t* argument_count,
intptr_t* positional_count) {
intptr_t dummy;
if (argument_count == nullptr) argument_count = &dummy;
*argument_count = ReadUInt(); // read arguments count.
// List of types.
SkipListOfDartTypes(); // read list of types.
{
AlternativeReadingScope _(&reader_);
if (positional_count == nullptr) positional_count = &dummy;
*positional_count = ReadListLength(); // read length of expression list
}
return BuildArgumentsFromActualArguments(argument_names);
}
Fragment StreamingFlowGraphBuilder::BuildArgumentsFromActualArguments(
Array* argument_names) {
Fragment instructions;
// List of positional.
intptr_t list_length = ReadListLength(); // read list length.
for (intptr_t i = 0; i < list_length; ++i) {
instructions += BuildExpression(); // read ith expression.
}
// List of named.
list_length = ReadListLength(); // read list length.
if (argument_names != nullptr && list_length > 0) {
*argument_names = Array::New(list_length, Heap::kOld);
}
for (intptr_t i = 0; i < list_length; ++i) {
String& name =
H.DartSymbolObfuscate(ReadStringReference()); // read ith name index.
instructions += BuildExpression(); // read ith expression.
if (argument_names != nullptr) {
argument_names->SetAt(i, name);
}
}
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildInvalidExpression(
TokenPosition* position) {
// The frontend will take care of emitting normal errors (like
// [NoSuchMethodError]s) and only emit [InvalidExpression]s in very special
// situations (e.g. an invalid annotation).
TokenPosition pos = ReadPosition();
if (position != nullptr) *position = pos;
const String& message = H.DartString(ReadStringReference());
Tag tag = ReadTag(); // read (first part of) expression.
if (tag == kSomething) {
SkipExpression(); // read (rest of) expression.
}
// Invalid expression message has pointer to the source code, no need to
// report it twice.
const auto& script = Script::Handle(Z, Script());
H.ReportError(script, TokenPosition::kNoSource, "%s", message.ToCString());
return Fragment();
}
Fragment StreamingFlowGraphBuilder::BuildVariableGet(TokenPosition* position) {
const TokenPosition pos = ReadPosition();
if (position != nullptr) *position = pos;
intptr_t variable_kernel_position = ReadUInt(); // read kernel position.
ReadUInt(); // read relative variable index.
SkipOptionalDartType(); // read promoted type.
return BuildVariableGetImpl(variable_kernel_position, pos);
}
Fragment StreamingFlowGraphBuilder::BuildVariableGet(uint8_t payload,
TokenPosition* position) {
const TokenPosition pos = ReadPosition();
if (position != nullptr) *position = pos;
intptr_t variable_kernel_position = ReadUInt(); // read kernel position.
return BuildVariableGetImpl(variable_kernel_position, pos);
}
Fragment StreamingFlowGraphBuilder::BuildVariableGetImpl(
intptr_t variable_kernel_position,
TokenPosition position) {
LocalVariable* variable = LookupVariable(variable_kernel_position);
if (!variable->is_late()) {
return LoadLocal(variable);
}
// Late variable, so check whether it has been initialized already.
Fragment instructions = LoadLocal(variable);
TargetEntryInstr* is_uninitialized;
TargetEntryInstr* is_initialized;
instructions += Constant(Object::sentinel());
instructions += flow_graph_builder_->BranchIfStrictEqual(&is_uninitialized,
&is_initialized);
JoinEntryInstr* join = BuildJoinEntry();
{
AlternativeReadingScope alt(&reader_, variable->late_init_offset());
const bool has_initializer = (ReadTag() != kNothing);
if (has_initializer) {
// If the variable isn't initialized, call the initializer and set it.
Fragment initialize(is_uninitialized);
initialize += BuildExpression();
if (variable->is_final()) {
// Late final variable, so check whether it has been assigned
// during initialization.
initialize += LoadLocal(variable);
TargetEntryInstr* is_uninitialized_after_init;
TargetEntryInstr* is_initialized_after_init;
initialize += Constant(Object::sentinel());
initialize += flow_graph_builder_->BranchIfStrictEqual(
&is_uninitialized_after_init, &is_initialized_after_init);
{
// The variable is uninitialized, so store the initializer result.
Fragment store_result(is_uninitialized_after_init);
store_result += StoreLocal(position, variable);
store_result += Drop();
store_result += Goto(join);
}
{
// Already initialized, so throw a LateInitializationError.
Fragment already_assigned(is_initialized_after_init);
already_assigned += flow_graph_builder_->ThrowLateInitializationError(
position, "_throwLocalAssignedDuringInitialization",
variable->name());
ASSERT(already_assigned.is_closed());
}
} else {
// Late non-final variable. Store the initializer result.
initialize += StoreLocal(position, variable);
initialize += Drop();
initialize += Goto(join);
}
} else {
// The variable has no initializer, so throw a late initialization error.
Fragment initialize(is_uninitialized);
initialize += flow_graph_builder_->ThrowLateInitializationError(
position, "_throwLocalNotInitialized", variable->name());
ASSERT(initialize.is_closed());
}
}
{
// Already initialized, so there's nothing to do.
Fragment already_initialized(is_initialized);
already_initialized += Goto(join);
}
Fragment done = Fragment(instructions.entry, join);
done += LoadLocal(variable);
return done;
}
Fragment StreamingFlowGraphBuilder::BuildVariableSet(TokenPosition* p) {
TokenPosition position = ReadPosition(); // read position.
if (p != nullptr) *p = position;
intptr_t variable_kernel_position = ReadUInt(); // read kernel position.
ReadUInt(); // read relative variable index.
return BuildVariableSetImpl(position, variable_kernel_position);
}
Fragment StreamingFlowGraphBuilder::BuildVariableSet(uint8_t payload,
TokenPosition* p) {
TokenPosition position = ReadPosition(); // read position.
if (p != nullptr) *p = position;
intptr_t variable_kernel_position = ReadUInt(); // read kernel position.
return BuildVariableSetImpl(position, variable_kernel_position);
}
Fragment StreamingFlowGraphBuilder::BuildVariableSetImpl(
TokenPosition position,
intptr_t variable_kernel_position) {
Fragment instructions = BuildExpression(); // read expression.
if (NeedsDebugStepCheck(stack(), position)) {
instructions = DebugStepCheck(position) + instructions;
}
LocalVariable* variable = LookupVariable(variable_kernel_position);
if (variable->is_late() && variable->is_final()) {
// Late final variable, so check whether it has been initialized.
LocalVariable* expr_temp = MakeTemporary();
instructions += LoadLocal(variable);
TargetEntryInstr* is_uninitialized;
TargetEntryInstr* is_initialized;
instructions += Constant(Object::sentinel());
instructions += flow_graph_builder_->BranchIfStrictEqual(&is_uninitialized,
&is_initialized);
JoinEntryInstr* join = BuildJoinEntry();
{
// The variable is uninitialized, so store the expression value.
Fragment initialize(is_uninitialized);
initialize += LoadLocal(expr_temp);
initialize += StoreLocal(position, variable);
initialize += Drop();
initialize += Goto(join);
}
{
// Already initialized, so throw a LateInitializationError.
Fragment already_initialized(is_initialized);
already_initialized += flow_graph_builder_->ThrowLateInitializationError(
position, "_throwLocalAlreadyInitialized", variable->name());
ASSERT(already_initialized.is_closed());
}
instructions = Fragment(instructions.entry, join);
} else {
instructions += StoreLocal(position, variable);
}
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildInstanceGet(TokenPosition* p) {
const intptr_t offset = ReaderOffset() - 1; // Include the tag.
ReadByte(); // read kind.
const TokenPosition position = ReadPosition(); // read position.
if (p != nullptr) *p = position;
const DirectCallMetadata direct_call =
direct_call_metadata_helper_.GetDirectTargetForPropertyGet(offset);
const InferredTypeMetadata result_type =
inferred_type_metadata_helper_.GetInferredType(offset);
Fragment instructions = BuildExpression(); // read receiver.
const String& getter_name = ReadNameAsGetterName(); // read name.
SkipDartType(); // read result_type.
const NameIndex itarget_name =
ReadInterfaceMemberNameReference(); // read interface_target_reference.
ASSERT(!H.IsRoot(itarget_name) && H.IsGetter(itarget_name));
const auto& interface_target = Function::ZoneHandle(
Z, H.LookupMethodByMember(itarget_name, H.DartGetterName(itarget_name)));
ASSERT(getter_name.ptr() == interface_target.name());
if (direct_call.check_receiver_for_null_) {
auto receiver = MakeTemporary();
instructions += CheckNull(position, receiver, getter_name);
}
if (!direct_call.target_.IsNull()) {
ASSERT(CompilerState::Current().is_aot());
instructions +=
StaticCall(position, direct_call.target_, 1, Array::null_array(),
ICData::kNoRebind, &result_type);
} else {
const intptr_t kTypeArgsLen = 0;
const intptr_t kNumArgsChecked = 1;
instructions +=
InstanceCall(position, getter_name, Token::kGET, kTypeArgsLen, 1,
Array::null_array(), kNumArgsChecked, interface_target,
Function::null_function(), &result_type);
}
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildDynamicGet(TokenPosition* p) {
const intptr_t offset = ReaderOffset() - 1; // Include the tag.
ReadByte(); // read kind.
const TokenPosition position = ReadPosition(); // read position.
if (p != nullptr) *p = position;
const DirectCallMetadata direct_call =
direct_call_metadata_helper_.GetDirectTargetForPropertyGet(offset);
const InferredTypeMetadata result_type =
inferred_type_metadata_helper_.GetInferredType(offset);
Fragment instructions = BuildExpression(); // read receiver.
const String& getter_name = ReadNameAsGetterName(); // read name.
const auto& mangled_name = String::ZoneHandle(
Z, Function::CreateDynamicInvocationForwarderName(getter_name));
const Function* direct_call_target = &direct_call.target_;
if (!direct_call_target->IsNull()) {
direct_call_target = &Function::ZoneHandle(
direct_call.target_.GetDynamicInvocationForwarder(mangled_name));
}
if (direct_call.check_receiver_for_null_) {
auto receiver = MakeTemporary();
instructions += CheckNull(position, receiver, getter_name);
}
if (!direct_call_target->IsNull()) {
ASSERT(CompilerState::Current().is_aot());
instructions +=
StaticCall(position, *direct_call_target, 1, Array::null_array(),
ICData::kNoRebind, &result_type);
} else {
const intptr_t kTypeArgsLen = 0;
const intptr_t kNumArgsChecked = 1;
instructions += InstanceCall(position, mangled_name, Token::kGET,
kTypeArgsLen, 1, Array::null_array(),
kNumArgsChecked, Function::null_function(),
Function::null_function(), &result_type);
}
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildInstanceTearOff(TokenPosition* p) {
const intptr_t offset = ReaderOffset() - 1; // Include the tag.
ReadByte(); // read kind.
const TokenPosition position = ReadPosition(); // read position.
if (p != nullptr) *p = position;
const DirectCallMetadata direct_call =
direct_call_metadata_helper_.GetDirectTargetForPropertyGet(offset);
const InferredTypeMetadata result_type =
inferred_type_metadata_helper_.GetInferredType(offset);
Fragment instructions = BuildExpression(); // read receiver.
const String& getter_name = ReadNameAsGetterName(); // read name.
SkipDartType(); // read result_type.
const NameIndex itarget_name =
ReadInterfaceMemberNameReference(); // read interface_target_reference.
ASSERT(!H.IsRoot(itarget_name) && H.IsMethod(itarget_name));
const auto& tearoff_interface_target = Function::ZoneHandle(
Z, H.LookupMethodByMember(itarget_name, H.DartMethodName(itarget_name)));
if (direct_call.check_receiver_for_null_) {
const auto receiver = MakeTemporary();
instructions += CheckNull(position, receiver, getter_name);
}
if (!direct_call.target_.IsNull()) {
ASSERT(CompilerState::Current().is_aot());
instructions +=
StaticCall(position, direct_call.target_, 1, Array::null_array(),
ICData::kNoRebind, &result_type);
} else {
const intptr_t kTypeArgsLen = 0;
const intptr_t kNumArgsChecked = 1;
instructions += InstanceCall(position, getter_name, Token::kGET,
kTypeArgsLen, 1, Array::null_array(),
kNumArgsChecked, Function::null_function(),
tearoff_interface_target, &result_type);
}
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildInstanceSet(TokenPosition* p) {
const intptr_t offset = ReaderOffset() - 1; // Include the tag.
ReadByte(); // read kind.
const DirectCallMetadata direct_call =
direct_call_metadata_helper_.GetDirectTargetForPropertySet(offset);
const CallSiteAttributesMetadata call_site_attributes =
call_site_attributes_metadata_helper_.GetCallSiteAttributes(offset);
const InferredTypeMetadata inferred_type =
inferred_type_metadata_helper_.GetInferredType(offset);
// True if callee can skip argument type checks.
bool is_unchecked_call = inferred_type.IsSkipCheck();
if (call_site_attributes.receiver_type != nullptr &&
call_site_attributes.receiver_type->HasTypeClass() &&
!Class::Handle(call_site_attributes.receiver_type->type_class())
.IsGeneric()) {
is_unchecked_call = true;
}
Fragment instructions(MakeTemp());
LocalVariable* variable = MakeTemporary();
const TokenPosition position = ReadPosition(); // read position.
if (p != nullptr) *p = position;
const bool is_call_on_this = PeekTag() == kThisExpression;
if (is_call_on_this) {
is_unchecked_call = true;
}
instructions += BuildExpression(); // read receiver.
LocalVariable* receiver = nullptr;
if (direct_call.check_receiver_for_null_) {
receiver = MakeTemporary();
}
const String& setter_name = ReadNameAsSetterName(); // read name.
instructions += BuildExpression(); // read value.
instructions += StoreLocal(TokenPosition::kNoSource, variable);
const NameIndex itarget_name =
ReadInterfaceMemberNameReference(); // read interface_target_reference.
ASSERT(!H.IsRoot(itarget_name));
const auto& interface_target = Function::ZoneHandle(
Z, H.LookupMethodByMember(itarget_name, H.DartSetterName(itarget_name)));
ASSERT(setter_name.ptr() == interface_target.name());
if (direct_call.check_receiver_for_null_) {
instructions += CheckNull(position, receiver, setter_name);
}
if (!direct_call.target_.IsNull()) {
ASSERT(CompilerState::Current().is_aot());
instructions +=
StaticCall(position, direct_call.target_, 2, Array::null_array(),
ICData::kNoRebind, /*result_type=*/nullptr,
/*type_args_count=*/0,
/*use_unchecked_entry=*/is_unchecked_call);
} else {
const intptr_t kTypeArgsLen = 0;
const intptr_t kNumArgsChecked = 1;
instructions += InstanceCall(
position, setter_name, Token::kSET, kTypeArgsLen, 2,
Array::null_array(), kNumArgsChecked, interface_target,
Function::null_function(),
/*result_type=*/nullptr,
/*use_unchecked_entry=*/is_unchecked_call, &call_site_attributes,
/*receiver_not_smi=*/false, is_call_on_this);
}
instructions += Drop(); // Drop result of the setter invocation.
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildDynamicSet(TokenPosition* p) {
const intptr_t offset = ReaderOffset() - 1; // Include the tag.
ReadByte(); // read kind.
const DirectCallMetadata direct_call =
direct_call_metadata_helper_.GetDirectTargetForPropertySet(offset);
const InferredTypeMetadata inferred_type =
inferred_type_metadata_helper_.GetInferredType(offset);
// True if callee can skip argument type checks.
const bool is_unchecked_call = inferred_type.IsSkipCheck();
Fragment instructions(MakeTemp());
LocalVariable* variable = MakeTemporary();
const TokenPosition position = ReadPosition(); // read position.
if (p != nullptr) *p = position;
instructions += BuildExpression(); // read receiver.
LocalVariable* receiver = nullptr;
if (direct_call.check_receiver_for_null_) {
receiver = MakeTemporary();
}
const String& setter_name = ReadNameAsSetterName(); // read name.
instructions += BuildExpression(); // read value.
instructions += StoreLocal(TokenPosition::kNoSource, variable);
if (direct_call.check_receiver_for_null_) {
instructions += CheckNull(position, receiver, setter_name);
}
const Function* direct_call_target = &direct_call.target_;
const auto& mangled_name = String::ZoneHandle(
Z, Function::CreateDynamicInvocationForwarderName(setter_name));
if (!direct_call_target->IsNull()) {
direct_call_target = &Function::ZoneHandle(
direct_call.target_.GetDynamicInvocationForwarder(mangled_name));
}
if (!direct_call_target->IsNull()) {
ASSERT(CompilerState::Current().is_aot());
instructions +=
StaticCall(position, *direct_call_target, 2, Array::null_array(),
ICData::kNoRebind, /*result_type=*/nullptr,
/*type_args_count=*/0,
/*use_unchecked_entry=*/is_unchecked_call);
} else {
const intptr_t kTypeArgsLen = 0;
const intptr_t kNumArgsChecked = 1;
instructions += InstanceCall(
position, mangled_name, Token::kSET, kTypeArgsLen, 2,
Array::null_array(), kNumArgsChecked, Function::null_function(),
Function::null_function(),
/*result_type=*/nullptr,
/*use_unchecked_entry=*/is_unchecked_call, /*call_site_attrs=*/nullptr);
}
instructions += Drop(); // Drop result of the setter invocation.
return instructions;
}
static Function& GetNoSuchMethodOrDie(Thread* thread,
Zone* zone,
const Class& klass) {
Function& nsm_function = Function::Handle(zone);
Class& iterate_klass = Class::Handle(zone, klass.ptr());
if (!iterate_klass.IsNull() &&
iterate_klass.EnsureIsFinalized(thread) == Error::null()) {
while (!iterate_klass.IsNull()) {
nsm_function = Resolver::ResolveDynamicFunction(zone, iterate_klass,
Symbols::NoSuchMethod());
if (!nsm_function.IsNull() && nsm_function.NumParameters() == 2 &&
nsm_function.NumTypeParameters() == 0) {
break;
}
iterate_klass = iterate_klass.SuperClass();
}
}
// We are guaranteed to find noSuchMethod of class Object.
ASSERT(!nsm_function.IsNull());
return nsm_function;
}
// Note, that this will always mark `super` flag to true.
Fragment StreamingFlowGraphBuilder::BuildAllocateInvocationMirrorCall(
TokenPosition position,
const String& name,
intptr_t num_type_arguments,
intptr_t num_arguments,
const Array& argument_names,
LocalVariable* actuals_array,
Fragment build_rest_of_actuals) {
Fragment instructions;
// Populate array containing the actual arguments. Just add [this] here.
instructions += LoadLocal(actuals_array); // array
instructions += IntConstant(num_type_arguments == 0 ? 0 : 1); // index
instructions += LoadLocal(parsed_function()->receiver_var()); // receiver
instructions += StoreIndexed(kArrayCid);
instructions += build_rest_of_actuals;
// First argument is receiver.
instructions += LoadLocal(parsed_function()->receiver_var());
// Push the arguments for allocating the invocation mirror:
// - the name.
instructions += Constant(String::ZoneHandle(Z, name.ptr()));
// - the arguments descriptor.
const Array& args_descriptor =
Array::Handle(Z, ArgumentsDescriptor::NewBoxed(
num_type_arguments, num_arguments, argument_names));
instructions += Constant(Array::ZoneHandle(Z, args_descriptor.ptr()));
// - an array containing the actual arguments.
instructions += LoadLocal(actuals_array);
// - [true] indicating this is a `super` NoSuchMethod.
instructions += Constant(Bool::True());
const Class& mirror_class =
Class::Handle(Z, Library::LookupCoreClass(Symbols::InvocationMirror()));
ASSERT(!mirror_class.IsNull());
const auto& error = mirror_class.EnsureIsFinalized(thread());
ASSERT(error == Error::null());
const Function& allocation_function = Function::ZoneHandle(
Z, mirror_class.LookupStaticFunction(
Library::PrivateCoreLibName(Symbols::AllocateInvocationMirror())));
ASSERT(!allocation_function.IsNull());
instructions += StaticCall(position, allocation_function,
/* argument_count = */ 4, ICData::kStatic);
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildSuperPropertyGet(TokenPosition* p) {
const intptr_t offset = ReaderOffset() - 1; // Include the tag.
const TokenPosition position = ReadPosition(); // read position.
if (p != nullptr) *p = position;
const InferredTypeMetadata result_type =
inferred_type_metadata_helper_.GetInferredType(offset);
Class& klass = GetSuperOrDie();
StringIndex name_index = ReadStringReference(); // read name index.
NameIndex library_reference =
((H.StringSize(name_index) >= 1) && H.CharacterAt(name_index, 0) == '_')
? ReadCanonicalNameReference() // read library index.
: NameIndex();
const String& getter_name = H.DartGetterName(library_reference, name_index);
const String& method_name = H.DartMethodName(library_reference, name_index);
SkipInterfaceMemberNameReference(); // skip target_reference.
// Search the superclass chain for the selector looking for either getter or
// method.
Function& function = Function::Handle(Z);
if (!klass.IsNull() && klass.EnsureIsFinalized(thread()) == Error::null()) {
while (!klass.IsNull()) {
function = Resolver::ResolveDynamicFunction(Z, klass, method_name);
if (!function.IsNull()) {
Function& target =
Function::ZoneHandle(Z, function.ImplicitClosureFunction());
ASSERT(!target.IsNull());
// Generate inline code for allocation closure object
// which captures `this`.
return B->BuildImplicitClosureCreation(position, target);
}
function = Resolver::ResolveDynamicFunction(Z, klass, getter_name);
if (!function.IsNull()) break;
klass = klass.SuperClass();
}
}
Fragment instructions;
if (klass.IsNull()) {
instructions +=
Constant(TypeArguments::ZoneHandle(Z, TypeArguments::null()));
instructions += IntConstant(1); // array size
instructions += CreateArray();
LocalVariable* actuals_array = MakeTemporary();
Class& parent_klass = GetSuperOrDie();
instructions += BuildAllocateInvocationMirrorCall(
position, getter_name,
/* num_type_arguments = */ 0,
/* num_arguments = */ 1,
/* argument_names = */ Object::empty_array(), actuals_array,
/* build_rest_of_actuals = */ Fragment());
Function& nsm_function = GetNoSuchMethodOrDie(thread(), Z, parent_klass);
instructions +=
StaticCall(position, Function::ZoneHandle(Z, nsm_function.ptr()),
/* argument_count = */ 2, ICData::kNSMDispatch);
instructions += DropTempsPreserveTop(1); // Drop array
} else {
ASSERT(!klass.IsNull());
ASSERT(!function.IsNull());
instructions += LoadLocal(parsed_function()->receiver_var());
instructions +=
StaticCall(position, Function::ZoneHandle(Z, function.ptr()),
/* argument_count = */ 1, Array::null_array(),
ICData::kSuper, &result_type);
}
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildSuperPropertySet(TokenPosition* p) {
const TokenPosition position = ReadPosition(); // read position.
if (p != nullptr) *p = position;
Class& klass = GetSuperOrDie();
const String& setter_name = ReadNameAsSetterName(); // read name.
Function& function = Function::Handle(Z);
if (klass.EnsureIsFinalized(thread()) == Error::null()) {
function = Resolver::ResolveDynamicFunction(Z, klass, setter_name);
}
Fragment instructions(MakeTemp());
LocalVariable* value = MakeTemporary(); // this holds RHS value
if (function.IsNull()) {
instructions +=
Constant(TypeArguments::ZoneHandle(Z, TypeArguments::null()));
instructions += IntConstant(2); // array size
instructions += CreateArray();
LocalVariable* actuals_array = MakeTemporary();
Fragment build_rest_of_actuals;
build_rest_of_actuals += LoadLocal(actuals_array); // array
build_rest_of_actuals += IntConstant(1); // index
build_rest_of_actuals += BuildExpression(); // value.
build_rest_of_actuals += StoreLocal(position, value);
build_rest_of_actuals += StoreIndexed(kArrayCid);
instructions += BuildAllocateInvocationMirrorCall(
position, setter_name, /* num_type_arguments = */ 0,
/* num_arguments = */ 2,
/* argument_names = */ Object::empty_array(), actuals_array,
build_rest_of_actuals);
SkipInterfaceMemberNameReference(); // skip target_reference.
Function& nsm_function = GetNoSuchMethodOrDie(thread(), Z, klass);
instructions +=
StaticCall(position, Function::ZoneHandle(Z, nsm_function.ptr()),
/* argument_count = */ 2, ICData::kNSMDispatch);
instructions += Drop(); // Drop result of NoSuchMethod invocation
instructions += Drop(); // Drop array
} else {
// receiver
instructions += LoadLocal(parsed_function()->receiver_var());
instructions += BuildExpression(); // read value.
instructions += StoreLocal(position, value);
SkipInterfaceMemberNameReference(); // skip target_reference.
instructions += StaticCall(
position, Function::ZoneHandle(Z, function.ptr()),
/* argument_count = */ 2, Array::null_array(), ICData::kSuper,
/*result_type=*/nullptr, /*type_args_len=*/0,
/*use_unchecked_entry=*/true);
instructions += Drop(); // Drop result of the setter invocation.
}
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildStaticGet(TokenPosition* p) {
ASSERT(Error::Handle(Z, H.thread()->sticky_error()).IsNull());
const intptr_t offset = ReaderOffset() - 1; // Include the tag.
TokenPosition position = ReadPosition(); // read position.
if (p != nullptr) *p = position;
const InferredTypeMetadata result_type =
inferred_type_metadata_helper_.GetInferredType(offset);
NameIndex target = ReadCanonicalNameReference(); // read target_reference.
ASSERT(H.IsGetter(target));
const Field& field = Field::ZoneHandle(
Z, H.LookupFieldByKernelGetterOrSetter(target, /*required=*/false));
if (!field.IsNull()) {
if (field.is_const()) {
// Since the CFE inlines all references to const variables and fields,
// it never emits a StaticGet of a const field.
// This situation only arises because of the static const fields in
// the ClassID class, which are generated internally in the VM
// during loading. See also Class::InjectCIDFields.
ASSERT(Class::Handle(field.Owner()).library() ==
Library::InternalLibrary() &&
Class::Handle(field.Owner()).Name() == Symbols::ClassID().ptr());
return Constant(Instance::ZoneHandle(
Z, Instance::RawCast(field.StaticConstFieldValue())));
} else if (field.is_final() && field.has_trivial_initializer()) {
// Final fields with trivial initializers are effectively constant.
return Constant(Instance::ZoneHandle(
Z, Instance::RawCast(field.StaticConstFieldValue())));
} else {
const Class& owner = Class::Handle(Z, field.Owner());
const String& getter_name = H.DartGetterName(target);
const Function& getter =
Function::ZoneHandle(Z, owner.LookupStaticFunction(getter_name));
if (!getter.IsNull() && field.NeedsGetter()) {
return StaticCall(position, getter, 0, Array::null_array(),
ICData::kStatic, &result_type);
} else {
if (result_type.IsConstant()) {
return Constant(result_type.constant_value);
}
return LoadStaticField(field, /*calls_initializer=*/false);
}
}
}
const Function& function = Function::ZoneHandle(
Z, H.LookupStaticMethodByKernelProcedure(target, /*required=*/false));
if (!function.IsNull()) {
if (H.IsGetter(target)) {
return StaticCall(position, function, 0, Array::null_array(),
ICData::kStatic, &result_type);
} else if (H.IsMethod(target)) {
const auto& closure_function =
Function::Handle(Z, function.ImplicitClosureFunction());
const auto& static_closure =
Instance::Handle(Z, closure_function.ImplicitStaticClosure());
return Constant(Instance::ZoneHandle(Z, H.Canonicalize(static_closure)));
} else {
UNIMPLEMENTED();
}
}
return StaticCallMissing(
position, H.DartSymbolPlain(H.CanonicalNameString(target)),
/* argument_count */ 0,
H.IsLibrary(H.EnclosingName(target)) ? InvocationMirror::Level::kTopLevel
: InvocationMirror::Level::kStatic,
InvocationMirror::Kind::kGetter);
}
Fragment StreamingFlowGraphBuilder::BuildStaticSet(TokenPosition* p) {
TokenPosition position = ReadPosition(); // read position.
if (p != nullptr) *p = position;
NameIndex target = ReadCanonicalNameReference(); // read target_reference.
ASSERT(H.IsSetter(target));
// Evaluate the expression on the right hand side.
Fragment instructions = BuildExpression(); // read expression.
// Look up the target as a setter first and, if not present, as a field
// second. This order is needed to avoid looking up a final field as the
// target.
const Function& function = Function::ZoneHandle(
Z, H.LookupStaticMethodByKernelProcedure(target, /*required=*/false));
if (!function.IsNull()) {
LocalVariable* variable = MakeTemporary();
// Prepare argument.
instructions += LoadLocal(variable);
// Invoke the setter function.
instructions += StaticCall(position, function, 1, ICData::kStatic);
// Drop the unused result & leave the stored value on the stack.
return instructions + Drop();
}
const Field& field = Field::ZoneHandle(
Z, H.LookupFieldByKernelGetterOrSetter(target, /*required=*/false));
if (!field.IsNull()) {
if (NeedsDebugStepCheck(stack(), position)) {
instructions = DebugStepCheck(position) + instructions;
}
LocalVariable* variable = MakeTemporary();
instructions += LoadLocal(variable);
instructions += StoreStaticField(position, field);
return instructions;
}
instructions += StaticCallMissing(
position, H.DartSymbolPlain(H.CanonicalNameString(target)),
/* argument_count */ 1,
H.IsLibrary(H.EnclosingName(target)) ? InvocationMirror::Level::kTopLevel
: InvocationMirror::Level::kStatic,
InvocationMirror::Kind::kSetter);
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildMethodInvocation(TokenPosition* p,
bool is_dynamic) {
const intptr_t offset = ReaderOffset() - 1; // Include the tag.
ReadByte(); // read kind.
// read flags.
const uint8_t flags = ReadFlags();
bool is_invariant = false;
bool is_implicit_call = false;
if (is_dynamic) {
is_implicit_call = (flags & kDynamicInvocationFlagImplicitCall) != 0;
} else {
is_invariant = (flags & kInstanceInvocationFlagInvariant) != 0;
}
const TokenPosition position = ReadPosition(); // read position.
if (p != nullptr) *p = position;
const DirectCallMetadata direct_call =
direct_call_metadata_helper_.GetDirectTargetForMethodInvocation(offset);
const InferredTypeMetadata result_type =
inferred_type_metadata_helper_.GetInferredType(offset);
const CallSiteAttributesMetadata call_site_attributes =
call_site_attributes_metadata_helper_.GetCallSiteAttributes(offset);
const Tag receiver_tag = PeekTag(); // peek tag for receiver.
bool is_unchecked_call = is_invariant || result_type.IsSkipCheck();
if (!is_dynamic && (call_site_attributes.receiver_type != nullptr) &&
call_site_attributes.receiver_type->HasTypeClass() &&
!call_site_attributes.receiver_type->IsDynamicType() &&
!Class::Handle(call_site_attributes.receiver_type->type_class())
.IsGeneric()) {
is_unchecked_call = true;
}
Fragment instructions;
intptr_t type_args_len = 0;
{
AlternativeReadingScope alt(&reader_);
SkipExpression(); // skip receiver
SkipName(); // skip method name
ReadUInt(); // read argument count.
intptr_t list_length = ReadListLength(); // read types list length.
if (list_length > 0) {
const TypeArguments& type_arguments =
T.BuildTypeArguments(list_length); // read types.
instructions += TranslateInstantiatedTypeArguments(type_arguments);
}
type_args_len = list_length;
}
// Take note of whether the invocation is against the receiver of the current
// function: in this case, we may skip some type checks in the callee.
const bool is_call_on_this = (PeekTag() == kThisExpression) && !is_dynamic;
if (is_call_on_this) {
is_unchecked_call = true;
}
instructions += BuildExpression(); // read receiver.
const String& name = ReadNameAsMethodName(); // read name.
const Token::Kind token_kind =
MethodTokenRecognizer::RecognizeTokenKind(name);
// Detect comparison with null.
if ((token_kind == Token::kEQ || token_kind == Token::kNE) &&
PeekArgumentsCount() == 1 &&
(receiver_tag == kNullLiteral ||
PeekArgumentsFirstPositionalTag() == kNullLiteral)) {
ASSERT(type_args_len == 0);
// "==" or "!=" with null on either side.
instructions +=
BuildArguments(nullptr /* named */, nullptr /* arg count */,
nullptr /* positional arg count */); // read arguments.
SkipInterfaceMemberNameReference(); // read interface_target_reference.
Token::Kind strict_cmp_kind =
token_kind == Token::kEQ ? Token::kEQ_STRICT : Token::kNE_STRICT;
return instructions +
StrictCompare(position, strict_cmp_kind, /*number_check = */ true);
}
LocalVariable* receiver_temp = nullptr;
if (direct_call.check_receiver_for_null_) {
receiver_temp = MakeTemporary();
}
intptr_t argument_count;
intptr_t positional_argument_count;
Array& argument_names = Array::ZoneHandle(Z);
instructions +=
BuildArguments(&argument_names, &argument_count,
&positional_argument_count); // read arguments.
++argument_count; // include receiver
intptr_t checked_argument_count = 1;
// If we have a special operation (e.g. +/-/==) we mark both arguments as
// to be checked.
if (token_kind != Token::kILLEGAL) {
ASSERT(argument_count <= 2);
checked_argument_count = argument_count;
}
if (!is_dynamic) {
SkipDartType(); // read function_type.
}
const Function* interface_target = &Function::null_function();
// read interface_target_reference.
const NameIndex itarget_name =
is_dynamic ? NameIndex() : ReadInterfaceMemberNameReference();
// TODO(dartbug.com/34497): Once front-end desugars calls via
// fields/getters, filtering of field and getter interface targets here
// can be turned into assertions.
if (!H.IsRoot(itarget_name) && !H.IsGetter(itarget_name)) {
interface_target = &Function::ZoneHandle(
Z, H.LookupMethodByMember(itarget_name,
H.DartProcedureName(itarget_name)));
ASSERT(name.ptr() == interface_target->name());
ASSERT(!interface_target->IsGetterFunction());
}
if (direct_call.check_receiver_for_null_) {
instructions += CheckNull(position, receiver_temp, name);
}
const String* mangled_name = &name;
// Do not mangle ==:
// * operator == takes an Object so its either not checked or checked
// at the entry because the parameter is marked covariant, neither of
// those cases require a dynamic invocation forwarder.
const Function* direct_call_target = &direct_call.target_;
if (is_dynamic && (name.ptr() != Symbols::EqualOperator().ptr())) {
mangled_name = &String::ZoneHandle(
Z, Function::CreateDynamicInvocationForwarderName(name));
if (!direct_call_target->IsNull()) {
direct_call_target = &Function::ZoneHandle(
direct_call_target->GetDynamicInvocationForwarder(*mangled_name));
}
if (is_implicit_call) {
ASSERT(mangled_name->ptr() == Symbols::DynamicCall().ptr());
mangled_name = &Symbols::DynamicImplicitCall();
}
}
if (!direct_call_target->IsNull()) {
// Even if TFA infers a concrete receiver type, the static type of the
// call-site may still be dynamic and we need to call the dynamic invocation
// forwarder to ensure type-checks are performed.
ASSERT(CompilerState::Current().is_aot());
instructions +=
StaticCall(position, *direct_call_target, argument_count,
argument_names, ICData::kNoRebind, &result_type,
type_args_len, /*use_unchecked_entry=*/is_unchecked_call);
} else {
instructions += InstanceCall(
position, *mangled_name, token_kind, type_args_len, argument_count,
argument_names, checked_argument_count, *interface_target,
Function::null_function(), &result_type,
/*use_unchecked_entry=*/is_unchecked_call, &call_site_attributes,
result_type.ReceiverNotInt(), is_call_on_this);
}
// Later optimization passes assume that result of a x.[]=(...) call is not
// used. We must guarantee this invariant because violation will lead to an
// illegal IL once we replace x.[]=(...) with a sequence that does not
// actually produce any value. See http://dartbug.com/29135 for more details.
if (name.ptr() == Symbols::AssignIndexToken().ptr()) {
instructions += Drop();
instructions += NullConstant();
}
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildLocalFunctionInvocation(
TokenPosition* p) {
const intptr_t offset = ReaderOffset() - 1; // Include the tag.
const TokenPosition position = ReadPosition();
if (p != nullptr) *p = position;
const InferredTypeMetadata result_type =
inferred_type_metadata_helper_.GetInferredType(offset);
// read variable kernel position.
const intptr_t variable_kernel_position = ReadUInt();
ReadUInt(); // read relative variable index.
LocalVariable* variable = LookupVariable(variable_kernel_position);
ASSERT(!variable->is_late());
auto& target_function = Function::ZoneHandle(Z);
{
AlternativeReadingScope alt(
&reader_, variable_kernel_position - data_program_offset_);
SkipVariableDeclaration();
// FunctionNode follows the variable declaration.
const intptr_t function_node_kernel_offset = ReaderOffset();
target_function = ClosureFunctionsCache::LookupClosureFunction(
Function::Handle(Z,
parsed_function()->function().GetOutermostFunction()),
function_node_kernel_offset);
RELEASE_ASSERT(!target_function.IsNull());
}
Fragment instructions;
// Type arguments.
intptr_t type_args_len = 0;
{
AlternativeReadingScope alt(&reader_);
ReadUInt(); // read argument count.
intptr_t list_length = ReadListLength(); // read types list length.
if (list_length > 0) {
const TypeArguments& type_arguments =
T.BuildTypeArguments(list_length); // read types.
instructions += TranslateInstantiatedTypeArguments(type_arguments);
}
type_args_len = list_length;
}
// Receiver (closure).
instructions += LoadLocal(variable);
intptr_t argument_count;
intptr_t positional_argument_count;
Array& argument_names = Array::ZoneHandle(Z);
instructions +=
BuildArguments(&argument_names, &argument_count,
&positional_argument_count); // read arguments.
++argument_count; // include receiver
SkipDartType(); // read function_type.
// Lookup the function in the closure.
instructions += LoadLocal(variable);
if (!FLAG_precompiled_mode) {
instructions += LoadNativeField(Slot::Closure_function());
}
if (parsed_function()->function().is_debuggable()) {
ASSERT(!parsed_function()->function().is_native());
instructions += DebugStepCheck(position);
}
instructions += B->ClosureCall(target_function, position, type_args_len,
argument_count, argument_names, &result_type);
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildFunctionInvocation(TokenPosition* p) {
const intptr_t offset = ReaderOffset() - 1; // Include the tag.
const FunctionAccessKind function_access_kind =
static_cast<FunctionAccessKind>(ReadByte()); // read kind.
const TokenPosition position = ReadPosition(); // read position.
if (p != nullptr) *p = position;
const DirectCallMetadata direct_call =
direct_call_metadata_helper_.GetDirectTargetForFunctionInvocation(offset);
const InferredTypeMetadata result_type =
inferred_type_metadata_helper_.GetInferredType(offset);
RELEASE_ASSERT((function_access_kind == FunctionAccessKind::kFunction) ||
(function_access_kind == FunctionAccessKind::kFunctionType));
const bool is_unchecked_closure_call =
(function_access_kind == FunctionAccessKind::kFunctionType);
Fragment instructions;
instructions += BuildExpression(); // read receiver.
LocalVariable* receiver_temp = MakeTemporary();
// Type arguments.
intptr_t type_args_len = 0;
{
AlternativeReadingScope alt(&reader_);
ReadUInt(); // read argument count.
intptr_t list_length = ReadListLength(); // read types list length.
if (list_length > 0) {
const TypeArguments& type_arguments =
T.BuildTypeArguments(list_length); // read types.
instructions += TranslateInstantiatedTypeArguments(type_arguments);
}
type_args_len = list_length;
}
// Receiver (closure).
instructions += LoadLocal(receiver_temp);
intptr_t argument_count;
intptr_t positional_argument_count;
Array& argument_names = Array::ZoneHandle(Z);
instructions +=
BuildArguments(&argument_names, &argument_count,
&positional_argument_count); // read arguments.
++argument_count; // include receiver
SkipDartType(); // read function_type.
if (is_unchecked_closure_call) {
instructions += CheckNull(position, receiver_temp, Symbols::call());
// Lookup the function in the closure.
instructions += LoadLocal(receiver_temp);
if (!FLAG_precompiled_mode) {
instructions += LoadNativeField(Slot::Closure_function());
}
if (parsed_function()->function().is_debuggable()) {
ASSERT(!parsed_function()->function().is_native());
instructions += DebugStepCheck(position);
}
instructions +=
B->ClosureCall(direct_call.target_, position, type_args_len,
argument_count, argument_names, &result_type);
} else {
instructions += InstanceCall(
position, Symbols::DynamicCall(), Token::kILLEGAL, type_args_len,
argument_count, argument_names, 1, Function::null_function(),
Function::null_function(), &result_type,
/*use_unchecked_entry=*/false, /*call_site_attrs=*/nullptr,
result_type.ReceiverNotInt());
}
instructions += DropTempsPreserveTop(1);
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildEqualsCall(TokenPosition* p) {
const intptr_t offset = ReaderOffset() - 1; // Include the tag.
const TokenPosition position = ReadPosition(); // read position.
if (p != nullptr) *p = position;
const DirectCallMetadata direct_call =
direct_call_metadata_helper_.GetDirectTargetForMethodInvocation(offset);
ASSERT(!direct_call.check_receiver_for_null_);
const InferredTypeMetadata result_type =
inferred_type_metadata_helper_.GetInferredType(offset);
const CallSiteAttributesMetadata call_site_attributes =
call_site_attributes_metadata_helper_.GetCallSiteAttributes(offset);
Fragment instructions;
instructions += BuildExpression(); // read left.
instructions += BuildExpression(); // read right.
SkipDartType(); // read function_type.
const NameIndex itarget_name =
ReadInterfaceMemberNameReference(); // read interface_target_reference.
const auto& interface_target = Function::ZoneHandle(
Z,
H.LookupMethodByMember(itarget_name, H.DartProcedureName(itarget_name)));
ASSERT(interface_target.name() == Symbols::EqualOperator().ptr());
const intptr_t kTypeArgsLen = 0;
const intptr_t kNumArgs = 2;
const intptr_t kNumCheckedArgs = 2;
if (!direct_call.target_.IsNull()) {
ASSERT(CompilerState::Current().is_aot());
instructions +=
StaticCall(position, direct_call.target_, kNumArgs, Array::null_array(),
ICData::kNoRebind, &result_type, kTypeArgsLen,
/*use_unchecked_entry=*/true);
} else {
instructions += InstanceCall(
position, Symbols::EqualOperator(), Token::kEQ, kTypeArgsLen, kNumArgs,
Array::null_array(), kNumCheckedArgs, interface_target,
Function::null_function(), &result_type,
/*use_unchecked_entry=*/true, &call_site_attributes,
result_type.ReceiverNotInt());
}
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildEqualsNull(TokenPosition* p) {
const TokenPosition position = ReadPosition(); // read position.
if (p != nullptr) *p = position;
Fragment instructions;
instructions += BuildExpression(); // read expression.
instructions += NullConstant();
if (parsed_function()->function().is_debuggable()) {
instructions += DebugStepCheck(position);
}
instructions +=
StrictCompare(position, Token::kEQ_STRICT, /*number_check=*/false);
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildSuperMethodInvocation(
TokenPosition* p) {
const intptr_t offset = ReaderOffset() - 1; // Include the tag.
const TokenPosition position = ReadPosition(); // read position.
if (p != nullptr) *p = position;
const InferredTypeMetadata result_type =
inferred_type_metadata_helper_.GetInferredType(offset);
intptr_t type_args_len = 0;
{
AlternativeReadingScope alt(&reader_);
SkipName(); // skip method name
ReadUInt(); // read argument count.
type_args_len = ReadListLength(); // read types list length.
}
Class& klass = GetSuperOrDie();
// Search the superclass chain for the selector.
const String& method_name = ReadNameAsMethodName(); // read name.
// Figure out selector signature.
intptr_t argument_count;
Array& argument_names = Array::Handle(Z);
{
AlternativeReadingScope alt(&reader_);
argument_count = ReadUInt();
SkipListOfDartTypes();
SkipListOfExpressions();
intptr_t named_list_length = ReadListLength();
argument_names = Array::New(named_list_length, H.allocation_space());
for (intptr_t i = 0; i < named_list_length; i++) {
const String& arg_name = H.DartSymbolObfuscate(ReadStringReference());
argument_names.SetAt(i, arg_name);
SkipExpression();
}
}
Function& function = FindMatchingFunction(
klass, method_name, type_args_len,
argument_count + 1 /* account for 'this' */, argument_names);
if (function.IsNull()) {
ReadUInt(); // argument count
intptr_t type_list_length = ReadListLength();
Fragment instructions;
instructions +=
Constant(TypeArguments::ZoneHandle(Z, TypeArguments::null()));
instructions += IntConstant(argument_count + 1 /* this */ +
(type_list_length == 0 ? 0 : 1)); // array size
instructions += CreateArray();
LocalVariable* actuals_array = MakeTemporary();
// Call allocationInvocationMirror to get instance of Invocation.
Fragment build_rest_of_actuals;
intptr_t actuals_array_index = 0;
if (type_list_length > 0) {
const TypeArguments& type_arguments =
T.BuildTypeArguments(type_list_length);
build_rest_of_actuals += LoadLocal(actuals_array);
build_rest_of_actuals += IntConstant(actuals_array_index);
build_rest_of_actuals +=
TranslateInstantiatedTypeArguments(type_arguments);
build_rest_of_actuals += StoreIndexed(kArrayCid);
++actuals_array_index;
}
++actuals_array_index; // account for 'this'.
// Read arguments
intptr_t list_length = ReadListLength();
intptr_t i = 0;
while (i < list_length) {
build_rest_of_actuals += LoadLocal(actuals_array); // array
build_rest_of_actuals += IntConstant(actuals_array_index + i); // index
build_rest_of_actuals += BuildExpression(); // value.
build_rest_of_actuals += StoreIndexed(kArrayCid);
++i;
}
// Read named arguments
intptr_t named_list_length = ReadListLength();
if (named_list_length > 0) {
ASSERT(argument_count == list_length + named_list_length);
while ((i - list_length) < named_list_length) {
SkipStringReference();
build_rest_of_actuals += LoadLocal(actuals_array); // array
build_rest_of_actuals += IntConstant(i + actuals_array_index); // index
build_rest_of_actuals += BuildExpression(); // value.
build_rest_of_actuals += StoreIndexed(kArrayCid);
++i;
}
}
instructions += BuildAllocateInvocationMirrorCall(
position, method_name, type_list_length,
/* num_arguments = */ argument_count + 1, argument_names, actuals_array,
build_rest_of_actuals);
SkipInterfaceMemberNameReference(); // skip target_reference.
Function& nsm_function = GetNoSuchMethodOrDie(thread(), Z, klass);
instructions += StaticCall(TokenPosition::kNoSource,
Function::ZoneHandle(Z, nsm_function.ptr()),
/* argument_count = */ 2, ICData::kNSMDispatch);
instructions += DropTempsPreserveTop(1); // Drop actuals_array temp.
return instructions;
} else {
Fragment instructions;
{
AlternativeReadingScope alt(&reader_);
ReadUInt(); // read argument count.
intptr_t list_length = ReadListLength(); // read types list length.
if (list_length > 0) {
const TypeArguments& type_arguments =
T.BuildTypeArguments(list_length); // read types.
instructions += TranslateInstantiatedTypeArguments(type_arguments);
}
}
// receiver
instructions += LoadLocal(parsed_function()->receiver_var());
Array& argument_names = Array::ZoneHandle(Z);
intptr_t argument_count;
instructions += BuildArguments(
&argument_names, &argument_count,
/* positional_argument_count = */ nullptr); // read arguments.
++argument_count; // include receiver
SkipInterfaceMemberNameReference(); // interfaceTargetReference
return instructions +
StaticCall(position, Function::ZoneHandle(Z, function.ptr()),
argument_count, argument_names, ICData::kSuper,
&result_type, type_args_len,
/*use_unchecked_entry_point=*/true);
}
}
Fragment StreamingFlowGraphBuilder::BuildStaticInvocation(TokenPosition* p) {
const intptr_t offset = ReaderOffset() - 1; // Include the tag.
TokenPosition position = ReadPosition(); // read position.
if (p != nullptr) *p = position;
const InferredTypeMetadata result_type =
inferred_type_metadata_helper_.GetInferredType(offset);
NameIndex procedure_reference =
ReadCanonicalNameReference(); // read procedure reference.
intptr_t argument_count = PeekArgumentsCount();
const Function& target =
Function::ZoneHandle(Z, H.LookupStaticMethodByKernelProcedure(
procedure_reference, /*required=*/false));
if (target.IsNull()) {
Fragment instructions;
Array& argument_names = Array::ZoneHandle(Z);
instructions +=
BuildArguments(&argument_names, nullptr /* arg count */,
nullptr /* positional arg count */); // read arguments.
instructions += StaticCallMissing(
position, H.DartSymbolPlain(H.CanonicalNameString(procedure_reference)),
argument_count,
H.IsLibrary(H.EnclosingName(procedure_reference))
? InvocationMirror::Level::kTopLevel
: InvocationMirror::Level::kStatic,
InvocationMirror::Kind::kMethod);
return instructions;
}
const Class& klass = Class::ZoneHandle(Z, target.Owner());
if (target.IsGenerativeConstructor() || target.IsFactory()) {
// The VM requires a TypeArguments object as first parameter for
// every factory constructor.
++argument_count;
}
if (target.IsCachableIdempotent()) {
return BuildCachableIdempotentCall(position, target);
}
const auto recognized_kind = target.recognized_kind();
switch (recognized_kind) {
case MethodRecognizer::kNativeEffect:
return BuildNativeEffect();
case MethodRecognizer::kReachabilityFence:
return BuildReachabilityFence();
case MethodRecognizer::kFfiCall:
return BuildFfiCall();
case MethodRecognizer::kFfiNativeCallbackFunction:
return BuildFfiNativeCallbackFunction(
FfiCallbackKind::kIsolateLocalStaticCallback);
case MethodRecognizer::kFfiNativeAddressOf:
return BuildFfiNativeAddressOf();
case MethodRecognizer::kFfiNativeIsolateLocalCallbackFunction:
return BuildFfiNativeCallbackFunction(
FfiCallbackKind::kIsolateLocalClosureCallback);
case MethodRecognizer::kFfiNativeIsolateGroupSharedCallbackFunction:
return BuildFfiNativeCallbackFunction(
FfiCallbackKind::kIsolateGroupSharedStaticCallback);
case MethodRecognizer::kFfiNativeIsolateGroupSharedClosureFunction:
return BuildFfiNativeCallbackFunction(
FfiCallbackKind::kIsolateGroupSharedClosureCallback);
case MethodRecognizer::kFfiNativeAsyncCallbackFunction:
return BuildFfiNativeCallbackFunction(FfiCallbackKind::kAsyncCallback);
case MethodRecognizer::kFfiLoadAbiSpecificInt:
return BuildLoadStoreAbiSpecificInt(/*is_store=*/false,
/*at_index=*/false);
case MethodRecognizer::kFfiLoadAbiSpecificIntAtIndex:
return BuildLoadStoreAbiSpecificInt(/*is_store=*/false,
/*at_index=*/true);
case MethodRecognizer::kFfiStoreAbiSpecificInt:
return BuildLoadStoreAbiSpecificInt(/*is_store=*/true,
/*at_index=*/false);
case MethodRecognizer::kFfiStoreAbiSpecificIntAtIndex:
return BuildLoadStoreAbiSpecificInt(/*is_store=*/true, /*at_index=*/true);
default:
break;
}
Fragment instructions;
LocalVariable* instance_variable = nullptr;
const bool special_case_unchecked_cast =
klass.IsTopLevel() && (klass.library() == Library::InternalLibrary()) &&
(target.name() == Symbols::UnsafeCast().ptr());
const bool special_case_identical =
klass.IsTopLevel() && (klass.library() == Library::CoreLibrary()) &&
(target.name() == Symbols::Identical().ptr());
const bool special_case =
special_case_identical || special_case_unchecked_cast;
// If we cross the Kernel -> VM core library boundary, a [StaticInvocation]
// can appear, but the thing we're calling is not a static method, but a
// factory constructor.
// The `H.LookupStaticmethodByKernelProcedure` will potentially resolve to the
// forwarded constructor.
// In that case we'll make an instance and pass it as first argument.
//
// TODO(27590): Get rid of this after we're using core libraries compiled
// into Kernel.
intptr_t type_args_len = 0;
if (target.IsGenerativeConstructor()) {
if (klass.NumTypeArguments() > 0) {
const TypeArguments& type_arguments =
PeekArgumentsInstantiatedType(klass);
instructions += TranslateInstantiatedTypeArguments(type_arguments);
instructions += AllocateObject(position, klass, 1);
} else {
instructions += AllocateObject(position, klass, 0);
}
instance_variable = MakeTemporary();
instructions += LoadLocal(instance_variable);
} else if (target.IsFactory()) {
// The VM requires currently a TypeArguments object as first parameter for
// every factory constructor :-/ !
//
// TODO(27590): Get rid of this after we're using core libraries compiled
// into Kernel.
const TypeArguments& type_arguments = PeekArgumentsInstantiatedType(klass);
instructions += TranslateInstantiatedTypeArguments(type_arguments);
} else if (!special_case) {
AlternativeReadingScope alt(&reader_);
ReadUInt(); // read argument count.
intptr_t list_length = ReadListLength(); // read types list length.
if (list_length > 0) {
const TypeArguments& type_arguments =
T.BuildTypeArguments(list_length); // read types.
instructions += TranslateInstantiatedTypeArguments(type_arguments);
}
type_args_len = list_length;
}
Array& argument_names = Array::ZoneHandle(Z);
instructions +=
BuildArguments(&argument_names, nullptr /* arg count */,
nullptr /* positional arg count */); // read arguments.
ASSERT(!special_case ||
target.AreValidArguments(type_args_len, argument_count, argument_names,
nullptr));
// Special case identical(x, y) call.
// TODO(27590) consider moving this into the inliner and force inline it
// there.
if (special_case_identical) {
ASSERT(argument_count == 2);
instructions +=
StrictCompare(position, Token::kEQ_STRICT, /*number_check=*/true);
} else if (special_case_unchecked_cast) {
// Simply do nothing: the result value is already pushed on the stack.
} else {
instructions += StaticCall(position, target, argument_count, argument_names,
ICData::kStatic, &result_type, type_args_len);
if (target.IsGenerativeConstructor()) {
// Drop the result of the constructor call and leave [instance_variable]
// on top-of-stack.
instructions += Drop();
}
// After reaching debugger(), we automatically do one single-step.
// Ensure this doesn't cause us to exit the current scope.
if (recognized_kind == MethodRecognizer::kDebugger) {
instructions += DebugStepCheck(position);
}
}
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildConstructorInvocation(
TokenPosition* p) {
TokenPosition position = ReadPosition(); // read position.
if (p != nullptr) *p = position;
NameIndex kernel_name =
ReadCanonicalNameReference(); // read target_reference.
Class& klass = Class::ZoneHandle(
Z, H.LookupClassByKernelClass(H.EnclosingName(kernel_name),
/*required=*/false));
Fragment instructions;
if (klass.IsNull()) {
Array& argument_names = Array::ZoneHandle(Z);
intptr_t argument_count;
instructions += BuildArguments(
&argument_names, &argument_count,
/* positional_argument_count = */ nullptr); // read arguments.
instructions += StaticCallMissing(
position, H.DartSymbolPlain(H.CanonicalNameString(kernel_name)),
argument_count, InvocationMirror::Level::kConstructor,
InvocationMirror::Kind::kMethod);
return instructions;
}
const auto& error = klass.EnsureIsFinalized(H.thread());
ASSERT(error == Error::null());
if (klass.NumTypeArguments() > 0) {
if (!klass.IsGeneric()) {
const TypeArguments& type_arguments = TypeArguments::ZoneHandle(
Z, klass.GetDeclarationInstanceTypeArguments());
instructions += Constant(type_arguments);
} else {
const TypeArguments& type_arguments =
PeekArgumentsInstantiatedType(klass);
instructions += TranslateInstantiatedTypeArguments(type_arguments);
}
instructions += AllocateObject(position, klass, 1);
} else {
instructions += AllocateObject(position, klass, 0);
}
LocalVariable* variable = MakeTemporary();
instructions += LoadLocal(variable);
Array& argument_names = Array::ZoneHandle(Z);
intptr_t argument_count;
instructions += BuildArguments(
&argument_names, &argument_count,
/* positional_argument_count = */ nullptr); // read arguments.
const Function& target =
Function::ZoneHandle(Z, H.LookupConstructorByKernelConstructor(
klass, kernel_name, /*required=*/false));
++argument_count;
if (target.IsNull()) {
instructions += StaticCallMissing(
position, H.DartSymbolPlain(H.CanonicalNameString(kernel_name)),
argument_count, InvocationMirror::Level::kConstructor,
InvocationMirror::Kind::kMethod);
} else {
instructions += StaticCall(position, target, argument_count, argument_names,
ICData::kStatic, /* result_type = */ nullptr);
}
return instructions + Drop();
}
Fragment StreamingFlowGraphBuilder::BuildNot(TokenPosition* p) {
TokenPosition position = ReadPosition();
if (p != nullptr) *p = position;
TokenPosition operand_position = TokenPosition::kNoSource;
Fragment instructions =
BuildExpression(&operand_position); // read expression.
instructions += BooleanNegate();
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildNullCheck(TokenPosition* p) {
const TokenPosition position = ReadPosition(); // read position.
if (p != nullptr) *p = position;
TokenPosition operand_position = TokenPosition::kNoSource;
Fragment instructions = BuildExpression(&operand_position);
LocalVariable* expr_temp = MakeTemporary();
instructions += CheckNull(position, expr_temp, String::null_string());
return instructions;
}
// Translate the logical expression (lhs && rhs or lhs || rhs) in a context
// where a value is required.
//
// Translation accumulates short-circuit exits from logical
// subexpressions in the side_exits. These exits are expected to store
// true and false into :expr_temp.
//
// The result of evaluating the last
// expression in chain would be stored in :expr_temp directly to avoid
// generating graph like:
//
// if (v) :expr_temp = true; else :expr_temp = false;
//
// Outer negations are stripped and instead negation is passed down via
// negated parameter.
Fragment StreamingFlowGraphBuilder::TranslateLogicalExpressionForValue(
bool negated,
TestFragment* side_exits) {
TestFragment left = TranslateConditionForControl().Negate(negated);
LogicalOperator op = static_cast<LogicalOperator>(ReadByte());
if (negated) {
op = (op == kAnd) ? kOr : kAnd;
}
// Short circuit the control flow after the left hand side condition.
if (op == kAnd) {
side_exits->false_successor_addresses->AddArray(
*left.false_successor_addresses);
} else {
side_exits->true_successor_addresses->AddArray(
*left.true_successor_addresses);
}
// Skip negations of the right hand side.
while (PeekTag() == kNot) {
SkipBytes(1);
ReadPosition();
negated = !negated;
}
Fragment right_value(op == kAnd
? left.CreateTrueSuccessor(flow_graph_builder_)
: left.CreateFalseSuccessor(flow_graph_builder_));
if (PeekTag() == kLogicalExpression) {
SkipBytes(1);
ReadPosition();
// Handle nested logical expressions specially to avoid materializing
// intermediate boolean values.
right_value += TranslateLogicalExpressionForValue(negated, side_exits);
} else {
// Arbitrary expression on the right hand side. Translate it for value.
TokenPosition position = TokenPosition::kNoSource;
right_value += BuildExpression(&position); // read expression.
if (negated) {
right_value += BooleanNegate();
}
right_value += StoreLocal(TokenPosition::kNoSource,
parsed_function()->expression_temp_var());
right_value += Drop();
}
return Fragment(left.entry, right_value.current);
}
Fragment StreamingFlowGraphBuilder::BuildLogicalExpression(TokenPosition* p) {
TokenPosition position = ReadPosition();
if (p != nullptr) *p = position;
TestFragment exits;
exits.true_successor_addresses = new TestFragment::SuccessorAddressArray(2);
exits.false_successor_addresses = new TestFragment::SuccessorAddressArray(2);
JoinEntryInstr* join = BuildJoinEntry();
Fragment instructions =
TranslateLogicalExpressionForValue(/*negated=*/false, &exits);
instructions += Goto(join);
// Generate :expr_temp = true if needed and connect it to true side-exits.
if (!exits.true_successor_addresses->is_empty()) {
Fragment constant_fragment(exits.CreateTrueSuccessor(flow_graph_builder_));
constant_fragment += Constant(Bool::Get(true));
constant_fragment += StoreLocal(TokenPosition::kNoSource,
parsed_function()->expression_temp_var());
constant_fragment += Drop();
constant_fragment += Goto(join);
}
// Generate :expr_temp = false if needed and connect it to false side-exits.
if (!exits.false_successor_addresses->is_empty()) {
Fragment constant_fragment(exits.CreateFalseSuccessor(flow_graph_builder_));
constant_fragment += Constant(Bool::Get(false));
constant_fragment += StoreLocal(TokenPosition::kNoSource,
parsed_function()->expression_temp_var());
constant_fragment += Drop();
constant_fragment += Goto(join);
}
return Fragment(instructions.entry, join) +
LoadLocal(parsed_function()->expression_temp_var());
}
Fragment StreamingFlowGraphBuilder::BuildConditionalExpression(
TokenPosition* p) {
TokenPosition position = ReadPosition();
if (p != nullptr) *p = position;
TestFragment condition = TranslateConditionForControl(); // read condition.
Value* top = stack();
Fragment then_fragment(condition.CreateTrueSuccessor(flow_graph_builder_));
then_fragment += BuildExpression(); // read then.
then_fragment += StoreLocal(TokenPosition::kNoSource,
parsed_function()->expression_temp_var());
then_fragment += Drop();
ASSERT(stack() == top);
Fragment otherwise_fragment(
condition.CreateFalseSuccessor(flow_graph_builder_));
otherwise_fragment += BuildExpression(); // read otherwise.
otherwise_fragment += StoreLocal(TokenPosition::kNoSource,
parsed_function()->expression_temp_var());
otherwise_fragment += Drop();
ASSERT(stack() == top);
JoinEntryInstr* join = BuildJoinEntry();
then_fragment += Goto(join);
otherwise_fragment += Goto(join);
SkipOptionalDartType(); // read unused static type.
return Fragment(condition.entry, join) +
LoadLocal(parsed_function()->expression_temp_var());
}
void StreamingFlowGraphBuilder::FlattenStringConcatenation(
PiecesCollector* collector) {
const auto length = ReadListLength();
for (intptr_t i = 0; i < length; ++i) {
const auto offset = reader_.offset();
switch (PeekTag()) {
case kStringLiteral: {
ReadTag();
ReadPosition();
const String& s = H.DartSymbolPlain(ReadStringReference());
// Skip empty strings.
if (!s.Equals("")) {
collector->Add({-1, &s});
}
break;
}
case kStringConcatenation: {
// Flatten by hoisting nested expressions up into the outer concat.
ReadTag();
ReadPosition();
FlattenStringConcatenation(collector);
break;
}
default: {
collector->Add({offset, nullptr});
SkipExpression();
}
}
}
}
Fragment StreamingFlowGraphBuilder::BuildStringConcatenation(TokenPosition* p) {
TokenPosition position = ReadPosition();
if (p != nullptr) {
*p = position;
}
// Collect and flatten all pieces of this and any nested StringConcats.
// The result is a single sequence of pieces, potentially flattened to
// a single String.
// The collector will hold concatenated strings and Reader offsets of
// non-string pieces.
PiecesCollector collector(Z, &H);
FlattenStringConcatenation(&collector);
collector.FlushRun();
if (collector.pieces.length() == 1) {
// No need to Interp. a single string, so return string as a Constant:
if (collector.pieces[0].literal != nullptr) {
return Constant(*collector.pieces[0].literal);
}
// A single non-string piece is handle by StringInterpolateSingle:
AlternativeReadingScope scope(&reader_, collector.pieces[0].offset);
Fragment instructions;
instructions += BuildExpression();
instructions += StringInterpolateSingle(position);
return instructions;
}
Fragment instructions;
instructions += Constant(TypeArguments::ZoneHandle(Z));
instructions += IntConstant(collector.pieces.length());
instructions += CreateArray();
LocalVariable* array = MakeTemporary();
for (intptr_t i = 0; i < collector.pieces.length(); ++i) {
// All pieces are now either a concat'd string or an expression we can
// read at a given offset.
if (collector.pieces[i].literal != nullptr) {
instructions += LoadLocal(array);
instructions += IntConstant(i);
instructions += Constant(*collector.pieces[i].literal);
} else {
AlternativeReadingScope scope(&reader_, collector.pieces[i].offset);
instructions += LoadLocal(array);
instructions += IntConstant(i);
instructions += BuildExpression();
}
instructions += StoreIndexed(kArrayCid);
}
instructions += StringInterpolate(position);
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildIsTest(TokenPosition position,
const AbstractType& type) {
Fragment instructions;
// The VM does not like an instanceOf call with a dynamic type. We need to
// special case this situation by detecting a top type.
if (type.IsTopTypeForInstanceOf()) {
// Evaluate the expression on the left but ignore its result.
instructions += Drop();
// Let condition be always true.
instructions += Constant(Bool::True());
} else {
// See if simple instanceOf is applicable.
if (dart::SimpleInstanceOfType(type)) {
instructions += Constant(type);
instructions += InstanceCall(
position, Library::PrivateCoreLibName(Symbols::_simpleInstanceOf()),
Token::kIS, 2, 2); // 2 checked arguments.
return instructions;
}
if (type.IsRecordType()) {
instructions += BuildRecordIsTest(position, RecordType::Cast(type));
return instructions;
}
if (!type.IsInstantiated(kCurrentClass)) {
instructions += LoadInstantiatorTypeArguments();
} else {
instructions += NullConstant();
}
if (!type.IsInstantiated(kFunctions)) {
instructions += LoadFunctionTypeArguments();
} else {
instructions += NullConstant();
}
instructions += Constant(type);
instructions += InstanceCall(
position, Library::PrivateCoreLibName(Symbols::_instanceOf()),
Token::kIS, 4);
}
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildRecordIsTest(TokenPosition position,
const RecordType& type) {
// Type of a record instance depends on the runtime types of all
// its fields, so subtype test cache cannot be used for testing
// record types and runtime call is used.
// So it is more efficient to test each record field separately
// without going to runtime.
Fragment instructions;
JoinEntryInstr* is_true = BuildJoinEntry();
JoinEntryInstr* is_false = BuildJoinEntry();
LocalVariable* instance = MakeTemporary();
// Test if instance is null.
if (type.IsNullable()) {
TargetEntryInstr* is_null;
TargetEntryInstr* not_null;
instructions += LoadLocal(instance);
instructions += BranchIfNull(&is_null, &not_null);
Fragment(is_null) + Goto(is_true);
instructions.current = not_null;
}
// Test if instance is record.
{
TargetEntryInstr* is_record;
TargetEntryInstr* not_record;
instructions += LoadLocal(instance);
instructions += B->LoadClassId();
instructions += IntConstant(kRecordCid);
instructions += BranchIfEqual(&is_record, &not_record);
Fragment(not_record) + Goto(is_false);
instructions.current = is_record;
}
// Test record shape.
{
TargetEntryInstr* same_shape;
TargetEntryInstr* different_shape;
instructions += LoadLocal(instance);
instructions += LoadNativeField(Slot::Record_shape());
instructions += IntConstant(type.shape().AsInt());
instructions += BranchIfEqual(&same_shape, &different_shape);
Fragment(different_shape) + Goto(is_false);
instructions.current = same_shape;
}
// Test each record field
for (intptr_t i = 0, n = type.NumFields(); i < n; ++i) {
TargetEntryInstr* success;
TargetEntryInstr* failure;
instructions += LoadLocal(instance);
instructions += LoadNativeField(Slot::GetRecordFieldSlot(
H.thread(), compiler::target::Record::field_offset(i)));
instructions +=
BuildIsTest(position, AbstractType::ZoneHandle(Z, type.FieldTypeAt(i)));
instructions += Constant(Bool::True());
instructions += BranchIfEqual(&success, &failure);
Fragment(failure) + Goto(is_false);
instructions.current = success;
}
instructions += Goto(is_true);
JoinEntryInstr* join = BuildJoinEntry();
LocalVariable* expr_temp = parsed_function()->expression_temp_var();
instructions.current = is_true;
instructions += Constant(Bool::True());
instructions += StoreLocal(TokenPosition::kNoSource, expr_temp);
instructions += Drop();
instructions += Goto(join);
instructions.current = is_false;
instructions += Constant(Bool::False());
instructions += StoreLocal(TokenPosition::kNoSource, expr_temp);
instructions += Drop();
instructions += Goto(join);
instructions.current = join;
instructions += Drop(); // Instance.
instructions += LoadLocal(expr_temp);
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildIsExpression(TokenPosition* p) {
TokenPosition position = ReadPosition(); // read position.
if (p != nullptr) *p = position;
Fragment instructions = BuildExpression(); // read operand.
const AbstractType& type = T.BuildType(); // read type.
instructions += BuildIsTest(position, type);
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildAsExpression(TokenPosition* p) {
TokenPosition position = ReadPosition(); // read position.
if (p != nullptr) *p = position;
const uint8_t flags = ReadFlags(); // read flags.
const bool is_unchecked_cast = (flags & kAsExpressionFlagUnchecked) != 0;
const bool is_type_error = (flags & kAsExpressionFlagTypeError) != 0;
Fragment instructions = BuildExpression(); // read operand.
const AbstractType& type = T.BuildType(); // read type.
if (is_unchecked_cast ||
(type.IsInstantiated() && type.IsTopTypeForSubtyping())) {
// We already evaluated the operand on the left and just leave it there as
// the result of unchecked cast or `obj as dynamic` expression.
} else {
// We do not care whether the 'as' cast as implicitly added by the frontend
// or explicitly written by the user, in both cases we use an assert
// assignable.
instructions += B->AssertAssignableLoadTypeArguments(
position, type,
is_type_error ? Symbols::Empty() : Symbols::InTypeCast(),
AssertAssignableInstr::kInsertedByFrontend);
}
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildTypeLiteral(TokenPosition* position) {
TokenPosition pos = ReadPosition(); // read position.
if (position != nullptr) *position = pos;
const AbstractType& type = T.BuildType(); // read type.
Fragment instructions;
if (type.IsInstantiated()) {
instructions += Constant(type);
} else {
if (!type.IsInstantiated(kCurrentClass)) {
instructions += LoadInstantiatorTypeArguments();
} else {
instructions += NullConstant();
}
if (!type.IsInstantiated(kFunctions)) {
instructions += LoadFunctionTypeArguments();
} else {
instructions += NullConstant();
}
instructions += InstantiateType(type);
}
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildThisExpression(
TokenPosition* position) {
ReadPosition(); // ignore file offset.
if (position != nullptr) *position = TokenPosition::kNoSource;
return LoadLocal(parsed_function()->receiver_var());
}
Fragment StreamingFlowGraphBuilder::BuildRethrow(TokenPosition* p) {
TokenPosition position = ReadPosition(); // read position.
if (p != nullptr) *p = position;
Fragment instructions = DebugStepCheck(position);
instructions += LoadLocal(catch_block()->exception_var());
instructions += LoadLocal(catch_block()->stack_trace_var());
instructions += RethrowException(position, catch_block()->catch_try_index());
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildThrow(TokenPosition* p) {
TokenPosition position = ReadPosition(); // read position.
if (p != nullptr) *p = position;
Fragment instructions;
const uint8_t flags = ReadByte();
const bool is_synthetic_error_handler = (flags & kThrowForErrorHandling) != 0;
if (is_synthetic_error_handler) {
synthetic_error_handler_depth_inc();
}
instructions += BuildExpression(); // read expression.
if (NeedsDebugStepCheck(stack(), position)) {
instructions = DebugStepCheck(position) + instructions;
}
instructions += ThrowException(position);
ASSERT(instructions.is_closed());
if (is_synthetic_error_handler) {
synthetic_error_handler_depth_dec();
}
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildListLiteral(TokenPosition* p) {
TokenPosition position = ReadPosition(); // read position.
if (p != nullptr) *p = position;
const TypeArguments& type_arguments = T.BuildTypeArguments(1); // read type.
intptr_t length = ReadListLength(); // read list length.
// Note: there will be "length" expressions.
// The type argument for the factory call.
Fragment instructions = TranslateInstantiatedTypeArguments(type_arguments);
// List literals up to 8 elements are lowered in the front-end
// (pkg/vm/lib/transformations/list_literals_lowering.dart)
const intptr_t kNumSpecializedListLiteralConstructors = 8;
ASSERT(length > kNumSpecializedListLiteralConstructors);
LocalVariable* type = MakeTemporary();
instructions += LoadLocal(type);
// The type arguments for CreateArray.
instructions += LoadLocal(type);
instructions += IntConstant(length);
instructions += CreateArray();
LocalVariable* array = MakeTemporary();
for (intptr_t i = 0; i < length; ++i) {
instructions += LoadLocal(array);
instructions += IntConstant(i);
instructions += BuildExpression(); // read ith expression.
instructions += StoreIndexed(kArrayCid);
}
const Class& growable_list_class =
Class::Handle(Z, Library::LookupCoreClass(Symbols::_GrowableList()));
ASSERT(!growable_list_class.IsNull());
const Function& factory_method =
Function::ZoneHandle(Z, growable_list_class.LookupFunctionAllowPrivate(
Symbols::_GrowableListLiteralFactory()));
ASSERT(!factory_method.IsNull());
instructions += StaticCall(position, factory_method, 2, ICData::kStatic);
instructions += DropTempsPreserveTop(1); // Instantiated type_arguments.
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildMapLiteral(TokenPosition* p) {
TokenPosition position = ReadPosition(); // read position.
if (p != nullptr) *p = position;
const TypeArguments& type_arguments =
T.BuildTypeArguments(2); // read key_type and value_type.
// The type argument for the factory call `new Map<K, V>._fromLiteral(List)`.
Fragment instructions = TranslateInstantiatedTypeArguments(type_arguments);
intptr_t length = ReadListLength(); // read list length.
// Note: there will be "length" map entries (i.e. key and value expressions).
if (length == 0) {
instructions += Constant(Object::empty_array());
} else {
// The type arguments for `new List<X>(int len)`.
instructions += Constant(TypeArguments::ZoneHandle(Z));
// We generate a list of tuples, i.e. [key1, value1, ..., keyN, valueN].
instructions += IntConstant(2 * length);
instructions += CreateArray();
LocalVariable* array = MakeTemporary();
for (intptr_t i = 0; i < length; ++i) {
instructions += LoadLocal(array);
instructions += IntConstant(2 * i);
instructions += BuildExpression(); // read ith key.
instructions += StoreIndexed(kArrayCid);
instructions += LoadLocal(array);
instructions += IntConstant(2 * i + 1);
instructions += BuildExpression(); // read ith value.
instructions += StoreIndexed(kArrayCid);
}
}
const Class& map_class =
Class::Handle(Z, Library::LookupCoreClass(Symbols::Map()));
Function& factory_method = Function::ZoneHandle(Z);
if (map_class.EnsureIsFinalized(H.thread()) == Error::null()) {
factory_method = map_class.LookupFactory(
Library::PrivateCoreLibName(Symbols::MapLiteralFactory()));
}
return instructions +
StaticCall(position, factory_method, 2, ICData::kStatic);
}
Fragment StreamingFlowGraphBuilder::BuildRecordLiteral(TokenPosition* p) {
const TokenPosition position = ReadPosition(); // read position.
if (p != nullptr) *p = position;
// Figure out record shape.
const intptr_t positional_count = ReadListLength();
intptr_t named_count = -1;
const Array* field_names = &Object::empty_array();
{
AlternativeReadingScope alt(&reader_);
for (intptr_t i = 0; i < positional_count; ++i) {
SkipExpression();
}
named_count = ReadListLength();
if (named_count > 0) {
Array& names = Array::ZoneHandle(Z, Array::New(named_count, Heap::kOld));
for (intptr_t i = 0; i < named_count; ++i) {
String& name =
H.DartSymbolObfuscate(ReadStringReference()); // read ith name.
SkipExpression(); // read ith expression.
names.SetAt(i, name);
}
names.MakeImmutable();
field_names = &names;
}
}
const intptr_t num_fields = positional_count + named_count;
const RecordShape shape =
RecordShape::Register(thread(), num_fields, *field_names);
Fragment instructions;
if (num_fields == 2 ||
(num_fields == 3 && AllocateSmallRecordABI::kValue2Reg != kNoRegister)) {
// Generate specialized allocation for a small number of fields.
for (intptr_t i = 0; i < positional_count; ++i) {
instructions += BuildExpression(); // read ith expression.
}
ReadListLength(); // read list length.
for (intptr_t i = 0; i < named_count; ++i) {
SkipStringReference(); // read ith name.
instructions += BuildExpression(); // read ith expression.
}
SkipDartType(); // read recordType.
instructions += B->AllocateSmallRecord(position, shape);
return instructions;
}
instructions += B->AllocateRecord(position, shape);
LocalVariable* record = MakeTemporary();
// List of positional.
intptr_t pos = 0;
for (intptr_t i = 0; i < positional_count; ++i, ++pos) {
instructions += LoadLocal(record);
instructions += BuildExpression(); // read ith expression.
instructions += B->StoreNativeField(
Slot::GetRecordFieldSlot(thread(),
compiler::target::Record::field_offset(pos)),
StoreFieldInstr::Kind::kInitializing);
}
// List of named.
ReadListLength(); // read list length.
for (intptr_t i = 0; i < named_count; ++i, ++pos) {
SkipStringReference(); // read ith name.
instructions += LoadLocal(record);
instructions += BuildExpression(); // read ith expression.
instructions += B->StoreNativeField(
Slot::GetRecordFieldSlot(thread(),
compiler::target::Record::field_offset(pos)),
StoreFieldInstr::Kind::kInitializing);
}
SkipDartType(); // read recordType.
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildRecordFieldGet(TokenPosition* p,
bool is_named) {
const TokenPosition position = ReadPosition(); // read position.
if (p != nullptr) *p = position;
Fragment instructions = BuildExpression(); // read receiver.
const RecordType& record_type =
RecordType::Cast(T.BuildType()); // read recordType.
intptr_t field_index = -1;
const Array& field_names =
Array::Handle(Z, record_type.GetFieldNames(H.thread()));
const intptr_t num_positional_fields =
record_type.NumFields() - field_names.Length();
if (is_named) {
const String& field_name = H.DartSymbolObfuscate(ReadStringReference());
for (intptr_t i = 0, n = field_names.Length(); i < n; ++i) {
if (field_names.At(i) == field_name.ptr()) {
field_index = i;
break;
}
}
ASSERT(field_index >= 0 && field_index < field_names.Length());
field_index += num_positional_fields;
} else {
field_index = ReadUInt();
ASSERT(field_index < num_positional_fields);
}
instructions += B->LoadNativeField(Slot::GetRecordFieldSlot(
thread(), compiler::target::Record::field_offset(field_index)));
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildFunctionExpression() {
const intptr_t offset = ReaderOffset() - 1; // Include the tag.
ReadPosition(); // read position.
return BuildFunctionNode(offset);
}
Fragment StreamingFlowGraphBuilder::BuildLet(TokenPosition* p) {
const intptr_t offset = ReaderOffset() - 1; // Include the tag.
const TokenPosition position = ReadPosition(); // read position.
if (p != nullptr) *p = position;
Fragment instructions;
instructions += EnterScope(offset);
instructions += BuildVariableDeclaration(nullptr); // read variable.
instructions += BuildExpression(); // read body.
instructions += ExitScope(offset);
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildBlockExpression() {
block_expression_depth_inc();
const intptr_t offset = ReaderOffset() - 1; // Include the tag.
Fragment instructions;
instructions += EnterScope(offset);
ReadPosition(); // ignore file offset.
const intptr_t list_length = ReadListLength(); // read number of statements.
for (intptr_t i = 0; i < list_length; ++i) {
instructions += BuildStatement(); // read ith statement.
}
instructions += BuildExpression(); // read expression (inside scope).
instructions += ExitScope(offset);
block_expression_depth_dec();
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildBigIntLiteral(
TokenPosition* position) {
ReadPosition(); // ignore file offset.
if (position != nullptr) *position = TokenPosition::kNoSource;
const String& value =
H.DartString(ReadStringReference()); // read index into string table.
const Integer& integer = Integer::ZoneHandle(Z, Integer::NewCanonical(value));
if (integer.IsNull()) {
const auto& script = Script::Handle(Z, Script());
H.ReportError(script, TokenPosition::kNoSource,
"Integer literal %s is out of range", value.ToCString());
UNREACHABLE();
}
return Constant(integer);
}
Fragment StreamingFlowGraphBuilder::BuildStringLiteral(
TokenPosition* position) {
ReadPosition(); // ignore file offset.
if (position != nullptr) *position = TokenPosition::kNoSource;
return Constant(H.DartSymbolPlain(
ReadStringReference())); // read index into string table.
}
Fragment StreamingFlowGraphBuilder::BuildIntLiteral(uint8_t payload,
TokenPosition* position) {
ReadPosition(); // ignore file offset.
if (position != nullptr) *position = TokenPosition::kNoSource;
int64_t value = static_cast<int32_t>(payload) - SpecializedIntLiteralBias;
return IntConstant(value);
}
Fragment StreamingFlowGraphBuilder::BuildIntLiteral(bool is_negative,
TokenPosition* position) {
ReadPosition(); // ignore file offset.
if (position != nullptr) *position = TokenPosition::kNoSource;
int64_t value = is_negative ? -static_cast<int64_t>(ReadUInt())
: ReadUInt(); // read value.
return IntConstant(value);
}
Fragment StreamingFlowGraphBuilder::BuildDoubleLiteral(
TokenPosition* position) {
ReadPosition(); // ignore file offset.
if (position != nullptr) *position = TokenPosition::kNoSource;
Double& constant = Double::ZoneHandle(
Z, Double::NewCanonical(ReadDouble())); // read double.
return Constant(constant);
}
Fragment StreamingFlowGraphBuilder::BuildBoolLiteral(bool value,
TokenPosition* position) {
ReadPosition(); // ignore file offset.
if (position != nullptr) *position = TokenPosition::kNoSource;
return Constant(Bool::Get(value));
}
Fragment StreamingFlowGraphBuilder::BuildNullLiteral(TokenPosition* position) {
ReadPosition(); // ignore file offset.
if (position != nullptr) *position = TokenPosition::kNoSource;
return Constant(Instance::ZoneHandle(Z, Instance::null()));
}
Fragment StreamingFlowGraphBuilder::BuildFutureNullValue(
TokenPosition* position) {
if (position != nullptr) *position = TokenPosition::kNoSource;
const Class& future = Class::Handle(Z, IG->object_store()->future_class());
ASSERT(!future.IsNull());
const auto& error = future.EnsureIsFinalized(thread());
ASSERT(error == Error::null());
Function& constructor = Function::ZoneHandle(
Z, Resolver::ResolveFunction(Z, future, Symbols::FutureValue()));
ASSERT(!constructor.IsNull());
Fragment instructions;
instructions += BuildNullLiteral(position);
instructions += StaticCall(TokenPosition::kNoSource, constructor,
/* argument_count = */ 1, ICData::kStatic);
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildConstantExpression(
TokenPosition* position,
Tag tag) {
TokenPosition p = TokenPosition::kNoSource;
if (tag == kConstantExpression) {
p = ReadPosition();
SkipDartType();
} else if (tag == kFileUriConstantExpression) {
// TODO(alexmarkov): Use file offset together with file uri.
ReadPosition();
ReadUInt();
SkipDartType();
}
if (position != nullptr) *position = p;
const intptr_t constant_index = ReadUInt();
Fragment result = Constant(
Object::ZoneHandle(Z, constant_reader_.ReadConstant(constant_index)));
return result;
}
Fragment StreamingFlowGraphBuilder::BuildPartialTearoffInstantiation(
TokenPosition* p) {
const TokenPosition position = ReadPosition(); // read position.
if (p != nullptr) *p = position;
// Create a copy of the closure.
Fragment instructions = BuildExpression();
LocalVariable* original_closure = MakeTemporary();
// Load the target function and context and allocate the closure.
instructions += LoadLocal(original_closure);
instructions +=
flow_graph_builder_->LoadNativeField(Slot::Closure_function());
instructions += LoadLocal(original_closure);
instructions += flow_graph_builder_->LoadNativeField(Slot::Closure_context());
instructions += LoadLocal(original_closure);
instructions += flow_graph_builder_->LoadNativeField(
Slot::Closure_instantiator_type_arguments());
instructions += flow_graph_builder_->AllocateClosure(
position, /*has_instantiator_type_args=*/true, /*is_generic=*/false,
/*is_tear_off=*/false);
LocalVariable* new_closure = MakeTemporary();
intptr_t num_type_args = ReadListLength();
const TypeArguments& type_args = T.BuildTypeArguments(num_type_args);
instructions += TranslateInstantiatedTypeArguments(type_args);
LocalVariable* type_args_vec = MakeTemporary("type_args");
// Check the bounds.
//
// TODO(sjindel): We should be able to skip this check in many cases, e.g.
// when the closure is coming from a tearoff of a top-level method or from a
// local closure.
instructions += LoadLocal(original_closure);
instructions += LoadLocal(type_args_vec);
const Library& dart_internal = Library::Handle(Z, Library::InternalLibrary());
const Function& bounds_check_function = Function::ZoneHandle(
Z, dart_internal.LookupFunctionAllowPrivate(
Symbols::BoundsCheckForPartialInstantiation()));
ASSERT(!bounds_check_function.IsNull());
instructions += StaticCall(TokenPosition::kNoSource, bounds_check_function, 2,
ICData::kStatic);
instructions += Drop();
instructions += LoadLocal(new_closure);
instructions += LoadLocal(type_args_vec);
instructions += flow_graph_builder_->StoreNativeField(
Slot::Closure_delayed_type_arguments(),
StoreFieldInstr::Kind::kInitializing);
instructions += DropTemporary(&type_args_vec);
// Copy over the function type arguments.
instructions += LoadLocal(new_closure);
instructions += LoadLocal(original_closure);
instructions += flow_graph_builder_->LoadNativeField(
Slot::Closure_function_type_arguments());
instructions += flow_graph_builder_->StoreNativeField(
Slot::Closure_function_type_arguments(),
StoreFieldInstr::Kind::kInitializing);
instructions += DropTempsPreserveTop(1); // Drop old closure.
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildLibraryPrefixAction(
TokenPosition* position,
const String& selector) {
const TokenPosition pos = ReadPosition(); // read position.
if (position != nullptr) *position = pos;
const intptr_t dependency_index = ReadUInt();
const Library& current_library = Library::Handle(
Z, Class::Handle(Z, parsed_function()->function().Owner()).library());
const Array& dependencies = Array::Handle(Z, current_library.dependencies());
const LibraryPrefix& prefix =
LibraryPrefix::CheckedZoneHandle(Z, dependencies.At(dependency_index));
const Function& function =
Function::ZoneHandle(Z, Library::Handle(Z, Library::CoreLibrary())
.LookupFunctionAllowPrivate(selector));
ASSERT(!function.IsNull());
Fragment instructions;
instructions += Constant(prefix);
instructions += StaticCall(pos, function, 1, ICData::kStatic);
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildAwaitExpression(
TokenPosition* position) {
ASSERT(parsed_function()->function().IsAsyncFunction() ||
parsed_function()->function().IsAsyncGenerator());
Fragment instructions;
const TokenPosition pos = ReadPosition(); // read file offset.
if (position != nullptr) *position = pos;
instructions += BuildExpression(); // read operand.
SuspendInstr::StubId stub_id = SuspendInstr::StubId::kAwait;
if (ReadTag() == kSomething) {
const AbstractType& type = T.BuildType(); // read runtime check type.
if (!type.IsType() ||
!Class::Handle(Z, type.type_class()).IsFutureClass()) {
FATAL("Unexpected type for runtime check in await: %s", type.ToCString());
}
ASSERT(type.IsFinalized());
const auto& type_args =
TypeArguments::ZoneHandle(Z, Type::Cast(type).arguments());
if (!type_args.IsNull()) {
const auto& type_arg = AbstractType::Handle(Z, type_args.TypeAt(0));
if (!type_arg.IsTopTypeForSubtyping()) {
instructions += TranslateInstantiatedTypeArguments(type_args);
stub_id = SuspendInstr::StubId::kAwaitWithTypeCheck;
}
}
}
if (NeedsDebugStepCheck(parsed_function()->function(), pos)) {
instructions += DebugStepCheck(pos);
}
instructions += B->Suspend(pos, stub_id);
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildFileUriExpression(
TokenPosition* position) {
ReadUInt(); // read uri
const TokenPosition pos = ReadPosition(); // read position.
if (position != nullptr) *position = pos;
return BuildExpression(position); // read expression.
}
Fragment StreamingFlowGraphBuilder::BuildExpressionStatement(
TokenPosition* position) {
Fragment instructions = BuildExpression(position); // read expression.
instructions += Drop();
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildBlock(TokenPosition* position) {
intptr_t offset = ReaderOffset() - 1; // Include the tag.
Fragment instructions;
instructions += EnterScope(offset);
const TokenPosition pos = ReadPosition(); // read file offset.
if (position != nullptr) *position = pos;
ReadPosition(); // read file end offset.
intptr_t list_length = ReadListLength(); // read number of statements.
for (intptr_t i = 0; i < list_length; ++i) {
if (instructions.is_open()) {
instructions += BuildStatement(); // read ith statement.
} else {
SkipStatement(); // read ith statement.
}
}
instructions += ExitScope(offset);
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildEmptyStatement() {
return Fragment();
}
Fragment StreamingFlowGraphBuilder::BuildAssertBlock(TokenPosition* position) {
if (!IG->asserts()) {
SkipStatementList();
return Fragment();
}
intptr_t offset = ReaderOffset() - 1; // Include the tag.
Fragment instructions;
instructions += EnterScope(offset);
intptr_t list_length = ReadListLength(); // read number of statements.
for (intptr_t i = 0; i < list_length; ++i) {
if (instructions.is_open()) {
// read ith statement.
instructions += BuildStatement(i == 0 ? position : nullptr);
} else {
SkipStatement(); // read ith statement.
}
}
instructions += ExitScope(offset);
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildAssertStatement(
TokenPosition* position) {
if (!IG->asserts()) {
SetOffset(ReaderOffset() - 1); // Include the tag.
SkipStatement(); // read this statement.
return Fragment();
}
TargetEntryInstr* then;
TargetEntryInstr* otherwise;
Fragment instructions;
instructions += BuildExpression(position); // read condition.
const TokenPosition condition_start_offset =
ReadPosition(); // read condition start offset.
const TokenPosition condition_end_offset =
ReadPosition(); // read condition end offset.
instructions += RecordCoverage(condition_start_offset);
instructions += Constant(Bool::True());
instructions += BranchIfEqual(&then, &otherwise);
const Class& klass =
Class::ZoneHandle(Z, Library::LookupCoreClass(Symbols::AssertionError()));
ASSERT(!klass.IsNull());
const auto& error = klass.EnsureIsFinalized(thread());
ASSERT(error == Error::null());
Fragment otherwise_fragment(otherwise);
if (CompilerState::Current().is_aot()) {
// When in AOT, figure out start line, end line, line fragment needed for
// the message now, because it won't be available at runtime.
const Function& target = Function::ZoneHandle(
Z, klass.LookupStaticFunctionAllowPrivate(Symbols::ThrowNewSource()));
ASSERT(!target.IsNull());
auto& script = Script::ZoneHandle(Z, Script());
auto& condition_text = String::ZoneHandle(Z);
intptr_t from_line = -1, from_column = -1;
if (script.GetTokenLocation(condition_start_offset, &from_line,
&from_column)) {
// Extract the assertion condition text (if source is available).
intptr_t to_line, to_column;
script.GetTokenLocation(condition_end_offset, &to_line, &to_column);
condition_text =
script.GetSnippet(from_line, from_column, to_line, to_column);
condition_text = Symbols::New(thread(), condition_text);
} else {
condition_text = Symbols::OptimizedOut().ptr();
}
otherwise_fragment += Constant(condition_text);
otherwise_fragment += Constant(String::ZoneHandle(Z, script.url()));
otherwise_fragment += IntConstant(from_line); // line
otherwise_fragment += IntConstant(from_column); // pos
Tag tag = ReadTag(); // read (first part of) message.
if (tag == kSomething) {
otherwise_fragment += BuildExpression(); // read (rest of) message.
} else {
otherwise_fragment += Constant(Instance::ZoneHandle(Z)); // null.
}
otherwise_fragment +=
StaticCall(condition_start_offset, target, 5, ICData::kStatic);
} else {
const Function& target = Function::ZoneHandle(
Z, klass.LookupStaticFunctionAllowPrivate(Symbols::ThrowNew()));
ASSERT(!target.IsNull());
otherwise_fragment += IntConstant(condition_start_offset.Pos());
otherwise_fragment += IntConstant(condition_end_offset.Pos());
Tag tag = ReadTag(); // read (first part of) message.
if (tag == kSomething) {
otherwise_fragment += BuildExpression(); // read (rest of) message.
} else {
otherwise_fragment += Constant(Instance::ZoneHandle(Z)); // null.
}
// Note: condition_start_offset points to the first token after the opening
// paren, not the beginning of 'assert'.
otherwise_fragment +=
StaticCall(condition_start_offset, target, 3, ICData::kStatic);
}
otherwise_fragment += Drop();
ASSERT(otherwise_fragment.is_closed());
return Fragment(instructions.entry, then);
}
Fragment StreamingFlowGraphBuilder::BuildLabeledStatement(
TokenPosition* position) {
const TokenPosition pos = ReadPosition(); // read position.
if (position != nullptr) *position = pos;
// There can be several cases:
//
// * the body contains a break
// * the body doesn't contain a break
//
// * translating the body results in a closed fragment
// * translating the body results in a open fragment
//
// => We will only know which case we are in after the body has been
// traversed.
BreakableBlock block(flow_graph_builder_);
Fragment instructions = BuildStatement(position); // read body.
if (block.HadJumper()) {
if (instructions.is_open()) {
instructions += Goto(block.destination());
}
return Fragment(instructions.entry, block.destination());
} else {
return instructions;
}
}
Fragment StreamingFlowGraphBuilder::BuildBreakStatement(
TokenPosition* position) {
const TokenPosition pos = ReadPosition(); // read position.
if (position != nullptr) *position = pos;
intptr_t target_index = ReadUInt(); // read target index.
TryFinallyBlock* outer_finally = nullptr;
intptr_t target_context_depth = -1;
JoinEntryInstr* destination = breakable_block()->BreakDestination(
target_index, &outer_finally, &target_context_depth);
Fragment instructions;
// Break statement should pause before manipulation of context, which
// will possibly cause debugger having incorrect context object.
if (NeedsDebugStepCheck(parsed_function()->function(), pos)) {
instructions += DebugStepCheck(pos);
}
instructions +=
TranslateFinallyFinalizers(outer_finally, target_context_depth);
if (instructions.is_open()) {
instructions += Goto(destination);
}
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildWhileStatement(
TokenPosition* position) {
loop_depth_inc();
const TokenPosition pos = ReadPosition(); // read position.
if (position != nullptr) *position = pos;
TestFragment condition = TranslateConditionForControl(); // read condition.
const Fragment body = BuildStatementWithBranchCoverage(); // read body
Fragment body_entry(condition.CreateTrueSuccessor(flow_graph_builder_));
body_entry += body;
Instruction* entry;
if (body_entry.is_open()) {
JoinEntryInstr* join = BuildJoinEntry();
body_entry += Goto(join);
Fragment loop(join);
loop += CheckStackOverflow(pos); // may have non-empty stack
loop.current->LinkTo(condition.entry);
entry = Goto(join).entry;
} else {
entry = condition.entry;
}
loop_depth_dec();
return Fragment(entry, condition.CreateFalseSuccessor(flow_graph_builder_));
}
Fragment StreamingFlowGraphBuilder::BuildDoStatement(TokenPosition* position) {
loop_depth_inc();
const TokenPosition pos = ReadPosition(); // read position.
if (position != nullptr) *position = pos;
Fragment body = BuildStatementWithBranchCoverage(); // read body.
if (body.is_closed()) {
SkipExpression(); // read condition.
loop_depth_dec();
return body;
}
TestFragment condition = TranslateConditionForControl();
JoinEntryInstr* join = BuildJoinEntry();
Fragment loop(join);
loop += CheckStackOverflow(pos); // may have non-empty stack
loop += body;
loop <<= condition.entry;
condition.IfTrueGoto(flow_graph_builder_, join);
loop_depth_dec();
return Fragment(
new (Z) GotoInstr(join, CompilerState::Current().GetNextDeoptId()),
condition.CreateFalseSuccessor(flow_graph_builder_));
}
Fragment StreamingFlowGraphBuilder::BuildForStatement(TokenPosition* position) {
intptr_t offset = ReaderOffset() - 1; // Include the tag.
const TokenPosition pos = ReadPosition(); // read position.
if (position != nullptr) *position = pos;
Fragment declarations;
loop_depth_inc();
const LocalScope* context_scope = nullptr;
declarations += EnterScope(offset, &context_scope);
intptr_t list_length = ReadListLength(); // read number of variables.
for (intptr_t i = 0; i < list_length; ++i) {
declarations += BuildVariableDeclaration(nullptr); // read ith variable.
}
Tag tag = ReadTag(); // Read first part of condition.
TestFragment condition;
BlockEntryInstr* body_entry;
BlockEntryInstr* loop_exit;
if (tag != kNothing) {
condition = TranslateConditionForControl();
body_entry = condition.CreateTrueSuccessor(flow_graph_builder_);
loop_exit = condition.CreateFalseSuccessor(flow_graph_builder_);
} else {
body_entry = BuildJoinEntry();
loop_exit = BuildJoinEntry();
}
Fragment updates;
list_length = ReadListLength(); // read number of updates.
for (intptr_t i = 0; i < list_length; ++i) {
updates += BuildExpression(); // read ith update.
updates += Drop();
}
Fragment body(body_entry);
body += BuildStatementWithBranchCoverage(); // read body.
if (body.is_open()) {
// We allocated a fresh context before the loop which contains captured
// [ForStatement] variables. Before jumping back to the loop entry we clone
// the context object (at same depth) which ensures the next iteration of
// the body gets a fresh set of [ForStatement] variables (with the old
// (possibly updated) values).
if (context_scope->num_context_variables() > 0) {
body += CloneContext(context_scope->context_slots());
}
body += updates;
JoinEntryInstr* join = BuildJoinEntry();
declarations += Goto(join);
body += Goto(join);
Fragment loop(join);
loop += CheckStackOverflow(pos); // may have non-empty stack
if (condition.entry != nullptr) {
loop <<= condition.entry;
} else {
loop += Goto(body_entry->AsJoinEntry());
}
} else {
if (condition.entry != nullptr) {
declarations <<= condition.entry;
} else {
declarations += Goto(body_entry->AsJoinEntry());
}
}
Fragment loop(declarations.entry, loop_exit);
loop += ExitScope(offset);
loop_depth_dec();
return loop;
}
Fragment StreamingFlowGraphBuilder::BuildSwitchStatement(
TokenPosition* position) {
const TokenPosition pos = ReadPosition(); // read position.
if (position != nullptr) *position = pos;
const bool is_exhaustive = ReadBool(); // read exhaustive flag.
// We need the number of cases. So start by getting that, then go back.
const intptr_t offset = ReaderOffset();
SkipExpression(); // temporarily skip condition
SkipOptionalDartType(); // temporarily skip expression type
intptr_t case_count = ReadListLength(); // read number of cases.
SetOffset(offset);
SwitchBlock block(flow_graph_builder_, case_count);
Fragment instructions = BuildExpression(); // read condition.
const AbstractType* expression_type = &Object::dynamic_type();
if (ReadTag() == kSomething) {
expression_type = &T.BuildType(); // read expression type.
}
instructions +=
StoreLocal(TokenPosition::kNoSource, scopes()->switch_variable);
instructions += Drop();
case_count = ReadListLength(); // read number of cases.
SwitchHelper helper(Z, pos, is_exhaustive, *expression_type, &block,
case_count);
// Build the case bodies and collect the expressions into the helper
// for the next step.
for (intptr_t i = 0; i < case_count; ++i) {
helper.AddCaseBody(BuildSwitchCase(&helper, i));
}
// Build the code to dispatch to the case bodies.
switch (helper.SelectDispatchStrategy()) {
case kSwitchDispatchAuto:
UNREACHABLE();
case kSwitchDispatchLinearScan:
instructions += BuildLinearScanSwitch(&helper);
break;
case kSwitchDispatchBinarySearch:
instructions += BuildBinarySearchSwitch(&helper);
break;
case kSwitchDispatchJumpTable:
instructions += BuildJumpTableSwitch(&helper);
break;
}
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildSwitchCase(SwitchHelper* helper,
intptr_t case_index) {
// Generate case body and try to find out whether the body will be target
// of a jump due to:
// * `continue case_label`
// * `case e1: case e2: body`
//
// Also collect switch expressions into helper.
ReadPosition(); // read file offset.
const int expression_count = ReadListLength(); // read number of expressions.
for (intptr_t j = 0; j < expression_count; ++j) {
const TokenPosition pos = ReadPosition(); // read jth position.
// read jth expression.
const Instance& value =
Instance::ZoneHandle(Z, constant_reader_.ReadConstantExpression());
helper->AddExpression(case_index, pos, value);
}
const bool is_default = ReadBool(); // read is_default.
if (is_default) helper->set_default_case(case_index);
Fragment body_fragment = BuildStatementWithBranchCoverage(); // read body.
if (body_fragment.entry == nullptr) {
// Make a NOP in order to ensure linking works properly.
body_fragment = NullConstant();
body_fragment += Drop();
}
if (!is_default && body_fragment.is_open() &&
(case_index < (helper->case_count() - 1))) {
body_fragment += B->Stop("Unreachable end of case");
}
// If there is an implicit fall-through we have one [SwitchCase] and
// multiple expressions, e.g.
//
// switch(expr) {
// case a:
// case b:
// <stmt-body>
// }
//
// This means that the <stmt-body> will have more than 1 incoming edge (one
// from `a == expr` and one from `a != expr && b == expr`). The
// `block.Destination()` records the additional jump.
if (expression_count > 1) {
helper->switch_block()->DestinationDirect(case_index);
}
return body_fragment;
}
Fragment StreamingFlowGraphBuilder::BuildLinearScanSwitch(
SwitchHelper* helper) {
// Build a switch using a sequence of equality tests.
//
// From a test:
// * jump directly to a body, if there is no jumper.
// * jump to a wrapper block which jumps to the body, if there is a jumper.
SwitchBlock* block = helper->switch_block();
const intptr_t case_count = helper->case_count();
const intptr_t default_case = helper->default_case();
const GrowableArray<Fragment>& case_bodies = helper->case_bodies();
Fragment current_instructions;
intptr_t expression_index = 0;
for (intptr_t i = 0; i < case_count; ++i) {
if (i == default_case) {
ASSERT(i == (case_count - 1));
if (block->HadJumper(i)) {
// There are several branches to the body, so we will make a goto to
// the join block (and prepend a join instruction to the real body).
JoinEntryInstr* join = block->DestinationDirect(i);
current_instructions += Goto(join);
current_instructions = Fragment(current_instructions.entry, join);
current_instructions += case_bodies[i];
} else {
current_instructions += case_bodies[i];
}
} else {
JoinEntryInstr* body_join = nullptr;
if (block->HadJumper(i)) {
body_join = block->DestinationDirect(i);
case_bodies[i] = Fragment(body_join) + case_bodies[i];
}
const intptr_t expression_count = helper->case_expression_counts().At(i);
for (intptr_t j = 0; j < expression_count; ++j) {
TargetEntryInstr* then;
TargetEntryInstr* otherwise;
const SwitchExpression& expression =
helper->expressions().At(expression_index++);
current_instructions += Constant(expression.value());
current_instructions += LoadLocal(scopes()->switch_variable);
current_instructions += InstanceCall(
expression.position(), Symbols::EqualOperator(), Token::kEQ,
/*argument_count=*/2,
/*checked_argument_count=*/2);
current_instructions += BranchIfTrue(&then, &otherwise, false);
Fragment then_fragment(then);
if (body_join != nullptr) {
// There are several branches to the body, so we will make a goto to
// the join block (the real body has already been prepended with a
// join instruction).
then_fragment += Goto(body_join);
} else {
// There is only a single branch to the body, so we will just append
// the body fragment.
then_fragment += case_bodies[i];
}
current_instructions = Fragment(current_instructions.entry, otherwise);
}
}
}
if (case_count > 0 && !helper->has_default()) {
// There is no default, which means we have an open [current_instructions]
// (which is a [TargetEntryInstruction] for the last "otherwise" branch).
//
// Furthermore the last [SwitchCase] can be open as well. If so, we need
// to join these two.
Fragment& last_body = case_bodies[case_count - 1];
if (last_body.is_open()) {
ASSERT(current_instructions.is_open());
ASSERT(current_instructions.current->IsTargetEntry());
// Join the last "otherwise" branch and the last [SwitchCase] fragment.
JoinEntryInstr* join = BuildJoinEntry();
current_instructions += Goto(join);
last_body += Goto(join);
current_instructions = Fragment(current_instructions.entry, join);
}
} else {
// All non-default cases will be closed (i.e. break/continue/throw/return)
// So it is fine to just let more statements after the switch append to the
// default case.
}
return current_instructions;
}
Fragment StreamingFlowGraphBuilder::BuildOptimizedSwitchPrelude(
SwitchHelper* helper,
JoinEntryInstr* join) {
const TokenPosition pos = helper->position();
Fragment instructions;
if (helper->is_enum_switch()) {
// For an enum switch, we need to load the enum index from the switch
// variable.
instructions += LoadLocal(scopes()->switch_variable);
const Field& enum_index_field =
Field::ZoneHandle(Z, IG->object_store()->enum_index_field());
instructions += B->LoadField(enum_index_field, /*calls_initializer=*/false);
instructions += StoreLocal(pos, scopes()->switch_variable);
instructions += Drop();
}
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildBinarySearchSwitch(
SwitchHelper* helper) {
// * We build a binary tree of conditional branches where each branch bisects
// the remaining cases.
// * At holes in the switch expression range we need to add additional
// bound checks.
// * At each leaf we add the body of the case or a goto, if the case has
// jumpers.
// * Leafs at the bounds of the switch expression range might need to
// do a bound check.
SwitchBlock* block = helper->switch_block();
const intptr_t case_count = helper->case_count();
const intptr_t default_case = helper->default_case();
const GrowableArray<Fragment>& case_bodies = helper->case_bodies();
const intptr_t expression_count = helper->expressions().length();
const GrowableArray<SwitchExpression*>& sorted_expressions =
helper->sorted_expressions();
TargetEntryInstr* then_entry;
TargetEntryInstr* otherwise_entry;
// Entry to the default case or the exit of the switch, if there is no
// default case.
JoinEntryInstr* join;
if (helper->has_default()) {
join = block->DestinationDirect(default_case);
} else {
join = BuildJoinEntry();
}
Fragment join_instructions(join);
if (helper->has_default()) {
join_instructions += case_bodies.At(default_case);
}
Fragment current_instructions = BuildOptimizedSwitchPrelude(helper, join);
GrowableArray<SwitchRange> stack;
stack.Add(SwitchRange::Branch(0, expression_count - 1, current_instructions));
while (!stack.is_empty()) {
const SwitchRange range = stack.RemoveLast();
Fragment branch_instructions = range.branch_instructions();
if (range.is_leaf()) {
const intptr_t expression_index = range.min();
const SwitchExpression& expression =
*sorted_expressions.At(expression_index);
if (!range.is_bounds_checked() &&
((helper->RequiresLowerBoundCheck() && expression_index == 0) ||
(helper->RequiresUpperBoundCheck() &&
expression_index == expression_count - 1))) {
// This leaf needs a bound check.
branch_instructions += LoadLocal(scopes()->switch_variable);
branch_instructions += Constant(expression.integer());
branch_instructions +=
StrictCompare(expression.position(), Token::kEQ_STRICT,
/*number_check=*/true);
branch_instructions +=
BranchIfTrue(&then_entry, &otherwise_entry, /*negate=*/false);
Fragment otherwise_instructions(otherwise_entry);
otherwise_instructions += Goto(join);
stack.Add(SwitchRange::Leaf(expression_index, Fragment(then_entry),
/*is_bounds_checked=*/true));
} else {
// We are at a leaf where we can add the body of the case or a goto to
// [join].
const intptr_t case_index = expression.case_index();
if (case_index == default_case) {
branch_instructions += Goto(join);
} else {
if (block->HadJumper(case_index)) {
JoinEntryInstr* join = block->DestinationDirect(case_index);
branch_instructions += Goto(join);
if (join->next() == nullptr) {
// The first time we reach an expression that jumps to a case
// body we emit the body.
branch_instructions = Fragment(join);
branch_instructions += case_bodies.At(case_index);
}
} else {
branch_instructions += case_bodies.At(case_index);
}
if (!helper->has_default() && case_index == case_count - 1) {
if (branch_instructions.is_open()) {
branch_instructions += Goto(join);
}
}
}
ASSERT(branch_instructions.is_closed());
}
} else {
// Add a conditional to bisect the range.
const intptr_t middle = range.min() + (range.max() - range.min()) / 2;
const intptr_t next = middle + 1;
const SwitchExpression& middle_expression =
*sorted_expressions.At(middle);
const SwitchExpression& next_expression = *sorted_expressions.At(next);
branch_instructions += LoadLocal(scopes()->switch_variable);
branch_instructions += Constant(middle_expression.integer());
branch_instructions +=
B->IntRelationalOp(middle_expression.position(), Token::kLTE);
branch_instructions +=
BranchIfTrue(&then_entry, &otherwise_entry, /*negate=*/false);
Fragment lower_branch_instructions(then_entry);
Fragment upper_branch_instructions(otherwise_entry);
if (next_expression.integer().Value() >
middle_expression.integer().Value() + 1) {
// The upper branch is not contiguous with the lower branch.
// Before continuing in the upper branch we add a bound check.
upper_branch_instructions += LoadLocal(scopes()->switch_variable);
upper_branch_instructions += Constant(next_expression.integer());
upper_branch_instructions +=
B->IntRelationalOp(next_expression.position(), Token::kGTE);
upper_branch_instructions +=
BranchIfTrue(&then_entry, &otherwise_entry, /*negate=*/false);
Fragment otherwise_instructions(otherwise_entry);
otherwise_instructions += Goto(join);
upper_branch_instructions = Fragment(then_entry);
}
stack.Add(
SwitchRange::Branch(next, range.max(), upper_branch_instructions));
stack.Add(
SwitchRange::Branch(range.min(), middle, lower_branch_instructions));
}
if (current_instructions.is_empty()) {
current_instructions = branch_instructions;
}
}
return Fragment(current_instructions.entry, join_instructions.current);
}
Fragment StreamingFlowGraphBuilder::BuildJumpTableSwitch(SwitchHelper* helper) {
// * If input value is not integer or enum value, goto default case or
// switch exit.
// * If value is enum value, load its index.
// * If input integer is outside of jump table range, goto default case
// or switch exit.
// * Jump to case with jump table.
// * For each expression, add entry to jump to case.
// * For each hole in the integer range, add entry to jump to default
// cause or switch exit.
SwitchBlock* block = helper->switch_block();
const TokenPosition pos = helper->position();
const intptr_t case_count = helper->case_count();
const intptr_t default_case = helper->default_case();
const GrowableArray<Fragment>& case_bodies = helper->case_bodies();
const Integer& expression_min = helper->expression_min();
const Integer& expression_max = helper->expression_max();
TargetEntryInstr* then_entry;
TargetEntryInstr* otherwise_entry;
// Entry to the default case or the exit of the switch, if there is no
// default case.
JoinEntryInstr* join;
if (helper->has_default()) {
join = block->DestinationDirect(default_case);
} else {
join = BuildJoinEntry();
}
Fragment join_instructions(join);
Fragment current_instructions = BuildOptimizedSwitchPrelude(helper, join);
if (helper->RequiresLowerBoundCheck()) {
current_instructions += LoadLocal(scopes()->switch_variable);
current_instructions += Constant(expression_min);
current_instructions += B->IntRelationalOp(pos, Token::kGTE);
current_instructions += BranchIfTrue(&then_entry, &otherwise_entry,
/*negate=*/false);
Fragment otherwise_instructions(otherwise_entry);
otherwise_instructions += Goto(join);
current_instructions = Fragment(current_instructions.entry, then_entry);
}
if (helper->RequiresUpperBoundCheck()) {
current_instructions += LoadLocal(scopes()->switch_variable);
current_instructions += Constant(expression_max);
current_instructions += B->IntRelationalOp(pos, Token::kLTE);
current_instructions += BranchIfTrue(&then_entry, &otherwise_entry,
/*negate=*/false);
Fragment otherwise_instructions(otherwise_entry);
otherwise_instructions += Goto(join);
current_instructions = Fragment(current_instructions.entry, then_entry);
}
current_instructions += LoadLocal(scopes()->switch_variable);
if (expression_min.Value() != 0) {
// Adjust for the range of the jump table, which starts at 0.
current_instructions += Constant(expression_min);
current_instructions +=
InstanceCall(pos, Symbols::Minus(), Token::kSUB, /*argument_count=*/2,
/*checked_argument_count=*/2);
}
const intptr_t table_size = helper->ExpressionRange();
IndirectGotoInstr* indirect_goto = IndirectGoto(table_size);
current_instructions <<= indirect_goto;
current_instructions = current_instructions.closed();
GrowableArray<TargetEntryInstr*> table_entries(table_size);
table_entries.FillWith(nullptr, 0, table_size);
// Generate the jump table entries for the switch cases.
intptr_t expression_index = 0;
for (intptr_t i = 0; i < case_count; ++i) {
const int expression_count = helper->case_expression_counts().At(i);
// Generate jump table entries for each case expression.
if (i != default_case) {
for (intptr_t j = 0; j < expression_count; ++j) {
const SwitchExpression& expression =
helper->expressions().At(expression_index++);
const intptr_t table_offset =
expression.integer().Value() - expression_min.Value();
IndirectEntryInstr* indirect_entry =
B->BuildIndirectEntry(table_offset, CurrentTryIndex());
Fragment indirect_entry_instructions(indirect_entry);
indirect_entry_instructions += Goto(block->DestinationDirect(i));
TargetEntryInstr* entry = B->BuildTargetEntry();
Fragment entry_instructions(entry);
entry_instructions += Goto(indirect_entry);
table_entries[table_offset] = entry;
}
}
// Connect the case body to its join entry.
if (i == default_case) {
join_instructions += case_bodies.At(i);
} else {
Fragment case_instructions(block->DestinationDirect(i));
case_instructions += case_bodies.At(i);
if (i == case_count - 1) {
// If the last case is not the default case and it is still open
// close it by going to the exit of the switch.
if (case_instructions.is_open()) {
case_instructions += Goto(join);
}
}
ASSERT(case_instructions.is_closed());
}
}
// Generate the jump table entries for holes in the integer range.
for (intptr_t i = 0; i < table_size; i++) {
if (table_entries.At(i) == nullptr) {
IndirectEntryInstr* indirect_entry =
B->BuildIndirectEntry(i, CurrentTryIndex());
Fragment indirect_entry_instructions(indirect_entry);
indirect_entry_instructions += Goto(join);
TargetEntryInstr* entry = flow_graph_builder_->BuildTargetEntry();
Fragment entry_instructions(entry);
entry_instructions += Goto(indirect_entry);
table_entries[i] = entry;
}
}
// Add the jump table entries to the jump table.
for (intptr_t i = 0; i < table_size; i++) {
indirect_goto->AddSuccessor(table_entries.At(i));
}
return Fragment(current_instructions.entry, join_instructions.current);
}
Fragment StreamingFlowGraphBuilder::BuildContinueSwitchStatement(
TokenPosition* position) {
const TokenPosition pos = ReadPosition(); // read position.
if (position != nullptr) *position = pos;
intptr_t target_index = ReadUInt(); // read target index.
TryFinallyBlock* outer_finally = nullptr;
intptr_t target_context_depth = -1;
JoinEntryInstr* entry = switch_block()->Destination(
target_index, &outer_finally, &target_context_depth);
Fragment instructions;
instructions +=
TranslateFinallyFinalizers(outer_finally, target_context_depth);
if (instructions.is_open()) {
if (NeedsDebugStepCheck(parsed_function()->function(), pos)) {
instructions += DebugStepCheck(pos);
}
instructions += Goto(entry);
}
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildIfStatement(TokenPosition* position) {
const TokenPosition pos = ReadPosition(); // read position.
if (position != nullptr) *position = pos;
TestFragment condition = TranslateConditionForControl();
Fragment then_fragment(condition.CreateTrueSuccessor(flow_graph_builder_));
then_fragment += BuildStatementWithBranchCoverage(); // read then.
Fragment otherwise_fragment(
condition.CreateFalseSuccessor(flow_graph_builder_));
otherwise_fragment += BuildStatementWithBranchCoverage(); // read otherwise.
if (then_fragment.is_open()) {
if (otherwise_fragment.is_open()) {
JoinEntryInstr* join = BuildJoinEntry();
then_fragment += Goto(join);
otherwise_fragment += Goto(join);
return Fragment(condition.entry, join);
} else {
return Fragment(condition.entry, then_fragment.current);
}
} else if (otherwise_fragment.is_open()) {
return Fragment(condition.entry, otherwise_fragment.current);
} else {
return Fragment(condition.entry, nullptr);
}
}
Fragment StreamingFlowGraphBuilder::BuildReturnStatement(
TokenPosition* position) {
const TokenPosition pos = ReadPosition(); // read position.
if (position != nullptr) *position = pos;
Tag tag = ReadTag(); // read first part of expression.
bool inside_try_finally = try_finally_block() != nullptr;
Fragment instructions;
if (parsed_function()->function().IsSyncGenerator()) {
// Return false from sync* function to indicate the end of iteration.
instructions += Constant(Bool::False());
if (tag != kNothing) {
ASSERT(PeekTag() == kNullLiteral);
SkipExpression();
}
} else {
instructions +=
(tag == kNothing ? NullConstant()
: BuildExpression()); // read rest of expression.
}
if (instructions.is_open()) {
if (inside_try_finally) {
LocalVariable* const finally_return_variable =
scopes()->finally_return_variable;
ASSERT(finally_return_variable != nullptr);
const Function& function = parsed_function()->function();
if (NeedsDebugStepCheck(function, pos)) {
instructions += DebugStepCheck(pos);
}
instructions += StoreLocal(pos, finally_return_variable);
instructions += Drop();
const intptr_t target_context_depth =
finally_return_variable->is_captured()
? finally_return_variable->owner()->context_level()
: -1;
instructions += TranslateFinallyFinalizers(nullptr, target_context_depth);
if (instructions.is_open()) {
const intptr_t saved_context_depth = B->context_depth_;
if (finally_return_variable->is_captured()) {
B->context_depth_ = target_context_depth;
}
instructions += LoadLocal(finally_return_variable);
instructions += Return(TokenPosition::kNoSource);
B->context_depth_ = saved_context_depth;
}
} else {
instructions += Return(pos);
}
} else {
Pop();
}
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildTryCatch(TokenPosition* position) {
ASSERT(block_expression_depth() == 0); // no try-catch in block-expr
InlineBailout("kernel::FlowgraphBuilder::VisitTryCatch");
const TokenPosition pos = ReadPosition(); // read position.
if (position != nullptr) *position = pos;
// [try entry, try_body=B1, catch=B3]
// B1: [try body, try_index = <n>]
// goto B2
// B3: [catch entry, try_handler_index = <n>]
// goto B2
// B2: [join]
// ...
intptr_t try_handler_index = AllocateTryIndex();
Fragment try_body = TryEntry(try_handler_index);
JoinEntryInstr* after_try = BuildJoinEntry();
// Fill in the body of the try.
try_depth_inc();
{
TryCatchBlock block(flow_graph_builder_, try_handler_index);
try_body += BuildStatementWithBranchCoverage(position); // read body.
try_body += Goto(after_try);
}
try_depth_dec();
const int kNeedsStracktraceBit = 1 << 0;
const int kIsSyntheticBit = 1 << 1;
uint8_t flags = ReadByte();
bool needs_stacktrace =
(flags & kNeedsStracktraceBit) == kNeedsStracktraceBit;
bool is_synthetic = (flags & kIsSyntheticBit) == kIsSyntheticBit;
catch_depth_inc();
intptr_t catch_count = ReadListLength(); // read number of catches.
const Array& handler_types =
Array::ZoneHandle(Z, Array::New(catch_count, Heap::kOld));
Fragment catch_body = CatchBlockEntry(handler_types, try_handler_index,
needs_stacktrace, is_synthetic);
// Fill in the body of the catch.
for (intptr_t i = 0; i < catch_count; ++i) {
intptr_t catch_offset = ReaderOffset(); // Catch has no tag.
TokenPosition pos = ReadPosition(); // read position.
const AbstractType& type_guard = T.BuildType(); // read guard.
handler_types.SetAt(i, type_guard);
Fragment catch_handler_body = EnterScope(catch_offset);
Tag tag = ReadTag(); // read first part of exception.
if (tag == kSomething) {
catch_handler_body += LoadLocal(CurrentException());
catch_handler_body +=
StoreLocal(TokenPosition::kNoSource,
LookupVariable(ReaderOffset() + data_program_offset_));
catch_handler_body += Drop();
SkipVariableDeclaration(); // read exception.
}
tag = ReadTag(); // read first part of stack trace.
if (tag == kSomething) {
catch_handler_body += LoadLocal(CurrentStackTrace());
catch_handler_body +=
StoreLocal(TokenPosition::kNoSource,
LookupVariable(ReaderOffset() + data_program_offset_));
catch_handler_body += Drop();
SkipVariableDeclaration(); // read stack trace.
}
{
CatchBlock block(flow_graph_builder_, CurrentException(),
CurrentStackTrace(), try_handler_index);
catch_handler_body += BuildStatementWithBranchCoverage(); // read body.
// Note: ExitScope adjusts context_depth_ so even if catch_handler_body
// is closed we still need to execute ExitScope for its side effect.
catch_handler_body += ExitScope(catch_offset);
if (catch_handler_body.is_open()) {
catch_handler_body += Goto(after_try);
}
}
if (!type_guard.IsCatchAllType()) {
catch_body += LoadLocal(CurrentException());
if (!type_guard.IsInstantiated(kCurrentClass)) {
catch_body += LoadInstantiatorTypeArguments();
} else {
catch_body += NullConstant();
}
if (!type_guard.IsInstantiated(kFunctions)) {
catch_body += LoadFunctionTypeArguments();
} else {
catch_body += NullConstant();
}
catch_body += Constant(type_guard);
catch_body +=
InstanceCall(pos, Library::PrivateCoreLibName(Symbols::_instanceOf()),
Token::kIS, 4);
TargetEntryInstr* catch_entry;
TargetEntryInstr* next_catch_entry;
catch_body += BranchIfTrue(&catch_entry, &next_catch_entry, false);
Fragment(catch_entry) + catch_handler_body;
catch_body = Fragment(next_catch_entry);
} else {
catch_body += catch_handler_body;
}
}
// In case the last catch body was not handling the exception and branching to
// after the try block, we will rethrow the exception (i.e. no default catch
// handler).
if (catch_body.is_open()) {
catch_body += LoadLocal(CurrentException());
catch_body += LoadLocal(CurrentStackTrace());
catch_body += RethrowException(TokenPosition::kNoSource, try_handler_index);
Drop();
}
catch_depth_dec();
return Fragment(try_body.entry, after_try);
}
Fragment StreamingFlowGraphBuilder::BuildTryFinally(TokenPosition* position) {
// Note on streaming:
// We only stream this TryFinally if we can stream everything inside it,
// so creating a "TryFinallyBlock" with a kernel binary offset instead of an
// AST node isn't a problem.
InlineBailout("kernel::FlowgraphBuilder::VisitTryFinally");
const TokenPosition pos = ReadPosition(); // read position.
if (position != nullptr) *position = pos;
// There are 5 different cases where we need to execute the finally block:
//
// a) 1/2/3th case: Special control flow going out of `node->body()`:
//
// * [BreakStatement] transfers control to a [LabeledStatement]
// * [ContinueSwitchStatement] transfers control to a [SwitchCase]
// * [ReturnStatement] returns a value
//
// => All three cases will automatically append all finally blocks
// between the branching point and the destination (so we don't need to
// do anything here).
//
// b) 4th case: Translating the body resulted in an open fragment (i.e. body
// executes without any control flow out of it)
//
// => We are responsible for jumping out of the body to a new block (with
// different try index) and execute the finalizer.
//
// c) 5th case: An exception occurred inside the body.
//
// => We are responsible for catching it, executing the finally block and
// rethrowing the exception.
intptr_t try_handler_index = AllocateTryIndex();
Fragment try_body = TryEntry(try_handler_index);
JoinEntryInstr* after_try = BuildJoinEntry();
intptr_t offset = ReaderOffset();
SkipStatement(); // temporarily read body.
intptr_t finalizer_offset = ReaderOffset();
SetOffset(offset);
// Fill in the body of the try.
try_depth_inc();
{
TryFinallyBlock tfb(flow_graph_builder_, finalizer_offset);
TryCatchBlock tcb(flow_graph_builder_, try_handler_index);
try_body += BuildStatementWithBranchCoverage(position); // read body.
}
try_depth_dec();
if (try_body.is_open()) {
// Please note: The try index will be on level out of this block,
// thereby ensuring if there's an exception in the finally block we
// won't run it twice.
JoinEntryInstr* finally_entry = BuildJoinEntry();
try_body += Goto(finally_entry);
Fragment finally_body(finally_entry);
finally_body += BuildStatementWithBranchCoverage(); // read finalizer.
finally_body += Goto(after_try);
}
// Fill in the body of the catch.
catch_depth_inc();
const Array& handler_types = Array::ZoneHandle(Z, Array::New(1, Heap::kOld));
handler_types.SetAt(0, Object::dynamic_type());
// Note: rethrow will actually force mark the handler as needing a stacktrace.
Fragment finally_body = CatchBlockEntry(handler_types, try_handler_index,
/* needs_stacktrace = */ false,
/* is_synthesized = */ true);
SetOffset(finalizer_offset);
// Try/finally might occur in control flow collections with non-empty
// expression stack (via desugaring of 'await for'). Note that catch-block
// generated for finally always throws so there is no merge.
// Save and reset expression stack around catch body in order to maintain
// correct stack depth, as catch entry drops expression stack.
Value* const saved_stack_top = stack();
set_stack(nullptr);
finally_body += BuildStatementWithBranchCoverage(); // read finalizer
if (finally_body.is_open()) {
finally_body += LoadLocal(CurrentException());
finally_body += LoadLocal(CurrentStackTrace());
finally_body +=
RethrowException(TokenPosition::kNoSource, try_handler_index);
Drop();
}
ASSERT(stack() == nullptr);
set_stack(saved_stack_top);
catch_depth_dec();
return Fragment(try_body.entry, after_try);
}
Fragment StreamingFlowGraphBuilder::BuildYieldStatement(
TokenPosition* position) {
const TokenPosition pos = ReadPosition(); // read position.
if (position != nullptr) *position = pos;
const uint8_t flags = ReadByte(); // read flags.
Fragment instructions;
const bool is_yield_star = (flags & kYieldStatementFlagYieldStar) != 0;
// Load :suspend_state variable using low-level FP-relative load
// in order to avoid confusing SSA construction (which cannot
// track its value as it is modified implicitly by stubs).
LocalVariable* suspend_state = parsed_function()->suspend_state_var();
ASSERT(suspend_state != nullptr);
instructions += IntConstant(0);
instructions += B->LoadFpRelativeSlot(
compiler::target::frame_layout.FrameSlotForVariable(suspend_state) *
compiler::target::kWordSize,
CompileType::Dynamic(), kTagged);
instructions += LoadNativeField(Slot::SuspendState_function_data());
instructions += BuildExpression(); // read expression.
if (NeedsDebugStepCheck(parsed_function()->function(), pos)) {
instructions += DebugStepCheck(pos);
}
if (parsed_function()->function().IsAsyncGenerator()) {
// In the async* functions, generate the following code for yield <expr>:
//
// _AsyncStarStreamController controller = :suspend_state._functionData;
// if (controller.add(<expr>)) {
// return;
// }
// if (suspend()) {
// return;
// }
//
// Generate the following code for yield* <expr>:
//
// _AsyncStarStreamController controller = :suspend_state._functionData;
// if (controller.addStream(<expr>)) {
// return;
// }
// if (suspend()) {
// return;
// }
//
auto& add_method = Function::ZoneHandle(Z);
if (is_yield_star) {
add_method =
IG->object_store()->async_star_stream_controller_add_stream();
} else {
add_method = IG->object_store()->async_star_stream_controller_add();
}
instructions +=
StaticCall(TokenPosition::kNoSource, add_method, 2, ICData::kNoRebind);
TargetEntryInstr *return1, *continue1;
instructions += BranchIfTrue(&return1, &continue1, false);
JoinEntryInstr* return_join = BuildJoinEntry();
Fragment(return1) + Goto(return_join);
instructions = Fragment(instructions.entry, continue1);
// Suspend and test value passed to the resumed async* body.
instructions += NullConstant();
instructions += B->Suspend(pos, SuspendInstr::StubId::kYieldAsyncStar);
TargetEntryInstr *return2, *continue2;
instructions += BranchIfTrue(&return2, &continue2, false);
Fragment(return2) + Goto(return_join);
instructions = Fragment(instructions.entry, continue2);
Fragment do_return(return_join);
do_return += TranslateFinallyFinalizers(nullptr, -1);
do_return += NullConstant();
do_return += Return(TokenPosition::kNoSource);
} else if (parsed_function()->function().IsSyncGenerator()) {
// In the sync* functions, generate the following code for yield <expr>:
//
// _SyncStarIterator iterator = :suspend_state._functionData;
// iterator._current = <expr>;
// suspend();
//
// Generate the following code for yield* <expr>:
//
// _SyncStarIterator iterator = :suspend_state._functionData;
// iterator._yieldStarIterable = <expr>;
// suspend();
//
auto& field = Field::ZoneHandle(Z);
if (is_yield_star) {
field = IG->object_store()->sync_star_iterator_yield_star_iterable();
} else {
field = IG->object_store()->sync_star_iterator_current();
}
instructions += B->StoreFieldGuarded(field);
instructions += B->Constant(Bool::True());
instructions +=
B->Suspend(pos, SuspendInstr::StubId::kSuspendSyncStarAtYield);
instructions += Drop();
} else {
UNREACHABLE();
}
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildVariableDeclaration(
TokenPosition* position) {
intptr_t kernel_position_no_tag = ReaderOffset() + data_program_offset_;
LocalVariable* variable = LookupVariable(kernel_position_no_tag);
VariableDeclarationHelper helper(this);
helper.ReadUntilExcluding(VariableDeclarationHelper::kType);
T.BuildType(); // read type.
bool has_initializer = (ReadTag() != kNothing);
Fragment instructions;
if (variable->is_late()) {
// TODO(liama): Treat the field as non-late if the initializer is trivial.
if (has_initializer) {
SkipExpression();
}
instructions += Constant(Object::sentinel());
} else if (!has_initializer) {
instructions += NullConstant();
} else {
// Initializer
instructions += BuildExpression(); // read (actual) initializer.
}
// Use position of equal sign if it exists. If the equal sign does not exist
// use the position of the identifier.
const TokenPosition debug_position = helper.equals_position_.IsReal()
? helper.equals_position_
: helper.position_;
if (position != nullptr) *position = helper.position_;
if (debug_position.IsDebugPause() && !helper.IsHoisted() &&
// We always make it possible to add a breakpoint on the equals sign if it
// exists.
(helper.equals_position_.IsReal() ||
NeedsDebugStepCheck(stack(), debug_position))) {
instructions = DebugStepCheck(debug_position) + instructions;
}
instructions += StoreLocal(helper.position_, variable);
instructions += Drop();
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildFunctionDeclaration(
TokenPosition* position) {
const intptr_t offset = ReaderOffset() - 1; // Include the tag.
const TokenPosition pos = ReadPosition();
if (position != nullptr) *position = pos;
const intptr_t variable_offset = ReaderOffset() + data_program_offset_;
SkipVariableDeclaration();
Fragment instructions = DebugStepCheck(pos);
instructions += BuildFunctionNode(offset);
instructions += StoreLocal(pos, LookupVariable(variable_offset));
instructions += Drop();
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildFunctionNode(
intptr_t func_decl_offset) {
const intptr_t func_node_offset = ReaderOffset();
const auto& member_function =
Function::Handle(Z, parsed_function()->function().GetOutermostFunction());
const Function& function = Function::ZoneHandle(
Z, KernelLoader::GetClosureFunction(
thread(), func_decl_offset, member_function,
parsed_function()->function(), closure_owner_));
if (function.context_scope() == ContextScope::null()) {
SafepointWriteRwLocker ml(thread(),
thread()->isolate_group()->program_lock());
if (function.context_scope() == ContextScope::null()) {
for (intptr_t i = 0; i < scopes()->function_scopes.length(); ++i) {
if (scopes()->function_scopes[i].kernel_offset !=
function.kernel_offset()) {
continue;
}
LocalScope* scope = scopes()->function_scopes[i].scope;
const ContextScope& context_scope = ContextScope::Handle(
Z, scope->PreserveOuterScope(function,
flow_graph_builder_->context_depth_));
function.set_context_scope(context_scope);
}
}
}
ASSERT(function.kernel_offset() == func_node_offset);
SkipFunctionNode();
Fragment instructions;
instructions += Constant(function);
if (scopes()->IsClosureWithEmptyContext(func_node_offset)) {
instructions += NullConstant();
} else {
instructions += LoadLocal(parsed_function()->current_context_var());
}
// The function signature can have uninstantiated class type parameters.
const bool has_instantiator_type_args =
!function.HasInstantiatedSignature(kCurrentClass);
if (has_instantiator_type_args) {
instructions += LoadInstantiatorTypeArguments();
}
instructions += flow_graph_builder_->AllocateClosure(
function.token_pos(), has_instantiator_type_args, function.IsGeneric(),
/*is_tear_off=*/false);
LocalVariable* closure = MakeTemporary();
// TODO(30455): We only need to save these if the closure uses any captured
// type parameters.
instructions += LoadLocal(closure);
instructions += LoadFunctionTypeArguments();
instructions += flow_graph_builder_->StoreNativeField(
Slot::Closure_function_type_arguments(),
StoreFieldInstr::Kind::kInitializing);
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildNativeEffect() {
const intptr_t argc = ReadUInt(); // Read argument count.
ASSERT(argc == 1); // Native side effect to ignore.
const intptr_t list_length = ReadListLength(); // Read types list length.
ASSERT(list_length == 0);
const intptr_t positional_count =
ReadListLength(); // Read positional argument count.
ASSERT(positional_count == 1);
BuildExpression(); // Consume expression but don't save the fragment.
Pop(); // Restore the stack.
const intptr_t named_args_len =
ReadListLength(); // Skip empty named arguments.
ASSERT(named_args_len == 0);
Fragment code;
code += NullConstant(); // Return type is void.
return code;
}
Fragment StreamingFlowGraphBuilder::BuildReachabilityFence() {
const intptr_t argc = ReadUInt(); // Read argument count.
ASSERT(argc == 1); // LoadField, can be late.
const intptr_t list_length = ReadListLength(); // Read types list length.
ASSERT(list_length == 0);
const intptr_t positional_count = ReadListLength();
ASSERT(positional_count == 1);
// The CFE transform only generates a subset of argument expressions:
// either variable get or `this`. However, subsequent transforms can
// generate different expressions, including: constant expressions.
// So, build an arbitrary expression here instead.
TokenPosition* position = nullptr;
Fragment code = BuildExpression(position);
const intptr_t named_args_len = ReadListLength();
ASSERT(named_args_len == 0);
code <<= new (Z) ReachabilityFenceInstr(Pop());
code += NullConstant(); // Return type is void.
return code;
}
static void ReportIfNotNull(const char* error) {
if (error != nullptr) {
const auto& language_error = Error::Handle(
LanguageError::New(String::Handle(String::New(error, Heap::kOld)),
Report::kError, Heap::kOld));
Report::LongJump(language_error);
}
}
Fragment StreamingFlowGraphBuilder::BuildLoadStoreAbiSpecificInt(
bool is_store,
bool at_index) {
const intptr_t argument_count = ReadUInt(); // Read argument count.
const intptr_t expected_argument_count = 2 // TypedDataBase, offset
+ (at_index ? 1 : 0) // index
+ (is_store ? 1 : 0); // value
ASSERT_EQUAL(argument_count, expected_argument_count);
const intptr_t list_length = ReadListLength();
ASSERT_EQUAL(list_length, 1);
// Read types.
const TypeArguments& type_arguments = T.BuildTypeArguments(list_length);
const AbstractType& type_argument =
AbstractType::Handle(type_arguments.TypeAt(0));
// AbiSpecificTypes can have an incomplete mapping.
const char* error = nullptr;
const auto* native_type =
compiler::ffi::NativeType::FromAbstractType(zone_, type_argument, &error);
ReportIfNotNull(error);
Fragment code;
// Read positional argument count.
const intptr_t positional_count = ReadListLength();
ASSERT(positional_count == argument_count);
code += BuildExpression(); // Argument 1: typedDataBase.
code += BuildExpression(); // Argument 2: offsetInBytes
if (at_index) {
code += BuildExpression(); // Argument 3: index
code += IntConstant(native_type->SizeInBytes());
code += B->BinaryIntegerOp(Token::kMUL, kTagged, /* truncate= */ true);
code += B->BinaryIntegerOp(Token::kADD, kTagged, /* truncate= */ true);
}
if (is_store) {
code += BuildExpression(); // Argument 4: value
}
// Skip (empty) named arguments list.
const intptr_t named_args_len = ReadListLength();
ASSERT(named_args_len == 0);
// This call site is not guaranteed to be optimized. So, do a call to the
// correct force optimized function instead of compiling the body.
MethodRecognizer::Kind kind;
if (is_store) {
kind = compiler::ffi::FfiStore(*native_type);
} else {
kind = compiler::ffi::FfiLoad(*native_type);
}
const char* function_name = MethodRecognizer::KindToFunctionNameCString(kind);
const Library& ffi_library = Library::Handle(Z, Library::FfiLibrary());
const Function& target = Function::ZoneHandle(
Z, ffi_library.LookupFunctionAllowPrivate(
String::Handle(Z, String::New(function_name))));
ASSERT(!target.IsNull());
Array& argument_names = Array::ZoneHandle(Z);
const intptr_t static_call_arg_count = 2 + (is_store ? 1 : 0);
code += StaticCall(TokenPosition::kNoSource, target, static_call_arg_count,
argument_names, ICData::kStatic);
return code;
}
Fragment StreamingFlowGraphBuilder::BuildFfiCall() {
const intptr_t argc = ReadUInt(); // Read argument count.
ASSERT(argc == 1); // Target pointer.
const intptr_t list_length = ReadListLength(); // Read types list length.
T.BuildTypeArguments(list_length); // Read types.
// Read positional argument count.
const intptr_t positional_count = ReadListLength();
ASSERT(positional_count == argc);
Fragment code;
// Push the target function pointer passed as Pointer object.
code += BuildExpression();
// This can only be Pointer, so the data field points to unmanaged memory.
code += LoadNativeField(Slot::PointerBase_data(),
InnerPointerAccess::kCannotBeInnerPointer);
// Skip (empty) named arguments list.
const intptr_t named_args_len = ReadListLength();
ASSERT(named_args_len == 0);
const auto& native_type = FunctionType::ZoneHandle(
Z, parsed_function()->function().FfiCSignature());
// AbiSpecificTypes can have an incomplete mapping.
const char* error = nullptr;
compiler::ffi::NativeFunctionTypeFromFunctionType(Z, native_type, &error);
if (error != nullptr) {
const auto& language_error = Error::Handle(
LanguageError::New(String::Handle(String::New(error, Heap::kOld)),
Report::kError, Heap::kOld));
Report::LongJump(language_error);
}
code += B->FfiCallFunctionBody(parsed_function()->function(), native_type,
/*first_argument_parameter_offset=*/1);
ASSERT(code.is_closed());
NullConstant(); // Maintain stack balance.
return code;
}
Fragment StreamingFlowGraphBuilder::BuildArgumentsCachableIdempotentCall(
intptr_t* argument_count) {
*argument_count = ReadUInt(); // read arguments count.
// List of types.
const intptr_t types_list_length = ReadListLength();
if (types_list_length != 0) {
FATAL("Type arguments for vm:cachable-idempotent not (yet) supported.");
}
Fragment code;
// List of positional.
intptr_t positional_list_length = ReadListLength();
for (intptr_t i = 0; i < positional_list_length; ++i) {
code += BuildExpression();
Definition* target_def = B->Peek();
if (!target_def->IsConstant()) {
FATAL(
"Arguments for vm:cachable-idempotent must be const, argument on "
"index %" Pd " is not.",
i);
}
}
// List of named.
const intptr_t named_args_len = ReadListLength();
if (named_args_len != 0) {
FATAL("Named arguments for vm:cachable-idempotent not (yet) supported.");
}
return code;
}
Fragment StreamingFlowGraphBuilder::BuildCachableIdempotentCall(
TokenPosition position,
const Function& target) {
// The call site must me fore optimized because the cache is untagged.
if (!parsed_function()->function().ForceOptimize()) {
FATAL(
"vm:cachable-idempotent functions can only be called from "
"vm:force-optimize functions.");
}
const auto& target_result_type = AbstractType::Handle(target.result_type());
if (!target_result_type.IsIntType()) {
FATAL("The return type vm:cachable-idempotent functions must be int.")
}
Fragment code;
Array& argument_names = Array::ZoneHandle(Z);
intptr_t argument_count;
code += BuildArgumentsCachableIdempotentCall(&argument_count);
code += flow_graph_builder_->CachableIdempotentCall(
position, kUnboxedAddress, target, argument_count, argument_names,
/*type_args_len=*/0);
return code;
}
Fragment StreamingFlowGraphBuilder::BuildFfiNativeCallbackFunction(
FfiCallbackKind kind) {
// The call-site must look like this (guaranteed by the FE which inserts it):
//
// FfiCallbackKind::kIsolateLocalStaticCallback:
// _nativeCallbackFunction<NativeSignatureType>(target, exceptionalReturn)
//
// FfiCallbackKind::kIsolateGroupSharedStaticCallback:
// _nativeCallbackFunction<NativeSignatureType>(target, exceptionalReturn)
//
// FfiCallbackKind::kAsyncCallback:
// _nativeAsyncCallbackFunction<NativeSignatureType>()
//
// FfiCallbackKind::kIsolateLocalClosureCallback:
// _nativeIsolateLocalCallbackFunction<NativeSignatureType>(
// exceptionalReturn)
//
// FfiCallbackKind::kIsolateGroupSharedClosureCallback:
// _nativeIsolateGroupSharedCallbackFunction<NativeSignatureType>(
// exceptionalReturn)
//
// The FE also guarantees that the arguments are constants.
const bool has_target =
kind == FfiCallbackKind::kIsolateLocalStaticCallback ||
kind == FfiCallbackKind::kIsolateGroupSharedStaticCallback;
const bool has_exceptional_return = kind != FfiCallbackKind::kAsyncCallback;
const intptr_t expected_argc =
static_cast<int>(has_target) + static_cast<int>(has_exceptional_return);
const intptr_t argc = ReadUInt(); // Read argument count.
ASSERT(argc == expected_argc);
const intptr_t list_length = ReadListLength(); // Read types list length.
ASSERT(list_length == 1); // The native signature.
const TypeArguments& type_arguments =
T.BuildTypeArguments(list_length); // Read types.
ASSERT(type_arguments.Length() == 1 && type_arguments.IsInstantiated());
const FunctionType& native_sig =
FunctionType::CheckedHandle(Z, type_arguments.TypeAt(0));
Fragment code;
const intptr_t positional_count =
ReadListLength(); // Read positional argument count.
ASSERT(positional_count == expected_argc);
// Read target expression and extract the target function.
Function& target = Function::Handle(Z, Function::null());
Instance& exceptional_return = Instance::ZoneHandle(Z, Instance::null());
if (has_target) {
// Build target argument.
code += BuildExpression();
Definition* target_def = B->Peek();
ASSERT(target_def->IsConstant());
const Closure& target_closure =
Closure::Cast(target_def->AsConstant()->value());
ASSERT(!target_closure.IsNull());
target = target_closure.function();
ASSERT(!target.IsNull() && target.IsImplicitClosureFunction());
target = target.parent_function();
code += Drop();
}
if (has_exceptional_return) {
// Build exceptionalReturn argument.
code += BuildExpression();
Definition* exceptional_return_def = B->Peek();
ASSERT(exceptional_return_def->IsConstant());
exceptional_return ^= exceptional_return_def->AsConstant()->value().ptr();
code += Drop();
}
const intptr_t named_args_len =
ReadListLength(); // Skip (empty) named arguments list.
ASSERT(named_args_len == 0);
// AbiSpecificTypes can have an incomplete mapping.
const char* error = nullptr;
compiler::ffi::NativeFunctionTypeFromFunctionType(zone_, native_sig, &error);
ReportIfNotNull(error);
const Function& result = Function::ZoneHandle(
Z, compiler::ffi::NativeCallbackFunction(native_sig, target,
exceptional_return, kind));
code += Constant(result);
return code;
}
Fragment StreamingFlowGraphBuilder::BuildFfiNativeAddressOf() {
const intptr_t argc = ReadUInt();
ASSERT(argc == 1);
const intptr_t types_length = ReadListLength();
ASSERT(types_length == 1);
T.BuildTypeArguments(types_length);
const intptr_t positional_count = ReadListLength();
ASSERT(positional_count == 1);
Fragment frag = BuildExpression();
ASSERT(frag.entry->IsConstant());
const auto& native_annotation =
Instance::Cast(frag.entry->AsConstant()->value());
Drop();
const auto& pointer_class =
Class::ZoneHandle(Z, IG->object_store()->ffi_pointer_class());
const auto& type_arguments =
TypeArguments::ZoneHandle(Z, IG->object_store()->type_argument_never());
Fragment code = Constant(type_arguments);
code += AllocateObject(TokenPosition::kNoSource, pointer_class, 1);
code += LoadLocal(MakeTemporary()); // Duplicate Pointer.
// FfiNativeLookupAddress pushes an unboxed value, which is safe even in
// unoptimized mode because then there is no reordering and we're consuming
// the value directly.
code += flow_graph_builder_->FfiNativeLookupAddress(native_annotation);
code += flow_graph_builder_->StoreNativeField(
Slot::PointerBase_data(), InnerPointerAccess::kCannotBeInnerPointer,
StoreFieldInstr::Kind::kInitializing);
const intptr_t named_arg_count = ReadListLength();
ASSERT(named_arg_count == 0);
return code;
}
} // namespace kernel
} // namespace dart