diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc index 2b5a7eaaa51..4c2100ab9bb 100644 --- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc +++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc @@ -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(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: diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h index eda7ac5265c..b12d4e383e6 100644 --- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h +++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h @@ -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; diff --git a/runtime/vm/compiler/frontend/kernel_to_il.cc b/runtime/vm/compiler/frontend/kernel_to_il.cc index bc47520ff13..9da71eff4a9 100644 --- a/runtime/vm/compiler/frontend/kernel_to_il.cc +++ b/runtime/vm/compiler/frontend/kernel_to_il.cc @@ -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(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(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(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(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(UncheckedEntryPointStyle::kNone)); + normal += BuildEntryPointsIntrospection(); + normal += normal_prologue; + normal += Goto(join_entry); + + Fragment extra(extra_entry); + extra += + IntConstant(static_cast(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 diff --git a/runtime/vm/compiler/frontend/kernel_to_il.h b/runtime/vm/compiler/frontend/kernel_to_il.h index ec76722fc23..26cb2846f77 100644 --- a/runtime/vm/compiler/frontend/kernel_to_il.h +++ b/runtime/vm/compiler/frontend/kernel_to_il.h @@ -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_; diff --git a/runtime/vm/parser.cc b/runtime/vm/parser.cc index a7022893c2f..b4b413541d7 100644 --- a/runtime/vm/parser.cc +++ b/runtime/vm/parser.cc @@ -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()) diff --git a/runtime/vm/parser.h b/runtime/vm/parser.h index 3c22b278ed3..bc7b0d2358d 100644 --- a/runtime/vm/parser.h +++ b/runtime/vm/parser.h @@ -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); };