Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(395)

Side by Side Diff: pkg/compiler/lib/src/closure.dart

Issue 2961563003: Hopefully the last bit of restructuring between closture classes and loop boxing, etc. (Closed)
Patch Set: Created 3 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | pkg/compiler/lib/src/kernel/closure.dart » ('j') | pkg/compiler/pubspec.yaml » ('J')
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2012, 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 import 'common/names.dart' show Identifiers; 5 import 'common/names.dart' show Identifiers;
6 import 'common/resolution.dart' show ParsingContext, Resolution; 6 import 'common/resolution.dart' show ParsingContext, Resolution;
7 import 'common/tasks.dart' show CompilerTask, Measurer; 7 import 'common/tasks.dart' show CompilerTask, Measurer;
8 import 'common.dart'; 8 import 'common.dart';
9 import 'compiler.dart' show Compiler; 9 import 'compiler.dart' show Compiler;
10 import 'constants/expressions.dart'; 10 import 'constants/expressions.dart';
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
45 // one for local functions. 45 // one for local functions.
46 ScopeInfo getScopeInfo(covariant Entity member); 46 ScopeInfo getScopeInfo(covariant Entity member);
47 47
48 /// This returns the same information as ScopeInfo, but can be called in 48 /// This returns the same information as ScopeInfo, but can be called in
49 /// situations when you are sure you are dealing with a closure specifically. 49 /// situations when you are sure you are dealing with a closure specifically.
50 ClosureRepresentationInfo getClosureRepresentationInfo( 50 ClosureRepresentationInfo getClosureRepresentationInfo(
51 covariant Entity member); 51 covariant Entity member);
52 52
53 /// Look up information about a loop, in case any variables it declares need 53 /// Look up information about a loop, in case any variables it declares need
54 /// to be boxed/snapshotted. 54 /// to be boxed/snapshotted.
55 LoopClosureRepresentationInfo getClosureRepresentationInfoForLoop(T loopNode); 55 ClosureBase getClosureRepresentationInfoForLoop(T loopNode);
56 56
57 /// Accessor to the information about closures that the SSA builder will use. 57 /// Accessor to the information about closures that the SSA builder will use.
58 ClosureAnalysisInfo getClosureAnalysisInfo(T node); 58 ClosureBase getClosureBase(T node);
59 } 59 }
60 60
61 /// Class that represents one level of scoping information, whether this scope 61 /// Class that represents one level of scoping information, whether this scope
62 /// is a closure or not. This is specifically used to store information 62 /// is a closure or not. This is specifically used to store information
63 /// about the usage of variables in try or sync blocks, because they need to be 63 /// about the usage of variables in try or sync blocks, because they need to be
64 /// boxed. 64 /// boxed.
65 /// 65 ///
66 /// Variables that are used in a try must be treated as boxed because the 66 /// Variables that are used in a try must be treated as boxed because the
67 /// control flow can be non-linear. Also parameters to a `sync*` generator must 67 /// control flow can be non-linear. Also parameters to a `sync*` generator must
68 /// be boxed, because of the way we rewrite sync* functions. See also comments 68 /// be boxed, because of the way we rewrite sync* functions. See also comments
69 /// in [ClosureClassMap.useLocal]. 69 /// in [ClosureClassMap.useLocal].
70 class ScopeInfo { 70 class ScopeInfo {
71 const ScopeInfo(); 71 const ScopeInfo();
72 72
73 /// Convenience reference pointer to the element representing `this`.
74 /// If this scope is not in an instance member, it will be null.
75 Local get thisLocal => null;
76
73 /// Returns true if this [variable] is used inside a `try` block or a `sync*` 77 /// Returns true if this [variable] is used inside a `try` block or a `sync*`
74 /// generator (this is important to know because boxing/redirection needs to 78 /// generator (this is important to know because boxing/redirection needs to
75 /// happen for those local variables). 79 /// happen for those local variables).
76 /// 80 ///
77 /// Variables that are used in a try must be treated as boxed because the 81 /// Variables that are used in a try must be treated as boxed because the
78 /// control flow can be non-linear. 82 /// control flow can be non-linear.
79 /// 83 ///
80 /// Also parameters to a `sync*` generator must be boxed, because of the way 84 /// Also parameters to a `sync*` generator must be boxed, because of the way
81 /// we rewrite sync* functions. See also comments in 85 /// we rewrite sync* functions. See also comments in
82 /// [ClosureClassMap.useLocal]. 86 /// [ClosureClassMap.useLocal].
83 bool variableIsUsedInTryOrSync(Local variable) => false; 87 bool variableIsUsedInTryOrSync(Local variable) => false;
84 88
85 /// Convenience reference pointer to the element representing `this`. 89 /// Loop through each variabe that has been defined in this scope, modified
Johnni Winther 2017/06/26 08:31:39 `variabe` -> `variable`
Emily Fortuna 2017/06/26 21:57:19 Done.
86 /// If this scope is not in an instance member, it will be null. 90 /// anywhere (this scope or another scope) and used in another scope . Because
Johnni Winther 2017/06/26 08:31:39 `scope .` -> `scope.` Start a new paragraph at`Be
Emily Fortuna 2017/06/26 21:57:20 Done.
87 Local get thisLocal => null; 91 /// it is used in another scope, these variables need to be "boxed", creating
92 /// a thin wrapper around accesses to these variables so that accesses get
93 /// the correct updated value. The variables in variablesUsedInTryOrSync may
94 /// be included in this set.
95 ///
96 /// In the case of loops, this is the set of iteration variables (or any
97 /// variables declared in the for loop expression (`for (...here...)`) that
98 /// need to be boxed to snapshot their value.
99 void forEachBoxedVariable(f(Local local, FieldEntity field)) {}
100
101 /// True if [variable] has been mutated and is also used in another scope.
102 bool isBoxed(Local variable) => false;
103
104 /// True if this loop declares any variables that need to be boxed.
Johnni Winther 2017/06/26 08:31:39 `this loop` -> `this scope`
Emily Fortuna 2017/06/26 21:57:20 thank you!
105 bool get hasBoxedVariables => false;
88 } 106 }
89 107
90 /// Class that provides a black-box interface to information gleaned from 108 /// Class that provides a basic interface for closures. Actual Dart closures
91 /// analyzing a closure's characteristics, most commonly used to influence how 109 /// will use this class (see [ClosureRepresentationInfo]) as well as loops.
92 /// code should be generated in SSA builder stage. 110 class ClosureBase extends ScopeInfo {
93 class ClosureAnalysisInfo { 111 const ClosureBase();
94 const ClosureAnalysisInfo();
95 112
96 /// If true, this closure accesses a variable that was defined in an outside 113 /// If true, this closure accesses a variable that was defined in an outside
97 /// scope and this variable gets modified at some point (sometimes we say that 114 /// scope and this variable gets modified at some point (sometimes we say that
98 /// variable has been "captured"). In this situation, access to this variable 115 /// variable has been "captured"). In this situation, access to this variable
99 /// is controlled via a wrapper (box) so that updates to this variable 116 /// is controlled via a wrapper (box) so that updates to this variable
100 /// are done in a way that is in line with Dart's closure rules. 117 /// are done in a way that is in line with Dart's closure rules.
101 bool get requiresContextBox => false; 118 bool get requiresContextBox => false;
102 119
103 /// Accessor to the local environment in which a particular closure node is 120 /// Accessor to the local environment in which a particular closure node is
104 /// executed. This will encapsulate the value of any variables that have been 121 /// executed. This will encapsulate the value of any variables that have been
105 /// scoped into this context from outside. This is an accessor to the 122 /// scoped into this context from outside. This is an accessor to the
106 /// contextBox that [requiresContextBox] is testing is required. 123 /// contextBox that [requiresContextBox] is testing is required.
107 Local get context => null; 124 Local get context => null;
108
109 /// True if the specified variable has been mutated inside the scope of this
110 /// closure.
111 bool isCaptured(Local variable) => false;
112
113 /// Loop through every variable that has been captured in this closure. This
114 /// consists of all the free variables (variables captured *just* in this
115 /// closure) and all variables captured in nested scopes that we may be
116 /// capturing as well.
117 void forEachCapturedVariable(f(Local from, FieldEntity to)) {}
118 }
119
120 /// Class that describes the actual mechanics of how a loop is
121 /// converted/rewritten without closures. Unlike JS, the value of a declared
122 /// loop iteration variable in any closure is captured/snapshotted inside at
123 /// each iteration point, as if we created a new local variable for that value
124 /// inside the loop. For example, for the following loop:
125 ///
126 /// var lst = [];
127 /// for (int i = 0; i < 5; i++) lst.add(()=>i);
128 /// var result = list.map((f) => f()).toList();
129 ///
130 /// `result` will be [0, 1, 2, 3, 4], whereas were this JS code
131 /// the result would be [5, 5, 5, 5, 5]. Because of this difference we need to
132 /// create a closure for these sorts of loops to capture the variable's value at
133 /// each iteration, by boxing the iteration variable[s].
134 class LoopClosureRepresentationInfo extends ClosureAnalysisInfo {
135 const LoopClosureRepresentationInfo();
136
137 /// True if this loop declares any variables that need to be boxed.
138 bool get hasBoxedVariables => false;
139
140 /// The set of iteration variables (or variables declared in the for loop
141 /// expression (`for (...here...)`) that need to be boxed to snapshot their
142 /// value.
143 List<Local> get boxedVariables => const <Local>[];
144 } 125 }
145 126
146 /// Class that describes the actual mechanics of how the converted, rewritten 127 /// Class that describes the actual mechanics of how the converted, rewritten
147 /// closure is implemented. For example, for the following closure (named foo 128 /// closure is implemented. For example, for the following closure (named foo
148 /// for convenience): 129 /// for convenience):
149 /// 130 ///
150 /// var foo = (x) => y + x; 131 /// var foo = (x) => y + x;
151 /// 132 ///
152 /// We would produce the following class to control access to these variables in 133 /// We would produce the following class to control access to these variables in
153 /// the following way (modulo naming of variables, assuming that y is modified 134 /// the following way (modulo naming of variables, assuming that y is modified
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after
202 /// Loop through every variable that has been captured in this closure. This 183 /// Loop through every variable that has been captured in this closure. This
203 /// consists of all the free variables (variables captured *just* in this 184 /// consists of all the free variables (variables captured *just* in this
204 /// closure) and all variables captured in nested scopes that we may be 185 /// closure) and all variables captured in nested scopes that we may be
205 /// capturing as well. These nested scopes hold "boxes" to hold the executable 186 /// capturing as well. These nested scopes hold "boxes" to hold the executable
206 /// context for that scope. 187 /// context for that scope.
207 void forEachCapturedVariable(f(Local from, FieldEntity to)) {} 188 void forEachCapturedVariable(f(Local from, FieldEntity to)) {}
208 189
209 /// Loop through each variable that has been boxed in this closure class. Only 190 /// Loop through each variable that has been boxed in this closure class. Only
210 /// captured variables that are mutated need to be "boxed" (which basically 191 /// captured variables that are mutated need to be "boxed" (which basically
211 /// puts a thin layer between updates and reads to this variable to ensure 192 /// puts a thin layer between updates and reads to this variable to ensure
212 /// that every place that accesses it gets the correct updated value). 193 /// that every place that accesses it gets the correct updated value). This
194 /// includes looping over variables that were boxed from other scopes, not
195 /// strictly variables defined in this closure, unlike the behavior in
196 /// the superclass ScopeInfo.
197 @override
213 void forEachBoxedVariable(f(Local local, FieldEntity field)) {} 198 void forEachBoxedVariable(f(Local local, FieldEntity field)) {}
214 199
215 /// Loop through each free variable in this closure. Free variables are the 200 /// Loop through each free variable in this closure. Free variables are the
216 /// variables that have been captured *just* in this closure, not in nested 201 /// variables that have been captured *just* in this closure, not in nested
217 /// scopes. 202 /// scopes.
218 void forEachFreeVariable(f(Local variable, FieldEntity field)) {} 203 void forEachFreeVariable(f(Local variable, FieldEntity field)) {}
219 204
220 /// Return true if [variable] has been captured and mutated (all other 205 /// Return true if [variable] has been captured and mutated (all other
221 /// variables do not require boxing). 206 /// variables do not require boxing).
222 bool isVariableBoxed(Local variable) => false; 207 bool isVariableBoxed(Local variable) => false;
(...skipping 15 matching lines...) Expand all
238 223
239 String get name => "Closure Simplifier"; 224 String get name => "Closure Simplifier";
240 225
241 DiagnosticReporter get reporter => compiler.reporter; 226 DiagnosticReporter get reporter => compiler.reporter;
242 227
243 void convertClosures(Iterable<MemberEntity> processedEntities, 228 void convertClosures(Iterable<MemberEntity> processedEntities,
244 ClosedWorldRefiner closedWorldRefiner) { 229 ClosedWorldRefiner closedWorldRefiner) {
245 createClosureClasses(closedWorldRefiner); 230 createClosureClasses(closedWorldRefiner);
246 } 231 }
247 232
248 ClosureAnalysisInfo getClosureAnalysisInfo(Node node) { 233 ClosureBase getClosureBase(Node node) {
249 var value = _closureInfoMap[node]; 234 var value = _closureInfoMap[node];
250 return value == null ? const ClosureAnalysisInfo() : value; 235 return value == null ? const ClosureBase() : value;
251 } 236 }
252 237
253 ScopeInfo getScopeInfo(Element member) { 238 ScopeInfo getScopeInfo(Element member) {
254 return getClosureToClassMapping(member); 239 return getClosureToClassMapping(member);
255 } 240 }
256 241
257 ClosureRepresentationInfo getClosureRepresentationInfo(Element member) { 242 ClosureRepresentationInfo getClosureRepresentationInfo(Element member) {
258 return getClosureToClassMapping(member); 243 return getClosureToClassMapping(member);
259 } 244 }
260 245
261 LoopClosureRepresentationInfo getClosureRepresentationInfoForLoop( 246 ClosureBase getClosureRepresentationInfoForLoop(Node loopNode) {
262 Node loopNode) {
263 var value = _closureInfoMap[loopNode]; 247 var value = _closureInfoMap[loopNode];
264 return value == null ? const LoopClosureRepresentationInfo() : value; 248 return value == null ? const ClosureBase() : value;
265 } 249 }
266 250
267 /// Returns the [ClosureClassMap] computed for [resolvedAst]. 251 /// Returns the [ClosureClassMap] computed for [resolvedAst].
268 ClosureClassMap getClosureToClassMapping(Element element) { 252 ClosureClassMap getClosureToClassMapping(Element element) {
269 return measure(() { 253 return measure(() {
270 if (element.isGenerativeConstructorBody) { 254 if (element.isGenerativeConstructorBody) {
271 ConstructorBodyElement constructorBody = element; 255 ConstructorBodyElement constructorBody = element;
272 element = constructorBody.constructor; 256 element = constructorBody.constructor;
273 } 257 }
274 ClosureClassMap closureClassMap = _closureMappingCache[element]; 258 ClosureClassMap closureClassMap = _closureMappingCache[element];
(...skipping 347 matching lines...) Expand 10 before | Expand all | Expand 10 after
622 expression.compilationUnit.script.resourceUri); 606 expression.compilationUnit.script.resourceUri);
623 } 607 }
624 608
625 accept(ElementVisitor visitor, arg) { 609 accept(ElementVisitor visitor, arg) {
626 return visitor.visitMethodElement(this, arg); 610 return visitor.visitMethodElement(this, arg);
627 } 611 }
628 } 612 }
629 613
630 // The box-element for a scope, and the captured variables that need to be 614 // The box-element for a scope, and the captured variables that need to be
631 // stored in the box. 615 // stored in the box.
632 class ClosureScope 616 class ClosureScope implements ClosureBase {
Siggi Cherem (dart-lang) 2017/06/26 16:54:42 mmm... I sort of like the idea of swapping the two
633 implements ClosureAnalysisInfo, LoopClosureRepresentationInfo {
634 final BoxLocal boxElement; 617 final BoxLocal boxElement;
635 final Map<Local, BoxFieldElement> capturedVariables; 618 final Map<Local, BoxFieldElement> capturedVariables;
636 619
637 // If the scope is attached to a [For] contains the variables that are 620 // If the scope is attached to a [For] contains the variables that are
638 // declared in the initializer of the [For] and that need to be boxed. 621 // declared in the initializer of the [For] and that need to be boxed.
639 // Otherwise contains the empty List. 622 // Otherwise contains the empty List.
640 List<Local> boxedLoopVariables = const <Local>[]; 623 List<Local> boxedLoopVariables = const <Local>[];
641 624
642 ClosureScope(this.boxElement, this.capturedVariables); 625 ClosureScope(this.boxElement, this.capturedVariables);
643 626
644 Local get context => boxElement; 627 Local get context => boxElement;
645 628
646 bool get requiresContextBox => capturedVariables.keys.isNotEmpty; 629 bool get requiresContextBox => capturedVariables.keys.isNotEmpty;
647 630
648 List<Local> get boxedVariables => boxedLoopVariables; 631 void forEachBoxedVariable(f(Local local, FieldEntity field)) {
632 if (capturedVariables.isNotEmpty) {
633 capturedVariables.forEach(f);
Johnni Winther 2017/06/26 08:31:39 assert(boxedLoopVariables.isEmpty)
634 } else {
635 for (Local l in boxedLoopVariables) {
636 // The boxes for loop variables are constructed on-demand per-iteration
637 // in the locals handler.
638 f(l, null);
639 }
640 }
641 }
649 642
650 bool get hasBoxedVariables => !boxedLoopVariables.isEmpty; 643 Map<Local, BoxFieldElement> get boxedVariables {
Johnni Winther 2017/06/26 08:31:39 When is this used?
Emily Fortuna 2017/06/26 21:57:20 oops. that was holdover from a previous edit. Remo
644 Map<Local, BoxFieldElement> temp = <Local, BoxFieldElement>{};
645 boxedLoopVariables.forEach((Local l) {
646 temp[l] = null;
647 });
648 return capturedVariables.isNotEmpty ? capturedVariables : temp;
649 }
651 650
652 bool isCaptured(Local variable) { 651 bool get hasBoxedVariables => !capturedVariables.isEmpty;
652
653 bool isBoxed(Local variable) {
653 return capturedVariables.containsKey(variable); 654 return capturedVariables.containsKey(variable);
654 } 655 }
655 656
656 void forEachCapturedVariable( 657 void forEachCapturedVariable(
657 f(LocalVariableElement variable, BoxFieldElement boxField)) { 658 f(LocalVariableElement variable, BoxFieldElement boxField)) {
658 capturedVariables.forEach(f); 659 capturedVariables.forEach(f);
659 } 660 }
660 661
662 // Should not be called. Added to make the new interface happy.
663 bool variableIsUsedInTryOrSync(Local variable) =>
664 throw new UnsupportedError("ClosureScope.variableIsUsedInTryOrSync");
665
666 // Should not be called. Added to make the new interface happy.
667 Local get thisLocal => throw new UnsupportedError("ClosureScope.thisLocal");
668
661 String toString() { 669 String toString() {
662 String separator = ''; 670 String separator = '';
663 StringBuffer sb = new StringBuffer(); 671 StringBuffer sb = new StringBuffer();
664 sb.write('ClosureScope('); 672 sb.write('ClosureScope(');
665 if (boxElement != null) { 673 if (boxElement != null) {
666 sb.write('box=$boxElement'); 674 sb.write('box=$boxElement');
667 separator = ','; 675 separator = ',';
668 } 676 }
669 if (boxedLoopVariables.isNotEmpty) { 677 if (boxedLoopVariables.isNotEmpty) {
670 sb.write(separator); 678 sb.write(separator);
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after
719 /// control flow can be non-linear. 727 /// control flow can be non-linear.
720 /// 728 ///
721 /// Also parameters to a `sync*` generator must be boxed, because of the way 729 /// Also parameters to a `sync*` generator must be boxed, because of the way
722 /// we rewrite sync* functions. See also comments in [useLocal]. 730 /// we rewrite sync* functions. See also comments in [useLocal].
723 // TODO(johnniwinther): Add variables to this only if the variable is mutated. 731 // TODO(johnniwinther): Add variables to this only if the variable is mutated.
724 final Set<Local> variablesUsedInTryOrSync = new Set<Local>(); 732 final Set<Local> variablesUsedInTryOrSync = new Set<Local>();
725 733
726 ClosureClassMap(this.closureEntity, this.closureClassEntity, this.callMethod, 734 ClosureClassMap(this.closureEntity, this.closureClassEntity, this.callMethod,
727 this.thisLocal); 735 this.thisLocal);
728 736
737 bool get hasBoxedVariables =>
738 throw new UnsupportedError("ClosureClassMap.hasBoxedVariables");
739
729 List<Local> get createdFieldEntities { 740 List<Local> get createdFieldEntities {
730 List<Local> fields = <Local>[]; 741 List<Local> fields = <Local>[];
731 if (closureClassEntity == null) return const <Local>[]; 742 if (closureClassEntity == null) return const <Local>[];
732 closureClassEntity.closureFields.forEach((field) { 743 closureClassEntity.closureFields.forEach((field) {
733 fields.add(field.local); 744 fields.add(field.local);
734 }); 745 });
735 return fields; 746 return fields;
736 } 747 }
737 748
738 void addFreeVariable(Local element) { 749 void addFreeVariable(Local element) {
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after
788 void forEachBoxedVariable( 799 void forEachBoxedVariable(
789 void f(LocalVariableElement local, BoxFieldElement field)) { 800 void f(LocalVariableElement local, BoxFieldElement field)) {
790 freeVariableMap.forEach((variable, copy) { 801 freeVariableMap.forEach((variable, copy) {
791 if (!isVariableBoxed(variable)) return; 802 if (!isVariableBoxed(variable)) return;
792 f(variable, copy); 803 f(variable, copy);
793 }); 804 });
794 capturingScopes.values.forEach((ClosureScope scope) { 805 capturingScopes.values.forEach((ClosureScope scope) {
795 scope.forEachCapturedVariable(f); 806 scope.forEachCapturedVariable(f);
796 }); 807 });
797 } 808 }
809
810 bool isBoxed(Local local) {
811 bool variableIsBoxed = false;
812 forEachBoxedVariable((LocalVariableElement element, BoxFieldElement field) {
813 if (element == local) variableIsBoxed = true;
814 });
815 return variableIsBoxed;
816 }
798 } 817 }
799 818
800 class ClosureTranslator extends Visitor { 819 class ClosureTranslator extends Visitor {
801 final Compiler compiler; 820 final Compiler compiler;
802 final ClosedWorldRefiner closedWorldRefiner; 821 final ClosedWorldRefiner closedWorldRefiner;
803 final TreeElements elements; 822 final TreeElements elements;
804 int closureFieldCounter = 0; 823 int closureFieldCounter = 0;
805 int boxedFieldCounter = 0; 824 int boxedFieldCounter = 0;
806 bool inTryStatement = false; 825 bool inTryStatement = false;
807 826
(...skipping 685 matching lines...) Expand 10 before | Expand all | Expand 10 after
1493 /// 1512 ///
1494 /// Move the below classes to a JS model eventually. 1513 /// Move the below classes to a JS model eventually.
1495 /// 1514 ///
1496 abstract class JSEntity implements MemberEntity { 1515 abstract class JSEntity implements MemberEntity {
1497 Local get declaredEntity; 1516 Local get declaredEntity;
1498 } 1517 }
1499 1518
1500 abstract class PrivatelyNamedJSEntity implements JSEntity { 1519 abstract class PrivatelyNamedJSEntity implements JSEntity {
1501 Entity get rootOfScope; 1520 Entity get rootOfScope;
1502 } 1521 }
OLDNEW
« no previous file with comments | « no previous file | pkg/compiler/lib/src/kernel/closure.dart » ('j') | pkg/compiler/pubspec.yaml » ('J')

Powered by Google App Engine
This is Rietveld 408576698