Skip to content

Commit 3fc0a83

Browse files
l46kokcopybara-github
authored andcommitted
Add helpers for performing fixed point optimization
PiperOrigin-RevId: 961182997
1 parent d86bfbe commit 3fc0a83

8 files changed

Lines changed: 449 additions & 138 deletions

File tree

optimizer/src/main/java/dev/cel/optimizer/AstMutator.java

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import com.google.common.base.Strings;
2424
import com.google.common.collect.HashBasedTable;
2525
import com.google.common.collect.ImmutableMap;
26+
import com.google.common.collect.Streams;
2627
import com.google.common.collect.Table;
2728
import com.google.errorprone.annotations.Immutable;
2829
import dev.cel.common.CelAbstractSyntaxTree;
@@ -49,6 +50,7 @@
4950
import java.util.Map.Entry;
5051
import java.util.NoSuchElementException;
5152
import java.util.Optional;
53+
import java.util.function.Function;
5254
import java.util.function.Predicate;
5355
import java.util.stream.Collectors;
5456

@@ -552,6 +554,136 @@ public CelMutableAst replaceSubtree(
552554
return CelMutableAst.of(mutatedRoot, newAstSource);
553555
}
554556

557+
/**
558+
* Replaces a subtree in the given AST with the specified {@link SubtreeReplacement}.
559+
*
560+
* <p>This operation is intended for AST optimization purposes.
561+
*
562+
* <p>This is a very dangerous operation. Callers must re-typecheck the mutated AST and
563+
* additionally verify that the resulting AST is semantically valid.
564+
*
565+
* <p>All expression IDs will be renumbered in a stable manner to ensure there's no ID collision
566+
* between the nodes. The renumbering occurs even if the subtree was not replaced.
567+
*
568+
* @param ast Original AST to mutate.
569+
* @param replacement Subtree replacement containing the target node ID and the new expression or
570+
* AST.
571+
*/
572+
public CelMutableAst replaceSubtree(CelMutableAst ast, SubtreeReplacement replacement) {
573+
Preconditions.checkNotNull(ast);
574+
Preconditions.checkNotNull(replacement);
575+
if (replacement.replacementAst().isPresent()) {
576+
return replaceSubtree(ast, replacement.replacementAst().get(), replacement.exprIdToReplace());
577+
}
578+
return replaceSubtree(ast, replacement.replacementExpr().get(), replacement.exprIdToReplace());
579+
}
580+
581+
/**
582+
* Repeatedly applies AST mutations using the provided AST-level rewriter until no further
583+
* replacements match (fixed point reached) or the mutator's iteration limit is exhausted.
584+
*
585+
* <p>This operation is intended for AST optimization purposes.
586+
*
587+
* <p>This is a very dangerous operation. Callers must re-typecheck the mutated AST and
588+
* additionally verify that the resulting AST is semantically valid.
589+
*
590+
* <p>All expression IDs will be renumbered in a stable manner to ensure there's no ID collision
591+
* between the nodes.
592+
*
593+
* @param ast Initial mutable AST to mutate.
594+
* @param astRewriter Function returning a {@link SubtreeReplacement} or {@code Optional.empty()}
595+
* when no further rewrites are possible.
596+
* @return Mutated {@link CelMutableAst} at fixed point.
597+
* @throws IllegalStateException If the iteration count exceeds {@code iterationLimit}.
598+
*/
599+
public CelMutableAst mutateUntilFixedPoint(
600+
CelMutableAst ast,
601+
Function<CelNavigableMutableAst, Optional<SubtreeReplacement>> astRewriter) {
602+
Preconditions.checkNotNull(ast);
603+
Preconditions.checkNotNull(astRewriter);
604+
CelMutableAst mutableAst = ast;
605+
for (long i = 0; i < iterationLimit; i++) {
606+
CelNavigableMutableAst navAst = CelNavigableMutableAst.fromAst(mutableAst);
607+
Optional<SubtreeReplacement> replacement = astRewriter.apply(navAst);
608+
if (!replacement.isPresent()) {
609+
return mutableAst;
610+
}
611+
mutableAst = replaceSubtree(mutableAst, replacement.get());
612+
}
613+
throw new IllegalStateException("Max iteration count reached.");
614+
}
615+
616+
/**
617+
* Traverses nodes using the specified {@link TraversalOrder} and repeatedly rewrites matching
618+
* subtrees until a fixed point is reached.
619+
*
620+
* <p>This operation is intended for AST optimization purposes.
621+
*
622+
* <p>This is a very dangerous operation. Callers must re-typecheck the mutated AST and
623+
* additionally verify that the resulting AST is semantically valid.
624+
*
625+
* <p>All expression IDs will be renumbered in a stable manner to ensure there's no ID collision
626+
* between the nodes.
627+
*
628+
* @param ast Initial mutable AST to mutate.
629+
* @param traversalOrder Order in which nodes are visited per iteration pass.
630+
* @param nodeRewriter Function returning a {@link SubtreeReplacement} or {@code
631+
* Optional.empty()}.
632+
* @return Mutated {@link CelMutableAst} at fixed point.
633+
* @throws IllegalStateException If the iteration count exceeds {@code iterationLimit}.
634+
*/
635+
public CelMutableAst mutateUntilFixedPoint(
636+
CelMutableAst ast,
637+
TraversalOrder traversalOrder,
638+
Function<CelNavigableMutableExpr, Optional<SubtreeReplacement>> nodeRewriter) {
639+
Preconditions.checkNotNull(traversalOrder);
640+
Preconditions.checkNotNull(nodeRewriter);
641+
return mutateUntilFixedPoint(
642+
ast,
643+
navAst ->
644+
navAst
645+
.getRoot()
646+
.allNodes(traversalOrder)
647+
.flatMap(node -> Streams.stream(nodeRewriter.apply(node)))
648+
.findFirst());
649+
}
650+
651+
/**
652+
* Traverses nodes using the specified {@link TraversalOrder}, applies the node matcher, and
653+
* substitutes matching nodes with the returned replacement expression (targeting {@code
654+
* node.id()}).
655+
*
656+
* <p>This operation is intended for AST optimization purposes.
657+
*
658+
* <p>This is a very dangerous operation. Callers must re-typecheck the mutated AST and
659+
* additionally verify that the resulting AST is semantically valid.
660+
*
661+
* <p>All expression IDs will be renumbered in a stable manner to ensure there's no ID collision
662+
* between the nodes.
663+
*
664+
* @param ast Initial mutable AST to mutate.
665+
* @param traversalOrder Order in which nodes are visited per iteration pass.
666+
* @param nodeMatcher Predicate to filter candidate nodes.
667+
* @param nodeRewriter Function producing the new {@link CelMutableExpr} for matched nodes.
668+
* @return Mutated {@link CelMutableAst} at fixed point.
669+
* @throws IllegalStateException If the iteration count exceeds {@code iterationLimit}.
670+
*/
671+
public CelMutableAst mutateUntilFixedPoint(
672+
CelMutableAst ast,
673+
TraversalOrder traversalOrder,
674+
Predicate<CelNavigableMutableExpr> nodeMatcher,
675+
Function<CelNavigableMutableExpr, Optional<CelMutableExpr>> nodeRewriter) {
676+
Preconditions.checkNotNull(nodeMatcher);
677+
Preconditions.checkNotNull(nodeRewriter);
678+
return mutateUntilFixedPoint(
679+
ast,
680+
traversalOrder,
681+
node ->
682+
nodeMatcher.test(node)
683+
? nodeRewriter.apply(node).map(newExpr -> SubtreeReplacement.of(node.id(), newExpr))
684+
: Optional.empty());
685+
}
686+
555687
private CelMutableExpr mangleIdentsInComprehensionExpr(
556688
CelMutableExpr root,
557689
CelMutableExpr comprehensionExpr,
@@ -983,4 +1115,32 @@ private static MangledComprehensionName of(
9831115
iterVarName, iterVar2Name, resultName);
9841116
}
9851117
}
1118+
1119+
/**
1120+
* Represents a planned subtree replacement containing the target node ID to replace and either a
1121+
* {@link CelMutableExpr} or {@link CelMutableAst}.
1122+
*/
1123+
@AutoValue
1124+
public abstract static class SubtreeReplacement {
1125+
1126+
public abstract long exprIdToReplace();
1127+
1128+
public abstract Optional<CelMutableExpr> replacementExpr();
1129+
1130+
public abstract Optional<CelMutableAst> replacementAst();
1131+
1132+
public static SubtreeReplacement of(long exprIdToReplace, CelMutableExpr replacementExpr) {
1133+
return new AutoValue_AstMutator_SubtreeReplacement(
1134+
exprIdToReplace,
1135+
Optional.of(Preconditions.checkNotNull(replacementExpr)),
1136+
Optional.empty());
1137+
}
1138+
1139+
public static SubtreeReplacement of(long exprIdToReplace, CelMutableAst replacementAst) {
1140+
return new AutoValue_AstMutator_SubtreeReplacement(
1141+
exprIdToReplace,
1142+
Optional.empty(),
1143+
Optional.of(Preconditions.checkNotNull(replacementAst)));
1144+
}
1145+
}
9861146
}

optimizer/src/main/java/dev/cel/optimizer/optimizers/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ java_library(
9696
"//common:operator",
9797
"//common/ast",
9898
"//common/ast:mutable_expr",
99+
"//common/navigation:common",
99100
"//common/navigation:expr_util",
100101
"//common/navigation:mutable_navigation",
101102
"//common/types",

0 commit comments

Comments
 (0)