| 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'; | |
| 11 | 10 |
| 12 Program transformProgram(Program program) { | 11 Program transformProgram(Program program) { |
| 13 new TreeShaker(program).transform(program); | 12 new TreeShaker(program).transform(program); |
| 14 return program; | 13 return program; |
| 15 } | 14 } |
| 16 | 15 |
| 17 /// Tree shaking based on class hierarchy analysis. | 16 /// Tree shaking based on class hierarchy analysis. |
| 18 /// | 17 /// |
| 19 /// Any dynamic dispatch not on `this` is conservatively assumed to target | 18 /// Any dynamic dispatch not on `this` is conservatively assumed to target |
| 20 /// any instantiated class that implements a member matching the selector. | 19 /// any instantiated class that implements a member matching the selector. |
| 21 /// | 20 /// |
| 22 /// Member bodies are analyzed relative to a given "host class" which is the | 21 /// 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 | 22 /// concrete type of `this` (or null if in static context), so dispatches on |
| 24 /// `this` can be resolved more precisely. | 23 /// `this` can be resolved more precisely. |
| 25 /// | 24 /// |
| 26 /// The tree shaker computes the following in a fixed-point iteration: | 25 /// The tree shaker computes the following in a fixed-point iteration: |
| 27 /// - a set of instantiated classes | 26 /// - a set of instantiated classes |
| 28 /// - for each member, a set of potential host classes | 27 /// - for each member, a set of potential host classes |
| 29 /// - a set of names used in dynamic dispatch not on `this` | 28 /// - a set of names used in dynamic dispatch not on `this` |
| 30 /// | 29 /// |
| 31 /// If the `dart:mirrors` library is used then nothing will be tree-shaken. | 30 /// The `dart:mirrors` library is not supported. |
| 32 // | 31 // |
| 33 // TODO(asgerf): Shake off parts of the core libraries based on the Target. | 32 // TODO(asgerf): Shake off parts of the core libraries based on the Target. |
| 34 // TODO(asgerf): Tree shake unused instance fields. | 33 // TODO(asgerf): Tree shake unused instance fields. |
| 35 class TreeShaker { | 34 class TreeShaker { |
| 36 final Program program; | 35 final Program program; |
| 37 final ClassHierarchy hierarchy; | 36 final ClassHierarchy hierarchy; |
| 38 final CoreTypes coreTypes; | 37 final CoreTypes coreTypes; |
| 39 final bool strongMode; | |
| 40 | 38 |
| 41 /// Map from classes to set of names that have been dispatched with that class | 39 /// Names used in a dynamic dispatch invocation that could not be resolved |
| 42 /// as the static receiver type (meaning any subtype of that class can be | 40 /// to a concrete target (i.e. not on `this`). |
| 43 /// the potential concrete receiver). | 41 final Set<Name> _dispatchedNames = new Set<Name>(); |
| 44 /// | |
| 45 /// The map is implemented as a list, indexed by | |
| 46 /// [ClassHierarchy.getClassIndex]. | |
| 47 final List<Set<Name>> _dispatchedNames; | |
| 48 | |
| 49 /// Map from names to the set of classes that might be the concrete receiver | |
| 50 /// of a call with the given name. | |
| 51 final Map<Name, ClassSet> _receiversOfName = <Name, ClassSet>{}; | |
| 52 | 42 |
| 53 /// Instance members that are potential targets for dynamic dispatch, but | 43 /// Instance members that are potential targets for dynamic dispatch, but |
| 54 /// whose name has not yet been seen in a dynamic dispatch invocation. | 44 /// whose name has not yet been seen in a dynamic dispatch invocation. |
| 55 /// | 45 /// |
| 56 /// The map is indexed by the name of the member, and value is a list of | 46 /// The map is indexed by the name of the member, and value is a list of |
| 57 /// interleaved (host class, member) pairs. | 47 /// interleaved (host class, member) pairs. |
| 58 final Map<Name, List<TreeNode>> _dispatchTargetCandidates = | 48 final Map<Name, List<TreeNode>> _dispatchTargetCandidates = |
| 59 <Name, List<TreeNode>>{}; | 49 <Name, List<TreeNode>>{}; |
| 60 | 50 |
| 61 /// Map from classes to the set of members that are reachable with that | 51 /// Map from classes to the set of members that are reachable with that |
| (...skipping 18 matching lines...) Expand all Loading... |
| 80 /// See [ClassRetention]. | 70 /// See [ClassRetention]. |
| 81 final List<ClassRetention> _classRetention; | 71 final List<ClassRetention> _classRetention; |
| 82 | 72 |
| 83 /// Interleaved (host class, member) pairs that are reachable but have not yet | 73 /// Interleaved (host class, member) pairs that are reachable but have not yet |
| 84 /// been analyzed for more uses. | 74 /// been analyzed for more uses. |
| 85 final List<TreeNode> _worklist = new List<TreeNode>(); | 75 final List<TreeNode> _worklist = new List<TreeNode>(); |
| 86 | 76 |
| 87 /// Classes whose interface can be used by external code to invoke user code. | 77 /// Classes whose interface can be used by external code to invoke user code. |
| 88 final Set<Class> _escapedClasses = new Set<Class>(); | 78 final Set<Class> _escapedClasses = new Set<Class>(); |
| 89 | 79 |
| 90 /// Members that have been overridden by a member whose concrete body is | |
| 91 /// needed. These must be preserved in order to maintain interface targets | |
| 92 /// for typed calls. | |
| 93 final Set<Member> _overriddenMembers = new Set<Member>(); | |
| 94 | |
| 95 final List<Expression> _typedCalls = <Expression>[]; | |
| 96 | |
| 97 /// AST visitor for finding static uses and dynamic dispatches in code. | 80 /// AST visitor for finding static uses and dynamic dispatches in code. |
| 98 _TreeShakerVisitor _visitor; | 81 _TreeShakerVisitor _visitor; |
| 99 | 82 |
| 100 /// AST visitor for analyzing type annotations on external members. | 83 /// AST visitor for analyzing type annotations on external members. |
| 101 _ExternalTypeVisitor _covariantVisitor; | 84 _ExternalTypeVisitor _covariantVisitor; |
| 102 _ExternalTypeVisitor _contravariantVisitor; | 85 _ExternalTypeVisitor _contravariantVisitor; |
| 103 _ExternalTypeVisitor _invariantVisitor; | 86 _ExternalTypeVisitor _bivariantVisitor; |
| 104 | 87 |
| 105 Library _mirrorsLibrary; | 88 TreeShaker(Program program, {ClassHierarchy hierarchy, CoreTypes coreTypes}) |
| 89 : this._internal(program, hierarchy ?? new ClassHierarchy(program), |
| 90 coreTypes ?? new CoreTypes(program)); |
| 106 | 91 |
| 107 /// Set to true if any use of the `dart:mirrors` API is found. | 92 bool isMemberUsed(Member member) { |
| 108 bool isUsingMirrors = false; | |
| 109 | |
| 110 TreeShaker(Program program, | |
| 111 {ClassHierarchy hierarchy, CoreTypes coreTypes, bool strongMode: false}) | |
| 112 : this._internal(program, hierarchy ?? new ClassHierarchy(program), | |
| 113 coreTypes ?? new CoreTypes(program), strongMode); | |
| 114 | |
| 115 bool isMemberBodyUsed(Member member) { | |
| 116 return _usedMembers.containsKey(member); | 93 return _usedMembers.containsKey(member); |
| 117 } | 94 } |
| 118 | 95 |
| 119 bool isMemberOverridden(Member member) { | |
| 120 return _overriddenMembers.contains(member); | |
| 121 } | |
| 122 | |
| 123 bool isMemberUsed(Member member) { | |
| 124 return isMemberBodyUsed(member) || isMemberOverridden(member); | |
| 125 } | |
| 126 | |
| 127 bool isInstantiated(Class classNode) { | 96 bool isInstantiated(Class classNode) { |
| 128 return getClassRetention(classNode).index >= ClassRetention.Instance.index; | 97 return getClassRetention(classNode).index >= ClassRetention.Instance.index; |
| 129 } | 98 } |
| 130 | 99 |
| 131 bool isHierarchyUsed(Class classNode) { | 100 bool isHierarchyUsed(Class classNode) { |
| 132 return getClassRetention(classNode).index >= ClassRetention.Hierarchy.index; | 101 return getClassRetention(classNode).index >= ClassRetention.Hierarchy.index; |
| 133 } | 102 } |
| 134 | 103 |
| 135 ClassRetention getClassRetention(Class classNode) { | 104 ClassRetention getClassRetention(Class classNode) { |
| 136 int index = hierarchy.getClassIndex(classNode); | 105 int index = hierarchy.getClassIndex(classNode); |
| 137 return _classRetention[index]; | 106 return _classRetention[index]; |
| 138 } | 107 } |
| 139 | 108 |
| 140 /// Applies the tree shaking results to the program. | 109 /// Applies the tree shaking results to the program. |
| 141 /// | 110 /// |
| 142 /// This removes unused classes, members, and hierarchy data. | 111 /// This removes unused classes, members, and hierarchy data. |
| 143 void transform(Program program) { | 112 void transform(Program program) { |
| 144 if (isUsingMirrors) return; // Give up if using mirrors. | |
| 145 new _TreeShakingTransformer(this).transform(program); | 113 new _TreeShakingTransformer(this).transform(program); |
| 146 } | 114 } |
| 147 | 115 |
| 148 TreeShaker._internal( | 116 TreeShaker._internal(this.program, ClassHierarchy hierarchy, this.coreTypes) |
| 149 this.program, ClassHierarchy hierarchy, this.coreTypes, this.strongMode) | |
| 150 : this.hierarchy = hierarchy, | 117 : this.hierarchy = hierarchy, |
| 151 this._dispatchedNames = new List<Set<Name>>(hierarchy.classes.length), | |
| 152 this._usedMembersWithHost = | 118 this._usedMembersWithHost = |
| 153 new List<Set<Member>>(hierarchy.classes.length), | 119 new List<Set<Member>>(hierarchy.classes.length), |
| 154 this._classRetention = new List<ClassRetention>.filled( | 120 this._classRetention = new List<ClassRetention>.filled( |
| 155 hierarchy.classes.length, ClassRetention.None) { | 121 hierarchy.classes.length, ClassRetention.None) { |
| 156 _visitor = new _TreeShakerVisitor(this); | 122 _visitor = new _TreeShakerVisitor(this); |
| 157 _covariantVisitor = new _ExternalTypeVisitor(this, isCovariant: true); | 123 _covariantVisitor = new _ExternalTypeVisitor(this, isCovariant: true); |
| 158 _contravariantVisitor = | 124 _contravariantVisitor = |
| 159 new _ExternalTypeVisitor(this, isContravariant: true); | 125 new _ExternalTypeVisitor(this, isContravariant: true); |
| 160 _invariantVisitor = new _ExternalTypeVisitor(this, | 126 _bivariantVisitor = new _ExternalTypeVisitor(this, |
| 161 isCovariant: true, isContravariant: true); | 127 isCovariant: true, isContravariant: true); |
| 162 _mirrorsLibrary = coreTypes.getCoreLibrary('dart:mirrors'); | 128 _build(); |
| 163 try { | |
| 164 _build(); | |
| 165 } on _UsingMirrorsException { | |
| 166 isUsingMirrors = true; | |
| 167 } | |
| 168 } | 129 } |
| 169 | 130 |
| 170 void _build() { | 131 void _build() { |
| 171 if (program.mainMethod == null) { | 132 if (program.mainMethod == null) { |
| 172 throw 'Cannot perform tree shaking on a program without a main method'; | 133 throw 'Cannot perform tree shaking on a program without a main method'; |
| 173 } | 134 } |
| 174 if (program.mainMethod.function.positionalParameters.length > 0) { | 135 if (program.mainMethod.function.positionalParameters.length > 0) { |
| 175 // The main method takes a List<String> as argument. | 136 // The main method takes a List<String> as argument. |
| 176 _addInstantiatedExternalSubclass(coreTypes.listClass); | 137 _addInstantiatedExternalSubclass(coreTypes.listClass); |
| 177 _addInstantiatedExternalSubclass(coreTypes.stringClass); | 138 _addInstantiatedExternalSubclass(coreTypes.stringClass); |
| 178 } | 139 } |
| 179 _addDispatchedName(hierarchy.rootClass, new Name('noSuchMethod')); | 140 _addDispatchedName(new Name('noSuchMethod')); |
| 180 _addPervasiveUses(); | 141 _addPervasiveUses(); |
| 181 _addUsedMember(null, program.mainMethod); | 142 _addUsedMember(null, program.mainMethod); |
| 182 _iterateWorklist(); | 143 _iterateWorklist(); |
| 183 | |
| 184 // Mark overridden members in order to preserve abstract members as | |
| 185 // necessary. | |
| 186 if (strongMode) { | |
| 187 for (int i = hierarchy.classes.length - 1; i >= 0; --i) { | |
| 188 Class class_ = hierarchy.classes[i]; | |
| 189 if (isHierarchyUsed(class_)) { | |
| 190 hierarchy.forEachOverridePair(class_, | |
| 191 (Member ownMember, Member superMember, bool isSetter) { | |
| 192 if (isMemberBodyUsed(ownMember) || | |
| 193 _overriddenMembers.contains(ownMember)) { | |
| 194 _overriddenMembers.add(superMember); | |
| 195 // Ensure the types mentioned in the member can be preserved. | |
| 196 _visitor.visitMemberInterface(superMember); | |
| 197 } | |
| 198 }); | |
| 199 } | |
| 200 } | |
| 201 // Marking members as overridden should not cause new code to become | |
| 202 // reachable. | |
| 203 assert(_worklist.isEmpty); | |
| 204 } | |
| 205 } | 144 } |
| 206 | 145 |
| 207 /// Registers some extremely commonly used core classes as instantiated, so | 146 /// Registers some extremely commonly used core classes as instantiated, so |
| 208 /// we don't have to register them for every use we find. | 147 /// we don't have to register them for every use we find. |
| 209 void _addPervasiveUses() { | 148 void _addPervasiveUses() { |
| 210 _addInstantiatedExternalSubclass(coreTypes.stringClass); | 149 _addInstantiatedExternalSubclass(coreTypes.stringClass); |
| 211 _addInstantiatedExternalSubclass(coreTypes.intClass); | 150 _addInstantiatedExternalSubclass(coreTypes.intClass); |
| 212 _addInstantiatedExternalSubclass(coreTypes.boolClass); | 151 _addInstantiatedExternalSubclass(coreTypes.boolClass); |
| 213 _addInstantiatedExternalSubclass(coreTypes.nullClass); | 152 _addInstantiatedExternalSubclass(coreTypes.nullClass); |
| 214 _addInstantiatedExternalSubclass(coreTypes.functionClass); | |
| 215 _addInstantiatedExternalSubclass(coreTypes.invocationClass); | |
| 216 } | 153 } |
| 217 | 154 |
| 218 /// Registers the given name as seen in a dynamic dispatch, and discovers used | 155 /// Registers the given name as seen in a dynamic dispatch, and discovers used |
| 219 /// instance members accordingly. | 156 /// instance members accordingly. |
| 220 void _addDispatchedName(Class receiver, Name name) { | 157 void _addDispatchedName(Name name) { |
| 221 int index = hierarchy.getClassIndex(receiver); | |
| 222 Set<Name> receiverNames = _dispatchedNames[index] ??= new Set<Name>(); | |
| 223 // TODO(asgerf): make use of selector arity and getter/setter kind | 158 // TODO(asgerf): make use of selector arity and getter/setter kind |
| 224 if (receiverNames.add(name)) { | 159 if (_dispatchedNames.add(name)) { |
| 225 List<TreeNode> candidates = _dispatchTargetCandidates[name]; | 160 List<TreeNode> targets = _dispatchTargetCandidates[name]; |
| 226 if (candidates != null) { | 161 if (targets != null) { |
| 227 for (int i = 0; i < candidates.length; i += 2) { | 162 for (int i = 0; i < targets.length; i += 2) { |
| 228 Class host = candidates[i]; | 163 _addUsedMember(targets[i], targets[i + 1]); |
| 229 if (hierarchy.isSubtypeOf(host, receiver)) { | |
| 230 // This (host, member) pair is a potential target of the dispatch. | |
| 231 Member member = candidates[i + 1]; | |
| 232 | |
| 233 // Remove the (host,member) pair from the candidate list. | |
| 234 // Move the last pair into the current index and shrink the list. | |
| 235 int lastPair = candidates.length - 2; | |
| 236 candidates[i] = candidates[lastPair]; | |
| 237 candidates[i + 1] = candidates[lastPair + 1]; | |
| 238 candidates.length -= 2; | |
| 239 i -= 2; // Revisit the same index now that it has been updated. | |
| 240 | |
| 241 // Mark the pair as used. This should be done after removing it | |
| 242 // from the candidate list, since this call may recursively scan | |
| 243 // for more used members. | |
| 244 _addUsedMember(host, member); | |
| 245 } | |
| 246 } | 164 } |
| 247 } | 165 } |
| 248 var subtypes = hierarchy.getSubtypesOf(receiver); | |
| 249 var receiverSet = _receiversOfName[name]; | |
| 250 _receiversOfName[name] = receiverSet == null | |
| 251 ? subtypes | |
| 252 : _receiversOfName[name].union(subtypes); | |
| 253 } | 166 } |
| 254 } | 167 } |
| 255 | 168 |
| 256 /// Registers the given method as a potential target of dynamic dispatch on | 169 /// Registers the given method as a potential target of dynamic dispatch on |
| 257 /// the given class. | 170 /// the given class. |
| 258 void _addDispatchTarget(Class host, Member member) { | 171 void _addDispatchTarget(Class host, Member member) { |
| 259 ClassSet receivers = _receiversOfName[member.name]; | 172 if (_dispatchedNames.contains(member.name)) { |
| 260 if (receivers != null && receivers.contains(host)) { | |
| 261 _addUsedMember(host, member); | 173 _addUsedMember(host, member); |
| 262 } else { | 174 } else { |
| 263 _dispatchTargetCandidates.putIfAbsent(member.name, _makeTreeNodeList) | 175 _dispatchTargetCandidates.putIfAbsent(member.name, _makeTreeNodeList) |
| 264 ..add(host) | 176 ..add(host) |
| 265 ..add(member); | 177 ..add(member); |
| 266 } | 178 } |
| 267 } | 179 } |
| 268 | 180 |
| 269 static List<TreeNode> _makeTreeNodeList() => <TreeNode>[]; | 181 static List<TreeNode> _makeTreeNodeList() => <TreeNode>[]; |
| 270 | 182 |
| (...skipping 23 matching lines...) Expand all Loading... |
| 294 if (oldRetention.index >= ClassRetention.ExternalInstance.index) { | 206 if (oldRetention.index >= ClassRetention.ExternalInstance.index) { |
| 295 return; | 207 return; |
| 296 } | 208 } |
| 297 _propagateClassInstanceLevel(classNode, oldRetention); | 209 _propagateClassInstanceLevel(classNode, oldRetention); |
| 298 for (Member member in hierarchy.getInterfaceMembers(classNode)) { | 210 for (Member member in hierarchy.getInterfaceMembers(classNode)) { |
| 299 if (member is Field) { | 211 if (member is Field) { |
| 300 _covariantVisitor.visit(member.type); | 212 _covariantVisitor.visit(member.type); |
| 301 } else { | 213 } else { |
| 302 _addCallToExternalProcedure(member); | 214 _addCallToExternalProcedure(member); |
| 303 } | 215 } |
| 304 _addDispatchTarget(classNode, member); | |
| 305 } | |
| 306 for (Member member | |
| 307 in hierarchy.getInterfaceMembers(classNode, setters: true)) { | |
| 308 _addDispatchTarget(classNode, member); | |
| 309 } | 216 } |
| 310 } | 217 } |
| 311 | 218 |
| 312 /// Called when the retention level for [classNode] has been raised from | 219 /// Called when the retention level for [classNode] has been raised from |
| 313 /// [oldRetention] to instance level. | 220 /// [oldRetention] to instance level. |
| 314 /// | 221 /// |
| 315 /// Ensures that the relevant members are put in the worklist, and super types | 222 /// Ensures that the relevant members are put in the worklist, and super types |
| 316 /// and raised to hierarchy level. | 223 /// and raised to hierarchy level. |
| 317 void _propagateClassInstanceLevel( | 224 void _propagateClassInstanceLevel( |
| 318 Class classNode, ClassRetention oldRetention) { | 225 Class classNode, ClassRetention oldRetention) { |
| (...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 393 } | 300 } |
| 394 } | 301 } |
| 395 | 302 |
| 396 /// Registers the given member as being used, in the following sense: | 303 /// 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 | 304 /// - Fields are used if they can be read or written or their initializer is |
| 398 /// evaluated. | 305 /// evaluated. |
| 399 /// - Constructors are used if they can be invoked, either directly or through | 306 /// - Constructors are used if they can be invoked, either directly or through |
| 400 /// the initializer list of another constructor. | 307 /// the initializer list of another constructor. |
| 401 /// - Procedures are used if they can be invoked or torn off. | 308 /// - Procedures are used if they can be invoked or torn off. |
| 402 void _addUsedMember(Class host, Member member) { | 309 void _addUsedMember(Class host, Member member) { |
| 403 if (member.enclosingLibrary == _mirrorsLibrary) { | |
| 404 throw new _UsingMirrorsException(); | |
| 405 } | |
| 406 if (host != null) { | 310 if (host != null) { |
| 407 // Check if the member has been seen with this host before. | 311 // Check if the member has been seen with this host before. |
| 408 int index = hierarchy.getClassIndex(host); | 312 int index = hierarchy.getClassIndex(host); |
| 409 Set<Member> members = _usedMembersWithHost[index] ??= new Set<Member>(); | 313 Set<Member> members = _usedMembersWithHost[index] ??= new Set<Member>(); |
| 410 if (!members.add(member)) return; | 314 if (!members.add(member)) return; |
| 411 _usedMembers.putIfAbsent(member, _makeIncompleteSummary); | 315 _usedMembers.putIfAbsent(member, _makeIncompleteSummary); |
| 412 } else { | 316 } else { |
| 413 // Check if the member has been seen before. | 317 // Check if the member has been seen before. |
| 414 if (_usedMembers.containsKey(member)) return; | 318 if (_usedMembers.containsKey(member)) return; |
| 415 _usedMembers[member] = _makeIncompleteSummary(); | 319 _usedMembers[member] = _makeIncompleteSummary(); |
| (...skipping 22 matching lines...) Expand all Loading... |
| 438 for (int i = 0; i < function.namedParameters.length; ++i) { | 342 for (int i = 0; i < function.namedParameters.length; ++i) { |
| 439 _contravariantVisitor.visit(function.namedParameters[i].type); | 343 _contravariantVisitor.visit(function.namedParameters[i].type); |
| 440 } | 344 } |
| 441 } | 345 } |
| 442 | 346 |
| 443 /// Called when external code may invoke the interface of the given class. | 347 /// Called when external code may invoke the interface of the given class. |
| 444 void _addEscapedClass(Class node) { | 348 void _addEscapedClass(Class node) { |
| 445 if (!_escapedClasses.add(node)) return; | 349 if (!_escapedClasses.add(node)) return; |
| 446 for (Member member in hierarchy.getInterfaceMembers(node)) { | 350 for (Member member in hierarchy.getInterfaceMembers(node)) { |
| 447 if (member is Procedure) { | 351 if (member is Procedure) { |
| 448 _addDispatchedName(node, member.name); | 352 _addDispatchedName(member.name); |
| 449 } | 353 } |
| 450 } | 354 } |
| 451 } | 355 } |
| 452 | 356 |
| 453 /// Creates a incomplete summary object, indicating that a member has not | 357 /// Creates a incomplete summary object, indicating that a member has not |
| 454 /// yet been analyzed. | 358 /// yet been analyzed. |
| 455 static List<Node> _makeIncompleteSummary() => <Node>[null]; | 359 static List<Node> _makeIncompleteSummary() => <Node>[null]; |
| 456 | 360 |
| 457 bool isIncompleteSummary(List<Node> summary) { | 361 bool isIncompleteSummary(List<Node> summary) { |
| 458 return summary.isNotEmpty && summary[0] == null; | 362 return summary.isNotEmpty && summary[0] == null; |
| (...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 507 } | 411 } |
| 508 | 412 |
| 509 /// Sentinel that occurs in method summaries in front of each name that should | 413 /// Sentinel that occurs in method summaries in front of each name that should |
| 510 /// be interpreted as a setter. | 414 /// be interpreted as a setter. |
| 511 final Node _setterSentinel = const InvalidType(); | 415 final Node _setterSentinel = const InvalidType(); |
| 512 | 416 |
| 513 /// Searches the AST for static references and dynamically dispatched names. | 417 /// Searches the AST for static references and dynamically dispatched names. |
| 514 class _TreeShakerVisitor extends RecursiveVisitor { | 418 class _TreeShakerVisitor extends RecursiveVisitor { |
| 515 final TreeShaker shaker; | 419 final TreeShaker shaker; |
| 516 final CoreTypes coreTypes; | 420 final CoreTypes coreTypes; |
| 517 final TypeEnvironment types; | |
| 518 final bool strongMode; | |
| 519 List<Node> summary; | 421 List<Node> summary; |
| 520 | 422 |
| 521 _TreeShakerVisitor(TreeShaker shaker) | 423 _TreeShakerVisitor(TreeShaker shaker) |
| 522 : this.shaker = shaker, | 424 : this.shaker = shaker, |
| 523 this.coreTypes = shaker.coreTypes, | 425 this.coreTypes = shaker.coreTypes; |
| 524 this.strongMode = shaker.strongMode, | |
| 525 this.types = new TypeEnvironment(shaker.coreTypes, shaker.hierarchy) { | |
| 526 types.errorHandler = handleError; | |
| 527 } | |
| 528 | 426 |
| 529 void handleError(TreeNode node, String message) { | 427 void analyzeAndBuildSummary(Node node, List<Node> summary) { |
| 530 print('[error] $message (${node.location})'); | |
| 531 } | |
| 532 | |
| 533 void analyzeAndBuildSummary(Member member, List<Node> summary) { | |
| 534 this.summary = summary; | 428 this.summary = summary; |
| 535 types.thisType = member.enclosingClass?.thisType; | 429 node.accept(this); |
| 536 member.accept(this); | |
| 537 } | |
| 538 | |
| 539 void visitMemberInterface(Member node) { | |
| 540 if (node is Field) { | |
| 541 node.type.accept(this); | |
| 542 } else if (node is Procedure) { | |
| 543 visitFunctionInterface(node.function); | |
| 544 } | |
| 545 } | |
| 546 | |
| 547 visitFunctionInterface(FunctionNode node) { | |
| 548 for (var parameter in node.typeParameters) { | |
| 549 parameter.bound.accept(this); | |
| 550 } | |
| 551 for (var parameter in node.positionalParameters) { | |
| 552 parameter.type.accept(this); | |
| 553 } | |
| 554 for (var parameter in node.namedParameters) { | |
| 555 parameter.type.accept(this); | |
| 556 } | |
| 557 node.returnType.accept(this); | |
| 558 } | 430 } |
| 559 | 431 |
| 560 @override | 432 @override |
| 561 visitFunctionNode(FunctionNode node) { | 433 visitFunctionNode(FunctionNode node) { |
| 562 switch (node.asyncMarker) { | 434 switch (node.asyncMarker) { |
| 563 case AsyncMarker.Sync: | 435 case AsyncMarker.Sync: |
| 564 break; | 436 break; |
| 565 case AsyncMarker.SyncStar: | 437 case AsyncMarker.SyncStar: |
| 566 shaker._addInstantiatedExternalSubclass(coreTypes.iterableClass); | 438 shaker._addInstantiatedExternalSubclass(coreTypes.iterableClass); |
| 567 break; | 439 break; |
| (...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 625 @override | 497 @override |
| 626 visitDirectMethodInvocation(DirectMethodInvocation node) { | 498 visitDirectMethodInvocation(DirectMethodInvocation node) { |
| 627 if (node.receiver is! ThisExpression) { | 499 if (node.receiver is! ThisExpression) { |
| 628 // TODO(asgerf): Support arbitrary direct calls. | 500 // TODO(asgerf): Support arbitrary direct calls. |
| 629 throw 'Direct calls are only supported on "this"'; | 501 throw 'Direct calls are only supported on "this"'; |
| 630 } | 502 } |
| 631 addUseFromCurrentHost(node.target); | 503 addUseFromCurrentHost(node.target); |
| 632 node.visitChildren(this); | 504 node.visitChildren(this); |
| 633 } | 505 } |
| 634 | 506 |
| 635 Class getKnownSupertype(DartType type) { | |
| 636 if (type is InterfaceType) { | |
| 637 return type.classNode; | |
| 638 } else if (type is TypeParameterType) { | |
| 639 return getKnownSupertype(type.parameter.bound); | |
| 640 } else if (type is FunctionType) { | |
| 641 return coreTypes.functionClass; | |
| 642 } else if (type is BottomType) { | |
| 643 return coreTypes.nullClass; | |
| 644 } else { | |
| 645 return coreTypes.objectClass; | |
| 646 } | |
| 647 } | |
| 648 | |
| 649 Class getStaticType(Expression node) { | |
| 650 if (!strongMode) return coreTypes.objectClass; | |
| 651 return getKnownSupertype(node.getStaticType(types)); | |
| 652 } | |
| 653 | |
| 654 @override | 507 @override |
| 655 visitMethodInvocation(MethodInvocation node) { | 508 visitMethodInvocation(MethodInvocation node) { |
| 656 if (node.receiver is ThisExpression) { | 509 if (node.receiver is ThisExpression) { |
| 657 addSelfDispatch(node.name); | 510 addSelfDispatch(node.name); |
| 658 } else { | 511 } else { |
| 659 shaker._addDispatchedName(getStaticType(node.receiver), node.name); | 512 shaker._addDispatchedName(node.name); |
| 660 if (node.interfaceTarget != null) { | |
| 661 shaker._typedCalls.add(node); | |
| 662 } | |
| 663 } | 513 } |
| 664 node.visitChildren(this); | 514 node.visitChildren(this); |
| 665 } | 515 } |
| 666 | 516 |
| 667 @override | 517 @override |
| 668 visitStaticGet(StaticGet node) { | 518 visitStaticGet(StaticGet node) { |
| 669 addStaticUse(node.target); | 519 addStaticUse(node.target); |
| 670 node.visitChildren(this); | 520 node.visitChildren(this); |
| 671 } | 521 } |
| 672 | 522 |
| (...skipping 21 matching lines...) Expand all Loading... |
| 694 } | 544 } |
| 695 addUseFromCurrentHost(node.target); | 545 addUseFromCurrentHost(node.target); |
| 696 node.visitChildren(this); | 546 node.visitChildren(this); |
| 697 } | 547 } |
| 698 | 548 |
| 699 @override | 549 @override |
| 700 visitPropertyGet(PropertyGet node) { | 550 visitPropertyGet(PropertyGet node) { |
| 701 if (node.receiver is ThisExpression) { | 551 if (node.receiver is ThisExpression) { |
| 702 addSelfDispatch(node.name); | 552 addSelfDispatch(node.name); |
| 703 } else { | 553 } else { |
| 704 shaker._addDispatchedName(getStaticType(node.receiver), node.name); | 554 shaker._addDispatchedName(node.name); |
| 705 if (node.interfaceTarget != null) { | |
| 706 shaker._typedCalls.add(node); | |
| 707 } | |
| 708 } | 555 } |
| 709 node.visitChildren(this); | 556 node.visitChildren(this); |
| 710 } | 557 } |
| 711 | 558 |
| 712 @override | 559 @override |
| 713 visitPropertySet(PropertySet node) { | 560 visitPropertySet(PropertySet node) { |
| 714 if (node.receiver is ThisExpression) { | 561 if (node.receiver is ThisExpression) { |
| 715 addSelfDispatch(node.name, setter: true); | 562 addSelfDispatch(node.name, setter: true); |
| 716 } else { | 563 } else { |
| 717 shaker._addDispatchedName(getStaticType(node.receiver), node.name); | 564 shaker._addDispatchedName(node.name); |
| 718 if (node.interfaceTarget != null) { | |
| 719 shaker._typedCalls.add(node); | |
| 720 } | |
| 721 } | 565 } |
| 722 node.visitChildren(this); | 566 node.visitChildren(this); |
| 723 } | 567 } |
| 724 | 568 |
| 725 @override | 569 @override |
| 726 visitListLiteral(ListLiteral node) { | 570 visitListLiteral(ListLiteral node) { |
| 727 shaker._addInstantiatedExternalSubclass(coreTypes.listClass); | 571 shaker._addInstantiatedExternalSubclass(coreTypes.listClass); |
| 728 node.visitChildren(this); | 572 node.visitChildren(this); |
| 729 } | 573 } |
| 730 | 574 |
| 731 @override | 575 @override |
| 732 visitMapLiteral(MapLiteral node) { | 576 visitMapLiteral(MapLiteral node) { |
| 733 shaker._addInstantiatedExternalSubclass(coreTypes.mapClass); | 577 shaker._addInstantiatedExternalSubclass(coreTypes.mapClass); |
| 734 node.visitChildren(this); | 578 node.visitChildren(this); |
| 735 } | 579 } |
| 736 | 580 |
| 737 static final Name _toStringName = new Name('toString'); | 581 static final Name _toStringName = new Name('toString'); |
| 738 | 582 |
| 739 @override | 583 @override |
| 740 visitStringConcatenation(StringConcatenation node) { | 584 visitStringConcatenation(StringConcatenation node) { |
| 741 for (var expression in node.expressions) { | 585 shaker._addDispatchedName(_toStringName); |
| 742 shaker._addDispatchedName(getStaticType(expression), _toStringName); | |
| 743 } | |
| 744 node.visitChildren(this); | 586 node.visitChildren(this); |
| 745 } | 587 } |
| 746 | 588 |
| 747 @override | 589 @override |
| 748 visitInterfaceType(InterfaceType node) { | 590 visitInterfaceType(InterfaceType node) { |
| 749 shaker._addClassUsedInType(node.classNode); | 591 shaker._addClassUsedInType(node.classNode); |
| 750 node.visitChildren(this); | 592 node.visitChildren(this); |
| 751 } | 593 } |
| 752 | 594 |
| 753 @override | 595 @override |
| 754 visitSupertype(Supertype node) { | |
| 755 shaker._addClassUsedInType(node.classNode); | |
| 756 node.visitChildren(this); | |
| 757 } | |
| 758 | |
| 759 @override | |
| 760 visitDoubleLiteral(DoubleLiteral node) { | 596 visitDoubleLiteral(DoubleLiteral node) { |
| 761 shaker._addInstantiatedExternalSubclass(coreTypes.doubleClass); | 597 shaker._addInstantiatedExternalSubclass(coreTypes.doubleClass); |
| 762 } | 598 } |
| 763 | 599 |
| 764 @override | 600 @override |
| 765 visitSymbolLiteral(SymbolLiteral node) { | 601 visitSymbolLiteral(SymbolLiteral node) { |
| 766 shaker._addInstantiatedExternalSubclass(coreTypes.symbolClass); | 602 shaker._addInstantiatedExternalSubclass(coreTypes.symbolClass); |
| 603 // Note: we do not support 'dart:mirrors' right now, so nothing else needs |
| 604 // to be done for symbols. |
| 767 } | 605 } |
| 768 | 606 |
| 769 @override | 607 @override |
| 770 visitTypeLiteral(TypeLiteral node) { | 608 visitTypeLiteral(TypeLiteral node) { |
| 771 shaker._addInstantiatedExternalSubclass(coreTypes.typeClass); | 609 shaker._addInstantiatedExternalSubclass(coreTypes.typeClass); |
| 772 node.visitChildren(this); | 610 node.visitChildren(this); |
| 773 } | 611 } |
| 774 } | 612 } |
| 775 | 613 |
| 776 /// The degree to which a class is needed in a program. | 614 /// The degree to which a class is needed in a program. |
| (...skipping 18 matching lines...) Expand all Loading... |
| 795 } | 633 } |
| 796 | 634 |
| 797 /// Removes classes and members that are not needed. | 635 /// Removes classes and members that are not needed. |
| 798 /// | 636 /// |
| 799 /// There must not be any dangling references in the program afterwards. | 637 /// There must not be any dangling references in the program afterwards. |
| 800 class _TreeShakingTransformer extends Transformer { | 638 class _TreeShakingTransformer extends Transformer { |
| 801 final TreeShaker shaker; | 639 final TreeShaker shaker; |
| 802 | 640 |
| 803 _TreeShakingTransformer(this.shaker); | 641 _TreeShakingTransformer(this.shaker); |
| 804 | 642 |
| 805 Member _translateInterfaceTarget(Member target) { | |
| 806 return target != null && shaker.isMemberUsed(target) ? target : null; | |
| 807 } | |
| 808 | |
| 809 void transform(Program program) { | 643 void transform(Program program) { |
| 810 for (var library in program.libraries) { | 644 for (var library in program.libraries) { |
| 811 if (library.importUri.scheme == 'dart') { | 645 if (library.importUri.scheme == 'dart') { |
| 812 // The backend expects certain things to be present in the core | 646 // As long as patching happens in the backend, we cannot shake off |
| 813 // libraries, so we currently don't shake off anything there. | 647 // anything in the core libraries. |
| 814 continue; | 648 continue; |
| 815 } | 649 } |
| 816 library.transformChildren(this); | 650 library.transformChildren(this); |
| 817 // Note: we can't shake off empty libraries yet since we don't check if | 651 // Note: we can't shake off empty libraries yet since we don't check if |
| 818 // there are private names that use the library. | 652 // there are private names that use the library. |
| 819 } | 653 } |
| 820 for (Expression node in shaker._typedCalls) { | |
| 821 // We should not leave dangling references, so if the target of a typed | |
| 822 // call has been removed, we must remove the reference. The receiver of | |
| 823 // such a call can only be null. | |
| 824 // TODO(asgerf): Rewrite to a NSM call instead of adding dynamic calls. | |
| 825 if (node is MethodInvocation) { | |
| 826 node.interfaceTarget = _translateInterfaceTarget(node.interfaceTarget); | |
| 827 } else if (node is PropertyGet) { | |
| 828 node.interfaceTarget = _translateInterfaceTarget(node.interfaceTarget); | |
| 829 } else if (node is PropertySet) { | |
| 830 node.interfaceTarget = _translateInterfaceTarget(node.interfaceTarget); | |
| 831 } | |
| 832 } | |
| 833 } | 654 } |
| 834 | 655 |
| 835 Class visitClass(Class node) { | 656 Class visitClass(Class node) { |
| 836 switch (shaker.getClassRetention(node)) { | 657 switch (shaker.getClassRetention(node)) { |
| 837 case ClassRetention.None: | 658 case ClassRetention.None: |
| 838 return null; // Remove the class. | 659 return null; // Remove the class. |
| 839 | 660 |
| 840 case ClassRetention.Namespace: | 661 case ClassRetention.Namespace: |
| 841 // The class is only a namespace for static members. Remove its | 662 // The class is only a namespace for static members. Remove its |
| 842 // hierarchy information. This is mandatory, since these references | 663 // hierarchy information. This is mandatory, since these references |
| 843 // might otherwise become dangling. | 664 // might otherwise become dangling. |
| 844 node.supertype = shaker.coreTypes.objectClass.asRawSupertype; | 665 node.supertype = shaker.coreTypes.objectClass.asRawSupertype; |
| 845 node.implementedTypes.clear(); | 666 node.implementedTypes.clear(); |
| 846 node.typeParameters.clear(); | 667 node.typeParameters.clear(); |
| 847 node.isAbstract = true; | |
| 848 // Mixin applications cannot have static members. | 668 // Mixin applications cannot have static members. |
| 849 assert(node.mixedInType == null); | 669 assert(node.mixedInType == null); |
| 850 // Unused members will be removed below. | 670 // Unused members will be removed below. |
| 851 break; | 671 break; |
| 852 | 672 |
| 853 case ClassRetention.Hierarchy: | 673 case ClassRetention.Hierarchy: |
| 854 node.isAbstract = true; | |
| 855 break; | |
| 856 | |
| 857 case ClassRetention.Instance: | 674 case ClassRetention.Instance: |
| 858 case ClassRetention.ExternalInstance: | 675 case ClassRetention.ExternalInstance: |
| 859 break; | 676 break; |
| 860 } | 677 } |
| 861 node.transformChildren(this); | 678 node.transformChildren(this); |
| 679 if (node.constructors.isEmpty && node.procedures.isEmpty) { |
| 680 // The VM does not like classes without any members, so ensure there is |
| 681 // always a constructor left. |
| 682 node.addMember(new Constructor(new FunctionNode(new EmptyStatement()))); |
| 683 } |
| 862 return node; | 684 return node; |
| 863 } | 685 } |
| 864 | 686 |
| 865 Member defaultMember(Member node) { | 687 Member defaultMember(Member node) { |
| 866 if (!shaker.isMemberBodyUsed(node)) { | 688 if (!shaker.isMemberUsed(node)) { |
| 867 if (!shaker.isMemberOverridden(node)) { | 689 return null; // Remove unused member. |
| 868 return null; | |
| 869 } | |
| 870 if (node is Procedure) { | |
| 871 // Remove body of unused member. | |
| 872 if (node.enclosingClass.isAbstract) { | |
| 873 node.isAbstract = true; | |
| 874 node.function.body = null; | |
| 875 } else { | |
| 876 // If the enclosing class is not abstract, the method should still | |
| 877 // have a body even if it can never be called. | |
| 878 if (node.function.body != null) { | |
| 879 node.function.body = new ExpressionStatement( | |
| 880 new Throw(new StringLiteral('Method removed by tree-shaking'))) | |
| 881 ..parent = node.function; | |
| 882 } | |
| 883 } | |
| 884 node.function.asyncMarker = AsyncMarker.Sync; | |
| 885 } else if (node is Field) { | |
| 886 node.initializer = null; | |
| 887 } | |
| 888 } | 690 } |
| 889 return node; | 691 return node; |
| 890 } | 692 } |
| 891 | 693 |
| 892 TreeNode defaultTreeNode(TreeNode node) { | 694 TreeNode defaultTreeNode(TreeNode node) { |
| 893 return node; // Do not traverse into other nodes. | 695 return node; // Do not traverse into other nodes. |
| 894 } | 696 } |
| 895 } | 697 } |
| 896 | 698 |
| 897 class _ExternalTypeVisitor extends DartTypeVisitor { | 699 class _ExternalTypeVisitor extends DartTypeVisitor { |
| (...skipping 13 matching lines...) Expand all Loading... |
| 911 type?.accept(this); | 713 type?.accept(this); |
| 912 } else if (isContravariant) { | 714 } else if (isContravariant) { |
| 913 type?.accept(shaker._covariantVisitor); | 715 type?.accept(shaker._covariantVisitor); |
| 914 } else { | 716 } else { |
| 915 type?.accept(shaker._contravariantVisitor); | 717 type?.accept(shaker._contravariantVisitor); |
| 916 } | 718 } |
| 917 } | 719 } |
| 918 | 720 |
| 919 visitCovariant(DartType type) => type?.accept(this); | 721 visitCovariant(DartType type) => type?.accept(this); |
| 920 | 722 |
| 921 visitInvariant(DartType type) => shaker._invariantVisitor.visit(type); | 723 visitBivariant(DartType type) => shaker._bivariantVisitor.visit(type); |
| 922 | 724 |
| 923 visitInvalidType(InvalidType node) {} | 725 visitInvalidType(InvalidType node) {} |
| 924 | 726 |
| 925 visitDynamicType(DynamicType node) { | 727 visitDynamicType(DynamicType node) { |
| 926 // TODO(asgerf): Find a suitable model for untyped externals, e.g. track | 728 // TODO(asgerf): Find a suitable model for untyped externals, e.g. track |
| 927 // them to the first type boundary. | 729 // them to the first type boundary. |
| 928 } | 730 } |
| 929 | 731 |
| 930 visitVoidType(VoidType node) {} | 732 visitVoidType(VoidType node) {} |
| 931 | 733 |
| 932 visitInterfaceType(InterfaceType node) { | 734 visitInterfaceType(InterfaceType node) { |
| 933 if (isCovariant) { | 735 if (isCovariant) { |
| 934 shaker._addInstantiatedExternalSubclass(node.classNode); | 736 shaker._addInstantiatedExternalSubclass(node.classNode); |
| 935 } | 737 } |
| 936 if (isContravariant) { | 738 if (isContravariant) { |
| 937 shaker._addEscapedClass(node.classNode); | 739 shaker._addEscapedClass(node.classNode); |
| 938 } | 740 } |
| 939 for (int i = 0; i < node.typeArguments.length; ++i) { | 741 for (int i = 0; i < node.typeArguments.length; ++i) { |
| 940 DartType typeArgument = node.typeArguments[i]; | 742 DartType typeArgument = node.typeArguments[i]; |
| 941 // In practice we don't get much out of analyzing variance here, so | 743 // In practice we don't get much out of analyzing variance here, so |
| 942 // just use a whitelist of classes that can be seen as covariant | 744 // just use a whitelist of classes that can be seen as covariant |
| 943 // for external purposes. | 745 // for external purposes. |
| 944 // TODO(asgerf): Variance analysis might pay off for other external APIs. | 746 // TODO(asgerf): Variance analysis might pay off for other external APIs. |
| 945 if (isWhitelistedCovariant(node.classNode)) { | 747 if (isWhitelistedCovariant(node.classNode)) { |
| 946 visitCovariant(typeArgument); | 748 visitCovariant(typeArgument); |
| 947 } else { | 749 } else { |
| 948 visitInvariant(typeArgument); | 750 visitBivariant(typeArgument); |
| 949 } | 751 } |
| 950 } | 752 } |
| 951 } | 753 } |
| 952 | 754 |
| 953 visitFunctionType(FunctionType node) { | 755 visitFunctionType(FunctionType node) { |
| 954 visit(node.returnType); | 756 visit(node.returnType); |
| 955 for (int i = 0; i < node.positionalParameters.length; ++i) { | 757 for (int i = 0; i < node.positionalParameters.length; ++i) { |
| 956 visitContravariant(node.positionalParameters[i]); | 758 visitContravariant(node.positionalParameters[i]); |
| 957 } | 759 } |
| 958 for (int i = 0; i < node.namedParameters.length; ++i) { | 760 for (int i = 0; i < node.namedParameters.length; ++i) { |
| 959 visitContravariant(node.namedParameters[i].type); | 761 visitContravariant(node.namedParameters[i].type); |
| 960 } | 762 } |
| 961 } | 763 } |
| 962 | 764 |
| 963 visitTypeParameterType(TypeParameterType node) {} | 765 visitTypeParameterType(TypeParameterType node) {} |
| 964 | 766 |
| 965 /// Just treat a couple of whitelisted classes as having covariant type | 767 /// Just treat a couple of whitelisted classes as having covariant type |
| 966 /// parameters. | 768 /// parameters. |
| 967 bool isWhitelistedCovariant(Class classNode) { | 769 bool isWhitelistedCovariant(Class classNode) { |
| 968 if (classNode.typeParameters.isEmpty) return false; | 770 if (classNode.typeParameters.isEmpty) return false; |
| 969 CoreTypes coreTypes = shaker.coreTypes; | 771 CoreTypes coreTypes = shaker.coreTypes; |
| 970 return classNode == coreTypes.iteratorClass || | 772 return classNode == coreTypes.iteratorClass || |
| 971 classNode == coreTypes.iterableClass || | 773 classNode == coreTypes.iterableClass || |
| 972 classNode == coreTypes.futureClass || | 774 classNode == coreTypes.futureClass || |
| 973 classNode == coreTypes.streamClass || | 775 classNode == coreTypes.streamClass || |
| 974 classNode == coreTypes.listClass || | 776 classNode == coreTypes.listClass || |
| 975 classNode == coreTypes.mapClass; | 777 classNode == coreTypes.mapClass; |
| 976 } | 778 } |
| 977 } | 779 } |
| 978 | |
| 979 /// Exception that is thrown to stop the tree shaking analysis when a use | |
| 980 /// of `dart:mirrors` is found. | |
| 981 class _UsingMirrorsException {} | |
| OLD | NEW |