[vm] Decouple flow graph building of implicit closure functions from kernel reading

Change-Id: I6bf5ad6ed76c363904ae1765d8303eff4a799363
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/95706
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Alexander Markov <alexmarkov@google.com>
This commit is contained in:
Alexander Markov
2019-03-07 18:09:48 +00:00
committed by commit-bot@chromium.org
parent f44fecbe39
commit f2a8e71ff9
6 changed files with 738 additions and 723 deletions
@@ -422,531 +422,30 @@ Fragment StreamingFlowGraphBuilder::BuildInitializers(
return instructions;
}
// If no type arguments are passed to a generic function, we need to fill the
// type arguments in with the default types stored on the TypeParameter nodes
// in Kernel.
Fragment StreamingFlowGraphBuilder::BuildDefaultTypeHandling(
const Function& function,
intptr_t type_parameters_offset) {
if (function.IsGeneric()) {
AlternativeReadingScope alt(&reader_);
SetOffset(type_parameters_offset);
intptr_t num_type_params = ReadListLength();
ASSERT(num_type_params == function.NumTypeParameters());
TypeArguments& default_types =
TypeArguments::ZoneHandle(TypeArguments::New(num_type_params));
for (intptr_t i = 0; i < num_type_params; ++i) {
TypeParameterHelper helper(this);
helper.ReadUntilExcludingAndSetJustRead(
TypeParameterHelper::kDefaultType);
if (ReadTag() == kSomething) {
default_types.SetTypeAt(i, T.BuildType());
} else {
default_types.SetTypeAt(i, Object::dynamic_type());
}
helper.Finish();
}
default_types = default_types.Canonicalize();
if (!default_types.IsNull()) {
Fragment then;
Fragment otherwise;
otherwise += TranslateInstantiatedTypeArguments(default_types);
otherwise += StoreLocal(TokenPosition::kNoSource,
parsed_function()->function_type_arguments());
otherwise += Drop();
return B->TestAnyTypeArgs(then, otherwise);
}
}
return Fragment();
}
void StreamingFlowGraphBuilder::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 (!B->IsInlining() && (parsed_function()->function().IsClosureFunction() ||
!FLAG_precompiled_mode)) {
B->graph_entry_->set_unchecked_entry(extra_entry);
} else if (B->InliningUncheckedEntry()) {
B->graph_entry_->set_normal_entry(extra_entry);
}
}
FlowGraph* StreamingFlowGraphBuilder::BuildGraphOfImplicitClosureFunction(
void StreamingFlowGraphBuilder::ReadDefaultFunctionTypeArguments(
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 (!function.IsGeneric()) {
return;
}
if (target.IsNull() ||
(parent.num_fixed_parameters() != target.num_fixed_parameters())) {
return BuildGraphOfNoSuchMethodForwarder(function, true,
parent.is_static());
}
// The prologue builder needs the default parameter values.
SetupDefaultParameterValues();
flow_graph_builder_->graph_entry_ =
new (Z) GraphEntryInstr(*parsed_function(), Compiler::kNoOSRDeoptId);
auto normal_entry = flow_graph_builder_->BuildFunctionEntry(
flow_graph_builder_->graph_entry_);
flow_graph_builder_->graph_entry_->set_normal_entry(normal_entry);
PrologueInfo prologue_info(-1, -1);
BlockEntryInstr* instruction_cursor =
flow_graph_builder_->BuildPrologue(normal_entry, &prologue_info);
const Fragment prologue =
flow_graph_builder_->CheckStackOverflowInPrologue(function.token_pos());
AlternativeReadingScope alt(&reader_);
FunctionNodeHelper function_node_helper(this);
function_node_helper.ReadUntilExcluding(FunctionNodeHelper::kTypeParameters);
const Fragment default_type_handling =
BuildDefaultTypeHandling(function, ReaderOffset());
const ProcedureAttributesMetadata parent_attrs =
procedure_attributes_metadata_helper_.GetProcedureAttributes(
parent.kernel_offset());
// 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.
AlternativeReadingScope _(&reader_);
B->BuildArgumentTypeChecks(
TypeChecksToBuild::kCheckAllTypeParameterBounds,
&explicit_checks_unused, &implicit_checks, nullptr);
intptr_t num_type_params = ReadListLength();
ASSERT(num_type_params == function.NumTypeParameters());
TypeArguments& default_types =
TypeArguments::Handle(Z, TypeArguments::New(num_type_params));
for (intptr_t i = 0; i < num_type_params; ++i) {
TypeParameterHelper helper(this);
helper.ReadUntilExcludingAndSetJustRead(TypeParameterHelper::kDefaultType);
if (ReadTag() == kSomething) {
default_types.SetTypeAt(i, T.BuildType());
} else {
// Check if parent function was annotated with no-dynamic-invocations.
if (MethodCanSkipTypeChecksForNonCovariantArguments(parent,
parent_attrs)) {
// If it was then we might need to build some checks in the
// tear-off.
AlternativeReadingScope _(&reader_);
B->BuildArgumentTypeChecks(
TypeChecksToBuild::kCheckNonCovariantTypeParameterBounds,
&explicit_checks_unused, &implicit_checks, nullptr);
}
default_types.SetTypeAt(i, Object::dynamic_type());
}
helper.Finish();
}
Fragment body;
function_node_helper.ReadUntilExcluding(
FunctionNodeHelper::kPositionalParameters);
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();
}
// Load all the arguments.
if (!target.is_static()) {
// The context has a fixed shape: a single variable which is the
// closed-over receiver.
body +=
LoadLocal(parsed_function()->node_sequence()->scope()->VariableAt(0));
body += LoadNativeField(Slot::Closure_context());
body += LoadNativeField(
Slot::GetContextVariableSlotFor(thread(), *scopes()->this_variable));
body += PushArgument();
}
// Positional.
intptr_t positional_argument_count = ReadListLength();
for (intptr_t i = 0; i < positional_argument_count; ++i) {
body += LoadLocal(LookupVariable(
ReaderOffset() + data_program_offset_)); // ith variable offset.
body += PushArgument();
SkipVariableDeclaration(); // read ith variable.
}
// Named.
intptr_t named_argument_count = ReadListLength();
Array& argument_names = Array::ZoneHandle(Z);
if (named_argument_count > 0) {
argument_names = Array::New(named_argument_count, H.allocation_space());
for (intptr_t i = 0; i < named_argument_count; ++i) {
// ith variable offset.
body += LoadLocal(LookupVariable(ReaderOffset() + data_program_offset_));
body += PushArgument();
// read ith variable.
VariableDeclarationHelper helper(this);
helper.ReadUntilExcluding(VariableDeclarationHelper::kEnd);
argument_names.SetAt(i, H.DartSymbolObfuscate(helper.name_index_));
}
}
// Forward them to the parent.
intptr_t argument_count = positional_argument_count + named_argument_count;
if (!parent.is_static()) {
++argument_count;
}
body += StaticCall(TokenPosition::kNoSource, target, argument_count,
argument_names, ICData::kNoRebind,
/* result_type = */ NULL, type_args_len);
// Return the result.
body += Return(function_node_helper.end_position_);
// 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 'this_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=*/
B->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(), flow_graph_builder_->graph_entry_,
flow_graph_builder_->last_used_block_id_, prologue_info);
}
// If throw_no_such_method_error is set to true (defaults to false), an
// instance of NoSuchMethodError is thrown. Otherwise, the instance
// noSuchMethod is called.
FlowGraph* StreamingFlowGraphBuilder::BuildGraphOfNoSuchMethodForwarder(
const Function& function,
bool is_implicit_closure_function,
bool throw_no_such_method_error) {
// The prologue builder needs the default parameter values.
SetupDefaultParameterValues();
B->graph_entry_ =
new (Z) GraphEntryInstr(*parsed_function(), Compiler::kNoOSRDeoptId);
auto normal_entry = B->BuildFunctionEntry(B->graph_entry_);
B->graph_entry_->set_normal_entry(normal_entry);
PrologueInfo prologue_info(-1, -1);
BlockEntryInstr* instruction_cursor =
B->BuildPrologue(normal_entry, &prologue_info);
Fragment body(instruction_cursor);
body += B->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 += B->LoadArgDescriptor();
body += LoadNativeField(Slot::ArgumentsDescriptor_count());
body += LoadLocal(parsed_function()->current_context_var());
body += B->LoadNativeField(
Slot::GetContextVariableSlotFor(thread(), *scopes()->this_variable));
body += B->StoreFpRelativeSlot(
kWordSize * compiler::target::frame_layout.param_end_from_fp);
} else {
body += LoadLocal(parsed_function()->current_context_var());
body += B->LoadNativeField(
Slot::GetContextVariableSlotFor(thread(), *scopes()->this_variable));
body += B->StoreFpRelativeSlot(
kWordSize * (compiler::target::frame_layout.param_end_from_fp +
function.NumParameters()));
}
}
FunctionNodeHelper function_node_helper(this);
function_node_helper.ReadUntilExcluding(FunctionNodeHelper::kTypeParameters);
if (function.NeedsArgumentTypeChecks(I)) {
AlternativeReadingScope _(&reader_);
B->BuildArgumentTypeChecks(TypeChecksToBuild::kCheckAllTypeParameterBounds,
&body, &body, nullptr);
}
function_node_helper.ReadUntilExcluding(
FunctionNodeHelper::kPositionalParameters);
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 += flow_graph_builder_->TestAnyTypeArgs(then, otherwise);
}
if (function.HasOptionalParameters()) {
body += B->LoadArgDescriptor();
body += LoadNativeField(Slot::ArgumentsDescriptor_count());
} else {
body += IntConstant(function.NumParameters());
}
body += LoadLocal(argument_count_var);
body += B->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 += B->TestAnyTypeArgs(store, Fragment());
}
TargetEntryInstr* body_entry;
TargetEntryInstr* loop_exit;
Fragment condition;
// i < argument_count
condition += LoadLocal(index);
condition += LoadLocal(argument_count);
condition += B->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 += B->SmiBinaryOp(Token::kSUB, /*truncate=*/true);
loop_body += B->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 += B->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 += B->LoadNativeField(
Slot::GetContextVariableSlotFor(thread(), *scopes()->this_variable));
}
} else {
LocalScope* scope = parsed_function()->node_sequence()->scope();
body += LoadLocal(scope->VariableAt(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 += B->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()->node_sequence()->scope()->VariableAt(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 += B->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, 2, 1);
}
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 += flow_graph_builder_->AssertAssignable(
TokenPosition::kNoSource, return_type, Symbols::Empty());
}
body += Return(TokenPosition::kNoSource);
return new (Z) FlowGraph(*parsed_function(), B->graph_entry_,
B->last_used_block_id_, prologue_info);
default_types = default_types.Canonicalize();
parsed_function()->SetDefaultFunctionTypeArguments(default_types);
}
Fragment StreamingFlowGraphBuilder::PushAllArguments(PushedArguments* pushed) {
@@ -1011,6 +510,8 @@ FlowGraph* StreamingFlowGraphBuilder::BuildGraphOfDynamicInvocationForwarder() {
// The prologue builder needs the default parameter values.
SetupDefaultParameterValues();
// BuildDefaultTypeHandling needs default function type arguments.
ReadDefaultFunctionTypeArguments(dart_function);
B->graph_entry_ = new (Z) GraphEntryInstr(*parsed_function(), B->osr_id_);
@@ -1050,7 +551,7 @@ FlowGraph* StreamingFlowGraphBuilder::BuildGraphOfDynamicInvocationForwarder() {
// Even if the caller did not pass argument vector we would still
// call the target with instantiate-to-bounds type arguments.
body += BuildDefaultTypeHandling(dart_function, type_parameters_offset);
body += B->BuildDefaultTypeHandling(dart_function);
String& name = String::Handle(Z, dart_function.name());
name = Function::DemangleDynamicInvocationForwarderName(name);
@@ -1164,10 +665,8 @@ Fragment StreamingFlowGraphBuilder::SetAsyncStackTrace(
}
Fragment StreamingFlowGraphBuilder::TypeArgumentsHandling(
const Function& dart_function,
intptr_t type_parameters_offset) {
Fragment prologue =
BuildDefaultTypeHandling(dart_function, type_parameters_offset);
const Function& dart_function) {
Fragment prologue = B->BuildDefaultTypeHandling(dart_function);
if (dart_function.IsClosureFunction() &&
dart_function.NumParentTypeParameters() > 0) {
@@ -1448,134 +947,7 @@ Fragment StreamingFlowGraphBuilder::BuildFirstTimePrologue(
return F;
}
// 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 StreamingFlowGraphBuilder::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(H.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 += B->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* StreamingFlowGraphBuilder::BuildSharedUncheckedEntryPoint(
Fragment shared_prologue_linked_in,
Fragment skippable_checks,
Fragment redefinitions_if_skipped,
Fragment body) {
ASSERT(shared_prologue_linked_in.entry == B->graph_entry_->normal_entry());
ASSERT(parsed_function()->has_entry_points_temp_var());
Instruction* prologue_start = shared_prologue_linked_in.entry->next();
auto* join_entry = B->BuildJoinEntry();
Fragment normal_entry(shared_prologue_linked_in.entry);
normal_entry += IntConstant(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 = B->BuildFunctionEntry(B->graph_entry_);
Fragment extra_entry(extra_target_entry);
extra_entry += IntConstant(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(UncheckedEntryPointStyle::kSharedWithVariable);
shared_prologue_linked_in +=
BranchIfEqual(&skip_checks, &do_checks, /*negate=*/false);
JoinEntryInstr* rest_entry = B->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* StreamingFlowGraphBuilder::BuildSeparateUncheckedEntryPoint(
BlockEntryInstr* normal_entry,
Fragment normal_prologue,
Fragment extra_prologue,
Fragment shared_prologue,
Fragment body) {
auto* join_entry = BuildJoinEntry();
auto* extra_entry = B->BuildFunctionEntry(B->graph_entry_);
Fragment normal(normal_entry);
normal += IntConstant(UncheckedEntryPointStyle::kNone);
normal += BuildEntryPointsIntrospection();
normal += normal_prologue;
normal += Goto(join_entry);
Fragment extra(extra_entry);
extra += IntConstant(UncheckedEntryPointStyle::kSeparate);
extra += BuildEntryPointsIntrospection();
extra += extra_prologue;
extra += Goto(join_entry);
Fragment(join_entry) + shared_prologue + body;
return extra_entry;
}
StreamingFlowGraphBuilder::UncheckedEntryPointStyle
StreamingFlowGraphBuilder::ChooseEntryPointStyle(
UncheckedEntryPointStyle StreamingFlowGraphBuilder::ChooseEntryPointStyle(
const Function& dart_function,
const Fragment& implicit_type_checks,
const Fragment& first_time_prologue,
@@ -1611,8 +983,13 @@ StreamingFlowGraphBuilder::ChooseEntryPointStyle(
FlowGraph* StreamingFlowGraphBuilder::BuildGraphOfFunction(
bool is_constructor) {
const Function& dart_function = parsed_function()->function();
// The prologue builder needs the default parameter values.
SetupDefaultParameterValues();
// TypeArgumentsHandling / BuildDefaultTypeHandling needs
// default function type arguments.
ReadDefaultFunctionTypeArguments(dart_function);
intptr_t type_parameters_offset = 0;
LocalVariable* first_parameter = nullptr;
@@ -1633,8 +1010,6 @@ FlowGraph* StreamingFlowGraphBuilder::BuildGraphOfFunction(
token_position = function_node_helper.position_;
}
const Function& dart_function = parsed_function()->function();
auto graph_entry = flow_graph_builder_->graph_entry_ =
new (Z) GraphEntryInstr(*parsed_function(), flow_graph_builder_->osr_id_);
@@ -1658,8 +1033,7 @@ FlowGraph* StreamingFlowGraphBuilder::BuildGraphOfFunction(
// TODO(#34162): We can remove the default type handling (and
// shorten the prologue type handling sequence) for non-dynamic invocations of
// regular methods.
const Fragment type_args_handling =
TypeArgumentsHandling(dart_function, type_parameters_offset);
const Fragment type_args_handling = TypeArgumentsHandling(dart_function);
Fragment explicit_type_checks;
Fragment implicit_type_checks;
@@ -1693,7 +1067,7 @@ FlowGraph* StreamingFlowGraphBuilder::BuildGraphOfFunction(
const Fragment prologue_copy = BuildEveryTimePrologue(
dart_function, token_position, type_parameters_offset);
extra_entry = BuildSeparateUncheckedEntryPoint(
extra_entry = B->BuildSeparateUncheckedEntryPoint(
normal_entry,
/*normal_prologue=*/every_time_prologue + implicit_type_checks,
/*extra_prologue=*/prologue_copy,
@@ -1707,7 +1081,7 @@ FlowGraph* StreamingFlowGraphBuilder::BuildGraphOfFunction(
prologue += first_time_prologue;
prologue += type_args_handling;
prologue += explicit_type_checks;
extra_entry = BuildSharedUncheckedEntryPoint(
extra_entry = B->BuildSharedUncheckedEntryPoint(
/*shared_prologue_linked_in=*/prologue,
/*skippable_checks=*/implicit_type_checks,
/*redefinitions_if_skipped=*/implicit_redefinitions,
@@ -1716,7 +1090,7 @@ FlowGraph* StreamingFlowGraphBuilder::BuildGraphOfFunction(
}
}
if (extra_entry != nullptr) {
RecordUncheckedEntryPoint(extra_entry);
B->RecordUncheckedEntryPoint(extra_entry);
}
} else {
// If the function's body contains any yield points, build switch statement
@@ -1824,15 +1198,8 @@ FlowGraph* StreamingFlowGraphBuilder::BuildGraph() {
switch (function.kind()) {
case RawFunction::kRegularFunction:
case RawFunction::kImplicitClosureFunction:
case RawFunction::kGetterFunction:
case RawFunction::kSetterFunction: {
ReadUntilFunctionNode();
if (function.IsImplicitClosureFunction()) {
return BuildGraphOfImplicitClosureFunction(function);
}
FALL_THROUGH;
}
case RawFunction::kSetterFunction:
case RawFunction::kClosureFunction: {
ReadUntilFunctionNode();
return BuildGraphOfFunction(false);
@@ -1861,6 +1228,11 @@ FlowGraph* StreamingFlowGraphBuilder::BuildGraph() {
return flow_graph_builder_->BuildGraphOfNoSuchMethodDispatcher(function);
case RawFunction::kInvokeFieldDispatcher:
return flow_graph_builder_->BuildGraphOfInvokeFieldDispatcher(function);
case RawFunction::kImplicitClosureFunction:
ReadUntilFunctionNode();
SetupDefaultParameterValues();
ReadDefaultFunctionTypeArguments(function);
return flow_graph_builder_->BuildGraphOfImplicitClosureFunction(function);
case RawFunction::kSignatureFunction:
case RawFunction::kIrregexpFunction:
case RawFunction::kFfiTrampoline:
@@ -59,48 +59,15 @@ class StreamingFlowGraphBuilder : public KernelReaderHelper {
FlowGraph* BuildGraphOfFieldInitializer();
FlowGraph* BuildGraphOfFieldAccessor(LocalVariable* setter_value);
void SetupDefaultParameterValues();
void ReadDefaultFunctionTypeArguments(const Function& function);
Fragment BuildFieldInitializer(NameIndex canonical_name);
Fragment BuildInitializers(const Class& parent_class);
FlowGraph* BuildGraphOfImplicitClosureFunction(const Function& function);
FlowGraph* BuildGraphOfFunction(bool constructor);
FlowGraph* BuildGraphOfDynamicInvocationForwarder();
FlowGraph* BuildGraphOfNoSuchMethodForwarder(
const Function& function,
bool is_implicit_closure_function,
bool throw_no_such_method_error = false);
Fragment BuildExpression(TokenPosition* position = NULL);
Fragment BuildStatement();
// Indicates which form of the unchecked entrypoint we are compiling.
//
// kNone:
//
// There is no unchecked entrypoint: the unchecked entry is set to NULL in
// the 'GraphEntryInstr'.
//
// kSeparate:
//
// The normal and unchecked entrypoint each point to their own versions of
// the prologue, containing exactly those checks which need to be performed
// on either side. Both sides jump directly to the body after performing
// their prologue.
//
// kSharedWithVariable:
//
// A temporary variable is allocated and initialized to 0 on normal entry
// and 2 on unchecked entry. Code which should be ommitted on the unchecked
// entrypoint is made conditional on this variable being equal to 0.
//
struct UncheckedEntryPointStyle_ {
enum Style {
kNone = 0,
kSeparate = 1,
kSharedWithVariable = 2,
};
};
typedef UncheckedEntryPointStyle_::Style UncheckedEntryPointStyle;
// Kernel offset:
// start of function expression -> end of function body statement
Fragment BuildFunctionBody(const Function& dart_function,
@@ -121,27 +88,13 @@ class StreamingFlowGraphBuilder : public KernelReaderHelper {
Fragment SetupCapturedParameters(const Function& dart_function);
Fragment ShortcutForUserDefinedEquals(const Function& dart_function,
LocalVariable* first_parameter);
Fragment TypeArgumentsHandling(const Function& dart_function,
intptr_t type_parameters_offset);
Fragment TypeArgumentsHandling(const Function& dart_function);
void CheckArgumentTypesAsNecessary(const Function& dart_function,
intptr_t type_parameters_offset,
Fragment* explicit_checks,
Fragment* implicit_checks,
Fragment* implicit_redefinitions);
Fragment CompleteBodyWithYieldContinuations(Fragment body);
FunctionEntryInstr* BuildSeparateUncheckedEntryPoint(
BlockEntryInstr* normal_entry,
Fragment normal_prologue,
Fragment extra_prologue,
Fragment shared_prologue,
Fragment body);
FunctionEntryInstr* BuildSharedUncheckedEntryPoint(
Fragment prologue_from_normal_entry,
Fragment skippable_checks,
Fragment redefinitions_if_skipped,
Fragment body);
Fragment BuildEntryPointsIntrospection();
static UncheckedEntryPointStyle ChooseEntryPointStyle(
const Function& dart_function,
@@ -150,8 +103,6 @@ class StreamingFlowGraphBuilder : public KernelReaderHelper {
const Fragment& every_time_prologue,
const Fragment& type_args_handling);
void RecordUncheckedEntryPoint(FunctionEntryInstr* extra_entry);
void loop_depth_inc();
void loop_depth_dec();
intptr_t for_in_depth();
@@ -242,10 +193,6 @@ class StreamingFlowGraphBuilder : public KernelReaderHelper {
bool use_unchecked_entry = false,
const CallSiteAttributesMetadata* call_site_attrs = nullptr);
// Does not move the cursor.
Fragment BuildDefaultTypeHandling(const Function& function,
intptr_t type_parameters_offset);
struct PushedArguments {
intptr_t type_args_len;
intptr_t argument_count;
+604 -1
View File
@@ -1389,7 +1389,7 @@ FlowGraph* FlowGraphBuilder::BuildGraphOfInvokeFieldDispatcher(
ASSERT(!owner.IsNull());
const String& field_name = String::Handle(Z, function.name());
const String& getter_name = String::ZoneHandle(
Z, Symbols::New(H.thread(),
Z, Symbols::New(thread_,
String::Handle(Z, Field::GetterSymbol(field_name))));
// Determine if this is `class Closure { get call => this; }`
@@ -1488,6 +1488,609 @@ FlowGraph* FlowGraphBuilder::BuildGraphOfInvokeFieldDispatcher(
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_, *scopes_->this_variable));
body += StoreFpRelativeSlot(
kWordSize * compiler::target::frame_layout.param_end_from_fp);
} else {
body += LoadLocal(parsed_function_->current_context_var());
body += LoadNativeField(
Slot::GetContextVariableSlotFor(thread_, *scopes_->this_variable));
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_, *scopes_->this_variable));
}
} else {
LocalScope* scope = parsed_function_->node_sequence()->scope();
body += LoadLocal(scope->VariableAt(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_->node_sequence()->scope()->VariableAt(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();
}
// Load all the arguments.
auto scope = parsed_function_->node_sequence()->scope();
if (!target.is_static()) {
// The context has a fixed shape: a single variable which is the
// closed-over receiver.
body += LoadLocal(scope->VariableAt(0));
body += LoadNativeField(Slot::Closure_context());
body += LoadNativeField(
Slot::GetContextVariableSlotFor(thread_, *scopes_->this_variable));
body += PushArgument();
}
// Positional.
intptr_t arg_index = function.NumImplicitParameters();
const intptr_t named_argument_count = function.NumOptionalNamedParameters();
const intptr_t positional_argument_count = function.NumParameters() -
function.NumImplicitParameters() -
named_argument_count;
for (intptr_t i = 0; i < positional_argument_count; ++i, ++arg_index) {
body += LoadLocal(scope->VariableAt(arg_index)); // ith variable offset.
body += PushArgument();
}
// Named.
Array& argument_names = Array::ZoneHandle(Z);
if (named_argument_count > 0) {
argument_names = Array::New(named_argument_count, Heap::kOld);
String& name = String::Handle(Z);
for (intptr_t i = 0; i < named_argument_count; ++i, ++arg_index) {
body += LoadLocal(scope->VariableAt(arg_index));
body += PushArgument();
name = function.ParameterNameAt(arg_index);
argument_names.SetAt(i, name);
}
}
// Forward them to the parent.
intptr_t argument_count = positional_argument_count + named_argument_count;
if (!parent.is_static()) {
++argument_count;
}
ASSERT(argument_count == target.NumParameters());
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 'this_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);
}
void FlowGraphBuilder::SetCurrentTryCatchBlock(TryCatchBlock* try_catch_block) {
try_catch_block_ = try_catch_block;
SetCurrentTryIndex(try_catch_block == nullptr ? kInvalidTryIndex
@@ -47,6 +47,32 @@ enum class TypeChecksToBuild {
kCheckCovariantTypeParameterBounds,
};
// Indicates which form of the unchecked entrypoint we are compiling.
//
// kNone:
//
// There is no unchecked entrypoint: the unchecked entry is set to NULL in
// the 'GraphEntryInstr'.
//
// kSeparate:
//
// The normal and unchecked entrypoint each point to their own versions of
// the prologue, containing exactly those checks which need to be performed
// on either side. Both sides jump directly to the body after performing
// their prologue.
//
// kSharedWithVariable:
//
// A temporary variable is allocated and initialized to 0 on normal entry
// and 2 on unchecked entry. Code which should be ommitted on the unchecked
// entrypoint is made conditional on this variable being equal to 0.
//
enum class UncheckedEntryPointStyle {
kNone = 0,
kSeparate = 1,
kSharedWithVariable = 2,
};
class FlowGraphBuilder : public BaseFlowGraphBuilder {
public:
FlowGraphBuilder(ParsedFunction* parsed_function,
@@ -186,6 +212,62 @@ class FlowGraphBuilder : public BaseFlowGraphBuilder {
Fragment* implicit_checks,
Fragment* implicit_redefinitions);
// Builds flow graph for noSuchMethod forwarder.
//
// If throw_no_such_method_error is set to true, an
// instance of NoSuchMethodError is thrown. Otherwise, the instance
// noSuchMethod is called.
//
// ParsedFunction should have the following information:
// - default_parameter_values()
// - is_forwarding_stub()
// - forwarding_stub_super_target()
//
// Scope should be populated with parameter variables including
// - needs_type_check()
// - is_explicit_covariant_parameter()
//
FlowGraph* BuildGraphOfNoSuchMethodForwarder(
const Function& function,
bool is_implicit_closure_function,
bool throw_no_such_method_error);
// If no type arguments are passed to a generic function, we need to fill the
// type arguments in with the default types stored on the TypeParameter nodes
// in Kernel.
//
// ParsedFunction should have the following information:
// - DefaultFunctionTypeArguments()
// - function_type_arguments()
Fragment BuildDefaultTypeHandling(const Function& function);
Fragment BuildEntryPointsIntrospection();
FunctionEntryInstr* BuildSharedUncheckedEntryPoint(
Fragment prologue_from_normal_entry,
Fragment skippable_checks,
Fragment redefinitions_if_skipped,
Fragment body);
FunctionEntryInstr* BuildSeparateUncheckedEntryPoint(
BlockEntryInstr* normal_entry,
Fragment normal_prologue,
Fragment extra_prologue,
Fragment shared_prologue,
Fragment body);
void RecordUncheckedEntryPoint(FunctionEntryInstr* extra_entry);
// Builds flow graph for implicit closure function (tear-off).
//
// ParsedFunction should have the following information:
// - default_parameter_values()
// - is_forwarding_stub()
// - forwarding_stub_super_target()
//
// Scope should be populated with parameter variables including
// - needs_type_check()
// - is_explicit_covariant_parameter()
//
FlowGraph* BuildGraphOfImplicitClosureFunction(const Function& function);
TranslationHelper translation_helper_;
Thread* thread_;
Zone* zone_;
+2 -1
View File
@@ -65,7 +65,8 @@ ParsedFunction::ParsedFunction(Thread* thread, const Function& function)
first_parameter_index_(),
num_stack_locals_(0),
have_seen_await_expr_(false),
kernel_scopes_(NULL) {
kernel_scopes_(NULL),
default_function_type_arguments_(TypeArguments::ZoneHandle(zone())) {
ASSERT(function.IsZoneHandle());
// Every function has a local variable for the current context.
LocalVariable* temp = new (zone())
+10
View File
@@ -198,6 +198,14 @@ class ParsedFunction : public ZoneAllocated {
return raw_parameters_->At(i);
}
void SetDefaultFunctionTypeArguments(const TypeArguments& value) {
default_function_type_arguments_ = value.raw();
}
const TypeArguments& DefaultFunctionTypeArguments() const {
return default_function_type_arguments_;
}
private:
Thread* thread_;
const Function& function_;
@@ -225,6 +233,8 @@ class ParsedFunction : public ZoneAllocated {
const Function* forwarding_stub_super_target_ = nullptr;
kernel::ScopeBuildingResult* kernel_scopes_;
TypeArguments& default_function_type_arguments_;
friend class Parser;
DISALLOW_COPY_AND_ASSIGN(ParsedFunction);
};