mirror of
https://github.com/dart-lang/sdk
synced 2024-09-05 00:13:50 +00:00
[vm/kernel/aot] Tree shaking based on results of TFA, take 2.
This is a re-landing of06ebf884db
after the fix was submitted separately ate3b9c2860e
Original review: https://dart-review.googlesource.com/c/sdk/+/46942 Issue: https://github.com/dart-lang/sdk/issues/30480 Change-Id: I2d3a10acb9b5af0184b3d1504acb2f4ebebfff8a Reviewed-on: https://dart-review.googlesource.com/48440 Reviewed-by: Ryan Macnak <rmacnak@google.com> Commit-Queue: Alexander Markov <alexmarkov@google.com>
This commit is contained in:
parent
b217e21907
commit
c60199cf86
|
@ -1087,6 +1087,8 @@ class TypeFlowAnalysis implements EntryPointsListener, CallHandler {
|
|||
}
|
||||
}
|
||||
|
||||
bool isClassAllocated(Class c) => hierarchyCache.allocatedClasses.contains(c);
|
||||
|
||||
Call callSite(TreeNode node) => summaryCollector.callSites[node];
|
||||
|
||||
Type fieldType(Field field) => _fieldValues[field]?.value;
|
||||
|
|
|
@ -6,6 +6,18 @@
|
|||
"name": "noSuchMethod",
|
||||
"action": "call"
|
||||
},
|
||||
{
|
||||
"library": "dart:core",
|
||||
"class": "Object",
|
||||
"name": "_haveSameRuntimeType",
|
||||
"action": "call"
|
||||
},
|
||||
{
|
||||
"library": "dart:core",
|
||||
"class": "Object",
|
||||
"name": "_instanceOf",
|
||||
"action": "call"
|
||||
},
|
||||
{
|
||||
"library": "dart:core",
|
||||
"class": "List",
|
||||
|
@ -28,6 +40,12 @@
|
|||
"class": "_ImmutableMap",
|
||||
"action": "create-instance"
|
||||
},
|
||||
{
|
||||
"library": "dart:core",
|
||||
"class": "_ImmutableMap",
|
||||
"name": "_create",
|
||||
"action": "call"
|
||||
},
|
||||
{
|
||||
"library": "dart:core",
|
||||
"class": "_StringBase",
|
||||
|
@ -50,11 +68,6 @@
|
|||
"name": "_classIdEqualsAssert",
|
||||
"action": "call"
|
||||
},
|
||||
{
|
||||
"library": "dart:_internal",
|
||||
"name": "_classRangeCheck",
|
||||
"action": "call"
|
||||
},
|
||||
{
|
||||
"library": "dart:core",
|
||||
"class": "_AssertionError",
|
||||
|
@ -67,6 +80,94 @@
|
|||
"name": "_throwNew",
|
||||
"action": "call"
|
||||
},
|
||||
{
|
||||
"library": "dart:core",
|
||||
"class": "_Closure",
|
||||
"name": "_instantiator_type_arguments",
|
||||
"action": "get"
|
||||
},
|
||||
{
|
||||
"library": "dart:core",
|
||||
"class": "_Closure",
|
||||
"name": "_function_type_arguments",
|
||||
"action": "get"
|
||||
},
|
||||
{
|
||||
"library": "dart:core",
|
||||
"class": "_Closure",
|
||||
"name": "_function",
|
||||
"action": "get"
|
||||
},
|
||||
{
|
||||
"library": "dart:core",
|
||||
"class": "_Closure",
|
||||
"name": "_context",
|
||||
"action": "get"
|
||||
},
|
||||
{
|
||||
"library": "dart:core",
|
||||
"class": "_Closure",
|
||||
"name": "_hash",
|
||||
"action": "get"
|
||||
},
|
||||
{
|
||||
"library": "dart:core",
|
||||
"class": "_CompileTimeError",
|
||||
"action": "create-instance"
|
||||
},
|
||||
{
|
||||
"library": "dart:core",
|
||||
"class": "Object",
|
||||
"name": "_simpleInstanceOf",
|
||||
"action": "call"
|
||||
},
|
||||
{
|
||||
"library": "dart:core",
|
||||
"class": "Object",
|
||||
"name": "_simpleInstanceOfTrue",
|
||||
"action": "call"
|
||||
},
|
||||
{
|
||||
"library": "dart:core",
|
||||
"class": "Object",
|
||||
"name": "_simpleInstanceOfFalse",
|
||||
"action": "call"
|
||||
},
|
||||
{
|
||||
"library": "dart:_internal",
|
||||
"name": "_classRangeCheck",
|
||||
"action": "call"
|
||||
},
|
||||
{
|
||||
"library": "dart:_internal",
|
||||
"name": "_prependTypeArguments",
|
||||
"action": "call"
|
||||
},
|
||||
{
|
||||
"library": "dart:async",
|
||||
"name": "_setAsyncThreadStackTrace",
|
||||
"action": "call"
|
||||
},
|
||||
{
|
||||
"library": "dart:async",
|
||||
"name": "_clearAsyncThreadStackTrace",
|
||||
"action": "call"
|
||||
},
|
||||
{
|
||||
"library": "dart:async",
|
||||
"name": "_asyncStarMoveNextHelper",
|
||||
"action": "call"
|
||||
},
|
||||
{
|
||||
"library": "dart:async",
|
||||
"name": "_completeOnAsyncReturn",
|
||||
"action": "call"
|
||||
},
|
||||
{
|
||||
"library": "dart:async",
|
||||
"class": "_AsyncStarStreamController",
|
||||
"action": "create-instance"
|
||||
},
|
||||
{
|
||||
"library": "dart:math",
|
||||
"class": "_Random",
|
||||
|
|
|
@ -33,9 +33,13 @@ class NativeCodeOracle {
|
|||
final Map<String, List<Map<String, dynamic>>> _nativeMethods =
|
||||
<String, List<Map<String, dynamic>>>{};
|
||||
final LibraryIndex _libraryIndex;
|
||||
final Set<Member> _membersReferencedFromNativeCode = new Set<Member>();
|
||||
|
||||
NativeCodeOracle(this._libraryIndex);
|
||||
|
||||
bool isMemberReferencedFromNativeCode(Member member) =>
|
||||
_membersReferencedFromNativeCode.contains(member);
|
||||
|
||||
/// Simulate the execution of a native method by adding its entry points
|
||||
/// using [entryPointsListener]. Returns result type of the native method.
|
||||
Type handleNativeProcedure(
|
||||
|
@ -171,6 +175,8 @@ class NativeCodeOracle {
|
|||
|
||||
entryPointsListener.addRawCall(selector);
|
||||
}
|
||||
|
||||
_membersReferencedFromNativeCode.add(member);
|
||||
} else {
|
||||
throw 'Bad entry point: unrecognized action "$action" in $rootDesc';
|
||||
}
|
||||
|
|
|
@ -959,9 +959,11 @@ class SummaryCollector extends RecursiveVisitor<TypeExpr> {
|
|||
|
||||
@override
|
||||
TypeExpr visitAssertStatement(AssertStatement node) {
|
||||
_visit(node.condition);
|
||||
if (node.message != null) {
|
||||
_visit(node.message);
|
||||
if (!kRemoveAsserts) {
|
||||
_visit(node.condition);
|
||||
if (node.message != null) {
|
||||
_visit(node.message);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -974,7 +976,9 @@ class SummaryCollector extends RecursiveVisitor<TypeExpr> {
|
|||
|
||||
@override
|
||||
TypeExpr visitAssertBlock(AssertBlock node) {
|
||||
node.statements.forEach(_visit);
|
||||
if (!kRemoveAsserts) {
|
||||
node.statements.forEach(_visit);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -1146,7 +1150,9 @@ class SummaryCollector extends RecursiveVisitor<TypeExpr> {
|
|||
|
||||
@override
|
||||
visitAssertInitializer(AssertInitializer node) {
|
||||
_visit(node.statement);
|
||||
if (!kRemoveAsserts) {
|
||||
_visit(node.statement);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -67,7 +67,7 @@ Component transformComponent(
|
|||
|
||||
final transformsStopWatch = new Stopwatch()..start();
|
||||
|
||||
new DropMethodBodiesVisitor(typeFlowAnalysis).visitComponent(component);
|
||||
new TreeShaker(component, typeFlowAnalysis).transformComponent(component);
|
||||
|
||||
new TFADevirtualization(component, typeFlowAnalysis)
|
||||
.visitComponent(component);
|
||||
|
@ -107,32 +107,6 @@ class TFADevirtualization extends Devirtualization {
|
|||
}
|
||||
}
|
||||
|
||||
/// Drop method bodies using results of type flow analysis.
|
||||
class DropMethodBodiesVisitor extends RecursiveVisitor<Null> {
|
||||
final TypeFlowAnalysis _typeFlowAnalysis;
|
||||
|
||||
DropMethodBodiesVisitor(this._typeFlowAnalysis);
|
||||
|
||||
@override
|
||||
defaultMember(Member m) {
|
||||
if (!m.isAbstract && !_typeFlowAnalysis.isMemberUsed(m)) {
|
||||
if (m.function != null && m.function.body != null) {
|
||||
m.function.body = new ExpressionStatement(
|
||||
new Throw(new StringLiteral("TFA Error: $m")))
|
||||
..parent = m.function;
|
||||
debugPrint("Dropped $m");
|
||||
} else if ((m is Field) &&
|
||||
(m.initializer != null) &&
|
||||
(m.initializer is! NullLiteral)) {
|
||||
m.isConst = false;
|
||||
m.initializer = new Throw(new StringLiteral("TFA Error: $m"))
|
||||
..parent = m;
|
||||
debugPrint("Dropped $m");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Annotates kernel AST with metadata using results of type flow analysis.
|
||||
class AnnotateKernel extends RecursiveVisitor<Null> {
|
||||
final TypeFlowAnalysis _typeFlowAnalysis;
|
||||
|
@ -298,3 +272,610 @@ class AnnotateKernel extends RecursiveVisitor<Null> {
|
|||
super.visitStaticGet(node);
|
||||
}
|
||||
}
|
||||
|
||||
/// Tree shaking based on results of type flow analysis (TFA).
|
||||
///
|
||||
/// TFA provides information about allocated classes and reachable member
|
||||
/// bodies. However, it is not enough to perform tree shaking in one pass:
|
||||
/// we need to figure out which classes, members and typedefs are used
|
||||
/// in types, interface targets and annotations.
|
||||
///
|
||||
/// So, tree shaking is performed in 2 passes:
|
||||
///
|
||||
/// * Pass 1 visits declarations of classes and members, and dives deep into
|
||||
/// bodies of reachable members. It collects sets of used classes, members
|
||||
/// and typedefs. Also, while visiting bodies of reachable members, it
|
||||
/// transforms unreachable calls into 'throw' expressions.
|
||||
///
|
||||
/// * Pass 2 removes unused classes and members, and replaces bodies of
|
||||
/// used but unreachable members.
|
||||
///
|
||||
class TreeShaker {
|
||||
final TypeFlowAnalysis typeFlowAnalysis;
|
||||
final Set<Class> _usedClasses = new Set<Class>();
|
||||
final Set<Class> _classesUsedInType = new Set<Class>();
|
||||
final Set<Member> _usedMembers = new Set<Member>();
|
||||
final Set<Typedef> _usedTypedefs = new Set<Typedef>();
|
||||
_TreeShakerTypeVisitor typeVisitor;
|
||||
_TreeShakerPass1 _pass1;
|
||||
_TreeShakerPass2 _pass2;
|
||||
|
||||
TreeShaker(Component component, this.typeFlowAnalysis) {
|
||||
typeVisitor = new _TreeShakerTypeVisitor(this);
|
||||
_pass1 = new _TreeShakerPass1(this);
|
||||
_pass2 = new _TreeShakerPass2(this);
|
||||
}
|
||||
|
||||
transformComponent(Component component) {
|
||||
_pass1.transform(component);
|
||||
_pass2.transform(component);
|
||||
}
|
||||
|
||||
bool isClassUsed(Class c) => _usedClasses.contains(c);
|
||||
bool isClassUsedInType(Class c) => _classesUsedInType.contains(c);
|
||||
bool isClassAllocated(Class c) => typeFlowAnalysis.isClassAllocated(c);
|
||||
bool isMemberUsed(Member m) => _usedMembers.contains(m);
|
||||
bool isMemberBodyReachable(Member m) => typeFlowAnalysis.isMemberUsed(m);
|
||||
bool isMemberReferencedFromNativeCode(Member m) =>
|
||||
typeFlowAnalysis.nativeCodeOracle.isMemberReferencedFromNativeCode(m);
|
||||
bool isTypedefUsed(Typedef t) => _usedTypedefs.contains(t);
|
||||
|
||||
void addClassUsedInType(Class c) {
|
||||
if (_classesUsedInType.add(c)) {
|
||||
if (kPrintDebug) {
|
||||
debugPrint('Class ${c.name} used in type');
|
||||
}
|
||||
_usedClasses.add(c);
|
||||
visitIterable(c.supers, typeVisitor);
|
||||
visitList(c.typeParameters, typeVisitor);
|
||||
transformList(c.annotations, _pass1, c);
|
||||
// Preserve NSM forwarders. They are overlooked by TFA / tree shaker
|
||||
// as they are abstract and don't have a body.
|
||||
for (Procedure p in c.procedures) {
|
||||
if (p.isAbstract && p.isNoSuchMethodForwarder) {
|
||||
addUsedMember(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void addUsedMember(Member m) {
|
||||
if (_usedMembers.add(m)) {
|
||||
final enclosingClass = m.enclosingClass;
|
||||
if (enclosingClass != null) {
|
||||
if (kPrintDebug) {
|
||||
debugPrint('Member $m from class ${enclosingClass.name} is used');
|
||||
}
|
||||
_usedClasses.add(enclosingClass);
|
||||
}
|
||||
|
||||
FunctionNode func = null;
|
||||
if (m is Field) {
|
||||
m.type.accept(typeVisitor);
|
||||
} else if (m is Procedure) {
|
||||
func = m.function;
|
||||
if (m.forwardingStubSuperTarget != null) {
|
||||
addUsedMember(m.forwardingStubSuperTarget);
|
||||
}
|
||||
if (m.forwardingStubInterfaceTarget != null) {
|
||||
addUsedMember(m.forwardingStubInterfaceTarget);
|
||||
}
|
||||
} else if (m is Constructor) {
|
||||
func = m.function;
|
||||
} else {
|
||||
throw 'Unexpected member ${m.runtimeType}: $m';
|
||||
}
|
||||
|
||||
if (func != null) {
|
||||
visitList(func.typeParameters, typeVisitor);
|
||||
visitList(func.positionalParameters, typeVisitor);
|
||||
visitList(func.namedParameters, typeVisitor);
|
||||
func.returnType.accept(typeVisitor);
|
||||
}
|
||||
|
||||
transformList(m.annotations, _pass1, m);
|
||||
}
|
||||
}
|
||||
|
||||
void addUsedTypedef(Typedef typedef) {
|
||||
if (_usedTypedefs.add(typedef)) {
|
||||
typedef.visitChildren(typeVisitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
class _TreeShakerTypeVisitor extends RecursiveVisitor<Null> {
|
||||
final TreeShaker shaker;
|
||||
|
||||
_TreeShakerTypeVisitor(this.shaker);
|
||||
|
||||
@override
|
||||
visitInterfaceType(InterfaceType node) {
|
||||
shaker.addClassUsedInType(node.classNode);
|
||||
node.visitChildren(this);
|
||||
}
|
||||
|
||||
@override
|
||||
visitSupertype(Supertype node) {
|
||||
shaker.addClassUsedInType(node.classNode);
|
||||
node.visitChildren(this);
|
||||
}
|
||||
|
||||
@override
|
||||
visitTypedefType(TypedefType node) {
|
||||
shaker.addUsedTypedef(node.typedefNode);
|
||||
}
|
||||
|
||||
@override
|
||||
visitFunctionType(FunctionType node) {
|
||||
node.visitChildren(this);
|
||||
final typedef = node.typedef;
|
||||
if (typedef != null) {
|
||||
shaker.addUsedTypedef(typedef);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The first pass of [TreeShaker].
|
||||
/// Visits all classes, members and bodies of reachable members.
|
||||
/// Collects all used classes, members and types, and
|
||||
/// transforms unreachable calls into 'throw' expressions.
|
||||
class _TreeShakerPass1 extends Transformer {
|
||||
final TreeShaker shaker;
|
||||
|
||||
_TreeShakerPass1(this.shaker);
|
||||
|
||||
void transform(Component component) {
|
||||
component.transformChildren(this);
|
||||
}
|
||||
|
||||
bool _isUnreachable(TreeNode node) {
|
||||
final callSite = shaker.typeFlowAnalysis.callSite(node);
|
||||
return (callSite != null) && !callSite.isReachable;
|
||||
}
|
||||
|
||||
List<Expression> _flattenArguments(Arguments arguments,
|
||||
{Expression receiver}) {
|
||||
final args = <Expression>[];
|
||||
if (receiver != null) {
|
||||
args.add(receiver);
|
||||
}
|
||||
args.addAll(arguments.positional);
|
||||
args.addAll(arguments.named.map((a) => a.value));
|
||||
return args;
|
||||
}
|
||||
|
||||
bool _isThrowExpression(Expression expr) {
|
||||
while (expr is Let) {
|
||||
expr = (expr as Let).body;
|
||||
}
|
||||
return expr is Throw;
|
||||
}
|
||||
|
||||
TreeNode _makeUnreachableCall(List<Expression> args) {
|
||||
TreeNode node;
|
||||
final int last = args.indexWhere(_isThrowExpression);
|
||||
if (last >= 0) {
|
||||
// One of the arguments is a Throw expression.
|
||||
// Ignore the rest of the arguments.
|
||||
node = args[last];
|
||||
args = args.sublist(0, last);
|
||||
Statistics.throwExpressionsPruned++;
|
||||
} else {
|
||||
node = new Throw(new StringLiteral(
|
||||
'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);
|
||||
}
|
||||
Statistics.callsDropped++;
|
||||
return node;
|
||||
}
|
||||
|
||||
TreeNode _makeUnreachableInitializer(List<Expression> args) {
|
||||
return new LocalInitializer(
|
||||
new VariableDeclaration(null, initializer: _makeUnreachableCall(args)));
|
||||
}
|
||||
|
||||
TreeNode _visitAssertNode(TreeNode node) {
|
||||
if (kRemoveAsserts) {
|
||||
return null;
|
||||
} else {
|
||||
node.transformChildren(this);
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
DartType visitDartType(DartType node) {
|
||||
node.accept(shaker.typeVisitor);
|
||||
return node;
|
||||
}
|
||||
|
||||
@override
|
||||
Supertype visitSupertype(Supertype node) {
|
||||
node.accept(shaker.typeVisitor);
|
||||
return node;
|
||||
}
|
||||
|
||||
@override
|
||||
TreeNode visitTypedef(Typedef node) {
|
||||
return node; // Do not go deeper.
|
||||
}
|
||||
|
||||
@override
|
||||
TreeNode visitClass(Class node) {
|
||||
if (shaker.isClassAllocated(node)) {
|
||||
shaker.addClassUsedInType(node);
|
||||
}
|
||||
transformList(node.constructors, this, node);
|
||||
transformList(node.procedures, this, node);
|
||||
transformList(node.fields, this, node);
|
||||
transformList(node.redirectingFactoryConstructors, this, node);
|
||||
return node;
|
||||
}
|
||||
|
||||
@override
|
||||
TreeNode defaultMember(Member node) {
|
||||
if (shaker.isMemberBodyReachable(node)) {
|
||||
if (kPrintTrace) {
|
||||
tracePrint("Visiting $node");
|
||||
}
|
||||
shaker.addUsedMember(node);
|
||||
node.transformChildren(this);
|
||||
} 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;
|
||||
}
|
||||
|
||||
@override
|
||||
TreeNode visitMethodInvocation(MethodInvocation node) {
|
||||
node.transformChildren(this);
|
||||
if (_isUnreachable(node)) {
|
||||
return _makeUnreachableCall(
|
||||
_flattenArguments(node.arguments, receiver: node.receiver));
|
||||
} else {
|
||||
if (node.interfaceTarget != null) {
|
||||
shaker.addUsedMember(node.interfaceTarget);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
TreeNode visitPropertyGet(PropertyGet node) {
|
||||
node.transformChildren(this);
|
||||
if (_isUnreachable(node)) {
|
||||
return _makeUnreachableCall([node.receiver]);
|
||||
} else {
|
||||
if (node.interfaceTarget != null) {
|
||||
shaker.addUsedMember(node.interfaceTarget);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
TreeNode visitPropertySet(PropertySet node) {
|
||||
node.transformChildren(this);
|
||||
if (_isUnreachable(node)) {
|
||||
return _makeUnreachableCall([node.receiver, node.value]);
|
||||
} else {
|
||||
if (node.interfaceTarget != null) {
|
||||
shaker.addUsedMember(node.interfaceTarget);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
TreeNode visitSuperMethodInvocation(SuperMethodInvocation node) {
|
||||
node.transformChildren(this);
|
||||
if (_isUnreachable(node)) {
|
||||
return _makeUnreachableCall(_flattenArguments(node.arguments));
|
||||
} else {
|
||||
if (node.interfaceTarget != null) {
|
||||
shaker.addUsedMember(node.interfaceTarget);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
TreeNode visitSuperPropertyGet(SuperPropertyGet node) {
|
||||
node.transformChildren(this);
|
||||
if (_isUnreachable(node)) {
|
||||
return _makeUnreachableCall([]);
|
||||
} else {
|
||||
if (node.interfaceTarget != null) {
|
||||
shaker.addUsedMember(node.interfaceTarget);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
TreeNode visitSuperPropertySet(SuperPropertySet node) {
|
||||
node.transformChildren(this);
|
||||
if (_isUnreachable(node)) {
|
||||
return _makeUnreachableCall([node.value]);
|
||||
} else {
|
||||
if (node.interfaceTarget != null) {
|
||||
shaker.addUsedMember(node.interfaceTarget);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
TreeNode visitStaticInvocation(StaticInvocation node) {
|
||||
node.transformChildren(this);
|
||||
if (_isUnreachable(node)) {
|
||||
return _makeUnreachableCall(_flattenArguments(node.arguments));
|
||||
} else {
|
||||
assertx(shaker.isMemberBodyReachable(node.target), details: node.target);
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
TreeNode visitStaticGet(StaticGet node) {
|
||||
node.transformChildren(this);
|
||||
if (_isUnreachable(node)) {
|
||||
return _makeUnreachableCall([]);
|
||||
} else {
|
||||
if (!shaker.isMemberBodyReachable(node.target)) {
|
||||
// Annotations could contain references to constant fields.
|
||||
assertx((node.target is Field) && (node.target as Field).isConst);
|
||||
shaker.addUsedMember(node.target);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
TreeNode visitStaticSet(StaticSet node) {
|
||||
node.transformChildren(this);
|
||||
if (_isUnreachable(node)) {
|
||||
return _makeUnreachableCall([node.value]);
|
||||
} else {
|
||||
assertx(shaker.isMemberBodyReachable(node.target), details: node.target);
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
TreeNode visitDirectMethodInvocation(DirectMethodInvocation node) {
|
||||
node.transformChildren(this);
|
||||
if (_isUnreachable(node)) {
|
||||
return _makeUnreachableCall(
|
||||
_flattenArguments(node.arguments, receiver: node.receiver));
|
||||
} else {
|
||||
assertx(shaker.isMemberBodyReachable(node.target), details: node.target);
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
TreeNode visitDirectPropertyGet(DirectPropertyGet node) {
|
||||
node.transformChildren(this);
|
||||
if (_isUnreachable(node)) {
|
||||
return _makeUnreachableCall([node.receiver]);
|
||||
} else {
|
||||
assertx(shaker.isMemberBodyReachable(node.target), details: node.target);
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
TreeNode visitDirectPropertySet(DirectPropertySet node) {
|
||||
node.transformChildren(this);
|
||||
if (_isUnreachable(node)) {
|
||||
return _makeUnreachableCall([node.receiver, node.value]);
|
||||
} else {
|
||||
assertx(shaker.isMemberBodyReachable(node.target), details: node.target);
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
TreeNode visitConstructorInvocation(ConstructorInvocation node) {
|
||||
node.transformChildren(this);
|
||||
if (_isUnreachable(node)) {
|
||||
return _makeUnreachableCall(_flattenArguments(node.arguments));
|
||||
} else {
|
||||
if (!shaker.isMemberBodyReachable(node.target)) {
|
||||
// Annotations could contain references to const constructors.
|
||||
assertx(node.isConst);
|
||||
shaker.addUsedMember(node.target);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
TreeNode visitRedirectingInitializer(RedirectingInitializer node) {
|
||||
node.transformChildren(this);
|
||||
if (_isUnreachable(node)) {
|
||||
return _makeUnreachableInitializer(_flattenArguments(node.arguments));
|
||||
} else {
|
||||
assertx(shaker.isMemberBodyReachable(node.target), details: node.target);
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
TreeNode visitSuperInitializer(SuperInitializer node) {
|
||||
node.transformChildren(this);
|
||||
if (_isUnreachable(node)) {
|
||||
return _makeUnreachableInitializer(_flattenArguments(node.arguments));
|
||||
} else {
|
||||
// Can't assert that node.target is used due to partial mixin resolution.
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
visitFieldInitializer(FieldInitializer node) {
|
||||
node.transformChildren(this);
|
||||
if (_isUnreachable(node)) {
|
||||
return _makeUnreachableInitializer([node.value]);
|
||||
} else {
|
||||
assertx(shaker.isMemberBodyReachable(node.field), details: node.field);
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
TreeNode visitAssertStatement(AssertStatement node) {
|
||||
return _visitAssertNode(node);
|
||||
}
|
||||
|
||||
@override
|
||||
TreeNode visitAssertBlock(AssertBlock node) {
|
||||
return _visitAssertNode(node);
|
||||
}
|
||||
|
||||
@override
|
||||
TreeNode visitAssertInitializer(AssertInitializer node) {
|
||||
return _visitAssertNode(node);
|
||||
}
|
||||
}
|
||||
|
||||
/// The second pass of [TreeShaker]. It is called after set of used
|
||||
/// classes, members and typedefs is determined during the first pass.
|
||||
/// This pass visits classes and members and removes unused classes and member.
|
||||
/// Bodies of unreachable but used members are replaced with 'throw'
|
||||
/// expressions. This pass does not dive deeper than member level.
|
||||
class _TreeShakerPass2 extends Transformer {
|
||||
final TreeShaker shaker;
|
||||
|
||||
_TreeShakerPass2(this.shaker);
|
||||
|
||||
void transform(Component component) {
|
||||
component.transformChildren(this);
|
||||
}
|
||||
|
||||
@override
|
||||
TreeNode visitLibrary(Library node) {
|
||||
node.transformChildren(this);
|
||||
// The transformer API does not iterate over `Library.additionalExports`,
|
||||
// so we manually delete the references to shaken nodes.
|
||||
node.additionalExports.removeWhere((Reference reference) {
|
||||
final node = reference.node;
|
||||
if (node is Class) {
|
||||
return !shaker.isClassUsed(node);
|
||||
} else if (node is Typedef) {
|
||||
return !shaker.isTypedefUsed(node);
|
||||
} else {
|
||||
return !shaker.isMemberUsed(node as Member);
|
||||
}
|
||||
});
|
||||
return node;
|
||||
}
|
||||
|
||||
@override
|
||||
Typedef visitTypedef(Typedef node) {
|
||||
return shaker.isTypedefUsed(node) ? node : null;
|
||||
}
|
||||
|
||||
@override
|
||||
Class visitClass(Class node) {
|
||||
if (!shaker.isClassUsed(node)) {
|
||||
debugPrint('Dropped class ${node.name}');
|
||||
node.canonicalName?.unbind();
|
||||
Statistics.classesDropped++;
|
||||
return null; // Remove the class.
|
||||
}
|
||||
|
||||
if (!shaker.isClassUsedInType(node)) {
|
||||
debugPrint('Dropped supers from class ${node.name}');
|
||||
// The class is only a namespace for static members. Remove its
|
||||
// hierarchy information. This is mandatory, since these references
|
||||
// might otherwise become dangling.
|
||||
node.supertype = shaker
|
||||
.typeFlowAnalysis.environment.coreTypes.objectClass.asRawSupertype;
|
||||
node.implementedTypes.clear();
|
||||
node.typeParameters.clear();
|
||||
node.isAbstract = true;
|
||||
// Mixin applications cannot have static members.
|
||||
assertx(node.mixedInType == null);
|
||||
node.annotations = const <Expression>[];
|
||||
}
|
||||
|
||||
if (!shaker.isClassAllocated(node)) {
|
||||
debugPrint('Class ${node.name} converted to abstract');
|
||||
node.isAbstract = true;
|
||||
}
|
||||
|
||||
node.transformChildren(this);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
/// Preserve instance fields of enums as VM relies on their existence.
|
||||
bool _preserveSpecialMember(Member node) =>
|
||||
node is Field &&
|
||||
!node.isStatic &&
|
||||
node.enclosingClass != null &&
|
||||
node.enclosingClass.isEnum;
|
||||
|
||||
@override
|
||||
Member defaultMember(Member node) {
|
||||
if (!shaker.isMemberUsed(node) && !_preserveSpecialMember(node)) {
|
||||
node.canonicalName?.unbind();
|
||||
Statistics.membersDropped++;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!shaker.isMemberBodyReachable(node)) {
|
||||
if (node is Procedure) {
|
||||
// Remove body of unused member.
|
||||
if (!node.isStatic && node.enclosingClass.isAbstract) {
|
||||
node.isAbstract = true;
|
||||
node.function.body = null;
|
||||
} else {
|
||||
// If the enclosing class is not abstract, the method should still
|
||||
// have a body even if it can never be called.
|
||||
_makeUnreachableBody(node.function);
|
||||
}
|
||||
node.function.asyncMarker = AsyncMarker.Sync;
|
||||
node.forwardingStubSuperTargetReference = null;
|
||||
node.forwardingStubInterfaceTargetReference = null;
|
||||
Statistics.methodBodiesDropped++;
|
||||
} else if (node is Field) {
|
||||
node.initializer = null;
|
||||
Statistics.fieldInitializersDropped++;
|
||||
} else if (node is Constructor) {
|
||||
_makeUnreachableBody(node.function);
|
||||
node.initializers = const <Initializer>[];
|
||||
Statistics.constructorBodiesDropped++;
|
||||
} else {
|
||||
throw 'Unexpected member ${node.runtimeType}: $node';
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
void _makeUnreachableBody(FunctionNode function) {
|
||||
if (function.body != null) {
|
||||
function.body = new ExpressionStatement(new Throw(new StringLiteral(
|
||||
"Attempt to execute method removed by Dart AOT compiler (TFA)")))
|
||||
..parent = function;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
TreeNode defaultTreeNode(TreeNode node) {
|
||||
return node; // Do not traverse into other nodes.
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,9 @@ const bool kPrintDebug =
|
|||
const bool kPrintStats =
|
||||
const bool.fromEnvironment('global.type.flow.print.stats');
|
||||
|
||||
const bool kRemoveAsserts =
|
||||
const bool.fromEnvironment('global.type.flow.remove.asserts');
|
||||
|
||||
/// Extended 'assert': always checks condition.
|
||||
assertx(bool cond, {details}) {
|
||||
if (!cond) {
|
||||
|
@ -74,6 +77,13 @@ class Statistics {
|
|||
static int invocationsInvalidated = 0;
|
||||
static int recursiveInvocationsApproximated = 0;
|
||||
static int typeConeSpecializations = 0;
|
||||
static int classesDropped = 0;
|
||||
static int membersDropped = 0;
|
||||
static int methodBodiesDropped = 0;
|
||||
static int fieldInitializersDropped = 0;
|
||||
static int constructorBodiesDropped = 0;
|
||||
static int callsDropped = 0;
|
||||
static int throwExpressionsPruned = 0;
|
||||
|
||||
/// Resets statistic counters.
|
||||
static void reset() {
|
||||
|
@ -84,6 +94,14 @@ class Statistics {
|
|||
usedCachedResultsOfInvocations = 0;
|
||||
invocationsInvalidated = 0;
|
||||
recursiveInvocationsApproximated = 0;
|
||||
typeConeSpecializations = 0;
|
||||
classesDropped = 0;
|
||||
membersDropped = 0;
|
||||
methodBodiesDropped = 0;
|
||||
fieldInitializersDropped = 0;
|
||||
constructorBodiesDropped = 0;
|
||||
callsDropped = 0;
|
||||
throwExpressionsPruned = 0;
|
||||
}
|
||||
|
||||
static void print(String caption) {
|
||||
|
@ -96,6 +114,13 @@ class Statistics {
|
|||
${invocationsInvalidated} invocations invalidated
|
||||
${recursiveInvocationsApproximated} recursive invocations approximated
|
||||
${typeConeSpecializations} type cones specialized
|
||||
${classesDropped} classes dropped
|
||||
${membersDropped} members dropped
|
||||
${methodBodiesDropped} method bodies dropped
|
||||
${fieldInitializersDropped} field initializers dropped
|
||||
${constructorBodiesDropped} constructor bodies dropped
|
||||
${callsDropped} calls dropped
|
||||
${throwExpressionsPruned} throw expressions pruned
|
||||
""");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,13 +10,10 @@ class _Vector extends core::Object {
|
|||
constructor •([@vm.inferred-type.metadata=!] core::int size) → void
|
||||
: self::_Vector::_offset = 0, self::_Vector::_length = size, self::_Vector::_elements = [@vm.inferred-type.metadata=dart.typed_data::_Float64List] typ::Float64List::•(size), super core::Object::•()
|
||||
;
|
||||
[@vm.unreachable.metadata=] constructor fromVOL(core::List<core::double> values, core::int offset, core::int length) → void
|
||||
: self::_Vector::_offset = offset, self::_Vector::_length = length, self::_Vector::_elements = values, super core::Object::•()
|
||||
throw "TFA Error: #lib::_Vector::fromVOL";
|
||||
operator [](core::int i) → core::double
|
||||
return [@vm.direct-call.metadata=dart.typed_data::_Float64List::[]] [@vm.inferred-type.metadata=dart.core::_Double] [@vm.direct-call.metadata=#lib::_Vector::_elements] [@vm.inferred-type.metadata=dart.typed_data::_Float64List] this.{self::_Vector::_elements}.{core::List::[]}(i.{core::num::+}([@vm.direct-call.metadata=#lib::_Vector::_offset] [@vm.inferred-type.metadata=!] this.{self::_Vector::_offset}));
|
||||
operator []=([@vm.inferred-type.metadata=!] core::int i, core::double value) → void {
|
||||
[@vm.unreachable.metadata=] [@vm.direct-call.metadata=#lib::_Vector::_elements] [@vm.inferred-type.metadata=dart.typed_data::_Float64List] this.{self::_Vector::_elements}.{core::List::[]=}([@vm.unreachable.metadata=] i.{core::num::+}([@vm.direct-call.metadata=#lib::_Vector::_offset] [@vm.inferred-type.metadata=!] this.{self::_Vector::_offset}), value);
|
||||
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=!] this.{self::_Vector::_offset} in throw "Attempt to execute code removed by Dart AOT compiler (TFA)";
|
||||
}
|
||||
operator *([@vm.inferred-type.metadata=#lib::_Vector?] self::_Vector a) → core::double {
|
||||
core::double result = 0.0;
|
||||
|
@ -28,7 +25,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 #t1 = new core::Stopwatch::•() in let final dynamic #t2 = [@vm.direct-call.metadata=dart.core::Stopwatch::start] #t1.{core::Stopwatch::start}() in #t1;
|
||||
core::Stopwatch timer = let final core::Stopwatch #t4 = new core::Stopwatch::•() in let final dynamic #t5 = [@vm.direct-call.metadata=dart.core::Stopwatch::start] #t4.{core::Stopwatch::start}() in #t4;
|
||||
for (core::int i = 0; i.{core::num::<}(100000000); i = i.{core::num::+}(1)) {
|
||||
self::x = [@vm.direct-call.metadata=dart.core::_Double::+??] [@vm.inferred-type.metadata=dart.core::_Double] [@vm.inferred-type.metadata=dart.core::_Double?] self::x.{core::double::+}([@vm.direct-call.metadata=#lib::_Vector::*??] [@vm.inferred-type.metadata=dart.core::_Double] [@vm.inferred-type.metadata=#lib::_Vector?] self::v.{self::_Vector::*}([@vm.inferred-type.metadata=#lib::_Vector?] self::v));
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import self as self;
|
|||
import "dart:core" as core;
|
||||
import "dart:async" as asy;
|
||||
|
||||
class A extends core::Object {
|
||||
abstract class A extends core::Object {
|
||||
synthetic constructor •() → void
|
||||
: super core::Object::•()
|
||||
;
|
||||
|
|
|
@ -13,9 +13,6 @@ class T2 extends core::Object {
|
|||
;
|
||||
}
|
||||
abstract class A extends core::Object {
|
||||
[@vm.unreachable.metadata=] synthetic constructor •() → void
|
||||
: super core::Object::•()
|
||||
throw "TFA Error: #lib::A::";
|
||||
abstract method foo() → core::Object;
|
||||
}
|
||||
class B extends core::Object implements self::A {
|
||||
|
|
|
@ -7,11 +7,6 @@ class T1 extends core::Object {
|
|||
: super core::Object::•()
|
||||
;
|
||||
}
|
||||
class T2 extends core::Object {
|
||||
[@vm.unreachable.metadata=] synthetic constructor •() → void
|
||||
: super core::Object::•()
|
||||
throw "TFA Error: #lib::T2::";
|
||||
}
|
||||
abstract class A extends core::Object {
|
||||
synthetic constructor •() → void
|
||||
: super core::Object::•()
|
||||
|
@ -25,13 +20,6 @@ class B extends self::A {
|
|||
method foo() → dynamic
|
||||
return new self::T1::•();
|
||||
}
|
||||
class C extends core::Object implements self::B {
|
||||
[@vm.unreachable.metadata=] synthetic constructor •() → void
|
||||
: super core::Object::•()
|
||||
throw "TFA Error: #lib::C::";
|
||||
[@vm.unreachable.metadata=] method foo() → dynamic
|
||||
throw "TFA Error: #lib::C::foo";
|
||||
}
|
||||
class Intermediate extends core::Object {
|
||||
synthetic constructor •() → void
|
||||
: super core::Object::•()
|
||||
|
|
|
@ -26,11 +26,8 @@ class B extends self::A {
|
|||
return new self::T1::•();
|
||||
}
|
||||
abstract class C extends core::Object implements self::B {
|
||||
[@vm.unreachable.metadata=] synthetic constructor •() → void
|
||||
: super core::Object::•()
|
||||
throw "TFA Error: #lib::C::";
|
||||
}
|
||||
class D extends core::Object {
|
||||
abstract class D extends core::Object {
|
||||
synthetic constructor •() → void
|
||||
: super core::Object::•()
|
||||
;
|
||||
|
|
|
@ -3,9 +3,6 @@ import self as self;
|
|||
import "dart:core" as core;
|
||||
|
||||
abstract class I extends core::Object {
|
||||
[@vm.unreachable.metadata=] synthetic constructor •() → void
|
||||
: super core::Object::•()
|
||||
throw "TFA Error: #lib::I::";
|
||||
abstract method foo() → void;
|
||||
}
|
||||
class T1 extends core::Object implements self::I {
|
||||
|
|
|
@ -12,11 +12,6 @@ class T2 extends core::Object {
|
|||
: super core::Object::•()
|
||||
;
|
||||
}
|
||||
class T3 extends core::Object {
|
||||
[@vm.unreachable.metadata=] synthetic constructor •() → void
|
||||
: super core::Object::•()
|
||||
throw "TFA Error: #lib::T3::";
|
||||
}
|
||||
class T4 extends core::Object {
|
||||
synthetic constructor •() → void
|
||||
: super core::Object::•()
|
||||
|
@ -56,7 +51,7 @@ class B extends self::A {
|
|||
abstract no-such-method-forwarder method foo() → dynamic;
|
||||
abstract no-such-method-forwarder method bazz(dynamic a1, dynamic a2, dynamic a3, [dynamic a4, dynamic a5]) → dynamic;
|
||||
}
|
||||
class C extends core::Object {
|
||||
abstract class C extends core::Object {
|
||||
synthetic constructor •() → void
|
||||
: super core::Object::•()
|
||||
;
|
||||
|
@ -76,8 +71,6 @@ class E extends core::Object implements self::A {
|
|||
synthetic constructor •() → void
|
||||
: super core::Object::•()
|
||||
;
|
||||
[@vm.unreachable.metadata=] method foo() → dynamic
|
||||
throw "TFA Error: #lib::E::foo";
|
||||
method noSuchMethod(core::Invocation invocation) → dynamic {
|
||||
return new self::T4::•();
|
||||
}
|
||||
|
@ -88,8 +81,6 @@ class F extends core::Object {
|
|||
synthetic constructor •() → void
|
||||
: super core::Object::•()
|
||||
;
|
||||
[@vm.unreachable.metadata=] method twoArg(dynamic a1, dynamic a2) → dynamic
|
||||
throw "TFA Error: #lib::F::twoArg";
|
||||
method noSuchMethod(core::Invocation invocation) → dynamic {
|
||||
return new self::T2::•();
|
||||
}
|
||||
|
|
|
@ -8,13 +8,6 @@ abstract class T0 extends core::Object {
|
|||
;
|
||||
abstract method foo() → void;
|
||||
}
|
||||
class T1 extends self::T0 {
|
||||
[@vm.unreachable.metadata=] synthetic constructor •() → void
|
||||
: super self::T0::•()
|
||||
throw "TFA Error: #lib::T1::";
|
||||
[@vm.unreachable.metadata=] method foo() → void
|
||||
throw "TFA Error: #lib::T1::foo";
|
||||
}
|
||||
class T2 extends self::T0 {
|
||||
synthetic constructor •() → void
|
||||
: super self::T0::•()
|
||||
|
@ -30,9 +23,6 @@ class A extends core::Object {
|
|||
}
|
||||
}
|
||||
abstract class B extends core::Object {
|
||||
[@vm.unreachable.metadata=] synthetic constructor •() → void
|
||||
: super core::Object::•()
|
||||
throw "TFA Error: #lib::B::";
|
||||
abstract method method2(covariant dynamic arg) → void;
|
||||
}
|
||||
class C extends core::Object implements self::B {
|
||||
|
|
|
@ -6,7 +6,6 @@ abstract class A extends core::Object {
|
|||
synthetic constructor •() → void
|
||||
: super core::Object::•()
|
||||
;
|
||||
abstract method foo() → core::int;
|
||||
}
|
||||
class B extends self::A {
|
||||
synthetic constructor •() → void
|
||||
|
@ -15,13 +14,6 @@ class B extends self::A {
|
|||
method foo() → core::int
|
||||
return 1.{core::num::+}([@vm.inferred-type.metadata=#lib::B] self::knownResult().foo() as{TypeError} core::num) as{TypeError} core::int;
|
||||
}
|
||||
class C extends core::Object implements self::A {
|
||||
[@vm.unreachable.metadata=] synthetic constructor •() → void
|
||||
: super core::Object::•()
|
||||
throw "TFA Error: #lib::C::";
|
||||
[@vm.unreachable.metadata=] method foo() → core::int
|
||||
throw "TFA Error: #lib::C::foo";
|
||||
}
|
||||
class TearOffDynamicMethod extends core::Object {
|
||||
field dynamic bazz;
|
||||
constructor •(dynamic arg) → void
|
||||
|
@ -29,7 +21,6 @@ class TearOffDynamicMethod extends core::Object {
|
|||
this.{self::TearOffDynamicMethod::bazz}();
|
||||
}
|
||||
}
|
||||
[@vm.unreachable.metadata=]static field self::A aa = throw "TFA Error: #lib::aa";
|
||||
static method knownResult() → dynamic
|
||||
return new self::B::•();
|
||||
static method main(core::List<core::String> args) → dynamic {
|
||||
|
|
|
@ -15,20 +15,12 @@ class B extends self::A {
|
|||
method foo() → core::int
|
||||
return 1.{core::num::+}([@vm.inferred-type.metadata=#lib::B] self::knownResult().foo() as{TypeError} core::num) as{TypeError} core::int;
|
||||
}
|
||||
class C extends core::Object implements self::A {
|
||||
[@vm.unreachable.metadata=] synthetic constructor •() → void
|
||||
: super core::Object::•()
|
||||
throw "TFA Error: #lib::C::";
|
||||
[@vm.unreachable.metadata=] method foo() → core::int
|
||||
throw "TFA Error: #lib::C::foo";
|
||||
}
|
||||
class TearOffInterfaceMethod extends core::Object {
|
||||
field dynamic bazz;
|
||||
constructor •([@vm.inferred-type.metadata=#lib::B] self::A arg) → void
|
||||
: self::TearOffInterfaceMethod::bazz = arg.{self::A::foo}, super core::Object::•()
|
||||
;
|
||||
}
|
||||
[@vm.unreachable.metadata=]static field self::A aa = throw "TFA Error: #lib::aa";
|
||||
static method knownResult() → dynamic
|
||||
return new self::B::•();
|
||||
static method main(core::List<core::String> args) → dynamic {
|
||||
|
|
|
@ -15,14 +15,7 @@ class B extends self::A {
|
|||
method foo() → core::int
|
||||
return 1.{core::num::+}([@vm.inferred-type.metadata=#lib::B] self::knownResult().foo() as{TypeError} core::num) as{TypeError} core::int;
|
||||
}
|
||||
class C extends core::Object implements self::A {
|
||||
[@vm.unreachable.metadata=] synthetic constructor •() → void
|
||||
: super core::Object::•()
|
||||
throw "TFA Error: #lib::C::";
|
||||
[@vm.unreachable.metadata=] method foo() → core::int
|
||||
throw "TFA Error: #lib::C::foo";
|
||||
}
|
||||
class Base extends core::Object {
|
||||
abstract class Base extends core::Object {
|
||||
synthetic constructor •() → void
|
||||
: super core::Object::•()
|
||||
;
|
||||
|
@ -35,8 +28,6 @@ class TearOffSuperMethod extends self::Base {
|
|||
synthetic constructor •() → void
|
||||
: super self::Base::•()
|
||||
;
|
||||
[@vm.unreachable.metadata=] method foo() → core::int
|
||||
throw "TFA Error: #lib::TearOffSuperMethod::foo";
|
||||
method bar() → core::int
|
||||
return [@vm.direct-call.metadata=#lib::Base::doCall] this.{self::Base::doCall}(super.{self::Base::foo});
|
||||
}
|
||||
|
|
|
@ -3,29 +3,18 @@ import self as self;
|
|||
import "dart:core" as core;
|
||||
|
||||
abstract class I extends core::Object {
|
||||
[@vm.unreachable.metadata=] synthetic constructor •() → void
|
||||
: super core::Object::•()
|
||||
throw "TFA Error: #lib::I::";
|
||||
abstract method foo(dynamic x) → void;
|
||||
}
|
||||
class A extends core::Object implements self::I {
|
||||
[@vm.unreachable.metadata=] synthetic constructor •() → void
|
||||
: super core::Object::•()
|
||||
throw "TFA Error: #lib::A::";
|
||||
[@vm.unreachable.metadata=] method foo(dynamic x) → void
|
||||
throw "TFA Error: #lib::A::foo";
|
||||
abstract class A extends core::Object implements self::I {
|
||||
}
|
||||
class B extends core::Object implements self::I {
|
||||
synthetic constructor •() → void
|
||||
: super core::Object::•()
|
||||
;
|
||||
[@vm.unreachable.metadata=] method foo(dynamic x) → void
|
||||
throw "TFA Error: #lib::B::foo";
|
||||
}
|
||||
[@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) {
|
||||
[@vm.unreachable.metadata=] i{self::A}.{self::A::foo}(42);
|
||||
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 {
|
||||
|
|
|
@ -2692,8 +2692,9 @@ void ClassFinalizer::FinalizeClass(const Class& cls) {
|
|||
// Every class should have at least a constructor, unless it is a top level
|
||||
// class or a typedef class. The Kernel frontend does not create an implicit
|
||||
// constructor for abstract classes.
|
||||
ASSERT(cls.IsTopLevel() || cls.IsTypedefClass() || cls.is_abstract() ||
|
||||
(Array::Handle(cls.functions()).Length() > 0));
|
||||
// Moreover, Dart 2 precompiler (TFA) can tree shake all members if unused.
|
||||
ASSERT(FLAG_precompiled_mode || cls.IsTopLevel() || cls.IsTypedefClass() ||
|
||||
cls.is_abstract() || (Array::Handle(cls.functions()).Length() > 0));
|
||||
// Resolve and finalize all member types.
|
||||
ResolveAndFinalizeMemberTypes(cls);
|
||||
// Run additional checks after all types are finalized.
|
||||
|
|
|
@ -71,30 +71,34 @@ void Intrinsifier::InitializeState() {
|
|||
Error& error = Error::Handle(zone);
|
||||
|
||||
#define SETUP_FUNCTION(class_name, function_name, destination, type, fp) \
|
||||
func = Function::null(); \
|
||||
if (strcmp(#class_name, "::") == 0) { \
|
||||
str = String::New(#function_name); \
|
||||
func = lib.LookupFunctionAllowPrivate(str); \
|
||||
} else { \
|
||||
str = String::New(#class_name); \
|
||||
cls = lib.LookupClassAllowPrivate(str); \
|
||||
ASSERT(!cls.IsNull()); \
|
||||
error = cls.EnsureIsFinalized(thread); \
|
||||
if (!error.IsNull()) { \
|
||||
OS::PrintErr("%s\n", error.ToErrorCString()); \
|
||||
ASSERT(FLAG_precompiled_mode || !cls.IsNull()); \
|
||||
if (!cls.IsNull()) { \
|
||||
error = cls.EnsureIsFinalized(thread); \
|
||||
if (!error.IsNull()) { \
|
||||
OS::PrintErr("%s\n", error.ToErrorCString()); \
|
||||
} \
|
||||
ASSERT(error.IsNull()); \
|
||||
if (#function_name[0] == '.') { \
|
||||
str = String::New(#class_name #function_name); \
|
||||
} else { \
|
||||
str = String::New(#function_name); \
|
||||
} \
|
||||
func = cls.LookupFunctionAllowPrivate(str); \
|
||||
} \
|
||||
ASSERT(error.IsNull()); \
|
||||
if (#function_name[0] == '.') { \
|
||||
str = String::New(#class_name #function_name); \
|
||||
} else { \
|
||||
str = String::New(#function_name); \
|
||||
} \
|
||||
func = cls.LookupFunctionAllowPrivate(str); \
|
||||
} \
|
||||
if (func.IsNull()) { \
|
||||
if (!func.IsNull()) { \
|
||||
func.set_is_intrinsic(true); \
|
||||
} else if (!FLAG_precompiled_mode) { \
|
||||
FATAL2("Intrinsifier failed to find method %s in class %s\n", \
|
||||
#function_name, #class_name); \
|
||||
} \
|
||||
func.set_is_intrinsic(true);
|
||||
}
|
||||
|
||||
// Set up all core lib functions that can be intrinsified.
|
||||
lib = Library::CoreLibrary();
|
||||
|
|
|
@ -136,23 +136,25 @@ void MethodRecognizer::InitializeState() {
|
|||
|
||||
#define SET_RECOGNIZED_KIND(class_name, function_name, enum_name, type, fp) \
|
||||
func = Library::GetFunction(libs, #class_name, #function_name); \
|
||||
if (func.IsNull()) { \
|
||||
if (!func.IsNull()) { \
|
||||
CHECK_FINGERPRINT3(func, class_name, function_name, enum_name, fp); \
|
||||
func.set_recognized_kind(k##enum_name); \
|
||||
} else if (!FLAG_precompiled_mode) { \
|
||||
OS::PrintErr("Missing %s::%s\n", #class_name, #function_name); \
|
||||
UNREACHABLE(); \
|
||||
} \
|
||||
CHECK_FINGERPRINT3(func, class_name, function_name, enum_name, fp); \
|
||||
func.set_recognized_kind(k##enum_name);
|
||||
}
|
||||
|
||||
RECOGNIZED_LIST(SET_RECOGNIZED_KIND);
|
||||
|
||||
#define SET_FUNCTION_BIT(class_name, function_name, dest, fp, setter, value) \
|
||||
func = Library::GetFunction(libs, #class_name, #function_name); \
|
||||
if (func.IsNull()) { \
|
||||
if (!func.IsNull()) { \
|
||||
CHECK_FINGERPRINT3(func, class_name, function_name, dest, fp); \
|
||||
func.setter(value); \
|
||||
} else if (!FLAG_precompiled_mode) { \
|
||||
OS::PrintErr("Missing %s::%s\n", #class_name, #function_name); \
|
||||
UNREACHABLE(); \
|
||||
} \
|
||||
CHECK_FINGERPRINT3(func, class_name, function_name, dest, fp); \
|
||||
func.setter(value);
|
||||
}
|
||||
|
||||
#define SET_IS_ALWAYS_INLINE(class_name, function_name, dest, fp) \
|
||||
SET_FUNCTION_BIT(class_name, function_name, dest, fp, set_always_inline, true)
|
||||
|
|
|
@ -925,10 +925,14 @@ assertion_initializer_const_error_test/01: Pass
|
|||
async_star_cancel_while_paused_test: RuntimeError
|
||||
async_star_pause_test: Fail, OK
|
||||
async_star_test/02: RuntimeError, CompileTimeError # Issue 31402 (Invocation arguments)
|
||||
bad_override_test/03: MissingCompileTimeError # Issue 32613: override check is missing in CFE.
|
||||
bad_override_test/04: MissingCompileTimeError # Issue 32613: override check is missing in CFE.
|
||||
bad_override_test/05: MissingCompileTimeError # Issue 32613: override check is missing in CFE.
|
||||
built_in_identifier_prefix_test: CompileTimeError
|
||||
call_method_must_not_be_field_test/03: RuntimeError # Issue 32265
|
||||
call_method_must_not_be_getter_test/03: RuntimeError # Issue 32265
|
||||
call_with_no_such_method_test: RuntimeError
|
||||
check_member_static_test/02: MissingCompileTimeError # Issue 32613: override check is missing in CFE.
|
||||
checked_setter3_test/01: MissingCompileTimeError
|
||||
checked_setter3_test/02: MissingCompileTimeError
|
||||
checked_setter3_test/03: MissingCompileTimeError
|
||||
|
@ -983,8 +987,15 @@ external_test/10: MissingRuntimeError # KernelVM bug: Unbound external.
|
|||
external_test/13: MissingRuntimeError # KernelVM bug: Unbound external.
|
||||
external_test/20: MissingRuntimeError # KernelVM bug: Unbound external.
|
||||
factory3_test/01: Pass
|
||||
fauxverride_test/03: MissingCompileTimeError # Issue 32613: override check is missing in CFE.
|
||||
fauxverride_test/05: MissingCompileTimeError # Issue 32613: override check is missing in CFE.
|
||||
field_increment_bailout_test: SkipByDesign
|
||||
field_initialization_order_test: Fail, OK
|
||||
field_override3_test/00: MissingCompileTimeError # Issue 32613: override check is missing in CFE.
|
||||
field_override3_test/01: MissingCompileTimeError # Issue 32613: override check is missing in CFE.
|
||||
field_override3_test/02: MissingCompileTimeError # Issue 32613: override check is missing in CFE.
|
||||
field_override3_test/03: MissingCompileTimeError # Issue 32613: override check is missing in CFE.
|
||||
field_override4_test/02: MissingCompileTimeError # Issue 32613: override check is missing in CFE.
|
||||
flatten_test/05: MissingRuntimeError
|
||||
flatten_test/08: MissingRuntimeError
|
||||
flatten_test/09: MissingRuntimeError
|
||||
|
@ -1007,6 +1018,10 @@ generic_methods_reuse_type_variables_test: Pass
|
|||
generic_no_such_method_dispatcher_simple_test: CompileTimeError # Issue 31533
|
||||
generic_no_such_method_dispatcher_test: CompileTimeError # Issue 31533
|
||||
generic_tearoff_test: CompileTimeError
|
||||
getter_override2_test/02: MissingCompileTimeError # Issue 32613: override check is missing in CFE.
|
||||
getter_override_test/00: MissingCompileTimeError # Issue 32613: override check is missing in CFE.
|
||||
getter_override_test/01: MissingCompileTimeError # Issue 32613: override check is missing in CFE.
|
||||
getter_override_test/02: MissingCompileTimeError # Issue 32613: override check is missing in CFE.
|
||||
hello_dart_test: Skip # Incompatible flag: --compile_all
|
||||
implicit_closure_test: Skip # Incompatible flag: --use_slow_path
|
||||
implicit_creation/implicit_new_constructor_generic_test: Pass
|
||||
|
@ -1064,6 +1079,10 @@ method_override5_test/03: MissingCompileTimeError
|
|||
method_override6_test/01: MissingCompileTimeError
|
||||
method_override6_test/02: MissingCompileTimeError
|
||||
method_override6_test/03: MissingCompileTimeError
|
||||
method_override7_test/00: MissingCompileTimeError # Issue 32613: override check is missing in CFE.
|
||||
method_override7_test/01: MissingCompileTimeError # Issue 32613: override check is missing in CFE.
|
||||
method_override7_test/02: MissingCompileTimeError # Issue 32613: override check is missing in CFE.
|
||||
method_override8_test/01: MissingCompileTimeError # Issue 32613: override check is missing in CFE.
|
||||
method_override_test: CompileTimeError # Issue 31616
|
||||
mixin_illegal_super_use_test: Skip # Issues 24478 and 23773
|
||||
mixin_illegal_superclass_test: Skip # Issues 24478 and 23773
|
||||
|
@ -1077,6 +1096,10 @@ null_no_such_method_test: CompileTimeError # Issue 31533
|
|||
null_test/mirrors: Skip # Uses mirrors.
|
||||
null_test/none: SkipByDesign
|
||||
overridden_no_such_method_test: SkipByDesign
|
||||
override_field_method2_negative_test: Fail # Issue 32613: override check is missing in CFE.
|
||||
override_field_method4_negative_test: Fail # Issue 32613: override check is missing in CFE.
|
||||
override_field_method5_negative_test: Fail # Issue 32613: override check is missing in CFE.
|
||||
override_field_test/01: MissingCompileTimeError # Issue 32613: override check is missing in CFE.
|
||||
override_inheritance_field_test/04: CompileTimeError # Issue 31616
|
||||
override_inheritance_field_test/06: CompileTimeError # Issue 31616
|
||||
override_inheritance_field_test/26: CompileTimeError # Issue 31616
|
||||
|
@ -1084,6 +1107,11 @@ override_inheritance_field_test/29: CompileTimeError # Issue 31616
|
|||
override_inheritance_generic_test/02: CompileTimeError
|
||||
override_inheritance_method_test/28: CompileTimeError
|
||||
override_inheritance_method_test/29: CompileTimeError
|
||||
override_inheritance_mixed_test/01: MissingCompileTimeError # Issue 32613: override check is missing in CFE.
|
||||
override_inheritance_mixed_test/02: MissingCompileTimeError # Issue 32613: override check is missing in CFE.
|
||||
override_inheritance_mixed_test/03: MissingCompileTimeError # Issue 32613: override check is missing in CFE.
|
||||
override_inheritance_mixed_test/04: MissingCompileTimeError # Issue 32613: override check is missing in CFE.
|
||||
override_inheritance_mixed_test/08: MissingCompileTimeError # Issue 32613: override check is missing in CFE.
|
||||
override_inheritance_mixed_test/08: Pass # Correctly passes.
|
||||
parser_quirks_test: CompileTimeError # Issue 31533
|
||||
redirecting_factory_infinite_steps_test/01: MissingCompileTimeError
|
||||
|
@ -1101,6 +1129,8 @@ regress_29025_test: CompileTimeError # Issue 31402 (Variable declaration)
|
|||
regress_29405_test: CompileTimeError # Issue 31402 (Invocation arguments)
|
||||
regress_30339_test: CompileTimeError # Issue 31402 (Variable declaration)
|
||||
setter_no_getter_test/01: Pass, CompileTimeError # Issue 31533 (started passing after switching to batch-mode)
|
||||
setter_override_test/00: MissingCompileTimeError # Issue 32613: override check is missing in CFE.
|
||||
setter_override_test/03: MissingCompileTimeError # Issue 32613: override check is missing in CFE.
|
||||
stacktrace_demangle_ctors_test: RuntimeError
|
||||
string_interpolate_test: CompileTimeError # Issue 31533
|
||||
string_interpolation_and_buffer_test: RuntimeError # Issue 31402 (Return and yield statements)
|
||||
|
|
Loading…
Reference in a new issue