Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2015, 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 tree_ir.optimization.variable_merger; | |
| 6 | |
| 7 import 'optimization.dart' show Pass, PassMixin; | |
| 8 import '../tree_ir_nodes.dart'; | |
| 9 import '../../elements/elements.dart' show Local, ParameterElement; | |
| 10 | |
| 11 /// Merges variables based on liveness and source variable information. | |
| 12 /// | |
| 13 /// This phase cleans up artifacts introduced by the translation through CPS, | |
| 14 /// where each source variable is translated into several copies. The copies | |
| 15 /// are merged again when they are not live simultaneously. | |
| 16 class VariableMerger extends RecursiveVisitor with PassMixin { | |
| 17 String get passName => 'Variable merger'; | |
| 18 | |
| 19 @override | |
| 20 void rewriteExecutableDefinition(ExecutableDefinition node) { | |
| 21 visitExecutableDefinition(node); | |
| 22 } | |
| 23 | |
| 24 /// Rewrites the given function. | |
| 25 /// This is called for the outermost function and inner functions. | |
| 26 void rewriteFunction(ExecutableDefinition node) { | |
| 27 BlockGraphBuilder builder = new BlockGraphBuilder(); | |
| 28 builder.visitExecutableDefinition(node); | |
| 29 _computeLiveness(builder.blocks); | |
| 30 Map<Variable, Variable> subst = _computeRegisterAllocation(builder.blocks); | |
| 31 new SubstituteVariables(subst).visitExecutableDefinition(node); | |
| 32 } | |
| 33 | |
| 34 visitFunctionDefinition(FunctionDefinition node) { | |
| 35 super.visitFunctionDefinition(node); // Recurse to visit inner functions. | |
| 36 rewriteFunction(node); | |
| 37 } | |
| 38 | |
| 39 visitFieldDefinition(FieldDefinition node) { | |
| 40 super.visitFieldDefinition(node); | |
| 41 rewriteFunction(node); | |
| 42 } | |
| 43 | |
| 44 visitConstructorDefinition(ConstructorDefinition node) { | |
| 45 super.visitConstructorDefinition(node); | |
| 46 rewriteFunction(node); | |
| 47 } | |
| 48 } | |
| 49 | |
| 50 /// A read or write access to a variable. | |
| 51 class VariableAccess { | |
| 52 Variable variable; | |
| 53 bool isRead; | |
| 54 bool get isWrite => !isRead; | |
| 55 | |
| 56 VariableAccess.read(this.variable) : isRead = true; | |
| 57 VariableAccess.write(this.variable) : isRead = false; | |
| 58 } | |
| 59 | |
| 60 /// Basic block in a control-flow graph. | |
| 61 class Block { | |
| 62 /// List of predecessors in the control-flow graph. | |
| 63 final List<Block> predecessors = <Block>[]; | |
| 64 | |
| 65 /// Entry to the catch block for the enclosing try, or `null`. | |
| 66 final Block catchBlock; | |
| 67 | |
| 68 /// List of nodes with this block as [catchBlock]. | |
| 69 final List<Block> catchPredecessors = <Block>[]; | |
| 70 | |
| 71 /// Sequence of read and write accesses in the block. | |
| 72 final List<VariableAccess> accesses = <VariableAccess>[]; | |
| 73 | |
| 74 /// Auxiliary fields used by the liveness analysis. | |
| 75 bool inWorklist = true; | |
| 76 Set<Variable> liveIn; | |
| 77 Set<Variable> liveOut = new Set<Variable>(); | |
| 78 Set<Variable> gen = new Set<Variable>(); | |
| 79 Set<Variable> kill = new Set<Variable>(); | |
| 80 | |
| 81 /// Adds a read operation to the block and updates gen/kill sets accordingly. | |
| 82 void addRead(Variable variable) { | |
|
Kevin Millikin (Google)
2015/04/07 14:03:00
I guess there should be a comment here reminding u
| |
| 83 if (!kill.contains(variable)) { | |
| 84 gen.add(variable); | |
| 85 } | |
| 86 accesses.add(new VariableAccess.read(variable)); | |
| 87 } | |
| 88 | |
| 89 /// Adds a write operation to the block and updates gen/kill sets accordingly. | |
| 90 void addWrite(Variable variable) { | |
| 91 if (!gen.contains(variable)) { | |
| 92 kill.add(variable); | |
| 93 } | |
| 94 accesses.add(new VariableAccess.write(variable)); | |
| 95 } | |
| 96 | |
| 97 Block(this.catchBlock) { | |
| 98 if (catchBlock != null) { | |
| 99 catchBlock.catchPredecessors.add(this); | |
| 100 } | |
| 101 } | |
| 102 } | |
| 103 | |
| 104 /// Builds a control-flow graph suitable for performing liveness analysis. | |
| 105 class BlockGraphBuilder extends RecursiveVisitor { | |
| 106 Map<Label, Block> _jumpTarget = <Label, Block>{}; | |
| 107 Block _currentBlock; | |
| 108 List<Block> blocks = <Block>[]; | |
| 109 | |
| 110 /// Variables with an assignment that should be treated as final. | |
| 111 /// | |
| 112 /// Such variables cannot be merged with any other variables, so we exclude | |
| 113 /// them from the control-flow graph entirely. | |
| 114 Set<Variable> _ignoredVariables = new Set<Variable>(); | |
| 115 | |
| 116 BlockGraphBuilder() { | |
| 117 _currentBlock = newBlock(); | |
| 118 } | |
| 119 | |
| 120 /// Creates a new block with the current exception handler or [catchBlock] | |
| 121 /// if provided. | |
| 122 Block newBlock({Block catchBlock}) { | |
| 123 if (catchBlock == null && _currentBlock != null) { | |
| 124 catchBlock = _currentBlock.catchBlock; | |
| 125 } | |
| 126 Block block = new Block(catchBlock); | |
| 127 blocks.add(block); | |
| 128 return block; | |
| 129 } | |
| 130 | |
| 131 /// Starts a new block after the end of [block]. | |
| 132 void branchFrom(Block block, {Block catchBlock}) { | |
| 133 _currentBlock = newBlock(catchBlock: catchBlock)..predecessors.add(block); | |
| 134 } | |
| 135 | |
| 136 /// Called when reading from [variable]. | |
| 137 /// | |
| 138 /// Appends a read operation to the current basic block. | |
| 139 void read(Variable variable) { | |
| 140 if (variable.isCaptured) return; | |
| 141 if (_ignoredVariables.contains(variable)) return; | |
| 142 _currentBlock.addRead(variable); | |
| 143 } | |
| 144 | |
| 145 /// Called when writing to [variable]. | |
| 146 /// | |
| 147 /// Appends a write operation to the current basic block. | |
| 148 void write(Variable variable) { | |
| 149 if (variable.isCaptured) return; | |
| 150 if (_ignoredVariables.contains(variable)) return; | |
| 151 _currentBlock.addWrite(variable); | |
| 152 } | |
| 153 | |
| 154 /// Called to indicate that [variable] should not be merged, and therefore | |
| 155 /// be excluded from the control-flow graph. | |
| 156 /// Subsequent calls to [read] and [write] will ignore it. | |
| 157 void ignoreVariable(Variable variable) { | |
| 158 _ignoredVariables.add(variable); | |
| 159 } | |
| 160 | |
| 161 visitVariableUse(VariableUse node) { | |
| 162 read(node.variable); | |
| 163 } | |
| 164 | |
| 165 visitAssign(Assign node) { | |
| 166 visitExpression(node.value); | |
| 167 write(node.variable); | |
| 168 visitStatement(node.next); | |
| 169 } | |
| 170 | |
| 171 visitIf(If node) { | |
| 172 visitExpression(node.condition); | |
| 173 Block afterCondition = _currentBlock; | |
| 174 branchFrom(afterCondition); | |
| 175 visitStatement(node.thenStatement); | |
| 176 branchFrom(afterCondition); | |
| 177 visitStatement(node.elseStatement); | |
| 178 } | |
| 179 | |
| 180 visitLabeledStatement(LabeledStatement node) { | |
| 181 Block join = _jumpTarget[node.label] = newBlock(); | |
| 182 visitStatement(node.body); // visitBreak will add predecessors to join. | |
| 183 _currentBlock = join; | |
| 184 visitStatement(node.next); | |
| 185 } | |
| 186 | |
| 187 visitBreak(Break node) { | |
| 188 _jumpTarget[node.target].predecessors.add(_currentBlock); | |
| 189 } | |
| 190 | |
| 191 visitContinue(Continue node) { | |
| 192 _jumpTarget[node.target].predecessors.add(_currentBlock); | |
| 193 } | |
| 194 | |
| 195 visitWhileTrue(WhileTrue node) { | |
| 196 Block join = _jumpTarget[node.label] = newBlock(); | |
| 197 join.predecessors.add(_currentBlock); | |
| 198 _currentBlock = join; | |
| 199 visitStatement(node.body); // visitContinue will add predecessors to join. | |
| 200 } | |
| 201 | |
| 202 visitWhileCondition(WhileCondition node) { | |
| 203 Block join = _jumpTarget[node.label] = newBlock(); | |
| 204 join.predecessors.add(_currentBlock); | |
| 205 _currentBlock = join; | |
| 206 visitExpression(node.condition); | |
| 207 Block afterCondition = _currentBlock; | |
| 208 branchFrom(afterCondition); | |
| 209 visitStatement(node.body); // visitContinue will add predecessors to join. | |
| 210 branchFrom(afterCondition); | |
| 211 visitStatement(node.next); | |
| 212 } | |
| 213 | |
| 214 visitTry(Try node) { | |
| 215 Block catchBlock = newBlock(); | |
| 216 branchFrom(_currentBlock, catchBlock: catchBlock); | |
| 217 visitStatement(node.tryBody); | |
| 218 _currentBlock = catchBlock; | |
| 219 // Catch parameters cannot be hoisted to the top of the function, so to | |
| 220 // avoid complications with scoping, we do not attempt to merge them. | |
| 221 node.catchParameters.forEach(ignoreVariable); | |
| 222 visitStatement(node.catchBody); | |
| 223 } | |
| 224 | |
| 225 visitConditional(Conditional node) { | |
| 226 visitExpression(node.condition); | |
| 227 // TODO(asgerf): When assignment expressions are added, this is no longer | |
| 228 // sound; then we need to handle as a branch. | |
| 229 visitExpression(node.thenExpression); | |
| 230 visitExpression(node.elseExpression); | |
| 231 } | |
| 232 | |
| 233 visitLogicalOperator(LogicalOperator node) { | |
| 234 visitExpression(node.left); | |
| 235 // TODO(asgerf): When assignment expressions are added, this is no longer | |
| 236 // sound; then we need to handle as a branch. | |
| 237 visitExpression(node.right); | |
| 238 } | |
| 239 | |
| 240 visitFunctionDeclaration(FunctionDeclaration node) { | |
| 241 // The function variable is final, hence cannot be merged. | |
| 242 ignoreVariable(node.variable); | |
| 243 visitStatement(node.next); | |
| 244 // Do not traverse inner function. | |
| 245 } | |
| 246 | |
| 247 visitFunctionExpression(FunctionExpression node) { | |
| 248 // Do not traverse inner function. | |
| 249 } | |
| 250 | |
| 251 visitFunctionDefinition(FunctionDefinition node) { | |
| 252 // Function parameters are treated as write operations at the entry point, | |
| 253 // so they can potentially be merged with other copies of the parameter. | |
| 254 // Note that function parameters always have distinct source variables, | |
| 255 // so we don't risk accidentally merging two parameters. | |
| 256 node.parameters.forEach(write); | |
| 257 visitStatement(node.body); | |
| 258 } | |
| 259 | |
| 260 visitConstructorDefinition(ConstructorDefinition node) { | |
| 261 node.parameters.forEach(write); | |
| 262 node.initializers.forEach(visitInitializer); | |
| 263 visitStatement(node.body); | |
| 264 } | |
| 265 } | |
| 266 | |
| 267 /// Computes liveness information of the given control-flow graph. | |
| 268 /// | |
| 269 /// The results are stored in [Block.liveIn] and [Block.liveOut]. | |
| 270 void _computeLiveness(List<Block> blocks) { | |
| 271 // We use a LIFO queue as worklist. Blocks are given in AST order, so by | |
| 272 // inserting them in this order, we initially visit them backwards, which | |
| 273 // is a good ordering. | |
| 274 // The choice of LIFO for re-inserted blocks is currently arbitrary, | |
| 275 List<Block> worklist = new List<Block>.from(blocks); | |
| 276 while (!worklist.isEmpty) { | |
| 277 Block block = worklist.removeLast(); | |
| 278 block.inWorklist = false; | |
| 279 | |
| 280 bool changed = false; | |
| 281 | |
| 282 // The liveIn set is computed as: | |
| 283 // | |
| 284 // liveIn = (liveOut - kill) + gen | |
| 285 // | |
| 286 // We do the computation in two steps: | |
| 287 // | |
| 288 // 1. liveIn = gen | |
| 289 // 2. liveIn += (liveOut - kill) | |
| 290 // | |
| 291 // However, since liveIn only grows, and gen never changes, we only have | |
| 292 // to do the first step at the first iteration. Moreover, the gen set is | |
| 293 // not needed anywhere else, so we don't even need to copy it. | |
| 294 if (block.liveIn == null) { | |
| 295 block.liveIn = block.gen; | |
| 296 block.gen = null; | |
| 297 changed = true; | |
| 298 } | |
| 299 | |
| 300 // liveIn += (liveOut - kill) | |
| 301 for (Variable variable in block.liveOut) { | |
| 302 if (!block.kill.contains(variable)) { | |
| 303 if (block.liveIn.add(variable)) { | |
| 304 changed = true; | |
| 305 } | |
| 306 } | |
| 307 } | |
| 308 | |
| 309 // If anything changed, propagate liveness backwards. | |
| 310 if (changed) { | |
| 311 // Propagate live variables to predecessors. | |
| 312 for (Block predecessor in block.predecessors) { | |
| 313 int lengthBeforeChange = predecessor.liveOut.length; | |
| 314 predecessor.liveOut.addAll(block.liveIn); | |
| 315 if (!predecessor.inWorklist && | |
| 316 predecessor.liveOut.length != lengthBeforeChange) { | |
| 317 worklist.add(predecessor); | |
| 318 predecessor.inWorklist = true; | |
| 319 } | |
| 320 } | |
| 321 | |
| 322 // Propagate live variables to catch predecessors. | |
| 323 for (Block pred in block.catchPredecessors) { | |
| 324 bool changed = false; | |
| 325 int lengthBeforeChange = pred.liveOut.length; | |
| 326 pred.liveOut.addAll(block.liveIn); | |
| 327 if (pred.liveOut.length != lengthBeforeChange) { | |
| 328 changed = true; | |
| 329 } | |
| 330 // Assigning to a variable that is live in the catch block, does not | |
| 331 // kill the variable, because we conservatively assume that an exception | |
| 332 // could be thrown immediately before the assignment. | |
| 333 // Therefore remove live variables from all kill sets inside the try. | |
| 334 // Since the kill set is only used to subtract live variables from a | |
| 335 // set, the analysis remains monotone. | |
| 336 lengthBeforeChange = pred.kill.length; | |
| 337 pred.kill.removeAll(block.liveIn); | |
| 338 if (pred.kill.length != lengthBeforeChange) { | |
| 339 changed = true; | |
| 340 } | |
| 341 if (changed && !pred.inWorklist) { | |
| 342 worklist.add(pred); | |
| 343 pred.inWorklist = true; | |
| 344 } | |
| 345 } | |
| 346 } | |
| 347 } | |
| 348 } | |
| 349 | |
| 350 /// For testing purposes, this flag can be passed to merge variables that | |
| 351 /// originated from different source variables. | |
| 352 /// | |
| 353 /// Correctness should not depend on the fact that we only merge variable | |
| 354 /// originating from the same source variable. Setting this flag makes a bug | |
| 355 /// more likely to provoke a test case failure. | |
| 356 const bool NO_PRESERVE_VARS = const bool.fromEnvironment('NO_PRESERVE_VARS'); | |
| 357 | |
| 358 /// Based on liveness information, computes a map of variable substitutions to | |
| 359 /// merge variables. | |
| 360 /// | |
| 361 /// Constructs a register interference graph. This is an undirected graph of | |
| 362 /// variables, with an edge between two variables if they cannot be merged | |
| 363 /// (because they are live simultaneously). | |
| 364 /// | |
| 365 /// We then compute a graph coloring, where the color of a node denotes which | |
| 366 /// variable it will be substituted by. | |
| 367 /// | |
| 368 /// We never merge variables that originated from distinct source variables, | |
| 369 /// so we build a separate register interference graph for each source variable. | |
| 370 Map<Variable, Variable> _computeRegisterAllocation(List<Block> blocks) { | |
| 371 Map<Variable, Set<Variable>> interference = <Variable, Set<Variable>>{}; | |
| 372 | |
| 373 /// Group for the given variable. We attempt to merge variables in the same | |
| 374 /// group. | |
| 375 /// By default, variables are grouped based on their source variable, but | |
| 376 /// this can be disabled for testing purposes. | |
| 377 Local group(Variable variable) { | |
| 378 if (NO_PRESERVE_VARS) { | |
| 379 // Parameters may not occur more than once in a parameter list, | |
| 380 // so except for parameters, we try to merge all variables. | |
| 381 return variable.element is ParameterElement ? variable.element : null; | |
| 382 } | |
| 383 return variable.element; | |
| 384 } | |
| 385 | |
| 386 Set<Variable> empty = new Set<Variable>(); | |
| 387 | |
| 388 // At the assignment to a variable x, add an edge to every variable that is | |
| 389 // live after the assignment (if it came from the same source variable). | |
| 390 for (Block block in blocks) { | |
| 391 // Group the liveOut set by source variable. | |
| 392 Map<Local, Set<Variable>> liveOut = <Local, Set<Variable>>{}; | |
| 393 for (Variable variable in block.liveOut) { | |
| 394 liveOut.putIfAbsent( | |
| 395 group(variable), | |
| 396 () => new Set<Variable>()).add(variable); | |
| 397 interference.putIfAbsent(variable, () => new Set<Variable>()); | |
| 398 } | |
| 399 // Get variables that are live at the catch block. | |
| 400 Set<Variable> liveCatch = block.catchBlock != null | |
| 401 ? block.catchBlock.liveIn | |
| 402 : empty; | |
| 403 // Add edges for each variable being assigned here. | |
| 404 for (VariableAccess access in block.accesses.reversed) { | |
| 405 Variable variable = access.variable; | |
| 406 interference.putIfAbsent(variable, () => new Set<Variable>()); | |
| 407 Set<Variable> live = | |
| 408 liveOut.putIfAbsent(group(variable), () => new Set<Variable>()); | |
| 409 if (access.isRead) { | |
| 410 live.add(variable); | |
| 411 } else { | |
| 412 if (!liveCatch.contains(variable)) { | |
| 413 // Assignment to a variable that is not live in the catch block. | |
| 414 live.remove(variable); | |
| 415 } | |
| 416 for (Variable other in live) { | |
| 417 interference[variable].add(other); | |
| 418 interference[other].add(variable); | |
| 419 } | |
| 420 } | |
| 421 } | |
| 422 } | |
| 423 | |
| 424 // Sort the variables by descending degree. | |
| 425 // The most constrained variables will be assigned a color first. | |
| 426 List<Variable> variables = interference.keys.toList(); | |
| 427 variables.sort((x, y) => interference[y].length - interference[x].length); | |
| 428 | |
| 429 Map<Local, List<Variable>> registers = <Local, List<Variable>>{}; | |
| 430 Map<Variable, Variable> subst = <Variable, Variable>{}; | |
| 431 | |
| 432 for (Variable v1 in variables) { | |
| 433 List<Variable> register = registers[group(v1)]; | |
| 434 | |
| 435 // Optimization: For the first variable in a group, allocate a new color | |
| 436 // without iterating over its interference edges. | |
| 437 if (register == null) { | |
| 438 registers[group(v1)] = <Variable>[v1]; | |
| 439 subst[v1] = v1; | |
| 440 continue; | |
| 441 } | |
| 442 | |
| 443 // Optimization: If there are no inteference edges for this variable, | |
| 444 // assign it the first color without copying the register list. | |
| 445 Set<Variable> interferenceSet = interference[v1]; | |
| 446 if (interferenceSet.isEmpty) { | |
| 447 subst[v1] = register[0]; | |
| 448 continue; | |
| 449 } | |
| 450 | |
| 451 // Find an unused color. | |
| 452 Set<Variable> potential = new Set<Variable>.from(register); | |
| 453 for (Variable v2 in interferenceSet) { | |
| 454 Variable v2subst = subst[v2]; | |
| 455 if (v2subst != null) { | |
| 456 potential.remove(v2subst); | |
| 457 if (potential.isEmpty) break; | |
| 458 } | |
| 459 } | |
| 460 | |
| 461 if (potential.isEmpty) { | |
| 462 // If no free color was found, add this variable as a new color. | |
| 463 register.add(v1); | |
| 464 subst[v1] = v1; | |
| 465 } else { | |
| 466 subst[v1] = potential.first; | |
| 467 } | |
| 468 } | |
| 469 | |
| 470 return subst; | |
| 471 } | |
| 472 | |
| 473 /// Performs variable substitution and removes redundant assignments. | |
| 474 class SubstituteVariables extends RecursiveVisitor { | |
| 475 | |
| 476 Map<Variable, Variable> mapping; | |
| 477 | |
| 478 SubstituteVariables(this.mapping); | |
| 479 | |
| 480 Variable replaceRead(Variable variable) { | |
| 481 Variable w = mapping[variable]; | |
| 482 if (w == null) return variable; // Skip ignored variables. | |
| 483 w.readCount++; | |
| 484 variable.readCount--; | |
| 485 return w; | |
| 486 } | |
| 487 | |
| 488 Variable replaceWrite(Variable variable) { | |
| 489 Variable w = mapping[variable]; | |
| 490 if (w == null) return variable; // Skip ignored variables. | |
| 491 w.writeCount++; | |
| 492 variable.writeCount--; | |
| 493 return w; | |
| 494 } | |
| 495 | |
| 496 void replaceParameters(List<Variable> parameters) { | |
| 497 for (int i = 0; i < parameters.length; ++i) { | |
| 498 parameters[i] = replaceWrite(parameters[i]); | |
| 499 } | |
| 500 } | |
| 501 | |
| 502 visitVariableUse(VariableUse node) { | |
| 503 node.variable = replaceRead(node.variable); | |
| 504 } | |
| 505 | |
| 506 visitFunctionDefinition(FunctionDefinition node) { | |
| 507 replaceParameters(node.parameters); | |
| 508 node.body = visitStatement(node.body); | |
| 509 } | |
| 510 | |
| 511 visitConstructorDefinition(ConstructorDefinition node) { | |
| 512 replaceParameters(node.parameters); | |
| 513 node.initializers.forEach(visitInitializer); | |
| 514 node.body = visitStatement(node.body); | |
| 515 } | |
| 516 | |
| 517 visitFieldInitializer(FieldInitializer node) { | |
| 518 node.body = visitStatement(node.body); | |
| 519 } | |
| 520 | |
| 521 visitSuperInitializer(SuperInitializer node) { | |
| 522 for (int i = 0; i<node.arguments.length; ++i) { | |
| 523 node.arguments[i] = visitStatement(node.arguments[i]); | |
| 524 } | |
| 525 } | |
| 526 | |
| 527 // Statement visitors should return the transformed statement so we | |
| 528 // can remove redundant assignments. | |
| 529 Statement visitStatement(Statement node) => super.visitStatement(node); | |
| 530 | |
| 531 Statement visitAssign(Assign node) { | |
| 532 node.variable = replaceWrite(node.variable); | |
| 533 | |
| 534 visitExpression(node.value); | |
| 535 node.next = visitStatement(node.next); | |
| 536 | |
| 537 // Remove assignments of form "x := x" | |
| 538 if (node.value is VariableUse) { | |
| 539 VariableUse value = node.value; | |
| 540 if (value.variable == node.variable) { | |
| 541 value.variable.readCount--; | |
| 542 node.variable.writeCount--; | |
| 543 return node.next; | |
| 544 } | |
| 545 } | |
| 546 | |
| 547 return node; | |
| 548 } | |
| 549 | |
| 550 Statement visitLabeledStatement(LabeledStatement node) { | |
| 551 node.body = visitStatement(node.body); | |
| 552 node.next = visitStatement(node.next); | |
| 553 return node; | |
| 554 } | |
| 555 | |
| 556 Statement visitReturn(Return node) { | |
| 557 visitExpression(node.value); | |
| 558 return node; | |
| 559 } | |
| 560 | |
| 561 Statement visitBreak(Break node) { | |
| 562 return node; | |
| 563 } | |
| 564 | |
| 565 Statement visitContinue(Continue node) { | |
| 566 return node; | |
| 567 } | |
| 568 | |
| 569 Statement visitIf(If node) { | |
| 570 visitExpression(node.condition); | |
| 571 node.thenStatement = visitStatement(node.thenStatement); | |
| 572 node.elseStatement = visitStatement(node.elseStatement); | |
| 573 return node; | |
| 574 } | |
| 575 | |
| 576 Statement visitWhileTrue(WhileTrue node) { | |
| 577 node.body = visitStatement(node.body); | |
| 578 return node; | |
| 579 } | |
| 580 | |
| 581 Statement visitWhileCondition(WhileCondition node) { | |
| 582 visitExpression(node.condition); | |
| 583 node.body = visitStatement(node.body); | |
| 584 node.next = visitStatement(node.next); | |
| 585 return node; | |
| 586 } | |
| 587 | |
| 588 Statement visitFunctionDeclaration(FunctionDeclaration node) { | |
| 589 node.next = visitStatement(node.next); | |
| 590 return node; | |
| 591 } | |
| 592 | |
| 593 Statement visitExpressionStatement(ExpressionStatement node) { | |
| 594 visitExpression(node.expression); | |
| 595 node.next = visitStatement(node.next); | |
| 596 return node; | |
| 597 } | |
| 598 | |
| 599 Statement visitTry(Try node) { | |
| 600 node.tryBody = visitStatement(node.tryBody); | |
| 601 node.catchBody = visitStatement(node.catchBody); | |
| 602 return node; | |
| 603 } | |
| 604 | |
| 605 Statement visitSetField(SetField node) { | |
| 606 visitExpression(node.object); | |
| 607 visitExpression(node.value); | |
| 608 node.next = visitStatement(node.next); | |
| 609 return node; | |
| 610 } | |
| 611 } | |
| OLD | NEW |