Skip to content

Commit 90d0931

Browse files
committed
Add inlining optimizer
1 parent e36c49f commit 90d0931

7 files changed

Lines changed: 275 additions & 1 deletion

File tree

.bazelversion

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
8.5.0

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: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,38 @@ java_library(
7171
],
7272
)
7373

74+
java_library(
75+
name = "inlining",
76+
srcs = [
77+
"InliningOptimizer.java",
78+
],
79+
tags = [
80+
],
81+
deps = [
82+
":default_optimizer_constants",
83+
"//:auto_value",
84+
"//bundle:cel",
85+
"//common:cel_ast",
86+
"//common:cel_source",
87+
"//common:compiler_common",
88+
"//common:mutable_ast",
89+
"//common:mutable_source",
90+
"//common/ast",
91+
"//common/ast:mutable_expr",
92+
"//common/navigation",
93+
"//common/navigation:common",
94+
"//common/navigation:mutable_navigation",
95+
"//common/types",
96+
"//common/types:type_providers",
97+
"//optimizer:ast_optimizer",
98+
"//optimizer:mutable_ast",
99+
"//optimizer:optimization_exception",
100+
"@maven//:com_google_errorprone_error_prone_annotations",
101+
"@maven//:com_google_guava_guava",
102+
"@maven//:org_jspecify_jspecify",
103+
],
104+
)
105+
74106
java_library(
75107
name = "default_optimizer_constants",
76108
srcs = [
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package dev.cel.optimizer.optimizers;
2+
3+
import static com.google.common.collect.ImmutableList.toImmutableList;
4+
5+
import com.google.auto.value.AutoValue;
6+
import com.google.common.collect.ImmutableList;
7+
import dev.cel.bundle.Cel;
8+
import dev.cel.common.CelAbstractSyntaxTree;
9+
import dev.cel.common.CelMutableAst;
10+
import dev.cel.common.ast.CelExpr.ExprKind.Kind;
11+
import dev.cel.common.ast.CelMutableExpr.CelMutableComprehension;
12+
import dev.cel.common.ast.CelMutableExprConverter;
13+
import dev.cel.common.navigation.CelNavigableMutableAst;
14+
import dev.cel.common.navigation.CelNavigableMutableExpr;
15+
import dev.cel.optimizer.AstMutator;
16+
import dev.cel.optimizer.CelAstOptimizer;
17+
import dev.cel.optimizer.CelOptimizationException;
18+
19+
public final class InliningOptimizer implements CelAstOptimizer {
20+
21+
private final ImmutableList<InlineVariable> inlineVariables;
22+
private final AstMutator astMutator;
23+
24+
public static InliningOptimizer newInstance(InlineVariable... inlineVariables) {
25+
return newInstance(InliningOptions.newBuilder().build(), ImmutableList.copyOf(inlineVariables));
26+
}
27+
28+
public static InliningOptimizer newInstance(InliningOptions options, InlineVariable... inlineVariables) {
29+
return newInstance(options, ImmutableList.copyOf(inlineVariables));
30+
}
31+
32+
public static InliningOptimizer newInstance(InliningOptions options, Iterable<InlineVariable> inlineVariables) {
33+
return new InliningOptimizer(options, ImmutableList.copyOf(inlineVariables));
34+
}
35+
36+
@Override
37+
public OptimizationResult optimize(CelAbstractSyntaxTree ast, Cel cel)
38+
throws CelOptimizationException {
39+
CelMutableAst mutableAst = CelMutableAst.fromCelAst(ast);
40+
for (InlineVariable inlineVariable : inlineVariables) {
41+
ImmutableList<CelNavigableMutableExpr> inlinableExprs =
42+
CelNavigableMutableAst.fromAst(mutableAst)
43+
.getRoot()
44+
.allNodes()
45+
.filter(node -> node.getKind().equals(Kind.IDENT))
46+
.filter(node -> node.expr().ident().name().equals(inlineVariable.name()))
47+
.filter(expr -> canInline(expr, inlineVariable.name()))
48+
.collect(toImmutableList());
49+
50+
for (CelNavigableMutableExpr inlinableExpr : inlinableExprs) {
51+
mutableAst = astMutator.replaceSubtree(mutableAst, CelMutableExprConverter.fromCelExpr(inlineVariable.ast().getExpr()), inlinableExpr.id());
52+
}
53+
}
54+
55+
return OptimizationResult.create(mutableAst.toParsedAst());
56+
}
57+
58+
private static boolean canInline(CelNavigableMutableExpr expr, String identifier) {
59+
for (CelNavigableMutableExpr p = expr.parent().orElse(null); p != null; p = p.parent().orElse(null)) {
60+
if (p.getKind() != Kind.COMPREHENSION) {
61+
continue;
62+
}
63+
64+
CelMutableComprehension comp = p.expr().comprehension();
65+
66+
if (comp.iterVar().equals(identifier) || comp.iterVar2().equals(identifier) || comp.accuVar().equals(identifier)) {
67+
return false;
68+
}
69+
}
70+
return true;
71+
}
72+
73+
@AutoValue
74+
public abstract static class InlineVariable {
75+
public abstract String name();
76+
public abstract CelAbstractSyntaxTree ast();
77+
78+
public static InlineVariable of(String name, CelAbstractSyntaxTree ast) {
79+
if (name.startsWith("@")) {
80+
throw new IllegalArgumentException("Internal variables cannot be inlined: " + name);
81+
}
82+
return new AutoValue_InliningOptimizer_InlineVariable(name, ast);
83+
}
84+
}
85+
86+
/**
87+
* TODO
88+
*/
89+
@AutoValue
90+
public abstract static class InliningOptions {
91+
public abstract int maxIterationLimit();
92+
93+
/** Builder for configuring the {@link InliningOptimizer.InliningOptions}. */
94+
@AutoValue.Builder
95+
public abstract static class Builder {
96+
97+
/**
98+
* Limit the number of iteration while inlining variables. An exception is thrown if
99+
* the iteration count exceeds the set value.
100+
*/
101+
public abstract InliningOptions.Builder maxIterationLimit(int value);
102+
103+
public abstract InliningOptimizer.InliningOptions build();
104+
105+
Builder() {}
106+
}
107+
108+
/** Returns a new options builder with recommended defaults pre-configured. */
109+
public static InliningOptimizer.InliningOptions.Builder newBuilder() {
110+
return new AutoValue_InliningOptimizer_InliningOptions.Builder()
111+
.maxIterationLimit(400);
112+
}
113+
114+
InliningOptions() {}
115+
}
116+
117+
private InliningOptimizer(InliningOptions options, ImmutableList<InlineVariable> inlineVariables) {
118+
this.inlineVariables = inlineVariables;
119+
this.astMutator = AstMutator.newInstance(options.maxIterationLimit());
120+
}
121+
}

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",
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package dev.cel.optimizer.optimizers;
2+
3+
import static com.google.common.truth.Truth.assertThat;
4+
import static org.junit.Assert.assertThrows;
5+
6+
import com.google.testing.junit.testparameterinjector.TestParameterInjector;
7+
import dev.cel.bundle.Cel;
8+
import dev.cel.bundle.CelFactory;
9+
import dev.cel.common.CelAbstractSyntaxTree;
10+
import dev.cel.common.CelOptions;
11+
import dev.cel.common.CelSource;
12+
import dev.cel.common.ast.CelExpr;
13+
import dev.cel.common.types.SimpleType;
14+
import dev.cel.extensions.CelExtensions;
15+
import dev.cel.optimizer.CelOptimizationException;
16+
import dev.cel.optimizer.CelOptimizer;
17+
import dev.cel.optimizer.CelOptimizerFactory;
18+
import dev.cel.optimizer.optimizers.InliningOptimizer.InlineVariable;
19+
import dev.cel.optimizer.optimizers.InliningOptimizer.InliningOptions;
20+
import dev.cel.parser.CelStandardMacro;
21+
import dev.cel.parser.CelUnparserFactory;
22+
import org.junit.Test;
23+
import org.junit.runner.RunWith;
24+
25+
@RunWith(TestParameterInjector.class)
26+
public class InliningOptimizerTest {
27+
28+
@Test
29+
public void inlineConstant() throws Exception {
30+
Cel cel = CelFactory.standardCelBuilder().addVar("ident_to_inline", SimpleType.INT).build();
31+
String expression = "ident_to_inline + 2 + ident_to_inline";
32+
CelAbstractSyntaxTree astToInline = cel.compile(expression).getAst();
33+
CelOptimizer optimizer = CelOptimizerFactory.standardCelOptimizerBuilder(cel)
34+
.addAstOptimizers(InliningOptimizer.newInstance(
35+
InlineVariable.of("ident_to_inline", cel.compile("1").getAst())
36+
))
37+
.build();
38+
39+
CelAbstractSyntaxTree optimized = optimizer.optimize(astToInline);
40+
41+
String unparsed = CelUnparserFactory.newUnparser().unparse(optimized);
42+
assertThat(unparsed).isEqualTo("1 + 2 + 1");
43+
}
44+
45+
@Test
46+
public void inline_doesNotInlineIterVar() throws Exception {
47+
Cel cel = CelFactory.standardCelBuilder()
48+
.setStandardMacros(CelStandardMacro.STANDARD_MACROS)
49+
.setOptions(CelOptions.current().populateMacroCalls(true).build())
50+
.addVar("shadowed_ident", SimpleType.INT)
51+
.build();
52+
String expression = "[0].exists(shadowed_ident, shadowed_ident == 0)";
53+
CelAbstractSyntaxTree astToInline = cel.compile(expression).getAst();
54+
CelOptimizer optimizer = CelOptimizerFactory.standardCelOptimizerBuilder(cel)
55+
.addAstOptimizers(InliningOptimizer.newInstance(
56+
InlineVariable.of("shadowed_ident", cel.compile("1").getAst())
57+
))
58+
.build();
59+
60+
CelAbstractSyntaxTree optimized = optimizer.optimize(astToInline);
61+
62+
String unparsed = CelUnparserFactory.newUnparser().unparse(optimized);
63+
assertThat(unparsed).isEqualTo("[0].exists(shadowed_ident, shadowed_ident == 0)");
64+
}
65+
66+
@Test
67+
public void inline_bindMacro_doesNotInlineVarName() throws Exception {
68+
Cel cel = CelFactory.standardCelBuilder()
69+
.setStandardMacros(CelStandardMacro.STANDARD_MACROS)
70+
.addCompilerLibraries(CelExtensions.bindings())
71+
.setOptions(CelOptions.current().populateMacroCalls(true).build())
72+
.addVar("shadowed_ident", SimpleType.INT)
73+
.build();
74+
String expression = "cel.bind(shadowed_ident, 2, shadowed_ident + 1)";
75+
CelAbstractSyntaxTree astToInline = cel.compile(expression).getAst();
76+
CelOptimizer optimizer = CelOptimizerFactory.standardCelOptimizerBuilder(cel)
77+
.addAstOptimizers(InliningOptimizer.newInstance(
78+
InlineVariable.of("shadowed_ident", cel.compile("1").getAst())
79+
))
80+
.build();
81+
82+
CelAbstractSyntaxTree optimized = optimizer.optimize(astToInline);
83+
84+
String unparsed = CelUnparserFactory.newUnparser().unparse(optimized);
85+
assertThat(unparsed).isEqualTo("cel.bind(shadowed_ident, 2, shadowed_ident + 1)");
86+
}
87+
88+
@Test
89+
public void inline_exceededIterationLimit_throws() throws Exception {
90+
Cel cel = CelFactory.standardCelBuilder()
91+
.setStandardMacros(CelStandardMacro.STANDARD_MACROS)
92+
.addCompilerLibraries(CelExtensions.bindings())
93+
.addVar("ident_to_replace", SimpleType.INT)
94+
.build();
95+
String expression = "ident_to_replace + ident_to_replace + ident_to_replace";
96+
CelAbstractSyntaxTree astToInline = cel.compile(expression).getAst();
97+
CelOptimizer optimizer = CelOptimizerFactory.standardCelOptimizerBuilder(cel)
98+
.addAstOptimizers(InliningOptimizer.newInstance(
99+
InliningOptions.newBuilder().maxIterationLimit(2).build(),
100+
InlineVariable.of("ident_to_replace", cel.compile("1").getAst())
101+
))
102+
.build();
103+
104+
CelOptimizationException e = assertThrows(CelOptimizationException.class, () -> optimizer.optimize(astToInline));
105+
assertThat(e).hasMessageThat().contains("Max iteration count reached.");
106+
}
107+
108+
@Test
109+
public void inlineVariableDecl_internalVar_throws() throws Exception {
110+
IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () ->
111+
InlineVariable.of("@internal_var", CelAbstractSyntaxTree.newParsedAst(CelExpr.ofNotSet(0L), CelSource.newBuilder().build())));
112+
assertThat(e).hasMessageThat().contains("Internal variables cannot be inlined: @internal_var" );
113+
}
114+
}

0 commit comments

Comments
 (0)