Revert "[vm/aot/tfa] Tree shake write-only fields"

This reverts commit ff34fd8110.

Reason: crash on the internal app
Issue: https://github.com/flutter/flutter/issues/50745

Change-Id: Ifcfbb38fa04d27558f9e78ca6b2a8637693c7d76
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/135794
Reviewed-by: Alexander Aprelev <aam@google.com>
Commit-Queue: Alexander Markov <alexmarkov@google.com>
This commit is contained in:
Alexander Markov 2020-02-14 02:04:01 +00:00 committed by commit-bot@chromium.org
parent 989180af3e
commit 8ac7f6bce7
25 changed files with 46 additions and 574 deletions

View file

@ -191,17 +191,12 @@ 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);
@ -211,7 +206,6 @@ 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()) {
@ -778,12 +772,6 @@ 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()) {
@ -1403,26 +1391,6 @@ 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];

View file

@ -16,8 +16,7 @@ enum CallKind {
Method, // x.foo(..) or foo()
PropertyGet, // ... x.foo ...
PropertySet, // x.foo = ...
FieldInitializer, // run initializer of a field
SetFieldInConstructor, // foo = ... in initializer list in a constructor
FieldInitializer,
}
/// [Selector] encapsulates the way of calling (at the call site).
@ -56,7 +55,6 @@ abstract class Selector {
return member.getterType;
case CallKind.PropertySet:
case CallKind.FieldInitializer:
case CallKind.SetFieldInConstructor:
return const BottomType();
}
return null;
@ -74,8 +72,7 @@ abstract class Selector {
case CallKind.PropertySet:
return (member is Field) || ((member is Procedure) && member.isSetter);
case CallKind.FieldInitializer:
case CallKind.SetFieldInConstructor:
return member is Field;
return (member is Field);
}
return false;
}
@ -87,7 +84,6 @@ abstract class Selector {
case CallKind.PropertyGet:
return 'get ';
case CallKind.PropertySet:
case CallKind.SetFieldInConstructor:
return 'set ';
case CallKind.FieldInitializer:
return 'init ';

View file

@ -800,7 +800,6 @@ class SummaryCollector extends RecursiveVisitor<TypeExpr> {
break;
case CallKind.PropertySet:
case CallKind.SetFieldInConstructor:
args.add(new Type.nullableAny());
break;
@ -2072,11 +2071,8 @@ class SummaryCollector extends RecursiveVisitor<TypeExpr> {
TypeExpr visitFieldInitializer(FieldInitializer node) {
final value = _visit(node.value);
final args = new Args<TypeExpr>([_receiver, value]);
_makeCall(
node,
new DirectSelector(node.field,
callKind: CallKind.SetFieldInConstructor),
args);
_makeCall(node,
new DirectSelector(node.field, callKind: CallKind.PropertySet), args);
return null;
}

View file

@ -68,15 +68,12 @@ Component transformComponent(
final transformsStopWatch = new Stopwatch()..start();
final treeShaker = new TreeShaker(component, typeFlowAnalysis);
treeShaker.transformComponent(component);
new TreeShaker(component, typeFlowAnalysis).transformComponent(component);
new TFADevirtualization(
component, typeFlowAnalysis, hierarchy, treeShaker.fieldMorpher)
new TFADevirtualization(component, typeFlowAnalysis, hierarchy)
.visitComponent(component);
new AnnotateKernel(component, typeFlowAnalysis, treeShaker.fieldMorpher)
.visitComponent(component);
new AnnotateKernel(component, typeFlowAnalysis).visitComponent(component);
transformsStopWatch.stop();
@ -91,10 +88,9 @@ 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, this.fieldMorpher)
TFADevirtualization(
Component component, this._typeFlowAnalysis, ClassHierarchy hierarchy)
: super(_typeFlowAnalysis.environment.coreTypes, component, hierarchy);
@override
@ -102,8 +98,7 @@ class TFADevirtualization extends Devirtualization {
{bool setter = false}) {
final callSite = _typeFlowAnalysis.callSite(node);
if (callSite != null) {
final Member singleTarget = fieldMorpher
.getMorphedMember(callSite.monomorphicTarget, isSetter: setter);
final Member singleTarget = callSite.monomorphicTarget;
if (singleTarget != null) {
return new DirectCallMetadata(
singleTarget, callSite.isNullableReceiver);
@ -116,7 +111,6 @@ class TFADevirtualization extends Devirtualization {
/// Annotates kernel AST with metadata using results of type flow analysis.
class AnnotateKernel extends RecursiveVisitor<Null> {
final TypeFlowAnalysis _typeFlowAnalysis;
final FieldMorpher fieldMorpher;
final DirectCallMetadataRepository _directCallMetadataRepository;
final InferredTypeMetadataRepository _inferredTypeMetadata;
final UnreachableNodeMetadataRepository _unreachableNodeMetadata;
@ -126,7 +120,7 @@ class AnnotateKernel extends RecursiveVisitor<Null> {
final Class _intClass;
Constant _nullConstant;
AnnotateKernel(Component component, this._typeFlowAnalysis, this.fieldMorpher)
AnnotateKernel(Component component, this._typeFlowAnalysis)
: _directCallMetadataRepository =
component.metadata[DirectCallMetadataRepository.repositoryTag],
_inferredTypeMetadata = new InferredTypeMetadataRepository(),
@ -309,8 +303,7 @@ class AnnotateKernel extends RecursiveVisitor<Null> {
// TODO(alexmarkov): figure out how to pass receiver type.
}
} else if (!member.isAbstract &&
!fieldMorpher.isExtraMemberWithReachableBody(member)) {
} else if (!member.isAbstract) {
_setUnreachable(member);
}
@ -319,15 +312,14 @@ class AnnotateKernel extends RecursiveVisitor<Null> {
// 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(original),
_typeFlowAnalysis.isCalledDynamically(member),
getterCalledDynamically:
_typeFlowAnalysis.isGetterCalledDynamically(original),
hasThisUses: _typeFlowAnalysis.isCalledViaThis(original),
hasNonThisUses: _typeFlowAnalysis.isCalledNotViaThis(original),
hasTearOffUses: _typeFlowAnalysis.isTearOffTaken(original),
_typeFlowAnalysis.isGetterCalledDynamically(member),
hasThisUses: _typeFlowAnalysis.isCalledViaThis(member),
hasNonThisUses: _typeFlowAnalysis.isCalledNotViaThis(member),
hasTearOffUses: _typeFlowAnalysis.isTearOffTaken(member),
methodOrSetterSelectorId:
_tableSelectorAssigner.methodOrSetterSelectorId(member),
getterSelectorId: _tableSelectorAssigner.getterSelectorId(member));
@ -456,14 +448,12 @@ class TreeShaker {
final Set<Member> _usedMembers = new Set<Member>();
final Set<Extension> _usedExtensions = new Set<Extension>();
final Set<Typedef> _usedTypedefs = new Set<Typedef>();
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);
@ -482,25 +472,13 @@ 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) ||
fieldMorpher.isExtraMemberWithReachableBody(m);
bool isMemberBodyReachable(Member m) => typeFlowAnalysis.isMemberUsed(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) {
@ -536,15 +514,9 @@ 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) {
@ -597,86 +569,6 @@ class TreeShaker {
}
}
class FieldMorpher {
final TreeShaker shaker;
final Set<Member> _extraMembersWithReachableBody = <Member>{};
final Map<Field, Member> _gettersForRemovedFields = <Field, Member>{};
final Map<Field, Member> _settersForRemovedFields = <Field, Member>{};
final Map<Member, Field> _removedFields = <Member, Field>{};
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.
@ -727,10 +619,9 @@ class _TreeShakerTypeVisitor extends RecursiveVisitor<Null> {
/// transforms unreachable calls into 'throw' expressions.
class _TreeShakerPass1 extends Transformer {
final TreeShaker shaker;
final FieldMorpher fieldMorpher;
Procedure _unsafeCast;
_TreeShakerPass1(this.shaker) : fieldMorpher = shaker.fieldMorpher;
_TreeShakerPass1(this.shaker);
void transform(Component component) {
component.transformChildren(this);
@ -773,9 +664,7 @@ class _TreeShakerPass1 extends Transformer {
'Attempt to execute code removed by Dart AOT compiler (TFA)'));
}
for (var arg in args.reversed) {
if (mayHaveSideEffects(arg)) {
node = new Let(new VariableDeclaration(null, initializer: arg), node);
}
node = new Let(new VariableDeclaration(null, initializer: arg), node);
}
Statistics.callsDropped++;
return node;
@ -852,7 +741,7 @@ class _TreeShakerPass1 extends Transformer {
@override
TreeNode visitField(Field node) {
if (shaker.retainField(node)) {
if (shaker.isMemberBodyReachable(node)) {
if (kPrintTrace) {
tracePrint("Visiting $node");
}
@ -864,10 +753,12 @@ class _TreeShakerPass1 extends Transformer {
node.initializer = _makeUnreachableCall([])..parent = 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);
} 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);
}
return node;
}
@ -879,8 +770,6 @@ 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);
}
@ -894,8 +783,6 @@ 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);
}
@ -909,8 +796,6 @@ 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);
}
@ -924,8 +809,6 @@ 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);
}
@ -939,8 +822,6 @@ class _TreeShakerPass1 extends Transformer {
if (_isUnreachable(node)) {
return _makeUnreachableCall([]);
} else {
node.interfaceTarget =
fieldMorpher.adjustInstanceCallTarget(node.interfaceTarget);
if (node.interfaceTarget != null) {
shaker.addUsedMember(node.interfaceTarget);
}
@ -954,8 +835,6 @@ 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);
}
@ -1001,11 +880,7 @@ class _TreeShakerPass1 extends Transformer {
if (_isUnreachable(node)) {
return _makeUnreachableCall([node.value]);
} else {
final target = node.target;
assertx(shaker.isMemberBodyReachable(target), details: node);
if (target is Field && !shaker.retainField(target)) {
return node.value;
}
assertx(shaker.isMemberBodyReachable(node.target), details: node.target);
return node;
}
}
@ -1017,7 +892,7 @@ class _TreeShakerPass1 extends Transformer {
return _makeUnreachableCall(
_flattenArguments(node.arguments, receiver: node.receiver));
} else {
assertx(shaker.isMemberBodyReachable(node.target), details: node);
assertx(shaker.isMemberBodyReachable(node.target), details: node.target);
return node;
}
}
@ -1028,10 +903,7 @@ class _TreeShakerPass1 extends Transformer {
if (_isUnreachable(node)) {
return _makeUnreachableCall([node.receiver]);
} else {
final target = node.target;
assertx(shaker.isMemberBodyReachable(target), details: node);
assertx(target is! Field || shaker.isFieldGetterReachable(target),
details: node);
assertx(shaker.isMemberBodyReachable(node.target), details: node.target);
return node;
}
}
@ -1042,9 +914,7 @@ class _TreeShakerPass1 extends Transformer {
if (_isUnreachable(node)) {
return _makeUnreachableCall([node.receiver, node.value]);
} else {
assertx(shaker.isMemberBodyReachable(node.target), details: node);
node.target =
fieldMorpher.adjustInstanceCallTarget(node.target, isSetter: true);
assertx(shaker.isMemberBodyReachable(node.target), details: node.target);
return node;
}
}
@ -1087,20 +957,12 @@ class _TreeShakerPass1 extends Transformer {
}
@override
TreeNode visitFieldInitializer(FieldInitializer node) {
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;
}
}
@ -1373,26 +1235,3 @@ class _TreeShakerConstantVisitor extends ConstantVisitor<Null> {
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;
}

