| OLD | NEW |
| 1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library kernel.tree_shaker; | 5 library kernel.tree_shaker; |
| 6 | 6 |
| 7 import '../ast.dart'; | 7 import '../ast.dart'; |
| 8 import '../class_hierarchy.dart'; | 8 import '../class_hierarchy.dart'; |
| 9 import '../core_types.dart'; | 9 import '../core_types.dart'; |
| 10 import '../type_environment.dart'; | 10 import '../type_environment.dart'; |
| 11 | 11 |
| 12 Program transformProgram(Program program) { | 12 Program transformProgram(Program program, {List<ProgramRoot> programRoots}) { |
| 13 new TreeShaker(program).transform(program); | 13 new TreeShaker(program, programRoots: programRoots).transform(program); |
| 14 return program; | 14 return program; |
| 15 } | 15 } |
| 16 | 16 |
| 17 enum ProgramRootKind { |
| 18 /// The root is a class which will be instantiated by |
| 19 /// external / non-Dart code. |
| 20 ExternallyInstantiatedClass, |
| 21 |
| 22 /// The root is a setter function or a field. |
| 23 Setter, |
| 24 |
| 25 /// The root is a getter function or a field. |
| 26 Getter, |
| 27 |
| 28 /// The root is some kind of constructor. |
| 29 Constructor, |
| 30 |
| 31 /// The root is a field, normal procedure or constructor. |
| 32 Other, |
| 33 } |
| 34 |
| 35 /// A program root which the vm or embedder uses and needs to be retained. |
| 36 class ProgramRoot { |
| 37 /// The library the root is contained in. |
| 38 final String library; |
| 39 |
| 40 /// The name of the class inside the library (optional). |
| 41 final String klass; |
| 42 |
| 43 /// The name of the member inside the library (or class, optional). |
| 44 final String member; |
| 45 |
| 46 /// The kind of this program root. |
| 47 final ProgramRootKind kind; |
| 48 |
| 49 ProgramRoot(this.library, this.klass, this.member, this.kind); |
| 50 |
| 51 String toString() => "ProgramRoot($library, $klass, $member, $kind)"; |
| 52 } |
| 53 |
| 17 /// Tree shaking based on class hierarchy analysis. | 54 /// Tree shaking based on class hierarchy analysis. |
| 18 /// | 55 /// |
| 19 /// Any dynamic dispatch not on `this` is conservatively assumed to target | 56 /// Any dynamic dispatch not on `this` is conservatively assumed to target |
| 20 /// any instantiated class that implements a member matching the selector. | 57 /// any instantiated class that implements a member matching the selector. |
| 21 /// | 58 /// |
| 22 /// Member bodies are analyzed relative to a given "host class" which is the | 59 /// Member bodies are analyzed relative to a given "host class" which is the |
| 23 /// concrete type of `this` (or null if in static context), so dispatches on | 60 /// concrete type of `this` (or null if in static context), so dispatches on |
| 24 /// `this` can be resolved more precisely. | 61 /// `this` can be resolved more precisely. |
| 25 /// | 62 /// |
| 26 /// The tree shaker computes the following in a fixed-point iteration: | 63 /// The tree shaker computes the following in a fixed-point iteration: |
| 27 /// - a set of instantiated classes | 64 /// - a set of instantiated classes |
| 28 /// - for each member, a set of potential host classes | 65 /// - for each member, a set of potential host classes |
| 29 /// - a set of names used in dynamic dispatch not on `this` | 66 /// - a set of names used in dynamic dispatch not on `this` |
| 30 /// | 67 /// |
| 31 /// If the `dart:mirrors` library is used then nothing will be tree-shaken. | 68 /// If the `dart:mirrors` library is used then nothing will be tree-shaken. |
| 32 // | 69 // |
| 33 // TODO(asgerf): Shake off parts of the core libraries based on the Target. | |
| 34 // TODO(asgerf): Tree shake unused instance fields. | 70 // TODO(asgerf): Tree shake unused instance fields. |
| 35 class TreeShaker { | 71 class TreeShaker { |
| 36 final Program program; | 72 final Program program; |
| 37 final ClassHierarchy hierarchy; | 73 final ClassHierarchy hierarchy; |
| 38 final CoreTypes coreTypes; | 74 final CoreTypes coreTypes; |
| 39 final bool strongMode; | 75 final bool strongMode; |
| 76 final List<ProgramRoot> programRoots; |
| 40 | 77 |
| 41 /// Map from classes to set of names that have been dispatched with that class | 78 /// Map from classes to set of names that have been dispatched with that class |
| 42 /// as the static receiver type (meaning any subtype of that class can be | 79 /// as the static receiver type (meaning any subtype of that class can be |
| 43 /// the potential concrete receiver). | 80 /// the potential concrete receiver). |
| 44 /// | 81 /// |
| 45 /// The map is implemented as a list, indexed by | 82 /// The map is implemented as a list, indexed by |
| 46 /// [ClassHierarchy.getClassIndex]. | 83 /// [ClassHierarchy.getClassIndex]. |
| 47 final List<Set<Name>> _dispatchedNames; | 84 final List<Set<Name>> _dispatchedNames; |
| 48 | 85 |
| 49 /// Map from names to the set of classes that might be the concrete receiver | 86 /// Map from names to the set of classes that might be the concrete receiver |
| (...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 100 /// AST visitor for analyzing type annotations on external members. | 137 /// AST visitor for analyzing type annotations on external members. |
| 101 _ExternalTypeVisitor _covariantVisitor; | 138 _ExternalTypeVisitor _covariantVisitor; |
| 102 _ExternalTypeVisitor _contravariantVisitor; | 139 _ExternalTypeVisitor _contravariantVisitor; |
| 103 _ExternalTypeVisitor _invariantVisitor; | 140 _ExternalTypeVisitor _invariantVisitor; |
| 104 | 141 |
| 105 Library _mirrorsLibrary; | 142 Library _mirrorsLibrary; |
| 106 | 143 |
| 107 /// Set to true if any use of the `dart:mirrors` API is found. | 144 /// Set to true if any use of the `dart:mirrors` API is found. |
| 108 bool isUsingMirrors = false; | 145 bool isUsingMirrors = false; |
| 109 | 146 |
| 147 /// If we have roots, we will shake, even if we encounter some elements from |
| 148 /// the mirrors library. |
| 149 bool get forceShaking => programRoots != null && programRoots.isNotEmpty; |
| 150 |
| 110 TreeShaker(Program program, | 151 TreeShaker(Program program, |
| 111 {ClassHierarchy hierarchy, CoreTypes coreTypes, bool strongMode: false}) | 152 {ClassHierarchy hierarchy, |
| 153 CoreTypes coreTypes, |
| 154 bool strongMode: false, |
| 155 List<ProgramRoot> programRoots}) |
| 112 : this._internal(program, hierarchy ?? new ClassHierarchy(program), | 156 : this._internal(program, hierarchy ?? new ClassHierarchy(program), |
| 113 coreTypes ?? new CoreTypes(program), strongMode); | 157 coreTypes ?? new CoreTypes(program), strongMode, programRoots); |
| 114 | 158 |
| 115 bool isMemberBodyUsed(Member member) { | 159 bool isMemberBodyUsed(Member member) { |
| 116 return _usedMembers.containsKey(member); | 160 return _usedMembers.containsKey(member); |
| 117 } | 161 } |
| 118 | 162 |
| 119 bool isMemberOverridden(Member member) { | 163 bool isMemberOverridden(Member member) { |
| 120 return _overriddenMembers.contains(member); | 164 return _overriddenMembers.contains(member); |
| 121 } | 165 } |
| 122 | 166 |
| 123 bool isMemberUsed(Member member) { | 167 bool isMemberUsed(Member member) { |
| (...skipping 14 matching lines...) Expand all Loading... |
| 138 } | 182 } |
| 139 | 183 |
| 140 /// Applies the tree shaking results to the program. | 184 /// Applies the tree shaking results to the program. |
| 141 /// | 185 /// |
| 142 /// This removes unused classes, members, and hierarchy data. | 186 /// This removes unused classes, members, and hierarchy data. |
| 143 void transform(Program program) { | 187 void transform(Program program) { |
| 144 if (isUsingMirrors) return; // Give up if using mirrors. | 188 if (isUsingMirrors) return; // Give up if using mirrors. |
| 145 new _TreeShakingTransformer(this).transform(program); | 189 new _TreeShakingTransformer(this).transform(program); |
| 146 } | 190 } |
| 147 | 191 |
| 148 TreeShaker._internal( | 192 TreeShaker._internal(this.program, ClassHierarchy hierarchy, this.coreTypes, |
| 149 this.program, ClassHierarchy hierarchy, this.coreTypes, this.strongMode) | 193 this.strongMode, this.programRoots) |
| 150 : this.hierarchy = hierarchy, | 194 : this.hierarchy = hierarchy, |
| 151 this._dispatchedNames = new List<Set<Name>>(hierarchy.classes.length), | 195 this._dispatchedNames = new List<Set<Name>>(hierarchy.classes.length), |
| 152 this._usedMembersWithHost = | 196 this._usedMembersWithHost = |
| 153 new List<Set<Member>>(hierarchy.classes.length), | 197 new List<Set<Member>>(hierarchy.classes.length), |
| 154 this._classRetention = new List<ClassRetention>.filled( | 198 this._classRetention = new List<ClassRetention>.filled( |
| 155 hierarchy.classes.length, ClassRetention.None) { | 199 hierarchy.classes.length, ClassRetention.None) { |
| 156 _visitor = new _TreeShakerVisitor(this); | 200 _visitor = new _TreeShakerVisitor(this); |
| 157 _covariantVisitor = new _ExternalTypeVisitor(this, isCovariant: true); | 201 _covariantVisitor = new _ExternalTypeVisitor(this, isCovariant: true); |
| 158 _contravariantVisitor = | 202 _contravariantVisitor = |
| 159 new _ExternalTypeVisitor(this, isContravariant: true); | 203 new _ExternalTypeVisitor(this, isContravariant: true); |
| (...skipping 12 matching lines...) Expand all Loading... |
| 172 throw 'Cannot perform tree shaking on a program without a main method'; | 216 throw 'Cannot perform tree shaking on a program without a main method'; |
| 173 } | 217 } |
| 174 if (program.mainMethod.function.positionalParameters.length > 0) { | 218 if (program.mainMethod.function.positionalParameters.length > 0) { |
| 175 // The main method takes a List<String> as argument. | 219 // The main method takes a List<String> as argument. |
| 176 _addInstantiatedExternalSubclass(coreTypes.listClass); | 220 _addInstantiatedExternalSubclass(coreTypes.listClass); |
| 177 _addInstantiatedExternalSubclass(coreTypes.stringClass); | 221 _addInstantiatedExternalSubclass(coreTypes.stringClass); |
| 178 } | 222 } |
| 179 _addDispatchedName(hierarchy.rootClass, new Name('noSuchMethod')); | 223 _addDispatchedName(hierarchy.rootClass, new Name('noSuchMethod')); |
| 180 _addPervasiveUses(); | 224 _addPervasiveUses(); |
| 181 _addUsedMember(null, program.mainMethod); | 225 _addUsedMember(null, program.mainMethod); |
| 226 programRoots?.forEach(_addUsedRoot); |
| 227 |
| 182 _iterateWorklist(); | 228 _iterateWorklist(); |
| 183 | 229 |
| 184 // Mark overridden members in order to preserve abstract members as | 230 // Mark overridden members in order to preserve abstract members as |
| 185 // necessary. | 231 // necessary. |
| 186 if (strongMode) { | 232 if (strongMode) { |
| 187 for (int i = hierarchy.classes.length - 1; i >= 0; --i) { | 233 for (int i = hierarchy.classes.length - 1; i >= 0; --i) { |
| 188 Class class_ = hierarchy.classes[i]; | 234 Class class_ = hierarchy.classes[i]; |
| 189 if (isHierarchyUsed(class_)) { | 235 if (isHierarchyUsed(class_)) { |
| 190 hierarchy.forEachOverridePair(class_, | 236 hierarchy.forEachOverridePair(class_, |
| 191 (Member ownMember, Member superMember, bool isSetter) { | 237 (Member ownMember, Member superMember, bool isSetter) { |
| (...skipping 171 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 363 /// | 409 /// |
| 364 /// Ensures that all annotations on the class are analyzed. | 410 /// Ensures that all annotations on the class are analyzed. |
| 365 void _propagateClassNamespaceLevel( | 411 void _propagateClassNamespaceLevel( |
| 366 Class classNode, ClassRetention oldRetention) { | 412 Class classNode, ClassRetention oldRetention) { |
| 367 if (oldRetention.index >= ClassRetention.Namespace.index) { | 413 if (oldRetention.index >= ClassRetention.Namespace.index) { |
| 368 return; | 414 return; |
| 369 } | 415 } |
| 370 visitList(classNode.annotations, _visitor); | 416 visitList(classNode.annotations, _visitor); |
| 371 } | 417 } |
| 372 | 418 |
| 419 /// Registers the given root as being used. |
| 420 void _addUsedRoot(ProgramRoot root) { |
| 421 Library rootLibrary = _findLibraryRoot(root, program); |
| 422 |
| 423 if (root.kind == ProgramRootKind.ExternallyInstantiatedClass) { |
| 424 Class rootClass = _findClassRoot(root, rootLibrary); |
| 425 |
| 426 // This is a class which will be instantiated by non-Dart code (whether it |
| 427 // has a valid generative construtor or not). |
| 428 _addInstantiatedClass(rootClass); |
| 429 |
| 430 // We keep all the constructors of externally instantiated classes. |
| 431 // Sometimes the runtime might do a constructor call and sometimes it |
| 432 // might just allocate the class without invoking the constructor. |
| 433 // So we try to be on the safe side here! |
| 434 for (var constructor in rootClass.constructors) { |
| 435 _addUsedMember(rootClass, constructor); |
| 436 } |
| 437 |
| 438 // We keep all factory constructors as well for the same reason. |
| 439 for (var member in rootClass.procedures) { |
| 440 if (member.isStatic && member.kind == ProcedureKind.Factory) { |
| 441 _addUsedMember(rootClass, member); |
| 442 } |
| 443 } |
| 444 } else { |
| 445 if (root.klass != null) { |
| 446 // For class members we mark the Field/Procedure/Constructor as used. |
| 447 // We also mark it as instantiated if it's a constructor. |
| 448 Class rootClass = _findClassRoot(root, rootLibrary); |
| 449 Member rootMember = _findMemberRoot(root, rootClass.members); |
| 450 _addUsedMember(rootClass, rootMember); |
| 451 if (rootMember is Constructor) { |
| 452 _addInstantiatedClass(rootClass); |
| 453 } |
| 454 } else { |
| 455 // For library members we mark the Field/Procedure as used. |
| 456 Member rootMember = _findMemberRoot(root, rootLibrary.members); |
| 457 _addUsedMember(null, rootMember); |
| 458 } |
| 459 } |
| 460 } |
| 461 |
| 373 /// Registers the given class as being used in a type annotation. | 462 /// Registers the given class as being used in a type annotation. |
| 374 void _addClassUsedInType(Class classNode) { | 463 void _addClassUsedInType(Class classNode) { |
| 375 int index = hierarchy.getClassIndex(classNode); | 464 int index = hierarchy.getClassIndex(classNode); |
| 376 ClassRetention retention = _classRetention[index]; | 465 ClassRetention retention = _classRetention[index]; |
| 377 if (retention.index < ClassRetention.Hierarchy.index) { | 466 if (retention.index < ClassRetention.Hierarchy.index) { |
| 378 _classRetention[index] = ClassRetention.Hierarchy; | 467 _classRetention[index] = ClassRetention.Hierarchy; |
| 379 _propagateClassHierarchyLevel(classNode, retention); | 468 _propagateClassHierarchyLevel(classNode, retention); |
| 380 } | 469 } |
| 381 } | 470 } |
| 382 | 471 |
| (...skipping 10 matching lines...) Expand all Loading... |
| 393 } | 482 } |
| 394 } | 483 } |
| 395 | 484 |
| 396 /// Registers the given member as being used, in the following sense: | 485 /// Registers the given member as being used, in the following sense: |
| 397 /// - Fields are used if they can be read or written or their initializer is | 486 /// - Fields are used if they can be read or written or their initializer is |
| 398 /// evaluated. | 487 /// evaluated. |
| 399 /// - Constructors are used if they can be invoked, either directly or through | 488 /// - Constructors are used if they can be invoked, either directly or through |
| 400 /// the initializer list of another constructor. | 489 /// the initializer list of another constructor. |
| 401 /// - Procedures are used if they can be invoked or torn off. | 490 /// - Procedures are used if they can be invoked or torn off. |
| 402 void _addUsedMember(Class host, Member member) { | 491 void _addUsedMember(Class host, Member member) { |
| 403 if (member.enclosingLibrary == _mirrorsLibrary) { | 492 if (!forceShaking && member.enclosingLibrary == _mirrorsLibrary) { |
| 404 throw new _UsingMirrorsException(); | 493 throw new _UsingMirrorsException(); |
| 405 } | 494 } |
| 406 if (host != null) { | 495 if (host != null) { |
| 407 // Check if the member has been seen with this host before. | 496 // Check if the member has been seen with this host before. |
| 408 int index = hierarchy.getClassIndex(host); | 497 int index = hierarchy.getClassIndex(host); |
| 409 Set<Member> members = _usedMembersWithHost[index] ??= new Set<Member>(); | 498 Set<Member> members = _usedMembersWithHost[index] ??= new Set<Member>(); |
| 410 if (!members.add(member)) return; | 499 if (!members.add(member)) return; |
| 411 _usedMembers.putIfAbsent(member, _makeIncompleteSummary); | 500 _usedMembers.putIfAbsent(member, _makeIncompleteSummary); |
| 412 } else { | 501 } else { |
| 413 // Check if the member has been seen before. | 502 // Check if the member has been seen before. |
| (...skipping 387 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 801 final TreeShaker shaker; | 890 final TreeShaker shaker; |
| 802 | 891 |
| 803 _TreeShakingTransformer(this.shaker); | 892 _TreeShakingTransformer(this.shaker); |
| 804 | 893 |
| 805 Member _translateInterfaceTarget(Member target) { | 894 Member _translateInterfaceTarget(Member target) { |
| 806 return target != null && shaker.isMemberUsed(target) ? target : null; | 895 return target != null && shaker.isMemberUsed(target) ? target : null; |
| 807 } | 896 } |
| 808 | 897 |
| 809 void transform(Program program) { | 898 void transform(Program program) { |
| 810 for (var library in program.libraries) { | 899 for (var library in program.libraries) { |
| 811 if (library.importUri.scheme == 'dart') { | 900 if (!shaker.forceShaking && library.importUri.scheme == 'dart') { |
| 812 // The backend expects certain things to be present in the core | 901 // The backend expects certain things to be present in the core |
| 813 // libraries, so we currently don't shake off anything there. | 902 // libraries, so we currently don't shake off anything there. |
| 814 continue; | 903 continue; |
| 815 } | 904 } |
| 816 library.transformChildren(this); | 905 library.transformChildren(this); |
| 817 // Note: we can't shake off empty libraries yet since we don't check if | 906 // Note: we can't shake off empty libraries yet since we don't check if |
| 818 // there are private names that use the library. | 907 // there are private names that use the library. |
| 819 } | 908 } |
| 820 for (Expression node in shaker._typedCalls) { | 909 for (Expression node in shaker._typedCalls) { |
| 821 // We should not leave dangling references, so if the target of a typed | 910 // We should not leave dangling references, so if the target of a typed |
| (...skipping 150 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 972 classNode == coreTypes.futureClass || | 1061 classNode == coreTypes.futureClass || |
| 973 classNode == coreTypes.streamClass || | 1062 classNode == coreTypes.streamClass || |
| 974 classNode == coreTypes.listClass || | 1063 classNode == coreTypes.listClass || |
| 975 classNode == coreTypes.mapClass; | 1064 classNode == coreTypes.mapClass; |
| 976 } | 1065 } |
| 977 } | 1066 } |
| 978 | 1067 |
| 979 /// Exception that is thrown to stop the tree shaking analysis when a use | 1068 /// Exception that is thrown to stop the tree shaking analysis when a use |
| 980 /// of `dart:mirrors` is found. | 1069 /// of `dart:mirrors` is found. |
| 981 class _UsingMirrorsException {} | 1070 class _UsingMirrorsException {} |
| 1071 |
| 1072 Library _findLibraryRoot(ProgramRoot root, Program program) { |
| 1073 for (var library in program.libraries) { |
| 1074 if (library.importUri.toString() == root.library) { |
| 1075 return library; |
| 1076 } |
| 1077 } |
| 1078 |
| 1079 throw "$root not found!"; |
| 1080 } |
| 1081 |
| 1082 Class _findClassRoot(ProgramRoot root, Library rootLibrary) { |
| 1083 for (var klass in rootLibrary.classes) { |
| 1084 if (klass.name == root.klass) { |
| 1085 return klass; |
| 1086 } |
| 1087 } |
| 1088 throw "$root not found!"; |
| 1089 } |
| 1090 |
| 1091 Member _findMemberRoot(ProgramRoot root, Iterable<Member> membersToSearch) { |
| 1092 for (var member in membersToSearch) { |
| 1093 if (member.name.name == root.member) { |
| 1094 switch (root.kind) { |
| 1095 case ProgramRootKind.Constructor: |
| 1096 if (member is Procedure && member.kind == ProcedureKind.Factory || |
| 1097 member is Constructor) { |
| 1098 return member; |
| 1099 } |
| 1100 break; |
| 1101 case ProgramRootKind.Setter: |
| 1102 if (member is Procedure && member.kind == ProcedureKind.Setter || |
| 1103 member is Field) { |
| 1104 return member; |
| 1105 } |
| 1106 break; |
| 1107 case ProgramRootKind.Getter: |
| 1108 if (member is Procedure && member.kind == ProcedureKind.Getter || |
| 1109 member is Field) { |
| 1110 return member; |
| 1111 } |
| 1112 break; |
| 1113 case ProgramRootKind.Other: |
| 1114 return member; |
| 1115 default: |
| 1116 } |
| 1117 } |
| 1118 } |
| 1119 throw "$root not found!"; |
| 1120 } |
| OLD | NEW |