Skip to content

Commit c0dbe70

Browse files
l46kokcopybara-github
authored andcommitted
Add InliningOptimizer
PiperOrigin-RevId: 867853388
1 parent e36c49f commit c0dbe70

6 files changed

Lines changed: 483 additions & 1 deletion

File tree

optimizer/optimizers/BUILD.bazel

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,8 @@ java_library(
1414
name = "common_subexpression_elimination",
1515
exports = ["//optimizer/src/main/java/dev/cel/optimizer/optimizers:common_subexpression_elimination"],
1616
)
17+
18+
java_library(
19+
name = "inlining",
20+
exports = ["//optimizer/src/main/java/dev/cel/optimizer/optimizers:inlining"],
21+
)

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,33 @@ java_library(
7171
],
7272
)
7373

74+
java_library(
75+
name = "inlining",
76+
srcs = [
77+
"InliningOptimizer.java",
78+
],
79+
tags = [
80+
],
81+
deps = [
82+
"//:auto_value",
83+
"//bundle:cel",
84+
"//common:cel_ast",
85+
"//common:mutable_ast",
86+
"//common:operator",
87+
"//common/ast",
88+
"//common/ast:mutable_expr",
89+
"//common/navigation:mutable_navigation",
90+
"//common/types",
91+
"//common/types:type_providers",
92+
"//common/values",
93+
"//optimizer:ast_optimizer",
94+
"//optimizer:mutable_ast",
95+
"@maven//:com_google_errorprone_error_prone_annotations",
96+
"@maven//:com_google_guava_guava",
97+
"@maven//:org_jspecify_jspecify",
98+
],
99+
)
100+
74101
java_library(
75102
name = "default_optimizer_constants",
76103
srcs = [
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package dev.cel.optimizer.optimizers;
16+
17+
import static com.google.common.collect.ImmutableList.toImmutableList;
18+
19+
import com.google.auto.value.AutoValue;
20+
import com.google.common.collect.ImmutableList;
21+
import dev.cel.bundle.Cel;
22+
import dev.cel.common.CelAbstractSyntaxTree;
23+
import dev.cel.common.CelMutableAst;
24+
import dev.cel.common.Operator;
25+
import dev.cel.common.ast.CelConstant;
26+
import dev.cel.common.ast.CelExpr.ExprKind.Kind;
27+
import dev.cel.common.ast.CelMutableExpr;
28+
import dev.cel.common.ast.CelMutableExpr.CelMutableCall;
29+
import dev.cel.common.ast.CelMutableExpr.CelMutableComprehension;
30+
import dev.cel.common.navigation.CelNavigableMutableAst;
31+
import dev.cel.common.navigation.CelNavigableMutableExpr;
32+
import dev.cel.common.types.CelKind;
33+
import dev.cel.common.types.CelType;
34+
import dev.cel.common.types.SimpleType;
35+
import dev.cel.common.values.NullValue;
36+
import dev.cel.optimizer.AstMutator;
37+
import dev.cel.optimizer.CelAstOptimizer;
38+
import java.util.NoSuchElementException;
39+
import java.util.Optional;
40+
import java.util.stream.Stream;
41+
42+
/**
43+
* Performs optimization for inlining variables within function calls and select statements with
44+
* their associated AST.
45+
*/
46+
public final class InliningOptimizer implements CelAstOptimizer {
47+
48+
private final ImmutableList<InlineVariable> inlineVariables;
49+
private final AstMutator astMutator;
50+
51+
public static InliningOptimizer newInstance(InlineVariable... inlineVariables) {
52+
return newInstance(InliningOptions.newBuilder().build(), ImmutableList.copyOf(inlineVariables));
53+
}
54+
55+
public static InliningOptimizer newInstance(
56+
InliningOptions options, InlineVariable... inlineVariables) {
57+
return newInstance(options, ImmutableList.copyOf(inlineVariables));
58+
}
59+
60+
public static InliningOptimizer newInstance(
61+
InliningOptions options, Iterable<InlineVariable> inlineVariables) {
62+
return new InliningOptimizer(options, ImmutableList.copyOf(inlineVariables));
63+
}
64+
65+
@Override
66+
public OptimizationResult optimize(CelAbstractSyntaxTree ast, Cel cel) {
67+
CelMutableAst mutableAst = CelMutableAst.fromCelAst(ast);
68+
for (InlineVariable inlineVariable : inlineVariables) {
69+
ImmutableList<CelNavigableMutableExpr> inlinableExprs =
70+
CelNavigableMutableAst.fromAst(mutableAst)
71+
.getRoot()
72+
.allNodes()
73+
.filter(node -> canInline(node, inlineVariable.name()))
74+
.collect(toImmutableList());
75+
76+
for (CelNavigableMutableExpr inlinableExpr : inlinableExprs) {
77+
CelMutableAst inlineVariableAst = CelMutableAst.fromCelAst(inlineVariable.ast());
78+
CelMutableExpr replacementExpr = inlineVariableAst.expr();
79+
80+
if (inlinableExpr.getKind().equals(Kind.SELECT)
81+
&& inlinableExpr.expr().select().testOnly()) {
82+
if (replacementExpr.getKind().equals(Kind.SELECT)) {
83+
// Preserve testOnly property for Select replacements (has(A) -> has(B))
84+
replacementExpr.select().setTestOnly(true);
85+
} else {
86+
CelType replacementType =
87+
inlineVariable
88+
.ast()
89+
.getType(replacementExpr.id())
90+
.orElseThrow(() -> new NoSuchElementException("Type is not present."));
91+
if (isSizerType(replacementType)) {
92+
// has(X) -> X.size() != 0
93+
replacementExpr =
94+
CelMutableExpr.ofCall(
95+
CelMutableCall.create(
96+
Operator.NOT_EQUALS.getFunction(),
97+
CelMutableExpr.ofCall(CelMutableCall.create(replacementExpr, "size")),
98+
CelMutableExpr.ofConstant(CelConstant.ofValue(0))));
99+
} else if (replacementType.isAssignableFrom(SimpleType.NULL_TYPE)) {
100+
// has(X) -> X != null
101+
replacementExpr =
102+
CelMutableExpr.ofCall(
103+
CelMutableCall.create(
104+
Operator.NOT_EQUALS.getFunction(),
105+
replacementExpr,
106+
CelMutableExpr.ofConstant(CelConstant.ofValue(NullValue.NULL_VALUE))));
107+
}
108+
}
109+
}
110+
111+
mutableAst =
112+
astMutator.replaceSubtree(
113+
mutableAst,
114+
CelMutableAst.of(replacementExpr, inlineVariableAst.source()),
115+
inlinableExpr.id());
116+
}
117+
}
118+
119+
return OptimizationResult.create(astMutator.renumberIdsConsecutively(mutableAst).toParsedAst());
120+
}
121+
122+
private static boolean isSizerType(CelType type) {
123+
return type.kind().equals(CelKind.LIST)
124+
|| type.kind().equals(CelKind.MAP)
125+
|| type.equals(SimpleType.STRING)
126+
|| type.equals(SimpleType.BYTES);
127+
}
128+
129+
private static boolean canInline(CelNavigableMutableExpr node, String identifier) {
130+
boolean matches = maybeToQualifiedName(node).map(name -> name.equals(identifier)).orElse(false);
131+
132+
if (!matches) {
133+
return false;
134+
}
135+
136+
for (CelNavigableMutableExpr p = node.parent().orElse(null);
137+
p != null;
138+
p = p.parent().orElse(null)) {
139+
if (p.getKind() != Kind.COMPREHENSION) {
140+
continue;
141+
}
142+
143+
CelMutableComprehension comp = p.expr().comprehension();
144+
boolean shadows =
145+
Stream.of(comp.iterVar(), comp.iterVar2(), comp.accuVar()).anyMatch(identifier::equals);
146+
147+
if (shadows) {
148+
return false;
149+
}
150+
}
151+
152+
return true;
153+
}
154+
155+
private static Optional<String> maybeToQualifiedName(CelNavigableMutableExpr node) {
156+
if (node.getKind().equals(Kind.IDENT)) {
157+
return Optional.of(node.expr().ident().name());
158+
}
159+
160+
if (node.getKind().equals(Kind.SELECT)) {
161+
return node.children()
162+
.findFirst()
163+
.flatMap(InliningOptimizer::maybeToQualifiedName)
164+
.map(operandName -> operandName + "." + node.expr().select().field());
165+
}
166+
167+
return Optional.empty();
168+
}
169+
170+
/** Represents a variable to be inlined. */
171+
@AutoValue
172+
public abstract static class InlineVariable {
173+
public abstract String name();
174+
175+
public abstract CelAbstractSyntaxTree ast();
176+
177+
/**
178+
* Creates a new {@link InlineVariable} with the given name and AST.
179+
*
180+
* <p>The name must be a simple identifier or a qualified name (e.g. "a.b.c") and cannot be an
181+
* internal variable (starting with @).
182+
*/
183+
public static InlineVariable of(String name, CelAbstractSyntaxTree ast) {
184+
if (name.startsWith("@")) {
185+
throw new IllegalArgumentException("Internal variables cannot be inlined: " + name);
186+
}
187+
return new AutoValue_InliningOptimizer_InlineVariable(name, ast);
188+
}
189+
}
190+
191+
/** Options to configure how Inlining behaves. */
192+
@AutoValue
193+
public abstract static class InliningOptions {
194+
public abstract int maxIterationLimit();
195+
196+
/** Builder for configuring the {@link InliningOptimizer.InliningOptions}. */
197+
@AutoValue.Builder
198+
public abstract static class Builder {
199+
200+
/**
201+
* Limit the number of iteration while inlining variables. An exception is thrown if the
202+
* iteration count exceeds the set value.
203+
*/
204+
public abstract InliningOptions.Builder maxIterationLimit(int value);
205+
206+
public abstract InliningOptimizer.InliningOptions build();
207+
208+
Builder() {}
209+
}
210+
211+
/** Returns a new options builder with recommended defaults pre-configured. */
212+
public static InliningOptimizer.InliningOptions.Builder newBuilder() {
213+
return new AutoValue_InliningOptimizer_InliningOptions.Builder().maxIterationLimit(400);
214+
}
215+
216+
InliningOptions() {}
217+
}
218+
219+
private InliningOptimizer(
220+
InliningOptions options, ImmutableList<InlineVariable> inlineVariables) {
221+
this.inlineVariables = inlineVariables;
222+
this.astMutator = AstMutator.newInstance(options.maxIterationLimit());
223+
}
224+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@
9191
* }
9292
* </pre>
9393
*/
94-
public class SubexpressionOptimizer implements CelAstOptimizer {
94+
public final class SubexpressionOptimizer implements CelAstOptimizer {
9595

9696
private static final SubexpressionOptimizer INSTANCE =
9797
new SubexpressionOptimizer(SubexpressionOptimizerOptions.newBuilder().build());

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ java_library(
2727
"//optimizer:optimizer_builder",
2828
"//optimizer/optimizers:common_subexpression_elimination",
2929
"//optimizer/optimizers:constant_folding",
30+
"//optimizer/optimizers:inlining",
3031
"//parser:macro",
3132
"//parser:unparser",
3233
"//runtime",

0 commit comments

Comments
 (0)