From ff34fd81106dbd4c2a341f5545e5f900b591ebfa Mon Sep 17 00:00:00 2001 From: Alexander Markov Date: Thu, 6 Feb 2020 19:31:32 +0000 Subject: [PATCH] [vm/aot/tfa] Tree shake write-only fields So far tree shaking was removing fields which are not used at all. This change improves tree shaking of fields so fields which are only written or used as interface targets can be removed. The following limitations apply: * Field is not removed if there is a constant object with that field, as it may impact identity of constant objects which is an observable behavior. * Field is not removed if it has a non-trivial initializer as it may have side-effects. * Late final fields are not removed, as writing such fields may have side-effect. * When field is removed, we may need to introduce an abstract getter or abstract setter if field is used as a target of an interface call. If a field was written, then setter would be non-abstract (but empty). Fixes https://github.com/dart-lang/sdk/issues/35310 Change-Id: I79c00158b8eb658081a647c5dbdecde481fddb41 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/134204 Commit-Queue: Alexander Markov Reviewed-by: Ryan Macnak Reviewed-by: Martin Kustermann --- .../transformations/type_flow/analysis.dart | 32 +++ .../lib/transformations/type_flow/calls.dart | 8 +- .../type_flow/summary_collector.dart | 8 +- .../type_flow/transformer.dart | 217 +++++++++++++++--- pkg/vm/test/common_test_utils.dart | 10 +- .../type_flow/transformer_test.dart | 11 +- .../transformer/bench_vector.dart.expect | 4 +- .../transformer/unreachable.dart.expect | 2 +- .../transformer/write_only_field.dart.expect | 5 +- .../transformer/write_only_field2.dart | 91 ++++++++ .../transformer/write_only_field2.dart.expect | 68 ++++++ .../transformer/write_only_field3_nnbd.dart | 42 ++++ .../write_only_field3_nnbd.dart.expect | 31 +++ .../tests/service/contexts_test.dart | 10 +- .../tests/service/dominator_tree_vm_test.dart | 24 ++ ...inator_tree_vm_with_double_field_test.dart | 24 ++ .../tests/service/get_instances_rpc_test.dart | 3 + .../service/get_retained_size_rpc_test.dart | 4 + .../service/get_retaining_path_rpc_test.dart | 3 + .../service/inbound_references_test.dart | 2 + .../instance_field_order_rpc_test.dart | 5 + .../tests/service/object_graph_vm_test.dart | 3 + .../tests/service/reachable_size_test.dart | 6 +- .../tests/service/regexp_function_test.dart | 3 + tests/lib_2/isolate/message3_test.dart | 4 + 25 files changed, 574 insertions(+), 46 deletions(-) create mode 100644 pkg/vm/testcases/transformations/type_flow/transformer/write_only_field2.dart create mode 100644 pkg/vm/testcases/transformations/type_flow/transformer/write_only_field2.dart.expect create mode 100644 pkg/vm/testcases/transformations/type_flow/transformer/write_only_field3_nnbd.dart create mode 100644 pkg/vm/testcases/transformations/type_flow/transformer/write_only_field3_nnbd.dart.expect diff --git a/pkg/vm/lib/transformations/type_flow/analysis.dart b/pkg/vm/lib/transformations/type_flow/analysis.dart index b41711e39a5..ec56e61948b 100644 --- a/pkg/vm/lib/transformations/type_flow/analysis.dart +++ b/pkg/vm/lib/transformations/type_flow/analysis.dart @@ -185,12 +185,17 @@ class _DirectInvocation extends _Invocation { case CallKind.PropertyGet: assertx(args.values.length == firstParamIndex); assertx(args.names.isEmpty); + fieldValue.isGetterUsed = true; return fieldValue.getValue( typeFlowAnalysis, field.isStatic ? null : args.values[0]); case CallKind.PropertySet: + case CallKind.SetFieldInConstructor: assertx(args.values.length == firstParamIndex + 1); assertx(args.names.isEmpty); + if (selector.callKind == CallKind.PropertySet) { + fieldValue.isSetterUsed = true; + } final Type setterArg = args.values[firstParamIndex]; fieldValue.setValue( setterArg, typeFlowAnalysis, field.isStatic ? null : args.receiver); @@ -200,6 +205,7 @@ class _DirectInvocation extends _Invocation { // Call via field. // TODO(alexmarkov): support function types and use inferred type // to get more precise return type. + fieldValue.isGetterUsed = true; final receiver = fieldValue.getValue( typeFlowAnalysis, field.isStatic ? null : args.values[0]); if (receiver != const EmptyType()) { @@ -766,6 +772,12 @@ class _FieldValue extends _DependencyTracker { /// Flag indicating if field initializer was executed. bool isInitialized = false; + /// Flag indicating if field getter was executed. + bool isGetterUsed = false; + + /// Flag indicating if field setter was executed. + bool isSetterUsed = false; + _FieldValue(this.field, this.typeGuardSummary, TypesBuilder typesBuilder) : staticType = typesBuilder.fromStaticType(field.type, true) { if (field.initializer == null && _isDefaultValueOfFieldObservable()) { @@ -1385,6 +1397,26 @@ class TypeFlowAnalysis implements EntryPointsListener, CallHandler { return false; } + /// Returns true if analysis found that getter corresponding to the given + /// [field] could be executed. + bool isFieldGetterUsed(Field field) { + final fieldValue = _fieldValues[field]; + if (fieldValue != null) { + return fieldValue.isGetterUsed; + } + return false; + } + + /// Returns true if analysis found that setter corresponding to the given + /// [field] could be executed. + bool isFieldSetterUsed(Field field) { + final fieldValue = _fieldValues[field]; + if (fieldValue != null) { + return fieldValue.isSetterUsed; + } + return false; + } + bool isClassAllocated(Class c) => hierarchyCache.allocatedClasses.contains(c); Call callSite(TreeNode node) => summaryCollector.callSites[node]; diff --git a/pkg/vm/lib/transformations/type_flow/calls.dart b/pkg/vm/lib/transformations/type_flow/calls.dart index d0c284ebb05..715b5c9570a 100644 --- a/pkg/vm/lib/transformations/type_flow/calls.dart +++ b/pkg/vm/lib/transformations/type_flow/calls.dart @@ -16,7 +16,8 @@ enum CallKind { Method, // x.foo(..) or foo() PropertyGet, // ... x.foo ... PropertySet, // x.foo = ... - FieldInitializer, + FieldInitializer, // run initializer of a field + SetFieldInConstructor, // foo = ... in initializer list in a constructor } /// [Selector] encapsulates the way of calling (at the call site). @@ -55,6 +56,7 @@ abstract class Selector { return member.getterType; case CallKind.PropertySet: case CallKind.FieldInitializer: + case CallKind.SetFieldInConstructor: return const BottomType(); } return null; @@ -72,7 +74,8 @@ abstract class Selector { case CallKind.PropertySet: return (member is Field) || ((member is Procedure) && member.isSetter); case CallKind.FieldInitializer: - return (member is Field); + case CallKind.SetFieldInConstructor: + return member is Field; } return false; } @@ -84,6 +87,7 @@ abstract class Selector { case CallKind.PropertyGet: return 'get '; case CallKind.PropertySet: + case CallKind.SetFieldInConstructor: return 'set '; case CallKind.FieldInitializer: return 'init '; diff --git a/pkg/vm/lib/transformations/type_flow/summary_collector.dart b/pkg/vm/lib/transformations/type_flow/summary_collector.dart index f0021f0bffe..95756120c98 100644 --- a/pkg/vm/lib/transformations/type_flow/summary_collector.dart +++ b/pkg/vm/lib/transformations/type_flow/summary_collector.dart @@ -800,6 +800,7 @@ class SummaryCollector extends RecursiveVisitor { break; case CallKind.PropertySet: + case CallKind.SetFieldInConstructor: args.add(new Type.nullableAny()); break; @@ -2071,8 +2072,11 @@ class SummaryCollector extends RecursiveVisitor { TypeExpr visitFieldInitializer(FieldInitializer node) { final value = _visit(node.value); final args = new Args([_receiver, value]); - _makeCall(node, - new DirectSelector(node.field, callKind: CallKind.PropertySet), args); + _makeCall( + node, + new DirectSelector(node.field, + callKind: CallKind.SetFieldInConstructor), + args); return null; } diff --git a/pkg/vm/lib/transformations/type_flow/transformer.dart b/pkg/vm/lib/transformations/type_flow/transformer.dart index c29fb3c05d0..5f20ca61d9c 100644 --- a/pkg/vm/lib/transformations/type_flow/transformer.dart +++ b/pkg/vm/lib/transformations/type_flow/transformer.dart @@ -67,12 +67,15 @@ Component transformComponent( final transformsStopWatch = new Stopwatch()..start(); - new TreeShaker(component, typeFlowAnalysis).transformComponent(component); + final treeShaker = new TreeShaker(component, typeFlowAnalysis); + treeShaker.transformComponent(component); - new TFADevirtualization(component, typeFlowAnalysis, hierarchy) + new TFADevirtualization( + component, typeFlowAnalysis, hierarchy, treeShaker.fieldMorpher) .visitComponent(component); - new AnnotateKernel(component, typeFlowAnalysis).visitComponent(component); + new AnnotateKernel(component, typeFlowAnalysis, treeShaker.fieldMorpher) + .visitComponent(component); transformsStopWatch.stop(); @@ -87,9 +90,10 @@ Component transformComponent( /// Devirtualization based on results of type flow analysis. class TFADevirtualization extends Devirtualization { final TypeFlowAnalysis _typeFlowAnalysis; + final FieldMorpher fieldMorpher; - TFADevirtualization( - Component component, this._typeFlowAnalysis, ClassHierarchy hierarchy) + TFADevirtualization(Component component, this._typeFlowAnalysis, + ClassHierarchy hierarchy, this.fieldMorpher) : super(_typeFlowAnalysis.environment.coreTypes, component, hierarchy); @override @@ -97,7 +101,8 @@ class TFADevirtualization extends Devirtualization { {bool setter = false}) { final callSite = _typeFlowAnalysis.callSite(node); if (callSite != null) { - final Member singleTarget = callSite.monomorphicTarget; + final Member singleTarget = fieldMorpher + .getMorphedMember(callSite.monomorphicTarget, isSetter: setter); if (singleTarget != null) { return new DirectCallMetadata( singleTarget, callSite.isNullableReceiver); @@ -110,6 +115,7 @@ class TFADevirtualization extends Devirtualization { /// Annotates kernel AST with metadata using results of type flow analysis. class AnnotateKernel extends RecursiveVisitor { final TypeFlowAnalysis _typeFlowAnalysis; + final FieldMorpher fieldMorpher; final InferredTypeMetadataRepository _inferredTypeMetadata; final UnreachableNodeMetadataRepository _unreachableNodeMetadata; final ProcedureAttributesMetadataRepository _procedureAttributesMetadata; @@ -117,7 +123,7 @@ class AnnotateKernel extends RecursiveVisitor { final Class _intClass; Constant _nullConstant; - AnnotateKernel(Component component, this._typeFlowAnalysis) + AnnotateKernel(Component component, this._typeFlowAnalysis, this.fieldMorpher) : _inferredTypeMetadata = new InferredTypeMetadataRepository(), _unreachableNodeMetadata = new UnreachableNodeMetadataRepository(), _procedureAttributesMetadata = @@ -278,7 +284,8 @@ class AnnotateKernel extends RecursiveVisitor { // TODO(alexmarkov): figure out how to pass receiver type. } - } else if (!member.isAbstract) { + } else if (!member.isAbstract && + !fieldMorpher.isExtraMemberWithReachableBody(member)) { _setUnreachable(member); } @@ -287,14 +294,15 @@ class AnnotateKernel extends RecursiveVisitor { // interface target, and table dispatch calls need selector IDs for all // interface targets. if (member.isInstanceMember) { + final original = fieldMorpher.getOriginalMember(member); final attrs = new ProcedureAttributesMetadata( methodOrSetterCalledDynamically: - _typeFlowAnalysis.isCalledDynamically(member), + _typeFlowAnalysis.isCalledDynamically(original), getterCalledDynamically: - _typeFlowAnalysis.isGetterCalledDynamically(member), - hasThisUses: _typeFlowAnalysis.isCalledViaThis(member), - hasNonThisUses: _typeFlowAnalysis.isCalledNotViaThis(member), - hasTearOffUses: _typeFlowAnalysis.isTearOffTaken(member), + _typeFlowAnalysis.isGetterCalledDynamically(original), + hasThisUses: _typeFlowAnalysis.isCalledViaThis(original), + hasNonThisUses: _typeFlowAnalysis.isCalledNotViaThis(original), + hasTearOffUses: _typeFlowAnalysis.isTearOffTaken(original), methodOrSetterSelectorId: _tableSelectorAssigner.methodOrSetterSelectorId(member), getterSelectorId: _tableSelectorAssigner.getterSelectorId(member)); @@ -393,12 +401,14 @@ class TreeShaker { final Set _usedMembers = new Set(); final Set _usedExtensions = new Set(); final Set _usedTypedefs = new Set(); + FieldMorpher fieldMorpher; _TreeShakerTypeVisitor typeVisitor; _TreeShakerConstantVisitor constantVisitor; _TreeShakerPass1 _pass1; _TreeShakerPass2 _pass2; TreeShaker(Component component, this.typeFlowAnalysis) { + fieldMorpher = new FieldMorpher(this); typeVisitor = new _TreeShakerTypeVisitor(this); constantVisitor = new _TreeShakerConstantVisitor(this, typeVisitor); _pass1 = new _TreeShakerPass1(this); @@ -417,13 +427,25 @@ class TreeShaker { bool isClassAllocated(Class c) => typeFlowAnalysis.isClassAllocated(c); bool isMemberUsed(Member m) => _usedMembers.contains(m); bool isExtensionUsed(Extension e) => _usedExtensions.contains(e); - bool isMemberBodyReachable(Member m) => typeFlowAnalysis.isMemberUsed(m); + bool isMemberBodyReachable(Member m) => + typeFlowAnalysis.isMemberUsed(m) || + fieldMorpher.isExtraMemberWithReachableBody(m); bool isFieldInitializerReachable(Field f) => typeFlowAnalysis.isFieldInitializerUsed(f); + bool isFieldGetterReachable(Field f) => typeFlowAnalysis.isFieldGetterUsed(f); + bool isFieldSetterReachable(Field f) => typeFlowAnalysis.isFieldSetterUsed(f); bool isMemberReferencedFromNativeCode(Member m) => typeFlowAnalysis.nativeCodeOracle.isMemberReferencedFromNativeCode(m); bool isTypedefUsed(Typedef t) => _usedTypedefs.contains(t); + bool retainField(Field f) => + isFieldGetterReachable(f) || + (f.initializer != null && + isFieldInitializerReachable(f) && + mayHaveSideEffects(f.initializer)) || + (f.isLate && f.isFinal) || + isMemberReferencedFromNativeCode(f); + void addClassUsedInType(Class c) { if (_classesUsedInType.add(c)) { if (kPrintDebug) { @@ -459,9 +481,15 @@ class TreeShaker { } else if (m is Procedure) { func = m.function; if (m.forwardingStubSuperTarget != null) { + m.forwardingStubSuperTarget = fieldMorpher.adjustInstanceCallTarget( + m.forwardingStubSuperTarget, + isSetter: m.isSetter); addUsedMember(m.forwardingStubSuperTarget); } if (m.forwardingStubInterfaceTarget != null) { + m.forwardingStubInterfaceTarget = fieldMorpher + .adjustInstanceCallTarget(m.forwardingStubInterfaceTarget, + isSetter: m.isSetter); addUsedMember(m.forwardingStubInterfaceTarget); } } else if (m is Constructor) { @@ -514,6 +542,86 @@ class TreeShaker { } } +class FieldMorpher { + final TreeShaker shaker; + final Set _extraMembersWithReachableBody = {}; + final Map _gettersForRemovedFields = {}; + final Map _settersForRemovedFields = {}; + final Map _removedFields = {}; + + FieldMorpher(this.shaker); + + Member _createAccessorForRemovedField(Field field, bool isSetter) { + assertx(!field.isStatic); + assertx(!shaker.retainField(field)); + Procedure accessor; + if (isSetter) { + final isAbstract = !shaker.isFieldSetterReachable(field); + final parameter = new VariableDeclaration('value', type: field.type) + ..isCovariant = field.isCovariant + ..isGenericCovariantImpl = field.isGenericCovariantImpl + ..fileOffset = field.fileOffset; + accessor = new Procedure( + field.name, + ProcedureKind.Setter, + new FunctionNode(null, + positionalParameters: [parameter], returnType: const VoidType()) + ..fileOffset = field.fileOffset, + isAbstract: isAbstract, + fileUri: field.fileUri); + if (!isAbstract) { + _extraMembersWithReachableBody.add(accessor); + } + } else { + accessor = new Procedure(field.name, ProcedureKind.Getter, + new FunctionNode(null, returnType: field.type), + isAbstract: true, fileUri: field.fileUri); + } + accessor.fileOffset = field.fileOffset; + field.enclosingClass.addMember(accessor); + _removedFields[accessor] = field; + shaker.addUsedMember(accessor); + return accessor; + } + + /// Return a replacement for an instance call target. + /// If necessary, creates a getter or setter as a replacement if target is a + /// field which is going to be removed by the tree shaker. + /// This method is used during tree shaker pass 1. + Member adjustInstanceCallTarget(Member target, {bool isSetter = false}) { + if (target is Field && !shaker.retainField(target)) { + final targets = + isSetter ? _settersForRemovedFields : _gettersForRemovedFields; + return targets[target] ??= + _createAccessorForRemovedField(target, isSetter); + } + return target; + } + + bool isExtraMemberWithReachableBody(Member member) => + _extraMembersWithReachableBody.contains(member); + + /// Return a member which replaced [target] in instance calls. + /// This method can be used after tree shaking to discover replacement. + Member getMorphedMember(Member target, {bool isSetter = false}) { + if (target == null) { + return null; + } + final targets = + isSetter ? _settersForRemovedFields : _gettersForRemovedFields; + return targets[target] ?? target; + } + + /// Return original member which was replaced by [target] in instance calls. + /// This method can be used after tree shaking. + Member getOriginalMember(Member target) { + if (target == null) { + return null; + } + return _removedFields[target] ?? target; + } +} + /// Visits Dart types and collects all classes and typedefs used in types. /// This visitor is used during pass 1 of tree shaking. It is a separate /// visitor because [Transformer] does not provide a way to traverse types. @@ -564,9 +672,10 @@ class _TreeShakerTypeVisitor extends RecursiveVisitor { /// transforms unreachable calls into 'throw' expressions. class _TreeShakerPass1 extends Transformer { final TreeShaker shaker; + final FieldMorpher fieldMorpher; Procedure _unsafeCast; - _TreeShakerPass1(this.shaker); + _TreeShakerPass1(this.shaker) : fieldMorpher = shaker.fieldMorpher; void transform(Component component) { component.transformChildren(this); @@ -609,7 +718,9 @@ class _TreeShakerPass1 extends Transformer { 'Attempt to execute code removed by Dart AOT compiler (TFA)')); } for (var arg in args.reversed) { - node = new Let(new VariableDeclaration(null, initializer: arg), node); + if (mayHaveSideEffects(arg)) { + node = new Let(new VariableDeclaration(null, initializer: arg), node); + } } Statistics.callsDropped++; return node; @@ -686,7 +797,7 @@ class _TreeShakerPass1 extends Transformer { @override TreeNode visitField(Field node) { - if (shaker.isMemberBodyReachable(node)) { + if (shaker.retainField(node)) { if (kPrintTrace) { tracePrint("Visiting $node"); } @@ -698,12 +809,10 @@ class _TreeShakerPass1 extends Transformer { node.initializer = _makeUnreachableCall([])..parent = node; } } - } else if (shaker.isMemberReferencedFromNativeCode(node)) { - // Preserve members referenced from native code to satisfy lookups, even - // if they are not reachable. An instance member could be added via - // native code entry point but still unreachable if no instances of - // its enclosing class are allocated. - shaker.addUsedMember(node); + } else if (shaker.isFieldSetterReachable(node) && !node.isStatic) { + // Make sure setter is created to replace the field even if field is not + // used as an instance call target. + fieldMorpher.adjustInstanceCallTarget(node, isSetter: true); } return node; } @@ -715,6 +824,8 @@ class _TreeShakerPass1 extends Transformer { return _makeUnreachableCall( _flattenArguments(node.arguments, receiver: node.receiver)); } else { + node.interfaceTarget = + fieldMorpher.adjustInstanceCallTarget(node.interfaceTarget); if (node.interfaceTarget != null) { shaker.addUsedMember(node.interfaceTarget); } @@ -728,6 +839,8 @@ class _TreeShakerPass1 extends Transformer { if (_isUnreachable(node)) { return _makeUnreachableCall([node.receiver]); } else { + node.interfaceTarget = + fieldMorpher.adjustInstanceCallTarget(node.interfaceTarget); if (node.interfaceTarget != null) { shaker.addUsedMember(node.interfaceTarget); } @@ -741,6 +854,8 @@ class _TreeShakerPass1 extends Transformer { if (_isUnreachable(node)) { return _makeUnreachableCall([node.receiver, node.value]); } else { + node.interfaceTarget = fieldMorpher + .adjustInstanceCallTarget(node.interfaceTarget, isSetter: true); if (node.interfaceTarget != null) { shaker.addUsedMember(node.interfaceTarget); } @@ -754,6 +869,8 @@ class _TreeShakerPass1 extends Transformer { if (_isUnreachable(node)) { return _makeUnreachableCall(_flattenArguments(node.arguments)); } else { + node.interfaceTarget = + fieldMorpher.adjustInstanceCallTarget(node.interfaceTarget); if (node.interfaceTarget != null) { shaker.addUsedMember(node.interfaceTarget); } @@ -767,6 +884,8 @@ class _TreeShakerPass1 extends Transformer { if (_isUnreachable(node)) { return _makeUnreachableCall([]); } else { + node.interfaceTarget = + fieldMorpher.adjustInstanceCallTarget(node.interfaceTarget); if (node.interfaceTarget != null) { shaker.addUsedMember(node.interfaceTarget); } @@ -780,6 +899,8 @@ class _TreeShakerPass1 extends Transformer { if (_isUnreachable(node)) { return _makeUnreachableCall([node.value]); } else { + node.interfaceTarget = fieldMorpher + .adjustInstanceCallTarget(node.interfaceTarget, isSetter: true); if (node.interfaceTarget != null) { shaker.addUsedMember(node.interfaceTarget); } @@ -825,7 +946,11 @@ class _TreeShakerPass1 extends Transformer { if (_isUnreachable(node)) { return _makeUnreachableCall([node.value]); } else { - assertx(shaker.isMemberBodyReachable(node.target), details: node.target); + final target = node.target; + assertx(shaker.isMemberBodyReachable(target), details: node); + if (target is Field && !shaker.retainField(target)) { + return node.value; + } return node; } } @@ -837,7 +962,7 @@ class _TreeShakerPass1 extends Transformer { return _makeUnreachableCall( _flattenArguments(node.arguments, receiver: node.receiver)); } else { - assertx(shaker.isMemberBodyReachable(node.target), details: node.target); + assertx(shaker.isMemberBodyReachable(node.target), details: node); return node; } } @@ -848,7 +973,10 @@ class _TreeShakerPass1 extends Transformer { if (_isUnreachable(node)) { return _makeUnreachableCall([node.receiver]); } else { - assertx(shaker.isMemberBodyReachable(node.target), details: node.target); + final target = node.target; + assertx(shaker.isMemberBodyReachable(target), details: node); + assertx(target is! Field || shaker.isFieldGetterReachable(target), + details: node); return node; } } @@ -859,7 +987,9 @@ class _TreeShakerPass1 extends Transformer { if (_isUnreachable(node)) { return _makeUnreachableCall([node.receiver, node.value]); } else { - assertx(shaker.isMemberBodyReachable(node.target), details: node.target); + assertx(shaker.isMemberBodyReachable(node.target), details: node); + node.target = + fieldMorpher.adjustInstanceCallTarget(node.target, isSetter: true); return node; } } @@ -902,12 +1032,20 @@ class _TreeShakerPass1 extends Transformer { } @override - visitFieldInitializer(FieldInitializer node) { + TreeNode visitFieldInitializer(FieldInitializer node) { node.transformChildren(this); if (_isUnreachable(node)) { return _makeUnreachableInitializer([node.value]); } else { assertx(shaker.isMemberBodyReachable(node.field), details: node.field); + if (!shaker.retainField(node.field)) { + if (mayHaveSideEffects(node.value)) { + return LocalInitializer( + VariableDeclaration(null, initializer: node.value)); + } else { + return null; + } + } return node; } } @@ -1180,3 +1318,26 @@ class _TreeShakerConstantVisitor extends ConstantVisitor { constant.type.accept(typeVisitor); } } + +bool mayHaveSideEffects(Expression node) { + if (node is BasicLiteral || + node is ConstantExpression || + node is ThisExpression) { + return false; + } + if (node is VariableGet && !node.variable.isLate) { + return false; + } + if (node is StaticGet) { + final target = node.target; + if (target is Field && !target.isLate) { + final initializer = target.initializer; + if (initializer == null || + initializer is BasicLiteral || + initializer is ConstantExpression) { + return false; + } + } + } + return true; +} diff --git a/pkg/vm/test/common_test_utils.dart b/pkg/vm/test/common_test_utils.dart index afa5e6bb0ac..e95b52b60b9 100644 --- a/pkg/vm/test/common_test_utils.dart +++ b/pkg/vm/test/common_test_utils.dart @@ -10,7 +10,9 @@ import 'package:front_end/src/api_unstable/vm.dart' CompilerOptions, DiagnosticMessage, computePlatformBinariesLocation, - kernelForProgram; + kernelForProgram, + parseExperimentalArguments, + parseExperimentalFlags; import 'package:kernel/ast.dart'; import 'package:kernel/text/ast_to_text.dart' show Printer; import 'package:kernel/binary/ast_to_binary.dart' show BinaryPrinter; @@ -35,6 +37,7 @@ class TestingVmTarget extends VmTarget { Future compileTestCaseToKernelProgram(Uri sourceUri, {Target target, bool enableSuperMixins = false, + List experimentalFlags, Map environmentDefines}) async { final platformKernel = computePlatformBinariesLocation().resolve('vm_platform_strong.dill'); @@ -45,6 +48,11 @@ Future compileTestCaseToKernelProgram(Uri sourceUri, ..target = target ..linkedDependencies = [platformKernel] ..environmentDefines = environmentDefines + ..experimentalFlags = + parseExperimentalFlags(parseExperimentalArguments(experimentalFlags), + onError: (String message) { + throw message; + }) ..onDiagnostic = (DiagnosticMessage message) { fail("Compilation error: ${message.plainTextFormatted.join('\n')}"); }; diff --git a/pkg/vm/test/transformations/type_flow/transformer_test.dart b/pkg/vm/test/transformations/type_flow/transformer_test.dart index b51cbd34500..d26a8ec307c 100644 --- a/pkg/vm/test/transformations/type_flow/transformer_test.dart +++ b/pkg/vm/test/transformations/type_flow/transformer_test.dart @@ -18,10 +18,10 @@ import '../../common_test_utils.dart'; final String pkgVmDir = Platform.script.resolve('../../..').toFilePath(); -runTestCase(Uri source) async { +runTestCase(Uri source, List experimentalFlags) async { final target = new TestingVmTarget(new TargetFlags()); - Component component = - await compileTestCaseToKernelProgram(source, target: target); + Component component = await compileTestCaseToKernelProgram(source, + target: target, experimentalFlags: experimentalFlags); final coreTypes = new CoreTypes(component); @@ -44,7 +44,10 @@ main() { .listSync(recursive: true, followLinks: false) .reversed) { if (entry.path.endsWith(".dart")) { - test(entry.path, () => runTestCase(entry.uri)); + final List experimentalFlags = [ + if (entry.path.endsWith("_nnbd.dart")) 'non-nullable', + ]; + test(entry.path, () => runTestCase(entry.uri, experimentalFlags)); } } }); diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/bench_vector.dart.expect b/pkg/vm/testcases/transformations/type_flow/transformer/bench_vector.dart.expect index 09996bb9812..db3abec3193 100644 --- a/pkg/vm/testcases/transformations/type_flow/transformer/bench_vector.dart.expect +++ b/pkg/vm/testcases/transformations/type_flow/transformer/bench_vector.dart.expect @@ -15,7 +15,7 @@ class _Vector extends core::Object { [@vm.procedure-attributes.metadata=getterCalledDynamically:false,hasTearOffUses:false,methodOrSetterSelectorId:1] operator []([@vm.inferred-type.metadata=!] core::int* i) → core::double* return [@vm.direct-call.metadata=dart.typed_data::_Float64List::[]] [@vm.inferred-type.metadata=dart.core::_Double (skip check)] [@vm.direct-call.metadata=#lib::_Vector::_elements] [@vm.inferred-type.metadata=dart.typed_data::_Float64List] this.{self::_Vector::_elements}.{core::List::[]}([@vm.direct-call.metadata=dart.core::_IntegerImplementation::+] [@vm.inferred-type.metadata=int (skip check)] i.{core::num::+}([@vm.direct-call.metadata=#lib::_Vector::_offset] [@vm.inferred-type.metadata=dart.core::_Smi (value: 0)] this.{self::_Vector::_offset})); [@vm.procedure-attributes.metadata=getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:2] operator []=([@vm.inferred-type.metadata=dart.core::_OneByteString] core::int* i, core::double* value) → void { - let dynamic #t1 = [@vm.direct-call.metadata=#lib::_Vector::_elements] [@vm.inferred-type.metadata=dart.typed_data::_Float64List] this.{self::_Vector::_elements} in let dynamic #t2 = i in let dynamic #t3 = [@vm.direct-call.metadata=#lib::_Vector::_offset] [@vm.inferred-type.metadata=dart.core::_Smi (value: 0)] this.{self::_Vector::_offset} in throw "Attempt to execute code removed by Dart AOT compiler (TFA)"; + let dynamic #t1 = [@vm.direct-call.metadata=#lib::_Vector::_elements] [@vm.inferred-type.metadata=dart.typed_data::_Float64List] this.{self::_Vector::_elements} in let dynamic #t2 = [@vm.direct-call.metadata=#lib::_Vector::_offset] [@vm.inferred-type.metadata=dart.core::_Smi (value: 0)] this.{self::_Vector::_offset} in throw "Attempt to execute code removed by Dart AOT compiler (TFA)"; } [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:3] operator *([@vm.inferred-type.metadata=#lib::_Vector?] self::_Vector* a) → core::double* { core::double* result = 0.0; @@ -27,7 +27,7 @@ class _Vector extends core::Object { [@vm.inferred-type.metadata=#lib::_Vector?]static field self::_Vector* v = new self::_Vector::•(10); [@vm.inferred-type.metadata=dart.core::_Double?]static field core::double* x = 0.0; static method main(core::List* args) → dynamic { - core::Stopwatch* timer = let final core::Stopwatch* #t4 = new core::Stopwatch::•() in let final void #t5 = [@vm.direct-call.metadata=dart.core::Stopwatch::start] [@vm.inferred-type.metadata=!? (skip check)] #t4.{core::Stopwatch::start}() in #t4; + core::Stopwatch* timer = let final core::Stopwatch* #t3 = new core::Stopwatch::•() in let final void #t4 = [@vm.direct-call.metadata=dart.core::Stopwatch::start] [@vm.inferred-type.metadata=!? (skip check)] #t3.{core::Stopwatch::start}() in #t3; for (core::int* i = 0; [@vm.direct-call.metadata=dart.core::_IntegerImplementation::<] [@vm.inferred-type.metadata=dart.core::bool (skip check)] i.{core::num::<}(100000000); i = [@vm.direct-call.metadata=dart.core::_IntegerImplementation::+] [@vm.inferred-type.metadata=int (skip check)] i.{core::num::+}(1)) { self::x = [@vm.direct-call.metadata=dart.core::_Double::+??] [@vm.inferred-type.metadata=dart.core::_Double (skip check)] [@vm.inferred-type.metadata=dart.core::_Double?] self::x.{core::double::+}([@vm.direct-call.metadata=#lib::_Vector::*??] [@vm.inferred-type.metadata=dart.core::_Double (skip check)] [@vm.inferred-type.metadata=#lib::_Vector?] self::v.{self::_Vector::*}([@vm.inferred-type.metadata=#lib::_Vector?] self::v)); } diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/unreachable.dart.expect b/pkg/vm/testcases/transformations/type_flow/transformer/unreachable.dart.expect index 47a15c43a76..0ed46d3ef4c 100644 --- a/pkg/vm/testcases/transformations/type_flow/transformer/unreachable.dart.expect +++ b/pkg/vm/testcases/transformations/type_flow/transformer/unreachable.dart.expect @@ -14,7 +14,7 @@ class B extends core::Object implements self::I { [@vm.inferred-type.metadata=#lib::B?]static field self::I* ii = new self::B::•(); static method bar([@vm.inferred-type.metadata=#lib::B?] self::I* i) → void { if(i is self::A*) { - let dynamic #t1 = i{self::A*} in let dynamic #t2 = 42 in throw "Attempt to execute code removed by Dart AOT compiler (TFA)"; + throw "Attempt to execute code removed by Dart AOT compiler (TFA)"; } } static method main(core::List* args) → dynamic { diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/write_only_field.dart.expect b/pkg/vm/testcases/transformations/type_flow/transformer/write_only_field.dart.expect index 0e5a825089d..7acac49570a 100644 --- a/pkg/vm/testcases/transformations/type_flow/transformer/write_only_field.dart.expect +++ b/pkg/vm/testcases/transformations/type_flow/transformer/write_only_field.dart.expect @@ -2,8 +2,6 @@ library #lib; import self as self; import "dart:core" as core; -abstract class A extends core::Object { -} class B extends core::Object { constructor •() → self::B* : super core::Object::•() { @@ -16,8 +14,7 @@ class C extends core::Object { : super core::Object::•() ; } -[@vm.inferred-type.metadata=dart.core::Null? (value: null)]static field self::A* field = throw "Attempt to execute code removed by Dart AOT compiler (TFA)"; static method main() → void { - self::field = null; + null; [@vm.direct-call.metadata=#lib::C::instanceField] new self::C::•().{self::C::instanceField} = null; } diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/write_only_field2.dart b/pkg/vm/testcases/transformations/type_flow/transformer/write_only_field2.dart new file mode 100644 index 00000000000..af6a9b8d515 --- /dev/null +++ b/pkg/vm/testcases/transformations/type_flow/transformer/write_only_field2.dart @@ -0,0 +1,91 @@ +// Copyright (c) 2020, 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. + +// Test for tree shaking of write-only fields. + +import "package:expect/expect.dart"; + +foo() {} + +class A { + // Should be removed. + var unused1; + + // Should be removed. + var unused2 = 42; + + // Not removed due to a non-trivial initializer. + var unused3 = foo(); +} + +class B { + // Should be removed. + var unused4; + + // Should be removed. + var unused5; + + B(this.unused4) : unused5 = foo(); +} + +class C { + // Should be replaced with setter. + T bar; +} + +class D implements C { + // Should be replaced with setter. + int bar; +} + +class E { + // Should be replaced with getter. + final int bar; + + E(this.bar); +} + +class F implements E { + int get bar => 42; +} + +class G { + // Not removed because used in a constant. + final int bazz; + + const G(this.bazz); +} + +class H { + // Should be replaced with setter. + int unused6; +} + +class I extends H { + foo() { + super.unused6 = 3; + } +} + +// Should be removed. +int unusedStatic7 = foo(); + +void main() { + new A(); + new B('hi'); + + C c = new D(); + Expect.throws(() { + c.bar = 3.14; + }); + + E e = new F(); + Expect.equals(42, e.bar); + + Expect.isTrue(!identical(const G(1), const G(2))); + + new I().foo(); + + unusedStatic7 = 5; +} diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/write_only_field2.dart.expect b/pkg/vm/testcases/transformations/type_flow/transformer/write_only_field2.dart.expect new file mode 100644 index 00000000000..9d81be01cfe --- /dev/null +++ b/pkg/vm/testcases/transformations/type_flow/transformer/write_only_field2.dart.expect @@ -0,0 +1,68 @@ +library #lib; +import self as self; +import "dart:core" as core; +import "package:expect/expect.dart" as exp; + +import "package:expect/expect.dart"; + +class A extends core::Object { +[@vm.inferred-type.metadata=dart.core::Null? (value: null)] [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:1,getterSelectorId:2] field dynamic unused3 = [@vm.inferred-type.metadata=dart.core::Null? (value: null)] self::foo(); + synthetic constructor •() → self::A* + : super core::Object::•() + ; +} +class B extends core::Object { + constructor •([@vm.inferred-type.metadata=dart.core::_OneByteString (value: hi)] dynamic unused4) → self::B* + : dynamic #t1 = [@vm.inferred-type.metadata=dart.core::Null? (value: null)] self::foo(), super core::Object::•() + ; +} +abstract class C extends core::Object { +[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:3] abstract set bar(generic-covariant-impl self::C::T* value) → void; +} +class D extends core::Object implements self::C { + synthetic constructor •() → self::D* + : super core::Object::•() + ; +[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:3] set bar(generic-covariant-impl core::int* value) → void; +} +abstract class E extends core::Object { +[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,getterSelectorId:4] abstract get bar() → core::int*; +} +class F extends core::Object implements self::E { + synthetic constructor •() → self::F* + : super core::Object::•() + ; +[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,getterSelectorId:4] get bar() → core::int* + return 42; +} +class G extends core::Object /*hasConstConstructor*/ { +[@vm.inferred-type.metadata=dart.core::_Smi] [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:5,getterSelectorId:6] final field core::int* bazz; +} +abstract class H extends core::Object { + synthetic constructor •() → self::H* + : super core::Object::•() + ; +[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasNonThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:7] set unused6(core::int* value) → void; +} +class I extends self::H { + synthetic constructor •() → self::I* + : super self::H::•() + ; +[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:8,getterSelectorId:9] method foo() → dynamic { + super.{self::H::unused6} = 3; + } +} +static method foo() → dynamic {} +static method main() → void { + new self::A::•(); + new self::B::•("hi"); + self::C* c = new self::D::•(); + exp::Expect::throws(() → core::Null? { + [@vm.call-site-attributes.metadata=receiverType:#lib::C*] [@vm.direct-call.metadata=#lib::D::bar] c.{self::C::bar} = 3.14; + }); + self::E* e = new self::F::•(); + exp::Expect::equals(42, [@vm.direct-call.metadata=#lib::F::bar] [@vm.inferred-type.metadata=dart.core::_Smi (value: 42)] e.{self::E::bar}); + exp::Expect::isTrue(![@vm.inferred-type.metadata=dart.core::bool] core::identical(#C2, #C4)); + [@vm.direct-call.metadata=#lib::I::foo] [@vm.inferred-type.metadata=!? (skip check)] new self::I::•().{self::I::foo}(); + 5; +} diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/write_only_field3_nnbd.dart b/pkg/vm/testcases/transformations/type_flow/transformer/write_only_field3_nnbd.dart new file mode 100644 index 00000000000..b368917ffb9 --- /dev/null +++ b/pkg/vm/testcases/transformations/type_flow/transformer/write_only_field3_nnbd.dart @@ -0,0 +1,42 @@ +// Copyright (c) 2020, 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. + +// Test for tree shaking of write-only late fields. +// This test requires non-nullable experiment. + +import "package:expect/expect.dart"; + +foo() {} + +class A { + // Should be replaced with setter. + late int x; + + use() { + x = 3; + } +} + +class B { + // Should be retained. + late final int x; + + use() { + x = 3; + } +} + +// Should be removed. +late int staticLateA; + +// Should be retained. +late final int staticLateB; + +void main() { + new A().use(); + new B().use(); + + staticLateA = 4; + staticLateB = 4; +} diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/write_only_field3_nnbd.dart.expect b/pkg/vm/testcases/transformations/type_flow/transformer/write_only_field3_nnbd.dart.expect new file mode 100644 index 00000000000..b2e47a86388 --- /dev/null +++ b/pkg/vm/testcases/transformations/type_flow/transformer/write_only_field3_nnbd.dart.expect @@ -0,0 +1,31 @@ +library #lib /*isNonNullableByDefault*/; +import self as self; +import "dart:core" as core; + +import "package:expect/expect.dart"; + +class A extends core::Object { + synthetic constructor •() → self::A + : super core::Object::•() + ; +[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:1,getterSelectorId:2] method use() → dynamic { + [@vm.direct-call.metadata=#lib::A::x] this.{self::A::x} = 3; + } +[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasNonThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:3] set /*isNullableByDefault*/ x(core::int value) → void; +} +class B extends core::Object { +[@vm.unreachable.metadata=] [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:3,getterSelectorId:4] late final field core::int x; + synthetic constructor •() → self::B + : super core::Object::•() + ; +[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:1,getterSelectorId:2] method use() → dynamic { + this.{self::B::x} = 3; + } +} +[@vm.inferred-type.metadata=dart.core::_Smi?]late static final field core::int staticLateB; +static method main() → void { + [@vm.direct-call.metadata=#lib::A::use] [@vm.inferred-type.metadata=!? (skip check)] new self::A::•().{self::A::use}(); + [@vm.direct-call.metadata=#lib::B::use] [@vm.inferred-type.metadata=!? (skip check)] new self::B::•().{self::B::use}(); + 4; + self::staticLateB = 4; +} diff --git a/runtime/observatory/tests/service/contexts_test.dart b/runtime/observatory/tests/service/contexts_test.dart index ecc9b44c297..a857b5a2dd3 100644 --- a/runtime/observatory/tests/service/contexts_test.dart +++ b/runtime/observatory/tests/service/contexts_test.dart @@ -8,7 +8,15 @@ import 'package:observatory/service_io.dart'; import 'package:unittest/unittest.dart'; import 'test_helper.dart'; -var cleanBlock, copyingBlock, fullBlock, fullBlockWithChain; +// Make sure these variables are not removed by the tree shaker. +@pragma("vm:entry-point") +var cleanBlock; +@pragma("vm:entry-point") +var copyingBlock; +@pragma("vm:entry-point") +var fullBlock; +@pragma("vm:entry-point") +var fullBlockWithChain; Function genCleanBlock() { block(x) => x; diff --git a/runtime/observatory/tests/service/dominator_tree_vm_test.dart b/runtime/observatory/tests/service/dominator_tree_vm_test.dart index be63548c45c..c136a911fb9 100644 --- a/runtime/observatory/tests/service/dominator_tree_vm_test.dart +++ b/runtime/observatory/tests/service/dominator_tree_vm_test.dart @@ -12,62 +12,86 @@ import 'test_helper.dart'; // small example from [Lenguaer & Tarjan 1979] class R { + // All fields are marked with @pragma("vm:entry-point") + // in order to make sure they are not removed by the tree shaker + // even though they are never read. + @pragma("vm:entry-point") var x; + @pragma("vm:entry-point") var y; + @pragma("vm:entry-point") var z; } class A { + @pragma("vm:entry-point") var x; } class B { + @pragma("vm:entry-point") var x; + @pragma("vm:entry-point") var y; + @pragma("vm:entry-point") var z; } class C { + @pragma("vm:entry-point") var x; + @pragma("vm:entry-point") var y; } class D { + @pragma("vm:entry-point") var x; } class E { + @pragma("vm:entry-point") var x; } class F { + @pragma("vm:entry-point") var x; } class G { + @pragma("vm:entry-point") var x; + @pragma("vm:entry-point") var y; } class H { + @pragma("vm:entry-point") var x; + @pragma("vm:entry-point") var y; } class I { + @pragma("vm:entry-point") var x; } class J { + @pragma("vm:entry-point") var x; } class K { + @pragma("vm:entry-point") var x; + @pragma("vm:entry-point") var y; } class L { + @pragma("vm:entry-point") var x; } diff --git a/runtime/observatory/tests/service/dominator_tree_vm_with_double_field_test.dart b/runtime/observatory/tests/service/dominator_tree_vm_with_double_field_test.dart index 9edaf233a88..83bed8987b1 100644 --- a/runtime/observatory/tests/service/dominator_tree_vm_with_double_field_test.dart +++ b/runtime/observatory/tests/service/dominator_tree_vm_with_double_field_test.dart @@ -21,62 +21,86 @@ double getDoubleWithHeapObjectTag() { // small example from [Lenguaer & Tarjan 1979] class R { final double fld = getDoubleWithHeapObjectTag(); + // Fields are marked with @pragma("vm:entry-point") + // in order to make sure they are not removed by the tree shaker + // even though they are never read. + @pragma("vm:entry-point") var x; + @pragma("vm:entry-point") var y; + @pragma("vm:entry-point") var z; } class A { + @pragma("vm:entry-point") var x; } class B { + @pragma("vm:entry-point") var x; + @pragma("vm:entry-point") var y; + @pragma("vm:entry-point") var z; } class C { + @pragma("vm:entry-point") var x; + @pragma("vm:entry-point") var y; } class D { + @pragma("vm:entry-point") var x; } class E { + @pragma("vm:entry-point") var x; } class F { + @pragma("vm:entry-point") var x; } class G { + @pragma("vm:entry-point") var x; + @pragma("vm:entry-point") var y; } class H { + @pragma("vm:entry-point") var x; + @pragma("vm:entry-point") var y; } class I { + @pragma("vm:entry-point") var x; } class J { + @pragma("vm:entry-point") var x; } class K { + @pragma("vm:entry-point") var x; + @pragma("vm:entry-point") var y; } class L { + @pragma("vm:entry-point") var x; } diff --git a/runtime/observatory/tests/service/get_instances_rpc_test.dart b/runtime/observatory/tests/service/get_instances_rpc_test.dart index 8479483bd1d..e1ddebc0755 100644 --- a/runtime/observatory/tests/service/get_instances_rpc_test.dart +++ b/runtime/observatory/tests/service/get_instances_rpc_test.dart @@ -9,7 +9,10 @@ import 'test_helper.dart'; class _TestClass { _TestClass(this.x, this.y); + // Make sure these fields are not removed by the tree shaker. + @pragma("vm:entry-point") var x; + @pragma("vm:entry-point") var y; } diff --git a/runtime/observatory/tests/service/get_retained_size_rpc_test.dart b/runtime/observatory/tests/service/get_retained_size_rpc_test.dart index 318c5116295..4db0fddcf16 100644 --- a/runtime/observatory/tests/service/get_retained_size_rpc_test.dart +++ b/runtime/observatory/tests/service/get_retained_size_rpc_test.dart @@ -9,10 +9,14 @@ import 'test_helper.dart'; class _TestClass { _TestClass(this.x, this.y); + // Make sure these fields are not removed by the tree shaker. + @pragma("vm:entry-point") var x; + @pragma("vm:entry-point") var y; } +@pragma("vm:entry-point") var myVar; @pragma("vm:entry-point") diff --git a/runtime/observatory/tests/service/get_retaining_path_rpc_test.dart b/runtime/observatory/tests/service/get_retaining_path_rpc_test.dart index e9f6e55a81d..50e720abd09 100644 --- a/runtime/observatory/tests/service/get_retaining_path_rpc_test.dart +++ b/runtime/observatory/tests/service/get_retaining_path_rpc_test.dart @@ -9,7 +9,10 @@ import 'test_helper.dart'; class _TestClass { _TestClass(); + // Make sure these fields are not removed by the tree shaker. + @pragma("vm:entry-point") var x; + @pragma("vm:entry-point") var y; } diff --git a/runtime/observatory/tests/service/inbound_references_test.dart b/runtime/observatory/tests/service/inbound_references_test.dart index fddc8ba7aaf..995935e8e7d 100644 --- a/runtime/observatory/tests/service/inbound_references_test.dart +++ b/runtime/observatory/tests/service/inbound_references_test.dart @@ -9,6 +9,8 @@ import 'package:unittest/unittest.dart'; import 'test_helper.dart'; class Node { + // Make sure this field is not removed by the tree shaker. + @pragma("vm:entry-point") var edge; } diff --git a/runtime/observatory/tests/service/instance_field_order_rpc_test.dart b/runtime/observatory/tests/service/instance_field_order_rpc_test.dart index c531ad6be38..978f0dc6352 100644 --- a/runtime/observatory/tests/service/instance_field_order_rpc_test.dart +++ b/runtime/observatory/tests/service/instance_field_order_rpc_test.dart @@ -9,12 +9,17 @@ import 'package:unittest/unittest.dart'; import 'test_helper.dart'; class Super { + // Make sure these fields are not removed by the tree shaker. + @pragma("vm:entry-point") var z = 1; + @pragma("vm:entry-point") var y = 2; } class Sub extends Super { + @pragma("vm:entry-point") var y = 3; + @pragma("vm:entry-point") var x = 4; } diff --git a/runtime/observatory/tests/service/object_graph_vm_test.dart b/runtime/observatory/tests/service/object_graph_vm_test.dart index 669d096eccf..b29ada54d4c 100644 --- a/runtime/observatory/tests/service/object_graph_vm_test.dart +++ b/runtime/observatory/tests/service/object_graph_vm_test.dart @@ -9,7 +9,10 @@ import 'package:unittest/unittest.dart'; import 'test_helper.dart'; class Foo { + // Make sure these fields are not removed by the tree shaker. + @pragma("vm:entry-point") dynamic left; + @pragma("vm:entry-point") dynamic right; } diff --git a/runtime/observatory/tests/service/reachable_size_test.dart b/runtime/observatory/tests/service/reachable_size_test.dart index 800ebb26434..c22b35f5bea 100644 --- a/runtime/observatory/tests/service/reachable_size_test.dart +++ b/runtime/observatory/tests/service/reachable_size_test.dart @@ -9,7 +9,11 @@ import 'test_helper.dart'; import 'service_test_common.dart'; class Pair { - var x, y; + // Make sure these fields are not removed by the tree shaker. + @pragma("vm:entry-point") + var x; + @pragma("vm:entry-point") + var y; } var p1; diff --git a/runtime/observatory/tests/service/regexp_function_test.dart b/runtime/observatory/tests/service/regexp_function_test.dart index 6fb09bc004b..c16ee1b974b 100644 --- a/runtime/observatory/tests/service/regexp_function_test.dart +++ b/runtime/observatory/tests/service/regexp_function_test.dart @@ -8,7 +8,10 @@ import 'package:observatory/service_io.dart'; import 'package:unittest/unittest.dart'; import 'test_helper.dart'; +// Make sure these variables are not removed by the tree shaker. +@pragma("vm:entry-point") var regex0; +@pragma("vm:entry-point") var regex; void script() { diff --git a/tests/lib_2/isolate/message3_test.dart b/tests/lib_2/isolate/message3_test.dart index aa3166cb875..6536b36ed3a 100644 --- a/tests/lib_2/isolate/message3_test.dart +++ b/tests/lib_2/isolate/message3_test.dart @@ -60,7 +60,11 @@ class D extends C with M { } class E { + // Make sure E.fun is not removed by the tree shaker, as this test verifies + // that an object with a tear-off in E.fun cannot be sent. + @pragma("vm:entry-point") Function fun; + E(this.fun); static fooFun() => 499;