Skip to content

Commit 5d8b795

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

8 files changed

Lines changed: 472 additions & 151 deletions

File tree

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

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@
1818
import static java.lang.Math.max;
1919
import static java.util.stream.Collectors.toCollection;
2020

21+
import com.google.auto.value.AutoOneOf;
2122
import com.google.auto.value.AutoValue;
2223
import com.google.common.base.Preconditions;
2324
import com.google.common.base.Strings;
2425
import com.google.common.collect.HashBasedTable;
2526
import com.google.common.collect.ImmutableMap;
27+
import com.google.common.collect.Streams;
2628
import com.google.common.collect.Table;
2729
import com.google.errorprone.annotations.Immutable;
2830
import dev.cel.common.CelAbstractSyntaxTree;
@@ -44,11 +46,13 @@
4446
import java.util.Arrays;
4547
import java.util.Collection;
4648
import java.util.HashMap;
49+
import java.util.Iterator;
4750
import java.util.LinkedHashMap;
4851
import java.util.List;
4952
import java.util.Map.Entry;
5053
import java.util.NoSuchElementException;
5154
import java.util.Optional;
55+
import java.util.function.Function;
5256
import java.util.function.Predicate;
5357
import java.util.stream.Collectors;
5458

@@ -552,6 +556,140 @@ public CelMutableAst replaceSubtree(
552556
return CelMutableAst.of(mutatedRoot, newAstSource);
553557
}
554558

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

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)