View file

@ -10,9 +10,7 @@ import 'package:front_end/src/api_unstable/vm.dart'
CompilerOptions,
DiagnosticMessage,
computePlatformBinariesLocation,
kernelForProgram,
parseExperimentalArguments,
parseExperimentalFlags;
kernelForProgram;
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;
@ -37,7 +35,6 @@ class TestingVmTarget extends VmTarget {
Future<Component> compileTestCaseToKernelProgram(Uri sourceUri,
{Target target,
bool enableSuperMixins = false,
List<String> experimentalFlags,
Map<String, String> environmentDefines}) async {
final platformKernel =
computePlatformBinariesLocation().resolve('vm_platform_strong.dill');
@ -48,11 +45,6 @@ Future<Component> compileTestCaseToKernelProgram(Uri sourceUri,
..target = target
..linkedDependencies = <Uri>[platformKernel]
..environmentDefines = environmentDefines
..experimentalFlags =
parseExperimentalFlags(parseExperimentalArguments(experimentalFlags),
onError: (String message) {
throw message;
})
..onDiagnostic = (DiagnosticMessage message) {
fail("Compilation error: ${message.plainTextFormatted.join('\n')}");
};

View file

@ -18,10 +18,10 @@ import '../../common_test_utils.dart';
final String pkgVmDir = Platform.script.resolve('../../..').toFilePath();
runTestCase(Uri source, List<String> experimentalFlags) async {
runTestCase(Uri source) async {
final target = new TestingVmTarget(new TargetFlags());
Component component = await compileTestCaseToKernelProgram(source,
target: target, experimentalFlags: experimentalFlags);
Component component =
await compileTestCaseToKernelProgram(source, target: target);
final coreTypes = new CoreTypes(component);
@ -44,10 +44,7 @@ main() {
.listSync(recursive: true, followLinks: false)
.reversed) {
if (entry.path.endsWith(".dart")) {
final List<String> experimentalFlags = [
if (entry.path.endsWith("_nnbd.dart")) 'non-nullable',
];
test(entry.path, () => runTestCase(entry.uri, experimentalFlags));
test(entry.path, () => runTestCase(entry.uri));
}
}
});

View file

@ -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 = [@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 = 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)";
}
[@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<core::String*>* args) → dynamic {
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;
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;
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));
}

