From 20ca5bbb056dec9980ba2e1cdfddff9aa773cba0 Mon Sep 17 00:00:00 2001 From: Vyacheslav Egorov Date: Mon, 25 Jun 2018 07:27:04 +0000 Subject: [PATCH] [vm/jit] Introduce dynamic invocation forwarders that perform type checking This forwarders are used at dynamic call-sites and perform type checking for all non-generic-covariant arguments. This allows to skip the same type checks in the actual method body. This yield on average 10% improvement in performance across the body of benchmarks including dart2js compilation times. Bug: https://github.com/dart-lang/sdk/issues/33257 Change-Id: If3fc94a2e0a6f496ec0633f0b379d053a54a40ca Reviewed-on: https://dart-review.googlesource.com/61244 Commit-Queue: Vyacheslav Egorov Reviewed-by: Alexander Markov Reviewed-by: Martin Kustermann --- .../lib/src/elements/function_view.dart | 2 + .../lib/src/models/objects/function.dart | 3 +- .../observatory/lib/src/service/object.dart | 2 + .../frontend/kernel_binary_flowgraph.cc | 488 +++++++++++++++--- .../frontend/kernel_binary_flowgraph.h | 29 +- runtime/vm/compiler/frontend/kernel_to_il.cc | 25 +- runtime/vm/compiler/frontend/kernel_to_il.h | 7 + runtime/vm/isolate.h | 8 + runtime/vm/kernel_isolate.cc | 3 + runtime/vm/log.cc | 2 +- runtime/vm/object.cc | 153 +++++- runtime/vm/object.h | 22 + runtime/vm/raw_object.h | 3 + runtime/vm/resolver.cc | 22 +- runtime/vm/runtime_entry.cc | 10 + runtime/vm/symbols.h | 1 + 16 files changed, 681 insertions(+), 99 deletions(-) diff --git a/runtime/observatory/lib/src/elements/function_view.dart b/runtime/observatory/lib/src/elements/function_view.dart index 93cc57ccfc8..c3edda81f33 100644 --- a/runtime/observatory/lib/src/elements/function_view.dart +++ b/runtime/observatory/lib/src/elements/function_view.dart @@ -433,6 +433,8 @@ class FunctionViewElement extends HtmlElement implements Renderable { return 'tag'; case M.FunctionKind.signatureFunction: return 'signature function'; + case M.FunctionKind.dynamicInvocationForwarder: + return 'dynamic invocation forwarder'; } throw new Exception('Unknown Functionkind ($kind)'); } diff --git a/runtime/observatory/lib/src/models/objects/function.dart b/runtime/observatory/lib/src/models/objects/function.dart index 904038d3683..e619f5e7472 100644 --- a/runtime/observatory/lib/src/models/objects/function.dart +++ b/runtime/observatory/lib/src/models/objects/function.dart @@ -23,7 +23,8 @@ enum FunctionKind { native, stub, tag, - signatureFunction + signatureFunction, + dynamicInvocationForwarder } bool isSyntheticFunction(FunctionKind kind) { diff --git a/runtime/observatory/lib/src/service/object.dart b/runtime/observatory/lib/src/service/object.dart index ace655c0ed9..768262c3cfa 100644 --- a/runtime/observatory/lib/src/service/object.dart +++ b/runtime/observatory/lib/src/service/object.dart @@ -3112,6 +3112,8 @@ M.FunctionKind stringToFunctionKind(String value) { return M.FunctionKind.tag; case 'SignatureFunction': return M.FunctionKind.signatureFunction; + case 'DynamicInvocationForwarder': + return M.FunctionKind.dynamicInvocationForwarder; } Logger.root.severe('Unrecognized function kind: $value'); throw new FallThroughError(); diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc index c329ac8cd41..01c88c84d58 100644 --- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc +++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc @@ -38,6 +38,31 @@ static bool IsFieldInitializer(const Function& function, Zone* zone) { .StartsWith(Symbols::InitPrefix()); } +// Returns true if the given method can skip type checks for all arguments +// that are not covariant or generic covariant in its implementation. +static bool MethodCanSkipTypeChecksForNonCovariantArguments( + const Function& method, + const ProcedureAttributesMetadata& attrs) { + // Dart 2 type system at non-dynamic call sites statically guarantees that + // argument values match declarated parameter types for all non-covariant + // and non-generic-covariant parameters. The same applies to type parameters + // bounds for type parameters of generic functions. + // In JIT mode we dynamically generate trampolines (dynamic invocation + // forwarders) that perform type checks when arriving to a method from a + // dynamic call-site. + // In AOT mode we don't dynamically generate such trampolines but + // instead rely on a static analysis to discover which methods can + // be invoked dynamically. + if (method.name() == Symbols::Call().raw()) { + // Currently we consider all call methods to be invoked dynamically and + // don't mangle their names. + // TODO(vegorov) remove this once we also introduce special type checking + // entry point for closures. + return false; + } + return !FLAG_precompiled_mode || !attrs.has_dynamic_invocations; +} + void FunctionNodeHelper::ReadUntilExcluding(Field field) { if (field <= next_read_) return; @@ -1690,19 +1715,22 @@ ScopeBuildingResult* StreamingScopeBuilder::BuildScopes() { if (function.IsNonImplicitClosureFunction()) { type_check_mode = kTypeCheckAllParameters; } else if (function.IsImplicitClosureFunction()) { - if (!attrs.has_dynamic_invocations) { + if (MethodCanSkipTypeChecksForNonCovariantArguments( + Function::Handle(Z, function.parent_function()), attrs)) { // This is a tear-off of an instance method that can not be reached // from any dynamic invocation. The method would not check any // parameters except covariant ones and those annotated with // generic-covariant-impl. Which means that we have to check - // the rest in the tear-off itself.. - type_check_mode = kTypeCheckForTearOffOfNonDynamicallyInvokedMethod; + // the rest in the tear-off itself. + type_check_mode = + kTypeCheckEverythingNotCheckedInNonDynamicallyInvokedMethod; } } else { if (function.is_static()) { // In static functions we don't check anything. type_check_mode = kTypeCheckForStaticFunction; - } else if (!attrs.has_dynamic_invocations) { + } else if (MethodCanSkipTypeChecksForNonCovariantArguments(function, + attrs)) { // If the current function is never a target of a dynamic invocation // and this parameter is not marked with generic-covariant-impl // (which means that among all super-interfaces no type parameters @@ -1769,7 +1797,8 @@ ScopeBuildingResult* StreamingScopeBuilder::BuildScopes() { AbstractType::ZoneHandle(Z, function.ParameterTypeAt(pos))); scope_->InsertParameterAt(pos++, result_->setter_value); - if (is_method && !attrs.has_dynamic_invocations) { + if (is_method && + MethodCanSkipTypeChecksForNonCovariantArguments(function, attrs)) { FieldHelper field_helper(builder_); field_helper.ReadUntilIncluding(FieldHelper::kFlags); @@ -1783,6 +1812,43 @@ ScopeBuildingResult* StreamingScopeBuilder::BuildScopes() { } break; } + case RawFunction::kDynamicInvocationForwarder: { + if (builder_->PeekTag() == kField) { + ASSERT(Field::IsSetterName(String::Handle(function.name()))); + + // Create [this] variable. + const Class& klass = Class::Handle(Z, function.Owner()); + result_->this_variable = + MakeVariable(TokenPosition::kNoSource, TokenPosition::kNoSource, + Symbols::This(), H.GetCanonicalType(klass)); + scope_->InsertParameterAt(0, result_->this_variable); + + // Create setter value variable. + result_->setter_value = MakeVariable( + TokenPosition::kNoSource, TokenPosition::kNoSource, + Symbols::Value(), + AbstractType::ZoneHandle(Z, function.ParameterTypeAt(1))); + scope_->InsertParameterAt(1, result_->setter_value); + } else { + builder_->ReadUntilFunctionNode(); + function_node_helper.ReadUntilExcluding( + FunctionNodeHelper::kPositionalParameters); + + // Create [this] variable. + intptr_t pos = 0; + Class& klass = Class::Handle(Z, function.Owner()); + result_->this_variable = + MakeVariable(TokenPosition::kNoSource, TokenPosition::kNoSource, + Symbols::This(), H.GetCanonicalType(klass)); + scope_->InsertParameterAt(pos++, result_->this_variable); + + // Create all positional and named parameters. + AddPositionalAndNamedParameters( + pos, kTypeCheckEverythingNotCheckedInNonDynamicallyInvokedMethod, + attrs); + } + break; + } case RawFunction::kMethodExtractor: { // Add a receiver parameter. Though it is captured, we emit code to // explicitly copy it to a fixed offset in a freshly-allocated context @@ -2863,7 +2929,7 @@ void StreamingScopeBuilder::AddVariableDeclarationParameter( variable->set_is_forced_stack(); } - const bool needs_covariant_checke_in_method = + const bool needs_covariant_check_in_method = helper.IsCovariant() || (helper.IsGenericCovariantImpl() && attrs.has_non_this_uses); @@ -2871,8 +2937,8 @@ void StreamingScopeBuilder::AddVariableDeclarationParameter( case kTypeCheckAllParameters: variable->set_type_check_mode(LocalVariable::kDoTypeCheck); break; - case kTypeCheckForTearOffOfNonDynamicallyInvokedMethod: - if (needs_covariant_checke_in_method) { + case kTypeCheckEverythingNotCheckedInNonDynamicallyInvokedMethod: + if (needs_covariant_check_in_method) { // Don't type check covariant parameters - they will be checked by // a function we forward to. Their types however are not known. variable->set_type_check_mode(LocalVariable::kSkipTypeCheck); @@ -2881,7 +2947,7 @@ void StreamingScopeBuilder::AddVariableDeclarationParameter( } break; case kTypeCheckForNonDynamicallyInvokedMethod: - if (needs_covariant_checke_in_method) { + if (needs_covariant_check_in_method) { variable->set_type_check_mode(LocalVariable::kDoTypeCheck); } else { // Types of non-covariant parameters are guaranteed to match by @@ -5266,8 +5332,13 @@ FlowGraph* StreamingFlowGraphBuilder::BuildGraphOfFieldAccessor( const Function& function = parsed_function()->function(); - bool is_setter = function.IsImplicitSetterFunction(); - bool is_method = !function.IsStaticFunction(); + // Instead of building a dynamic invocation forwarder that checks argument + // type and then invokes original setter we simply generate the type check + // and inlined field store. Scope builder takes care of setting correct + // type check mode in this case. + const bool is_setter = function.IsDynamicInvocationForwader() || + function.IsImplicitSetterFunction(); + const bool is_method = !function.IsStaticFunction(); Field& field = Field::ZoneHandle( Z, H.LookupFieldByKernelField(field_helper.canonical_name_)); @@ -5277,6 +5348,10 @@ FlowGraph* StreamingFlowGraphBuilder::BuildGraphOfFieldAccessor( Fragment body(normal_entry); if (is_setter) { + // We only expect to generate a dynamic invocation forwarder if + // the value needs type check. + ASSERT(!function.IsDynamicInvocationForwader() || + setter_value->needs_type_check()); if (is_method) { body += LoadLocal(scopes()->this_variable); } @@ -5695,18 +5770,17 @@ FlowGraph* StreamingFlowGraphBuilder::BuildGraphOfImplicitClosureFunction( // Tearoffs of static methods needs to perform arguments checks since // static methods they forward to don't do it themselves. AlternativeReadingScope _(&reader_); - body += BuildArgumentTypeChecks(); + body += BuildArgumentTypeChecks(kCheckAllTypeParameterBounds); } else { // Check if parent function was annotated with no-dynamic-invocations. const ProcedureAttributesMetadata attrs = procedure_attributes_metadata_helper_.GetProcedureAttributes( parent.kernel_offset()); - if (!attrs.has_dynamic_invocations) { + if (MethodCanSkipTypeChecksForNonCovariantArguments(parent, attrs)) { // If it was then we might need to build some checks in the // tear-off. AlternativeReadingScope _(&reader_); - body += - BuildArgumentTypeChecks(kTypeChecksForNoDynamicInvocationsTearOff); + body += BuildArgumentTypeChecks(kCheckNonCovariantTypeParameterBounds); } } } @@ -5813,7 +5887,7 @@ FlowGraph* StreamingFlowGraphBuilder::BuildGraphOfNoSuchMethodForwarder( if (function.NeedsArgumentTypeChecks(I)) { AlternativeReadingScope _(&reader_); - body += BuildArgumentTypeChecks(); + body += BuildArgumentTypeChecks(kCheckAllTypeParameterBounds); } function_node_helper.ReadUntilExcluding( @@ -6062,8 +6136,84 @@ FlowGraph* StreamingFlowGraphBuilder::BuildGraphOfNoSuchMethodForwarder( B->last_used_block_id_, prologue_info); } +bool StreamingFlowGraphBuilder::NeedsDynamicInvocationForwarder( + const Function& function) { + // Setup a [ActiveClassScope] and a [ActiveMemberScope] which will be used + // e.g. for type translation. + const Class& klass = Class::Handle(zone_, function.Owner()); + Function& outermost_function = Function::Handle(Z); + DiscoverEnclosingElements(Z, function, &outermost_function); + + ActiveClassScope active_class_scope(active_class(), &klass); + ActiveMemberScope active_member(active_class(), &outermost_function); + ActiveTypeParametersScope active_type_params(active_class(), function, Z); + + SetOffset(function.kernel_offset()); + + // Handle setters. + if (PeekTag() == kField) { + ASSERT(function.IsImplicitSetterFunction()); + FieldHelper field_helper(this); + field_helper.ReadUntilIncluding(FieldHelper::kFlags); + return !(field_helper.IsCovariant() || + field_helper.IsGenericCovariantImpl()); + } + + ReadUntilFunctionNode(); + + FunctionNodeHelper function_node_helper(this); + function_node_helper.ReadUntilExcluding(FunctionNodeHelper::kTypeParameters); + intptr_t num_type_params = ReadListLength(); + + for (intptr_t i = 0; i < num_type_params; ++i) { + TypeParameterHelper helper(this); + helper.ReadUntilExcludingAndSetJustRead(TypeParameterHelper::kBound); + AbstractType& bound = T.BuildType(); // read bound + helper.Finish(); + + if (!bound.IsTopType() && !helper.IsGenericCovariantImpl()) { + return true; + } + } + function_node_helper.SetJustRead(FunctionNodeHelper::kTypeParameters); + function_node_helper.ReadUntilExcluding( + FunctionNodeHelper::kPositionalParameters); + + // Positional. + const intptr_t num_positional_params = ReadListLength(); + for (intptr_t i = 0; i < num_positional_params; ++i) { + VariableDeclarationHelper helper(this); + helper.ReadUntilExcluding(VariableDeclarationHelper::kType); + AbstractType& type = T.BuildType(); // read type. + helper.SetJustRead(VariableDeclarationHelper::kType); + helper.ReadUntilExcluding(VariableDeclarationHelper::kEnd); + + if (!type.IsTopType() && !helper.IsGenericCovariantImpl() && + !helper.IsCovariant()) { + return true; + } + } + + // Named. + const intptr_t num_named_params = ReadListLength(); + for (intptr_t i = 0; i < num_named_params; ++i) { + VariableDeclarationHelper helper(this); + helper.ReadUntilExcluding(VariableDeclarationHelper::kType); + AbstractType& type = T.BuildType(); // read type. + helper.SetJustRead(VariableDeclarationHelper::kType); + helper.ReadUntilExcluding(VariableDeclarationHelper::kEnd); + + if (!type.IsTopType() && !helper.IsGenericCovariantImpl() && + !helper.IsCovariant()) { + return true; + } + } + + return false; +} + Fragment StreamingFlowGraphBuilder::BuildArgumentTypeChecks( - TypeChecksToBuild mode /*= kDefaultTypeChecks*/) { + TypeChecksToBuild mode) { FunctionNodeHelper function_node_helper(this); function_node_helper.SetNext(FunctionNodeHelper::kTypeParameters); const Function& dart_function = parsed_function()->function(); @@ -6081,45 +6231,65 @@ Fragment StreamingFlowGraphBuilder::BuildArgumentTypeChecks( ASSERT(!forwarding_target->IsNull()); } - // Type parameters - if (mode == kDefaultTypeChecks) { - intptr_t num_type_params = ReadListLength(); - TypeArguments& forwarding_params = TypeArguments::Handle(Z); - if (forwarding_target != NULL) { - forwarding_params = forwarding_target->type_parameters(); - ASSERT(forwarding_params.Length() == num_type_params); - } - TypeParameter& forwarding_param = TypeParameter::Handle(Z); - for (intptr_t i = 0; i < num_type_params; ++i) { - TypeParameterHelper helper(this); - helper.ReadUntilExcludingAndSetJustRead(TypeParameterHelper::kBound); - String& name = H.DartSymbolObfuscate(helper.name_index_); - AbstractType& bound = T.BuildType(); // read bound - helper.Finish(); - - if (forwarding_target != NULL) { - forwarding_param ^= forwarding_params.TypeAt(i); - bound = forwarding_param.bound(); - } - - if (I->strong() && !bound.IsObjectType() && !bound.IsDynamicType() && - (I->reify_generic_functions() || dart_function.IsFactory())) { - TypeParameter& param = TypeParameter::Handle(Z); - if (dart_function.IsFactory()) { - param ^= TypeArguments::Handle( - Class::Handle(dart_function.Owner()).type_parameters()) - .TypeAt(i); - } else { - param ^= - TypeArguments::Handle(dart_function.type_parameters()).TypeAt(i); - } - ASSERT(param.IsFinalized()); - body += CheckTypeArgumentBound(param, bound, name); - } - } - function_node_helper.SetJustRead(FunctionNodeHelper::kTypeParameters); + intptr_t num_type_params = ReadListLength(); + TypeArguments& forwarding_params = TypeArguments::Handle(Z); + if (forwarding_target != NULL) { + forwarding_params = forwarding_target->type_parameters(); + ASSERT(forwarding_params.Length() == num_type_params); } + const bool has_reified_type_arguments = + I->strong() && I->reify_generic_functions(); + + TypeParameter& forwarding_param = TypeParameter::Handle(Z); + for (intptr_t i = 0; i < num_type_params; ++i) { + TypeParameterHelper helper(this); + helper.ReadUntilExcludingAndSetJustRead(TypeParameterHelper::kBound); + String& name = H.DartSymbolObfuscate(helper.name_index_); + AbstractType& bound = T.BuildType(); // read bound + helper.Finish(); + + if (!has_reified_type_arguments) { + continue; + } + + if (forwarding_target != NULL) { + forwarding_param ^= forwarding_params.TypeAt(i); + bound = forwarding_param.bound(); + } + + if (bound.IsTopType()) { + continue; + } + + switch (mode) { + case kCheckAllTypeParameterBounds: + break; + case kCheckCovariantTypeParameterBounds: + if (!helper.IsGenericCovariantImpl()) { + continue; + } + break; + case kCheckNonCovariantTypeParameterBounds: + if (helper.IsGenericCovariantImpl()) { + continue; + } + break; + } + + TypeParameter& param = TypeParameter::Handle(Z); + if (dart_function.IsFactory()) { + param ^= TypeArguments::Handle( + Class::Handle(dart_function.Owner()).type_parameters()) + .TypeAt(i); + } else { + param ^= TypeArguments::Handle(dart_function.type_parameters()).TypeAt(i); + } + ASSERT(param.IsFinalized()); + body += CheckTypeArgumentBound(param, bound, name); + } + + function_node_helper.SetJustRead(FunctionNodeHelper::kTypeParameters); function_node_helper.ReadUntilExcluding( FunctionNodeHelper::kPositionalParameters); @@ -6173,6 +6343,163 @@ Fragment StreamingFlowGraphBuilder::BuildArgumentTypeChecks( return body; } +Fragment StreamingFlowGraphBuilder::PushAllArguments(PushedArguments* pushed) { + ASSERT(I->strong()); + + FunctionNodeHelper function_node_helper(this); + function_node_helper.SetNext(FunctionNodeHelper::kTypeParameters); + + Fragment body; + + const intptr_t num_type_params = ReadListLength(); + if (num_type_params > 0) { + // Skip type arguments. + for (intptr_t i = 0; i < num_type_params; ++i) { + TypeParameterHelper helper(this); + helper.Finish(); + } + + if (I->reify_generic_functions()) { + body += LoadLocal(parsed_function()->function_type_arguments()); + body += PushArgument(); + pushed->type_args_len = num_type_params; + } + } + function_node_helper.SetJustRead(FunctionNodeHelper::kTypeParameters); + function_node_helper.ReadUntilExcluding( + FunctionNodeHelper::kPositionalParameters); + + // Push receiver. + body += LoadLocal(scopes()->this_variable); + body += PushArgument(); + + // Push positional parameters. + const intptr_t num_positional_params = ReadListLength(); + for (intptr_t i = 0; i < num_positional_params; ++i) { + // ith variable offset. + const intptr_t offset = ReaderOffset(); + SkipVariableDeclaration(); + + LocalVariable* param = LookupVariable(offset + data_program_offset_); + body += LoadLocal(param); + body += PushArgument(); + } + + // Push named parameters. + const intptr_t num_named_params = ReadListLength(); + pushed->argument_names = Array::New(num_named_params, Heap::kOld); + for (intptr_t i = 0; i < num_named_params; ++i) { + // ith variable offset. + const intptr_t offset = ReaderOffset(); + SkipVariableDeclaration(); + + LocalVariable* param = LookupVariable(offset + data_program_offset_); + pushed->argument_names.SetAt(i, param->name()); + body += LoadLocal(param); + body += PushArgument(); + } + + pushed->argument_count = num_positional_params + num_named_params + 1; + + return body; +} + +FlowGraph* StreamingFlowGraphBuilder::BuildGraphOfDynamicInvocationForwarder() { + // The prologue builder needs the default parameter values. + SetupDefaultParameterValues(); + + const Function& dart_function = parsed_function()->function(); + TargetEntryInstr* normal_entry = flow_graph_builder_->BuildTargetEntry(); + PrologueInfo prologue_info(-1, -1); + BlockEntryInstr* instruction_cursor = + flow_graph_builder_->BuildPrologue(normal_entry, &prologue_info); + + flow_graph_builder_->graph_entry_ = new (Z) GraphEntryInstr( + *parsed_function(), normal_entry, flow_graph_builder_->osr_id_); + + Fragment body; + if (!dart_function.is_native()) { + body += flow_graph_builder_->CheckStackOverflowInPrologue( + dart_function.token_pos()); + } + + ASSERT(parsed_function()->node_sequence()->scope()->num_context_variables() == + 0); + + FunctionNodeHelper function_node_helper(this); + function_node_helper.ReadUntilExcluding(FunctionNodeHelper::kTypeParameters); + const intptr_t type_parameters_offset = ReaderOffset(); + function_node_helper.ReadUntilExcluding( + FunctionNodeHelper::kPositionalParameters); + intptr_t first_parameter_offset = -1; + { + AlternativeReadingScope alt(&reader_); + intptr_t list_length = ReadListLength(); // read number of positionals. + if (list_length > 0) { + first_parameter_offset = ReaderOffset() + data_program_offset_; + } + } + // Current position: About to read list of positionals. + + // Should never build a dynamic invocation forwarder for equality + // operator. + ASSERT(dart_function.name() != Symbols::EqualOperator().raw()); + + // Even if the caller did not pass argument vector we would still + // call the target with instantiate-to-bounds type arguments. + body += BuildDefaultTypeHandling(dart_function, type_parameters_offset); + + String& name = String::Handle(Z, dart_function.name()); + name = Function::DemangleDynamicInvocationForwarderName(name); + const Class& owner = Class::Handle(Z, dart_function.Owner()); + const Function& target = + Function::ZoneHandle(Z, owner.LookupDynamicFunction(name)); + ASSERT(!target.IsNull()); + + // Build argument type checks that complement those that are emitted in the + // target. + { + AlternativeReadingScope alt(&reader_); + SetOffset(type_parameters_offset); + body += BuildArgumentTypeChecks(kCheckNonCovariantTypeParameterBounds); + } + + // Push all arguments and invoke the original method. + PushedArguments pushed = {0, 0, Array::ZoneHandle(Z)}; + { + AlternativeReadingScope alt(&reader_); + SetOffset(type_parameters_offset); + body += PushAllArguments(&pushed); + } + body += StaticCall(TokenPosition::kNoSource, target, pushed.argument_count, + pushed.argument_names, ICData::kNoRebind, nullptr, + pushed.type_args_len); + + // Some IL optimization passes assume that result of operator []= invocation + // is never used, so we drop it and replace with an explicit null constant. + if (name.raw() == Symbols::AssignIndexToken().raw()) { + body += Drop(); + body += NullConstant(); + } + + body += Return(TokenPosition::kNoSource); + + instruction_cursor->LinkTo(body.entry); + + GraphEntryInstr* graph_entry = flow_graph_builder_->graph_entry_; + // When compiling for OSR, use a depth first search to find the OSR + // entry and make graph entry jump to it instead of normal entry. + // Catch entries are always considered reachable, even if they + // become unreachable after OSR. + if (flow_graph_builder_->osr_id_ != Compiler::kNoOSRDeoptId) { + graph_entry->RelinkToOsrEntry(Z, + flow_graph_builder_->last_used_block_id_ + 1); + } + return new (Z) + FlowGraph(*parsed_function(), graph_entry, + flow_graph_builder_->last_used_block_id_, prologue_info); +} + FlowGraph* StreamingFlowGraphBuilder::BuildGraphOfFunction(bool constructor) { // The prologue builder needs the default parameter values. SetupDefaultParameterValues(); @@ -6288,17 +6615,25 @@ FlowGraph* StreamingFlowGraphBuilder::BuildGraphOfFunction(bool constructor) { body = Fragment(body.entry, non_null_entry); } - // If we run in checked mode or strong mode, we have to check the type of the - // passed arguments. + // If we run in checked mode or strong mode, we have to check the type of + // the passed arguments. if (dart_function.NeedsArgumentTypeChecks(I)) { + // Check if parent function was annotated with no-dynamic-invocations. + const ProcedureAttributesMetadata attrs = + procedure_attributes_metadata_helper_.GetProcedureAttributes( + dart_function.kernel_offset()); + AlternativeReadingScope _(&reader_); SetOffset(type_parameters_offset); - body += BuildArgumentTypeChecks(); + body += BuildArgumentTypeChecks( + MethodCanSkipTypeChecksForNonCovariantArguments(dart_function, attrs) + ? kCheckCovariantTypeParameterBounds + : kCheckAllTypeParameterBounds); } function_node_helper.ReadUntilExcluding(FunctionNodeHelper::kBody); - bool has_body = ReadTag() == kSomething; // read first part of body. + const bool has_body = ReadTag() == kSomething; // read first part of body. if (dart_function.is_native()) { body += flow_graph_builder_->NativeFunctionBody(first_parameter_offset, @@ -6511,9 +6846,10 @@ FlowGraph* StreamingFlowGraphBuilder::BuildGraphOfFunction(bool constructor) { flow_graph_builder_->last_used_block_id_, prologue_info); } -FlowGraph* StreamingFlowGraphBuilder::BuildGraph(intptr_t kernel_offset) { +FlowGraph* StreamingFlowGraphBuilder::BuildGraph() { ASSERT(Error::Handle(Z, H.thread()->sticky_error()).IsNull()); const Function& function = parsed_function()->function(); + const intptr_t kernel_offset = function.kernel_offset(); // Setup a [ActiveClassScope] and a [ActiveMemberScope] which will be used // e.g. for type translation. @@ -6585,6 +6921,13 @@ FlowGraph* StreamingFlowGraphBuilder::BuildGraph(intptr_t kernel_offset) { ? BuildGraphOfFieldInitializer() : BuildGraphOfFieldAccessor(scopes()->setter_value); } + case RawFunction::kDynamicInvocationForwarder: + if (PeekTag() == kField) { + return BuildGraphOfFieldAccessor(scopes()->setter_value); + } else { + ReadUntilFunctionNode(parsed_function()); + return BuildGraphOfDynamicInvocationForwarder(); + } case RawFunction::kMethodExtractor: return flow_graph_builder_->BuildGraphOfMethodExtractor(function); case RawFunction::kNoSuchMethodDispatcher: @@ -8262,8 +8605,15 @@ Fragment StreamingFlowGraphBuilder::BuildPropertySet(TokenPosition* p) { } else { const intptr_t kTypeArgsLen = 0; const intptr_t kNumArgsChecked = 1; + + const String* mangled_name = &setter_name; + if (!FLAG_precompiled_mode && I->strong() && H.IsRoot(itarget_name)) { + mangled_name = &String::ZoneHandle( + Z, Function::CreateDynamicInvocationForwarderName(setter_name)); + } + instructions += - InstanceCall(position, setter_name, Token::kSET, kTypeArgsLen, 2, + InstanceCall(position, *mangled_name, Token::kSET, kTypeArgsLen, 2, Array::null_array(), kNumArgsChecked, *interface_target, /* result_type = */ NULL); } @@ -8820,10 +9170,22 @@ Fragment StreamingFlowGraphBuilder::BuildMethodInvocation(TokenPosition* p) { argument_names, ICData::kNoRebind, &result_type, type_args_len); } else { + const String* mangled_name = &name; + // Do not mangle == or call: + // * operator == takes an Object so its either not checked or checked + // at the entry because the parameter is marked covariant, neither of + // those cases require a dynamic invocation forwarder; + // * we assume that all closures are entered in a checked way. + if (!FLAG_precompiled_mode && I->strong() && + (name.raw() != Symbols::EqualOperator().raw()) && + (name.raw() != Symbols::Call().raw()) && H.IsRoot(itarget_name)) { + mangled_name = &String::ZoneHandle( + Z, Function::CreateDynamicInvocationForwarderName(name)); + } instructions += - InstanceCall(position, name, token_kind, type_args_len, argument_count, - argument_names, checked_argument_count, *interface_target, - &result_type); + InstanceCall(position, *mangled_name, token_kind, type_args_len, + argument_count, argument_names, checked_argument_count, + *interface_target, &result_type); } // Drop temporaries preserving result on the top of the stack. diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h index a28476b2f0c..5b1c7dd3011 100644 --- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h +++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h @@ -115,6 +115,10 @@ class TypeParameterHelper { void Finish() { ReadUntilExcluding(kEnd); } + bool IsGenericCovariantImpl() { + return (flags_ & kIsGenericCovariantImpl) != 0; + } + TokenPosition position_; uint8_t flags_; StringIndex name_index_; @@ -814,8 +818,8 @@ class StreamingScopeBuilder { // Only parameters *not* marked as covariant or generic-covariant-impl will // be checked. The rest would be checked in the method itself. - // Inverse of kTypeCheckOnlyGenericCovariantImplParameters. - kTypeCheckForTearOffOfNonDynamicallyInvokedMethod, + // Inverse of kTypeCheckForNonDynamicallyInvokedMethod. + kTypeCheckEverythingNotCheckedInNonDynamicallyInvokedMethod, // No parameters will be checked. kTypeCheckForStaticFunction, @@ -1273,7 +1277,9 @@ class StreamingFlowGraphBuilder : public KernelReaderHelper { virtual ~StreamingFlowGraphBuilder() {} - FlowGraph* BuildGraph(intptr_t kernel_offset); + bool NeedsDynamicInvocationForwarder(const Function& function); + + FlowGraph* BuildGraph(); void ReportUnexpectedTag(const char* variant, Tag tag) override; @@ -1296,8 +1302,6 @@ class StreamingFlowGraphBuilder : public KernelReaderHelper { // it crosses a procedure node for a concrete forwarding stub. void ReadUntilFunctionNode(ParsedFunction* set_forwarding_stub = NULL); - enum DispatchCategory { Interface, ViaThis, Closure, DynamicDispatch }; - private: void LoadAndSetupTypeParameters(ActiveClass* active_class, const Object& set_on, @@ -1320,6 +1324,7 @@ class StreamingFlowGraphBuilder : public KernelReaderHelper { 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, @@ -1418,15 +1423,23 @@ class StreamingFlowGraphBuilder : public KernelReaderHelper { const InferredTypeMetadata* result_type = NULL); enum TypeChecksToBuild { - kDefaultTypeChecks, - kTypeChecksForNoDynamicInvocationsTearOff + kCheckAllTypeParameterBounds, + kCheckNonCovariantTypeParameterBounds, + kCheckCovariantTypeParameterBounds, }; // Does not move the cursor. Fragment BuildDefaultTypeHandling(const Function& function, intptr_t type_parameters_offset); - Fragment BuildArgumentTypeChecks(TypeChecksToBuild mode = kDefaultTypeChecks); + struct PushedArguments { + intptr_t type_args_len; + intptr_t argument_count; + Array& argument_names; + }; + Fragment PushAllArguments(PushedArguments* pushed); + + Fragment BuildArgumentTypeChecks(TypeChecksToBuild mode); Fragment ThrowException(TokenPosition position); Fragment BooleanNegate(); diff --git a/runtime/vm/compiler/frontend/kernel_to_il.cc b/runtime/vm/compiler/frontend/kernel_to_il.cc index f01934215bf..1bb584ee341 100644 --- a/runtime/vm/compiler/frontend/kernel_to_il.cc +++ b/runtime/vm/compiler/frontend/kernel_to_il.cc @@ -2061,6 +2061,28 @@ void FlowGraphBuilder::InlineBailout(const char* reason) { } } +bool FlowGraphBuilder::NeedsDynamicInvocationForwarder( + const Function& function) { + ASSERT(Isolate::Current()->strong()); + + Thread* thread = Thread::Current(); + Zone* zone_ = thread->zone(); + + TranslationHelper helper(thread); + Script& script = Script::Handle(Z, function.script()); + helper.InitFromScript(script); + + const Class& owner_class = Class::Handle(Z, function.Owner()); + ActiveClass active_class; + ActiveClassScope active_class_scope(&active_class, &owner_class); + + StreamingFlowGraphBuilder streaming_flow_graph_builder( + &helper, script, Z, ExternalTypedData::Handle(Z, function.KernelData()), + function.KernelDataProgramOffset(), &active_class); + + return streaming_flow_graph_builder.NeedsDynamicInvocationForwarder(function); +} + FlowGraph* FlowGraphBuilder::BuildGraph() { const Function& function = parsed_function_->function(); @@ -2080,8 +2102,7 @@ FlowGraph* FlowGraphBuilder::BuildGraph() { this, ExternalTypedData::Handle(Z, function.KernelData()), function.KernelDataProgramOffset()); streaming_flow_graph_builder_ = &streaming_flow_graph_builder; - FlowGraph* result = - streaming_flow_graph_builder_->BuildGraph(function.kernel_offset()); + FlowGraph* result = streaming_flow_graph_builder_->BuildGraph(); streaming_flow_graph_builder_ = NULL; return result; } diff --git a/runtime/vm/compiler/frontend/kernel_to_il.h b/runtime/vm/compiler/frontend/kernel_to_il.h index e0671a2f24b..9bb30e5e361 100644 --- a/runtime/vm/compiler/frontend/kernel_to_il.h +++ b/runtime/vm/compiler/frontend/kernel_to_il.h @@ -217,6 +217,7 @@ class ActiveClass { function_kind == RawFunction::kGetterFunction || function_kind == RawFunction::kSetterFunction || function_kind == RawFunction::kMethodExtractor || + function_kind == RawFunction::kDynamicInvocationForwarder || member->IsFactory(); } @@ -705,6 +706,12 @@ class FlowGraphBuilder : public BaseFlowGraphBuilder { FlowGraph* BuildGraph(); + // Returns true if the given function needs dynamic invocation forwarder: + // that is if any of the arguments require checking on the dynamic + // call-site: if function has no parameters or has only covariant parameters + // as such function already checks all of its parameters. + static bool NeedsDynamicInvocationForwarder(const Function& function); + private: BlockEntryInstr* BuildPrologue(TargetEntryInstr* normal_entry, PrologueInfo* prologue_info); diff --git a/runtime/vm/isolate.h b/runtime/vm/isolate.h index 7097f5a8812..974c447b6bb 100644 --- a/runtime/vm/isolate.h +++ b/runtime/vm/isolate.h @@ -687,6 +687,13 @@ class Isolate : public BaseIsolate { isolate_flags_ = IsServiceIsolateBit::update(value, isolate_flags_); } + bool is_kernel_isolate() const { + return IsKernelIsolateBit::decode(isolate_flags_); + } + void set_is_kernel_isolate(bool value) { + isolate_flags_ = IsKernelIsolateBit::update(value, isolate_flags_); + } + bool should_load_vmservice() const { return ShouldLoadVmServiceBit::decode(isolate_flags_); } @@ -844,6 +851,7 @@ class Isolate : public BaseIsolate { V(ErrorsFatal) \ V(IsRunnable) \ V(IsServiceIsolate) \ + V(IsKernelIsolate) \ V(CompilationAllowed) \ V(AllClassesFinalized) \ V(RemappingCids) \ diff --git a/runtime/vm/kernel_isolate.cc b/runtime/vm/kernel_isolate.cc index b5efb446f70..4f9a2af81eb 100644 --- a/runtime/vm/kernel_isolate.cc +++ b/runtime/vm/kernel_isolate.cc @@ -265,6 +265,9 @@ bool KernelIsolate::Exists() { void KernelIsolate::SetKernelIsolate(Isolate* isolate) { MonitorLocker ml(monitor_); + if (isolate != nullptr) { + isolate->set_is_kernel_isolate(true); + } isolate_ = isolate; } diff --git a/runtime/vm/log.cc b/runtime/vm/log.cc index 06a4263ba44..83b99b20f07 100644 --- a/runtime/vm/log.cc +++ b/runtime/vm/log.cc @@ -115,7 +115,7 @@ intptr_t Log::cursor() const { bool Log::ShouldLogForIsolate(const Isolate* isolate) { if (FLAG_isolate_log_filter == NULL) { - if (isolate->is_service_isolate()) { + if (isolate->is_service_isolate() || isolate->is_kernel_isolate()) { // By default, do not log for the service isolate. return false; } diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc index 1aa4b5263a8..b5dc110e74c 100644 --- a/runtime/vm/object.cc +++ b/runtime/vm/object.cc @@ -2714,27 +2714,54 @@ void Class::CalculateFieldOffsets() const { set_next_field_offset(offset); } +struct InvocationDispatcherCacheLayout { + enum { kNameIndex = 0, kArgsDescIndex, kFunctionIndex, kEntrySize }; +}; + +void Class::AddInvocationDispatcher(const String& target_name, + const Array& args_desc, + const Function& dispatcher) const { + // Search for a free entry. + Array& cache = Array::Handle(invocation_dispatcher_cache()); + intptr_t i = 0; + while (i < cache.Length() && cache.At(i) != Object::null()) { + i += InvocationDispatcherCacheLayout::kEntrySize; + } + + if (i == cache.Length()) { + // Allocate new larger cache. + intptr_t new_len = + (cache.Length() == 0) + ? static_cast(InvocationDispatcherCacheLayout::kEntrySize) + : cache.Length() * 2; + cache ^= Array::Grow(cache, new_len); + set_invocation_dispatcher_cache(cache); + } + cache.SetAt(i + InvocationDispatcherCacheLayout::kNameIndex, target_name); + cache.SetAt(i + InvocationDispatcherCacheLayout::kArgsDescIndex, args_desc); + cache.SetAt(i + InvocationDispatcherCacheLayout::kFunctionIndex, dispatcher); +} + RawFunction* Class::GetInvocationDispatcher(const String& target_name, const Array& args_desc, RawFunction::Kind kind, bool create_if_absent) const { - enum { kNameIndex = 0, kArgsDescIndex, kFunctionIndex, kEntrySize }; - ASSERT(kind == RawFunction::kNoSuchMethodDispatcher || - kind == RawFunction::kInvokeFieldDispatcher); + kind == RawFunction::kInvokeFieldDispatcher || + kind == RawFunction::kDynamicInvocationForwarder); Function& dispatcher = Function::Handle(); Array& cache = Array::Handle(invocation_dispatcher_cache()); ASSERT(!cache.IsNull()); String& name = String::Handle(); Array& desc = Array::Handle(); intptr_t i = 0; - for (; i < cache.Length(); i += kEntrySize) { - name ^= cache.At(i + kNameIndex); + for (; i < cache.Length(); i += InvocationDispatcherCacheLayout::kEntrySize) { + name ^= cache.At(i + InvocationDispatcherCacheLayout::kNameIndex); if (name.IsNull()) break; // Reached last entry. if (!name.Equals(target_name)) continue; - desc ^= cache.At(i + kArgsDescIndex); + desc ^= cache.At(i + InvocationDispatcherCacheLayout::kArgsDescIndex); if (desc.raw() != args_desc.raw()) continue; - dispatcher ^= cache.At(i + kFunctionIndex); + dispatcher ^= cache.At(i + InvocationDispatcherCacheLayout::kFunctionIndex); if (dispatcher.kind() == kind) { // Found match. ASSERT(dispatcher.IsFunction()); @@ -2743,18 +2770,8 @@ RawFunction* Class::GetInvocationDispatcher(const String& target_name, } if (dispatcher.IsNull() && create_if_absent) { - if (i == cache.Length()) { - // Allocate new larger cache. - intptr_t new_len = (cache.Length() == 0) - ? static_cast(kEntrySize) - : cache.Length() * 2; - cache ^= Array::Grow(cache, new_len); - set_invocation_dispatcher_cache(cache); - } dispatcher ^= CreateInvocationDispatcher(target_name, args_desc, kind); - cache.SetAt(i + kNameIndex, target_name); - cache.SetAt(i + kArgsDescIndex, args_desc); - cache.SetAt(i + kFunctionIndex, dispatcher); + AddInvocationDispatcher(target_name, args_desc, dispatcher); } return dispatcher.raw(); } @@ -2874,6 +2891,81 @@ RawFunction* Function::GetMethodExtractor(const String& getter_name) const { return result.raw(); } +#if !defined(DART_PRECOMPILED_RUNTIME) +RawFunction* Function::CreateDynamicInvocationForwarder( + const String& mangled_name) const { + Thread* thread = Thread::Current(); + Zone* zone = thread->zone(); + + Function& forwarder = Function::Handle(zone); + forwarder ^= Object::Clone(*this, Heap::kOld); + + forwarder.set_name(mangled_name); + forwarder.set_kind(RawFunction::kDynamicInvocationForwarder); + forwarder.set_is_debuggable(false); + + // TODO(vegorov) for error reporting reasons it is better to make this + // function visible and instead use a TailCall to invoke the target. + // Our TailCall instruction is not ready for such usage though it + // blocks inlining and can't take Function-s only Code objects. + forwarder.set_is_visible(false); + + forwarder.ClearICDataArray(); + forwarder.ClearCode(); + forwarder.set_usage_counter(0); + forwarder.set_deoptimization_counter(0); + forwarder.set_optimized_instruction_count(0); + forwarder.set_inlining_depth(0); + forwarder.set_optimized_call_site_count(0); + forwarder.set_kernel_offset(kernel_offset()); + + return forwarder.raw(); +} + +bool Function::IsDynamicInvocationForwaderName(const String& name) { + return name.StartsWith(Symbols::DynamicPrefix()); +} + +RawString* Function::CreateDynamicInvocationForwarderName(const String& name) { + return Symbols::FromConcat(Thread::Current(), Symbols::DynamicPrefix(), name); +} + +RawString* Function::DemangleDynamicInvocationForwarderName( + const String& name) { + const intptr_t kDynamicPrefixLength = 4; // "dyn:" + ASSERT(Symbols::DynamicPrefix().Length() == kDynamicPrefixLength); + return Symbols::New(Thread::Current(), name, kDynamicPrefixLength, + name.Length() - kDynamicPrefixLength); +} + +RawFunction* Function::GetDynamicInvocationForwarder( + const String& mangled_name, + bool allow_add /* = true */) const { + ASSERT(IsDynamicInvocationForwaderName(mangled_name)); + const Class& owner = Class::Handle(Owner()); + Function& result = Function::Handle(owner.GetInvocationDispatcher( + mangled_name, Array::null_array(), + RawFunction::kDynamicInvocationForwarder, /*create_if_absent=*/false)); + + if (!result.IsNull()) { + return result.raw(); + } + + // Check if function actually needs a dynamic invocation forwarder. + if (!kernel::FlowGraphBuilder::NeedsDynamicInvocationForwarder(*this)) { + result = raw(); + } else if (allow_add) { + result = CreateDynamicInvocationForwarder(mangled_name); + } + + if (allow_add) { + owner.AddInvocationDispatcher(mangled_name, Array::null_array(), result); + } + + return result.raw(); +} +#endif + bool AbstractType::InstantiateAndTestSubtype( AbstractType* subtype, AbstractType* supertype, @@ -6042,6 +6134,8 @@ const char* Function::KindToCString(RawFunction::Kind kind) { case RawFunction::kIrregexpFunction: return "IrregexpFunction"; break; + case RawFunction::kDynamicInvocationForwarder: + return "DynamicInvocationForwarder"; default: UNREACHABLE(); return NULL; @@ -7128,7 +7222,6 @@ RawFunction* Function::Clone(const Class& new_owner) const { clone.set_optimized_instruction_count(0); clone.set_inlining_depth(0); clone.set_optimized_call_site_count(0); - clone.set_kernel_offset(kernel_offset()); if (new_owner.NumTypeParameters() > 0) { // Adjust uninstantiated types to refer to type parameters of the new owner. @@ -7969,6 +8062,9 @@ const char* Function::ToCString() const { case RawFunction::kNoSuchMethodDispatcher: kind_str = " no-such-method-dispatcher"; break; + case RawFunction::kDynamicInvocationForwarder: + kind_str = " dynamic-invocation-forwader"; + break; case RawFunction::kInvokeFieldDispatcher: kind_str = "invoke-field-dispatcher"; break; @@ -13694,8 +13790,18 @@ bool ICData::AddSmiSmiCheckForFastSmiStubs() const { const String& name = String::Handle(target_name()); const Class& smi_class = Class::Handle(Smi::Class()); Zone* zone = Thread::Current()->zone(); - const Function& smi_op_target = + Function& smi_op_target = Function::Handle(Resolver::ResolveDynamicAnyArgs(zone, smi_class, name)); + +#if !defined(DART_PRECOMPILED_RUNTIME) + if (smi_op_target.IsNull() && + Function::IsDynamicInvocationForwaderName(name)) { + const String& demangled = + String::Handle(Function::DemangleDynamicInvocationForwarderName(name)); + smi_op_target = Resolver::ResolveDynamicAnyArgs(zone, smi_class, demangled); + } +#endif + if (NumberOfChecksIs(0)) { GrowableArray class_ids(2); class_ids.Add(kSmiCid); @@ -13753,6 +13859,13 @@ void ICData::AddTarget(const Function& target) const { } bool ICData::ValidateInterceptor(const Function& target) const { +#if !defined(DART_PRECOMPILED_RUNTIME) + const String& name = String::Handle(target_name()); + if (Function::IsDynamicInvocationForwaderName(name)) { + return Function::DemangleDynamicInvocationForwarderName(name) == + target.name(); + } +#endif ObjectStore* store = Isolate::Current()->object_store(); ASSERT((target.raw() == store->simple_instance_of_true_function()) || (target.raw() == store->simple_instance_of_false_function())); diff --git a/runtime/vm/object.h b/runtime/vm/object.h index 513d91c60a9..23c4988afe0 100644 --- a/runtime/vm/object.h +++ b/runtime/vm/object.h @@ -1442,6 +1442,10 @@ class Class : public Object { void CheckReload(const Class& replacement, IsolateReloadContext* context) const; + void AddInvocationDispatcher(const String& target_name, + const Array& args_desc, + const Function& dispatcher) const; + private: bool CanReloadFinalized(const Class& replacement, IsolateReloadContext* context) const; @@ -2302,6 +2306,10 @@ class Function : public Object { return kind() == RawFunction::kInvokeFieldDispatcher; } + bool IsDynamicInvocationForwader() const { + return kind() == RawFunction::kDynamicInvocationForwarder; + } + bool IsImplicitGetterOrSetter() const { return kind() == RawFunction::kImplicitGetter || kind() == RawFunction::kImplicitSetter || @@ -2367,6 +2375,7 @@ class Function : public Object { case RawFunction::kMethodExtractor: case RawFunction::kNoSuchMethodDispatcher: case RawFunction::kInvokeFieldDispatcher: + case RawFunction::kDynamicInvocationForwarder: return true; case RawFunction::kClosureFunction: case RawFunction::kImplicitClosureFunction: @@ -2400,6 +2409,7 @@ class Function : public Object { case RawFunction::kMethodExtractor: case RawFunction::kNoSuchMethodDispatcher: case RawFunction::kInvokeFieldDispatcher: + case RawFunction::kDynamicInvocationForwarder: return false; default: UNREACHABLE(); @@ -2609,6 +2619,7 @@ class Function : public Object { case RawFunction::kImplicitSetter: case RawFunction::kNoSuchMethodDispatcher: case RawFunction::kInvokeFieldDispatcher: + case RawFunction::kDynamicInvocationForwarder: return true; default: return false; @@ -2770,6 +2781,17 @@ class Function : public Object { RawFunction* CreateMethodExtractor(const String& getter_name) const; RawFunction* GetMethodExtractor(const String& getter_name) const; +#if !defined(DART_PRECOMPILED_RUNTIME) + static bool IsDynamicInvocationForwaderName(const String& name); + static RawString* DemangleDynamicInvocationForwarderName(const String& name); + static RawString* CreateDynamicInvocationForwarderName(const String& name); + + RawFunction* CreateDynamicInvocationForwarder( + const String& mangled_name) const; + RawFunction* GetDynamicInvocationForwarder(const String& mangled_name, + bool allow_add = true) const; +#endif + // Allocate new function object, clone values from this function. The // owner of the clone is new_owner. RawFunction* Clone(const Class& new_owner) const; diff --git a/runtime/vm/raw_object.h b/runtime/vm/raw_object.h index 1fd9696c60a..5d7a7f0332a 100644 --- a/runtime/vm/raw_object.h +++ b/runtime/vm/raw_object.h @@ -867,6 +867,9 @@ class RawFunction : public RawObject { kNoSuchMethodDispatcher, // invokes noSuchMethod. kInvokeFieldDispatcher, // invokes a field as a closure. kIrregexpFunction, // represents a generated irregexp matcher function. + kDynamicInvocationForwarder, // represents forwarder which performs type + // checks for arguments of a dynamic + // invocation. }; enum AsyncModifier { diff --git a/runtime/vm/resolver.cc b/runtime/vm/resolver.cc index b0238ad5efe..abb9130f692 100644 --- a/runtime/vm/resolver.cc +++ b/runtime/vm/resolver.cc @@ -69,16 +69,30 @@ RawFunction* Resolver::ResolveDynamicAnyArgs(Zone* zone, THR_Print("ResolveDynamic '%s' for class %s\n", function_name.ToCString(), String::Handle(zone, cls.Name()).ToCString()); } + Function& function = Function::Handle(zone); + + String& demangled = String::Handle(zone); const bool is_getter = Field::IsGetterName(function_name); - String& field_name = String::Handle(zone); if (is_getter) { - field_name ^= Field::NameFromGetter(function_name); + demangled ^= Field::NameFromGetter(function_name); } +#if !defined(DART_PRECOMPILED_RUNTIME) + if (Function::IsDynamicInvocationForwaderName(function_name)) { + ASSERT(!FLAG_precompiled_mode); + demangled ^= + Function::DemangleDynamicInvocationForwarderName(function_name); + function = + ResolveDynamicAnyArgs(zone, receiver_class, demangled, allow_add); + return function.IsNull() ? function.raw() + : function.GetDynamicInvocationForwarder( + function_name, allow_add); + } +#endif + // Now look for an instance function whose name matches function_name // in the class. - Function& function = Function::Handle(zone); while (!cls.IsNull()) { function ^= cls.LookupDynamicFunction(function_name); if (!function.IsNull()) { @@ -87,7 +101,7 @@ RawFunction* Resolver::ResolveDynamicAnyArgs(Zone* zone, // Getter invocation might actually be a method extraction. if (FLAG_lazy_dispatchers) { if (is_getter && function.IsNull()) { - function ^= cls.LookupDynamicFunction(field_name); + function ^= cls.LookupDynamicFunction(demangled); if (!function.IsNull() && allow_add) { // We were looking for the getter but found a method with the same // name. Create a method extractor and return it. diff --git a/runtime/vm/runtime_entry.cc b/runtime/vm/runtime_entry.cc index 4ffa33078d0..38d5fa12ca1 100644 --- a/runtime/vm/runtime_entry.cc +++ b/runtime/vm/runtime_entry.cc @@ -1024,6 +1024,16 @@ RawFunction* InlineCacheMissHelper(const Instance& receiver, const String& target_name) { const Class& receiver_class = Class::Handle(receiver.clazz()); +#if !defined(DART_PRECOMPILED_RUNTIME) + // Handle noSuchMethod for dyn:methodName by getting a noSuchMethod dispatcher + // (or a call-through getter for methodName). + if (Function::IsDynamicInvocationForwaderName(target_name)) { + const String& demangled = String::Handle( + Function::DemangleDynamicInvocationForwarderName(target_name)); + return InlineCacheMissHelper(receiver, args_descriptor, demangled); + } +#endif + Function& result = Function::Handle(); if (!ResolveCallThroughGetter(receiver, receiver_class, target_name, args_descriptor, &result)) { diff --git a/runtime/vm/symbols.h b/runtime/vm/symbols.h index 50e5b36b34d..d901830e538 100644 --- a/runtime/vm/symbols.h +++ b/runtime/vm/symbols.h @@ -367,6 +367,7 @@ class ObjectPointerVisitor; V(GetterPrefix, "get:") \ V(SetterPrefix, "set:") \ V(InitPrefix, "init:") \ + V(DynamicPrefix, "dyn:") \ V(Index, "index") \ V(DartScheme, "dart:") \ V(DartSchemePrivate, "dart:_") \