Skip to content

Commit 6bc3192

Browse files
CEL Dev Teamcopybara-github
authored andcommitted
Avoid unnecessary AST renumbering in CEL optimizers when no changes are made.
PiperOrigin-RevId: 976189920
1 parent 9a97aec commit 6bc3192

4 files changed

Lines changed: 66 additions & 15 deletions

File tree

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

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ final class CelOptimizerImpl implements CelOptimizer {
3939
}
4040

4141
@Override
42+
@SuppressWarnings("ReferenceEquality")
4243
public CelAbstractSyntaxTree optimize(CelAbstractSyntaxTree ast) throws CelOptimizationException {
4344
if (!ast.isChecked()) {
4445
throw new IllegalArgumentException("AST must be type-checked.");
@@ -49,16 +50,18 @@ public CelAbstractSyntaxTree optimize(CelAbstractSyntaxTree ast) throws CelOptim
4950
try {
5051
for (CelAstOptimizer optimizer : astOptimizers) {
5152
OptimizationResult result = optimizer.optimize(optimizedAst, celOptimizerEnv);
52-
if (!result.newFunctionDecls().isEmpty() || !result.newVarDecls().isEmpty()) {
53-
celOptimizerEnv =
54-
celOptimizerEnv
55-
.toCelBuilder()
56-
.addVarDeclarations(result.newVarDecls())
57-
.addFunctionDeclarations(result.newFunctionDecls())
58-
.build();
53+
if (result.optimizedAst() != optimizedAst) {
54+
if (!result.newFunctionDecls().isEmpty() || !result.newVarDecls().isEmpty()) {
55+
celOptimizerEnv =
56+
celOptimizerEnv
57+
.toCelBuilder()
58+
.addVarDeclarations(result.newVarDecls())
59+
.addFunctionDeclarations(result.newFunctionDecls())
60+
.build();
61+
}
62+
optimizedAst = celOptimizerEnv.check(result.optimizedAst()).getAst();
63+
assertAstIdCorrectness(optimizedAst);
5964
}
60-
optimizedAst = celOptimizerEnv.check(result.optimizedAst()).getAst();
61-
assertAstIdCorrectness(optimizedAst);
6265
}
6366
} catch (CelValidationException e) {
6467
throw new CelOptimizationException(

optimizer/src/main/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizer.java

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ private static CelMutableExpr newOptionalNoneExpr() {
121121
}
122122

123123
@Override
124+
@SuppressWarnings("ReferenceEquality")
124125
public OptimizationResult optimize(CelAbstractSyntaxTree ast, Cel cel)
125126
throws CelOptimizationException {
126127
CelBuilder builder = cel.toCelBuilder();
@@ -134,12 +135,17 @@ public OptimizationResult optimize(CelAbstractSyntaxTree ast, Cel cel)
134135
// Override the environment's expected type to generally allow all subtrees to be folded.
135136
Cel optimizerEnv = builder.setResultType(SimpleType.DYN).build();
136137

137-
CelMutableAst mutableAst = CelMutableAst.fromCelAst(ast);
138-
ImmutableMap<String, CelType> identTypes = precomputeIdentTypes(mutableAst);
138+
CelMutableAst initialMutableAst = CelMutableAst.fromCelAst(ast);
139+
ImmutableMap<String, CelType> identTypes = precomputeIdentTypes(initialMutableAst);
139140

140-
mutableAst = foldConstants(optimizerEnv, valueProvider, identTypes, mutableAst);
141+
CelMutableAst mutableAst =
142+
foldConstants(optimizerEnv, valueProvider, identTypes, initialMutableAst);
141143
mutableAst = pruneOptionalElements(mutableAst);
142144

145+
if (mutableAst == initialMutableAst) {
146+
return OptimizationResult.create(ast);
147+
}
148+
143149
return OptimizationResult.create(astMutator.renumberIdsConsecutively(mutableAst).toParsedAst());
144150
}
145151

@@ -735,10 +741,21 @@ private CelMutableAst pruneOptionalListElements(CelMutableAst mutableAst, CelMut
735741
updatedIndicesBuilder.add(newOptIndex);
736742
}
737743

744+
// An optional list is modified if:
745+
// 1. An optional.none() was dropped - it this case, the updatedElements.size() decreases.
746+
// 2. An optional.of(literal) was unwrapped into a regular element - in this case,
747+
// updatedIndices.size() decreases.
748+
// If both counts are unchanged, neither case occurred, and we can return the original AST.
749+
ImmutableList<CelMutableExpr> updatedElements = updatedElemBuilder.build();
750+
ImmutableList<Integer> updatedIndices = updatedIndicesBuilder.build();
751+
if (updatedElements.size() == list.elements().size()
752+
&& updatedIndices.size() == list.optionalIndices().size()) {
753+
return mutableAst;
754+
}
755+
738756
return astMutator.replaceSubtree(
739757
mutableAst,
740-
CelMutableExpr.ofList(
741-
CelMutableList.create(updatedElemBuilder.build(), updatedIndicesBuilder.build())),
758+
CelMutableExpr.ofList(CelMutableList.create(updatedElements, updatedIndices)),
742759
expr.id());
743760
}
744761

optimizer/src/main/java/dev/cel/optimizer/optimizers/InliningOptimizer.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,10 @@ public static InliningOptimizer newInstance(
101101
}
102102

103103
@Override
104+
@SuppressWarnings("ReferenceEquality")
104105
public OptimizationResult optimize(CelAbstractSyntaxTree ast, Cel cel) {
105-
CelMutableAst mutableAst = CelMutableAst.fromCelAst(ast);
106+
CelMutableAst initialMutableAst = CelMutableAst.fromCelAst(ast);
107+
CelMutableAst mutableAst = initialMutableAst;
106108
for (InlineVariable inlineVariable : inlineVariables) {
107109
mutableAst =
108110
astMutator.mutateUntilFixedPoint(
@@ -125,6 +127,10 @@ public OptimizationResult optimize(CelAbstractSyntaxTree ast, Cel cel) {
125127
});
126128
}
127129

130+
if (mutableAst == initialMutableAst) {
131+
return OptimizationResult.create(ast);
132+
}
133+
128134
return OptimizationResult.create(astMutator.renumberIdsConsecutively(mutableAst).toParsedAst());
129135
}
130136

optimizer/src/test/java/dev/cel/optimizer/optimizers/SubexpressionOptimizerTest.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -702,6 +702,31 @@ public void block_lazyEvaluationContainsError_cleansUpCycleState() throws Except
702702
assertThat(e).hasMessageThat().doesNotContain("Cycle detected");
703703
}
704704

705+
@Test
706+
public void cse_nestedMacro_noOp_assertAstIdCorrectness() throws Exception {
707+
Cel cel =
708+
runtimeFlavor
709+
.builder()
710+
.addVar("x", SimpleType.DYN)
711+
.setStandardMacros(CelStandardMacro.STANDARD_MACROS)
712+
.setOptions(CelOptions.current().populateMacroCalls(true).build())
713+
.addCompilerLibraries(CelExtensions.comprehensions())
714+
.addRuntimeLibraries(CelExtensions.comprehensions())
715+
.build();
716+
CelOptimizer celOptimizer =
717+
CelOptimizerFactory.standardCelOptimizerBuilder(cel)
718+
.addAstOptimizers(SubexpressionOptimizer.getInstance())
719+
.build();
720+
CelAbstractSyntaxTree ast =
721+
cel.compile("[{}, {\"a\": 1}, {\"b\": 2}].filter(m, has(x.a))").getAst();
722+
723+
CelAbstractSyntaxTree optimizedAst = celOptimizer.optimize(ast);
724+
725+
assertThat(CEL_UNPARSER.unparse(optimizedAst))
726+
.isEqualTo("[{}, {\"a\": 1}, {\"b\": 2}].filter(m, has(x.a))");
727+
assertThat(optimizedAst).isSameInstanceAs(ast);
728+
}
729+
705730
/**
706731
* Converts AST containing cel.block related test functions to internal functions (e.g: cel.block
707732
* -> cel.@block)

0 commit comments

Comments
 (0)