From ce4d2179fc69458234367d1f61ea3fe1f80a178d Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Wed, 1 Apr 2026 15:03:56 -0700 Subject: [PATCH] Move fast-path unary/binary apply methods into an internal interface PiperOrigin-RevId: 893131554 --- .../src/main/java/dev/cel/runtime/BUILD.bazel | 10 ++++- .../dev/cel/runtime/CelFunctionBinding.java | 8 ++-- .../dev/cel/runtime/CelFunctionOverload.java | 9 ----- .../cel/runtime/CelLateFunctionBindings.java | 2 +- .../dev/cel/runtime/CelResolvedOverload.java | 38 +++++++++++++++++- .../dev/cel/runtime/DefaultDispatcher.java | 39 +------------------ .../dev/cel/runtime/FunctionBindingImpl.java | 8 ++-- .../runtime/OptimizedFunctionOverload.java | 35 +++++++++++++++++ .../dev/cel/runtime/planner/EvalHelpers.java | 6 +-- 9 files changed, 94 insertions(+), 61 deletions(-) create mode 100644 runtime/src/main/java/dev/cel/runtime/OptimizedFunctionOverload.java diff --git a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel index 2681c17de..6f0607de4 100644 --- a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel @@ -129,7 +129,6 @@ java_library( "//:auto_value", "//common:error_codes", "//common/annotations", - "//common/exceptions:overload_not_found", "@maven//:com_google_code_findbugs_annotations", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", @@ -151,7 +150,6 @@ cel_android_library( "//:auto_value", "//common:error_codes", "//common/annotations", - "//common/exceptions:overload_not_found", "@maven//:com_google_code_findbugs_annotations", "@maven//:com_google_errorprone_error_prone_annotations", "@maven_android//:com_google_guava_guava", @@ -790,6 +788,7 @@ java_library( name = "function_overload", srcs = [ "CelFunctionOverload.java", + "OptimizedFunctionOverload.java", ], tags = [ ], @@ -805,6 +804,7 @@ cel_android_library( name = "function_overload_android", srcs = [ "CelFunctionOverload.java", + "OptimizedFunctionOverload.java", ], deps = [ ":evaluation_exception", @@ -1306,9 +1306,12 @@ java_library( tags = [ ], deps = [ + ":evaluation_exception", + ":function_binding", ":function_overload", "//:auto_value", "//common/annotations", + "//common/exceptions:overload_not_found", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", ], @@ -1320,9 +1323,12 @@ cel_android_library( tags = [ ], deps = [ + ":evaluation_exception", + ":function_binding_android", ":function_overload_android", "//:auto_value", "//common/annotations", + "//common/exceptions:overload_not_found", "@maven//:com_google_errorprone_error_prone_annotations", "@maven_android//:com_google_guava_guava", ], diff --git a/runtime/src/main/java/dev/cel/runtime/CelFunctionBinding.java b/runtime/src/main/java/dev/cel/runtime/CelFunctionBinding.java index 06e5facdf..88be0d3c3 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelFunctionBinding.java +++ b/runtime/src/main/java/dev/cel/runtime/CelFunctionBinding.java @@ -51,13 +51,13 @@ public interface CelFunctionBinding { boolean isStrict(); /** Create a unary function binding from the {@code overloadId}, {@code arg}, and {@code impl}. */ - @SuppressWarnings("unchecked") + @SuppressWarnings("unchecked") // Safe from CelFunctionOverload.canHandle check before invocation static CelFunctionBinding from( String overloadId, Class arg, CelFunctionOverload.Unary impl) { return from( overloadId, ImmutableList.of(arg), - new CelFunctionOverload() { + new OptimizedFunctionOverload() { @Override public Object apply(Object[] args) throws CelEvaluationException { return impl.apply((T) args[0]); @@ -74,13 +74,13 @@ public Object apply(Object arg1) throws CelEvaluationException { * Create a binary function binding from the {@code overloadId}, {@code arg1}, {@code arg2}, and * {@code impl}. */ - @SuppressWarnings("unchecked") + @SuppressWarnings("unchecked") // Safe from CelFunctionOverload.canHandle check before invocation static CelFunctionBinding from( String overloadId, Class arg1, Class arg2, CelFunctionOverload.Binary impl) { return from( overloadId, ImmutableList.of(arg1, arg2), - new CelFunctionOverload() { + new OptimizedFunctionOverload() { @Override public Object apply(Object[] args) throws CelEvaluationException { return impl.apply((T1) args[0], (T2) args[1]); diff --git a/runtime/src/main/java/dev/cel/runtime/CelFunctionOverload.java b/runtime/src/main/java/dev/cel/runtime/CelFunctionOverload.java index e1bdbf886..c5f75096d 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelFunctionOverload.java +++ b/runtime/src/main/java/dev/cel/runtime/CelFunctionOverload.java @@ -26,15 +26,6 @@ public interface CelFunctionOverload { /** Evaluate a set of arguments throwing a {@code CelException} on error. */ Object apply(Object[] args) throws CelEvaluationException; - /** Fast-path for unary function execution to avoid Object[] allocation. */ - default Object apply(Object arg) throws CelEvaluationException { - return apply(new Object[] {arg}); - } - - /** Fast-path for binary function execution to avoid Object[] allocation. */ - default Object apply(Object arg1, Object arg2) throws CelEvaluationException { - return apply(new Object[] {arg1, arg2}); - } /** * Helper interface for describing unary functions where the type-parameter is used to improve diff --git a/runtime/src/main/java/dev/cel/runtime/CelLateFunctionBindings.java b/runtime/src/main/java/dev/cel/runtime/CelLateFunctionBindings.java index c1f4b236f..3d75845cf 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelLateFunctionBindings.java +++ b/runtime/src/main/java/dev/cel/runtime/CelLateFunctionBindings.java @@ -65,7 +65,7 @@ public static CelLateFunctionBindings from(Collection functi private static CelResolvedOverload createResolvedOverload(CelFunctionBinding binding) { return CelResolvedOverload.of( binding.getOverloadId(), - (args) -> binding.getDefinition().apply(args), + binding.getDefinition(), binding.isStrict(), binding.getArgTypes()); } diff --git a/runtime/src/main/java/dev/cel/runtime/CelResolvedOverload.java b/runtime/src/main/java/dev/cel/runtime/CelResolvedOverload.java index 2bcdf3a2d..7063720a1 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelResolvedOverload.java +++ b/runtime/src/main/java/dev/cel/runtime/CelResolvedOverload.java @@ -18,6 +18,7 @@ import com.google.common.collect.ImmutableList; import com.google.errorprone.annotations.Immutable; import dev.cel.common.annotations.Internal; +import dev.cel.common.exceptions.CelOverloadNotFoundException; import java.util.List; /** @@ -52,6 +53,33 @@ public abstract class CelResolvedOverload { /** The function definition. */ public abstract CelFunctionOverload getDefinition(); + abstract OptimizedFunctionOverload getOptimizedDefinition(); + + public Object invoke(Object[] args) throws CelEvaluationException { + // Note: canHandle check is handled separately in DynamicDispatchOverload + if (isDynamicDispatch() + || CelFunctionOverload.canHandle(args, getParameterTypes(), isStrict())) { + return getDefinition().apply(args); + } + throw new CelOverloadNotFoundException(getOverloadId()); + } + + public Object invoke(Object arg) throws CelEvaluationException { + if (isDynamicDispatch() + || CelFunctionOverload.canHandle(arg, getParameterTypes(), isStrict())) { + return getOptimizedDefinition().apply(arg); + } + throw new CelOverloadNotFoundException(getOverloadId()); + } + + public Object invoke(Object arg1, Object arg2) throws CelEvaluationException { + if (isDynamicDispatch() + || CelFunctionOverload.canHandle(arg1, arg2, getParameterTypes(), isStrict())) { + return getOptimizedDefinition().apply(arg1, arg2); + } + throw new CelOverloadNotFoundException(getOverloadId()); + } + /** * Creates a new resolved overload from the given overload id, parameter types, and definition. */ @@ -71,8 +99,12 @@ public static CelResolvedOverload of( CelFunctionOverload definition, boolean isStrict, List> parameterTypes) { + OptimizedFunctionOverload optimizedDef = + (definition instanceof OptimizedFunctionOverload) + ? (OptimizedFunctionOverload) definition + : definition::apply; return new AutoValue_CelResolvedOverload( - overloadId, ImmutableList.copyOf(parameterTypes), isStrict, definition); + overloadId, ImmutableList.copyOf(parameterTypes), isStrict, definition, optimizedDef); } /** @@ -81,4 +113,8 @@ public static CelResolvedOverload of( boolean canHandle(Object[] arguments) { return CelFunctionOverload.canHandle(arguments, getParameterTypes(), isStrict()); } + + private boolean isDynamicDispatch() { + return getDefinition() instanceof FunctionBindingImpl.DynamicDispatchOverload; + } } diff --git a/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java b/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java index 87cb07945..d6ddf3965 100644 --- a/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java +++ b/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java @@ -26,7 +26,6 @@ import com.google.errorprone.annotations.Immutable; import dev.cel.common.CelErrorCode; import dev.cel.common.annotations.Internal; -import dev.cel.common.exceptions.CelOverloadNotFoundException; import dev.cel.runtime.FunctionBindingImpl.DynamicDispatchOverload; import java.util.ArrayList; import java.util.Collection; @@ -202,46 +201,10 @@ public DefaultDispatcher build() { OverloadEntry overloadEntry = entry.getValue(); CelFunctionOverload overloadImpl = overloadEntry.overload(); - CelFunctionOverload guardedApply; - if (overloadImpl instanceof DynamicDispatchOverload) { - // Dynamic dispatcher already does its own internal canHandle checks - guardedApply = overloadImpl; - } else { - boolean isStrict = overloadEntry.isStrict(); - ImmutableList> argTypes = overloadEntry.argTypes(); - - guardedApply = - new CelFunctionOverload() { - @Override - public Object apply(Object[] args) throws CelEvaluationException { - if (CelFunctionOverload.canHandle(args, argTypes, isStrict)) { - return overloadImpl.apply(args); - } - throw new CelOverloadNotFoundException(overloadId); - } - - @Override - public Object apply(Object arg) throws CelEvaluationException { - if (CelFunctionOverload.canHandle(arg, argTypes, isStrict)) { - return overloadImpl.apply(arg); - } - throw new CelOverloadNotFoundException(overloadId); - } - - @Override - public Object apply(Object arg1, Object arg2) throws CelEvaluationException { - if (CelFunctionOverload.canHandle(arg1, arg2, argTypes, isStrict)) { - return overloadImpl.apply(arg1, arg2); - } - throw new CelOverloadNotFoundException(overloadId); - } - }; - } - resolvedOverloads.put( overloadId, CelResolvedOverload.of( - overloadId, guardedApply, overloadEntry.isStrict(), overloadEntry.argTypes())); + overloadId, overloadImpl, overloadEntry.isStrict(), overloadEntry.argTypes())); } return new DefaultDispatcher(resolvedOverloads.buildOrThrow()); diff --git a/runtime/src/main/java/dev/cel/runtime/FunctionBindingImpl.java b/runtime/src/main/java/dev/cel/runtime/FunctionBindingImpl.java index 1f47f1dfd..c1306ce19 100644 --- a/runtime/src/main/java/dev/cel/runtime/FunctionBindingImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/FunctionBindingImpl.java @@ -126,7 +126,7 @@ private DynamicDispatchBinding( } @Immutable - static final class DynamicDispatchOverload implements CelFunctionOverload { + static final class DynamicDispatchOverload implements OptimizedFunctionOverload { private final String functionName; private final ImmutableSet overloadBindings; @@ -149,7 +149,8 @@ public Object apply(Object[] args) throws CelEvaluationException { public Object apply(Object arg) throws CelEvaluationException { for (CelFunctionBinding overload : overloadBindings) { if (CelFunctionOverload.canHandle(arg, overload.getArgTypes(), overload.isStrict())) { - return overload.getDefinition().apply(arg); + OptimizedFunctionOverload def = (OptimizedFunctionOverload) overload.getDefinition(); + return def.apply(arg); } } throw new CelOverloadNotFoundException( @@ -164,7 +165,8 @@ public Object apply(Object arg1, Object arg2) throws CelEvaluationException { for (CelFunctionBinding overload : overloadBindings) { if (CelFunctionOverload.canHandle( arg1, arg2, overload.getArgTypes(), overload.isStrict())) { - return overload.getDefinition().apply(arg1, arg2); + OptimizedFunctionOverload def = (OptimizedFunctionOverload) overload.getDefinition(); + return def.apply(arg1, arg2); } } throw new CelOverloadNotFoundException( diff --git a/runtime/src/main/java/dev/cel/runtime/OptimizedFunctionOverload.java b/runtime/src/main/java/dev/cel/runtime/OptimizedFunctionOverload.java new file mode 100644 index 000000000..fde8bcc15 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/OptimizedFunctionOverload.java @@ -0,0 +1,35 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dev.cel.runtime; + +import com.google.errorprone.annotations.Immutable; + +/** + * Internal interface to support fast-path Unary and Binary evaluations, avoiding Object[] + * allocation. + */ +@Immutable +interface OptimizedFunctionOverload extends CelFunctionOverload { + + /** Fast-path for unary function execution to avoid Object[] allocation. */ + default Object apply(Object arg) throws CelEvaluationException { + return apply(new Object[] {arg}); + } + + /** Fast-path for binary function execution to avoid Object[] allocation. */ + default Object apply(Object arg1, Object arg2) throws CelEvaluationException { + return apply(new Object[] {arg1, arg2}); + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java index 5c1dd80b3..a30f91880 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java @@ -59,7 +59,7 @@ static Object dispatch( CelResolvedOverload overload, CelValueConverter valueConverter, Object[] args) throws CelEvaluationException { try { - Object result = overload.getDefinition().apply(args); + Object result = overload.invoke(args); return valueConverter.maybeUnwrap(valueConverter.toRuntimeValue(result)); } catch (RuntimeException e) { throw handleDispatchException(e, overload, args); @@ -69,7 +69,7 @@ static Object dispatch( static Object dispatch(CelResolvedOverload overload, CelValueConverter valueConverter, Object arg) throws CelEvaluationException { try { - Object result = overload.getDefinition().apply(arg); + Object result = overload.invoke(arg); return valueConverter.maybeUnwrap(valueConverter.toRuntimeValue(result)); } catch (RuntimeException e) { throw handleDispatchException(e, overload, arg); @@ -80,7 +80,7 @@ static Object dispatch( CelResolvedOverload overload, CelValueConverter valueConverter, Object arg1, Object arg2) throws CelEvaluationException { try { - Object result = overload.getDefinition().apply(arg1, arg2); + Object result = overload.invoke(arg1, arg2); return valueConverter.maybeUnwrap(valueConverter.toRuntimeValue(result)); } catch (RuntimeException e) { throw handleDispatchException(e, overload, arg1, arg2);