|
26 | 26 | import com.google.common.annotations.VisibleForTesting; |
27 | 27 | import com.google.common.base.Strings; |
28 | 28 | import com.google.common.base.Verify; |
| 29 | +import com.google.common.collect.HashMultiset; |
29 | 30 | import com.google.common.collect.ImmutableList; |
30 | | -import com.google.common.collect.ImmutableListMultimap; |
31 | 31 | import com.google.common.collect.ImmutableMap; |
32 | 32 | import com.google.common.collect.ImmutableSet; |
33 | | -import com.google.common.collect.Multimaps; |
| 33 | +import com.google.common.collect.Multiset; |
34 | 34 | import com.google.common.collect.Sets; |
35 | 35 | import com.google.common.collect.Streams; |
36 | 36 | import com.google.errorprone.annotations.CanIgnoreReturnValue; |
|
85 | 85 | import java.util.List; |
86 | 86 | import java.util.Map; |
87 | 87 | import java.util.Set; |
88 | | -import java.util.function.Predicate; |
89 | 88 | import java.util.stream.Stream; |
90 | 89 |
|
91 | 90 | /** |
@@ -412,55 +411,118 @@ private ImmutableList<CelMutableExpr> getCseCandidates( |
412 | 411 | Set<CelMutableExpr> ineligibleExprs = getIneligibleExprsFromComprehensionBranches(navAst); |
413 | 412 | ImmutableList<CelNavigableMutableExpr> allNodes = |
414 | 413 | navAst.getRoot().allNodes(TraversalOrder.PRE_ORDER).collect(toImmutableList()); |
415 | | - ImmutableListMultimap<Integer, CelNavigableMutableExpr> nodesByHeight = |
416 | | - Multimaps.index(allNodes, CelNavigableMutableExpr::height); |
417 | | - Set<Integer> seenVns = new HashSet<>(); |
| 414 | + // Loop-invariant subexpressions are extracted even if they occur once: block slots are lazily |
| 415 | + // memoized, so they then run at most once and only if reached, instead of once per iteration. |
| 416 | + @SuppressWarnings("ValueClassIdentity") // Integer values are only unboxed, never compared. |
| 417 | + IdentityHashMap<CelMutableExpr, Integer> loopDepths = new IdentityHashMap<>(); |
418 | 418 | for (CelNavigableMutableExpr node : allNodes) { |
419 | | - ImmutableList<CelNavigableMutableExpr> sameHeightNodes = nodesByHeight.get(node.height()); |
420 | | - if (sameHeightNodes.size() < 2 || !canEliminate(node, ineligibleExprs)) { |
421 | | - continue; |
| 419 | + if (node.getKind().equals(Kind.COMPREHENSION)) { |
| 420 | + CelNavigableMutableExpr.fromExpr(node.expr().comprehension().loopStep()) |
| 421 | + .allNodes() |
| 422 | + .forEach(step -> loopDepths.merge(step.expr(), 1, Integer::sum)); |
422 | 423 | } |
423 | | - int vn = valueNumberer.vnOf(node.expr()); |
424 | | - if (!seenVns.add(vn)) { |
425 | | - return sameHeightNodes.stream() |
426 | | - .filter(candidate -> canEliminate(candidate, ineligibleExprs)) |
| 424 | + } |
| 425 | + Set<CelMutableExpr> eligibleExprs = getEligibleExprs(allNodes, ineligibleExprs); |
| 426 | + Multiset<Integer> vnCounts = HashMultiset.create(); |
| 427 | + eligibleExprs.forEach(expr -> vnCounts.add(valueNumberer.vnOf(expr))); |
| 428 | + Set<Integer> seenVns = new HashSet<>(); |
| 429 | + for (CelNavigableMutableExpr node : allNodes) { |
| 430 | + CelMutableExpr expr = node.expr(); |
| 431 | + if (eligibleExprs.contains(expr) |
| 432 | + && (loopDepths.containsKey(expr) || !seenVns.add(valueNumberer.vnOf(expr)))) { |
| 433 | + int vn = |
| 434 | + valueNumberer.vnOf( |
| 435 | + extractionTarget(node, loopDepths, vnCounts, valueNumberer, eligibleExprs).expr()); |
| 436 | + return allNodes.stream() |
427 | 437 | .map(CelNavigableMutableExpr::expr) |
428 | | - .filter(expr -> valueNumberer.vnOf(expr) == vn) |
| 438 | + .filter(e -> eligibleExprs.contains(e) && valueNumberer.vnOf(e) == vn) |
429 | 439 | .collect(toImmutableList()); |
430 | 440 | } |
431 | 441 | } |
432 | 442 |
|
433 | 443 | return ImmutableList.of(); |
434 | 444 | } |
435 | 445 |
|
436 | | - private boolean canEliminate( |
437 | | - CelNavigableMutableExpr navigableExpr, Set<CelMutableExpr> ineligibleExprs) { |
438 | | - return !navigableExpr.getKind().equals(Kind.CONSTANT) |
439 | | - && !navigableExpr.getKind().equals(Kind.IDENT) |
440 | | - // Exclude empty lists (cel.bind sets this for iterRange). |
441 | | - && !(navigableExpr.getKind().equals(Kind.LIST) |
442 | | - && navigableExpr.expr().list().elements().isEmpty()) |
443 | | - && !ineligibleExprs.contains(navigableExpr.expr()) |
444 | | - && containsEliminableFunctionOnly(navigableExpr) |
445 | | - && containsValidComprehensionIdents(navigableExpr); |
| 446 | + // Slots are never revisited, so before extracting `node` this first extracts any eligible |
| 447 | + // descendant that is duplicated within or also occurs outside of it, or (for a loop-step node) |
| 448 | + // that is invariant to a loop nested inside it. |
| 449 | + private static CelNavigableMutableExpr extractionTarget( |
| 450 | + CelNavigableMutableExpr node, |
| 451 | + IdentityHashMap<CelMutableExpr, Integer> loopDepths, |
| 452 | + Multiset<Integer> vnCounts, |
| 453 | + ValueNumberer valueNumberer, |
| 454 | + Set<CelMutableExpr> eligibleExprs) { |
| 455 | + // A descendant occurring more often than `node` is duplicated within it or shared outside it. |
| 456 | + int copies = vnCounts.count(valueNumberer.vnOf(node.expr())); |
| 457 | + Integer depth = loopDepths.get(node.expr()); |
| 458 | + return node.descendants(TraversalOrder.PRE_ORDER) |
| 459 | + .filter( |
| 460 | + d -> |
| 461 | + eligibleExprs.contains(d.expr()) |
| 462 | + && (vnCounts.count(valueNumberer.vnOf(d.expr())) > copies |
| 463 | + || (depth != null && loopDepths.get(d.expr()) > depth))) |
| 464 | + .findFirst() |
| 465 | + .map(d -> extractionTarget(d, loopDepths, vnCounts, valueNumberer, eligibleExprs)) |
| 466 | + .orElse(node); |
446 | 467 | } |
447 | 468 |
|
448 | | - private static boolean containsValidComprehensionIdents(CelNavigableMutableExpr navExpr) { |
449 | | - // For nested comprehensions of form [1].exists(x, [2].exists(y, x == y)), the inner |
450 | | - // comprehension [2].exists(y, x == y) should not be extracted out into a block index, as it |
451 | | - // causes issues with scoping. Non-comprehension subexpressions cannot reference any mangled |
452 | | - // comprehension variables. |
453 | | - Predicate<String> isInvalidIdent = |
454 | | - navExpr.getKind().equals(Kind.COMPREHENSION) |
455 | | - ? CelNavigableExprUtil.getEnclosingComprehensionVariables(navExpr)::contains |
456 | | - : name -> |
457 | | - name.startsWith(MANGLED_COMPREHENSION_ITER_VAR_PREFIX) |
458 | | - || name.startsWith(MANGLED_COMPREHENSION_ACCU_VAR_PREFIX); |
459 | | - return navExpr |
460 | | - .descendants() |
461 | | - .filter(node -> node.getKind().equals(Kind.IDENT)) |
462 | | - .map(node -> node.expr().ident().name()) |
463 | | - .noneMatch(isInvalidIdent); |
| 469 | + // The planner accumulates `@ac + [elem]` steps (map, filter) in place, so `[elem]` is not |
| 470 | + // extracted. |
| 471 | + private static boolean isAccumulation(CelMutableExpr expr) { |
| 472 | + if (!expr.getKind().equals(Kind.CALL) |
| 473 | + || !expr.call().function().equals(Operator.ADD.getFunction())) { |
| 474 | + return false; |
| 475 | + } |
| 476 | + CelMutableExpr accu = expr.call().args().get(0); |
| 477 | + return accu.getKind().equals(Kind.IDENT) |
| 478 | + && accu.ident().name().startsWith(MANGLED_COMPREHENSION_ACCU_VAR_PREFIX) |
| 479 | + && expr.call().args().get(1).getKind().equals(Kind.LIST); |
| 480 | + } |
| 481 | + |
| 482 | + // Computed bottom-up, as checking every node's subtree separately is quadratic. Only |
| 483 | + // comprehensions still walk their own subtree. |
| 484 | + private Set<CelMutableExpr> getEligibleExprs( |
| 485 | + ImmutableList<CelNavigableMutableExpr> preOrderNodes, Set<CelMutableExpr> ineligibleExprs) { |
| 486 | + // Subtrees containing an ineliminable call, or any mangled comprehension variable. |
| 487 | + Set<CelMutableExpr> impureExprs = Sets.newIdentityHashSet(); |
| 488 | + Set<CelMutableExpr> mangledExprs = Sets.newIdentityHashSet(); |
| 489 | + Set<CelMutableExpr> eligibleExprs = Sets.newIdentityHashSet(); |
| 490 | + // Reverse pre-order visits children before their parents. |
| 491 | + for (CelNavigableMutableExpr node : preOrderNodes.reverse()) { |
| 492 | + CelMutableExpr expr = node.expr(); |
| 493 | + Kind kind = node.getKind(); |
| 494 | + if ((kind.equals(Kind.CALL) && !cseEliminableFunctions.contains(expr.call().function())) |
| 495 | + || node.children().anyMatch(child -> impureExprs.contains(child.expr()))) { |
| 496 | + impureExprs.add(expr); |
| 497 | + } |
| 498 | + if ((kind.equals(Kind.IDENT) |
| 499 | + && (expr.ident().name().startsWith(MANGLED_COMPREHENSION_ITER_VAR_PREFIX) |
| 500 | + || expr.ident().name().startsWith(MANGLED_COMPREHENSION_ACCU_VAR_PREFIX))) |
| 501 | + || node.children().anyMatch(child -> mangledExprs.contains(child.expr()))) { |
| 502 | + mangledExprs.add(expr); |
| 503 | + } |
| 504 | + if (!kind.equals(Kind.CONSTANT) |
| 505 | + && !kind.equals(Kind.IDENT) |
| 506 | + // Exclude empty lists (cel.bind sets this for iterRange). |
| 507 | + && !(kind.equals(Kind.LIST) && expr.list().elements().isEmpty()) |
| 508 | + && !ineligibleExprs.contains(expr) |
| 509 | + && !impureExprs.contains(expr) |
| 510 | + && (kind.equals(Kind.COMPREHENSION) |
| 511 | + ? !referencesEnclosingComprehensionVars(node) |
| 512 | + : !mangledExprs.contains(expr))) { |
| 513 | + eligibleExprs.add(expr); |
| 514 | + } |
| 515 | + } |
| 516 | + return eligibleExprs; |
| 517 | + } |
| 518 | + |
| 519 | + // Comprehensions may reference their own variables, but not those of enclosing ones (e.g. |
| 520 | + // [2].exists(y, x == y) in [1].exists(x, [2].exists(y, x == y))), as that breaks scoping. |
| 521 | + private static boolean referencesEnclosingComprehensionVars(CelNavigableMutableExpr node) { |
| 522 | + return node.descendants() |
| 523 | + .filter(d -> d.getKind().equals(Kind.IDENT)) |
| 524 | + .map(d -> d.expr().ident().name()) |
| 525 | + .anyMatch(CelNavigableExprUtil.getEnclosingComprehensionVariables(node)::contains); |
464 | 526 | } |
465 | 527 |
|
466 | 528 | /** |
@@ -488,21 +550,14 @@ private static Set<CelMutableExpr> getIneligibleExprsFromComprehensionBranches( |
488 | 550 | .allNodes()) |
489 | 551 | .map(CelNavigableMutableExpr::expr) |
490 | 552 | .forEach(ineligibleExprs::add)); |
491 | | - |
492 | | - return ineligibleExprs; |
493 | | - } |
494 | | - |
495 | | - private boolean containsEliminableFunctionOnly(CelNavigableMutableExpr navigableExpr) { |
496 | | - return navigableExpr |
| 553 | + navAst |
| 554 | + .getRoot() |
497 | 555 | .allNodes() |
498 | | - .allMatch( |
499 | | - node -> { |
500 | | - if (node.getKind().equals(Kind.CALL)) { |
501 | | - return cseEliminableFunctions.contains(node.expr().call().function()); |
502 | | - } |
| 556 | + .map(CelNavigableMutableExpr::expr) |
| 557 | + .filter(SubexpressionOptimizer::isAccumulation) |
| 558 | + .forEach(expr -> ineligibleExprs.add(expr.call().args().get(1))); |
503 | 559 |
|
504 | | - return true; |
505 | | - }); |
| 560 | + return ineligibleExprs; |
506 | 561 | } |
507 | 562 |
|
508 | 563 | // Assigns equal value numbers to equivalent subexpressions, including commuted and converse calls |
|
0 commit comments