blob: 88dbd35c1cb1438fbd83ae402ce14a227e0f8082 [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/aot/precompiler.h"
#include "vm/compiler/frontend/kernel_to_il.h"
#include "vm/compiler/backend/il.h"
#include "vm/compiler/backend/il_printer.h"
#include "vm/compiler/frontend/kernel_binary_flowgraph.h"
#include "vm/compiler/frontend/kernel_translation_helper.h"
#include "vm/compiler/frontend/prologue_builder.h"
#include "vm/compiler/jit/compiler.h"
#include "vm/kernel_loader.h"
#include "vm/longjump.h"
#include "vm/object_store.h"
#include "vm/report.h"
#include "vm/resolver.h"
#include "vm/stack_frame.h"
#if !defined(DART_PRECOMPILED_RUNTIME)
namespace dart {
namespace kernel {
#define Z (zone_)
#define H (translation_helper_)
#define T (type_translator_)
#define I Isolate::Current()
FlowGraphBuilder::FlowGraphBuilder(
ParsedFunction* parsed_function,
ZoneGrowableArray<const ICData*>* ic_data_array,
ZoneGrowableArray<intptr_t>* context_level_array,
InlineExitCollector* exit_collector,
bool optimizing,
intptr_t osr_id,
intptr_t first_block_id,
bool inlining_unchecked_entry)
: BaseFlowGraphBuilder(parsed_function,
first_block_id - 1,
osr_id,
context_level_array,
exit_collector,
inlining_unchecked_entry),
translation_helper_(Thread::Current()),
thread_(translation_helper_.thread()),
zone_(translation_helper_.zone()),
parsed_function_(parsed_function),
optimizing_(optimizing),
ic_data_array_(*ic_data_array),
next_function_id_(0),
try_depth_(0),
catch_depth_(0),
for_in_depth_(0),
graph_entry_(NULL),
scopes_(NULL),
breakable_block_(NULL),
switch_block_(NULL),
try_catch_block_(NULL),
try_finally_block_(NULL),
catch_block_(NULL) {
const Script& script =
Script::Handle(Z, parsed_function->function().script());
H.InitFromScript(script);
}
FlowGraphBuilder::~FlowGraphBuilder() {}
Fragment FlowGraphBuilder::EnterScope(intptr_t kernel_offset,
intptr_t* num_context_variables) {
Fragment instructions;
const intptr_t context_size =
scopes_->scopes.Lookup(kernel_offset)->num_context_variables();
if (context_size > 0) {
instructions += PushContext(context_size);
instructions += Drop();
}
if (num_context_variables != NULL) {
*num_context_variables = context_size;
}
return instructions;
}
Fragment FlowGraphBuilder::ExitScope(intptr_t kernel_offset) {
Fragment instructions;
const intptr_t context_size =
scopes_->scopes.Lookup(kernel_offset)->num_context_variables();
if (context_size > 0) {
instructions += PopContext();
}
return instructions;
}
Fragment FlowGraphBuilder::AdjustContextTo(int depth) {
ASSERT(depth <= context_depth_ && depth >= 0);
Fragment instructions;
if (depth < context_depth_) {
instructions += LoadContextAt(depth);
instructions += StoreLocal(TokenPosition::kNoSource,
parsed_function_->current_context_var());
instructions += Drop();
context_depth_ = depth;
}
return instructions;
}
Fragment FlowGraphBuilder::PushContext(int size) {
ASSERT(size > 0);
Fragment instructions = AllocateContext(size);
LocalVariable* context = MakeTemporary();
instructions += LoadLocal(context);
instructions += LoadLocal(parsed_function_->current_context_var());
instructions +=
StoreInstanceField(TokenPosition::kNoSource, Context::parent_offset());
instructions += StoreLocal(TokenPosition::kNoSource,
parsed_function_->current_context_var());
++context_depth_;
return instructions;
}
Fragment FlowGraphBuilder::PopContext() {
return AdjustContextTo(context_depth_ - 1);
}
Fragment FlowGraphBuilder::LoadInstantiatorTypeArguments() {
// TODO(27590): We could use `active_class_->IsGeneric()`.
Fragment instructions;
if (scopes_->type_arguments_variable != NULL) {
#ifdef DEBUG
Function& function =
Function::Handle(Z, parsed_function_->function().raw());
while (function.IsClosureFunction()) {
function = function.parent_function();
}
ASSERT(function.IsFactory());
#endif
instructions += LoadLocal(scopes_->type_arguments_variable);
} else if (scopes_->this_variable != NULL &&
active_class_.ClassNumTypeArguments() > 0) {
ASSERT(!parsed_function_->function().IsFactory());
instructions += LoadLocal(scopes_->this_variable);
instructions += LoadNativeField(
NativeFieldDesc::GetTypeArgumentsFieldFor(Z, *active_class_.klass));
} else {
instructions += NullConstant();
}
return instructions;
}
// This function is responsible for pushing a type arguments vector which
// contains all type arguments of enclosing functions prepended to the type
// arguments of the current function.
Fragment FlowGraphBuilder::LoadFunctionTypeArguments() {
Fragment instructions;
if (!FLAG_reify_generic_functions) {
instructions += NullConstant();
return instructions;
}
const Function& function = parsed_function_->function();
if (function.IsGeneric() || function.HasGenericParent()) {
ASSERT(parsed_function_->function_type_arguments() != NULL);
instructions += LoadLocal(parsed_function_->function_type_arguments());
} else {
instructions += NullConstant();
}
return instructions;
}
Fragment FlowGraphBuilder::TranslateInstantiatedTypeArguments(
const TypeArguments& type_arguments) {
Fragment instructions;
if (type_arguments.IsNull() || type_arguments.IsInstantiated()) {
// There are no type references to type parameters so we can just take it.
instructions += Constant(type_arguments);
} else {
// The [type_arguments] vector contains a type reference to a type
// parameter we need to resolve it.
const bool use_instantiator =
type_arguments.IsUninstantiatedIdentity() ||
type_arguments.CanShareInstantiatorTypeArguments(*active_class_.klass);
if (use_instantiator) {
// If the instantiator type arguments are just passed on, we don't need to
// resolve the type parameters.
//
// This is for example the case here:
// class Foo<T> {
// newList() => new List<T>();
// }
// We just use the type argument vector from the [Foo] object and pass it
// directly to the `new List<T>()` factory constructor.
instructions += LoadInstantiatorTypeArguments();
} else {
// Otherwise we need to resolve [TypeParameterType]s in the type
// expression based on the current instantiator type argument vector.
if (!type_arguments.IsInstantiated(kCurrentClass)) {
instructions += LoadInstantiatorTypeArguments();
} else {
instructions += NullConstant();
}
if (!type_arguments.IsInstantiated(kFunctions)) {
instructions += LoadFunctionTypeArguments();
} else {
instructions += NullConstant();
}
instructions += InstantiateTypeArguments(type_arguments);
}
}
return instructions;
}
Fragment FlowGraphBuilder::AllocateObject(TokenPosition position,
const Class& klass,
intptr_t argument_count) {
ArgumentArray arguments = GetArguments(argument_count);
AllocateObjectInstr* allocate =
new (Z) AllocateObjectInstr(position, klass, arguments);
Push(allocate);
return Fragment(allocate);
}
Fragment FlowGraphBuilder::AllocateObject(const Class& klass,
const Function& closure_function) {
ArgumentArray arguments = new (Z) ZoneGrowableArray<PushArgumentInstr*>(Z, 0);
AllocateObjectInstr* allocate =
new (Z) AllocateObjectInstr(TokenPosition::kNoSource, klass, arguments);
allocate->set_closure_function(closure_function);
Push(allocate);
return Fragment(allocate);
}
Fragment FlowGraphBuilder::CatchBlockEntry(const Array& handler_types,
intptr_t handler_index,
bool needs_stacktrace,
bool is_synthesized) {
LocalVariable* exception_var = CurrentException();
LocalVariable* stacktrace_var = CurrentStackTrace();
LocalVariable* raw_exception_var = CurrentRawException();
LocalVariable* raw_stacktrace_var = CurrentRawStackTrace();
CatchBlockEntryInstr* entry = new (Z) CatchBlockEntryInstr(
TokenPosition::kNoSource, // Token position of catch block.
is_synthesized, // whether catch block was synthesized by FE compiler
AllocateBlockId(), CurrentTryIndex(), graph_entry_, handler_types,
handler_index, needs_stacktrace, GetNextDeoptId(), exception_var,
stacktrace_var, raw_exception_var, raw_stacktrace_var);
graph_entry_->AddCatchEntry(entry);
Fragment instructions(entry);
// Auxiliary variables introduced by the try catch can be captured if we are
// inside a function with yield/resume points. In this case we first need
// to restore the context to match the context at entry into the closure.
const bool should_restore_closure_context =
CurrentException()->is_captured() || CurrentCatchContext()->is_captured();
LocalVariable* context_variable = parsed_function_->current_context_var();
if (should_restore_closure_context) {
ASSERT(parsed_function_->function().IsClosureFunction());
LocalScope* scope = parsed_function_->node_sequence()->scope();
LocalVariable* closure_parameter = scope->VariableAt(0);
ASSERT(!closure_parameter->is_captured());
instructions += LoadLocal(closure_parameter);
instructions += LoadField(Closure::context_offset());
instructions += StoreLocal(TokenPosition::kNoSource, context_variable);
instructions += Drop();
}
if (exception_var->is_captured()) {
instructions += LoadLocal(context_variable);
instructions += LoadLocal(raw_exception_var);
instructions += StoreInstanceField(
TokenPosition::kNoSource,
Context::variable_offset(exception_var->index().value()));
}
if (stacktrace_var->is_captured()) {
instructions += LoadLocal(context_variable);
instructions += LoadLocal(raw_stacktrace_var);
instructions += StoreInstanceField(
TokenPosition::kNoSource,
Context::variable_offset(stacktrace_var->index().value()));
}
// :saved_try_context_var can be captured in the context of
// of the closure, in this case CatchBlockEntryInstr restores
// :current_context_var to point to closure context in the
// same way as normal function prologue does.
// Update current context depth to reflect that.
const intptr_t saved_context_depth = context_depth_;
ASSERT(!CurrentCatchContext()->is_captured() ||
CurrentCatchContext()->owner()->context_level() == 0);
context_depth_ = 0;
instructions += LoadLocal(CurrentCatchContext());
instructions += StoreLocal(TokenPosition::kNoSource,
parsed_function_->current_context_var());
instructions += Drop();
context_depth_ = saved_context_depth;
return instructions;
}
Fragment FlowGraphBuilder::TryCatch(int try_handler_index) {
// The body of the try needs to have it's own block in order to get a new try
// index.
//
// => We therefore create a block for the body (fresh try index) and another
// join block (with current try index).
Fragment body;
JoinEntryInstr* entry = new (Z)
JoinEntryInstr(AllocateBlockId(), try_handler_index, GetNextDeoptId());
body += LoadLocal(parsed_function_->current_context_var());
body += StoreLocal(TokenPosition::kNoSource, CurrentCatchContext());
body += Drop();
body += Goto(entry);
return Fragment(body.entry, entry);
}
Fragment FlowGraphBuilder::CheckStackOverflowInPrologue(
TokenPosition position) {
if (IsInlining()) {
// If we are inlining don't actually attach the stack check. We must still
// create the stack check in order to allocate a deopt id.
CheckStackOverflow(position);
return Fragment();
}
return CheckStackOverflow(position);
}
Fragment FlowGraphBuilder::CloneContext(intptr_t num_context_variables) {
LocalVariable* context_variable = parsed_function_->current_context_var();
Fragment instructions = LoadLocal(context_variable);
CloneContextInstr* clone_instruction = new (Z) CloneContextInstr(
TokenPosition::kNoSource, Pop(), num_context_variables, GetNextDeoptId());
instructions <<= clone_instruction;
Push(clone_instruction);
instructions += StoreLocal(TokenPosition::kNoSource, context_variable);
instructions += Drop();
return instructions;
}
Fragment FlowGraphBuilder::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 InferredTypeMetadata* result_type,
bool use_unchecked_entry,
const CallSiteAttributesMetadata* call_site_attrs) {
const intptr_t total_count = argument_count + (type_args_len > 0 ? 1 : 0);
ArgumentArray arguments = GetArguments(total_count);
InstanceCallInstr* call = new (Z)
InstanceCallInstr(position, name, kind, arguments, type_args_len,
argument_names, checked_argument_count, ic_data_array_,
GetNextDeoptId(), interface_target);
if ((result_type != NULL) && !result_type->IsTrivial()) {
call->SetResultType(Z, result_type->ToCompileType(Z));
}
if (use_unchecked_entry) {
call->set_entry_kind(Code::EntryKind::kUnchecked);
}
if (call_site_attrs != nullptr && call_site_attrs->receiver_type != nullptr &&
call_site_attrs->receiver_type->IsInstantiated()) {
call->set_static_receiver_type(call_site_attrs->receiver_type);
}
Push(call);
return Fragment(call);
}
Fragment FlowGraphBuilder::ClosureCall(TokenPosition position,
intptr_t type_args_len,
intptr_t argument_count,
const Array& argument_names,
bool is_statically_checked) {
Value* function = Pop();
const intptr_t total_count = argument_count + (type_args_len > 0 ? 1 : 0);
ArgumentArray arguments = GetArguments(total_count);
ClosureCallInstr* call = new (Z)
ClosureCallInstr(function, arguments, type_args_len, argument_names,
position, GetNextDeoptId(),
is_statically_checked ? Code::EntryKind::kUnchecked
: Code::EntryKind::kNormal);
Push(call);
return Fragment(call);
}
Fragment FlowGraphBuilder::RethrowException(TokenPosition position,
int catch_try_index) {
Fragment instructions;
instructions += Drop();
instructions += Drop();
instructions += Fragment(new (Z) ReThrowInstr(position, catch_try_index,
GetNextDeoptId()))
.closed();
// Use it's side effect of leaving a constant on the stack (does not change
// the graph).
NullConstant();
pending_argument_count_ -= 2;
return instructions;
}
Fragment FlowGraphBuilder::LoadClassId() {
LoadClassIdInstr* load = new (Z) LoadClassIdInstr(Pop());
Push(load);
return Fragment(load);
}
Fragment FlowGraphBuilder::LoadLocal(LocalVariable* variable) {
if (variable->is_captured()) {
Fragment instructions;
instructions += LoadContextAt(variable->owner()->context_level());
instructions +=
LoadField(Context::variable_offset(variable->index().value()));
return instructions;
} else {
return BaseFlowGraphBuilder::LoadLocal(variable);
}
}
Fragment FlowGraphBuilder::InitStaticField(const Field& field) {
InitStaticFieldInstr* init = new (Z)
InitStaticFieldInstr(Pop(), MayCloneField(field), GetNextDeoptId());
return Fragment(init);
}
Fragment FlowGraphBuilder::NativeCall(const String* name,
const Function* function) {
InlineBailout("kernel::FlowGraphBuilder::NativeCall");
const intptr_t num_args =
function->NumParameters() +
((function->IsGeneric() && FLAG_reify_generic_functions) ? 1 : 0);
ArgumentArray arguments = GetArguments(num_args);
NativeCallInstr* call =
new (Z) NativeCallInstr(name, function, FLAG_link_natives_lazily,
function->end_token_pos(), arguments);
Push(call);
return Fragment(call);
}
Fragment FlowGraphBuilder::Return(TokenPosition position,
bool omit_result_type_check /* = false */) {
Fragment instructions;
const Function& function = parsed_function_->function();
// Emit a type check of the return type in checked mode for all functions
// and in strong mode for native functions.
if (!omit_result_type_check &&
(I->type_checks() || (function.is_native() && FLAG_strong))) {
const AbstractType& return_type =
AbstractType::Handle(Z, function.result_type());
instructions += CheckAssignable(return_type, Symbols::FunctionResult());
}
if (NeedsDebugStepCheck(function, position)) {
instructions += DebugStepCheck(position);
}
if (FLAG_causal_async_stacks &&
(function.IsAsyncClosure() || function.IsAsyncGenClosure())) {
// We are returning from an asynchronous closure. Before we do that, be
// sure to clear the thread's asynchronous stack trace.
const Function& target = Function::ZoneHandle(
Z, I->object_store()->async_clear_thread_stack_trace());
ASSERT(!target.IsNull());
instructions += StaticCall(TokenPosition::kNoSource, target,
/* argument_count = */ 0, ICData::kStatic);
instructions += Drop();
}
instructions += BaseFlowGraphBuilder::Return(position);
return instructions;
}
Fragment FlowGraphBuilder::CheckNull(TokenPosition position,
LocalVariable* receiver,
const String& function_name,
bool clear_the_temp /* = true */) {
Fragment instructions = LoadLocal(receiver);
CheckNullInstr* check_null =
new (Z) CheckNullInstr(Pop(), function_name, GetNextDeoptId(), position);
instructions <<= check_null;
if (clear_the_temp) {
// Null out receiver to make sure it is not saved into the frame before
// doing the call.
instructions += NullConstant();
instructions += StoreLocal(TokenPosition::kNoSource, receiver);
instructions += Drop();
}
return instructions;
}
Fragment FlowGraphBuilder::StaticCall(TokenPosition position,
const Function& target,
intptr_t argument_count,
ICData::RebindRule rebind_rule) {
return StaticCall(position, target, argument_count, Array::null_array(),
rebind_rule);
}
static intptr_t GetResultCidOfListFactory(Zone* zone,
const Function& function,
intptr_t argument_count) {
if (!function.IsFactory()) {
return kDynamicCid;
}
const Class& owner = Class::Handle(zone, function.Owner());
if ((owner.library() != Library::CoreLibrary()) &&
(owner.library() != Library::TypedDataLibrary())) {
return kDynamicCid;
}
if ((owner.Name() == Symbols::List().raw()) &&
(function.name() == Symbols::ListFactory().raw())) {
ASSERT(argument_count == 1 || argument_count == 2);
return (argument_count == 1) ? kGrowableObjectArrayCid : kArrayCid;
}
return FactoryRecognizer::ResultCid(function);
}
void FlowGraphBuilder::SetResultTypeForStaticCall(
StaticCallInstr* call,
const Function& target,
intptr_t argument_count,
const InferredTypeMetadata* result_type) {
const intptr_t list_cid =
GetResultCidOfListFactory(Z, target, argument_count);
if (list_cid != kDynamicCid) {
ASSERT((result_type == NULL) || (result_type->cid == kDynamicCid) ||
(result_type->cid == list_cid));
call->SetResultType(Z, CompileType::FromCid(list_cid));
call->set_is_known_list_constructor(true);
return;
}
if (target.recognized_kind() != MethodRecognizer::kUnknown) {
intptr_t recognized_cid = MethodRecognizer::ResultCid(target);
if (recognized_cid != kDynamicCid) {
ASSERT((result_type == NULL) || (result_type->cid == kDynamicCid) ||
(result_type->cid == recognized_cid));
call->SetResultType(Z, CompileType::FromCid(recognized_cid));
return;
}
}
if ((result_type != NULL) && !result_type->IsTrivial()) {
call->SetResultType(Z, result_type->ToCompileType(Z));
}
}
Fragment FlowGraphBuilder::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) {
const intptr_t total_count = argument_count + (type_args_count > 0 ? 1 : 0);
ArgumentArray arguments = GetArguments(total_count);
StaticCallInstr* call = new (Z)
StaticCallInstr(position, target, type_args_count, argument_names,
arguments, ic_data_array_, GetNextDeoptId(), rebind_rule);
SetResultTypeForStaticCall(call, target, argument_count, result_type);
Push(call);
return Fragment(call);
}
Fragment FlowGraphBuilder::StoreInstanceFieldGuarded(
const Field& field,
bool is_initialization_store) {
Fragment instructions;
const AbstractType& dst_type = AbstractType::ZoneHandle(Z, field.type());
if (I->type_checks()) {
instructions +=
CheckAssignable(dst_type, String::ZoneHandle(Z, field.name()));
}
instructions += BaseFlowGraphBuilder::StoreInstanceFieldGuarded(
field, is_initialization_store);
return instructions;
}
Fragment FlowGraphBuilder::StringInterpolate(TokenPosition position) {
Value* array = Pop();
StringInterpolateInstr* interpolate =
new (Z) StringInterpolateInstr(array, position, GetNextDeoptId());
Push(interpolate);
return Fragment(interpolate);
}
Fragment FlowGraphBuilder::StringInterpolateSingle(TokenPosition position) {
const int kTypeArgsLen = 0;
const int kNumberOfArguments = 1;
const Array& kNoArgumentNames = Object::null_array();
const Class& cls =
Class::Handle(Library::LookupCoreClass(Symbols::StringBase()));
ASSERT(!cls.IsNull());
const Function& function = Function::ZoneHandle(
Z, Resolver::ResolveStatic(
cls, Library::PrivateCoreLibName(Symbols::InterpolateSingle()),
kTypeArgsLen, kNumberOfArguments, kNoArgumentNames));
Fragment instructions;
instructions += PushArgument();
instructions +=
StaticCall(position, function, /* argument_count = */ 1, ICData::kStatic);
return instructions;
}
Fragment FlowGraphBuilder::ThrowTypeError() {
const Class& klass =
Class::ZoneHandle(Z, Library::LookupCoreClass(Symbols::TypeError()));
ASSERT(!klass.IsNull());
GrowableHandlePtrArray<const String> pieces(Z, 3);
pieces.Add(Symbols::TypeError());
pieces.Add(Symbols::Dot());
pieces.Add(H.DartSymbolObfuscate("_create"));
const Function& constructor = Function::ZoneHandle(
Z, klass.LookupConstructorAllowPrivate(
String::ZoneHandle(Z, Symbols::FromConcatAll(thread_, pieces))));
ASSERT(!constructor.IsNull());
const String& url = H.DartString(
parsed_function_->function().ToLibNamePrefixedQualifiedCString(),
Heap::kOld);
Fragment instructions;
// Create instance of _FallThroughError
instructions += AllocateObject(TokenPosition::kNoSource, klass, 0);
LocalVariable* instance = MakeTemporary();
// Call _TypeError._create constructor.
instructions += LoadLocal(instance);
instructions += PushArgument(); // this
instructions += Constant(url);
instructions += PushArgument(); // url
instructions += NullConstant();
instructions += PushArgument(); // line
instructions += IntConstant(0);
instructions += PushArgument(); // column
instructions += Constant(H.DartSymbolPlain("Malformed type."));
instructions += PushArgument(); // message
instructions += StaticCall(TokenPosition::kNoSource, constructor,
/* argument_count = */ 5, ICData::kStatic);
instructions += Drop();
// Throw the exception
instructions += PushArgument();
instructions += ThrowException(TokenPosition::kNoSource);
return instructions;
}
Fragment FlowGraphBuilder::ThrowNoSuchMethodError() {
const Class& klass = Class::ZoneHandle(
Z, Library::LookupCoreClass(Symbols::NoSuchMethodError()));
ASSERT(!klass.IsNull());
const Function& throw_function = Function::ZoneHandle(
Z, klass.LookupStaticFunctionAllowPrivate(Symbols::ThrowNew()));
ASSERT(!throw_function.IsNull());
Fragment instructions;
// Call NoSuchMethodError._throwNew static function.
instructions += NullConstant();
instructions += PushArgument(); // receiver
instructions += Constant(H.DartString("<unknown>", Heap::kOld));
instructions += PushArgument(); // memberName
instructions += IntConstant(-1);
instructions += PushArgument(); // invocation_type
instructions += NullConstant();
instructions += PushArgument(); // type arguments
instructions += NullConstant();
instructions += PushArgument(); // arguments
instructions += NullConstant();
instructions += PushArgument(); // argumentNames
instructions += StaticCall(TokenPosition::kNoSource, throw_function,
/* argument_count = */ 6, ICData::kStatic);
// Leave "result" on the stack since callers expect it to be there (even
// though the function will result in an exception).
return instructions;
}
LocalVariable* FlowGraphBuilder::LookupVariable(intptr_t kernel_offset) {
LocalVariable* local = scopes_->locals.Lookup(kernel_offset);
ASSERT(local != NULL);
return local;
}
FlowGraph* FlowGraphBuilder::BuildGraph() {
const Function& function = parsed_function_->function();
#ifdef DEBUG
// If we attached the native name to the function after it's creation (namely
// after reading the constant table from the kernel blob), we must have done
// so before building flow graph for the functions (since FGB depends needs
// the native name to be there).
const Script& script = Script::Handle(Z, function.script());
const KernelProgramInfo& info =
KernelProgramInfo::Handle(script.kernel_program_info());
ASSERT(info.IsNull() ||
info.potential_natives() == GrowableObjectArray::null());
#endif
StreamingFlowGraphBuilder streaming_flow_graph_builder(
this, ExternalTypedData::Handle(Z, function.KernelData()),
function.KernelDataProgramOffset());
return streaming_flow_graph_builder.BuildGraph();
}
Fragment FlowGraphBuilder::NativeFunctionBody(const Function& function,
LocalVariable* first_parameter) {
ASSERT(function.is_native());
// We explicitly build the graph for native functions in the same way that the
// from-source backend does. We should find a way to have a single component
// to build these graphs so that this code is not duplicated.
Fragment body;
const MethodRecognizer::Kind kind = MethodRecognizer::RecognizeKind(function);
bool omit_result_type_check = true;
switch (kind) {
case MethodRecognizer::kObjectEquals:
body += LoadLocal(scopes_->this_variable);
body += LoadLocal(first_parameter);
body += StrictCompare(Token::kEQ_STRICT);
break;
case MethodRecognizer::kStringBaseLength:
case MethodRecognizer::kStringBaseIsEmpty:
body += LoadLocal(scopes_->this_variable);
body += LoadNativeField(NativeFieldDesc::String_length());
if (kind == MethodRecognizer::kStringBaseIsEmpty) {
body += IntConstant(0);
body += StrictCompare(Token::kEQ_STRICT);
}
break;
case MethodRecognizer::kGrowableArrayLength:
body += LoadLocal(scopes_->this_variable);
body += LoadNativeField(NativeFieldDesc::GrowableObjectArray_length());
break;
case MethodRecognizer::kObjectArrayLength:
case MethodRecognizer::kImmutableArrayLength:
body += LoadLocal(scopes_->this_variable);
body += LoadNativeField(NativeFieldDesc::Array_length());
break;
case MethodRecognizer::kTypedDataLength:
body += LoadLocal(scopes_->this_variable);
body += LoadNativeField(NativeFieldDesc::TypedData_length());
break;
case MethodRecognizer::kClassIDgetID:
body += LoadLocal(first_parameter);
body += LoadClassId();
break;
case MethodRecognizer::kGrowableArrayCapacity:
body += LoadLocal(scopes_->this_variable);
body += LoadField(GrowableObjectArray::data_offset(), kArrayCid);
body += LoadNativeField(NativeFieldDesc::Array_length());
break;
case MethodRecognizer::kListFactory: {
// factory List<E>([int length]) {
// return (:arg_desc.positional_count == 2) ? new _List<E>(length)
// : new _GrowableList<E>(0);
// }
const Library& core_lib = Library::Handle(Z, Library::CoreLibrary());
TargetEntryInstr *allocate_non_growable, *allocate_growable;
body += LoadArgDescriptor();
body +=
LoadField(ArgumentsDescriptor::positional_count_offset(), kSmiCid);
body += IntConstant(2);
body += BranchIfStrictEqual(&allocate_non_growable, &allocate_growable);
JoinEntryInstr* join = BuildJoinEntry();
{
const Class& cls = Class::Handle(
Z, core_lib.LookupClass(
Library::PrivateCoreLibName(Symbols::_List())));
ASSERT(!cls.IsNull());
const Function& func = Function::ZoneHandle(
Z, cls.LookupFactoryAllowPrivate(Symbols::_ListFactory()));
ASSERT(!func.IsNull());
Fragment allocate(allocate_non_growable);
allocate += LoadLocal(scopes_->type_arguments_variable);
allocate += PushArgument();
allocate += LoadLocal(first_parameter);
allocate += PushArgument();
allocate +=
StaticCall(TokenPosition::kNoSource, func, 2, ICData::kStatic);
allocate += StoreLocal(TokenPosition::kNoSource,
parsed_function_->expression_temp_var());
allocate += Drop();
allocate += Goto(join);
}
{
const Class& cls = Class::Handle(
Z, core_lib.LookupClass(
Library::PrivateCoreLibName(Symbols::_GrowableList())));
ASSERT(!cls.IsNull());
const Function& func = Function::ZoneHandle(
Z, cls.LookupFactoryAllowPrivate(Symbols::_GrowableListFactory()));
ASSERT(!func.IsNull());
Fragment allocate(allocate_growable);
allocate += LoadLocal(scopes_->type_arguments_variable);
allocate += PushArgument();
allocate += IntConstant(0);
allocate += PushArgument();
allocate +=
StaticCall(TokenPosition::kNoSource, func, 2, ICData::kStatic);
allocate += StoreLocal(TokenPosition::kNoSource,
parsed_function_->expression_temp_var());
allocate += Drop();
allocate += Goto(join);
}
body = Fragment(body.entry, join);
body += LoadLocal(parsed_function_->expression_temp_var());
break;
}
case MethodRecognizer::kObjectArrayAllocate:
body += LoadLocal(scopes_->type_arguments_variable);
body += LoadLocal(first_parameter);
body += CreateArray();
break;
case MethodRecognizer::kLinkedHashMap_getIndex:
body += LoadLocal(scopes_->this_variable);
body += LoadNativeField(NativeFieldDesc::LinkedHashMap_index());
break;
case MethodRecognizer::kLinkedHashMap_setIndex:
body += LoadLocal(scopes_->this_variable);
body += LoadLocal(first_parameter);
body += StoreInstanceField(TokenPosition::kNoSource,
LinkedHashMap::index_offset());
body += NullConstant();
break;
case MethodRecognizer::kLinkedHashMap_getData:
body += LoadLocal(scopes_->this_variable);
body += LoadNativeField(NativeFieldDesc::LinkedHashMap_data());
break;
case MethodRecognizer::kLinkedHashMap_setData:
body += LoadLocal(scopes_->this_variable);
body += LoadLocal(first_parameter);
body += StoreInstanceField(TokenPosition::kNoSource,
LinkedHashMap::data_offset());
body += NullConstant();
break;
case MethodRecognizer::kLinkedHashMap_getHashMask:
body += LoadLocal(scopes_->this_variable);
body += LoadNativeField(NativeFieldDesc::LinkedHashMap_hash_mask());
break;
case MethodRecognizer::kLinkedHashMap_setHashMask:
body += LoadLocal(scopes_->this_variable);
body += LoadLocal(first_parameter);
body += StoreInstanceField(TokenPosition::kNoSource,
LinkedHashMap::hash_mask_offset(),
kNoStoreBarrier);
body += NullConstant();
break;
case MethodRecognizer::kLinkedHashMap_getUsedData:
body += LoadLocal(scopes_->this_variable);
body += LoadNativeField(NativeFieldDesc::LinkedHashMap_used_data());
break;
case MethodRecognizer::kLinkedHashMap_setUsedData:
body += LoadLocal(scopes_->this_variable);
body += LoadLocal(first_parameter);
body += StoreInstanceField(TokenPosition::kNoSource,
LinkedHashMap::used_data_offset(),
kNoStoreBarrier);
body += NullConstant();
break;
case MethodRecognizer::kLinkedHashMap_getDeletedKeys:
body += LoadLocal(scopes_->this_variable);
body += LoadNativeField(NativeFieldDesc::LinkedHashMap_deleted_keys());
break;
case MethodRecognizer::kLinkedHashMap_setDeletedKeys:
body += LoadLocal(scopes_->this_variable);
body += LoadLocal(first_parameter);
body += StoreInstanceField(TokenPosition::kNoSource,
LinkedHashMap::deleted_keys_offset(),
kNoStoreBarrier);
body += NullConstant();
break;
default: {
String& name = String::ZoneHandle(Z, function.native_name());
if (function.IsGeneric() && FLAG_reify_generic_functions) {
body += LoadLocal(parsed_function_->RawTypeArgumentsVariable());
body += PushArgument();
}
for (intptr_t i = 0; i < function.NumParameters(); ++i) {
body += LoadLocal(parsed_function_->RawParameterVariable(i));
body += PushArgument();
}
body += NativeCall(&name, &function);
// We typecheck results of native calls for type safety.
omit_result_type_check = false;
break;
}
}
return body + Return(TokenPosition::kNoSource, omit_result_type_check);
}
Fragment FlowGraphBuilder::BuildImplicitClosureCreation(
const Function& target) {
Fragment fragment;
const Class& closure_class =
Class::ZoneHandle(Z, I->object_store()->closure_class());
fragment += AllocateObject(closure_class, target);
LocalVariable* closure = MakeTemporary();
// The function signature can have uninstantiated class type parameters.
if (!target.HasInstantiatedSignature(kCurrentClass)) {
fragment += LoadLocal(closure);
fragment += LoadInstantiatorTypeArguments();
fragment +=
StoreInstanceField(TokenPosition::kNoSource,
Closure::instantiator_type_arguments_offset());
}
// The function signature cannot have uninstantiated function type parameters,
// because the function cannot be local and have parent generic functions.
ASSERT(target.HasInstantiatedSignature(kFunctions));
// Allocate a context that closes over `this`.
fragment += AllocateContext(1);
LocalVariable* context = MakeTemporary();
// Store the function and the context in the closure.
fragment += LoadLocal(closure);
fragment += Constant(target);
fragment +=
StoreInstanceField(TokenPosition::kNoSource, Closure::function_offset());
fragment += LoadLocal(closure);
fragment += LoadLocal(context);
fragment +=
StoreInstanceField(TokenPosition::kNoSource, Closure::context_offset());
fragment += LoadLocal(closure);
fragment += Constant(Object::empty_type_arguments());
fragment += StoreInstanceField(TokenPosition::kNoSource,
Closure::delayed_type_arguments_offset());
// The context is on top of the operand stack. Store `this`. The context
// doesn't need a parent pointer because it doesn't close over anything
// else.
fragment += LoadLocal(scopes_->this_variable);
fragment +=
StoreInstanceField(TokenPosition::kNoSource, Context::variable_offset(0));
return fragment;
}
Fragment FlowGraphBuilder::CheckVariableTypeInCheckedMode(
const AbstractType& dst_type,
const String& name_symbol) {
if (I->type_checks()) {
return CheckAssignable(dst_type, name_symbol);
}
return Fragment();
}
bool FlowGraphBuilder::NeedsDebugStepCheck(const Function& function,
TokenPosition position) {
return position.IsDebugPause() && !function.is_native() &&
function.is_debuggable();
}
bool FlowGraphBuilder::NeedsDebugStepCheck(Value* value,
TokenPosition position) {
if (!position.IsDebugPause()) {
return false;
}
Definition* definition = value->definition();
if (definition->IsConstant() || definition->IsLoadStaticField()) {
return true;
}
if (definition->IsAllocateObject()) {
return !definition->AsAllocateObject()->closure_function().IsNull();
}
return definition->IsLoadLocal() &&
!definition->AsLoadLocal()->local().IsInternal();
}
Fragment FlowGraphBuilder::DebugStepCheck(TokenPosition position) {
return Fragment(new (Z) DebugStepCheckInstr(
position, RawPcDescriptors::kRuntimeCall, GetNextDeoptId()));
}
Fragment FlowGraphBuilder::EvaluateAssertion() {
const Class& klass =
Class::ZoneHandle(Z, Library::LookupCoreClass(Symbols::AssertionError()));
ASSERT(!klass.IsNull());
const Function& target = Function::ZoneHandle(
Z, klass.LookupStaticFunctionAllowPrivate(Symbols::EvaluateAssertion()));
ASSERT(!target.IsNull());
return StaticCall(TokenPosition::kNoSource, target, /* argument_count = */ 1,
ICData::kStatic);
}
Fragment FlowGraphBuilder::CheckBoolean(TokenPosition position) {
Fragment instructions;
if (FLAG_strong || I->type_checks() || I->asserts()) {
LocalVariable* top_of_stack = MakeTemporary();
instructions += LoadLocal(top_of_stack);
instructions += AssertBool(position);
instructions += Drop();
}
return instructions;
}
Fragment FlowGraphBuilder::CheckAssignable(const AbstractType& dst_type,
const String& dst_name,
AssertAssignableInstr::Kind kind) {
Fragment instructions;
if (dst_type.IsMalformed()) {
return ThrowTypeError();
}
if (!I->should_emit_strong_mode_checks()) {
return Fragment();
}
if (!dst_type.IsDynamicType() && !dst_type.IsObjectType() &&
!dst_type.IsVoidType()) {
LocalVariable* top_of_stack = MakeTemporary();
instructions += LoadLocal(top_of_stack);
instructions +=
AssertAssignable(TokenPosition::kNoSource, dst_type, dst_name, kind);
instructions += Drop();
}
return instructions;
}
Fragment FlowGraphBuilder::AssertAssignable(TokenPosition position,
const AbstractType& dst_type,
const String& dst_name,
AssertAssignableInstr::Kind kind) {
if (!I->should_emit_strong_mode_checks()) {
return Fragment();
}
Fragment instructions;
Value* value = Pop();
if (!dst_type.IsInstantiated(kCurrentClass)) {
instructions += LoadInstantiatorTypeArguments();
} else {
instructions += NullConstant();
}
Value* instantiator_type_args = Pop();
if (!dst_type.IsInstantiated(kFunctions)) {
instructions += LoadFunctionTypeArguments();
} else {
instructions += NullConstant();
}
Value* function_type_args = Pop();
AssertAssignableInstr* instr = new (Z) AssertAssignableInstr(
position, value, instantiator_type_args, function_type_args, dst_type,
dst_name, GetNextDeoptId(), kind);
Push(instr);
instructions += Fragment(instr);
return instructions;
}
Fragment FlowGraphBuilder::AssertSubtype(TokenPosition position,
const AbstractType& sub_type,
const AbstractType& super_type,
const String& dst_name) {
Fragment instructions;
instructions += LoadInstantiatorTypeArguments();
Value* instantiator_type_args = Pop();
instructions += LoadFunctionTypeArguments();
Value* function_type_args = Pop();
AssertSubtypeInstr* instr = new (Z)
AssertSubtypeInstr(position, instantiator_type_args, function_type_args,
sub_type, super_type, dst_name, GetNextDeoptId());
instructions += Fragment(instr);
return instructions;
}
BlockEntryInstr* FlowGraphBuilder::BuildPrologue(BlockEntryInstr* normal_entry,
PrologueInfo* prologue_info) {
const bool compiling_for_osr = IsCompiledForOsr();
kernel::PrologueBuilder prologue_builder(
parsed_function_, last_used_block_id_, compiling_for_osr, IsInlining());
BlockEntryInstr* instruction_cursor =
prologue_builder.BuildPrologue(normal_entry, prologue_info);
last_used_block_id_ = prologue_builder.last_used_block_id();
return instruction_cursor;
}
FlowGraph* FlowGraphBuilder::BuildGraphOfMethodExtractor(
const Function& method) {
// A method extractor is the implicit getter for a method.
const Function& function =
Function::ZoneHandle(Z, method.extracted_method_closure());
graph_entry_ =
new (Z) GraphEntryInstr(*parsed_function_, Compiler::kNoOSRDeoptId);
auto normal_entry = BuildFunctionEntry(graph_entry_);
graph_entry_->set_normal_entry(normal_entry);
Fragment body(normal_entry);
body += CheckStackOverflowInPrologue(method.token_pos());
body += BuildImplicitClosureCreation(function);
body += Return(TokenPosition::kNoSource);
// There is no prologue code for a method extractor.
PrologueInfo prologue_info(-1, -1);
return new (Z) FlowGraph(*parsed_function_, graph_entry_, last_used_block_id_,
prologue_info);
}
FlowGraph* FlowGraphBuilder::BuildGraphOfNoSuchMethodDispatcher(
const Function& function) {
// This function is specialized for a receiver class, a method name, and
// the arguments descriptor at a call site.
graph_entry_ =
new (Z) GraphEntryInstr(*parsed_function_, Compiler::kNoOSRDeoptId);
auto normal_entry = BuildFunctionEntry(graph_entry_);
graph_entry_->set_normal_entry(normal_entry);
PrologueInfo prologue_info(-1, -1);
BlockEntryInstr* instruction_cursor =
BuildPrologue(normal_entry, &prologue_info);
// The backend will expect an array of default values for all the named
// parameters, even if they are all known to be passed at the call site
// because the call site matches the arguments descriptor. Use null for
// the default values.
const Array& descriptor_array =
Array::ZoneHandle(Z, function.saved_args_desc());
ArgumentsDescriptor descriptor(descriptor_array);
ZoneGrowableArray<const Instance*>* default_values =
new ZoneGrowableArray<const Instance*>(Z, descriptor.NamedCount());
for (intptr_t i = 0; i < descriptor.NamedCount(); ++i) {
default_values->Add(&Object::null_instance());
}
parsed_function_->set_default_parameter_values(default_values);
Fragment body(instruction_cursor);
body += CheckStackOverflowInPrologue(function.token_pos());
// The receiver is the first argument to noSuchMethod, and it is the first
// argument passed to the dispatcher function.
LocalScope* scope = parsed_function_->node_sequence()->scope();
body += LoadLocal(scope->VariableAt(0));
body += PushArgument();
// The second argument to noSuchMethod is an invocation mirror. Push the
// arguments for allocating the invocation mirror. First, the name.
body += Constant(String::ZoneHandle(Z, function.name()));
body += PushArgument();
// Second, the arguments descriptor.
body += Constant(descriptor_array);
body += PushArgument();
// Third, an array containing the original arguments. Create it and fill
// it in.
const intptr_t receiver_index = descriptor.TypeArgsLen() > 0 ? 1 : 0;
body += Constant(TypeArguments::ZoneHandle(Z, TypeArguments::null()));
body += IntConstant(receiver_index + descriptor.Count());
body += CreateArray();
LocalVariable* array = MakeTemporary();
if (receiver_index > 0) {
LocalVariable* type_args = parsed_function_->function_type_arguments();
ASSERT(type_args != NULL);
body += LoadLocal(array);
body += IntConstant(0);
body += LoadLocal(type_args);
body += StoreIndexed(kArrayCid);
body += Drop();
}
for (intptr_t i = 0; i < descriptor.PositionalCount(); ++i) {
body += LoadLocal(array);
body += IntConstant(receiver_index + i);
body += LoadLocal(scope->VariableAt(i));
body += StoreIndexed(kArrayCid);
body += Drop();
}
String& name = String::Handle(Z);
for (intptr_t i = 0; i < descriptor.NamedCount(); ++i) {
intptr_t parameter_index = descriptor.PositionalCount() + i;
name = descriptor.NameAt(i);
name = Symbols::New(H.thread(), name);
body += LoadLocal(array);
body += IntConstant(receiver_index + descriptor.PositionAt(i));
body += LoadLocal(scope->VariableAt(parameter_index));
body += StoreIndexed(kArrayCid);
body += Drop();
}
body += PushArgument();
// Fourth, false indicating this is not a super NoSuchMethod.
body += Constant(Bool::False());
body += PushArgument();
const Class& mirror_class =
Class::Handle(Z, Library::LookupCoreClass(Symbols::InvocationMirror()));
ASSERT(!mirror_class.IsNull());
const Function& allocation_function = Function::ZoneHandle(
Z, mirror_class.LookupStaticFunction(
Library::PrivateCoreLibName(Symbols::AllocateInvocationMirror())));
ASSERT(!allocation_function.IsNull());
body += StaticCall(TokenPosition::kMinSource, allocation_function,
/* argument_count = */ 4, ICData::kStatic);
body += PushArgument(); // For the call to noSuchMethod.
const int kTypeArgsLen = 0;
ArgumentsDescriptor two_arguments(
Array::Handle(Z, ArgumentsDescriptor::New(kTypeArgsLen, 2)));
Function& no_such_method =
Function::ZoneHandle(Z, Resolver::ResolveDynamicForReceiverClass(
Class::Handle(Z, function.Owner()),
Symbols::NoSuchMethod(), two_arguments));
if (no_such_method.IsNull()) {
// If noSuchMethod is not found on the receiver class, call
// Object.noSuchMethod.
no_such_method = Resolver::ResolveDynamicForReceiverClass(
Class::Handle(Z, I->object_store()->object_class()),
Symbols::NoSuchMethod(), two_arguments);
}
body += StaticCall(TokenPosition::kMinSource, no_such_method,
/* argument_count = */ 2, ICData::kNSMDispatch);
body += Return(TokenPosition::kNoSource);
return new (Z) FlowGraph(*parsed_function_, graph_entry_, last_used_block_id_,
prologue_info);
}
FlowGraph* FlowGraphBuilder::BuildGraphOfInvokeFieldDispatcher(
const Function& function) {
// Find the name of the field we should dispatch to.
const Class& owner = Class::Handle(Z, function.Owner());
ASSERT(!owner.IsNull());
const String& field_name = String::Handle(Z, function.name());
const String& getter_name = String::ZoneHandle(
Z, Symbols::New(H.thread(),
String::Handle(Z, Field::GetterSymbol(field_name))));
// Determine if this is `class Closure { get call => this; }`
const Class& closure_class =
Class::Handle(Z, I->object_store()->closure_class());
const bool is_closure_call = (owner.raw() == closure_class.raw()) &&
field_name.Equals(Symbols::Call());
// Set default parameters & construct argument names array.
//
// The backend will expect an array of default values for all the named
// parameters, even if they are all known to be passed at the call site
// because the call site matches the arguments descriptor. Use null for
// the default values.
const Array& descriptor_array =
Array::ZoneHandle(Z, function.saved_args_desc());
ArgumentsDescriptor descriptor(descriptor_array);
const Array& argument_names =
Array::ZoneHandle(Z, Array::New(descriptor.NamedCount(), Heap::kOld));
ZoneGrowableArray<const Instance*>* default_values =
new ZoneGrowableArray<const Instance*>(Z, descriptor.NamedCount());
String& string_handle = String::Handle(Z);
for (intptr_t i = 0; i < descriptor.NamedCount(); ++i) {
default_values->Add(&Object::null_instance());
string_handle = descriptor.NameAt(i);
argument_names.SetAt(i, string_handle);
}
parsed_function_->set_default_parameter_values(default_values);
graph_entry_ =
new (Z) GraphEntryInstr(*parsed_function_, Compiler::kNoOSRDeoptId);
auto normal_entry = BuildFunctionEntry(graph_entry_);
graph_entry_->set_normal_entry(normal_entry);
PrologueInfo prologue_info(-1, -1);
BlockEntryInstr* instruction_cursor =
BuildPrologue(normal_entry, &prologue_info);
Fragment body(instruction_cursor);
body += CheckStackOverflowInPrologue(function.token_pos());
LocalScope* scope = parsed_function_->node_sequence()->scope();
if (descriptor.TypeArgsLen() > 0) {
LocalVariable* type_args = parsed_function_->function_type_arguments();
ASSERT(type_args != NULL);
body += LoadLocal(type_args);
body += PushArgument();
}
LocalVariable* closure = NULL;
if (is_closure_call) {
closure = scope->VariableAt(0);
// The closure itself is the first argument.
body += LoadLocal(closure);
} else {
// Invoke the getter to get the field value.
body += LoadLocal(scope->VariableAt(0));
body += PushArgument();
const intptr_t kTypeArgsLen = 0;
const intptr_t kNumArgsChecked = 1;
body += InstanceCall(TokenPosition::kMinSource, getter_name, Token::kGET,
kTypeArgsLen, 1, Array::null_array(), kNumArgsChecked,
Function::null_function());
}
body += PushArgument();
// Push all arguments onto the stack.
intptr_t pos = 1;
for (; pos < descriptor.Count(); pos++) {
body += LoadLocal(scope->VariableAt(pos));
body += PushArgument();
}
if (is_closure_call) {
// Lookup the function in the closure.
body += LoadLocal(closure);
body += LoadField(Closure::function_offset());
body += ClosureCall(TokenPosition::kNoSource, descriptor.TypeArgsLen(),
descriptor.Count(), argument_names);
} else {
const intptr_t kNumArgsChecked = 1;
body += InstanceCall(TokenPosition::kMinSource, Symbols::Call(),
Token::kILLEGAL, descriptor.TypeArgsLen(),
descriptor.Count(), argument_names, kNumArgsChecked,
Function::null_function());
}
body += Return(TokenPosition::kNoSource);
return new (Z) FlowGraph(*parsed_function_, graph_entry_, last_used_block_id_,
prologue_info);
}
void FlowGraphBuilder::SetCurrentTryCatchBlock(TryCatchBlock* try_catch_block) {
try_catch_block_ = try_catch_block;
SetCurrentTryIndex(try_catch_block == nullptr ? kInvalidTryIndex
: try_catch_block->try_index());
}
} // namespace kernel
} // namespace dart
#endif // !defined(DART_PRECOMPILED_RUNTIME)