994f535e03
Megamorphic benchmark in bytecode/JIT-only mode: Before: MegaNeverOverridden(RunTime): 1847.3550369344412 us. MegaNeverOverriddenIndirect(RunTime): 2054.2040605749485 us. MegaOnlyOverriddenOnceIndirect(RunTime): 3444.1204027538724 us. MegaFieldIndirect(RunTime): 2334.6812497082847 us. MegaFieldHasGetterOnceIndirect(RunTime): 3212.210929373997 us. After: MegaNeverOverridden(RunTime): 1393.4616984679665 us. MegaNeverOverriddenIndirect(RunTime): 1363.8226980231766 us. MegaOnlyOverriddenOnceIndirect(RunTime): 2814.319476793249 us. MegaFieldIndirect(RunTime): 1893.1759697256387 us. MegaFieldHasGetterOnceIndirect(RunTime): 2782.111602225313 us. https://github.com/dart-lang/sdk/issues/36342 Change-Id: Ibb73c1339f5b8f859531c8ed29aca4054d733ce0 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/97843 Reviewed-by: Vyacheslav Egorov <vegorov@google.com> Commit-Queue: Alexander Markov <alexmarkov@google.com>
2573 lines
96 KiB
C++
2573 lines
96 KiB
C++
// 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_to_il.h"
|
|
#include "vm/compiler/aot/precompiler.h"
|
|
#include "vm/compiler/backend/locations.h"
|
|
|
|
#include "vm/compiler/backend/il.h"
|
|
#include "vm/compiler/backend/il_printer.h"
|
|
#include "vm/compiler/ffi.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),
|
|
loop_depth_(0),
|
|
try_depth_(0),
|
|
catch_depth_(0),
|
|
for_in_depth_(0),
|
|
block_expression_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,
|
|
const LocalScope** context_scope /* = nullptr */) {
|
|
Fragment instructions;
|
|
const LocalScope* scope = scopes_->scopes.Lookup(kernel_offset);
|
|
if (scope->num_context_variables() > 0) {
|
|
instructions += PushContext(scope);
|
|
instructions += Drop();
|
|
}
|
|
if (context_scope != nullptr) {
|
|
*context_scope = scope;
|
|
}
|
|
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(const LocalScope* scope) {
|
|
ASSERT(scope->num_context_variables() > 0);
|
|
Fragment instructions = AllocateContext(scope->context_variables());
|
|
LocalVariable* context = MakeTemporary();
|
|
instructions += LoadLocal(context);
|
|
instructions += LoadLocal(parsed_function_->current_context_var());
|
|
instructions +=
|
|
StoreInstanceField(TokenPosition::kNoSource, Slot::Context_parent());
|
|
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_ != nullptr && scopes_->type_arguments_variable != nullptr) {
|
|
#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 (parsed_function_->has_receiver_var() &&
|
|
active_class_.ClassNumTypeArguments() > 0) {
|
|
ASSERT(!parsed_function_->function().IsFactory());
|
|
instructions += LoadLocal(parsed_function_->receiver_var());
|
|
instructions += LoadNativeField(
|
|
Slot::GetTypeArgumentsSlotFor(thread_, *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;
|
|
|
|
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.
|
|
if (type_arguments.CanShareInstantiatorTypeArguments(
|
|
*active_class_.klass)) {
|
|
// 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 if (type_arguments.CanShareFunctionTypeArguments(
|
|
parsed_function_->function())) {
|
|
instructions += LoadFunctionTypeArguments();
|
|
} 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());
|
|
|
|
LocalVariable* closure_parameter = parsed_function_->ParameterVariable(0);
|
|
ASSERT(!closure_parameter->is_captured());
|
|
instructions += LoadLocal(closure_parameter);
|
|
instructions += LoadNativeField(Slot::Closure_context());
|
|
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,
|
|
Slot::GetContextVariableSlotFor(thread_, *exception_var));
|
|
}
|
|
if (stacktrace_var->is_captured()) {
|
|
instructions += LoadLocal(context_variable);
|
|
instructions += LoadLocal(raw_stacktrace_var);
|
|
instructions += StoreInstanceField(
|
|
TokenPosition::kNoSource,
|
|
Slot::GetContextVariableSlotFor(thread_, *stacktrace_var));
|
|
}
|
|
|
|
// :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) {
|
|
ASSERT(loop_depth_ == 0);
|
|
return BaseFlowGraphBuilder::CheckStackOverflowInPrologue(position);
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::CloneContext(
|
|
const GrowableArray<LocalVariable*>& context_variables) {
|
|
LocalVariable* context_variable = parsed_function_->current_context_var();
|
|
|
|
Fragment instructions = LoadLocal(context_variable);
|
|
|
|
CloneContextInstr* clone_instruction = new (Z) CloneContextInstr(
|
|
TokenPosition::kNoSource, Pop(), 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::FfiCall(
|
|
const Function& signature,
|
|
const ZoneGrowableArray<Representation>& arg_reps,
|
|
const ZoneGrowableArray<Location>& arg_locs) {
|
|
Fragment body;
|
|
|
|
FfiCallInstr* call =
|
|
new (Z) FfiCallInstr(Z, GetNextDeoptId(), signature, arg_reps, arg_locs);
|
|
|
|
for (intptr_t i = call->InputCount() - 1; i >= 0; --i) {
|
|
call->SetInputAt(i, Pop());
|
|
}
|
|
|
|
Push(call);
|
|
body <<= call;
|
|
|
|
return body;
|
|
}
|
|
|
|
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::LoadLocal(LocalVariable* variable) {
|
|
if (variable->is_captured()) {
|
|
Fragment instructions;
|
|
instructions += LoadContextAt(variable->owner()->context_level());
|
|
instructions +=
|
|
LoadNativeField(Slot::GetContextVariableSlotFor(thread_, *variable));
|
|
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() ? 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 && function.is_native()) {
|
|
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.has_pragma()) {
|
|
intptr_t recognized_cid = MethodRecognizer::ResultCidFromPragma(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,
|
|
bool use_unchecked_entry) {
|
|
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);
|
|
if (use_unchecked_entry) {
|
|
call->set_entry_kind(Code::EntryKind::kUnchecked);
|
|
}
|
|
Push(call);
|
|
return Fragment(call);
|
|
}
|
|
|
|
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::kTypedData_ByteDataView_factory:
|
|
body += BuildTypedDataViewFactoryConstructor(function, kByteDataViewCid);
|
|
break;
|
|
case MethodRecognizer::kTypedData_Int8ArrayView_factory:
|
|
body += BuildTypedDataViewFactoryConstructor(function,
|
|
kTypedDataInt8ArrayViewCid);
|
|
break;
|
|
case MethodRecognizer::kTypedData_Uint8ArrayView_factory:
|
|
body += BuildTypedDataViewFactoryConstructor(function,
|
|
kTypedDataUint8ArrayViewCid);
|
|
break;
|
|
case MethodRecognizer::kTypedData_Uint8ClampedArrayView_factory:
|
|
body += BuildTypedDataViewFactoryConstructor(
|
|
function, kTypedDataUint8ClampedArrayViewCid);
|
|
break;
|
|
case MethodRecognizer::kTypedData_Int16ArrayView_factory:
|
|
body += BuildTypedDataViewFactoryConstructor(function,
|
|
kTypedDataInt16ArrayViewCid);
|
|
break;
|
|
case MethodRecognizer::kTypedData_Uint16ArrayView_factory:
|
|
body += BuildTypedDataViewFactoryConstructor(
|
|
function, kTypedDataUint16ArrayViewCid);
|
|
break;
|
|
case MethodRecognizer::kTypedData_Int32ArrayView_factory:
|
|
body += BuildTypedDataViewFactoryConstructor(function,
|
|
kTypedDataInt32ArrayViewCid);
|
|
break;
|
|
case MethodRecognizer::kTypedData_Uint32ArrayView_factory:
|
|
body += BuildTypedDataViewFactoryConstructor(
|
|
function, kTypedDataUint32ArrayViewCid);
|
|
break;
|
|
case MethodRecognizer::kTypedData_Int64ArrayView_factory:
|
|
body += BuildTypedDataViewFactoryConstructor(function,
|
|
kTypedDataInt64ArrayViewCid);
|
|
break;
|
|
case MethodRecognizer::kTypedData_Uint64ArrayView_factory:
|
|
body += BuildTypedDataViewFactoryConstructor(
|
|
function, kTypedDataUint64ArrayViewCid);
|
|
break;
|
|
case MethodRecognizer::kTypedData_Float32ArrayView_factory:
|
|
body += BuildTypedDataViewFactoryConstructor(
|
|
function, kTypedDataFloat32ArrayViewCid);
|
|
break;
|
|
case MethodRecognizer::kTypedData_Float64ArrayView_factory:
|
|
body += BuildTypedDataViewFactoryConstructor(
|
|
function, kTypedDataFloat64ArrayViewCid);
|
|
break;
|
|
case MethodRecognizer::kTypedData_Float32x4ArrayView_factory:
|
|
body += BuildTypedDataViewFactoryConstructor(
|
|
function, kTypedDataFloat32x4ArrayViewCid);
|
|
break;
|
|
case MethodRecognizer::kTypedData_Int32x4ArrayView_factory:
|
|
body += BuildTypedDataViewFactoryConstructor(
|
|
function, kTypedDataInt32x4ArrayViewCid);
|
|
break;
|
|
case MethodRecognizer::kTypedData_Float64x2ArrayView_factory:
|
|
body += BuildTypedDataViewFactoryConstructor(
|
|
function, kTypedDataFloat64x2ArrayViewCid);
|
|
break;
|
|
case MethodRecognizer::kObjectEquals:
|
|
body += LoadLocal(parsed_function_->receiver_var());
|
|
body += LoadLocal(first_parameter);
|
|
body += StrictCompare(Token::kEQ_STRICT);
|
|
break;
|
|
case MethodRecognizer::kStringBaseLength:
|
|
case MethodRecognizer::kStringBaseIsEmpty:
|
|
body += LoadLocal(parsed_function_->receiver_var());
|
|
body += LoadNativeField(Slot::String_length());
|
|
if (kind == MethodRecognizer::kStringBaseIsEmpty) {
|
|
body += IntConstant(0);
|
|
body += StrictCompare(Token::kEQ_STRICT);
|
|
}
|
|
break;
|
|
case MethodRecognizer::kGrowableArrayLength:
|
|
body += LoadLocal(parsed_function_->receiver_var());
|
|
body += LoadNativeField(Slot::GrowableObjectArray_length());
|
|
break;
|
|
case MethodRecognizer::kObjectArrayLength:
|
|
case MethodRecognizer::kImmutableArrayLength:
|
|
body += LoadLocal(parsed_function_->receiver_var());
|
|
body += LoadNativeField(Slot::Array_length());
|
|
break;
|
|
case MethodRecognizer::kTypedDataLength:
|
|
body += LoadLocal(parsed_function_->receiver_var());
|
|
body += LoadNativeField(Slot::TypedData_length());
|
|
break;
|
|
case MethodRecognizer::kByteDataViewLength:
|
|
case MethodRecognizer::kTypedDataViewLength:
|
|
body += LoadLocal(parsed_function_->receiver_var());
|
|
body += LoadNativeField(Slot::TypedDataView_length());
|
|
break;
|
|
case MethodRecognizer::kByteDataViewOffsetInBytes:
|
|
case MethodRecognizer::kTypedDataViewOffsetInBytes:
|
|
body += LoadLocal(parsed_function_->receiver_var());
|
|
body += LoadNativeField(Slot::TypedDataView_offset_in_bytes());
|
|
break;
|
|
case MethodRecognizer::kByteDataViewTypedData:
|
|
case MethodRecognizer::kTypedDataViewTypedData:
|
|
body += LoadLocal(parsed_function_->receiver_var());
|
|
body += LoadNativeField(Slot::TypedDataView_data());
|
|
break;
|
|
case MethodRecognizer::kClassIDgetID:
|
|
body += LoadLocal(first_parameter);
|
|
body += LoadClassId();
|
|
break;
|
|
case MethodRecognizer::kGrowableArrayCapacity:
|
|
body += LoadLocal(parsed_function_->receiver_var());
|
|
body += LoadNativeField(Slot::GrowableObjectArray_data());
|
|
body += LoadNativeField(Slot::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 += LoadNativeField(Slot::ArgumentsDescriptor_positional_count());
|
|
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(parsed_function_->receiver_var());
|
|
body += LoadNativeField(Slot::LinkedHashMap_index());
|
|
break;
|
|
case MethodRecognizer::kLinkedHashMap_setIndex:
|
|
body += LoadLocal(parsed_function_->receiver_var());
|
|
body += LoadLocal(first_parameter);
|
|
body += StoreInstanceField(TokenPosition::kNoSource,
|
|
Slot::LinkedHashMap_index());
|
|
body += NullConstant();
|
|
break;
|
|
case MethodRecognizer::kLinkedHashMap_getData:
|
|
body += LoadLocal(parsed_function_->receiver_var());
|
|
body += LoadNativeField(Slot::LinkedHashMap_data());
|
|
break;
|
|
case MethodRecognizer::kLinkedHashMap_setData:
|
|
body += LoadLocal(parsed_function_->receiver_var());
|
|
body += LoadLocal(first_parameter);
|
|
body += StoreInstanceField(TokenPosition::kNoSource,
|
|
Slot::LinkedHashMap_data());
|
|
body += NullConstant();
|
|
break;
|
|
case MethodRecognizer::kLinkedHashMap_getHashMask:
|
|
body += LoadLocal(parsed_function_->receiver_var());
|
|
body += LoadNativeField(Slot::LinkedHashMap_hash_mask());
|
|
break;
|
|
case MethodRecognizer::kLinkedHashMap_setHashMask:
|
|
body += LoadLocal(parsed_function_->receiver_var());
|
|
body += LoadLocal(first_parameter);
|
|
body +=
|
|
StoreInstanceField(TokenPosition::kNoSource,
|
|
Slot::LinkedHashMap_hash_mask(), kNoStoreBarrier);
|
|
body += NullConstant();
|
|
break;
|
|
case MethodRecognizer::kLinkedHashMap_getUsedData:
|
|
body += LoadLocal(parsed_function_->receiver_var());
|
|
body += LoadNativeField(Slot::LinkedHashMap_used_data());
|
|
break;
|
|
case MethodRecognizer::kLinkedHashMap_setUsedData:
|
|
body += LoadLocal(parsed_function_->receiver_var());
|
|
body += LoadLocal(first_parameter);
|
|
body +=
|
|
StoreInstanceField(TokenPosition::kNoSource,
|
|
Slot::LinkedHashMap_used_data(), kNoStoreBarrier);
|
|
body += NullConstant();
|
|
break;
|
|
case MethodRecognizer::kLinkedHashMap_getDeletedKeys:
|
|
body += LoadLocal(parsed_function_->receiver_var());
|
|
body += LoadNativeField(Slot::LinkedHashMap_deleted_keys());
|
|
break;
|
|
case MethodRecognizer::kLinkedHashMap_setDeletedKeys:
|
|
body += LoadLocal(parsed_function_->receiver_var());
|
|
body += LoadLocal(first_parameter);
|
|
body += StoreInstanceField(TokenPosition::kNoSource,
|
|
Slot::LinkedHashMap_deleted_keys(),
|
|
kNoStoreBarrier);
|
|
body += NullConstant();
|
|
break;
|
|
default: {
|
|
String& name = String::ZoneHandle(Z, function.native_name());
|
|
if (function.IsGeneric()) {
|
|
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::BuildTypedDataViewFactoryConstructor(
|
|
const Function& function,
|
|
classid_t cid) {
|
|
auto token_pos = function.token_pos();
|
|
auto class_table = Thread::Current()->isolate()->class_table();
|
|
|
|
ASSERT(class_table->HasValidClassAt(cid));
|
|
const auto& view_class = Class::ZoneHandle(H.zone(), class_table->At(cid));
|
|
|
|
LocalVariable* typed_data = parsed_function_->RawParameterVariable(1);
|
|
LocalVariable* offset_in_bytes = parsed_function_->RawParameterVariable(2);
|
|
LocalVariable* length = parsed_function_->RawParameterVariable(3);
|
|
|
|
Fragment body;
|
|
|
|
body += AllocateObject(token_pos, view_class, /*arg_count=*/0);
|
|
LocalVariable* view_object = MakeTemporary();
|
|
|
|
body += LoadLocal(view_object);
|
|
body += LoadLocal(typed_data);
|
|
body += StoreInstanceField(token_pos, Slot::TypedDataView_data());
|
|
|
|
body += LoadLocal(view_object);
|
|
body += LoadLocal(offset_in_bytes);
|
|
body += StoreInstanceField(token_pos, Slot::TypedDataView_offset_in_bytes());
|
|
|
|
body += LoadLocal(view_object);
|
|
body += LoadLocal(length);
|
|
body += StoreInstanceField(token_pos, Slot::TypedDataView_length());
|
|
|
|
return body;
|
|
}
|
|
|
|
static const LocalScope* MakeImplicitClosureScope(Zone* Z, const Class& klass) {
|
|
ASSERT(!klass.IsNull());
|
|
// Note that if klass is _Closure, DeclarationType will be _Closure,
|
|
// and not the signature type.
|
|
Type& klass_type = Type::ZoneHandle(Z, klass.DeclarationType());
|
|
|
|
LocalVariable* receiver_variable = new (Z)
|
|
LocalVariable(TokenPosition::kNoSource, TokenPosition::kNoSource,
|
|
Symbols::This(), klass_type, /*param_type=*/nullptr);
|
|
|
|
receiver_variable->set_is_captured();
|
|
// receiver_variable->set_is_final();
|
|
LocalScope* scope = new (Z) LocalScope(NULL, 0, 0);
|
|
scope->set_context_level(0);
|
|
scope->AddVariable(receiver_variable);
|
|
scope->AddContextVariable(receiver_variable);
|
|
return scope;
|
|
}
|
|
|
|
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,
|
|
Slot::Closure_instantiator_type_arguments());
|
|
}
|
|
|
|
// 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`.
|
|
// Note: this must be kept in sync with ScopeBuilder::BuildScopes.
|
|
const LocalScope* implicit_closure_scope =
|
|
MakeImplicitClosureScope(Z, Class::Handle(Z, target.Owner()));
|
|
fragment += AllocateContext(implicit_closure_scope->context_variables());
|
|
LocalVariable* context = MakeTemporary();
|
|
|
|
// Store the function and the context in the closure.
|
|
fragment += LoadLocal(closure);
|
|
fragment += Constant(target);
|
|
fragment +=
|
|
StoreInstanceField(TokenPosition::kNoSource, Slot::Closure_function());
|
|
|
|
fragment += LoadLocal(closure);
|
|
fragment += LoadLocal(context);
|
|
fragment +=
|
|
StoreInstanceField(TokenPosition::kNoSource, Slot::Closure_context());
|
|
|
|
fragment += LoadLocal(closure);
|
|
fragment += Constant(Object::empty_type_arguments());
|
|
fragment += StoreInstanceField(TokenPosition::kNoSource,
|
|
Slot::Closure_delayed_type_arguments());
|
|
|
|
// 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(parsed_function_->receiver_var());
|
|
fragment += StoreInstanceField(
|
|
TokenPosition::kNoSource,
|
|
Slot::GetContextVariableSlotFor(
|
|
thread_, *implicit_closure_scope->context_variables()[0]));
|
|
|
|
return fragment;
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::CheckVariableTypeInCheckedMode(
|
|
const AbstractType& dst_type,
|
|
const String& 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;
|
|
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 (!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;
|
|
}
|
|
|
|
void FlowGraphBuilder::BuildArgumentTypeChecks(
|
|
TypeChecksToBuild mode,
|
|
Fragment* explicit_checks,
|
|
Fragment* implicit_checks,
|
|
Fragment* implicit_redefinitions) {
|
|
if (!I->should_emit_strong_mode_checks()) return;
|
|
const Function& dart_function = parsed_function_->function();
|
|
|
|
const Function* forwarding_target = nullptr;
|
|
if (parsed_function_->is_forwarding_stub()) {
|
|
forwarding_target = parsed_function_->forwarding_stub_super_target();
|
|
ASSERT(!forwarding_target->IsNull());
|
|
}
|
|
|
|
TypeArguments& type_parameters = TypeArguments::Handle(Z);
|
|
if (dart_function.IsFactory()) {
|
|
type_parameters = Class::Handle(Z, dart_function.Owner()).type_parameters();
|
|
} else {
|
|
type_parameters = dart_function.type_parameters();
|
|
}
|
|
intptr_t num_type_params = type_parameters.Length();
|
|
if (forwarding_target != nullptr) {
|
|
type_parameters = forwarding_target->type_parameters();
|
|
ASSERT(type_parameters.Length() == num_type_params);
|
|
}
|
|
|
|
TypeParameter& type_param = TypeParameter::Handle(Z);
|
|
String& name = String::Handle(Z);
|
|
AbstractType& bound = AbstractType::Handle(Z);
|
|
Fragment check_bounds;
|
|
for (intptr_t i = 0; i < num_type_params; ++i) {
|
|
type_param ^= type_parameters.TypeAt(i);
|
|
|
|
bound = type_param.bound();
|
|
if (bound.IsTopType()) {
|
|
continue;
|
|
}
|
|
|
|
switch (mode) {
|
|
case TypeChecksToBuild::kCheckAllTypeParameterBounds:
|
|
break;
|
|
case TypeChecksToBuild::kCheckCovariantTypeParameterBounds:
|
|
if (!type_param.IsGenericCovariantImpl()) {
|
|
continue;
|
|
}
|
|
break;
|
|
case TypeChecksToBuild::kCheckNonCovariantTypeParameterBounds:
|
|
if (type_param.IsGenericCovariantImpl()) {
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
name = type_param.name();
|
|
|
|
ASSERT(type_param.IsFinalized());
|
|
check_bounds +=
|
|
AssertSubtype(TokenPosition::kNoSource, type_param, bound, name);
|
|
}
|
|
|
|
// Type arguments passed through partial instantiation are guaranteed to be
|
|
// bounds-checked at the point of partial instantiation, so we don't need to
|
|
// check them again at the call-site.
|
|
if (dart_function.IsClosureFunction() && !check_bounds.is_empty() &&
|
|
FLAG_eliminate_type_checks) {
|
|
LocalVariable* closure = parsed_function_->ParameterVariable(0);
|
|
*implicit_checks += TestDelayedTypeArgs(closure, /*present=*/{},
|
|
/*absent=*/check_bounds);
|
|
} else {
|
|
*implicit_checks += check_bounds;
|
|
}
|
|
|
|
const intptr_t num_params = dart_function.NumParameters();
|
|
for (intptr_t i = dart_function.NumImplicitParameters(); i < num_params;
|
|
++i) {
|
|
LocalVariable* param = parsed_function_->ParameterVariable(i);
|
|
if (!param->needs_type_check()) {
|
|
continue;
|
|
}
|
|
|
|
const AbstractType* target_type = ¶m->type();
|
|
if (forwarding_target != NULL) {
|
|
// We add 1 to the parameter index to account for the receiver.
|
|
target_type =
|
|
&AbstractType::ZoneHandle(Z, forwarding_target->ParameterTypeAt(i));
|
|
}
|
|
|
|
if (target_type->IsTopType()) continue;
|
|
|
|
const bool is_covariant = param->is_explicit_covariant_parameter();
|
|
Fragment* checks = is_covariant ? explicit_checks : implicit_checks;
|
|
|
|
*checks += LoadLocal(param);
|
|
*checks += CheckAssignable(*target_type, param->name(),
|
|
AssertAssignableInstr::kParameterCheck);
|
|
*checks += Drop();
|
|
|
|
if (!is_covariant && implicit_redefinitions != nullptr && optimizing_) {
|
|
// We generate slightly different code in optimized vs. un-optimized code,
|
|
// which is ok since we don't allocate any deopt ids.
|
|
AssertNoDeoptIdsAllocatedScope no_deopt_allocation(thread_);
|
|
|
|
*implicit_redefinitions += LoadLocal(param);
|
|
*implicit_redefinitions += RedefinitionWithType(*target_type);
|
|
*implicit_redefinitions += StoreLocal(TokenPosition::kNoSource, param);
|
|
*implicit_redefinitions += Drop();
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
RawArray* FlowGraphBuilder::GetOptionalParameterNames(
|
|
const Function& function) {
|
|
if (!function.HasOptionalNamedParameters()) {
|
|
return Array::null();
|
|
}
|
|
|
|
const intptr_t num_fixed_params = function.num_fixed_parameters();
|
|
const intptr_t num_opt_params = function.NumOptionalNamedParameters();
|
|
const auto& names = Array::Handle(Z, Array::New(num_opt_params, Heap::kOld));
|
|
auto& name = String::Handle(Z);
|
|
for (intptr_t i = 0; i < num_opt_params; ++i) {
|
|
name = function.ParameterNameAt(num_fixed_params + i);
|
|
names.SetAt(i, name);
|
|
}
|
|
return names.raw();
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::PushExplicitParameters(const Function& function) {
|
|
Fragment instructions;
|
|
for (intptr_t i = function.NumImplicitParameters(),
|
|
n = function.NumParameters();
|
|
i < n; ++i) {
|
|
instructions += LoadLocal(parsed_function_->ParameterVariable(i));
|
|
instructions += PushArgument();
|
|
}
|
|
return instructions;
|
|
}
|
|
|
|
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.
|
|
body += LoadLocal(parsed_function_->ParameterVariable(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);
|
|
}
|
|
for (intptr_t i = 0; i < descriptor.PositionalCount(); ++i) {
|
|
body += LoadLocal(array);
|
|
body += IntConstant(receiver_index + i);
|
|
body += LoadLocal(parsed_function_->ParameterVariable(i));
|
|
body += StoreIndexed(kArrayCid);
|
|
}
|
|
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(parsed_function_->ParameterVariable(parameter_index));
|
|
body += StoreIndexed(kArrayCid);
|
|
}
|
|
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(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());
|
|
|
|
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 = parsed_function_->ParameterVariable(0);
|
|
|
|
// The closure itself is the first argument.
|
|
body += LoadLocal(closure);
|
|
} else {
|
|
// Invoke the getter to get the field value.
|
|
body += LoadLocal(parsed_function_->ParameterVariable(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(parsed_function_->ParameterVariable(pos));
|
|
body += PushArgument();
|
|
}
|
|
|
|
if (is_closure_call) {
|
|
// Lookup the function in the closure.
|
|
body += LoadLocal(closure);
|
|
body += LoadNativeField(Slot::Closure_function());
|
|
|
|
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);
|
|
}
|
|
|
|
FlowGraph* FlowGraphBuilder::BuildGraphOfNoSuchMethodForwarder(
|
|
const Function& function,
|
|
bool is_implicit_closure_function,
|
|
bool throw_no_such_method_error) {
|
|
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());
|
|
|
|
// If we are inside the tearoff wrapper function (implicit closure), we need
|
|
// to extract the receiver from the context. We just replace it directly on
|
|
// the stack to simplify the rest of the code.
|
|
if (is_implicit_closure_function && !function.is_static()) {
|
|
if (parsed_function_->has_arg_desc_var()) {
|
|
body += LoadArgDescriptor();
|
|
body += LoadNativeField(Slot::ArgumentsDescriptor_count());
|
|
body += LoadLocal(parsed_function_->current_context_var());
|
|
body += LoadNativeField(Slot::GetContextVariableSlotFor(
|
|
thread_, *parsed_function_->receiver_var()));
|
|
body += StoreFpRelativeSlot(
|
|
kWordSize * compiler::target::frame_layout.param_end_from_fp);
|
|
} else {
|
|
body += LoadLocal(parsed_function_->current_context_var());
|
|
body += LoadNativeField(Slot::GetContextVariableSlotFor(
|
|
thread_, *parsed_function_->receiver_var()));
|
|
body += StoreFpRelativeSlot(
|
|
kWordSize * (compiler::target::frame_layout.param_end_from_fp +
|
|
function.NumParameters()));
|
|
}
|
|
}
|
|
|
|
if (function.NeedsArgumentTypeChecks(I)) {
|
|
BuildArgumentTypeChecks(TypeChecksToBuild::kCheckAllTypeParameterBounds,
|
|
&body, &body, nullptr);
|
|
}
|
|
|
|
body += MakeTemp();
|
|
LocalVariable* result = MakeTemporary();
|
|
|
|
// Do "++argument_count" if any type arguments were passed.
|
|
LocalVariable* argument_count_var = parsed_function_->expression_temp_var();
|
|
body += IntConstant(0);
|
|
body += StoreLocal(TokenPosition::kNoSource, argument_count_var);
|
|
body += Drop();
|
|
if (function.IsGeneric()) {
|
|
Fragment then;
|
|
Fragment otherwise;
|
|
otherwise += IntConstant(1);
|
|
otherwise += StoreLocal(TokenPosition::kNoSource, argument_count_var);
|
|
otherwise += Drop();
|
|
body += TestAnyTypeArgs(then, otherwise);
|
|
}
|
|
|
|
if (function.HasOptionalParameters()) {
|
|
body += LoadArgDescriptor();
|
|
body += LoadNativeField(Slot::ArgumentsDescriptor_count());
|
|
} else {
|
|
body += IntConstant(function.NumParameters());
|
|
}
|
|
body += LoadLocal(argument_count_var);
|
|
body += SmiBinaryOp(Token::kADD, /* truncate= */ true);
|
|
LocalVariable* argument_count = MakeTemporary();
|
|
|
|
// We are generating code like the following:
|
|
//
|
|
// var arguments = new Array<dynamic>(argument_count);
|
|
//
|
|
// int i = 0;
|
|
// if (any type arguments are passed) {
|
|
// arguments[0] = function_type_arguments;
|
|
// ++i;
|
|
// }
|
|
//
|
|
// for (; i < argument_count; ++i) {
|
|
// arguments[i] = LoadFpRelativeSlot(
|
|
// kWordSize * (frame_layout.param_end_from_fp + argument_count - i));
|
|
// }
|
|
body += Constant(TypeArguments::ZoneHandle(Z, TypeArguments::null()));
|
|
body += LoadLocal(argument_count);
|
|
body += CreateArray();
|
|
LocalVariable* arguments = MakeTemporary();
|
|
|
|
{
|
|
// int i = 0
|
|
LocalVariable* index = parsed_function_->expression_temp_var();
|
|
body += IntConstant(0);
|
|
body += StoreLocal(TokenPosition::kNoSource, index);
|
|
body += Drop();
|
|
|
|
// if (any type arguments are passed) {
|
|
// arguments[0] = function_type_arguments;
|
|
// i = 1;
|
|
// }
|
|
if (function.IsGeneric()) {
|
|
Fragment store;
|
|
store += LoadLocal(arguments);
|
|
store += IntConstant(0);
|
|
store += LoadFunctionTypeArguments();
|
|
store += StoreIndexed(kArrayCid);
|
|
store += IntConstant(1);
|
|
store += StoreLocal(TokenPosition::kNoSource, index);
|
|
store += Drop();
|
|
body += TestAnyTypeArgs(store, Fragment());
|
|
}
|
|
|
|
TargetEntryInstr* body_entry;
|
|
TargetEntryInstr* loop_exit;
|
|
|
|
Fragment condition;
|
|
// i < argument_count
|
|
condition += LoadLocal(index);
|
|
condition += LoadLocal(argument_count);
|
|
condition += SmiRelationalOp(Token::kLT);
|
|
condition += BranchIfTrue(&body_entry, &loop_exit, /*negate=*/false);
|
|
|
|
Fragment loop_body(body_entry);
|
|
|
|
// arguments[i] = LoadFpRelativeSlot(
|
|
// kWordSize * (frame_layout.param_end_from_fp + argument_count - i));
|
|
loop_body += LoadLocal(arguments);
|
|
loop_body += LoadLocal(index);
|
|
loop_body += LoadLocal(argument_count);
|
|
loop_body += LoadLocal(index);
|
|
loop_body += SmiBinaryOp(Token::kSUB, /*truncate=*/true);
|
|
loop_body += LoadFpRelativeSlot(
|
|
kWordSize * compiler::target::frame_layout.param_end_from_fp);
|
|
loop_body += StoreIndexed(kArrayCid);
|
|
|
|
// ++i
|
|
loop_body += LoadLocal(index);
|
|
loop_body += IntConstant(1);
|
|
loop_body += SmiBinaryOp(Token::kADD, /*truncate=*/true);
|
|
loop_body += StoreLocal(TokenPosition::kNoSource, index);
|
|
loop_body += Drop();
|
|
|
|
JoinEntryInstr* join = BuildJoinEntry();
|
|
loop_body += Goto(join);
|
|
|
|
Fragment loop(join);
|
|
loop += condition;
|
|
|
|
Instruction* entry =
|
|
new (Z) GotoInstr(join, CompilerState::Current().GetNextDeoptId());
|
|
body += Fragment(entry, loop_exit);
|
|
}
|
|
|
|
// Load receiver.
|
|
if (is_implicit_closure_function) {
|
|
if (throw_no_such_method_error) {
|
|
const Function& parent =
|
|
Function::ZoneHandle(Z, function.parent_function());
|
|
const Class& owner = Class::ZoneHandle(Z, parent.Owner());
|
|
AbstractType& type = AbstractType::ZoneHandle(Z);
|
|
type ^= Type::New(owner, TypeArguments::Handle(Z), owner.token_pos(),
|
|
Heap::kOld);
|
|
type ^= ClassFinalizer::FinalizeType(owner, type);
|
|
body += Constant(type);
|
|
} else {
|
|
body += LoadLocal(parsed_function_->current_context_var());
|
|
body += LoadNativeField(Slot::GetContextVariableSlotFor(
|
|
thread_, *parsed_function_->receiver_var()));
|
|
}
|
|
} else {
|
|
body += LoadLocal(parsed_function_->ParameterVariable(0));
|
|
}
|
|
body += PushArgument();
|
|
|
|
body += Constant(String::ZoneHandle(Z, function.name()));
|
|
body += PushArgument();
|
|
|
|
if (!parsed_function_->has_arg_desc_var()) {
|
|
// If there is no variable for the arguments descriptor (this function's
|
|
// signature doesn't require it), then we need to create one.
|
|
Array& args_desc = Array::ZoneHandle(
|
|
Z, ArgumentsDescriptor::New(0, function.NumParameters()));
|
|
body += Constant(args_desc);
|
|
} else {
|
|
body += LoadArgDescriptor();
|
|
}
|
|
body += PushArgument();
|
|
|
|
body += LoadLocal(arguments);
|
|
body += PushArgument();
|
|
|
|
if (throw_no_such_method_error) {
|
|
const Function& parent =
|
|
Function::ZoneHandle(Z, function.parent_function());
|
|
const Class& owner = Class::ZoneHandle(Z, parent.Owner());
|
|
InvocationMirror::Level im_level = owner.IsTopLevel()
|
|
? InvocationMirror::kTopLevel
|
|
: InvocationMirror::kStatic;
|
|
InvocationMirror::Kind im_kind;
|
|
if (function.IsImplicitGetterFunction() || function.IsGetterFunction()) {
|
|
im_kind = InvocationMirror::kGetter;
|
|
} else if (function.IsImplicitSetterFunction() ||
|
|
function.IsSetterFunction()) {
|
|
im_kind = InvocationMirror::kSetter;
|
|
} else {
|
|
im_kind = InvocationMirror::kMethod;
|
|
}
|
|
body += IntConstant(InvocationMirror::EncodeType(im_level, im_kind));
|
|
} else {
|
|
body += NullConstant();
|
|
}
|
|
body += PushArgument();
|
|
|
|
// Push the number of delayed type arguments.
|
|
if (function.IsClosureFunction()) {
|
|
LocalVariable* closure = parsed_function_->ParameterVariable(0);
|
|
Fragment then;
|
|
then += IntConstant(function.NumTypeParameters());
|
|
then += StoreLocal(TokenPosition::kNoSource, argument_count_var);
|
|
then += Drop();
|
|
Fragment otherwise;
|
|
otherwise += IntConstant(0);
|
|
otherwise += StoreLocal(TokenPosition::kNoSource, argument_count_var);
|
|
otherwise += Drop();
|
|
body += TestDelayedTypeArgs(closure, then, otherwise);
|
|
body += LoadLocal(argument_count_var);
|
|
} else {
|
|
body += IntConstant(0);
|
|
}
|
|
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::AllocateInvocationMirrorForClosure())));
|
|
ASSERT(!allocation_function.IsNull());
|
|
body += StaticCall(TokenPosition::kMinSource, allocation_function,
|
|
/* argument_count = */ 5, ICData::kStatic);
|
|
body += PushArgument(); // For the call to noSuchMethod.
|
|
|
|
if (throw_no_such_method_error) {
|
|
const Class& klass = Class::ZoneHandle(
|
|
Z, Library::LookupCoreClass(Symbols::NoSuchMethodError()));
|
|
ASSERT(!klass.IsNull());
|
|
const Function& throw_function = Function::ZoneHandle(
|
|
Z,
|
|
klass.LookupStaticFunctionAllowPrivate(Symbols::ThrowNewInvocation()));
|
|
ASSERT(!throw_function.IsNull());
|
|
body += StaticCall(TokenPosition::kNoSource, throw_function, 2,
|
|
ICData::kStatic);
|
|
} else {
|
|
body += InstanceCall(
|
|
TokenPosition::kNoSource, Symbols::NoSuchMethod(), Token::kILLEGAL,
|
|
/*type_args_len=*/0, /*argument_count=*/2, Array::null_array(),
|
|
/*checked_argument_count=*/1, Function::null_function());
|
|
}
|
|
body += StoreLocal(TokenPosition::kNoSource, result);
|
|
body += Drop();
|
|
|
|
body += Drop(); // arguments
|
|
body += Drop(); // argument count
|
|
|
|
AbstractType& return_type = AbstractType::Handle(function.result_type());
|
|
if (!return_type.IsDynamicType() && !return_type.IsVoidType() &&
|
|
!return_type.IsObjectType()) {
|
|
body += AssertAssignable(TokenPosition::kNoSource, return_type,
|
|
Symbols::Empty());
|
|
}
|
|
body += Return(TokenPosition::kNoSource);
|
|
|
|
return new (Z) FlowGraph(*parsed_function_, graph_entry_, last_used_block_id_,
|
|
prologue_info);
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::BuildDefaultTypeHandling(const Function& function) {
|
|
if (function.IsGeneric()) {
|
|
const TypeArguments& default_types =
|
|
parsed_function_->DefaultFunctionTypeArguments();
|
|
|
|
if (!default_types.IsNull()) {
|
|
Fragment then;
|
|
Fragment otherwise;
|
|
|
|
otherwise += TranslateInstantiatedTypeArguments(default_types);
|
|
otherwise += StoreLocal(TokenPosition::kNoSource,
|
|
parsed_function_->function_type_arguments());
|
|
otherwise += Drop();
|
|
return TestAnyTypeArgs(then, otherwise);
|
|
}
|
|
}
|
|
return Fragment();
|
|
}
|
|
|
|
// Pop the index of the current entry-point off the stack. If there is any
|
|
// entrypoint-tracing hook registered in a pragma for the function, it is called
|
|
// with the name of the current function and the current entry-point index.
|
|
Fragment FlowGraphBuilder::BuildEntryPointsIntrospection() {
|
|
if (!FLAG_enable_testing_pragmas) return Drop();
|
|
|
|
auto& function = Function::Handle(Z, parsed_function_->function().raw());
|
|
|
|
if (function.IsImplicitClosureFunction()) {
|
|
const auto& parent = Function::Handle(Z, function.parent_function());
|
|
const auto& func_name = String::Handle(Z, parent.name());
|
|
const auto& owner = Class::Handle(Z, parent.Owner());
|
|
function = owner.LookupFunction(func_name);
|
|
}
|
|
|
|
auto& tmp = Object::Handle(Z);
|
|
tmp = function.Owner();
|
|
tmp = Class::Cast(tmp).library();
|
|
auto& library = Library::Cast(tmp);
|
|
|
|
Object& options = Object::Handle(Z);
|
|
if (!library.FindPragma(thread_, function, Symbols::vm_trace_entrypoints(),
|
|
&options) ||
|
|
options.IsNull() || !options.IsClosure()) {
|
|
return Drop();
|
|
}
|
|
auto& closure = Closure::ZoneHandle(Z, Closure::Cast(options).raw());
|
|
LocalVariable* entry_point_num = MakeTemporary();
|
|
|
|
auto& function_name = String::ZoneHandle(
|
|
Z, String::New(function.ToLibNamePrefixedQualifiedCString(), Heap::kOld));
|
|
if (parsed_function_->function().IsImplicitClosureFunction()) {
|
|
function_name = String::Concat(
|
|
function_name, String::Handle(Z, String::New("#tearoff", Heap::kNew)),
|
|
Heap::kOld);
|
|
}
|
|
|
|
Fragment call_hook;
|
|
call_hook += Constant(closure);
|
|
call_hook += PushArgument();
|
|
call_hook += Constant(function_name);
|
|
call_hook += PushArgument();
|
|
call_hook += LoadLocal(entry_point_num);
|
|
call_hook += PushArgument();
|
|
call_hook += Constant(Function::ZoneHandle(Z, closure.function()));
|
|
call_hook += ClosureCall(TokenPosition::kNoSource,
|
|
/*type_args_len=*/0, /*argument_count=*/3,
|
|
/*argument_names=*/Array::ZoneHandle(Z));
|
|
call_hook += Drop(); // result of closure call
|
|
call_hook += Drop(); // entrypoint number
|
|
return call_hook;
|
|
}
|
|
|
|
FunctionEntryInstr* FlowGraphBuilder::BuildSharedUncheckedEntryPoint(
|
|
Fragment shared_prologue_linked_in,
|
|
Fragment skippable_checks,
|
|
Fragment redefinitions_if_skipped,
|
|
Fragment body) {
|
|
ASSERT(shared_prologue_linked_in.entry == graph_entry_->normal_entry());
|
|
ASSERT(parsed_function_->has_entry_points_temp_var());
|
|
Instruction* prologue_start = shared_prologue_linked_in.entry->next();
|
|
|
|
auto* join_entry = BuildJoinEntry();
|
|
|
|
Fragment normal_entry(shared_prologue_linked_in.entry);
|
|
normal_entry +=
|
|
IntConstant(static_cast<intptr_t>(UncheckedEntryPointStyle::kNone));
|
|
normal_entry += StoreLocal(TokenPosition::kNoSource,
|
|
parsed_function_->entry_points_temp_var());
|
|
normal_entry += Drop();
|
|
normal_entry += Goto(join_entry);
|
|
|
|
auto* extra_target_entry = BuildFunctionEntry(graph_entry_);
|
|
Fragment extra_entry(extra_target_entry);
|
|
extra_entry += IntConstant(
|
|
static_cast<intptr_t>(UncheckedEntryPointStyle::kSharedWithVariable));
|
|
extra_entry += StoreLocal(TokenPosition::kNoSource,
|
|
parsed_function_->entry_points_temp_var());
|
|
extra_entry += Drop();
|
|
extra_entry += Goto(join_entry);
|
|
|
|
join_entry->LinkTo(prologue_start);
|
|
|
|
TargetEntryInstr *do_checks, *skip_checks;
|
|
shared_prologue_linked_in +=
|
|
LoadLocal(parsed_function_->entry_points_temp_var());
|
|
shared_prologue_linked_in += BuildEntryPointsIntrospection();
|
|
shared_prologue_linked_in +=
|
|
LoadLocal(parsed_function_->entry_points_temp_var());
|
|
shared_prologue_linked_in += IntConstant(
|
|
static_cast<intptr_t>(UncheckedEntryPointStyle::kSharedWithVariable));
|
|
shared_prologue_linked_in +=
|
|
BranchIfEqual(&skip_checks, &do_checks, /*negate=*/false);
|
|
|
|
JoinEntryInstr* rest_entry = BuildJoinEntry();
|
|
|
|
Fragment(do_checks) + skippable_checks + Goto(rest_entry);
|
|
Fragment(skip_checks) + redefinitions_if_skipped + Goto(rest_entry);
|
|
Fragment(rest_entry) + body;
|
|
|
|
return extra_target_entry;
|
|
}
|
|
|
|
FunctionEntryInstr* FlowGraphBuilder::BuildSeparateUncheckedEntryPoint(
|
|
BlockEntryInstr* normal_entry,
|
|
Fragment normal_prologue,
|
|
Fragment extra_prologue,
|
|
Fragment shared_prologue,
|
|
Fragment body) {
|
|
auto* join_entry = BuildJoinEntry();
|
|
auto* extra_entry = BuildFunctionEntry(graph_entry_);
|
|
|
|
Fragment normal(normal_entry);
|
|
normal += IntConstant(static_cast<intptr_t>(UncheckedEntryPointStyle::kNone));
|
|
normal += BuildEntryPointsIntrospection();
|
|
normal += normal_prologue;
|
|
normal += Goto(join_entry);
|
|
|
|
Fragment extra(extra_entry);
|
|
extra +=
|
|
IntConstant(static_cast<intptr_t>(UncheckedEntryPointStyle::kSeparate));
|
|
extra += BuildEntryPointsIntrospection();
|
|
extra += extra_prologue;
|
|
extra += Goto(join_entry);
|
|
|
|
Fragment(join_entry) + shared_prologue + body;
|
|
return extra_entry;
|
|
}
|
|
|
|
void FlowGraphBuilder::RecordUncheckedEntryPoint(
|
|
FunctionEntryInstr* extra_entry) {
|
|
// Closures always check all arguments on their checked entry-point, most
|
|
// call-sites are unchecked, and they're inlined less often, so it's very
|
|
// beneficial to build multiple entry-points for them. Regular methods however
|
|
// have fewer checks to begin with since they have dynamic invocation
|
|
// forwarders, so in AOT we implement a more conservative time-space tradeoff
|
|
// by only building the unchecked entry-point when inlining. We should
|
|
// reconsider this heuristic if we identify non-inlined type-checks in
|
|
// hotspots of new benchmarks.
|
|
if (!IsInlining() && (parsed_function_->function().IsClosureFunction() ||
|
|
!FLAG_precompiled_mode)) {
|
|
graph_entry_->set_unchecked_entry(extra_entry);
|
|
} else if (InliningUncheckedEntry()) {
|
|
graph_entry_->set_normal_entry(extra_entry);
|
|
}
|
|
}
|
|
|
|
FlowGraph* FlowGraphBuilder::BuildGraphOfImplicitClosureFunction(
|
|
const Function& function) {
|
|
const Function& parent = Function::ZoneHandle(Z, function.parent_function());
|
|
const String& func_name = String::ZoneHandle(Z, parent.name());
|
|
const Class& owner = Class::ZoneHandle(Z, parent.Owner());
|
|
Function& target = Function::ZoneHandle(Z, owner.LookupFunction(func_name));
|
|
|
|
if (!target.IsNull() && (target.raw() != parent.raw())) {
|
|
DEBUG_ASSERT(Isolate::Current()->HasAttemptedReload());
|
|
if ((target.is_static() != parent.is_static()) ||
|
|
(target.kind() != parent.kind())) {
|
|
target = Function::null();
|
|
}
|
|
}
|
|
|
|
if (target.IsNull() ||
|
|
(parent.num_fixed_parameters() != target.num_fixed_parameters())) {
|
|
return BuildGraphOfNoSuchMethodForwarder(function, true,
|
|
parent.is_static());
|
|
}
|
|
|
|
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);
|
|
|
|
const Fragment prologue = CheckStackOverflowInPrologue(function.token_pos());
|
|
|
|
const Fragment default_type_handling = BuildDefaultTypeHandling(function);
|
|
|
|
// We're going to throw away the explicit checks because the target will
|
|
// always check them.
|
|
Fragment implicit_checks;
|
|
if (function.NeedsArgumentTypeChecks(I)) {
|
|
Fragment explicit_checks_unused;
|
|
if (target.is_static()) {
|
|
// Tearoffs of static methods needs to perform arguments checks since
|
|
// static methods they forward to don't do it themselves.
|
|
BuildArgumentTypeChecks(TypeChecksToBuild::kCheckAllTypeParameterBounds,
|
|
&explicit_checks_unused, &implicit_checks,
|
|
nullptr);
|
|
} else {
|
|
if (MethodCanSkipTypeChecksForNonCovariantArguments(
|
|
parent, ProcedureAttributesMetadata())) {
|
|
// Generate checks that are skipped inside a body of a function.
|
|
BuildArgumentTypeChecks(
|
|
TypeChecksToBuild::kCheckNonCovariantTypeParameterBounds,
|
|
&explicit_checks_unused, &implicit_checks, nullptr);
|
|
}
|
|
}
|
|
}
|
|
|
|
Fragment body;
|
|
|
|
intptr_t type_args_len = 0;
|
|
if (function.IsGeneric()) {
|
|
type_args_len = function.NumTypeParameters();
|
|
ASSERT(parsed_function_->function_type_arguments() != NULL);
|
|
body += LoadLocal(parsed_function_->function_type_arguments());
|
|
body += PushArgument();
|
|
}
|
|
|
|
// Push receiver.
|
|
if (!target.is_static()) {
|
|
// The context has a fixed shape: a single variable which is the
|
|
// closed-over receiver.
|
|
body += LoadLocal(parsed_function_->ParameterVariable(0));
|
|
body += LoadNativeField(Slot::Closure_context());
|
|
body += LoadNativeField(Slot::GetContextVariableSlotFor(
|
|
thread_, *parsed_function_->receiver_var()));
|
|
body += PushArgument();
|
|
}
|
|
|
|
body += PushExplicitParameters(function);
|
|
|
|
// Forward parameters to the target.
|
|
intptr_t argument_count = function.NumParameters() -
|
|
function.NumImplicitParameters() +
|
|
(target.is_static() ? 0 : 1);
|
|
ASSERT(argument_count == target.NumParameters());
|
|
|
|
Array& argument_names =
|
|
Array::ZoneHandle(Z, GetOptionalParameterNames(function));
|
|
|
|
body += StaticCall(TokenPosition::kNoSource, target, argument_count,
|
|
argument_names, ICData::kNoRebind,
|
|
/* result_type = */ NULL, type_args_len);
|
|
|
|
// Return the result.
|
|
body += Return(function.end_token_pos());
|
|
|
|
// Setup multiple entrypoints if useful.
|
|
FunctionEntryInstr* extra_entry = nullptr;
|
|
if (function.MayHaveUncheckedEntryPoint(I)) {
|
|
// The prologue for a closure will always have context handling (e.g.
|
|
// setting up the receiver variable), but we don't need it on the unchecked
|
|
// entry because the only time we reference this is for loading the
|
|
// receiver, which we fetch directly from the context.
|
|
if (PrologueBuilder::PrologueSkippableOnUncheckedEntry(function)) {
|
|
// Use separate entry points since we can skip almost everything on the
|
|
// static entry.
|
|
extra_entry = BuildSeparateUncheckedEntryPoint(
|
|
/*normal_entry=*/instruction_cursor,
|
|
/*normal_prologue=*/prologue + default_type_handling +
|
|
implicit_checks,
|
|
/*extra_prologue=*/
|
|
CheckStackOverflowInPrologue(function.token_pos()),
|
|
/*shared_prologue=*/Fragment(),
|
|
/*body=*/body);
|
|
} else {
|
|
Fragment shared_prologue(normal_entry, instruction_cursor);
|
|
shared_prologue += prologue;
|
|
extra_entry = BuildSharedUncheckedEntryPoint(
|
|
/*shared_prologue_linked_in=*/shared_prologue,
|
|
/*skippable_checks=*/default_type_handling + implicit_checks,
|
|
/*redefinitions_if_skipped=*/Fragment(),
|
|
/*body=*/body);
|
|
}
|
|
RecordUncheckedEntryPoint(extra_entry);
|
|
} else {
|
|
Fragment function(instruction_cursor);
|
|
function += prologue;
|
|
function += default_type_handling;
|
|
function += implicit_checks;
|
|
function += body;
|
|
}
|
|
|
|
return new (Z) FlowGraph(*parsed_function_, graph_entry_, last_used_block_id_,
|
|
prologue_info);
|
|
}
|
|
|
|
FlowGraph* FlowGraphBuilder::BuildGraphOfFieldAccessor(
|
|
const Function& function) {
|
|
ASSERT(function.IsImplicitGetterOrSetter() ||
|
|
function.IsDynamicInvocationForwarder());
|
|
|
|
// Instead of building a dynamic invocation forwarder that checks argument
|
|
// type and then invokes original setter we simply generate the type check
|
|
// and inlined field store. Scope builder takes care of setting correct
|
|
// type check mode in this case.
|
|
const bool is_setter = function.IsDynamicInvocationForwarder() ||
|
|
function.IsImplicitSetterFunction();
|
|
const bool is_method = !function.IsStaticFunction();
|
|
|
|
Field& field = Field::ZoneHandle(Z, function.accessor_field());
|
|
|
|
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);
|
|
if (is_setter) {
|
|
LocalVariable* setter_value =
|
|
parsed_function_->ParameterVariable(is_method ? 1 : 0);
|
|
|
|
// We only expect to generate a dynamic invocation forwarder if
|
|
// the value needs type check.
|
|
ASSERT(!function.IsDynamicInvocationForwarder() ||
|
|
setter_value->needs_type_check());
|
|
if (is_method) {
|
|
body += LoadLocal(parsed_function_->ParameterVariable(0));
|
|
}
|
|
body += LoadLocal(setter_value);
|
|
if (I->argument_type_checks() && setter_value->needs_type_check()) {
|
|
body += CheckAssignable(setter_value->type(), setter_value->name(),
|
|
AssertAssignableInstr::kParameterCheck);
|
|
}
|
|
if (is_method) {
|
|
body += StoreInstanceFieldGuarded(field, false);
|
|
} else {
|
|
body += StoreStaticField(TokenPosition::kNoSource, field);
|
|
}
|
|
body += NullConstant();
|
|
} else if (is_method) {
|
|
body += LoadLocal(parsed_function_->ParameterVariable(0));
|
|
body += LoadField(field);
|
|
} else if (field.is_const()) {
|
|
// If the parser needs to know the value of an uninitialized constant field
|
|
// it will set the value to the transition sentinel (used to detect circular
|
|
// initialization) and then call the implicit getter. Thus, the getter
|
|
// cannot contain the InitStaticField instruction that normal static getters
|
|
// contain because it would detect spurious circular initialization when it
|
|
// checks for the transition sentinel.
|
|
ASSERT(!field.IsUninitialized());
|
|
body += Constant(Instance::ZoneHandle(Z, field.StaticValue()));
|
|
} else {
|
|
// The field always has an initializer because static fields without
|
|
// initializers are initialized eagerly and do not have implicit getters.
|
|
ASSERT(field.has_initializer());
|
|
body += Constant(field);
|
|
body += InitStaticField(field);
|
|
body += Constant(field);
|
|
body += LoadStaticField();
|
|
}
|
|
body += Return(TokenPosition::kNoSource);
|
|
|
|
PrologueInfo prologue_info(-1, -1);
|
|
return new (Z) FlowGraph(*parsed_function_, graph_entry_, last_used_block_id_,
|
|
prologue_info);
|
|
}
|
|
|
|
FlowGraph* FlowGraphBuilder::BuildGraphOfDynamicInvocationForwarder(
|
|
const Function& function) {
|
|
auto& name = String::Handle(Z, function.name());
|
|
name = Function::DemangleDynamicInvocationForwarderName(name);
|
|
const auto& owner = Class::Handle(Z, function.Owner());
|
|
const auto& target =
|
|
Function::ZoneHandle(Z, owner.LookupDynamicFunction(name));
|
|
ASSERT(!target.IsNull());
|
|
ASSERT(!target.IsImplicitGetterFunction());
|
|
|
|
if (target.IsImplicitSetterFunction()) {
|
|
return BuildGraphOfFieldAccessor(function);
|
|
}
|
|
|
|
graph_entry_ = new (Z) GraphEntryInstr(*parsed_function_, osr_id_);
|
|
|
|
auto normal_entry = BuildFunctionEntry(graph_entry_);
|
|
graph_entry_->set_normal_entry(normal_entry);
|
|
|
|
PrologueInfo prologue_info(-1, -1);
|
|
auto instruction_cursor = BuildPrologue(normal_entry, &prologue_info);
|
|
|
|
Fragment body;
|
|
if (!function.is_native()) {
|
|
body += CheckStackOverflowInPrologue(function.token_pos());
|
|
}
|
|
|
|
ASSERT(parsed_function_->node_sequence()->scope()->num_context_variables() ==
|
|
0);
|
|
|
|
// Should never build a dynamic invocation forwarder for equality
|
|
// operator.
|
|
ASSERT(function.name() != Symbols::EqualOperator().raw());
|
|
|
|
// Even if the caller did not pass argument vector we would still
|
|
// call the target with instantiate-to-bounds type arguments.
|
|
body += BuildDefaultTypeHandling(function);
|
|
|
|
// Build argument type checks that complement those that are emitted in the
|
|
// target.
|
|
BuildArgumentTypeChecks(
|
|
TypeChecksToBuild::kCheckNonCovariantTypeParameterBounds, &body, &body,
|
|
nullptr);
|
|
|
|
// Push all arguments and invoke the original method.
|
|
|
|
intptr_t type_args_len = 0;
|
|
if (function.IsGeneric()) {
|
|
type_args_len = function.NumTypeParameters();
|
|
ASSERT(parsed_function_->function_type_arguments() != nullptr);
|
|
body += LoadLocal(parsed_function_->function_type_arguments());
|
|
body += PushArgument();
|
|
}
|
|
|
|
// Push receiver.
|
|
ASSERT(function.NumImplicitParameters() == 1);
|
|
body += LoadLocal(parsed_function_->receiver_var());
|
|
body += PushArgument();
|
|
|
|
body += PushExplicitParameters(function);
|
|
|
|
const intptr_t argument_count = function.NumParameters();
|
|
const auto& argument_names =
|
|
Array::ZoneHandle(Z, GetOptionalParameterNames(function));
|
|
|
|
body += StaticCall(TokenPosition::kNoSource, target, argument_count,
|
|
argument_names, ICData::kNoRebind, nullptr, type_args_len);
|
|
|
|
// 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.raw() == Symbols::AssignIndexToken().raw()) {
|
|
body += Drop();
|
|
body += NullConstant();
|
|
}
|
|
|
|
body += Return(TokenPosition::kNoSource);
|
|
|
|
instruction_cursor->LinkTo(body.entry);
|
|
|
|
// 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.
|
|
// Catch entries are always considered reachable, even if they
|
|
// become unreachable after OSR.
|
|
if (IsCompiledForOsr()) {
|
|
graph_entry_->RelinkToOsrEntry(Z, last_used_block_id_ + 1);
|
|
}
|
|
return new (Z) FlowGraph(*parsed_function_, graph_entry_, last_used_block_id_,
|
|
prologue_info);
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::UnboxTruncate(Representation to) {
|
|
auto* unbox = UnboxInstr::Create(to, Pop(), DeoptId::kNone,
|
|
Instruction::kNotSpeculative);
|
|
Push(unbox);
|
|
return Fragment(unbox);
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::LoadAddressFromFfiPointer() {
|
|
Fragment test;
|
|
TargetEntryInstr* null_entry;
|
|
TargetEntryInstr* not_null_entry;
|
|
JoinEntryInstr* join = BuildJoinEntry();
|
|
|
|
LocalVariable* result = parsed_function_->expression_temp_var();
|
|
|
|
LocalVariable* pointer = MakeTemporary();
|
|
test += LoadLocal(pointer);
|
|
test += BranchIfNull(&null_entry, ¬_null_entry);
|
|
|
|
Fragment load_0(null_entry);
|
|
load_0 += IntConstant(0);
|
|
load_0 += StoreLocal(TokenPosition::kNoSource, result);
|
|
load_0 += Drop();
|
|
load_0 += Goto(join);
|
|
|
|
Fragment unbox(not_null_entry);
|
|
unbox += LoadLocal(pointer);
|
|
unbox += LoadNativeField(Slot::Pointer_c_memory_address());
|
|
unbox += StoreLocal(TokenPosition::kNoSource, result);
|
|
unbox += Drop();
|
|
unbox += Goto(join);
|
|
|
|
Fragment done{test.entry, join};
|
|
done += Drop();
|
|
done += LoadLocal(result);
|
|
|
|
return done;
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::Box(Representation from) {
|
|
BoxInstr* box = BoxInstr::Create(from, Pop());
|
|
Push(box);
|
|
return Fragment(box);
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::FfiUnboxedExtend(Representation representation,
|
|
const AbstractType& ffi_type) {
|
|
const intptr_t width =
|
|
compiler::ffi::ElementSizeInBytes(ffi_type.type_class_id());
|
|
if (width >= compiler::ffi::kMinimumArgumentWidth) return {};
|
|
|
|
auto* extend =
|
|
new (Z) UnboxedWidthExtenderInstr(Pop(), representation, width);
|
|
Push(extend);
|
|
return Fragment(extend);
|
|
}
|
|
|
|
Fragment FlowGraphBuilder::FfiPointerFromAddress(const Type& result_type) {
|
|
Fragment test;
|
|
TargetEntryInstr* null_entry;
|
|
TargetEntryInstr* not_null_entry;
|
|
JoinEntryInstr* join = BuildJoinEntry();
|
|
|
|
LocalVariable* address = MakeTemporary();
|
|
LocalVariable* result = parsed_function_->expression_temp_var();
|
|
|
|
test += LoadLocal(address);
|
|
test += IntConstant(0);
|
|
test += BranchIfEqual(&null_entry, ¬_null_entry);
|
|
|
|
// If the result is 0, we return null because "0 means null".
|
|
Fragment load_null(null_entry);
|
|
{
|
|
load_null += NullConstant();
|
|
load_null += StoreLocal(TokenPosition::kNoSource, result);
|
|
load_null += Drop();
|
|
load_null += Goto(join);
|
|
}
|
|
|
|
Fragment box(not_null_entry);
|
|
{
|
|
Class& result_class = Class::ZoneHandle(Z, result_type.type_class());
|
|
TypeArguments& args = TypeArguments::ZoneHandle(Z, result_type.arguments());
|
|
|
|
// A kernel transform for FFI in the front-end ensures that type parameters
|
|
// do not appear in the type arguments to a any Pointer classes in an FFI
|
|
// signature.
|
|
ASSERT(args.IsNull() || args.IsInstantiated());
|
|
|
|
box += Constant(args);
|
|
box += PushArgument();
|
|
box += AllocateObject(TokenPosition::kNoSource, result_class, 1);
|
|
LocalVariable* pointer = MakeTemporary();
|
|
box += LoadLocal(pointer);
|
|
box += LoadLocal(address);
|
|
box += StoreInstanceField(TokenPosition::kNoSource,
|
|
Slot::Pointer_c_memory_address());
|
|
box += StoreLocal(TokenPosition::kNoSource, result);
|
|
box += Drop();
|
|
box += Goto(join);
|
|
}
|
|
|
|
Fragment rest(test.entry, join);
|
|
rest += Drop();
|
|
rest += LoadLocal(result);
|
|
|
|
return rest;
|
|
}
|
|
|
|
FlowGraph* FlowGraphBuilder::BuildGraphOfFfiTrampoline(
|
|
const Function& function) {
|
|
#if !defined(TARGET_ARCH_X64) && !defined(TARGET_ARCH_ARM64) && \
|
|
!defined(TARGET_ARCH_IA32)
|
|
UNREACHABLE();
|
|
#else
|
|
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());
|
|
|
|
const Function& signature = Function::ZoneHandle(Z, function.FfiCSignature());
|
|
const auto& arg_reps = *compiler::ffi::ArgumentRepresentations(signature);
|
|
const auto& arg_locs = *compiler::ffi::ArgumentLocations(arg_reps);
|
|
|
|
BuildArgumentTypeChecks(TypeChecksToBuild::kCheckAllTypeParameterBounds,
|
|
&body, &body, &body);
|
|
|
|
// Unbox and push the arguments.
|
|
AbstractType& ffi_type = AbstractType::Handle(Z);
|
|
for (intptr_t pos = 1; pos < function.num_fixed_parameters(); pos++) {
|
|
body += LoadLocal(parsed_function_->ParameterVariable(pos));
|
|
ffi_type = signature.ParameterTypeAt(pos);
|
|
|
|
// Check for 'null'. Only ffi.Pointers are allowed to be null.
|
|
if (!compiler::ffi::NativeTypeIsPointer(ffi_type)) {
|
|
body += LoadLocal(parsed_function_->ParameterVariable(pos));
|
|
body <<=
|
|
new (Z) CheckNullInstr(Pop(), String::ZoneHandle(Z, function.name()),
|
|
GetNextDeoptId(), TokenPosition::kNoSource);
|
|
}
|
|
|
|
if (compiler::ffi::NativeTypeIsPointer(ffi_type)) {
|
|
body += LoadAddressFromFfiPointer();
|
|
body += UnboxTruncate(kUnboxedIntPtr);
|
|
} else {
|
|
Representation rep = arg_reps[pos - 1];
|
|
body += UnboxTruncate(rep);
|
|
body += FfiUnboxedExtend(rep, ffi_type);
|
|
}
|
|
}
|
|
|
|
// Push the function pointer, which is stored (boxed) in the first slot of the
|
|
// context.
|
|
body += LoadLocal(parsed_function_->ParameterVariable(0));
|
|
body += LoadNativeField(Slot::Closure_context());
|
|
body += LoadNativeField(Slot::GetContextVariableSlotFor(
|
|
thread_, *MakeImplicitClosureScope(
|
|
Z, Class::Handle(I->object_store()->ffi_pointer_class()))
|
|
->context_variables()[0]));
|
|
body += UnboxTruncate(kUnboxedIntPtr);
|
|
body += FfiCall(signature, arg_reps, arg_locs);
|
|
|
|
ffi_type = signature.result_type();
|
|
if (compiler::ffi::NativeTypeIsPointer(ffi_type)) {
|
|
body += Box(kUnboxedIntPtr);
|
|
body += FfiPointerFromAddress(Type::Cast(ffi_type));
|
|
} else if (compiler::ffi::NativeTypeIsVoid(ffi_type)) {
|
|
body += Drop();
|
|
body += NullConstant();
|
|
} else {
|
|
Representation rep = compiler::ffi::ResultRepresentation(signature);
|
|
body += FfiUnboxedExtend(rep, ffi_type);
|
|
body += Box(rep);
|
|
}
|
|
|
|
body += Return(TokenPosition::kNoSource);
|
|
|
|
return new (Z) FlowGraph(*parsed_function_, graph_entry_, last_used_block_id_,
|
|
prologue_info);
|
|
#endif
|
|
}
|
|
|
|
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)
|