[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 <vegorov@google.com>
Reviewed-by: Alexander Markov <alexmarkov@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
diff --git a/runtime/observatory/lib/src/elements/function_view.dart b/runtime/observatory/lib/src/elements/function_view.dart
index 93cc57c..c3edda8 100644
--- a/runtime/observatory/lib/src/elements/function_view.dart
+++ b/runtime/observatory/lib/src/elements/function_view.dart
@@ -433,6 +433,8 @@
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 904038d..e619f5e 100644
--- a/runtime/observatory/lib/src/models/objects/function.dart
+++ b/runtime/observatory/lib/src/models/objects/function.dart
@@ -23,7 +23,8 @@
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 ace655c..768262c 100644
--- a/runtime/observatory/lib/src/service/object.dart
+++ b/runtime/observatory/lib/src/service/object.dart
@@ -3112,6 +3112,8 @@
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 c329ac8..01c88c8 100644
--- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
+++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
@@ -38,6 +38,31 @@
.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 @@
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 @@
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 @@
}
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 @@
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 @@
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 @@
}
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 @@
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 @@
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 @@
// 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 @@
if (function.NeedsArgumentTypeChecks(I)) {
AlternativeReadingScope _(&reader_);
- body += BuildArgumentTypeChecks();
+ body += BuildArgumentTypeChecks(kCheckAllTypeParameterBounds);
}
function_node_helper.ReadUntilExcluding(
@@ -6062,8 +6136,84 @@
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 @@
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 @@
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 @@
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 @@
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 @@
? 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 @@
} 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 @@
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 a28476b..5b1c7dd 100644
--- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h
+++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h
@@ -115,6 +115,10 @@
void Finish() { ReadUntilExcluding(kEnd); }
+ bool IsGenericCovariantImpl() {
+ return (flags_ & kIsGenericCovariantImpl) != 0;
+ }
+
TokenPosition position_;
uint8_t flags_;
StringIndex name_index_;
@@ -814,8 +818,8 @@
// 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 @@
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 @@
// 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 @@
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 @@
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 f019342..1bb584e 100644
--- a/runtime/vm/compiler/frontend/kernel_to_il.cc
+++ b/runtime/vm/compiler/frontend/kernel_to_il.cc
@@ -2061,6 +2061,28 @@
}
}
+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 @@
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 e0671a2..9bb30e5 100644
--- a/runtime/vm/compiler/frontend/kernel_to_il.h
+++ b/runtime/vm/compiler/frontend/kernel_to_il.h
@@ -217,6 +217,7 @@
function_kind == RawFunction::kGetterFunction ||
function_kind == RawFunction::kSetterFunction ||
function_kind == RawFunction::kMethodExtractor ||
+ function_kind == RawFunction::kDynamicInvocationForwarder ||
member->IsFactory();
}
@@ -705,6 +706,12 @@
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 7097f5a..974c447 100644
--- a/runtime/vm/isolate.h
+++ b/runtime/vm/isolate.h
@@ -687,6 +687,13 @@
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 @@
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 b5efb44..4f9a2af 100644
--- a/runtime/vm/kernel_isolate.cc
+++ b/runtime/vm/kernel_isolate.cc
@@ -265,6 +265,9 @@
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 06a4263..83b99b2 100644
--- a/runtime/vm/log.cc
+++ b/runtime/vm/log.cc
@@ -115,7 +115,7 @@
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 1aa4b52..b5dc110 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -2714,27 +2714,54 @@
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<intptr_t>(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 @@
}
if (dispatcher.IsNull() && create_if_absent) {
- if (i == cache.Length()) {
- // Allocate new larger cache.
- intptr_t new_len = (cache.Length() == 0)
- ? static_cast<intptr_t>(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 @@
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 @@
case RawFunction::kIrregexpFunction:
return "IrregexpFunction";
break;
+ case RawFunction::kDynamicInvocationForwarder:
+ return "DynamicInvocationForwarder";
default:
UNREACHABLE();
return NULL;
@@ -7128,7 +7222,6 @@
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 @@
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 @@
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<intptr_t> class_ids(2);
class_ids.Add(kSmiCid);
@@ -13753,6 +13859,13 @@
}
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 513d91c..23c4988 100644
--- a/runtime/vm/object.h
+++ b/runtime/vm/object.h
@@ -1442,6 +1442,10 @@
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 @@
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 @@
case RawFunction::kMethodExtractor:
case RawFunction::kNoSuchMethodDispatcher:
case RawFunction::kInvokeFieldDispatcher:
+ case RawFunction::kDynamicInvocationForwarder:
return true;
case RawFunction::kClosureFunction:
case RawFunction::kImplicitClosureFunction:
@@ -2400,6 +2409,7 @@
case RawFunction::kMethodExtractor:
case RawFunction::kNoSuchMethodDispatcher:
case RawFunction::kInvokeFieldDispatcher:
+ case RawFunction::kDynamicInvocationForwarder:
return false;
default:
UNREACHABLE();
@@ -2609,6 +2619,7 @@
case RawFunction::kImplicitSetter:
case RawFunction::kNoSuchMethodDispatcher:
case RawFunction::kInvokeFieldDispatcher:
+ case RawFunction::kDynamicInvocationForwarder:
return true;
default:
return false;
@@ -2770,6 +2781,17 @@
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 1fd9696..5d7a7f0 100644
--- a/runtime/vm/raw_object.h
+++ b/runtime/vm/raw_object.h
@@ -867,6 +867,9 @@
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 b0238ad..abb9130 100644
--- a/runtime/vm/resolver.cc
+++ b/runtime/vm/resolver.cc
@@ -69,16 +69,30 @@
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 @@
// 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 4ffa330..38d5fa1 100644
--- a/runtime/vm/runtime_entry.cc
+++ b/runtime/vm/runtime_entry.cc
@@ -1024,6 +1024,16 @@
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 50e5b36..d901830 100644
--- a/runtime/vm/symbols.h
+++ b/runtime/vm/symbols.h
@@ -367,6 +367,7 @@
V(GetterPrefix, "get:") \
V(SetterPrefix, "set:") \
V(InitPrefix, "init:") \
+ V(DynamicPrefix, "dyn:") \
V(Index, "index") \
V(DartScheme, "dart:") \
V(DartSchemePrivate, "dart:_") \