OLD | NEW |
---|---|
(Empty) | |
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 | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 /// A transformation to create a self-contained modular kernel without | |
6 /// unnecessary references to other libraries. | |
7 library fasta.kernel.kernel_outline_shaker; | |
8 | |
9 import 'package:kernel/ast.dart'; | |
10 import 'package:kernel/core_types.dart'; | |
11 | |
12 import '../errors.dart' show internalError; | |
13 | |
14 /// Removes from [program] unnecessary libraries, classes, and members. | |
ahe
2017/05/22 12:04:24
Removes unnecessary libraries, classes, and member
| |
15 /// | |
16 /// This applies a simple "tree-shaking" technique: the full body of libraries | |
17 /// whose URI match [isIncluded] are preserved, and so is the outline of the | |
18 /// members and classes which are referred to transitively from these included | |
19 /// libraries. | |
20 /// | |
21 /// The intent is that the resulting program has the entire code that is meant | |
22 /// to be included and the minimum required to prevent dangling references and | |
23 /// allow modular program transformations. | |
24 /// | |
25 /// Note that the resulting program may include libraries not in [isIncluded], | |
26 /// but those will be marked as external. There should be no method bodies for | |
27 /// any members of those libraries. | |
28 /// | |
29 /// When [retainClassMembers] is true, all members of retained classes will be | |
30 /// retained as well. | |
31 void trimProgram(Program program, bool isIncluded(Uri uri), | |
32 {bool retainClassMembers: true}) { | |
33 var result = new _Analysis(program, isIncluded, retainClassMembers).run(); | |
34 new _Shaker(result, isIncluded).transform(program); | |
35 } | |
36 | |
37 /// Result of analyzing a program before tree-shaking it. | |
38 abstract class _AnalysisResult { | |
39 /// Whether a library should be preserved and mark as external. | |
40 bool isLibraryUsed(Library library); | |
41 | |
42 /// Whether a class should be preserved. If a class is preserved, its | |
43 /// supertypes will be preserved too, but some of it members may not be | |
44 /// included. | |
45 bool isClassUsed(Class cls); | |
46 | |
47 /// Whether a member should be preserved. If so, its enclosing class/library | |
48 /// will be preserved too. | |
49 bool isMemberUsed(Member member); | |
50 } | |
51 | |
52 /// Visitor used by [trimProgram] to collects which libraries, classes, and | |
53 /// members should be preserved. | |
54 class _Analysis extends RecursiveVisitor implements _AnalysisResult { | |
55 /// The program being analyzed. | |
56 final Program program; | |
57 | |
58 /// Helper to fetch classes and members of the core libraries. | |
59 final CoreTypes coreTypes; | |
60 | |
61 /// Filter to determine libraries that should be analyzed. | |
62 final Filter isIncluded; | |
63 | |
64 /// When retaining a class, whether to retain all of its members. | |
65 // TODO(sigmund): delete this option once we figure out the long term plan. Do | |
66 // we need to do this? Today it seems necessary because some transformations | |
67 // require it, but maybe we don't need them all (e.g. only constructors and | |
68 // members whose inferenceTarget is referred do in the sources?) Do we need a | |
69 // different retention policy for including the type hierarchy with and | |
70 // without members? | |
71 final bool retainClassMembers; | |
72 | |
73 /// Libraries that contained code that is transitively reachable from the | |
74 /// included libraries. | |
75 final Set<Library> _libraries = new Set<Library>(); | |
76 | |
77 /// Classes that are transitively reachable from the included libraries. | |
78 final Set<Class> _classes = new Set<Class>(); | |
79 | |
80 /// Members that are transitively reachable from the included libraries. | |
81 final Set<Member> _members = new Set<Member>(); | |
82 | |
83 @override | |
84 bool isLibraryUsed(Library library) => _libraries.contains(library); | |
85 | |
86 @override | |
87 bool isClassUsed(Class cls) => _classes.contains(cls); | |
88 | |
89 @override | |
90 bool isMemberUsed(Member m) => _members.contains(m); | |
91 | |
92 _Analysis(this.program, this.isIncluded, this.retainClassMembers) | |
93 : coreTypes = new CoreTypes(program); | |
94 | |
95 _AnalysisResult run() { | |
96 _markRequired(); | |
97 _markMember(program.mainMethod); | |
98 for (var library in program.libraries) { | |
99 if (isIncluded(library.importUri)) { | |
100 library.accept(this); | |
101 } | |
102 } | |
103 return this; | |
104 } | |
105 | |
106 /// Marks classes and members that are assumed to exist by fasta or by | |
107 /// transformers. | |
108 void _markRequired() { | |
109 coreTypes.objectClass.members.forEach(_markMember); | |
110 | |
111 // These are assumed to be available by fasta: | |
112 _markClass(coreTypes.objectClass); | |
113 _markClass(coreTypes.nullClass); | |
114 _markClass(coreTypes.boolClass); | |
115 _markClass(coreTypes.intClass); | |
116 _markClass(coreTypes.numClass); | |
117 _markClass(coreTypes.doubleClass); | |
118 _markClass(coreTypes.stringClass); | |
119 _markClass(coreTypes.listClass); | |
120 _markClass(coreTypes.mapClass); | |
121 _markClass(coreTypes.iterableClass); | |
122 _markClass(coreTypes.iteratorClass); | |
123 _markClass(coreTypes.futureClass); | |
124 _markClass(coreTypes.streamClass); | |
125 _markClass(coreTypes.symbolClass); | |
126 _markClass(coreTypes.internalSymbolClass); | |
127 _markClass(coreTypes.typeClass); | |
128 _markClass(coreTypes.functionClass); | |
129 _markClass(coreTypes.invocationClass); | |
130 _markMember(coreTypes.getMember("dart:_internal", "ExternalName", "")); | |
131 | |
132 // These are needed by the continuation (async/await) transformer: | |
133 _markClass(coreTypes.getClass('dart:core', 'Iterator')); | |
134 _markClass(coreTypes.getClass('dart:async', 'Future')); | |
135 _markClass(coreTypes.getClass('dart:async', 'FutureOr')); | |
136 _markClass(coreTypes.getClass('dart:async', 'Completer')); | |
137 _markMember(coreTypes.getMember('dart:async', 'Completer', 'sync')); | |
138 _markMember(coreTypes.getMember('dart:core', '_SyncIterable', '')); | |
139 _markMember(coreTypes.getMember('dart:async', '_StreamIterator', '')); | |
140 _markMember(coreTypes.getMember('dart:async', 'Future', 'microtask')); | |
141 _markMember( | |
142 coreTypes.getMember('dart:async', '_AsyncStarStreamController', '')); | |
143 _markMember(coreTypes.getTopLevelMember('dart:core', 'print')); | |
144 _markMember( | |
145 coreTypes.getTopLevelMember('dart:async', '_asyncThenWrapperHelper')); | |
146 _markMember( | |
147 coreTypes.getTopLevelMember('dart:async', '_asyncErrorWrapperHelper')); | |
148 _markMember(coreTypes.getTopLevelMember('dart:async', '_awaitHelper')); | |
149 | |
150 // These are needed by the mixin transformer | |
151 _markMember(coreTypes.getMember('dart:core', '_InvocationMirror', '')); | |
152 _markMember(coreTypes.getMember('dart:core', 'List', 'from')); | |
153 } | |
154 | |
155 /// Mark a library as used. | |
156 _markLibrary(Library lib) { | |
157 if (!_libraries.add(lib)) return; | |
158 } | |
159 | |
160 /// Mark a class and it's supertypes as used. | |
161 void _markClass(Class cls) { | |
162 if (cls == null || !_classes.add(cls)) return; | |
163 _markLibrary(cls.parent); | |
164 visitList(cls.annotations, this); | |
165 cls.supertype?.accept(this); | |
166 cls.mixedInType?.accept(this); | |
167 visitList(cls.implementedTypes, this); | |
168 visitList(cls.typeParameters, this); | |
169 | |
170 if (retainClassMembers) cls.members.forEach(_markMember); | |
171 } | |
172 | |
173 _markMember(Member m) { | |
174 if (m == null || !_members.add(m)) return; | |
175 _markMemberInterface(m); | |
176 var parent = m.parent; | |
177 if (parent is Library) { | |
178 _markLibrary(parent); | |
179 } else if (parent is Class) { | |
180 _markClass(parent); | |
181 } | |
182 } | |
183 | |
184 void _markMemberInterface(Member node) { | |
185 if (node is Field) { | |
186 node.type.accept(this); | |
187 } else if (node is Procedure) { | |
188 _markFunctionInterface(node.function); | |
189 } | |
190 } | |
191 | |
192 _markFunctionInterface(FunctionNode node) { | |
193 for (var parameter in node.typeParameters) { | |
194 parameter.bound.accept(this); | |
195 } | |
196 for (var parameter in node.positionalParameters) { | |
197 parameter.type.accept(this); | |
198 } | |
199 for (var parameter in node.namedParameters) { | |
200 parameter.type.accept(this); | |
201 } | |
202 node.returnType.accept(this); | |
203 } | |
204 | |
205 @override | |
206 visitFunctionNode(FunctionNode node) { | |
207 switch (node.asyncMarker) { | |
208 case AsyncMarker.Sync: | |
209 break; | |
210 case AsyncMarker.SyncStar: | |
211 _markClass(coreTypes.iterableClass); | |
scheglov
2017/05/18 15:54:08
These classes are already included in _markRequire
Siggi Cherem (dart-lang)
2017/05/19 04:37:32
good point. I removed them from here. Also - now t
| |
212 break; | |
213 case AsyncMarker.Async: | |
214 _markClass(coreTypes.futureClass); | |
215 break; | |
216 case AsyncMarker.AsyncStar: | |
217 _markClass(coreTypes.streamClass); | |
218 break; | |
219 case AsyncMarker.SyncYielding: | |
220 break; | |
221 } | |
222 node.visitChildren(this); | |
223 } | |
224 | |
225 @override | |
226 visitSuperInitializer(SuperInitializer node) { | |
227 _markMember(node.target); | |
228 node.visitChildren(this); | |
229 } | |
230 | |
231 @override | |
232 visitRedirectingInitializer(RedirectingInitializer node) { | |
233 _markMember(node.target); | |
234 node.visitChildren(this); | |
235 } | |
236 | |
237 @override | |
238 visitConstructorInvocation(ConstructorInvocation node) { | |
239 _markMember(node.target); | |
240 node.visitChildren(this); | |
241 } | |
242 | |
243 @override | |
244 visitStaticInvocation(StaticInvocation node) { | |
245 _markMember(node.target); | |
246 node.visitChildren(this); | |
247 } | |
248 | |
249 @override | |
250 visitDirectMethodInvocation(DirectMethodInvocation node) { | |
251 if (node.receiver is! ThisExpression) { | |
252 return internalError('Direct calls are only supported on "this"'); | |
253 } | |
254 _markMember(node.target); | |
255 node.visitChildren(this); | |
256 } | |
257 | |
258 @override | |
259 visitMethodInvocation(MethodInvocation node) { | |
260 _markMember(node.interfaceTarget); | |
261 node.visitChildren(this); | |
262 } | |
263 | |
264 @override | |
265 visitStaticGet(StaticGet node) { | |
266 _markMember(node.target); | |
267 node.visitChildren(this); | |
268 } | |
269 | |
270 @override | |
271 visitStaticSet(StaticSet node) { | |
272 _markMember(node.target); | |
273 node.visitChildren(this); | |
274 } | |
275 | |
276 @override | |
277 visitDirectPropertyGet(DirectPropertyGet node) { | |
278 _markMember(node.target); | |
279 node.visitChildren(this); | |
280 } | |
281 | |
282 @override | |
283 visitDirectPropertySet(DirectPropertySet node) { | |
284 _markMember(node.target); | |
285 node.visitChildren(this); | |
286 } | |
287 | |
288 @override | |
289 visitSuperPropertyGet(SuperPropertyGet node) { | |
290 _markMember(node.interfaceTarget); | |
291 node.visitChildren(this); | |
292 } | |
293 | |
294 @override | |
295 visitSuperPropertySet(SuperPropertySet node) { | |
296 _markMember(node.interfaceTarget); | |
297 node.visitChildren(this); | |
298 } | |
299 | |
300 @override | |
301 visitPropertyGet(PropertyGet node) { | |
302 _markMember(node.interfaceTarget); | |
303 node.visitChildren(this); | |
304 } | |
305 | |
306 @override | |
307 visitPropertySet(PropertySet node) { | |
308 _markMember(node.interfaceTarget); | |
309 node.visitChildren(this); | |
310 } | |
311 | |
312 @override | |
313 visitListLiteral(ListLiteral node) { | |
314 _markClass(coreTypes.listClass); | |
315 node.visitChildren(this); | |
316 } | |
317 | |
318 @override | |
319 visitMapLiteral(MapLiteral node) { | |
320 _markClass(coreTypes.mapClass); | |
321 node.visitChildren(this); | |
322 } | |
323 | |
324 @override | |
325 visitStringConcatenation(StringConcatenation node) { | |
326 node.visitChildren(this); | |
327 } | |
328 | |
329 @override | |
330 visitInterfaceType(InterfaceType node) { | |
331 _markClass(node.classNode); | |
332 node.visitChildren(this); | |
333 } | |
334 | |
335 @override | |
336 visitSupertype(Supertype node) { | |
337 _markClass(node.classNode); | |
338 node.visitChildren(this); | |
339 } | |
340 | |
341 @override | |
342 visitDoubleLiteral(DoubleLiteral node) { | |
343 _markClass(coreTypes.doubleClass); | |
344 } | |
345 | |
346 @override | |
347 visitSymbolLiteral(SymbolLiteral node) { | |
348 _markClass(coreTypes.symbolClass); | |
349 } | |
350 | |
351 @override | |
352 visitTypeLiteral(TypeLiteral node) { | |
353 _markClass(coreTypes.typeClass); | |
354 node.visitChildren(this); | |
355 } | |
356 | |
357 @override | |
358 visitTypedefReference(Typedef node) { | |
359 return internalError('not implemented'); | |
360 } | |
361 } | |
362 | |
363 /// Transformer that trims everything in the excluded libraries that is not | |
364 /// marked as preserved by the [_Analysis] above. For every member in these | |
365 /// excluded libraries, this transformer also removes function bodies and | |
366 /// initializers. | |
367 class _Shaker extends Transformer { | |
368 final _AnalysisResult result; | |
369 final Filter isIncluded; | |
370 | |
371 _Shaker(this.result, this.isIncluded); | |
372 | |
373 void transform(Program program) { | |
374 var toRemove = new Set<Library>(); | |
375 for (var library in program.libraries) { | |
376 if (!isIncluded(library.importUri)) { | |
377 if (!result.isLibraryUsed(library)) { | |
378 toRemove.add(library); | |
379 } else { | |
380 library.isExternal = true; | |
381 library.transformChildren(this); | |
382 } | |
383 } | |
384 } | |
385 program.libraries.removeWhere(toRemove.contains); | |
386 } | |
387 | |
388 Class visitClass(Class node) { | |
389 if (!result.isClassUsed(node)) { | |
390 node.canonicalName?.unbind(); | |
391 return null; // Remove the class. | |
392 } else { | |
393 node.transformChildren(this); | |
394 return node; | |
395 } | |
396 } | |
397 | |
398 Member defaultMember(Member node) { | |
399 if (!result.isMemberUsed(node)) { | |
400 node.canonicalName?.unbind(); | |
401 return null; | |
402 } else { | |
403 if (node is Procedure) { | |
404 _clearFunction(node.function); | |
405 } else if (node is Field) { | |
406 node.initializer = null; | |
407 } else if (node is Constructor) { | |
408 node.initializers.clear(); | |
409 _clearFunction(node.function); | |
410 } | |
411 return node; | |
412 } | |
413 } | |
414 | |
415 _clearFunction(FunctionNode function) { | |
416 function.body = null; | |
417 // TODO(sigmund): Fix directly the continuation transformer. The async/await | |
418 // continuation transformer fails if it finds a function with no body that | |
419 // it is marked with async/async*/sync*, we shouldn't be patching that here. | |
420 function.dartAsyncMarker = function.asyncMarker; | |
421 function.asyncMarker = AsyncMarker.Sync; | |
422 } | |
423 | |
424 /// Types appear to be encoded directly, so we have no need to preserve | |
425 /// typedefs. | |
426 // TODO(sigmund): revisit if this is not the case, the `inputError` in the | |
427 // analysis step is meant to detect this. | |
428 Typedef visitTypedef(Typedef node) => null; | |
429 | |
430 TreeNode defaultTreeNode(TreeNode node) => node; | |
431 } | |
432 | |
433 typedef bool Filter(Uri uri); | |
OLD | NEW |