Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 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 | 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 library tree_ir.optimization.variable_merger; | 5 library tree_ir.optimization.variable_merger; |
| 6 | 6 |
| 7 import 'optimization.dart' show Pass; | 7 import 'optimization.dart' show Pass; |
| 8 import '../tree_ir_nodes.dart'; | 8 import '../tree_ir_nodes.dart'; |
| 9 | 9 |
| 10 /// Merges variables based on liveness and source variable information. | 10 /// Merges variables based on liveness and source variable information. |
| 11 /// | 11 /// |
| 12 /// This phase cleans up artifacts introduced by the translation through CPS, | 12 /// This phase cleans up artifacts introduced by the translation through CPS, |
| 13 /// where each source variable is translated into several copies. The copies | 13 /// where each source variable is translated into several copies. The copies |
| 14 /// are merged again when they are not live simultaneously. | 14 /// are merged again when they are not live simultaneously. |
| 15 class VariableMerger implements Pass { | 15 class VariableMerger implements Pass { |
| 16 String get passName => 'Variable merger'; | 16 String get passName => 'Variable merger'; |
| 17 | 17 |
| 18 final bool minifying; | |
| 19 | |
| 20 VariableMerger({this.minifying}); | |
|
Kevin Millikin (Google)
2016/01/26 08:27:26
Is this an optional parameter, or merely named? I
asgerf
2016/01/26 09:07:38
Set default to false.
| |
| 21 | |
| 18 void rewrite(FunctionDefinition node) { | 22 void rewrite(FunctionDefinition node) { |
| 19 BlockGraphBuilder builder = new BlockGraphBuilder(); | 23 BlockGraphBuilder builder = new BlockGraphBuilder()..build(node); |
| 20 builder.build(node); | |
| 21 _computeLiveness(builder.blocks); | 24 _computeLiveness(builder.blocks); |
| 22 Map<Variable, Variable> subst = | 25 PriorityPairs priority = new PriorityPairs()..build(node); |
| 23 _computeRegisterAllocation(builder.blocks, node.parameters); | 26 Map<Variable, Variable> subst = _computeRegisterAllocation( |
| 27 builder.blocks, node.parameters, priority, minifying: minifying); | |
| 24 new SubstituteVariables(subst).apply(node); | 28 new SubstituteVariables(subst).apply(node); |
| 25 } | 29 } |
| 26 } | 30 } |
| 27 | 31 |
| 28 /// A read or write access to a variable. | 32 /// A read or write access to a variable. |
| 29 class VariableAccess { | 33 class VariableAccess { |
| 30 Variable variable; | 34 Variable variable; |
| 31 bool isRead; | 35 bool isRead; |
| 32 bool get isWrite => !isRead; | 36 bool get isWrite => !isRead; |
| 33 | 37 |
| (...skipping 200 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 234 | 238 |
| 235 visitLogicalOperator(LogicalOperator node) { | 239 visitLogicalOperator(LogicalOperator node) { |
| 236 visitExpression(node.left); | 240 visitExpression(node.left); |
| 237 Block afterLeft = _currentBlock; | 241 Block afterLeft = _currentBlock; |
| 238 branchFrom(afterLeft); | 242 branchFrom(afterLeft); |
| 239 visitExpression(node.right); | 243 visitExpression(node.right); |
| 240 joinFrom(_currentBlock, afterLeft); | 244 joinFrom(_currentBlock, afterLeft); |
| 241 } | 245 } |
| 242 } | 246 } |
| 243 | 247 |
| 248 /// Collects prioritized variable pairs -- pairs that lead to significant code | |
| 249 /// reduction if merged into one variable. | |
| 250 /// | |
| 251 /// These arise from moving assigments `v1 = v2`, and compoundable assignments | |
| 252 /// `v1 = v2 [+] E` where [+] is a compoundable operator. | |
| 253 // | |
| 254 // TODO(asgerf): We could have a more fine-grained priority level. All pairs | |
| 255 // are treated as equally important, but some pairs can eliminate more than | |
| 256 // one assignment. | |
| 257 // Also, some assignments are more important to remove than others, as they | |
| 258 // can block a later optimization, such rewriting a loop, or removing the | |
| 259 // 'else' part of an 'if'. | |
| 260 // | |
| 261 class PriorityPairs extends RecursiveVisitor { | |
| 262 final Map<Variable, List<Variable>> _priority = <Variable, List<Variable>>{}; | |
| 263 | |
| 264 void build(FunctionDefinition node) { | |
| 265 visitStatement(node.body); | |
| 266 } | |
| 267 | |
| 268 void _prioritize(Variable x, Variable y) { | |
| 269 _priority.putIfAbsent(x, () => new List<Variable>()).add(y); | |
| 270 _priority.putIfAbsent(y, () => new List<Variable>()).add(x); | |
| 271 } | |
| 272 | |
| 273 visitAssign(Assign node) { | |
| 274 super.visitAssign(node); | |
| 275 Expression value = node.value; | |
| 276 if (value is VariableUse) { | |
| 277 _prioritize(node.variable, value.variable); | |
| 278 } else if (value is ApplyBuiltinOperator && | |
| 279 isCompoundableOperator(value.operator) && | |
| 280 value.arguments[0] is VariableUse) { | |
| 281 VariableUse use = value.arguments[0]; | |
| 282 _prioritize(node.variable, use.variable); | |
| 283 } | |
| 284 } | |
| 285 | |
| 286 /// Returns the other half of every priority pair containing [variable]. | |
| 287 List<Variable> getPriorityPairsWith(Variable variable) { | |
| 288 return _priority[variable] ?? const <Variable>[]; | |
| 289 } | |
| 290 | |
| 291 bool hasPriorityPairs(Variable variable) { | |
| 292 return _priority.containsKey(variable); | |
| 293 } | |
| 294 } | |
| 295 | |
| 244 /// Computes liveness information of the given control-flow graph. | 296 /// Computes liveness information of the given control-flow graph. |
| 245 /// | 297 /// |
| 246 /// The results are stored in [Block.liveIn] and [Block.liveOut]. | 298 /// The results are stored in [Block.liveIn] and [Block.liveOut]. |
| 247 void _computeLiveness(List<Block> blocks) { | 299 void _computeLiveness(List<Block> blocks) { |
| 248 // We use a LIFO queue as worklist. Blocks are given in AST order, so by | 300 // We use a LIFO queue as worklist. Blocks are given in AST order, so by |
| 249 // inserting them in this order, we initially visit them backwards, which | 301 // inserting them in this order, we initially visit them backwards, which |
| 250 // is a good ordering. | 302 // is a good ordering. |
| 251 // The choice of LIFO for re-inserted blocks is currently arbitrary, | 303 // The choice of LIFO for re-inserted blocks is currently arbitrary, |
| 252 List<Block> worklist = new List<Block>.from(blocks); | 304 List<Block> worklist = new List<Block>.from(blocks); |
| 253 while (!worklist.isEmpty) { | 305 while (!worklist.isEmpty) { |
| (...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 317 } | 369 } |
| 318 if (changed && !pred.inWorklist) { | 370 if (changed && !pred.inWorklist) { |
| 319 worklist.add(pred); | 371 worklist.add(pred); |
| 320 pred.inWorklist = true; | 372 pred.inWorklist = true; |
| 321 } | 373 } |
| 322 } | 374 } |
| 323 } | 375 } |
| 324 } | 376 } |
| 325 } | 377 } |
| 326 | 378 |
| 327 /// For testing purposes, this flag can be passed to merge variables that | |
| 328 /// originated from different source variables. | |
| 329 /// | |
| 330 /// Correctness should not depend on the fact that we only merge variables | |
| 331 /// originating from the same source variable. Setting this flag makes a bug | |
| 332 /// more likely to provoke a test case failure. | |
| 333 const bool NO_PRESERVE_VARS = const bool.fromEnvironment('NO_PRESERVE_VARS'); | |
| 334 | |
| 335 /// Based on liveness information, computes a map of variable substitutions to | 379 /// Based on liveness information, computes a map of variable substitutions to |
| 336 /// merge variables. | 380 /// merge variables. |
| 337 /// | 381 /// |
| 338 /// Constructs a register interference graph. This is an undirected graph of | 382 /// Constructs a register interference graph. This is an undirected graph of |
| 339 /// variables, with an edge between two variables if they cannot be merged | 383 /// variables, with an edge between two variables if they cannot be merged |
| 340 /// (because they are live simultaneously). | 384 /// (because they are live simultaneously). |
| 341 /// | 385 /// |
| 342 /// We then compute a graph coloring, where the color of a node denotes which | 386 /// We then compute a graph coloring, where the color of a node denotes which |
| 343 /// variable it will be substituted by. | 387 /// variable it will be substituted by. |
| 344 /// | |
| 345 /// We never merge variables that originated from distinct source variables, | |
| 346 /// so we build a separate register interference graph for each source variable. | |
| 347 Map<Variable, Variable> _computeRegisterAllocation(List<Block> blocks, | 388 Map<Variable, Variable> _computeRegisterAllocation(List<Block> blocks, |
| 348 List<Variable> parameters) { | 389 List<Variable> parameters, |
| 390 PriorityPairs priority, | |
| 391 {bool minifying}) { | |
| 349 Map<Variable, Set<Variable>> interference = <Variable, Set<Variable>>{}; | 392 Map<Variable, Set<Variable>> interference = <Variable, Set<Variable>>{}; |
| 350 | 393 |
| 351 /// Group for the given variable. We attempt to merge variables in the same | 394 bool allowUnmotivatedMerge(Variable x, Variable y) { |
| 352 /// group. | 395 if (minifying) return true; |
| 353 /// By default, variables are grouped based on their source variable name, | 396 // Do not allow merging temporaries with named variables if they are |
| 354 /// but this can be disabled for testing purposes. | 397 // not connected by a phi. That would leads to confusing mergings like: |
| 355 String group(Variable variable) { | 398 // var v0 = receiver.length; |
| 356 if (NO_PRESERVE_VARS) return ''; | 399 // ==> |
| 357 // Group variables based on the source variable's name, not its element, | 400 // receiver = receiver.length; |
| 358 // so if multiple locals are declared with the same name, they will | 401 return x.element?.name == y.element?.name; |
| 359 // map to the same (hoisted) variable in the output. | 402 } |
| 360 return variable.element == null ? '' : variable.element.name; | 403 |
| 404 bool allowPhiMerge(Variable x, Variable y) { | |
| 405 if (minifying) return true; | |
| 406 // Temporaries may be merged with a named variable if this eliminates a phi. | |
| 407 // The presence of the phi implies that the two variables can contain the | |
| 408 // same value, so it is not that confusing that they get the same name. | |
| 409 return x.element == null || | |
| 410 y.element == null || | |
| 411 x.element.name == y.element.name; | |
| 361 } | 412 } |
| 362 | 413 |
| 363 Set<Variable> empty = new Set<Variable>(); | 414 Set<Variable> empty = new Set<Variable>(); |
| 364 | 415 |
| 365 // At the assignment to a variable x, add an edge to every variable that is | 416 // At the assignment to a variable x, add an edge to every variable that is |
| 366 // live after the assignment (if it came from the same source variable). | 417 // live after the assignment (if it came from the same source variable). |
| 367 for (Block block in blocks) { | 418 for (Block block in blocks) { |
| 368 // Group the liveOut set by source variable. | 419 // Track the live set while traversing the block. |
| 369 Map<String, Set<Variable>> liveOut = <String, Set<Variable>>{}; | 420 Set<Variable> live = new Set<Variable>(); |
| 370 for (Variable variable in block.liveOut) { | 421 for (Variable variable in block.liveOut) { |
| 371 liveOut.putIfAbsent( | 422 live.add(variable); |
| 372 group(variable), | |
| 373 () => new Set<Variable>()).add(variable); | |
| 374 interference.putIfAbsent(variable, () => new Set<Variable>()); | 423 interference.putIfAbsent(variable, () => new Set<Variable>()); |
| 375 } | 424 } |
| 376 // Get variables that are live at the catch block. | 425 // Get variables that are live at the catch block. |
| 377 Set<Variable> liveCatch = block.catchBlock != null | 426 Set<Variable> liveCatch = block.catchBlock != null |
| 378 ? block.catchBlock.liveIn | 427 ? block.catchBlock.liveIn |
| 379 : empty; | 428 : empty; |
| 380 // Add edges for each variable being assigned here. | 429 // Add edges for each variable being assigned here. |
| 381 for (VariableAccess access in block.accesses.reversed) { | 430 for (VariableAccess access in block.accesses.reversed) { |
| 382 Variable variable = access.variable; | 431 Variable variable = access.variable; |
| 383 interference.putIfAbsent(variable, () => new Set<Variable>()); | 432 interference.putIfAbsent(variable, () => new Set<Variable>()); |
| 384 Set<Variable> live = | |
| 385 liveOut.putIfAbsent(group(variable), () => new Set<Variable>()); | |
| 386 if (access.isRead) { | 433 if (access.isRead) { |
| 387 live.add(variable); | 434 live.add(variable); |
| 388 } else { | 435 } else { |
| 389 if (!liveCatch.contains(variable)) { | 436 if (!liveCatch.contains(variable)) { |
| 390 // Assignment to a variable that is not live in the catch block. | 437 // Assignment to a variable that is not live in the catch block. |
| 391 live.remove(variable); | 438 live.remove(variable); |
| 392 } | 439 } |
| 393 for (Variable other in live) { | 440 for (Variable other in live) { |
| 394 interference[variable].add(other); | 441 interference[variable].add(other); |
| 395 interference[other].add(variable); | 442 interference[other].add(variable); |
| 396 } | 443 } |
| 397 } | 444 } |
| 398 } | 445 } |
| 399 } | 446 } |
| 400 | 447 |
| 401 // Sort the variables by descending degree. | 448 // Sort the variables by descending degree. |
| 402 // The most constrained variables will be assigned a color first. | 449 // The most constrained variables will be assigned a color first. |
| 403 List<Variable> variables = interference.keys.toList(); | 450 List<Variable> variables = interference.keys.toList(); |
| 404 variables.sort((x, y) => interference[y].length - interference[x].length); | 451 variables.sort((x, y) => interference[y].length - interference[x].length); |
| 405 | 452 |
| 406 Map<String, List<Variable>> registers = <String, List<Variable>>{}; | 453 List<Variable> registers = <Variable>[]; |
| 407 Map<Variable, Variable> subst = <Variable, Variable>{}; | 454 Map<Variable, Variable> subst = <Variable, Variable>{}; |
| 408 | 455 |
| 409 // Parameters are special in that they must have a ParameterElement and | 456 /// Called when [variable] has been assigned [target] as its register/color. |
| 410 // cannot be merged with each other. Ensure that they are not substituted. | 457 /// Will immediately try to satisfy its priority pairs by assigning the same |
| 411 // Other variables can still be substituted by a parameter. | 458 /// color the other half of each pair. |
| 459 void searchPriorityPairs(Variable variable, Variable target) { | |
| 460 if (!priority.hasPriorityPairs(variable)) { | |
| 461 return; // Most variables (around 90%) do not have priority pairs. | |
| 462 } | |
| 463 List<Variable> worklist = <Variable>[variable]; | |
| 464 while (worklist.isNotEmpty) { | |
| 465 Variable v1 = worklist.removeLast(); | |
| 466 for (Variable v2 in priority.getPriorityPairsWith(v1)) { | |
| 467 // If v2 already has a color, we cannot change it. | |
| 468 if (subst.containsKey(v2)) continue; | |
| 469 | |
| 470 // Do not merge differently named variables. | |
| 471 if (!allowPhiMerge(v1, v2)) continue; | |
| 472 | |
| 473 // Ensure the graph coloring remains valid. If a neighbour of v2 already | |
| 474 // has the desired color, we cannot assign the same color to v2. | |
| 475 if (interference[v2].any((v3) => subst[v3] == target)) continue; | |
| 476 | |
| 477 subst[v2] = target; | |
| 478 target.element ??= v2.element; // Preserve the name. | |
| 479 worklist.add(v2); | |
| 480 } | |
| 481 } | |
| 482 } | |
| 483 | |
| 484 void assignRegister(Variable variable, Variable registerRepresentative) { | |
| 485 subst[variable] = registerRepresentative; | |
| 486 // Ensure this register is never assigned to a variable with another name. | |
| 487 // This also ensures that named variables keep their name when merged | |
| 488 // with a temporary. | |
| 489 registerRepresentative.element ??= variable.element; | |
| 490 searchPriorityPairs(variable, registerRepresentative); | |
| 491 } | |
| 492 | |
| 493 void assignNewRegister(Variable variable) { | |
| 494 registers.add(variable); | |
| 495 subst[variable] = variable; | |
| 496 searchPriorityPairs(variable, variable); | |
| 497 } | |
| 498 | |
| 499 // Parameters cannot be merged with each other. Ensure that they are not | |
| 500 // substituted. Other variables can still be substituted by a parameter. | |
| 412 for (Variable parameter in parameters) { | 501 for (Variable parameter in parameters) { |
| 413 if (parameter.isCaptured) continue; | 502 if (parameter.isCaptured) continue; |
| 503 registers.add(parameter); | |
| 414 subst[parameter] = parameter; | 504 subst[parameter] = parameter; |
| 415 registers[group(parameter)] = <Variable>[parameter]; | |
| 416 } | 505 } |
| 417 | 506 |
| 507 // Try to merge parameters with locals to eliminate phis. | |
| 508 for (Variable parameter in parameters) { | |
| 509 searchPriorityPairs(parameter, parameter); | |
| 510 } | |
| 511 | |
| 512 v1loop: | |
| 418 for (Variable v1 in variables) { | 513 for (Variable v1 in variables) { |
| 419 // Parameters have already been assigned a substitute; skip those. | 514 // Ignore if the variable has already been assigned a register. |
| 420 if (subst.containsKey(v1)) continue; | 515 if (subst.containsKey(v1)) continue; |
| 421 | 516 |
| 422 List<Variable> register = registers[group(v1)]; | |
| 423 | |
| 424 // Optimization: For the first variable in a group, allocate a new color | |
| 425 // without iterating over its interference edges. | |
| 426 if (register == null) { | |
| 427 registers[group(v1)] = <Variable>[v1]; | |
| 428 subst[v1] = v1; | |
| 429 continue; | |
| 430 } | |
| 431 | |
| 432 // Optimization: If there are no interference edges for this variable, | 517 // Optimization: If there are no interference edges for this variable, |
| 433 // assign it the first color without copying the register list. | 518 // find a color for it without copying the register list. |
| 434 Set<Variable> interferenceSet = interference[v1]; | 519 Set<Variable> interferenceSet = interference[v1]; |
| 435 if (interferenceSet.isEmpty) { | 520 if (interferenceSet.isEmpty) { |
| 436 subst[v1] = register[0]; | 521 // Use the first register where naming constraints allow the merge. |
| 522 for (Variable v2 in registers) { | |
| 523 if (allowUnmotivatedMerge(v1, v2)) { | |
| 524 assignRegister(v1, v2); | |
| 525 continue v1loop; | |
| 526 } | |
| 527 } | |
| 528 // No register allows merging with this one, create a new register. | |
| 529 assignNewRegister(v1); | |
| 437 continue; | 530 continue; |
| 438 } | 531 } |
| 439 | 532 |
| 440 // Find an unused color. | 533 // Find an unused color. |
| 441 Set<Variable> potential = new Set<Variable>.from(register); | 534 Set<Variable> potential = new Set<Variable>.from( |
| 535 registers.where((v2) => allowUnmotivatedMerge(v1, v2))); | |
| 442 for (Variable v2 in interferenceSet) { | 536 for (Variable v2 in interferenceSet) { |
| 443 Variable v2subst = subst[v2]; | 537 Variable v2subst = subst[v2]; |
| 444 if (v2subst != null) { | 538 if (v2subst != null) { |
| 445 potential.remove(v2subst); | 539 potential.remove(v2subst); |
| 446 if (potential.isEmpty) break; | 540 if (potential.isEmpty) break; |
| 447 } | 541 } |
| 448 } | 542 } |
| 449 | 543 |
| 450 if (potential.isEmpty) { | 544 if (potential.isEmpty) { |
| 451 // If no free color was found, add this variable as a new color. | 545 // If no free color was found, add this variable as a new color. |
| 452 register.add(v1); | 546 assignNewRegister(v1); |
| 453 subst[v1] = v1; | |
| 454 } else { | 547 } else { |
| 455 subst[v1] = potential.first; | 548 assignRegister(v1, potential.first); |
| 456 } | 549 } |
| 457 } | 550 } |
| 458 | 551 |
| 459 return subst; | 552 return subst; |
| 460 } | 553 } |
| 461 | 554 |
| 462 /// Performs variable substitution and removes redundant assignments. | 555 /// Performs variable substitution and removes redundant assignments. |
| 463 class SubstituteVariables extends RecursiveTransformer { | 556 class SubstituteVariables extends RecursiveTransformer { |
| 464 | 557 |
| 465 Map<Variable, Variable> mapping; | 558 Map<Variable, Variable> mapping; |
| (...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 514 node.expression = visitExpression(node.expression); | 607 node.expression = visitExpression(node.expression); |
| 515 node.next = visitStatement(node.next); | 608 node.next = visitStatement(node.next); |
| 516 if (node.expression is VariableUse) { | 609 if (node.expression is VariableUse) { |
| 517 VariableUse use = node.expression; | 610 VariableUse use = node.expression; |
| 518 --use.variable.readCount; | 611 --use.variable.readCount; |
| 519 return node.next; | 612 return node.next; |
| 520 } | 613 } |
| 521 return node; | 614 return node; |
| 522 } | 615 } |
| 523 } | 616 } |
| OLD | NEW |