From d117760ba6da68d499ff76dd0a17a42b445b47fe Mon Sep 17 00:00:00 2001 From: Vyacheslav Egorov Date: Fri, 2 Feb 2018 11:43:55 +0000 Subject: [PATCH] [vm/kernel/aot] Skip unnecessary type checks on parameters of instance methods. This relands 75a9579ea087d6cecce26cc6ebae0a01d6f0eb48. The approach works as follows: Step 1: Kernel transform. Under the closed world assumption compute the set of selectors dispatched dynamically, then mark all procedures that don't match any of those selectors as 'not-dispatched-dynamically'. Step 2: VM backend. When building IR for a function if this function was marked as not-dispatched-dynamically then omit type checks for any parameter that is not marked as generic-covariant-impl, as such arguments are guaranteed to be checked on the caller side (by front-end). +------------------------+------------+----------+-----------+--------------+ | benchmark | baseline | current | with opt | improved by | +------------------------+------------+----------+-----------+--------------+ | stock_layout_iteration | 2366.3786 | 2724.3 | 2562.75 | -5.93% | | stock_build_iteration | 3824.3 | 4914.8 | 4681 | -4.76% | +------------------------+------------+----------+-----------+--------------+ * Flutter gallery Instructions size is reduced by 11% (8748720 bytes to 7846368 bytes). Baseline is at 6196496 bytes. Alternatively to annotating individual procedures, I considered annotating Program node with a set of dynamically dispatched selectors. Decoding and passing this information around proved to be quite cumbersome in the "streaming" world, so I opted for a simpler approach where all individual procedures are annotated. Bug: https://github.com/dart-lang/sdk/issues/3179 Change-Id: I2f32a609e3872c74d5ae7bbd97555453aaedf15f Reviewed-on: https://dart-review.googlesource.com/38125 Commit-Queue: Samir Jindel Reviewed-by: Samir Jindel --- pkg/vm/bin/dump_kernel.dart | 3 + pkg/vm/lib/kernel_front_end.dart | 3 + pkg/vm/lib/metadata/procedure_attributes.dart | 41 +++ .../no_dynamic_invocations_annotator.dart | 117 +++++++++ .../vm/dart/callee_side_type_checks_test.dart | 87 +++++++ runtime/tests/vm/vm.status | 3 + .../vm/compiler/backend/type_propagator.cc | 14 +- .../frontend/kernel_binary_flowgraph.cc | 246 ++++++++++++++---- .../frontend/kernel_binary_flowgraph.h | 72 ++++- runtime/vm/scopes.h | 20 ++ 10 files changed, 537 insertions(+), 69 deletions(-) create mode 100644 pkg/vm/lib/metadata/procedure_attributes.dart create mode 100644 pkg/vm/lib/transformations/no_dynamic_invocations_annotator.dart create mode 100644 runtime/tests/vm/dart/callee_side_type_checks_test.dart diff --git a/pkg/vm/bin/dump_kernel.dart b/pkg/vm/bin/dump_kernel.dart index 28499bc26ae..46f66800092 100644 --- a/pkg/vm/bin/dump_kernel.dart +++ b/pkg/vm/bin/dump_kernel.dart @@ -11,6 +11,8 @@ import 'package:kernel/binary/ast_from_binary.dart' import 'package:vm/metadata/direct_call.dart' show DirectCallMetadataRepository; import 'package:vm/metadata/inferred_type.dart' show InferredTypeMetadataRepository; +import 'package:vm/metadata/procedure_attributes.dart' + show ProcedureAttributesMetadataRepository; final String _usage = ''' Usage: dump_kernel input.dill output.txt @@ -31,6 +33,7 @@ main(List arguments) async { // Register VM-specific metadata. program.addMetadataRepository(new DirectCallMetadataRepository()); program.addMetadataRepository(new InferredTypeMetadataRepository()); + program.addMetadataRepository(new ProcedureAttributesMetadataRepository()); final List bytes = new File(input).readAsBytesSync(); new BinaryBuilderWithMetadata(bytes).readProgram(program); diff --git a/pkg/vm/lib/kernel_front_end.dart b/pkg/vm/lib/kernel_front_end.dart index 281f67c117b..569b786e8f1 100644 --- a/pkg/vm/lib/kernel_front_end.dart +++ b/pkg/vm/lib/kernel_front_end.dart @@ -19,6 +19,8 @@ import 'package:kernel/core_types.dart' show CoreTypes; import 'transformations/devirtualization.dart' as devirtualization show transformProgram; +import 'transformations/no_dynamic_invocations_annotator.dart' + as no_dynamic_invocations_annotator show transformProgram; import 'transformations/type_flow/transformer.dart' as globalTypeFlow show transformProgram; @@ -58,6 +60,7 @@ _runGlobalTransformations(Program program, bool strongMode) { globalTypeFlow.transformProgram(coreTypes, program); } else { devirtualization.transformProgram(coreTypes, program); + no_dynamic_invocations_annotator.transformProgram(coreTypes, program); } } } diff --git a/pkg/vm/lib/metadata/procedure_attributes.dart b/pkg/vm/lib/metadata/procedure_attributes.dart new file mode 100644 index 00000000000..1e6acb8659d --- /dev/null +++ b/pkg/vm/lib/metadata/procedure_attributes.dart @@ -0,0 +1,41 @@ +// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +library vm.metadata.procedure_attributes; + +import 'package:kernel/ast.dart'; + +/// Metadata for annotating procedures with various attributes. +class ProcedureAttributesMetadata { + final bool hasDynamicInvocations; + + const ProcedureAttributesMetadata({this.hasDynamicInvocations}); + + const ProcedureAttributesMetadata.noDynamicInvocations() + : hasDynamicInvocations = false; + + @override + String toString() => "hasDynamicInvocations:${hasDynamicInvocations}"; +} + +/// Repository for [ProcedureAttributesMetadata]. +class ProcedureAttributesMetadataRepository + extends MetadataRepository { + @override + final String tag = 'vm.procedure-attributes.metadata'; + + @override + final Map mapping = + {}; + + @override + void writeToBinary(ProcedureAttributesMetadata metadata, BinarySink sink) { + assert(!metadata.hasDynamicInvocations); + } + + @override + ProcedureAttributesMetadata readFromBinary(BinarySource source) { + return const ProcedureAttributesMetadata.noDynamicInvocations(); + } +} diff --git a/pkg/vm/lib/transformations/no_dynamic_invocations_annotator.dart b/pkg/vm/lib/transformations/no_dynamic_invocations_annotator.dart new file mode 100644 index 00000000000..7950fa0e68b --- /dev/null +++ b/pkg/vm/lib/transformations/no_dynamic_invocations_annotator.dart @@ -0,0 +1,117 @@ +// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +library vm.transformations.no_dynamic_invocations_annotator; + +import 'package:kernel/ast.dart'; +import 'package:kernel/core_types.dart' show CoreTypes; + +import '../metadata/procedure_attributes.dart'; + +/// Assumes strong mode and closed world. If a procedure can not be riched +/// via dynamic invocation from anywhere then annotates it with appropriate +/// [ProcedureAttributeMetadata] annotation. +Program transformProgram(CoreTypes coreTypes, Program program) { + new NoDynamicInvocationsAnnotator(program).visitProgram(program); + return program; +} + +enum Action { get, set, invoke } + +class Selector { + final Action action; + final Name target; + + Selector(this.action, this.target); + + bool operator ==(other) { + return other is Selector && + other.action == this.action && + other.target == this.target; + } + + int get hashCode => (action.index * 31) ^ target.hashCode; + + @override + String toString() { + switch (action) { + case Action.get: + return 'get:${target}'; + case Action.set: + return 'set:${target}'; + case Action.invoke: + return '${target}'; + } + return '?'; + } +} + +class NoDynamicInvocationsAnnotator extends RecursiveVisitor { + final Set _dynamicSelectors; + final ProcedureAttributesMetadataRepository _metadata; + + NoDynamicInvocationsAnnotator(Program program) + : _dynamicSelectors = DynamicSelectorsCollector.collect(program), + _metadata = new ProcedureAttributesMetadataRepository() { + program.addMetadataRepository(_metadata); + } + + @override + visitProcedure(Procedure node) { + if (node.isStatic || node.name.name == 'call') { + return; + } + + Selector selector; + if (node.kind == ProcedureKind.Method) { + selector = new Selector(Action.invoke, node.name); + } else if (node.kind == ProcedureKind.Setter) { + selector = new Selector(Action.set, node.name); + } else { + return; + } + + if (!_dynamicSelectors.contains(selector)) { + _metadata.mapping[node] = + const ProcedureAttributesMetadata.noDynamicInvocations(); + } + } +} + +class DynamicSelectorsCollector extends RecursiveVisitor { + final Set dynamicSelectors = new Set(); + + static Set collect(Program program) { + final v = new DynamicSelectorsCollector(); + v.visitProgram(program); + return v.dynamicSelectors; + } + + @override + visitMethodInvocation(MethodInvocation node) { + super.visitMethodInvocation(node); + + if (node.dispatchCategory == DispatchCategory.dynamicDispatch) { + dynamicSelectors.add(new Selector(Action.invoke, node.name)); + } + } + + @override + visitPropertyGet(PropertyGet node) { + super.visitPropertyGet(node); + + if (node.dispatchCategory == DispatchCategory.dynamicDispatch) { + dynamicSelectors.add(new Selector(Action.get, node.name)); + } + } + + @override + visitPropertySet(PropertySet node) { + super.visitPropertySet(node); + + if (node.dispatchCategory == DispatchCategory.dynamicDispatch) { + dynamicSelectors.add(new Selector(Action.set, node.name)); + } + } +} diff --git a/runtime/tests/vm/dart/callee_side_type_checks_test.dart b/runtime/tests/vm/dart/callee_side_type_checks_test.dart new file mode 100644 index 00000000000..5a8dfaf80a4 --- /dev/null +++ b/runtime/tests/vm/dart/callee_side_type_checks_test.dart @@ -0,0 +1,87 @@ +// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// VMOptions=--reify-generic-functions + +import "package:expect/expect.dart"; + +// This test tests that AOT compiler does not optimize away necessary +// type checks. + +class A { + int _addOneToArgument(int x) => x + 1; +} + +abstract class G { + int _addOneToArgument(T x); +} + +class B extends A implements G {} + +class C { + int _addTwoToArgument(int x) => x + 2; +} + +class D { + int _addThreeToArgument(num x) { + return 0; + } +} + +class E extends D { + int _addThreeToArgument(covariant int x) { + return x + 3; + } +} + +typedef dynamic F0(T val); +typedef U F1(T val); + +class F { + T fMethod1(F0 f, T val) => f(val) as T; + U fMethod2(F1 f, T val) => f(val); +} + +final arr = [ + new B(), + new C(), + new E(), + new D(), // Just to confuse CHA + new F(), +]; + +int _add42Int(int v) => v + 42; +double _add42Double(double v) => v + 42; +double _add42_0Int(int v) => v + 42.0; + +main() { + final b = arr[0] as G; + + Expect.equals(1, b._addOneToArgument(0)); + Expect.equals(0, b._addOneToArgument(-1)); + Expect.throwsTypeError(() => b._addOneToArgument(1.1)); + + final c = (arr[1] as C); + final tornMethod = c._addTwoToArgument; + Expect.equals(2, c._addTwoToArgument(0)); + Expect.equals(0, c._addTwoToArgument(-2)); + Expect.throwsTypeError(() => (tornMethod as dynamic)(1.1)); + + final e = (arr[2] as D); + Expect.equals(3, e._addThreeToArgument(0)); + Expect.equals(0, e._addThreeToArgument(-3)); + Expect.throwsTypeError(() => e._addThreeToArgument(1.1)); + + final f = (arr[4] as F); + final torn1 = f.fMethod1 as dynamic; + Expect.equals(43, torn1(_add42Int, 1)); + Expect.throwsTypeError(() => torn1(_add42Double, 1)); + Expect.throwsTypeError(() => torn1(_add42Int, 1.1)); + + final torn2 = f.fMethod2 as dynamic; + Expect.equals(43, torn2(_add42Int, 1)); + Expect.equals(43.0, torn2(_add42_0Int, 1)); + Expect.throwsTypeError(() => torn2(_add42Int, 1)); + Expect.throwsTypeError(() => torn2(_add42_0Int, 1)); +} diff --git a/runtime/tests/vm/vm.status b/runtime/tests/vm/vm.status index e69c0e20286..dcb48d1081f 100644 --- a/runtime/tests/vm/vm.status +++ b/runtime/tests/vm/vm.status @@ -105,6 +105,9 @@ dart/spawn_shutdown_test: Skip # OOM crash can bring down the OS. cc/CorelibCompilerStats: Skip cc/Service_Profile: Skip +[ !$strong ] +dart/callee_side_type_checks_test: SkipByDesign + # Following tests are failing in a weird way on macos/ia32/debug builds # need to investigate. [ $arch == ia32 && $mode == debug && $runtime == vm && $system == macos ] diff --git a/runtime/vm/compiler/backend/type_propagator.cc b/runtime/vm/compiler/backend/type_propagator.cc index 2520f4b0b52..d69740c99ba 100644 --- a/runtime/vm/compiler/backend/type_propagator.cc +++ b/runtime/vm/compiler/backend/type_propagator.cc @@ -942,8 +942,18 @@ CompileType ParameterInstr::ComputeType() const { return CompileType(CompileType::kNonNullable, cid, &type); } - // TODO(dartbug.com/30480): Figure out how to use parameter types - // without interfering with argument type checks. + if (Isolate::Current()->strong() && FLAG_use_strong_mode_types) { + LocalScope* scope = graph_entry->parsed_function().node_sequence()->scope(); + // Note: in catch-blocks we have ParameterInstr for each local variable + // not only for normal parameters. + if (index() < scope->num_variables()) { + LocalVariable* param = scope->VariableAt(index()); + if (param->was_type_checked_by_caller()) { + return CompileType::FromAbstractType(param->type(), + CompileType::kNullable); + } + } + } return CompileType::Dynamic(); } diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc index 52d5650d1d8..8b9b987c5f4 100644 --- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc +++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc @@ -566,9 +566,8 @@ void MetadataHelper::SetMetadataMappings(intptr_t mappings_offset, } #endif // DEBUG - last_node_offset_ = builder_->data_program_offset_ + - builder_->parsed_function()->function().kernel_offset(); - last_mapping_index_ = FindMetadataMapping(last_node_offset_); + last_node_offset_ = kIntptrMax; + last_mapping_index_ = 0; } intptr_t MetadataHelper::FindMetadataMapping(intptr_t node_offset) { @@ -732,6 +731,25 @@ DirectCallMetadata DirectCallMetadataHelper::GetDirectTargetForMethodInvocation( return DirectCallMetadata(target, check_receiver_for_null); } +bool ProcedureAttributesMetadataHelper::ReadMetadata(intptr_t node_offset, + bool* has_dynamic_calls) { + intptr_t md_offset = GetNextMetadataPayloadOffset(node_offset); + if (md_offset < 0) { + *has_dynamic_calls = true; + return false; + } + *has_dynamic_calls = false; + return true; +} + +ProcedureAttributesMetadata +ProcedureAttributesMetadataHelper::GetProcedureAttributes( + intptr_t node_offset) { + bool has_dynamic_calls = true; + ReadMetadata(node_offset, &has_dynamic_calls); + return ProcedureAttributesMetadata(has_dynamic_calls); +} + InferredTypeMetadata InferredTypeMetadataHelper::GetInferredType( intptr_t node_offset) { const intptr_t md_offset = GetNextMetadataPayloadOffset(node_offset); @@ -803,7 +821,21 @@ ScopeBuildingResult* StreamingScopeBuilder::BuildScopes() { ActiveTypeParametersScope active_type_params(&active_class_, function, Z); LocalScope* enclosing_scope = NULL; - if (function.IsLocalFunction()) { + if (function.IsImplicitClosureFunction() && !function.is_static()) { + // Create artificial enclosing scope for the tear-off that contains + // captured receiver value. This ensure that AssertAssignable will correctly + // load instantiator type arguments if they are needed. + Class& klass = Class::Handle(Z, function.Owner()); + Type& klass_type = H.GetCanonicalType(klass); + result_->this_variable = + MakeVariable(TokenPosition::kNoSource, TokenPosition::kNoSource, + Symbols::This(), klass_type); + result_->this_variable->set_index(0); + result_->this_variable->set_is_captured(); + enclosing_scope = new (Z) LocalScope(NULL, 0, 0); + enclosing_scope->set_context_level(1); + enclosing_scope->AddVariable(result_->this_variable); + } else if (function.IsLocalFunction()) { enclosing_scope = LocalScope::RestoreOuterScope( ContextScope::Handle(Z, function.context_scope())); } @@ -835,6 +867,9 @@ ScopeBuildingResult* StreamingScopeBuilder::BuildScopes() { builder_->SetOffset(function.kernel_offset()); FunctionNodeHelper function_node_helper(builder_); + const ProcedureAttributesMetadata attrs = + builder_->procedure_attributes_metadata_helper_.GetProcedureAttributes( + function.kernel_offset()); switch (function.kind()) { case RawFunction::kClosureFunction: @@ -904,9 +939,34 @@ ScopeBuildingResult* StreamingScopeBuilder::BuildScopes() { result_->type_arguments_variable = variable; } + ParameterTypeCheckMode type_check_mode = kTypeCheckAllParameters; + if (!function.IsImplicitClosureFunction()) { + if (function.is_static()) { + // In static functions we don't check anything. + type_check_mode = kTypeCheckForStaticFunction; + } else if (!attrs.has_dynamic_invocations) { + // 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 + // ever occur at the position of this parameter) then we don't need + // to check this parameter on the callee side, because strong mode + // guarantees that it was checked at the caller side. + type_check_mode = kTypeCheckForNonDynamicallyInvokedMethod; + } + } else { + if (!attrs.has_dynamic_invocations) { + // 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; + } + } + // Continue reading FunctionNode: // read positional_parameters and named_parameters. - AddPositionalAndNamedParameters(pos); + AddPositionalAndNamedParameters(pos, type_check_mode); // We generate a synthetic body for implicit closure functions - which // will forward the call to the real function. @@ -1945,7 +2005,7 @@ void StreamingScopeBuilder::HandleLocalFunction(intptr_t parent_kernel_offset) { // read positional_parameters and named_parameters. function_node_helper.ReadUntilExcluding( FunctionNodeHelper::kPositionalParameters); - AddPositionalAndNamedParameters(); + AddPositionalAndNamedParameters(0, kTypeCheckAllParameters); // "Peek" is now done. builder_->SetOffset(offset); @@ -1971,21 +2031,25 @@ void StreamingScopeBuilder::ExitScope(TokenPosition start_position, scope_ = scope_->parent(); } -void StreamingScopeBuilder::AddPositionalAndNamedParameters(intptr_t pos) { +void StreamingScopeBuilder::AddPositionalAndNamedParameters( + intptr_t pos, + ParameterTypeCheckMode type_check_mode /* = kTypeCheckAllParameters*/) { // List of positional. intptr_t list_length = builder_->ReadListLength(); // read list length. for (intptr_t i = 0; i < list_length; ++i) { - AddVariableDeclarationParameter(pos++); // read ith positional parameter. + AddVariableDeclarationParameter(pos++, type_check_mode); } // List of named. list_length = builder_->ReadListLength(); // read list length. for (intptr_t i = 0; i < list_length; ++i) { - AddVariableDeclarationParameter(pos++); // read ith named parameter. + AddVariableDeclarationParameter(pos++, type_check_mode); } } -void StreamingScopeBuilder::AddVariableDeclarationParameter(intptr_t pos) { +void StreamingScopeBuilder::AddVariableDeclarationParameter( + intptr_t pos, + ParameterTypeCheckMode type_check_mode) { intptr_t kernel_offset = builder_->ReaderOffset(); // no tag. VariableDeclarationHelper helper(builder_); helper.ReadUntilExcluding(VariableDeclarationHelper::kType); @@ -2002,6 +2066,35 @@ void StreamingScopeBuilder::AddVariableDeclarationParameter(intptr_t pos) { if (variable->name().raw() == Symbols::IteratorParameter().raw()) { variable->set_is_forced_stack(); } + + const bool is_covariant = + helper.IsGenericCovariantImpl() || helper.IsCovariant(); + switch (type_check_mode) { + case kTypeCheckAllParameters: + variable->set_type_check_mode(LocalVariable::kDoTypeCheck); + break; + case kTypeCheckForTearOffOfNonDynamicallyInvokedMethod: + if (is_covariant) { + // 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); + } else { + variable->set_type_check_mode(LocalVariable::kDoTypeCheck); + } + break; + case kTypeCheckForNonDynamicallyInvokedMethod: + if (is_covariant) { + variable->set_type_check_mode(LocalVariable::kDoTypeCheck); + } else { + // Types of non-covariant parameters are guaranteed to match by + // front-end enforcing strong mode types at call site. + variable->set_type_check_mode(LocalVariable::kTypeCheckedByCaller); + } + break; + case kTypeCheckForStaticFunction: + variable->set_type_check_mode(LocalVariable::kTypeCheckedByCaller); + break; + } scope_->InsertParameterAt(pos, variable); result_->locals.Insert(builder_->data_program_offset_ + kernel_offset, variable); @@ -3980,11 +4073,25 @@ FlowGraph* StreamingFlowGraphBuilder::BuildGraphOfImplicitClosureFunction( FunctionNodeHelper function_node_helper(this); function_node_helper.ReadUntilExcluding(FunctionNodeHelper::kTypeParameters); - // Tearoffs of static methods needs to perform arguments checks since static - // methods they forward to don't do it themselves. - if (I->argument_type_checks() && !target.NeedsArgumentTypeChecks(I)) { - AlternativeReadingScope _(reader_); - body += BuildArgumentTypeChecks(); + if (I->argument_type_checks()) { + if (!target.NeedsArgumentTypeChecks(I)) { + // Tearoffs of static methods needs to perform arguments checks since + // static methods they forward to don't do it themselves. + AlternativeReadingScope _(reader_); + body += BuildArgumentTypeChecks(); + } else { + // Check if target function was annotated with no-dynamic-invocations. + const ProcedureAttributesMetadata attrs = + procedure_attributes_metadata_helper_.GetProcedureAttributes( + target.kernel_offset()); + if (!attrs.has_dynamic_invocations) { + // If it was then we might need to build some checks in the + // tear-off. + AlternativeReadingScope _(reader_); + body += + BuildArgumentTypeChecks(kTypeChecksForNoDynamicInvocationsTearOff); + } + } } function_node_helper.ReadUntilExcluding( @@ -4041,7 +4148,8 @@ FlowGraph* StreamingFlowGraphBuilder::BuildGraphOfImplicitClosureFunction( flow_graph_builder_->last_used_block_id_, prologue_info); } -Fragment StreamingFlowGraphBuilder::BuildArgumentTypeChecks() { +Fragment StreamingFlowGraphBuilder::BuildArgumentTypeChecks( + TypeChecksToBuild mode /*= kDefaultTypeChecks*/) { FunctionNodeHelper function_node_helper(this); function_node_helper.SetNext(FunctionNodeHelper::kTypeParameters); const Function& dart_function = parsed_function()->function(); @@ -4060,53 +4168,60 @@ Fragment StreamingFlowGraphBuilder::BuildArgumentTypeChecks() { } // Type parameters - intptr_t list_length = ReadListLength(); - TypeArguments& forwarding_params = TypeArguments::Handle(Z); - if (forwarding_target != NULL) { - forwarding_params = forwarding_target->type_parameters(); - ASSERT(forwarding_params.Length() == list_length); - } - TypeParameter& forwarding_param = TypeParameter::Handle(Z); - for (intptr_t i = 0; i < list_length; ++i) { - ReadFlags(); // skip flags - SkipListOfExpressions(); // skip annotations - String& name = H.DartSymbol(ReadStringReference()); // read name - AbstractType& bound = T.BuildType(); // read bound - + if (mode == kDefaultTypeChecks) { + intptr_t num_type_params = ReadListLength(); + TypeArguments& forwarding_params = TypeArguments::Handle(Z); if (forwarding_target != NULL) { - forwarding_param ^= forwarding_params.TypeAt(i); - bound = forwarding_param.bound(); + 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) { + ReadFlags(); // skip flags + SkipListOfExpressions(); // skip annotations + String& name = H.DartSymbol(ReadStringReference()); // read name + AbstractType& bound = T.BuildType(); // read bound - if (I->strong() && !bound.IsObjectType() && - (I->reify_generic_functions() || dart_function.IsFactory())) { - ASSERT(!bound.IsDynamicType()); - 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); + if (forwarding_target != NULL) { + forwarding_param ^= forwarding_params.TypeAt(i); + bound = forwarding_param.bound(); + } + + if (I->strong() && !bound.IsObjectType() && + (I->reify_generic_functions() || dart_function.IsFactory())) { + ASSERT(!bound.IsDynamicType()); + 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); } - ASSERT(param.IsFinalized()); - body += CheckTypeArgumentBound(param, bound, name); } + function_node_helper.SetJustRead(FunctionNodeHelper::kTypeParameters); } - function_node_helper.SetJustRead(FunctionNodeHelper::kTypeParameters); function_node_helper.ReadUntilExcluding( FunctionNodeHelper::kPositionalParameters); // Positional. - list_length = ReadListLength(); - const intptr_t positional_length = list_length; + const intptr_t num_positional_params = ReadListLength(); const intptr_t kFirstParameterOffset = 1; - for (intptr_t i = 0; i < list_length; ++i) { + 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_); + if (!param->needs_type_check()) { + continue; + } + const AbstractType* target_type = ¶m->type(); if (forwarding_target != NULL) { // We add 1 to the parameter index to account for the receiver. @@ -4116,25 +4231,29 @@ Fragment StreamingFlowGraphBuilder::BuildArgumentTypeChecks() { body += LoadLocal(param); body += CheckArgumentType(param, *target_type); body += Drop(); - SkipVariableDeclaration(); // read ith variable. } // Named. - list_length = ReadListLength(); - for (intptr_t i = 0; i < list_length; ++i) { + const intptr_t num_named_params = ReadListLength(); + for (intptr_t i = 0; i < num_named_params; ++i) { // ith variable offset. - LocalVariable* param = - LookupVariable(ReaderOffset() + data_program_offset_); - body += LoadLocal(param); + const intptr_t offset = ReaderOffset(); + SkipVariableDeclaration(); + + LocalVariable* param = LookupVariable(offset + data_program_offset_); + if (!param->needs_type_check()) { + continue; + } + const AbstractType* target_type = ¶m->type(); if (forwarding_target != NULL) { // We add 1 to the parameter index to account for the receiver. target_type = &AbstractType::ZoneHandle( - Z, forwarding_target->ParameterTypeAt(positional_length + i + 1)); + Z, forwarding_target->ParameterTypeAt(num_positional_params + i + 1)); } + body += LoadLocal(param); body += CheckArgumentType(param, *target_type); body += Drop(); - SkipVariableDeclaration(); // read ith variable. } return body; @@ -9584,6 +9703,9 @@ void StreamingFlowGraphBuilder::EnsureMetadataIsScanned() { const intptr_t kUInt32Size = 4; Reader reader(H.metadata_mappings()); + if (reader.size() == 0) { + return; + } // Scan through metadata mappings in reverse direction. @@ -9631,6 +9753,18 @@ void StreamingFlowGraphBuilder::EnsureMetadataIsScanned() { inferred_type_metadata_helper_.SetMetadataMappings(offset + kUInt32Size, mappings_num); } + } else if (H.StringEquals(tag, ProcedureAttributesMetadataHelper::tag())) { + ASSERT(node_references_num == 0); + + if (mappings_num > 0) { + if (!FLAG_precompiled_mode) { + FATAL( + "ProcedureAttributesMetadata is allowed in precompiled mode " + "only"); + } + procedure_attributes_metadata_helper_.SetMetadataMappings( + offset + kUInt32Size, mappings_num); + } } } } diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h index 7a2ea01fd08..b99111b3917 100644 --- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h +++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h @@ -590,6 +590,26 @@ class InferredTypeMetadataHelper : public MetadataHelper { InferredTypeMetadata GetInferredType(intptr_t node_offset); }; +struct ProcedureAttributesMetadata { + explicit ProcedureAttributesMetadata(bool has_dynamic_invocations) + : has_dynamic_invocations(has_dynamic_invocations) {} + const bool has_dynamic_invocations; +}; + +// Helper class which provides access to direct call metadata. +class ProcedureAttributesMetadataHelper : public MetadataHelper { + public: + static const char* tag() { return "vm.procedure-attributes.metadata"; } + + explicit ProcedureAttributesMetadataHelper(StreamingFlowGraphBuilder* builder) + : MetadataHelper(builder) {} + + ProcedureAttributesMetadata GetProcedureAttributes(intptr_t node_offset); + + private: + bool ReadMetadata(intptr_t node_offset, bool* has_dynamic_invocations); +}; + class StreamingDartTypeTranslator { public: StreamingDartTypeTranslator(StreamingFlowGraphBuilder* builder, @@ -692,16 +712,34 @@ class StreamingScopeBuilder { void EnterScope(intptr_t kernel_offset); void ExitScope(TokenPosition start_position, TokenPosition end_position); - /** - * This assumes that the reader is at a FunctionNode, - * about to read the positional parameters. - */ - void AddPositionalAndNamedParameters(intptr_t pos = 0); - /** - * This assumes that the reader is at a FunctionNode, - * about to read a parameter (i.e. VariableDeclaration). - */ - void AddVariableDeclarationParameter(intptr_t pos); + // This enum controls which parameters would be marked as requring type + // check on the callee side. + enum ParameterTypeCheckMode { + // All parameters will be checked. + kTypeCheckAllParameters, + + // Only parameters marked as covariant or generic-covariant-impl will be + // checked. + kTypeCheckForNonDynamicallyInvokedMethod, + + // 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, + + // No parameters will be checked. + kTypeCheckForStaticFunction, + }; + + // This assumes that the reader is at a FunctionNode, + // about to read the positional parameters. + void AddPositionalAndNamedParameters(intptr_t pos, + ParameterTypeCheckMode type_check_mode); + + // This assumes that the reader is at a FunctionNode, + // about to read a parameter (i.e. VariableDeclaration). + void AddVariableDeclarationParameter(intptr_t pos, + ParameterTypeCheckMode type_check_mode); LocalVariable* MakeVariable(TokenPosition declaration_pos, TokenPosition token_pos, @@ -894,6 +932,7 @@ class StreamingFlowGraphBuilder { record_yield_positions_into_(NULL), direct_call_metadata_helper_(this), inferred_type_metadata_helper_(this), + procedure_attributes_metadata_helper_(this), metadata_scanned_(false) {} StreamingFlowGraphBuilder(TranslationHelper* translation_helper, @@ -915,6 +954,7 @@ class StreamingFlowGraphBuilder { record_yield_positions_into_(NULL), direct_call_metadata_helper_(this), inferred_type_metadata_helper_(this), + procedure_attributes_metadata_helper_(this), metadata_scanned_(false) {} StreamingFlowGraphBuilder(TranslationHelper* translation_helper, @@ -936,6 +976,7 @@ class StreamingFlowGraphBuilder { record_yield_positions_into_(NULL), direct_call_metadata_helper_(this), inferred_type_metadata_helper_(this), + procedure_attributes_metadata_helper_(this), metadata_scanned_(false) {} ~StreamingFlowGraphBuilder() { delete reader_; } @@ -1126,7 +1167,14 @@ class StreamingFlowGraphBuilder { const InferredTypeMetadata* result_type = NULL, intptr_t argument_check_bits = 0, intptr_t type_argument_check_bits = 0); - Fragment BuildArgumentTypeChecks(); + + enum TypeChecksToBuild { + kDefaultTypeChecks, + kTypeChecksForNoDynamicInvocationsTearOff + }; + + Fragment BuildArgumentTypeChecks(TypeChecksToBuild mode = kDefaultTypeChecks); + Fragment ThrowException(TokenPosition position); Fragment BooleanNegate(); Fragment TranslateInstantiatedTypeArguments( @@ -1323,12 +1371,14 @@ class StreamingFlowGraphBuilder { GrowableArray* record_yield_positions_into_; DirectCallMetadataHelper direct_call_metadata_helper_; InferredTypeMetadataHelper inferred_type_metadata_helper_; + ProcedureAttributesMetadataHelper procedure_attributes_metadata_helper_; bool metadata_scanned_; friend class ClassHelper; friend class ConstantHelper; friend class ConstructorHelper; friend class DirectCallMetadataHelper; + friend class ProcedureAttributesMetadataHelper; friend class FieldHelper; friend class FunctionNodeHelper; friend class InferredTypeMetadataHelper; diff --git a/runtime/vm/scopes.h b/runtime/vm/scopes.h index 7ed478ed0a9..7a1a51ef87c 100644 --- a/runtime/vm/scopes.h +++ b/runtime/vm/scopes.h @@ -35,6 +35,7 @@ class LocalVariable : public ZoneAllocated { is_invisible_(false), is_captured_parameter_(false), is_forced_stack_(false), + type_check_mode_(kDoTypeCheck), index_(LocalVariable::kUninitializedIndex) { ASSERT(type.IsZoneHandle() || type.IsReadOnlyHandle()); ASSERT(type.IsFinalized()); @@ -65,6 +66,24 @@ class LocalVariable : public ZoneAllocated { bool is_forced_stack() const { return is_forced_stack_; } void set_is_forced_stack() { is_forced_stack_ = true; } + enum TypeCheckMode { + kDoTypeCheck, + kSkipTypeCheck, + kTypeCheckedByCaller, + }; + + // Returns true if this local variable represents a parameter that needs type + // check when we enter the function. + bool needs_type_check() const { return type_check_mode_ == kDoTypeCheck; } + + // Returns true if this local variable represents a parameter which type is + // guaranteed by the caller. + bool was_type_checked_by_caller() const { + return type_check_mode_ == kTypeCheckedByCaller; + } + + void set_type_check_mode(TypeCheckMode mode) { type_check_mode_ = mode; } + bool HasIndex() const { return index_ != kUninitializedIndex; } int index() const { ASSERT(HasIndex()); @@ -124,6 +143,7 @@ class LocalVariable : public ZoneAllocated { bool is_invisible_; bool is_captured_parameter_; bool is_forced_stack_; + TypeCheckMode type_check_mode_; int index_; // Allocation index in words relative to frame pointer (if not // captured), or relative to the context pointer (if captured).