| 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 library kernel.transformations.closure_conversion; | |
| 6 | |
| 7 import '../ast.dart'; | |
| 8 import '../core_types.dart'; | |
| 9 import '../visitor.dart'; | |
| 10 | |
| 11 Program transformProgram(Program program) { | |
| 12 var captured = new CapturedVariables(); | |
| 13 captured.visitProgram(program); | |
| 14 | |
| 15 var convert = | |
| 16 new ClosureConverter(new CoreTypes(program), captured.variables); | |
| 17 return convert.visitProgram(program); | |
| 18 } | |
| 19 | |
| 20 class CapturedVariables extends RecursiveVisitor { | |
| 21 FunctionNode _currentFunction; | |
| 22 final Map<VariableDeclaration, FunctionNode> _function = | |
| 23 <VariableDeclaration, FunctionNode>{}; | |
| 24 | |
| 25 final Set<VariableDeclaration> variables = new Set<VariableDeclaration>(); | |
| 26 | |
| 27 visitFunctionNode(FunctionNode node) { | |
| 28 var saved = _currentFunction; | |
| 29 _currentFunction = node; | |
| 30 node.visitChildren(this); | |
| 31 _currentFunction = saved; | |
| 32 } | |
| 33 | |
| 34 visitVariableDeclaration(VariableDeclaration node) { | |
| 35 _function[node] = _currentFunction; | |
| 36 node.visitChildren(this); | |
| 37 } | |
| 38 | |
| 39 visitVariableGet(VariableGet node) { | |
| 40 if (_function[node.variable] != _currentFunction) { | |
| 41 variables.add(node.variable); | |
| 42 } | |
| 43 node.visitChildren(this); | |
| 44 } | |
| 45 | |
| 46 visitVariableSet(VariableSet node) { | |
| 47 if (_function[node.variable] != _currentFunction) { | |
| 48 variables.add(node.variable); | |
| 49 } | |
| 50 node.visitChildren(this); | |
| 51 } | |
| 52 } | |
| 53 | |
| 54 abstract class Context { | |
| 55 Expression get expression; | |
| 56 | |
| 57 void extend(VariableDeclaration variable, Expression value); | |
| 58 | |
| 59 Expression lookup(VariableDeclaration variable); | |
| 60 Expression assign(VariableDeclaration variable, Expression value); | |
| 61 | |
| 62 Context toClosureContext(VariableDeclaration parameter); | |
| 63 } | |
| 64 | |
| 65 class NoContext extends Context { | |
| 66 final ClosureConverter converter; | |
| 67 | |
| 68 NoContext(this.converter); | |
| 69 | |
| 70 Expression get expression => new NullLiteral(); | |
| 71 | |
| 72 void extend(VariableDeclaration variable, Expression value) { | |
| 73 converter.context = | |
| 74 new LocalContext(converter, this)..extend(variable, value); | |
| 75 } | |
| 76 | |
| 77 | |
| 78 Expression lookup(VariableDeclaration variable) { | |
| 79 throw 'Unbound NoContext.lookup($variable)'; | |
| 80 } | |
| 81 | |
| 82 Expression assign(VariableDeclaration variable, Expression value) { | |
| 83 throw 'Unbound NoContext.assign($variable, ...)'; | |
| 84 } | |
| 85 | |
| 86 Context toClosureContext(VariableDeclaration parameter) { | |
| 87 return new ClosureContext(converter, parameter, | |
| 88 <List<VariableDeclaration>>[]); | |
| 89 } | |
| 90 } | |
| 91 | |
| 92 | |
| 93 class LocalContext extends Context { | |
| 94 final ClosureConverter converter; | |
| 95 final Context parent; | |
| 96 final VariableDeclaration self; | |
| 97 final IntLiteral size; | |
| 98 final List<VariableDeclaration> variables = <VariableDeclaration>[]; | |
| 99 | |
| 100 LocalContext._internal(this.converter, this.parent, this.self, this.size); | |
| 101 | |
| 102 factory LocalContext(ClosureConverter converter, Context parent) { | |
| 103 Class contextClass = converter.internalContextClass; | |
| 104 assert(contextClass.constructors.length == 1); | |
| 105 IntLiteral zero = new IntLiteral(0); | |
| 106 VariableDeclaration declaration = | |
| 107 new VariableDeclaration.forValue( | |
| 108 new ConstructorInvocation(contextClass.constructors.first, | |
| 109 new Arguments(<Expression>[zero])), | |
| 110 type: new InterfaceType(contextClass)); | |
| 111 converter.insert(declaration); | |
| 112 converter.insert(new ExpressionStatement( | |
| 113 new PropertySet(new VariableGet(declaration), | |
| 114 new Name('parent'), | |
| 115 parent.expression))); | |
| 116 | |
| 117 return new LocalContext._internal(converter, parent, declaration, zero); | |
| 118 } | |
| 119 | |
| 120 Expression get expression => new VariableGet(self); | |
| 121 | |
| 122 void extend(VariableDeclaration variable, Expression value) { | |
| 123 converter.insert( | |
| 124 new ExpressionStatement( | |
| 125 new MethodInvocation( | |
| 126 expression, | |
| 127 new Name('[]='), | |
| 128 new Arguments( | |
| 129 <Expression>[new IntLiteral(variables.length), value])))); | |
| 130 ++size.value; | |
| 131 variables.add(variable); | |
| 132 } | |
| 133 | |
| 134 Expression lookup(VariableDeclaration variable) { | |
| 135 var index = variables.indexOf(variable); | |
| 136 return index == -1 | |
| 137 ? parent.lookup(variable) | |
| 138 : new MethodInvocation( | |
| 139 expression, | |
| 140 new Name('[]'), | |
| 141 new Arguments(<Expression>[new IntLiteral(index)])); | |
| 142 } | |
| 143 | |
| 144 Expression assign(VariableDeclaration variable, Expression value) { | |
| 145 var index = variables.indexOf(variable); | |
| 146 return index == -1 | |
| 147 ? parent.assign(variable, value) | |
| 148 : new MethodInvocation( | |
| 149 expression, | |
| 150 new Name('[]='), | |
| 151 new Arguments(<Expression>[new IntLiteral(index), value])); | |
| 152 } | |
| 153 | |
| 154 Context toClosureContext(VariableDeclaration parameter) { | |
| 155 List<List<VariableDeclaration>> variabless = <List<VariableDeclaration>>[]; | |
| 156 var current = this; | |
| 157 while (current != null && current is! NoContext) { | |
| 158 if (current is LocalContext) { | |
| 159 variabless.add(current.variables); | |
| 160 current = current.parent; | |
| 161 } else if (current is ClosureContext) { | |
| 162 variabless.addAll(current.variabless); | |
| 163 current = null; | |
| 164 } else if (current is LoopContext) { | |
| 165 // TODO. | |
| 166 current = current.parent; | |
| 167 } | |
| 168 } | |
| 169 return new ClosureContext(converter, parameter, variabless); | |
| 170 } | |
| 171 } | |
| 172 | |
| 173 class LoopContext { | |
| 174 final ClosureConverter converter; | |
| 175 final Context parent; | |
| 176 | |
| 177 LoopContext(this.converter, this.parent); | |
| 178 | |
| 179 void extend(VariableDeclaration variable, Expression value) { | |
| 180 converter.context = | |
| 181 new LocalContext(converter, parent)..extend(variable, value); | |
| 182 } | |
| 183 } | |
| 184 | |
| 185 class ClosureContext extends Context { | |
| 186 final ClosureConverter converter; | |
| 187 final VariableDeclaration self; | |
| 188 final List<List<VariableDeclaration>> variabless; | |
| 189 | |
| 190 ClosureContext(this.converter, this.self, this.variabless); | |
| 191 | |
| 192 Expression get expression => new VariableGet(self); | |
| 193 | |
| 194 void extend(VariableDeclaration variable, Expression value) { | |
| 195 converter.context = | |
| 196 new LocalContext(converter, this)..extend(variable, value); | |
| 197 } | |
| 198 | |
| 199 Expression lookup(VariableDeclaration variable) { | |
| 200 var context = expression; | |
| 201 for (var variables in variabless) { | |
| 202 var index = variables.indexOf(variable); | |
| 203 if (index != -1) { | |
| 204 return new MethodInvocation( | |
| 205 context, | |
| 206 new Name('[]'), | |
| 207 new Arguments(<Expression>[new IntLiteral(index)])); | |
| 208 } | |
| 209 context = new PropertyGet(context, new Name('parent')); | |
| 210 } | |
| 211 throw 'Unbound ClosureContext.lookup($variable)'; | |
| 212 } | |
| 213 | |
| 214 Expression assign(VariableDeclaration variable, Expression value) { | |
| 215 var context = expression; | |
| 216 for (var variables in variabless) { | |
| 217 var index = variables.indexOf(variable); | |
| 218 if (index != -1) { | |
| 219 return new MethodInvocation( | |
| 220 context, | |
| 221 new Name('[]='), | |
| 222 new Arguments(<Expression>[new IntLiteral(index), value])); | |
| 223 } | |
| 224 context = new PropertyGet(context, new Name('parent')); | |
| 225 } | |
| 226 throw 'Unbound ClosureContext.lookup($variable)'; | |
| 227 } | |
| 228 | |
| 229 Context toClosureContext(VariableDeclaration parameter) { | |
| 230 return new ClosureContext(converter, parameter, variabless); | |
| 231 } | |
| 232 } | |
| 233 | |
| 234 class ClosureConverter extends Transformer { | |
| 235 final CoreTypes coreTypes; | |
| 236 Class internalContextClass; | |
| 237 final Set<VariableDeclaration> captured; | |
| 238 | |
| 239 Block _currentBlock; | |
| 240 int _insertionIndex = 0; | |
| 241 | |
| 242 Context context; | |
| 243 | |
| 244 ClosureConverter(this.coreTypes, this.captured) { | |
| 245 internalContextClass = coreTypes.getCoreClass('dart:_internal', 'Context'); | |
| 246 } | |
| 247 | |
| 248 void insert(Statement statement) { | |
| 249 _currentBlock.statements.insert(_insertionIndex++, statement); | |
| 250 statement.parent = _currentBlock; | |
| 251 } | |
| 252 | |
| 253 TreeNode visitConstructor(Constructor node) { | |
| 254 return node; | |
| 255 } | |
| 256 | |
| 257 TreeNode visitFunctionDeclaration(FunctionDeclaration node) { | |
| 258 if (captured.contains(node.variable)) { | |
| 259 context.extend(node.variable, | |
| 260 new FunctionExpression(node.function)); | |
| 261 } | |
| 262 | |
| 263 Block savedBlock = _currentBlock; | |
| 264 int savedIndex = _insertionIndex; | |
| 265 Context savedContext = context; | |
| 266 | |
| 267 Statement body = node.function.body; | |
| 268 assert(body != null); | |
| 269 | |
| 270 if (body is Block) { | |
| 271 _currentBlock = body; | |
| 272 } else { | |
| 273 _currentBlock = new Block(<Statement>[body]); | |
| 274 node.function.body = body.parent = _currentBlock; | |
| 275 } | |
| 276 _insertionIndex = 0; | |
| 277 | |
| 278 // TODO: This is really the closure, not the context. | |
| 279 VariableDeclaration parameter = | |
| 280 new VariableDeclaration(null, | |
| 281 type: internalContextClass.rawType, | |
| 282 isFinal: true); | |
| 283 node.function.positionalParameters.insert(0, parameter); | |
| 284 parameter.parent = node.function; | |
| 285 ++node.function.requiredParameterCount; | |
| 286 context = context.toClosureContext(parameter); | |
| 287 | |
| 288 // Don't visit the children, because that included a variable declaration. | |
| 289 node.function = node.function.accept(this); | |
| 290 | |
| 291 _currentBlock = savedBlock; | |
| 292 _insertionIndex = savedIndex; | |
| 293 context = savedContext; | |
| 294 | |
| 295 return captured.contains(node.variable) ? null : node; | |
| 296 } | |
| 297 | |
| 298 TreeNode visitFunctionExpression(FunctionExpression node) { | |
| 299 Block savedBlock = _currentBlock; | |
| 300 int savedIndex = _insertionIndex; | |
| 301 Context savedContext = context; | |
| 302 | |
| 303 Statement body = node.function.body; | |
| 304 assert(body != null); | |
| 305 | |
| 306 if (body is Block) { | |
| 307 _currentBlock = body; | |
| 308 } else { | |
| 309 _currentBlock = new Block(<Statement>[body]); | |
| 310 node.function.body = body.parent = _currentBlock; | |
| 311 } | |
| 312 _insertionIndex = 0; | |
| 313 | |
| 314 // TODO: This is really the closure, not the context. | |
| 315 VariableDeclaration parameter = | |
| 316 new VariableDeclaration(null, | |
| 317 type: internalContextClass.rawType, | |
| 318 isFinal: true); | |
| 319 node.function.positionalParameters.insert(0, parameter); | |
| 320 parameter.parent = node.function; | |
| 321 ++node.function.requiredParameterCount; | |
| 322 context = context.toClosureContext(parameter); | |
| 323 | |
| 324 node.transformChildren(this); | |
| 325 | |
| 326 _currentBlock = savedBlock; | |
| 327 _insertionIndex = savedIndex; | |
| 328 context = savedContext; | |
| 329 | |
| 330 return node; | |
| 331 } | |
| 332 | |
| 333 TreeNode visitProcedure(Procedure node) { | |
| 334 assert(_currentBlock == null); | |
| 335 assert(_insertionIndex == 0); | |
| 336 assert(context == null); | |
| 337 | |
| 338 Statement body = node.function.body; | |
| 339 if (body == null) return node; | |
| 340 | |
| 341 // Ensure that the body is a block which becomes the current block. | |
| 342 if (body is Block) { | |
| 343 _currentBlock = body; | |
| 344 } else { | |
| 345 _currentBlock = new Block(<Statement>[body]); | |
| 346 node.function.body = body.parent = _currentBlock; | |
| 347 } | |
| 348 _insertionIndex = 0; | |
| 349 | |
| 350 // Start with no context. This happens after setting up _currentBlock | |
| 351 // so statements can be emitted into _currentBlock if necessary. | |
| 352 context = new NoContext(this); | |
| 353 | |
| 354 node.transformChildren(this); | |
| 355 | |
| 356 _currentBlock = null; | |
| 357 _insertionIndex = 0; | |
| 358 context = null; | |
| 359 return node; | |
| 360 } | |
| 361 | |
| 362 TreeNode visitLocalInitializer(LocalInitializer node) { | |
| 363 assert(!captured.contains(node.variable)); | |
| 364 node.transformChildren(this); | |
| 365 return node; | |
| 366 } | |
| 367 | |
| 368 TreeNode visitFunctionNode(FunctionNode node) { | |
| 369 transformList(node.typeParameters, this, node); | |
| 370 | |
| 371 void extend(VariableDeclaration parameter) { | |
| 372 context.extend(parameter, new VariableGet(parameter)); | |
| 373 } | |
| 374 // TODO: Can parameters contain initializers (e.g., for optional ones) that | |
| 375 // need to be closure converted? | |
| 376 node.positionalParameters.where(captured.contains).forEach(extend); | |
| 377 node.namedParameters.where(captured.contains).forEach(extend); | |
| 378 | |
| 379 assert(node.body != null); | |
| 380 node.body = node.body.accept(this); | |
| 381 node.body.parent = node; | |
| 382 return node; | |
| 383 } | |
| 384 | |
| 385 TreeNode visitBlock(Block node) { | |
| 386 Block savedBlock; | |
| 387 int savedIndex; | |
| 388 if (_currentBlock != node) { | |
| 389 savedBlock = _currentBlock; | |
| 390 savedIndex = _insertionIndex; | |
| 391 _currentBlock = node; | |
| 392 _insertionIndex = 0; | |
| 393 } | |
| 394 | |
| 395 while (_insertionIndex < _currentBlock.statements.length) { | |
| 396 assert(_currentBlock == node); | |
| 397 | |
| 398 var original = _currentBlock.statements[_insertionIndex]; | |
| 399 var transformed = original.accept(this); | |
| 400 assert(_currentBlock.statements[_insertionIndex] == original); | |
| 401 if (transformed == null) { | |
| 402 _currentBlock.statements.removeAt(_insertionIndex); | |
| 403 } else { | |
| 404 _currentBlock.statements[_insertionIndex++] = transformed; | |
| 405 transformed.parent = _currentBlock; | |
| 406 } | |
| 407 } | |
| 408 | |
| 409 if (savedBlock != null) { | |
| 410 _currentBlock = savedBlock; | |
| 411 _insertionIndex = savedIndex; | |
| 412 } | |
| 413 return node; | |
| 414 } | |
| 415 | |
| 416 TreeNode visitVariableDeclaration(VariableDeclaration node) { | |
| 417 node.transformChildren(this); | |
| 418 | |
| 419 if (!captured.contains(node)) return node; | |
| 420 context.extend(node, node.initializer ?? new NullLiteral()); | |
| 421 | |
| 422 // TODO(ahe): Return null here when the parent has been correctly | |
| 423 // rewritten. So far, only for-in is known to use this return value. | |
| 424 return new VariableDeclaration(null, initializer: new InvalidExpression()); | |
| 425 } | |
| 426 | |
| 427 TreeNode visitVariableGet(VariableGet node) { | |
| 428 return captured.contains(node.variable) | |
| 429 ? context.lookup(node.variable) | |
| 430 : node; | |
| 431 } | |
| 432 | |
| 433 TreeNode visitVariableSet(VariableSet node) { | |
| 434 node.transformChildren(this); | |
| 435 | |
| 436 return captured.contains(node.variable) | |
| 437 ? context.assign(node.variable, node.value) | |
| 438 : node; | |
| 439 } | |
| 440 } | |
| OLD | NEW |