OLD | NEW |
1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2017, 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 'package:kernel/ast.dart' as ir; | 5 import 'package:kernel/ast.dart' as ir; |
6 | 6 |
7 import '../closure.dart'; | 7 import '../closure.dart'; |
8 import '../common/tasks.dart'; | 8 import '../common/tasks.dart'; |
9 import '../elements/entities.dart'; | 9 import '../elements/entities.dart'; |
10 import '../kernel/element_map.dart'; | 10 import '../kernel/element_map.dart'; |
11 import '../world.dart'; | 11 import '../world.dart'; |
| 12 import 'elements.dart'; |
| 13 import 'closure_visitors.dart'; |
12 import 'locals.dart'; | 14 import 'locals.dart'; |
13 | 15 |
14 class KernelClosureDataBuilder extends ir.Visitor { | |
15 final KernelToLocalsMap _localsMap; | |
16 final KernelClosureRepresentationInfo info; | |
17 | |
18 bool _inTry = false; | |
19 | |
20 KernelClosureDataBuilder(this._localsMap, ThisLocal thisLocal) | |
21 : info = new KernelClosureRepresentationInfo(thisLocal); | |
22 | |
23 @override | |
24 defaultNode(ir.Node node) { | |
25 node.visitChildren(this); | |
26 } | |
27 | |
28 @override | |
29 visitTryCatch(ir.TryCatch node) { | |
30 bool oldInTry = _inTry; | |
31 _inTry = true; | |
32 node.visitChildren(this); | |
33 _inTry = oldInTry; | |
34 } | |
35 | |
36 @override | |
37 visitTryFinally(ir.TryFinally node) { | |
38 bool oldInTry = _inTry; | |
39 _inTry = true; | |
40 node.visitChildren(this); | |
41 _inTry = oldInTry; | |
42 } | |
43 | |
44 @override | |
45 visitVariableGet(ir.VariableGet node) { | |
46 if (_inTry) { | |
47 info.registerUsedInTryOrSync(_localsMap.getLocal(node.variable)); | |
48 } | |
49 } | |
50 | |
51 @override | |
52 visitVariableSet(ir.VariableSet node) { | |
53 if (_inTry) { | |
54 info.registerUsedInTryOrSync(_localsMap.getLocal(node.variable)); | |
55 } | |
56 node.visitChildren(this); | |
57 } | |
58 } | |
59 | |
60 /// Closure conversion code using our new Entity model. Closure conversion is | 16 /// Closure conversion code using our new Entity model. Closure conversion is |
61 /// necessary because the semantics of closures are slightly different in Dart | 17 /// necessary because the semantics of closures are slightly different in Dart |
62 /// than JavaScript. Closure conversion is separated out into two phases: | 18 /// than JavaScript. Closure conversion is separated out into two phases: |
63 /// generation of a new (temporary) representation to store where variables need | 19 /// generation of a new (temporary) representation to store where variables need |
64 /// to be hoisted/captured up at another level to re-write the closure, and then | 20 /// to be hoisted/captured up at another level to re-write the closure, and then |
65 /// the code generation phase where we generate elements and/or instructions to | 21 /// the code generation phase where we generate elements and/or instructions to |
66 /// represent this new code path. | 22 /// represent this new code path. |
67 /// | 23 /// |
68 /// For a general explanation of how closure conversion works at a high level, | 24 /// For a general explanation of how closure conversion works at a high level, |
69 /// check out: | 25 /// check out: |
70 /// http://siek.blogspot.com/2012/07/essence-of-closure-conversion.html or | 26 /// http://siek.blogspot.com/2012/07/essence-of-closure-conversion.html or |
71 /// http://matt.might.net/articles/closure-conversion/. | 27 /// http://matt.might.net/articles/closure-conversion/. |
| 28 // TODO(efortuna): Change inheritance hierarchy so that the |
| 29 // ClosureConversionTask doesn't inherit from ClosureTask because it's just a |
| 30 // glorified timer. |
72 class KernelClosureConversionTask extends ClosureConversionTask<ir.Node> { | 31 class KernelClosureConversionTask extends ClosureConversionTask<ir.Node> { |
73 final KernelToElementMapForBuilding _elementMap; | 32 final KernelToElementMapForBuilding _elementMap; |
74 final GlobalLocalsMap _globalLocalsMap; | 33 final GlobalLocalsMap _globalLocalsMap; |
75 Map<Entity, ClosureRepresentationInfo> _infoMap = | 34 Map<ir.Node, ClosureScope> _closureScopeMap = <ir.Node, ClosureScope>{}; |
| 35 |
| 36 Map<Entity, ClosureRepresentationInfo> _closureRepresentationMap = |
76 <Entity, ClosureRepresentationInfo>{}; | 37 <Entity, ClosureRepresentationInfo>{}; |
77 | 38 |
78 KernelClosureConversionTask( | 39 /// Should only be used at the very beginning to ensure we are looking at the |
79 Measurer measurer, this._elementMap, this._globalLocalsMap) | 40 /// right kind of elements. |
| 41 // TODO(efortuna): Remove this map once we have one kernel backend strategy. |
| 42 final JsToFrontendMap _kToJElementMap; |
| 43 |
| 44 KernelClosureConversionTask(Measurer measurer, this._elementMap, |
| 45 this._kToJElementMap, this._globalLocalsMap) |
80 : super(measurer); | 46 : super(measurer); |
81 | 47 |
82 /// The combined steps of generating our intermediate representation of | 48 /// The combined steps of generating our intermediate representation of |
83 /// closures that need to be rewritten and generating the element model. | 49 /// closures that need to be rewritten and generating the element model. |
84 /// Ultimately these two steps will be split apart with the second step | 50 /// Ultimately these two steps will be split apart with the second step |
85 /// happening later in compilation just before codegen. These steps are | 51 /// happening later in compilation just before codegen. These steps are |
86 /// combined here currently to provide a consistent interface to the rest of | 52 /// combined here currently to provide a consistent interface to the rest of |
87 /// the compiler until we are ready to separate these phases. | 53 /// the compiler until we are ready to separate these phases. |
88 @override | 54 @override |
89 void convertClosures(Iterable<MemberEntity> processedEntities, | 55 void convertClosures(Iterable<MemberEntity> processedEntities, |
90 ClosedWorldRefiner closedWorldRefiner) { | 56 ClosedWorldRefiner closedWorldRefiner) { |
91 // TODO(efortuna): implement. | 57 var closuresToGenerate = <ir.Node, ScopeInfo>{}; |
| 58 processedEntities.forEach((MemberEntity kEntity) { |
| 59 MemberEntity entity = kEntity; |
| 60 if (_kToJElementMap != null) { |
| 61 entity = _kToJElementMap.toBackendMember(kEntity); |
| 62 } |
| 63 if (entity.isAbstract) return; |
| 64 if (entity.isField && !entity.isInstanceMember) { |
| 65 ir.Field field = _elementMap.getMemberNode(entity); |
| 66 // Skip top-level/static fields without an initializer. |
| 67 if (field.initializer == null) return; |
| 68 } |
| 69 _buildClosureModel(entity, closuresToGenerate, closedWorldRefiner); |
| 70 }); |
| 71 |
| 72 for (ir.Node node in closuresToGenerate.keys) { |
| 73 _produceSyntheticElements( |
| 74 node, closuresToGenerate[node], closedWorldRefiner); |
| 75 } |
92 } | 76 } |
93 | 77 |
94 /// TODO(johnniwinther,efortuna): Implement this. | 78 /// Inspect members and mark if those members capture any state that needs to |
95 @override | 79 /// be marked as free variables. |
96 ClosureScope getClosureScope(ir.Node node) { | 80 void _buildClosureModel( |
97 return const ClosureScope(); | 81 MemberEntity entity, |
| 82 Map<ir.Node, ScopeInfo> closuresToGenerate, |
| 83 ClosedWorldRefiner closedWorldRefiner) { |
| 84 ir.Node node = _elementMap.getMemberNode(entity); |
| 85 if (_closureScopeMap.keys.contains(node)) return; |
| 86 ClosureScopeBuilder translator = new ClosureScopeBuilder(_closureScopeMap, |
| 87 closuresToGenerate, _globalLocalsMap.getLocalsMap(entity), _elementMap); |
| 88 if (entity.isField) { |
| 89 if (node is ir.Field && node.initializer != null) { |
| 90 translator.translateLazyInitializer(node); |
| 91 } |
| 92 } else { |
| 93 assert(node is ir.Procedure || node is ir.Constructor); |
| 94 translator.translateConstructorOrProcedure(node); |
| 95 } |
| 96 } |
| 97 |
| 98 /// Given what variables are captured at each point, construct closure classes |
| 99 /// with fields containing the captured variables to replicate the Dart |
| 100 /// closure semantics in JS. |
| 101 void _produceSyntheticElements( |
| 102 ir.Node node, ScopeInfo info, ClosedWorldRefiner closedWorldRefiner) { |
| 103 Entity entity; |
| 104 KernelClosureClass closureClass = |
| 105 new KernelClosureClass.fromScopeInfo(info); |
| 106 if (node is ir.FunctionNode) { |
| 107 // We want the original declaration where that function is used to point |
| 108 // to the correct closure class. |
| 109 // TODO(efortuna): entity equivalent of element.declaration? |
| 110 node = (node as ir.FunctionNode).parent; |
| 111 _closureRepresentationMap[closureClass.callMethod] = closureClass; |
| 112 } |
| 113 |
| 114 if (node is ir.Member) { |
| 115 entity = _elementMap.getMember(node); |
| 116 } else { |
| 117 entity = _elementMap.getLocalFunction(node); |
| 118 } |
| 119 assert(entity != null); |
| 120 |
| 121 _closureRepresentationMap[entity] = closureClass; |
98 } | 122 } |
99 | 123 |
100 @override | 124 @override |
101 ScopeInfo getScopeInfo(Entity entity) { | 125 ScopeInfo getScopeInfo(Entity entity) { |
102 // TODO(efortuna): Specialize this function from the one below. | |
103 return getClosureRepresentationInfo(entity); | 126 return getClosureRepresentationInfo(entity); |
104 } | 127 } |
105 | 128 |
106 /// TODO(johnniwinther,efortuna): Implement this. | |
107 @override | 129 @override |
108 LoopClosureScope getLoopClosureScope(ir.Node loopNode) { | 130 // TODO(efortuna): Eventually closureScopeMap[node] should always be non-null, |
109 return const LoopClosureScope(); | 131 // and we should just test that with an assert. |
| 132 ClosureScope getClosureScope(ir.Node node) => |
| 133 _closureScopeMap[node] ?? const ClosureScope(); |
| 134 |
| 135 @override |
| 136 // TODO(efortuna): Eventually closureScopeMap[node] should always be non-null, |
| 137 // and we should just test that with an assert. |
| 138 LoopClosureScope getLoopClosureScope(ir.Node loopNode) => |
| 139 _closureScopeMap[loopNode] ?? const LoopClosureScope(); |
| 140 |
| 141 @override |
| 142 // TODO(efortuna): Eventually closureRepresentationMap[node] should always be |
| 143 // non-null, and we should just test that with an assert. |
| 144 ClosureRepresentationInfo getClosureRepresentationInfo(Entity entity) => |
| 145 _closureRepresentationMap[entity] ?? const ClosureRepresentationInfo(); |
| 146 } |
| 147 |
| 148 class KernelScopeInfo extends ScopeInfo { |
| 149 final Set<Local> localsUsedInTryOrSync; |
| 150 final Local thisLocal; |
| 151 final Set<Local> boxedVariables; |
| 152 |
| 153 /// The set of variables that were defined in another scope, but are used in |
| 154 /// this scope. |
| 155 Set<ir.VariableDeclaration> freeVariables = new Set<ir.VariableDeclaration>(); |
| 156 |
| 157 KernelScopeInfo(this.thisLocal) |
| 158 : localsUsedInTryOrSync = new Set<Local>(), |
| 159 boxedVariables = new Set<Local>(); |
| 160 |
| 161 KernelScopeInfo.from(this.thisLocal, KernelScopeInfo info) |
| 162 : localsUsedInTryOrSync = info.localsUsedInTryOrSync, |
| 163 boxedVariables = info.boxedVariables; |
| 164 |
| 165 KernelScopeInfo.withBoxedVariables(this.boxedVariables, this.thisLocal) |
| 166 : localsUsedInTryOrSync = new Set<Local>(); |
| 167 |
| 168 void forEachBoxedVariable(f(Local local, FieldEntity field)) { |
| 169 boxedVariables.forEach((Local l) { |
| 170 // TODO(efortuna): add FieldEntities as created. |
| 171 f(l, null); |
| 172 }); |
110 } | 173 } |
111 | 174 |
112 @override | 175 bool localIsUsedInTryOrSync(Local variable) => |
113 ClosureRepresentationInfo getClosureRepresentationInfo(Entity entity) { | 176 localsUsedInTryOrSync.contains(variable); |
114 return _infoMap.putIfAbsent(entity, () { | |
115 if (entity is MemberEntity) { | |
116 ir.Member node = _elementMap.getMemberNode(entity); | |
117 ThisLocal thisLocal; | |
118 if (entity.isInstanceMember) { | |
119 thisLocal = new ThisLocal(entity); | |
120 } | |
121 KernelClosureDataBuilder builder = new KernelClosureDataBuilder( | |
122 _globalLocalsMap.getLocalsMap(entity), thisLocal); | |
123 node.accept(builder); | |
124 return builder.info; | |
125 } | |
126 | |
127 /// TODO(johnniwinther,efortuna): Implement this. | |
128 return const ClosureRepresentationInfo(); | |
129 }); | |
130 } | |
131 } | |
132 | |
133 // TODO(johnniwinther): Add unittest for the computed | |
134 // [ClosureRepresentationInfo]. | |
135 class KernelClosureRepresentationInfo extends ClosureRepresentationInfo { | |
136 final ThisLocal thisLocal; | |
137 final Set<Local> _localsUsedInTryOrSync = new Set<Local>(); | |
138 | |
139 KernelClosureRepresentationInfo(this.thisLocal); | |
140 | |
141 void registerUsedInTryOrSync(Local local) { | |
142 _localsUsedInTryOrSync.add(local); | |
143 } | |
144 | |
145 bool variableIsUsedInTryOrSync(Local variable) => | |
146 _localsUsedInTryOrSync.contains(variable); | |
147 | 177 |
148 String toString() { | 178 String toString() { |
149 StringBuffer sb = new StringBuffer(); | 179 StringBuffer sb = new StringBuffer(); |
150 sb.write('this=$thisLocal,'); | 180 sb.write('this=$thisLocal,'); |
151 sb.write('localsUsedInTryOrSync={${_localsUsedInTryOrSync.join(', ')}}'); | 181 sb.write('localsUsedInTryOrSync={${localsUsedInTryOrSync.join(', ')}}'); |
152 return sb.toString(); | 182 return sb.toString(); |
153 } | 183 } |
| 184 |
| 185 bool isBoxed(Local variable) => boxedVariables.contains(variable); |
154 } | 186 } |
| 187 |
| 188 class KernelClosureScope extends KernelScopeInfo implements ClosureScope { |
| 189 final Local context; |
| 190 |
| 191 KernelClosureScope(Set<Local> boxedVariables, this.context, Local thisLocal) |
| 192 : super.withBoxedVariables(boxedVariables, thisLocal); |
| 193 |
| 194 bool get requiresContextBox => boxedVariables.isNotEmpty; |
| 195 } |
| 196 |
| 197 class KernelLoopClosureScope extends KernelClosureScope |
| 198 implements LoopClosureScope { |
| 199 final List<Local> boxedLoopVariables; |
| 200 |
| 201 KernelLoopClosureScope(Set<Local> boxedVariables, this.boxedLoopVariables, |
| 202 Local context, Local thisLocal) |
| 203 : super(boxedVariables, context, thisLocal); |
| 204 |
| 205 bool get hasBoxedLoopVariables => boxedLoopVariables.isNotEmpty; |
| 206 } |
| 207 |
| 208 // TODO(johnniwinther): Add unittest for the computed [ClosureClass]. |
| 209 class KernelClosureClass extends KernelScopeInfo |
| 210 implements ClosureRepresentationInfo { |
| 211 KernelClosureClass.fromScopeInfo(ScopeInfo info) |
| 212 : super.from(info.thisLocal, info); |
| 213 |
| 214 // TODO(efortuna): Implement. |
| 215 Local get closureEntity => null; |
| 216 |
| 217 // TODO(efortuna): Implement. |
| 218 ClassEntity get closureClassEntity => null; |
| 219 |
| 220 // TODO(efortuna): Implement. |
| 221 FunctionEntity get callMethod => null; |
| 222 |
| 223 // TODO(efortuna): Implement. |
| 224 List<Local> get createdFieldEntities => const <Local>[]; |
| 225 |
| 226 // TODO(efortuna): Implement. |
| 227 FieldEntity get thisFieldEntity => null; |
| 228 |
| 229 // TODO(efortuna): Implement. |
| 230 void forEachCapturedVariable(f(Local from, FieldEntity to)) {} |
| 231 |
| 232 // TODO(efortuna): Implement. |
| 233 @override |
| 234 void forEachBoxedVariable(f(Local local, FieldEntity field)) {} |
| 235 |
| 236 // TODO(efortuna): Implement. |
| 237 void forEachFreeVariable(f(Local variable, FieldEntity field)) {} |
| 238 |
| 239 // TODO(efortuna): Implement. |
| 240 bool isVariableBoxed(Local variable) => false; |
| 241 |
| 242 // TODO(efortuna): Implement. |
| 243 // Why is this closure not actually a closure? Well, to properly call |
| 244 // ourselves a closure, we need to register the new closure class with the |
| 245 // ClosedWorldRefiner, which currently only takes elements. The change to |
| 246 // that (and the subsequent adjustment here) will follow soon. |
| 247 bool get isClosure => false; |
| 248 } |
OLD | NEW |