View file

@ -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*) {
throw "Attempt to execute code removed by Dart AOT compiler (TFA)";
let dynamic #t1 = i{self::A*} in let dynamic #t2 = 42 in throw "Attempt to execute code removed by Dart AOT compiler (TFA)";
}
}
static method main(core::List<core::String*>* args) → dynamic {

View file

@ -2,6 +2,8 @@ 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::•() {
@ -14,7 +16,8 @@ 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 {
null;
self::field = null;
[@vm.direct-call.metadata=#lib::C::instanceField] [@vm.inferred-type.metadata=!? (skip check)] new self::C::•().{self::C::instanceField} = null;
}

View file

@ -1,91 +0,0 @@
// 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<T> {
// Should be replaced with setter.
T bar;
}
class D implements C<int> {
// 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<num> 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;
}

View file

@ -1,68 +0,0 @@
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<T extends core::Object* = dynamic> 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<core::int*> {
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<core::num*>* c = new self::D::•();
exp::Expect::throws<dynamic>(() → core::Null? {
[@vm.call-site-attributes.metadata=receiverType:#lib::C<dart.core::num*>*] [@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;
}

View file

@ -1,42 +0,0 @@
// 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;
}

View file

@ -1,31 +0,0 @@
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] [@vm.inferred-type.metadata=!? (skip check)] 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 {
[@vm.inferred-type.metadata=!? (skip check)] 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;
}

View file

@ -8,15 +8,7 @@ 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 cleanBlock;
@pragma("vm:entry-point")
var copyingBlock;
@pragma("vm:entry-point")
var fullBlock;
@pragma("vm:entry-point")
var fullBlockWithChain;
var cleanBlock, copyingBlock, fullBlock, fullBlockWithChain;
Function genCleanBlock() {
block(x) => x;

View file

@ -12,86 +12,62 @@ 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;
}

View file

@ -21,86 +21,62 @@ 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;
}

View file

@ -9,10 +9,7 @@ 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;
}

View file

@ -9,14 +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;
}
@pragma("vm:entry-point")
var myVar;
@pragma("vm:entry-point")

View file

@ -9,10 +9,7 @@ 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;
}

View file

@ -9,8 +9,6 @@ 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;
}

View file

@ -9,17 +9,12 @@ 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;
}

View file

@ -9,10 +9,7 @@ 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;
}

View file

@ -9,11 +9,7 @@ import 'test_helper.dart';
import 'service_test_common.dart';
class Pair {
// Make sure these fields are not removed by the tree shaker.
@pragma("vm:entry-point")
var x;
@pragma("vm:entry-point")
var y;
var x, y;
}
var p1;

View file

@ -8,10 +8,7 @@ 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() {

View file

@ -60,11 +60,7 @@ 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;