This is an automated email from the ASF dual-hosted git repository.

clintropolis pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/druid.git


The following commit(s) were added to refs/heads/master by this push:
     new e61fddc3dbe feat: add simd add/sub/mul expressions (#19512)
e61fddc3dbe is described below

commit e61fddc3dbe1af432b60f6bf30afd4187cb4e539
Author: Clint Wylie <[email protected]>
AuthorDate: Mon Jun 1 14:06:15 2026 -0700

    feat: add simd add/sub/mul expressions (#19512)
---
 benchmarks/pom.xml                                 | 20 +++++
 .../benchmark/query/SqlExpressionBenchmark.java    | 16 ++++
 docs/operations/java.md                            |  6 +-
 examples/bin/run-java                              |  1 +
 .../druid/indexing/overlord/ForkingTaskRunner.java |  3 +-
 pom.xml                                            | 15 ++++
 .../druid/math/expr/ExpressionProcessing.java      | 22 ++++-
 .../math/expr/ExpressionProcessingConfig.java      | 13 ++-
 .../SimpleVectorMathBivariateProcessorFactory.java | 55 ++++++++++++
 .../math/expr/vector/VectorMathProcessors.java     | 14 +++-
 .../vector/simd/SimdDoubleDoubleAddProcessor.java  | 93 ++++++++++++++++++++
 .../vector/simd/SimdDoubleDoubleMulProcessor.java  | 93 ++++++++++++++++++++
 .../vector/simd/SimdDoubleDoubleProcessor.java     | 95 +++++++++++++++++++++
 .../vector/simd/SimdDoubleDoubleSubProcessor.java  | 93 ++++++++++++++++++++
 .../vector/simd/SimdDoubleLongAddProcessor.java    | 96 +++++++++++++++++++++
 .../vector/simd/SimdDoubleLongMulProcessor.java    | 96 +++++++++++++++++++++
 .../expr/vector/simd/SimdDoubleLongProcessor.java  | 98 ++++++++++++++++++++++
 .../vector/simd/SimdDoubleLongSubProcessor.java    | 96 +++++++++++++++++++++
 .../vector/simd/SimdLongDoubleAddProcessor.java    | 96 +++++++++++++++++++++
 .../vector/simd/SimdLongDoubleMulProcessor.java    | 96 +++++++++++++++++++++
 .../expr/vector/simd/SimdLongDoubleProcessor.java  | 98 ++++++++++++++++++++++
 .../vector/simd/SimdLongDoubleSubProcessor.java    | 96 +++++++++++++++++++++
 .../expr/vector/simd/SimdLongLongAddProcessor.java | 93 ++++++++++++++++++++
 .../expr/vector/simd/SimdLongLongMulProcessor.java | 93 ++++++++++++++++++++
 .../expr/vector/simd/SimdLongLongProcessor.java    | 96 +++++++++++++++++++++
 .../expr/vector/simd/SimdLongLongSubProcessor.java | 93 ++++++++++++++++++++
 .../math/expr/vector/simd/SimdProcessors.java      | 98 ++++++++++++++++++++++
 .../expr/vector/simd/SimdSupportedBinaryOp.java    | 36 ++++++++
 .../math/expr/VectorExprResultConsistencyTest.java | 37 ++++----
 .../VectorExprResultConsistencyVectorApiTest.java  | 42 ++++++++++
 30 files changed, 1875 insertions(+), 24 deletions(-)

diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml
index 429b9323777..c02c2166f11 100644
--- a/benchmarks/pom.xml
+++ b/benchmarks/pom.xml
@@ -244,6 +244,26 @@
 
   <build>
     <plugins>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>3.14.1</version>
+        <inherited>true</inherited>
+        <configuration>
+          <release>${maven.compiler.release}</release>
+          <annotationProcessors>
+            
<annotationProcessor>org.openjdk.jmh.generators.BenchmarkProcessor</annotationProcessor>
+          </annotationProcessors>
+          <annotationProcessorPaths combine.children="append">
+            <path>
+              <groupId>org.openjdk.jmh</groupId>
+              <artifactId>jmh-generator-annprocess</artifactId>
+              <version>${jmh.version}</version>
+            </path>
+          </annotationProcessorPaths>
+        </configuration>
+      </plugin>
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-assembly-plugin</artifactId>
diff --git 
a/benchmarks/src/test/java/org/apache/druid/benchmark/query/SqlExpressionBenchmark.java
 
b/benchmarks/src/test/java/org/apache/druid/benchmark/query/SqlExpressionBenchmark.java
index 8988973f982..0ef6395a1fc 100644
--- 
a/benchmarks/src/test/java/org/apache/druid/benchmark/query/SqlExpressionBenchmark.java
+++ 
b/benchmarks/src/test/java/org/apache/druid/benchmark/query/SqlExpressionBenchmark.java
@@ -21,12 +21,15 @@ package org.apache.druid.benchmark.query;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import org.apache.druid.math.expr.ExpressionProcessing;
 import org.apache.druid.query.QueryContexts;
 import org.apache.druid.query.groupby.GroupByQueryConfig;
 import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Level;
 import org.openjdk.jmh.annotations.Measurement;
 import org.openjdk.jmh.annotations.Param;
 import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
 import org.openjdk.jmh.annotations.State;
 import org.openjdk.jmh.annotations.Warmup;
 
@@ -171,6 +174,9 @@ public class SqlExpressionBenchmark extends 
SqlBaseQueryBenchmark
   })
   private String deferExpressionDimensions;
 
+  @Param({"false", "true"})
+  private boolean useVectorApi;
+
   @Param({
       // non-expression reference
       "0",
@@ -238,6 +244,16 @@ public class SqlExpressionBenchmark extends 
SqlBaseQueryBenchmark
   })
   private String query;
 
+  @Setup(Level.Trial)
+  public void setupExpressionProcessing()
+  {
+    if (useVectorApi) {
+      ExpressionProcessing.initializeForVectorApiTests();
+    } else {
+      ExpressionProcessing.initializeForTests();
+    }
+  }
+
   @Override
   public String getQuery()
   {
diff --git a/docs/operations/java.md b/docs/operations/java.md
index f4a8c029db2..c6117e1f426 100644
--- a/docs/operations/java.md
+++ b/docs/operations/java.md
@@ -85,5 +85,9 @@ added. There are many ways of doing this. Choose the one that 
works best for you
 --add-opens=java.base/jdk.internal.ref=ALL-UNNAMED \
 --add-opens=java.base/java.io=ALL-UNNAMED \
 --add-opens=java.base/java.lang=ALL-UNNAMED \
---add-opens=jdk.management/com.sun.management.internal=ALL-UNNAMED
+--add-opens=jdk.management/com.sun.management.internal=ALL-UNNAMED \
+--add-modules=jdk.incubator.vector
 ```
+
+The `--add-modules=jdk.incubator.vector` flag is optional, but adding it makes 
the JDK's incubator Vector API available
+to Druid to support `druid.expressions.useVectorApi=true`.
diff --git a/examples/bin/run-java b/examples/bin/run-java
index 80190d0a793..5a30cd54fbd 100755
--- a/examples/bin/run-java
+++ b/examples/bin/run-java
@@ -43,6 +43,7 @@ then
     --add-opens=java.base/java.io=ALL-UNNAMED \
     --add-opens=java.base/java.lang=ALL-UNNAMED \
     --add-opens=jdk.management/com.sun.management.internal=ALL-UNNAMED \
+    --add-modules=jdk.incubator.vector \
     "$@"
 else
   exec "$JAVA_BIN" "$@"
diff --git 
a/indexing-service/src/main/java/org/apache/druid/indexing/overlord/ForkingTaskRunner.java
 
b/indexing-service/src/main/java/org/apache/druid/indexing/overlord/ForkingTaskRunner.java
index fb09cb5f154..52c2dcc7ba3 100644
--- 
a/indexing-service/src/main/java/org/apache/druid/indexing/overlord/ForkingTaskRunner.java
+++ 
b/indexing-service/src/main/java/org/apache/druid/indexing/overlord/ForkingTaskRunner.java
@@ -115,7 +115,8 @@ public class ForkingTaskRunner
       "--add-opens=java.base/jdk.internal.ref=ALL-UNNAMED",
       "--add-opens=java.base/java.io=ALL-UNNAMED",
       "--add-opens=java.base/java.lang=ALL-UNNAMED",
-      "--add-opens=jdk.management/com.sun.management.internal=ALL-UNNAMED"
+      "--add-opens=jdk.management/com.sun.management.internal=ALL-UNNAMED",
+      "--add-modules=jdk.incubator.vector"
   );
 
   private final ForkingTaskRunnerConfig config;
diff --git a/pom.xml b/pom.xml
index d871b7cdb28..dfd23d67061 100644
--- a/pom.xml
+++ b/pom.xml
@@ -164,6 +164,9 @@
 
             <!-- required for certain EqualsVerifier tests (not required in 
production) -->
             --add-opens=java.base/java.util=ALL-UNNAMED
+
+            <!-- required for SIMD for druid.expressions.useVectorApi -->
+            --add-modules=jdk.incubator.vector
         </jdk.strong.encapsulation.argLine>
         <jdk.security.manager.allow.argLine><!-- empty placeholder 
--></jdk.security.manager.allow.argLine>
         <repoOrgId>maven.org</repoOrgId>
@@ -1794,6 +1797,8 @@
                       <exclude>**/*_jmhType_*.class</exclude>
                       <exclude>**/*_jmhTest_*.class</exclude>
                       <exclude>**/*_generated*.class</exclude>
+                      <!-- forbidden-apis can't resolve jdk.incubator.vector 
classes from its own classpath -->
+                      <exclude>**/math/expr/vector/simd/Simd*.class</exclude>
                     </excludes>
                     <suppressAnnotations>
                         <annotation>**.SuppressForbidden</annotation>
@@ -2143,6 +2148,11 @@
 
                         <!-- HadoopFsWrapper javadocs cannot be generated due 
to missing annotations -->
                         
<excludePackageNames>org.apache.hadoop.fs</excludePackageNames>
+
+                        <!-- required for SIMD expression vector processors 
that import jdk.incubator.vector -->
+                        <additionalOptions>
+                            
<additionalOption>--add-modules=jdk.incubator.vector</additionalOption>
+                        </additionalOptions>
                     </configuration>
                 </plugin>
                 <plugin>
@@ -2152,6 +2162,9 @@
                     <inherited>true</inherited>
                     <configuration>
                         <release>${maven.compiler.release}</release>
+                        <compilerArgs>
+                            <arg>--add-modules=jdk.incubator.vector</arg>
+                        </compilerArgs>
                     </configuration>
                 </plugin>
               <plugin>
@@ -2212,6 +2225,8 @@
                                 
<arg>-J--add-exports=java.base/sun.nio.ch=ALL-UNNAMED</arg>
                                 
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED</arg>
                                 
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED</arg>
+                                <arg>-J--add-modules=jdk.incubator.vector</arg>
+                                <arg>--add-modules=jdk.incubator.vector</arg>
                             </compilerArgs>
                               <annotationProcessorPaths>
                                 <path>
diff --git 
a/processing/src/main/java/org/apache/druid/math/expr/ExpressionProcessing.java 
b/processing/src/main/java/org/apache/druid/math/expr/ExpressionProcessing.java
index 7b2e1d26ae4..68b5cef8cc1 100644
--- 
a/processing/src/main/java/org/apache/druid/math/expr/ExpressionProcessing.java
+++ 
b/processing/src/main/java/org/apache/druid/math/expr/ExpressionProcessing.java
@@ -45,13 +45,19 @@ public class ExpressionProcessing
   @VisibleForTesting
   public static void initializeForTests()
   {
-    INSTANCE = new ExpressionProcessingConfig(null, null, null);
+    INSTANCE = new ExpressionProcessingConfig(null, null, null, null);
   }
 
   @VisibleForTesting
   public static void initializeForHomogenizeNullMultiValueStrings()
   {
-    INSTANCE = new ExpressionProcessingConfig(null, true, null);
+    INSTANCE = new ExpressionProcessingConfig(null, true, null, null);
+  }
+
+  @VisibleForTesting
+  public static void initializeForVectorApiTests()
+  {
+    INSTANCE = new ExpressionProcessingConfig(null, null, null, true);
   }
 
   /**
@@ -81,6 +87,18 @@ public class ExpressionProcessing
     return INSTANCE.allowVectorizeFallback();
   }
 
+  /**
+   * Whether {@link org.apache.druid.math.expr.vector.ExprVectorProcessor} 
implementations may dispatch to specialized
+   * {@code jdk.incubator.vector} (SIMD) variants for supported math 
operations. Off by default; opt-in via
+   * {@link ExpressionProcessingConfig#USE_VECTOR_API}. Requires the JVM to be 
started with
+   * {@code --add-modules=jdk.incubator.vector}, which Druid already adds to 
its standard launch arguments.
+   */
+  public static boolean useVectorApi()
+  {
+    checkInitialized();
+    return INSTANCE.useVectorApi();
+  }
+
   private static void checkInitialized()
   {
     // this should only be null in a unit test context, in production this 
will be injected by the null handling module
diff --git 
a/processing/src/main/java/org/apache/druid/math/expr/ExpressionProcessingConfig.java
 
b/processing/src/main/java/org/apache/druid/math/expr/ExpressionProcessingConfig.java
index 78c2ecfcbbc..d24600a302a 100644
--- 
a/processing/src/main/java/org/apache/druid/math/expr/ExpressionProcessingConfig.java
+++ 
b/processing/src/main/java/org/apache/druid/math/expr/ExpressionProcessingConfig.java
@@ -34,6 +34,7 @@ public class ExpressionProcessingConfig
   public static final String HOMOGENIZE_NULL_MULTIVALUE_STRING_ARRAYS =
       "druid.expressions.homogenizeNullMultiValueStringArrays";
   public static final String ALLOW_VECTORIZE_FALLBACK = 
"druid.expressions.allowVectorizeFallback";
+  public static final String USE_VECTOR_API = "druid.expressions.useVectorApi";
 
   @JsonProperty("processArraysAsMultiValueStrings")
   private final boolean processArraysAsMultiValueStrings;
@@ -44,11 +45,15 @@ public class ExpressionProcessingConfig
   @JsonProperty("allowVectorizeFallback")
   private final boolean allowVectorizeFallback;
 
+  @JsonProperty("useVectorApi")
+  private final boolean useVectorApi;
+
   @JsonCreator
   public ExpressionProcessingConfig(
       @JsonProperty("processArraysAsMultiValueStrings") @Nullable Boolean 
processArraysAsMultiValueStrings,
       @JsonProperty("homogenizeNullMultiValueStringArrays") @Nullable Boolean 
homogenizeNullMultiValueStringArrays,
-      @JsonProperty("allowVectorizeFallback") @Nullable Boolean 
allowVectorizeFallback
+      @JsonProperty("allowVectorizeFallback") @Nullable Boolean 
allowVectorizeFallback,
+      @JsonProperty("useVectorApi") @Nullable Boolean useVectorApi
   )
   {
     this.processArraysAsMultiValueStrings = getWithPropertyFallbackFalse(
@@ -64,6 +69,7 @@ public class ExpressionProcessingConfig
         ALLOW_VECTORIZE_FALLBACK,
         "true"
     );
+    this.useVectorApi = getWithPropertyFallbackFalse(useVectorApi, 
USE_VECTOR_API);
   }
 
   public boolean processArraysAsMultiValueStrings()
@@ -81,6 +87,11 @@ public class ExpressionProcessingConfig
     return allowVectorizeFallback;
   }
 
+  public boolean useVectorApi()
+  {
+    return useVectorApi;
+  }
+
   private static boolean getWithPropertyFallbackFalse(@Nullable Boolean value, 
String property)
   {
     return getWithPropertyFallback(value, property, "false");
diff --git 
a/processing/src/main/java/org/apache/druid/math/expr/vector/SimpleVectorMathBivariateProcessorFactory.java
 
b/processing/src/main/java/org/apache/druid/math/expr/vector/SimpleVectorMathBivariateProcessorFactory.java
index 230b2e44f35..deebbeb565f 100644
--- 
a/processing/src/main/java/org/apache/druid/math/expr/vector/SimpleVectorMathBivariateProcessorFactory.java
+++ 
b/processing/src/main/java/org/apache/druid/math/expr/vector/SimpleVectorMathBivariateProcessorFactory.java
@@ -20,10 +20,15 @@
 package org.apache.druid.math.expr.vector;
 
 import org.apache.druid.math.expr.Expr;
+import org.apache.druid.math.expr.ExpressionProcessing;
 import 
org.apache.druid.math.expr.vector.functional.DoubleBivariateDoubleLongFunction;
 import 
org.apache.druid.math.expr.vector.functional.DoubleBivariateDoublesFunction;
 import 
org.apache.druid.math.expr.vector.functional.DoubleBivariateLongDoubleFunction;
 import org.apache.druid.math.expr.vector.functional.LongBivariateLongsFunction;
+import org.apache.druid.math.expr.vector.simd.SimdProcessors;
+import org.apache.druid.math.expr.vector.simd.SimdSupportedBinaryOp;
+
+import javax.annotation.Nullable;
 
 /**
  * Make a 2 argument, math processor with the following type rules
@@ -31,6 +36,10 @@ import 
org.apache.druid.math.expr.vector.functional.LongBivariateLongsFunction;
  * long, double    -> double
  * double, long    -> double
  * double, double  -> double
+ *
+ * If a non-null {@link SimdSupportedBinaryOp} is supplied to the constructor 
and
+ * {@link ExpressionProcessing#useVectorApi()} is true, this factory will 
return SIMD-specialized processors backed
+ * by the JDK incubator {@code jdk.incubator.vector} API instead of the 
standard scalar implementations.
  */
 public class SimpleVectorMathBivariateProcessorFactory extends 
VectorMathBivariateProcessorFactory
 {
@@ -38,6 +47,8 @@ public class SimpleVectorMathBivariateProcessorFactory 
extends VectorMathBivaria
   private final DoubleBivariateLongDoubleFunction longDoubleFunction;
   private final DoubleBivariateDoubleLongFunction doubleLongFunction;
   private final DoubleBivariateDoublesFunction doublesFunction;
+  @Nullable
+  private final SimdSupportedBinaryOp simdOp;
 
   protected SimpleVectorMathBivariateProcessorFactory(
       LongBivariateLongsFunction longsFunction,
@@ -45,16 +56,36 @@ public class SimpleVectorMathBivariateProcessorFactory 
extends VectorMathBivaria
       DoubleBivariateDoubleLongFunction doubleLongFunction,
       DoubleBivariateDoublesFunction doublesFunction
   )
+  {
+    this(longsFunction, longDoubleFunction, doubleLongFunction, 
doublesFunction, null);
+  }
+
+  protected SimpleVectorMathBivariateProcessorFactory(
+      LongBivariateLongsFunction longsFunction,
+      DoubleBivariateLongDoubleFunction longDoubleFunction,
+      DoubleBivariateDoubleLongFunction doubleLongFunction,
+      DoubleBivariateDoublesFunction doublesFunction,
+      @Nullable SimdSupportedBinaryOp simdOp
+  )
   {
     this.longsFunction = longsFunction;
     this.longDoubleFunction = longDoubleFunction;
     this.doubleLongFunction = doubleLongFunction;
     this.doublesFunction = doublesFunction;
+    this.simdOp = simdOp;
   }
 
   @Override
   public final ExprVectorProcessor<long[]> 
longsProcessor(Expr.VectorInputBindingInspector inspector, Expr left, Expr 
right)
   {
+    if (simdOp != null && ExpressionProcessing.useVectorApi()) {
+      return SimdProcessors.makeLongLong(
+          left.asVectorProcessor(inspector),
+          right.asVectorProcessor(inspector),
+          simdOp,
+          longsFunction
+      );
+    }
     return new LongBivariateLongsFunctionVectorProcessor(
         left.asVectorProcessor(inspector),
         right.asVectorProcessor(inspector),
@@ -69,6 +100,14 @@ public class SimpleVectorMathBivariateProcessorFactory 
extends VectorMathBivaria
       Expr right
   )
   {
+    if (simdOp != null && ExpressionProcessing.useVectorApi()) {
+      return SimdProcessors.makeLongDouble(
+          left.asVectorProcessor(inspector),
+          right.asVectorProcessor(inspector),
+          simdOp,
+          longDoubleFunction
+      );
+    }
     return new DoubleBivariateLongDoubleFunctionVectorProcessor(
         left.asVectorProcessor(inspector),
         right.asVectorProcessor(inspector),
@@ -83,6 +122,14 @@ public class SimpleVectorMathBivariateProcessorFactory 
extends VectorMathBivaria
       Expr right
   )
   {
+    if (simdOp != null && ExpressionProcessing.useVectorApi()) {
+      return SimdProcessors.makeDoubleLong(
+          left.asVectorProcessor(inspector),
+          right.asVectorProcessor(inspector),
+          simdOp,
+          doubleLongFunction
+      );
+    }
     return new DoubleBivariateDoubleLongFunctionVectorProcessor(
         left.asVectorProcessor(inspector),
         right.asVectorProcessor(inspector),
@@ -97,6 +144,14 @@ public class SimpleVectorMathBivariateProcessorFactory 
extends VectorMathBivaria
       Expr right
   )
   {
+    if (simdOp != null && ExpressionProcessing.useVectorApi()) {
+      return SimdProcessors.makeDoubleDouble(
+          left.asVectorProcessor(inspector),
+          right.asVectorProcessor(inspector),
+          simdOp,
+          doublesFunction
+      );
+    }
     return new DoubleBivariateDoublesFunctionVectorProcessor(
         left.asVectorProcessor(inspector),
         right.asVectorProcessor(inspector),
diff --git 
a/processing/src/main/java/org/apache/druid/math/expr/vector/VectorMathProcessors.java
 
b/processing/src/main/java/org/apache/druid/math/expr/vector/VectorMathProcessors.java
index c12ebc55eaa..4a26f814153 100644
--- 
a/processing/src/main/java/org/apache/druid/math/expr/vector/VectorMathProcessors.java
+++ 
b/processing/src/main/java/org/apache/druid/math/expr/vector/VectorMathProcessors.java
@@ -23,6 +23,7 @@ import com.google.common.math.LongMath;
 import com.google.common.primitives.Ints;
 import org.apache.druid.math.expr.ExpressionValidationException;
 import org.apache.druid.math.expr.Function;
+import org.apache.druid.math.expr.vector.simd.SimdSupportedBinaryOp;
 
 public class VectorMathProcessors
 {
@@ -300,7 +301,7 @@ public class VectorMathProcessors
 
     public Add()
     {
-      super(Long::sum, Double::sum, Double::sum, Double::sum);
+      super(Long::sum, Double::sum, Double::sum, Double::sum, 
SimdSupportedBinaryOp.ADD);
     }
   }
 
@@ -314,7 +315,8 @@ public class VectorMathProcessors
           (left, right) -> left - right,
           (left, right) -> (double) left - right,
           (left, right) -> left - (double) right,
-          (left, right) -> left - right
+          (left, right) -> left - right,
+          SimdSupportedBinaryOp.SUB
       );
     }
   }
@@ -325,7 +327,13 @@ public class VectorMathProcessors
 
     public Multiply()
     {
-      super(Multiply::multiply, Multiply::multiply, Multiply::multiply, 
Multiply::multiply);
+      super(
+          Multiply::multiply,
+          Multiply::multiply,
+          Multiply::multiply,
+          Multiply::multiply,
+          SimdSupportedBinaryOp.MUL
+      );
     }
 
     private static long multiply(long x, long y)
diff --git 
a/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdDoubleDoubleAddProcessor.java
 
b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdDoubleDoubleAddProcessor.java
new file mode 100644
index 00000000000..d476468cb07
--- /dev/null
+++ 
b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdDoubleDoubleAddProcessor.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ *
+ *   http://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 org.apache.druid.math.expr.vector.simd;
+
+import jdk.incubator.vector.DoubleVector;
+import jdk.incubator.vector.VectorMask;
+import org.apache.druid.math.expr.vector.ExprVectorProcessor;
+import 
org.apache.druid.math.expr.vector.functional.DoubleBivariateDoublesFunction;
+
+import java.util.Arrays;
+
+/**
+ * SIMD specialization of {@code (double[], double[]) -> double[]} addition. 
The op is hardcoded to
+ * {@link DoubleVector#add} so the JIT statically resolves it to the 
platform's double-add intrinsic.
+ */
+public final class SimdDoubleDoubleAddProcessor extends 
SimdDoubleDoubleProcessor
+{
+  public SimdDoubleDoubleAddProcessor(
+      ExprVectorProcessor<?> left,
+      ExprVectorProcessor<?> right,
+      DoubleBivariateDoublesFunction scalarFallback
+  )
+  {
+    super(left, right, scalarFallback);
+  }
+
+  @Override
+  protected void processVector(
+      double[] leftInput,
+      double[] rightInput,
+      boolean[] leftNulls,
+      boolean[] rightNulls,
+      int currentSize
+  )
+  {
+    final boolean hasLeftNulls = leftNulls != null;
+    final boolean hasRightNulls = rightNulls != null;
+    final int laneCount = SPECIES.length();
+    final int upperBound = SPECIES.loopBound(currentSize);
+    int i = 0;
+    if (!hasLeftNulls && !hasRightNulls) {
+      for (; i < upperBound; i += laneCount) {
+        final DoubleVector va = DoubleVector.fromArray(SPECIES, leftInput, i);
+        final DoubleVector vb = DoubleVector.fromArray(SPECIES, rightInput, i);
+        va.add(vb).intoArray(outValues, i);
+      }
+      for (; i < currentSize; i++) {
+        outValues[i] = scalarFallback.process(leftInput[i], rightInput[i]);
+      }
+      Arrays.fill(outNulls, 0, currentSize, false);
+    } else {
+      for (; i < upperBound; i += laneCount) {
+        final VectorMask<Double> nm;
+        if (hasLeftNulls && hasRightNulls) {
+          nm = VectorMask.fromArray(SPECIES, leftNulls, i)
+                         .or(VectorMask.fromArray(SPECIES, rightNulls, i));
+        } else if (hasLeftNulls) {
+          nm = VectorMask.fromArray(SPECIES, leftNulls, i);
+        } else {
+          nm = VectorMask.fromArray(SPECIES, rightNulls, i);
+        }
+        final DoubleVector va = DoubleVector.fromArray(SPECIES, leftInput, i);
+        final DoubleVector vb = DoubleVector.fromArray(SPECIES, rightInput, i);
+        va.add(vb).intoArray(outValues, i);
+        nm.intoArray(outNulls, i);
+      }
+      for (; i < currentSize; i++) {
+        final boolean isNull = (hasLeftNulls && leftNulls[i]) || 
(hasRightNulls && rightNulls[i]);
+        outNulls[i] = isNull;
+        if (!isNull) {
+          outValues[i] = scalarFallback.process(leftInput[i], rightInput[i]);
+        }
+      }
+    }
+  }
+}
diff --git 
a/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdDoubleDoubleMulProcessor.java
 
b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdDoubleDoubleMulProcessor.java
new file mode 100644
index 00000000000..56cf53e3309
--- /dev/null
+++ 
b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdDoubleDoubleMulProcessor.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ *
+ *   http://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 org.apache.druid.math.expr.vector.simd;
+
+import jdk.incubator.vector.DoubleVector;
+import jdk.incubator.vector.VectorMask;
+import org.apache.druid.math.expr.vector.ExprVectorProcessor;
+import 
org.apache.druid.math.expr.vector.functional.DoubleBivariateDoublesFunction;
+
+import java.util.Arrays;
+
+/**
+ * SIMD specialization of {@code (double[], double[]) -> double[]} 
multiplication. The op is hardcoded to
+ * {@link DoubleVector#mul} so the JIT statically resolves it to the 
platform's double-multiply intrinsic.
+ */
+public final class SimdDoubleDoubleMulProcessor extends 
SimdDoubleDoubleProcessor
+{
+  public SimdDoubleDoubleMulProcessor(
+      ExprVectorProcessor<?> left,
+      ExprVectorProcessor<?> right,
+      DoubleBivariateDoublesFunction scalarFallback
+  )
+  {
+    super(left, right, scalarFallback);
+  }
+
+  @Override
+  protected void processVector(
+      double[] leftInput,
+      double[] rightInput,
+      boolean[] leftNulls,
+      boolean[] rightNulls,
+      int currentSize
+  )
+  {
+    final boolean hasLeftNulls = leftNulls != null;
+    final boolean hasRightNulls = rightNulls != null;
+    final int laneCount = SPECIES.length();
+    final int upperBound = SPECIES.loopBound(currentSize);
+    int i = 0;
+    if (!hasLeftNulls && !hasRightNulls) {
+      for (; i < upperBound; i += laneCount) {
+        final DoubleVector va = DoubleVector.fromArray(SPECIES, leftInput, i);
+        final DoubleVector vb = DoubleVector.fromArray(SPECIES, rightInput, i);
+        va.mul(vb).intoArray(outValues, i);
+      }
+      for (; i < currentSize; i++) {
+        outValues[i] = scalarFallback.process(leftInput[i], rightInput[i]);
+      }
+      Arrays.fill(outNulls, 0, currentSize, false);
+    } else {
+      for (; i < upperBound; i += laneCount) {
+        final VectorMask<Double> nm;
+        if (hasLeftNulls && hasRightNulls) {
+          nm = VectorMask.fromArray(SPECIES, leftNulls, i)
+                         .or(VectorMask.fromArray(SPECIES, rightNulls, i));
+        } else if (hasLeftNulls) {
+          nm = VectorMask.fromArray(SPECIES, leftNulls, i);
+        } else {
+          nm = VectorMask.fromArray(SPECIES, rightNulls, i);
+        }
+        final DoubleVector va = DoubleVector.fromArray(SPECIES, leftInput, i);
+        final DoubleVector vb = DoubleVector.fromArray(SPECIES, rightInput, i);
+        va.mul(vb).intoArray(outValues, i);
+        nm.intoArray(outNulls, i);
+      }
+      for (; i < currentSize; i++) {
+        final boolean isNull = (hasLeftNulls && leftNulls[i]) || 
(hasRightNulls && rightNulls[i]);
+        outNulls[i] = isNull;
+        if (!isNull) {
+          outValues[i] = scalarFallback.process(leftInput[i], rightInput[i]);
+        }
+      }
+    }
+  }
+}
diff --git 
a/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdDoubleDoubleProcessor.java
 
b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdDoubleDoubleProcessor.java
new file mode 100644
index 00000000000..8f3eeebac2c
--- /dev/null
+++ 
b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdDoubleDoubleProcessor.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ *
+ *   http://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 org.apache.druid.math.expr.vector.simd;
+
+import jdk.incubator.vector.DoubleVector;
+import jdk.incubator.vector.VectorSpecies;
+import org.apache.druid.math.expr.Expr;
+import org.apache.druid.math.expr.ExpressionType;
+import org.apache.druid.math.expr.vector.CastToTypeVectorProcessor;
+import org.apache.druid.math.expr.vector.ExprEvalDoubleVector;
+import org.apache.druid.math.expr.vector.ExprEvalVector;
+import org.apache.druid.math.expr.vector.ExprVectorProcessor;
+import 
org.apache.druid.math.expr.vector.functional.DoubleBivariateDoublesFunction;
+
+import javax.annotation.Nullable;
+
+/**
+ * Abstract base for SIMD processors that compute {@code (double[], double[]) 
-> double[]} ops. See
+ * {@link SimdLongLongProcessor} for the design rationale.
+ */
+abstract class SimdDoubleDoubleProcessor implements 
ExprVectorProcessor<double[]>
+{
+  static final VectorSpecies<Double> SPECIES = DoubleVector.SPECIES_PREFERRED;
+
+  private final ExprVectorProcessor<double[]> left;
+  private final ExprVectorProcessor<double[]> right;
+  final DoubleBivariateDoublesFunction scalarFallback;
+  final double[] outValues;
+  final boolean[] outNulls;
+
+  protected SimdDoubleDoubleProcessor(
+      ExprVectorProcessor<?> left,
+      ExprVectorProcessor<?> right,
+      DoubleBivariateDoublesFunction scalarFallback
+  )
+  {
+    this.left = CastToTypeVectorProcessor.cast(left, ExpressionType.DOUBLE);
+    this.right = CastToTypeVectorProcessor.cast(right, ExpressionType.DOUBLE);
+    this.scalarFallback = scalarFallback;
+    this.outValues = new double[this.left.maxVectorSize()];
+    this.outNulls = new boolean[this.left.maxVectorSize()];
+  }
+
+  @Override
+  public final ExprEvalVector<double[]> evalVector(Expr.VectorInputBinding 
bindings)
+  {
+    final ExprEvalVector<double[]> lhs = left.evalVector(bindings);
+    final ExprEvalVector<double[]> rhs = right.evalVector(bindings);
+    processVector(
+        lhs.values(),
+        rhs.values(),
+        lhs.getNullVector(),
+        rhs.getNullVector(),
+        bindings.getCurrentVectorSize()
+    );
+    return new ExprEvalDoubleVector(outValues, outNulls);
+  }
+
+  protected abstract void processVector(
+      double[] leftInput,
+      double[] rightInput,
+      @Nullable boolean[] leftNulls,
+      @Nullable boolean[] rightNulls,
+      int currentSize
+  );
+
+  @Override
+  public final ExpressionType getOutputType()
+  {
+    return ExpressionType.DOUBLE;
+  }
+
+  @Override
+  public final int maxVectorSize()
+  {
+    return outValues.length;
+  }
+}
diff --git 
a/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdDoubleDoubleSubProcessor.java
 
b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdDoubleDoubleSubProcessor.java
new file mode 100644
index 00000000000..9f290240bce
--- /dev/null
+++ 
b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdDoubleDoubleSubProcessor.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ *
+ *   http://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 org.apache.druid.math.expr.vector.simd;
+
+import jdk.incubator.vector.DoubleVector;
+import jdk.incubator.vector.VectorMask;
+import org.apache.druid.math.expr.vector.ExprVectorProcessor;
+import 
org.apache.druid.math.expr.vector.functional.DoubleBivariateDoublesFunction;
+
+import java.util.Arrays;
+
+/**
+ * SIMD specialization of {@code (double[], double[]) -> double[]} 
subtraction. The op is hardcoded to
+ * {@link DoubleVector#sub} so the JIT statically resolves it to the 
platform's double-subtract intrinsic.
+ */
+public final class SimdDoubleDoubleSubProcessor extends 
SimdDoubleDoubleProcessor
+{
+  public SimdDoubleDoubleSubProcessor(
+      ExprVectorProcessor<?> left,
+      ExprVectorProcessor<?> right,
+      DoubleBivariateDoublesFunction scalarFallback
+  )
+  {
+    super(left, right, scalarFallback);
+  }
+
+  @Override
+  protected void processVector(
+      double[] leftInput,
+      double[] rightInput,
+      boolean[] leftNulls,
+      boolean[] rightNulls,
+      int currentSize
+  )
+  {
+    final boolean hasLeftNulls = leftNulls != null;
+    final boolean hasRightNulls = rightNulls != null;
+    final int laneCount = SPECIES.length();
+    final int upperBound = SPECIES.loopBound(currentSize);
+    int i = 0;
+    if (!hasLeftNulls && !hasRightNulls) {
+      for (; i < upperBound; i += laneCount) {
+        final DoubleVector va = DoubleVector.fromArray(SPECIES, leftInput, i);
+        final DoubleVector vb = DoubleVector.fromArray(SPECIES, rightInput, i);
+        va.sub(vb).intoArray(outValues, i);
+      }
+      for (; i < currentSize; i++) {
+        outValues[i] = scalarFallback.process(leftInput[i], rightInput[i]);
+      }
+      Arrays.fill(outNulls, 0, currentSize, false);
+    } else {
+      for (; i < upperBound; i += laneCount) {
+        final VectorMask<Double> nm;
+        if (hasLeftNulls && hasRightNulls) {
+          nm = VectorMask.fromArray(SPECIES, leftNulls, i)
+                         .or(VectorMask.fromArray(SPECIES, rightNulls, i));
+        } else if (hasLeftNulls) {
+          nm = VectorMask.fromArray(SPECIES, leftNulls, i);
+        } else {
+          nm = VectorMask.fromArray(SPECIES, rightNulls, i);
+        }
+        final DoubleVector va = DoubleVector.fromArray(SPECIES, leftInput, i);
+        final DoubleVector vb = DoubleVector.fromArray(SPECIES, rightInput, i);
+        va.sub(vb).intoArray(outValues, i);
+        nm.intoArray(outNulls, i);
+      }
+      for (; i < currentSize; i++) {
+        final boolean isNull = (hasLeftNulls && leftNulls[i]) || 
(hasRightNulls && rightNulls[i]);
+        outNulls[i] = isNull;
+        if (!isNull) {
+          outValues[i] = scalarFallback.process(leftInput[i], rightInput[i]);
+        }
+      }
+    }
+  }
+}
diff --git 
a/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdDoubleLongAddProcessor.java
 
b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdDoubleLongAddProcessor.java
new file mode 100644
index 00000000000..5d1eb74f0d9
--- /dev/null
+++ 
b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdDoubleLongAddProcessor.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ *
+ *   http://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 org.apache.druid.math.expr.vector.simd;
+
+import jdk.incubator.vector.DoubleVector;
+import jdk.incubator.vector.LongVector;
+import jdk.incubator.vector.VectorMask;
+import org.apache.druid.math.expr.vector.ExprVectorProcessor;
+import 
org.apache.druid.math.expr.vector.functional.DoubleBivariateDoubleLongFunction;
+
+import java.util.Arrays;
+
+/**
+ * SIMD specialization of {@code (double[], long[]) -> double[]} addition. The 
op is hardcoded to
+ * {@link DoubleVector#add} so the JIT statically resolves it to the 
platform's double-add intrinsic.
+ */
+public final class SimdDoubleLongAddProcessor extends SimdDoubleLongProcessor
+{
+  public SimdDoubleLongAddProcessor(
+      ExprVectorProcessor<?> left,
+      ExprVectorProcessor<?> right,
+      DoubleBivariateDoubleLongFunction scalarFallback
+  )
+  {
+    super(left, right, scalarFallback);
+  }
+
+  @Override
+  protected void processVector(
+      double[] leftInput,
+      long[] rightInput,
+      boolean[] leftNulls,
+      boolean[] rightNulls,
+      int currentSize
+  )
+  {
+    final boolean hasLeftNulls = leftNulls != null;
+    final boolean hasRightNulls = rightNulls != null;
+    final int laneCount = DOUBLE_SPECIES.length();
+    final int upperBound = DOUBLE_SPECIES.loopBound(currentSize);
+    int i = 0;
+    if (!hasLeftNulls && !hasRightNulls) {
+      for (; i < upperBound; i += laneCount) {
+        final DoubleVector va = DoubleVector.fromArray(DOUBLE_SPECIES, 
leftInput, i);
+        final DoubleVector vb =
+            (DoubleVector) LongVector.fromArray(LONG_SPECIES, rightInput, 
i).castShape(DOUBLE_SPECIES, 0);
+        va.add(vb).intoArray(outValues, i);
+      }
+      for (; i < currentSize; i++) {
+        outValues[i] = scalarFallback.process(leftInput[i], rightInput[i]);
+      }
+      Arrays.fill(outNulls, 0, currentSize, false);
+    } else {
+      for (; i < upperBound; i += laneCount) {
+        final VectorMask<Double> nm;
+        if (hasLeftNulls && hasRightNulls) {
+          nm = VectorMask.fromArray(DOUBLE_SPECIES, leftNulls, i)
+                         .or(VectorMask.fromArray(DOUBLE_SPECIES, rightNulls, 
i));
+        } else if (hasLeftNulls) {
+          nm = VectorMask.fromArray(DOUBLE_SPECIES, leftNulls, i);
+        } else {
+          nm = VectorMask.fromArray(DOUBLE_SPECIES, rightNulls, i);
+        }
+        final DoubleVector va = DoubleVector.fromArray(DOUBLE_SPECIES, 
leftInput, i);
+        final DoubleVector vb =
+            (DoubleVector) LongVector.fromArray(LONG_SPECIES, rightInput, 
i).castShape(DOUBLE_SPECIES, 0);
+        va.add(vb).intoArray(outValues, i);
+        nm.intoArray(outNulls, i);
+      }
+      for (; i < currentSize; i++) {
+        final boolean isNull = (hasLeftNulls && leftNulls[i]) || 
(hasRightNulls && rightNulls[i]);
+        outNulls[i] = isNull;
+        if (!isNull) {
+          outValues[i] = scalarFallback.process(leftInput[i], rightInput[i]);
+        }
+      }
+    }
+  }
+}
diff --git 
a/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdDoubleLongMulProcessor.java
 
b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdDoubleLongMulProcessor.java
new file mode 100644
index 00000000000..b593799ee26
--- /dev/null
+++ 
b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdDoubleLongMulProcessor.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ *
+ *   http://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 org.apache.druid.math.expr.vector.simd;
+
+import jdk.incubator.vector.DoubleVector;
+import jdk.incubator.vector.LongVector;
+import jdk.incubator.vector.VectorMask;
+import org.apache.druid.math.expr.vector.ExprVectorProcessor;
+import 
org.apache.druid.math.expr.vector.functional.DoubleBivariateDoubleLongFunction;
+
+import java.util.Arrays;
+
+/**
+ * SIMD specialization of {@code (double[], long[]) -> double[]} 
multiplication. The op is hardcoded to
+ * {@link DoubleVector#mul} so the JIT statically resolves it to the 
platform's double-multiply intrinsic.
+ */
+public final class SimdDoubleLongMulProcessor extends SimdDoubleLongProcessor
+{
+  public SimdDoubleLongMulProcessor(
+      ExprVectorProcessor<?> left,
+      ExprVectorProcessor<?> right,
+      DoubleBivariateDoubleLongFunction scalarFallback
+  )
+  {
+    super(left, right, scalarFallback);
+  }
+
+  @Override
+  protected void processVector(
+      double[] leftInput,
+      long[] rightInput,
+      boolean[] leftNulls,
+      boolean[] rightNulls,
+      int currentSize
+  )
+  {
+    final boolean hasLeftNulls = leftNulls != null;
+    final boolean hasRightNulls = rightNulls != null;
+    final int laneCount = DOUBLE_SPECIES.length();
+    final int upperBound = DOUBLE_SPECIES.loopBound(currentSize);
+    int i = 0;
+    if (!hasLeftNulls && !hasRightNulls) {
+      for (; i < upperBound; i += laneCount) {
+        final DoubleVector va = DoubleVector.fromArray(DOUBLE_SPECIES, 
leftInput, i);
+        final DoubleVector vb =
+            (DoubleVector) LongVector.fromArray(LONG_SPECIES, rightInput, 
i).castShape(DOUBLE_SPECIES, 0);
+        va.mul(vb).intoArray(outValues, i);
+      }
+      for (; i < currentSize; i++) {
+        outValues[i] = scalarFallback.process(leftInput[i], rightInput[i]);
+      }
+      Arrays.fill(outNulls, 0, currentSize, false);
+    } else {
+      for (; i < upperBound; i += laneCount) {
+        final VectorMask<Double> nm;
+        if (hasLeftNulls && hasRightNulls) {
+          nm = VectorMask.fromArray(DOUBLE_SPECIES, leftNulls, i)
+                         .or(VectorMask.fromArray(DOUBLE_SPECIES, rightNulls, 
i));
+        } else if (hasLeftNulls) {
+          nm = VectorMask.fromArray(DOUBLE_SPECIES, leftNulls, i);
+        } else {
+          nm = VectorMask.fromArray(DOUBLE_SPECIES, rightNulls, i);
+        }
+        final DoubleVector va = DoubleVector.fromArray(DOUBLE_SPECIES, 
leftInput, i);
+        final DoubleVector vb =
+            (DoubleVector) LongVector.fromArray(LONG_SPECIES, rightInput, 
i).castShape(DOUBLE_SPECIES, 0);
+        va.mul(vb).intoArray(outValues, i);
+        nm.intoArray(outNulls, i);
+      }
+      for (; i < currentSize; i++) {
+        final boolean isNull = (hasLeftNulls && leftNulls[i]) || 
(hasRightNulls && rightNulls[i]);
+        outNulls[i] = isNull;
+        if (!isNull) {
+          outValues[i] = scalarFallback.process(leftInput[i], rightInput[i]);
+        }
+      }
+    }
+  }
+}
diff --git 
a/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdDoubleLongProcessor.java
 
b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdDoubleLongProcessor.java
new file mode 100644
index 00000000000..e3e705d656a
--- /dev/null
+++ 
b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdDoubleLongProcessor.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ *
+ *   http://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 org.apache.druid.math.expr.vector.simd;
+
+import jdk.incubator.vector.DoubleVector;
+import jdk.incubator.vector.LongVector;
+import jdk.incubator.vector.VectorSpecies;
+import org.apache.druid.math.expr.Expr;
+import org.apache.druid.math.expr.ExpressionType;
+import org.apache.druid.math.expr.vector.CastToTypeVectorProcessor;
+import org.apache.druid.math.expr.vector.ExprEvalDoubleVector;
+import org.apache.druid.math.expr.vector.ExprEvalVector;
+import org.apache.druid.math.expr.vector.ExprVectorProcessor;
+import 
org.apache.druid.math.expr.vector.functional.DoubleBivariateDoubleLongFunction;
+
+import javax.annotation.Nullable;
+
+/**
+ * Abstract base for SIMD processors that compute {@code (double[], long[]) -> 
double[]} ops. The long lane is
+ * widened to {@link DoubleVector} via {@code 
castShape(DoubleVector.SPECIES_PREFERRED, 0)} in each subclass's hot
+ * loop. See {@link SimdLongLongProcessor} for the design rationale.
+ */
+abstract class SimdDoubleLongProcessor implements ExprVectorProcessor<double[]>
+{
+  static final VectorSpecies<Long> LONG_SPECIES = LongVector.SPECIES_PREFERRED;
+  static final VectorSpecies<Double> DOUBLE_SPECIES = 
DoubleVector.SPECIES_PREFERRED;
+
+  private final ExprVectorProcessor<double[]> left;
+  private final ExprVectorProcessor<long[]> right;
+  final DoubleBivariateDoubleLongFunction scalarFallback;
+  final double[] outValues;
+  final boolean[] outNulls;
+
+  protected SimdDoubleLongProcessor(
+      ExprVectorProcessor<?> left,
+      ExprVectorProcessor<?> right,
+      DoubleBivariateDoubleLongFunction scalarFallback
+  )
+  {
+    this.left = CastToTypeVectorProcessor.cast(left, ExpressionType.DOUBLE);
+    this.right = CastToTypeVectorProcessor.cast(right, ExpressionType.LONG);
+    this.scalarFallback = scalarFallback;
+    this.outValues = new double[this.left.maxVectorSize()];
+    this.outNulls = new boolean[this.left.maxVectorSize()];
+  }
+
+  @Override
+  public final ExprEvalVector<double[]> evalVector(Expr.VectorInputBinding 
bindings)
+  {
+    final ExprEvalVector<double[]> lhs = left.evalVector(bindings);
+    final ExprEvalVector<long[]> rhs = right.evalVector(bindings);
+    processVector(
+        lhs.values(),
+        rhs.values(),
+        lhs.getNullVector(),
+        rhs.getNullVector(),
+        bindings.getCurrentVectorSize()
+    );
+    return new ExprEvalDoubleVector(outValues, outNulls);
+  }
+
+  protected abstract void processVector(
+      double[] leftInput,
+      long[] rightInput,
+      @Nullable boolean[] leftNulls,
+      @Nullable boolean[] rightNulls,
+      int currentSize
+  );
+
+  @Override
+  public final ExpressionType getOutputType()
+  {
+    return ExpressionType.DOUBLE;
+  }
+
+  @Override
+  public final int maxVectorSize()
+  {
+    return outValues.length;
+  }
+}
diff --git 
a/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdDoubleLongSubProcessor.java
 
b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdDoubleLongSubProcessor.java
new file mode 100644
index 00000000000..97da5a718f6
--- /dev/null
+++ 
b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdDoubleLongSubProcessor.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ *
+ *   http://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 org.apache.druid.math.expr.vector.simd;
+
+import jdk.incubator.vector.DoubleVector;
+import jdk.incubator.vector.LongVector;
+import jdk.incubator.vector.VectorMask;
+import org.apache.druid.math.expr.vector.ExprVectorProcessor;
+import 
org.apache.druid.math.expr.vector.functional.DoubleBivariateDoubleLongFunction;
+
+import java.util.Arrays;
+
+/**
+ * SIMD specialization of {@code (double[], long[]) -> double[]} subtraction. 
The op is hardcoded to
+ * {@link DoubleVector#sub} so the JIT statically resolves it to the 
platform's double-subtract intrinsic.
+ */
+public final class SimdDoubleLongSubProcessor extends SimdDoubleLongProcessor
+{
+  public SimdDoubleLongSubProcessor(
+      ExprVectorProcessor<?> left,
+      ExprVectorProcessor<?> right,
+      DoubleBivariateDoubleLongFunction scalarFallback
+  )
+  {
+    super(left, right, scalarFallback);
+  }
+
+  @Override
+  protected void processVector(
+      double[] leftInput,
+      long[] rightInput,
+      boolean[] leftNulls,
+      boolean[] rightNulls,
+      int currentSize
+  )
+  {
+    final boolean hasLeftNulls = leftNulls != null;
+    final boolean hasRightNulls = rightNulls != null;
+    final int laneCount = DOUBLE_SPECIES.length();
+    final int upperBound = DOUBLE_SPECIES.loopBound(currentSize);
+    int i = 0;
+    if (!hasLeftNulls && !hasRightNulls) {
+      for (; i < upperBound; i += laneCount) {
+        final DoubleVector va = DoubleVector.fromArray(DOUBLE_SPECIES, 
leftInput, i);
+        final DoubleVector vb =
+            (DoubleVector) LongVector.fromArray(LONG_SPECIES, rightInput, 
i).castShape(DOUBLE_SPECIES, 0);
+        va.sub(vb).intoArray(outValues, i);
+      }
+      for (; i < currentSize; i++) {
+        outValues[i] = scalarFallback.process(leftInput[i], rightInput[i]);
+      }
+      Arrays.fill(outNulls, 0, currentSize, false);
+    } else {
+      for (; i < upperBound; i += laneCount) {
+        final VectorMask<Double> nm;
+        if (hasLeftNulls && hasRightNulls) {
+          nm = VectorMask.fromArray(DOUBLE_SPECIES, leftNulls, i)
+                         .or(VectorMask.fromArray(DOUBLE_SPECIES, rightNulls, 
i));
+        } else if (hasLeftNulls) {
+          nm = VectorMask.fromArray(DOUBLE_SPECIES, leftNulls, i);
+        } else {
+          nm = VectorMask.fromArray(DOUBLE_SPECIES, rightNulls, i);
+        }
+        final DoubleVector va = DoubleVector.fromArray(DOUBLE_SPECIES, 
leftInput, i);
+        final DoubleVector vb =
+            (DoubleVector) LongVector.fromArray(LONG_SPECIES, rightInput, 
i).castShape(DOUBLE_SPECIES, 0);
+        va.sub(vb).intoArray(outValues, i);
+        nm.intoArray(outNulls, i);
+      }
+      for (; i < currentSize; i++) {
+        final boolean isNull = (hasLeftNulls && leftNulls[i]) || 
(hasRightNulls && rightNulls[i]);
+        outNulls[i] = isNull;
+        if (!isNull) {
+          outValues[i] = scalarFallback.process(leftInput[i], rightInput[i]);
+        }
+      }
+    }
+  }
+}
diff --git 
a/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdLongDoubleAddProcessor.java
 
b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdLongDoubleAddProcessor.java
new file mode 100644
index 00000000000..bd077a05a02
--- /dev/null
+++ 
b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdLongDoubleAddProcessor.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ *
+ *   http://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 org.apache.druid.math.expr.vector.simd;
+
+import jdk.incubator.vector.DoubleVector;
+import jdk.incubator.vector.LongVector;
+import jdk.incubator.vector.VectorMask;
+import org.apache.druid.math.expr.vector.ExprVectorProcessor;
+import 
org.apache.druid.math.expr.vector.functional.DoubleBivariateLongDoubleFunction;
+
+import java.util.Arrays;
+
+/**
+ * SIMD specialization of {@code (long[], double[]) -> double[]} addition. The 
op is hardcoded to
+ * {@link DoubleVector#add} so the JIT statically resolves it to the 
platform's double-add intrinsic.
+ */
+public final class SimdLongDoubleAddProcessor extends SimdLongDoubleProcessor
+{
+  public SimdLongDoubleAddProcessor(
+      ExprVectorProcessor<?> left,
+      ExprVectorProcessor<?> right,
+      DoubleBivariateLongDoubleFunction scalarFallback
+  )
+  {
+    super(left, right, scalarFallback);
+  }
+
+  @Override
+  protected void processVector(
+      long[] leftInput,
+      double[] rightInput,
+      boolean[] leftNulls,
+      boolean[] rightNulls,
+      int currentSize
+  )
+  {
+    final boolean hasLeftNulls = leftNulls != null;
+    final boolean hasRightNulls = rightNulls != null;
+    final int laneCount = DOUBLE_SPECIES.length();
+    final int upperBound = DOUBLE_SPECIES.loopBound(currentSize);
+    int i = 0;
+    if (!hasLeftNulls && !hasRightNulls) {
+      for (; i < upperBound; i += laneCount) {
+        final DoubleVector va =
+            (DoubleVector) LongVector.fromArray(LONG_SPECIES, leftInput, 
i).castShape(DOUBLE_SPECIES, 0);
+        final DoubleVector vb = DoubleVector.fromArray(DOUBLE_SPECIES, 
rightInput, i);
+        va.add(vb).intoArray(outValues, i);
+      }
+      for (; i < currentSize; i++) {
+        outValues[i] = scalarFallback.process(leftInput[i], rightInput[i]);
+      }
+      Arrays.fill(outNulls, 0, currentSize, false);
+    } else {
+      for (; i < upperBound; i += laneCount) {
+        final VectorMask<Double> nm;
+        if (hasLeftNulls && hasRightNulls) {
+          nm = VectorMask.fromArray(DOUBLE_SPECIES, leftNulls, i)
+                         .or(VectorMask.fromArray(DOUBLE_SPECIES, rightNulls, 
i));
+        } else if (hasLeftNulls) {
+          nm = VectorMask.fromArray(DOUBLE_SPECIES, leftNulls, i);
+        } else {
+          nm = VectorMask.fromArray(DOUBLE_SPECIES, rightNulls, i);
+        }
+        final DoubleVector va =
+            (DoubleVector) LongVector.fromArray(LONG_SPECIES, leftInput, 
i).castShape(DOUBLE_SPECIES, 0);
+        final DoubleVector vb = DoubleVector.fromArray(DOUBLE_SPECIES, 
rightInput, i);
+        va.add(vb).intoArray(outValues, i);
+        nm.intoArray(outNulls, i);
+      }
+      for (; i < currentSize; i++) {
+        final boolean isNull = (hasLeftNulls && leftNulls[i]) || 
(hasRightNulls && rightNulls[i]);
+        outNulls[i] = isNull;
+        if (!isNull) {
+          outValues[i] = scalarFallback.process(leftInput[i], rightInput[i]);
+        }
+      }
+    }
+  }
+}
diff --git 
a/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdLongDoubleMulProcessor.java
 
b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdLongDoubleMulProcessor.java
new file mode 100644
index 00000000000..2d211e26b7e
--- /dev/null
+++ 
b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdLongDoubleMulProcessor.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ *
+ *   http://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 org.apache.druid.math.expr.vector.simd;
+
+import jdk.incubator.vector.DoubleVector;
+import jdk.incubator.vector.LongVector;
+import jdk.incubator.vector.VectorMask;
+import org.apache.druid.math.expr.vector.ExprVectorProcessor;
+import 
org.apache.druid.math.expr.vector.functional.DoubleBivariateLongDoubleFunction;
+
+import java.util.Arrays;
+
+/**
+ * SIMD specialization of {@code (long[], double[]) -> double[]} 
multiplication. The op is hardcoded to
+ * {@link DoubleVector#mul} so the JIT statically resolves it to the 
platform's double-multiply intrinsic.
+ */
+public final class SimdLongDoubleMulProcessor extends SimdLongDoubleProcessor
+{
+  public SimdLongDoubleMulProcessor(
+      ExprVectorProcessor<?> left,
+      ExprVectorProcessor<?> right,
+      DoubleBivariateLongDoubleFunction scalarFallback
+  )
+  {
+    super(left, right, scalarFallback);
+  }
+
+  @Override
+  protected void processVector(
+      long[] leftInput,
+      double[] rightInput,
+      boolean[] leftNulls,
+      boolean[] rightNulls,
+      int currentSize
+  )
+  {
+    final boolean hasLeftNulls = leftNulls != null;
+    final boolean hasRightNulls = rightNulls != null;
+    final int laneCount = DOUBLE_SPECIES.length();
+    final int upperBound = DOUBLE_SPECIES.loopBound(currentSize);
+    int i = 0;
+    if (!hasLeftNulls && !hasRightNulls) {
+      for (; i < upperBound; i += laneCount) {
+        final DoubleVector va =
+            (DoubleVector) LongVector.fromArray(LONG_SPECIES, leftInput, 
i).castShape(DOUBLE_SPECIES, 0);
+        final DoubleVector vb = DoubleVector.fromArray(DOUBLE_SPECIES, 
rightInput, i);
+        va.mul(vb).intoArray(outValues, i);
+      }
+      for (; i < currentSize; i++) {
+        outValues[i] = scalarFallback.process(leftInput[i], rightInput[i]);
+      }
+      Arrays.fill(outNulls, 0, currentSize, false);
+    } else {
+      for (; i < upperBound; i += laneCount) {
+        final VectorMask<Double> nm;
+        if (hasLeftNulls && hasRightNulls) {
+          nm = VectorMask.fromArray(DOUBLE_SPECIES, leftNulls, i)
+                         .or(VectorMask.fromArray(DOUBLE_SPECIES, rightNulls, 
i));
+        } else if (hasLeftNulls) {
+          nm = VectorMask.fromArray(DOUBLE_SPECIES, leftNulls, i);
+        } else {
+          nm = VectorMask.fromArray(DOUBLE_SPECIES, rightNulls, i);
+        }
+        final DoubleVector va =
+            (DoubleVector) LongVector.fromArray(LONG_SPECIES, leftInput, 
i).castShape(DOUBLE_SPECIES, 0);
+        final DoubleVector vb = DoubleVector.fromArray(DOUBLE_SPECIES, 
rightInput, i);
+        va.mul(vb).intoArray(outValues, i);
+        nm.intoArray(outNulls, i);
+      }
+      for (; i < currentSize; i++) {
+        final boolean isNull = (hasLeftNulls && leftNulls[i]) || 
(hasRightNulls && rightNulls[i]);
+        outNulls[i] = isNull;
+        if (!isNull) {
+          outValues[i] = scalarFallback.process(leftInput[i], rightInput[i]);
+        }
+      }
+    }
+  }
+}
diff --git 
a/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdLongDoubleProcessor.java
 
b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdLongDoubleProcessor.java
new file mode 100644
index 00000000000..366354f82b2
--- /dev/null
+++ 
b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdLongDoubleProcessor.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ *
+ *   http://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 org.apache.druid.math.expr.vector.simd;
+
+import jdk.incubator.vector.DoubleVector;
+import jdk.incubator.vector.LongVector;
+import jdk.incubator.vector.VectorSpecies;
+import org.apache.druid.math.expr.Expr;
+import org.apache.druid.math.expr.ExpressionType;
+import org.apache.druid.math.expr.vector.CastToTypeVectorProcessor;
+import org.apache.druid.math.expr.vector.ExprEvalDoubleVector;
+import org.apache.druid.math.expr.vector.ExprEvalVector;
+import org.apache.druid.math.expr.vector.ExprVectorProcessor;
+import 
org.apache.druid.math.expr.vector.functional.DoubleBivariateLongDoubleFunction;
+
+import javax.annotation.Nullable;
+
+/**
+ * Abstract base for SIMD processors that compute {@code (long[], double[]) -> 
double[]} ops. The long lane is
+ * widened to {@link DoubleVector} via {@code 
castShape(DoubleVector.SPECIES_PREFERRED, 0)} in each subclass's hot
+ * loop. See {@link SimdLongLongProcessor} for the design rationale.
+ */
+abstract class SimdLongDoubleProcessor implements ExprVectorProcessor<double[]>
+{
+  static final VectorSpecies<Long> LONG_SPECIES = LongVector.SPECIES_PREFERRED;
+  static final VectorSpecies<Double> DOUBLE_SPECIES = 
DoubleVector.SPECIES_PREFERRED;
+
+  private final ExprVectorProcessor<long[]> left;
+  private final ExprVectorProcessor<double[]> right;
+  final DoubleBivariateLongDoubleFunction scalarFallback;
+  final double[] outValues;
+  final boolean[] outNulls;
+
+  protected SimdLongDoubleProcessor(
+      ExprVectorProcessor<?> left,
+      ExprVectorProcessor<?> right,
+      DoubleBivariateLongDoubleFunction scalarFallback
+  )
+  {
+    this.left = CastToTypeVectorProcessor.cast(left, ExpressionType.LONG);
+    this.right = CastToTypeVectorProcessor.cast(right, ExpressionType.DOUBLE);
+    this.scalarFallback = scalarFallback;
+    this.outValues = new double[this.left.maxVectorSize()];
+    this.outNulls = new boolean[this.left.maxVectorSize()];
+  }
+
+  @Override
+  public final ExprEvalVector<double[]> evalVector(Expr.VectorInputBinding 
bindings)
+  {
+    final ExprEvalVector<long[]> lhs = left.evalVector(bindings);
+    final ExprEvalVector<double[]> rhs = right.evalVector(bindings);
+    processVector(
+        lhs.values(),
+        rhs.values(),
+        lhs.getNullVector(),
+        rhs.getNullVector(),
+        bindings.getCurrentVectorSize()
+    );
+    return new ExprEvalDoubleVector(outValues, outNulls);
+  }
+
+  protected abstract void processVector(
+      long[] leftInput,
+      double[] rightInput,
+      @Nullable boolean[] leftNulls,
+      @Nullable boolean[] rightNulls,
+      int currentSize
+  );
+
+  @Override
+  public final ExpressionType getOutputType()
+  {
+    return ExpressionType.DOUBLE;
+  }
+
+  @Override
+  public final int maxVectorSize()
+  {
+    return outValues.length;
+  }
+}
diff --git 
a/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdLongDoubleSubProcessor.java
 
b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdLongDoubleSubProcessor.java
new file mode 100644
index 00000000000..33c8602cf88
--- /dev/null
+++ 
b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdLongDoubleSubProcessor.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ *
+ *   http://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 org.apache.druid.math.expr.vector.simd;
+
+import jdk.incubator.vector.DoubleVector;
+import jdk.incubator.vector.LongVector;
+import jdk.incubator.vector.VectorMask;
+import org.apache.druid.math.expr.vector.ExprVectorProcessor;
+import 
org.apache.druid.math.expr.vector.functional.DoubleBivariateLongDoubleFunction;
+
+import java.util.Arrays;
+
+/**
+ * SIMD specialization of {@code (long[], double[]) -> double[]} subtraction. 
The op is hardcoded to
+ * {@link DoubleVector#sub} so the JIT statically resolves it to the 
platform's double-subtract intrinsic.
+ */
+public final class SimdLongDoubleSubProcessor extends SimdLongDoubleProcessor
+{
+  public SimdLongDoubleSubProcessor(
+      ExprVectorProcessor<?> left,
+      ExprVectorProcessor<?> right,
+      DoubleBivariateLongDoubleFunction scalarFallback
+  )
+  {
+    super(left, right, scalarFallback);
+  }
+
+  @Override
+  protected void processVector(
+      long[] leftInput,
+      double[] rightInput,
+      boolean[] leftNulls,
+      boolean[] rightNulls,
+      int currentSize
+  )
+  {
+    final boolean hasLeftNulls = leftNulls != null;
+    final boolean hasRightNulls = rightNulls != null;
+    final int laneCount = DOUBLE_SPECIES.length();
+    final int upperBound = DOUBLE_SPECIES.loopBound(currentSize);
+    int i = 0;
+    if (!hasLeftNulls && !hasRightNulls) {
+      for (; i < upperBound; i += laneCount) {
+        final DoubleVector va =
+            (DoubleVector) LongVector.fromArray(LONG_SPECIES, leftInput, 
i).castShape(DOUBLE_SPECIES, 0);
+        final DoubleVector vb = DoubleVector.fromArray(DOUBLE_SPECIES, 
rightInput, i);
+        va.sub(vb).intoArray(outValues, i);
+      }
+      for (; i < currentSize; i++) {
+        outValues[i] = scalarFallback.process(leftInput[i], rightInput[i]);
+      }
+      Arrays.fill(outNulls, 0, currentSize, false);
+    } else {
+      for (; i < upperBound; i += laneCount) {
+        final VectorMask<Double> nm;
+        if (hasLeftNulls && hasRightNulls) {
+          nm = VectorMask.fromArray(DOUBLE_SPECIES, leftNulls, i)
+                         .or(VectorMask.fromArray(DOUBLE_SPECIES, rightNulls, 
i));
+        } else if (hasLeftNulls) {
+          nm = VectorMask.fromArray(DOUBLE_SPECIES, leftNulls, i);
+        } else {
+          nm = VectorMask.fromArray(DOUBLE_SPECIES, rightNulls, i);
+        }
+        final DoubleVector va =
+            (DoubleVector) LongVector.fromArray(LONG_SPECIES, leftInput, 
i).castShape(DOUBLE_SPECIES, 0);
+        final DoubleVector vb = DoubleVector.fromArray(DOUBLE_SPECIES, 
rightInput, i);
+        va.sub(vb).intoArray(outValues, i);
+        nm.intoArray(outNulls, i);
+      }
+      for (; i < currentSize; i++) {
+        final boolean isNull = (hasLeftNulls && leftNulls[i]) || 
(hasRightNulls && rightNulls[i]);
+        outNulls[i] = isNull;
+        if (!isNull) {
+          outValues[i] = scalarFallback.process(leftInput[i], rightInput[i]);
+        }
+      }
+    }
+  }
+}
diff --git 
a/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdLongLongAddProcessor.java
 
b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdLongLongAddProcessor.java
new file mode 100644
index 00000000000..f5e0298af09
--- /dev/null
+++ 
b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdLongLongAddProcessor.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ *
+ *   http://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 org.apache.druid.math.expr.vector.simd;
+
+import jdk.incubator.vector.LongVector;
+import jdk.incubator.vector.VectorMask;
+import org.apache.druid.math.expr.vector.ExprVectorProcessor;
+import org.apache.druid.math.expr.vector.functional.LongBivariateLongsFunction;
+
+import java.util.Arrays;
+
+/**
+ * SIMD specialization of {@code (long[], long[]) -> long[]} addition. The op 
is hardcoded to {@link LongVector#add}
+ * so the JIT statically resolves it to the platform's long-add intrinsic.
+ */
+public final class SimdLongLongAddProcessor extends SimdLongLongProcessor
+{
+  public SimdLongLongAddProcessor(
+      ExprVectorProcessor<?> left,
+      ExprVectorProcessor<?> right,
+      LongBivariateLongsFunction scalarFallback
+  )
+  {
+    super(left, right, scalarFallback);
+  }
+
+  @Override
+  protected void processVector(
+      long[] leftInput,
+      long[] rightInput,
+      boolean[] leftNulls,
+      boolean[] rightNulls,
+      int currentSize
+  )
+  {
+    final boolean hasLeftNulls = leftNulls != null;
+    final boolean hasRightNulls = rightNulls != null;
+    final int laneCount = SPECIES.length();
+    final int upperBound = SPECIES.loopBound(currentSize);
+    int i = 0;
+    if (!hasLeftNulls && !hasRightNulls) {
+      for (; i < upperBound; i += laneCount) {
+        final LongVector va = LongVector.fromArray(SPECIES, leftInput, i);
+        final LongVector vb = LongVector.fromArray(SPECIES, rightInput, i);
+        va.add(vb).intoArray(outValues, i);
+      }
+      for (; i < currentSize; i++) {
+        outValues[i] = scalarFallback.process(leftInput[i], rightInput[i]);
+      }
+      Arrays.fill(outNulls, 0, currentSize, false);
+    } else {
+      for (; i < upperBound; i += laneCount) {
+        final VectorMask<Long> nm;
+        if (hasLeftNulls && hasRightNulls) {
+          nm = VectorMask.fromArray(SPECIES, leftNulls, i)
+                         .or(VectorMask.fromArray(SPECIES, rightNulls, i));
+        } else if (hasLeftNulls) {
+          nm = VectorMask.fromArray(SPECIES, leftNulls, i);
+        } else {
+          nm = VectorMask.fromArray(SPECIES, rightNulls, i);
+        }
+        final LongVector va = LongVector.fromArray(SPECIES, leftInput, i);
+        final LongVector vb = LongVector.fromArray(SPECIES, rightInput, i);
+        va.add(vb).intoArray(outValues, i);
+        nm.intoArray(outNulls, i);
+      }
+      for (; i < currentSize; i++) {
+        final boolean isNull = (hasLeftNulls && leftNulls[i]) || 
(hasRightNulls && rightNulls[i]);
+        outNulls[i] = isNull;
+        if (!isNull) {
+          outValues[i] = scalarFallback.process(leftInput[i], rightInput[i]);
+        }
+      }
+    }
+  }
+}
diff --git 
a/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdLongLongMulProcessor.java
 
b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdLongLongMulProcessor.java
new file mode 100644
index 00000000000..32e8e8aa751
--- /dev/null
+++ 
b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdLongLongMulProcessor.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ *
+ *   http://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 org.apache.druid.math.expr.vector.simd;
+
+import jdk.incubator.vector.LongVector;
+import jdk.incubator.vector.VectorMask;
+import org.apache.druid.math.expr.vector.ExprVectorProcessor;
+import org.apache.druid.math.expr.vector.functional.LongBivariateLongsFunction;
+
+import java.util.Arrays;
+
+/**
+ * SIMD specialization of {@code (long[], long[]) -> long[]} multiplication. 
The op is hardcoded to
+ * {@link LongVector#mul} so the JIT statically resolves it to the platform's 
long-multiply intrinsic.
+ */
+public final class SimdLongLongMulProcessor extends SimdLongLongProcessor
+{
+  public SimdLongLongMulProcessor(
+      ExprVectorProcessor<?> left,
+      ExprVectorProcessor<?> right,
+      LongBivariateLongsFunction scalarFallback
+  )
+  {
+    super(left, right, scalarFallback);
+  }
+
+  @Override
+  protected void processVector(
+      long[] leftInput,
+      long[] rightInput,
+      boolean[] leftNulls,
+      boolean[] rightNulls,
+      int currentSize
+  )
+  {
+    final boolean hasLeftNulls = leftNulls != null;
+    final boolean hasRightNulls = rightNulls != null;
+    final int laneCount = SPECIES.length();
+    final int upperBound = SPECIES.loopBound(currentSize);
+    int i = 0;
+    if (!hasLeftNulls && !hasRightNulls) {
+      for (; i < upperBound; i += laneCount) {
+        final LongVector va = LongVector.fromArray(SPECIES, leftInput, i);
+        final LongVector vb = LongVector.fromArray(SPECIES, rightInput, i);
+        va.mul(vb).intoArray(outValues, i);
+      }
+      for (; i < currentSize; i++) {
+        outValues[i] = scalarFallback.process(leftInput[i], rightInput[i]);
+      }
+      Arrays.fill(outNulls, 0, currentSize, false);
+    } else {
+      for (; i < upperBound; i += laneCount) {
+        final VectorMask<Long> nm;
+        if (hasLeftNulls && hasRightNulls) {
+          nm = VectorMask.fromArray(SPECIES, leftNulls, i)
+                         .or(VectorMask.fromArray(SPECIES, rightNulls, i));
+        } else if (hasLeftNulls) {
+          nm = VectorMask.fromArray(SPECIES, leftNulls, i);
+        } else {
+          nm = VectorMask.fromArray(SPECIES, rightNulls, i);
+        }
+        final LongVector va = LongVector.fromArray(SPECIES, leftInput, i);
+        final LongVector vb = LongVector.fromArray(SPECIES, rightInput, i);
+        va.mul(vb).intoArray(outValues, i);
+        nm.intoArray(outNulls, i);
+      }
+      for (; i < currentSize; i++) {
+        final boolean isNull = (hasLeftNulls && leftNulls[i]) || 
(hasRightNulls && rightNulls[i]);
+        outNulls[i] = isNull;
+        if (!isNull) {
+          outValues[i] = scalarFallback.process(leftInput[i], rightInput[i]);
+        }
+      }
+    }
+  }
+}
diff --git 
a/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdLongLongProcessor.java
 
b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdLongLongProcessor.java
new file mode 100644
index 00000000000..999f4149fac
--- /dev/null
+++ 
b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdLongLongProcessor.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ *
+ *   http://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 org.apache.druid.math.expr.vector.simd;
+
+import jdk.incubator.vector.LongVector;
+import jdk.incubator.vector.VectorSpecies;
+import org.apache.druid.math.expr.Expr;
+import org.apache.druid.math.expr.ExpressionType;
+import org.apache.druid.math.expr.vector.CastToTypeVectorProcessor;
+import org.apache.druid.math.expr.vector.ExprEvalLongVector;
+import org.apache.druid.math.expr.vector.ExprEvalVector;
+import org.apache.druid.math.expr.vector.ExprVectorProcessor;
+import org.apache.druid.math.expr.vector.functional.LongBivariateLongsFunction;
+
+import javax.annotation.Nullable;
+
+/**
+ * Abstract base for SIMD processors that compute {@code (long[], long[]) -> 
long[]} ops. Each concrete subclass
+ * (one per op) overrides {@link #processVector} with a hot loop that calls a 
statically-resolved {@link LongVector}
+ * method (e.g. {@code va.add(vb)}) so the JIT emits the corresponding SIMD 
intrinsic.
+ */
+abstract class SimdLongLongProcessor implements ExprVectorProcessor<long[]>
+{
+  static final VectorSpecies<Long> SPECIES = LongVector.SPECIES_PREFERRED;
+
+  private final ExprVectorProcessor<long[]> left;
+  private final ExprVectorProcessor<long[]> right;
+  final LongBivariateLongsFunction scalarFallback;
+  final long[] outValues;
+  final boolean[] outNulls;
+
+  protected SimdLongLongProcessor(
+      ExprVectorProcessor<?> left,
+      ExprVectorProcessor<?> right,
+      LongBivariateLongsFunction scalarFallback
+  )
+  {
+    this.left = CastToTypeVectorProcessor.cast(left, ExpressionType.LONG);
+    this.right = CastToTypeVectorProcessor.cast(right, ExpressionType.LONG);
+    this.scalarFallback = scalarFallback;
+    this.outValues = new long[this.left.maxVectorSize()];
+    this.outNulls = new boolean[this.left.maxVectorSize()];
+  }
+
+  @Override
+  public final ExprEvalVector<long[]> evalVector(Expr.VectorInputBinding 
bindings)
+  {
+    final ExprEvalVector<long[]> lhs = left.evalVector(bindings);
+    final ExprEvalVector<long[]> rhs = right.evalVector(bindings);
+    processVector(
+        lhs.values(),
+        rhs.values(),
+        lhs.getNullVector(),
+        rhs.getNullVector(),
+        bindings.getCurrentVectorSize()
+    );
+    return new ExprEvalLongVector(outValues, outNulls);
+  }
+
+  protected abstract void processVector(
+      long[] leftInput,
+      long[] rightInput,
+      @Nullable boolean[] leftNulls,
+      @Nullable boolean[] rightNulls,
+      int currentSize
+  );
+
+  @Override
+  public final ExpressionType getOutputType()
+  {
+    return ExpressionType.LONG;
+  }
+
+  @Override
+  public final int maxVectorSize()
+  {
+    return outValues.length;
+  }
+}
diff --git 
a/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdLongLongSubProcessor.java
 
b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdLongLongSubProcessor.java
new file mode 100644
index 00000000000..ab85396463d
--- /dev/null
+++ 
b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdLongLongSubProcessor.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ *
+ *   http://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 org.apache.druid.math.expr.vector.simd;
+
+import jdk.incubator.vector.LongVector;
+import jdk.incubator.vector.VectorMask;
+import org.apache.druid.math.expr.vector.ExprVectorProcessor;
+import org.apache.druid.math.expr.vector.functional.LongBivariateLongsFunction;
+
+import java.util.Arrays;
+
+/**
+ * SIMD specialization of {@code (long[], long[]) -> long[]} subtraction. The 
op is hardcoded to {@link LongVector#sub}
+ * so the JIT statically resolves it to the platform's long-subtract intrinsic.
+ */
+public final class SimdLongLongSubProcessor extends SimdLongLongProcessor
+{
+  public SimdLongLongSubProcessor(
+      ExprVectorProcessor<?> left,
+      ExprVectorProcessor<?> right,
+      LongBivariateLongsFunction scalarFallback
+  )
+  {
+    super(left, right, scalarFallback);
+  }
+
+  @Override
+  protected void processVector(
+      long[] leftInput,
+      long[] rightInput,
+      boolean[] leftNulls,
+      boolean[] rightNulls,
+      int currentSize
+  )
+  {
+    final boolean hasLeftNulls = leftNulls != null;
+    final boolean hasRightNulls = rightNulls != null;
+    final int laneCount = SPECIES.length();
+    final int upperBound = SPECIES.loopBound(currentSize);
+    int i = 0;
+    if (!hasLeftNulls && !hasRightNulls) {
+      for (; i < upperBound; i += laneCount) {
+        final LongVector va = LongVector.fromArray(SPECIES, leftInput, i);
+        final LongVector vb = LongVector.fromArray(SPECIES, rightInput, i);
+        va.sub(vb).intoArray(outValues, i);
+      }
+      for (; i < currentSize; i++) {
+        outValues[i] = scalarFallback.process(leftInput[i], rightInput[i]);
+      }
+      Arrays.fill(outNulls, 0, currentSize, false);
+    } else {
+      for (; i < upperBound; i += laneCount) {
+        final VectorMask<Long> nm;
+        if (hasLeftNulls && hasRightNulls) {
+          nm = VectorMask.fromArray(SPECIES, leftNulls, i)
+                         .or(VectorMask.fromArray(SPECIES, rightNulls, i));
+        } else if (hasLeftNulls) {
+          nm = VectorMask.fromArray(SPECIES, leftNulls, i);
+        } else {
+          nm = VectorMask.fromArray(SPECIES, rightNulls, i);
+        }
+        final LongVector va = LongVector.fromArray(SPECIES, leftInput, i);
+        final LongVector vb = LongVector.fromArray(SPECIES, rightInput, i);
+        va.sub(vb).intoArray(outValues, i);
+        nm.intoArray(outNulls, i);
+      }
+      for (; i < currentSize; i++) {
+        final boolean isNull = (hasLeftNulls && leftNulls[i]) || 
(hasRightNulls && rightNulls[i]);
+        outNulls[i] = isNull;
+        if (!isNull) {
+          outValues[i] = scalarFallback.process(leftInput[i], rightInput[i]);
+        }
+      }
+    }
+  }
+}
diff --git 
a/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdProcessors.java
 
b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdProcessors.java
new file mode 100644
index 00000000000..d8d74021c7a
--- /dev/null
+++ 
b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdProcessors.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ *
+ *   http://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 org.apache.druid.math.expr.vector.simd;
+
+import org.apache.druid.error.DruidException;
+import org.apache.druid.math.expr.vector.ExprVectorProcessor;
+import 
org.apache.druid.math.expr.vector.functional.DoubleBivariateDoubleLongFunction;
+import 
org.apache.druid.math.expr.vector.functional.DoubleBivariateDoublesFunction;
+import 
org.apache.druid.math.expr.vector.functional.DoubleBivariateLongDoubleFunction;
+import org.apache.druid.math.expr.vector.functional.LongBivariateLongsFunction;
+
+/**
+ * Dispatch table from a {@link SimdSupportedBinaryOp} identifier to a 
concrete, op-specialized SIMD processor.
+ * One class per op and type-combo so the JIT sees a monomorphic call site for 
the SIMD operation in each hot loop.
+ */
+public final class SimdProcessors
+{
+  private SimdProcessors()
+  {
+  }
+
+  public static ExprVectorProcessor<long[]> makeLongLong(
+      ExprVectorProcessor<?> left,
+      ExprVectorProcessor<?> right,
+      SimdSupportedBinaryOp op,
+      LongBivariateLongsFunction scalarFallback
+  )
+  {
+    return switch (op) {
+      case ADD -> new SimdLongLongAddProcessor(left, right, scalarFallback);
+      case SUB -> new SimdLongLongSubProcessor(left, right, scalarFallback);
+      case MUL -> new SimdLongLongMulProcessor(left, right, scalarFallback);
+      default -> throw DruidException.defensive("Unsupported SIMD binary 
op[%s]", op);
+    };
+  }
+
+  public static ExprVectorProcessor<double[]> makeDoubleDouble(
+      ExprVectorProcessor<?> left,
+      ExprVectorProcessor<?> right,
+      SimdSupportedBinaryOp op,
+      DoubleBivariateDoublesFunction scalarFallback
+  )
+  {
+    return switch (op) {
+      case ADD -> new SimdDoubleDoubleAddProcessor(left, right, 
scalarFallback);
+      case SUB -> new SimdDoubleDoubleSubProcessor(left, right, 
scalarFallback);
+      case MUL -> new SimdDoubleDoubleMulProcessor(left, right, 
scalarFallback);
+      default -> throw DruidException.defensive("Unsupported SIMD binary 
op[%s]", op);
+    };
+  }
+
+  public static ExprVectorProcessor<double[]> makeLongDouble(
+      ExprVectorProcessor<?> left,
+      ExprVectorProcessor<?> right,
+      SimdSupportedBinaryOp op,
+      DoubleBivariateLongDoubleFunction scalarFallback
+  )
+  {
+    return switch (op) {
+      case ADD -> new SimdLongDoubleAddProcessor(left, right, scalarFallback);
+      case SUB -> new SimdLongDoubleSubProcessor(left, right, scalarFallback);
+      case MUL -> new SimdLongDoubleMulProcessor(left, right, scalarFallback);
+      default -> throw DruidException.defensive("Unsupported SIMD binary 
op[%s]", op);
+    };
+  }
+
+  public static ExprVectorProcessor<double[]> makeDoubleLong(
+      ExprVectorProcessor<?> left,
+      ExprVectorProcessor<?> right,
+      SimdSupportedBinaryOp op,
+      DoubleBivariateDoubleLongFunction scalarFallback
+  )
+  {
+    return switch (op) {
+      case ADD -> new SimdDoubleLongAddProcessor(left, right, scalarFallback);
+      case SUB -> new SimdDoubleLongSubProcessor(left, right, scalarFallback);
+      case MUL -> new SimdDoubleLongMulProcessor(left, right, scalarFallback);
+      default -> throw DruidException.defensive("Unsupported SIMD binary 
op[%s]", op);
+    };
+  }
+}
diff --git 
a/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdSupportedBinaryOp.java
 
b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdSupportedBinaryOp.java
new file mode 100644
index 00000000000..953571ba4d3
--- /dev/null
+++ 
b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdSupportedBinaryOp.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ *
+ *   http://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 org.apache.druid.math.expr.vector.simd;
+
+/**
+ * Identifies which binary math operations have a {@code jdk.incubator.vector} 
(SIMD) specialization. Used by
+ * {@link 
org.apache.druid.math.expr.vector.SimpleVectorMathBivariateProcessorFactory} 
subclasses to declare that
+ * their operation can be dispatched to a SIMD variant when the user enables
+ * {@link 
org.apache.druid.math.expr.ExpressionProcessingConfig#USE_VECTOR_API}.
+ *
+ * Deliberately does not reference any {@code jdk.incubator.vector} types so 
that callers wiring the enum into
+ * factories do not need the incubator module visible.
+ */
+public enum SimdSupportedBinaryOp
+{
+  ADD,
+  SUB,
+  MUL
+}
diff --git 
a/processing/src/test/java/org/apache/druid/math/expr/VectorExprResultConsistencyTest.java
 
b/processing/src/test/java/org/apache/druid/math/expr/VectorExprResultConsistencyTest.java
index 7f21e489f3d..9ffe5da3ace 100644
--- 
a/processing/src/test/java/org/apache/druid/math/expr/VectorExprResultConsistencyTest.java
+++ 
b/processing/src/test/java/org/apache/druid/math/expr/VectorExprResultConsistencyTest.java
@@ -67,7 +67,7 @@ public class VectorExprResultConsistencyTest extends 
InitializedNullHandlingTest
 {
   private static final Logger log = new 
Logger(VectorExprResultConsistencyTest.class);
   private static final int NUM_ITERATIONS = 10;
-  private static final int VECTOR_SIZE = 4;
+  private static final List<Integer> VECTOR_SIZES = List.of(3, 8, 17, 67);
 
 
   private static final Map<String, String> LOOKUP = Map.of(
@@ -764,16 +764,18 @@ public class VectorExprResultConsistencyTest extends 
InitializedNullHandlingTest
       final int numIterations
   )
   {
-    for (int iter = 0; iter < numIterations; iter++) {
-      assertEvalsMatch(
-          expr,
-          parsed,
-          makeSequentialBinding(
-              VECTOR_SIZE,
-              types,
-              -2 + (iter * VECTOR_SIZE) // include negative numbers and zero
-          )
-      );
+    for (int vectorSize : VECTOR_SIZES) {
+      for (int iter = 0; iter < numIterations; iter++) {
+        assertEvalsMatch(
+            expr,
+            parsed,
+            makeSequentialBinding(
+                vectorSize,
+                types,
+                -2 + (iter * vectorSize) // include negative numbers and zero
+            )
+        );
+      }
     }
   }
 
@@ -784,8 +786,10 @@ public class VectorExprResultConsistencyTest extends 
InitializedNullHandlingTest
       final int numIterations
   )
   {
-    for (int iterations = 0; iterations < numIterations; iterations++) {
-      assertEvalsMatch(expr, parsed, makeRandomizedBindings(VECTOR_SIZE, 
types));
+    for (int vectorSize : VECTOR_SIZES) {
+      for (int iterations = 0; iterations < numIterations; iterations++) {
+        assertEvalsMatch(expr, parsed, makeRandomizedBindings(vectorSize, 
types));
+      }
     }
   }
 
@@ -808,7 +812,8 @@ public class VectorExprResultConsistencyTest extends 
InitializedNullHandlingTest
     );
 
     if (vectorEval.isValue() && nonVectorEval.isValue()) {
-      for (int i = 0; i < VECTOR_SIZE; i++) {
+      final int vectorSize = bindings.lhs.length;
+      for (int i = 0; i < vectorSize; i++) {
         final String message = StringUtils.format(
             "Values do not match for row[%s] for expression[%s], bindings[%s]",
             i,
@@ -1000,9 +1005,9 @@ public class VectorExprResultConsistencyTest extends 
InitializedNullHandlingTest
       @Nullable ExpressionType outputType
   )
   {
-    final Object[] exprValues = new Object[VECTOR_SIZE];
+    final Object[] exprValues = new Object[bindings.length];
 
-    for (int i = 0; i < VECTOR_SIZE; i++) {
+    for (int i = 0; i < bindings.length; i++) {
       ExprEval<?> eval;
       try {
         eval = expr.eval(bindings[i]);
diff --git 
a/processing/src/test/java/org/apache/druid/math/expr/VectorExprResultConsistencyVectorApiTest.java
 
b/processing/src/test/java/org/apache/druid/math/expr/VectorExprResultConsistencyVectorApiTest.java
new file mode 100644
index 00000000000..a14190796a2
--- /dev/null
+++ 
b/processing/src/test/java/org/apache/druid/math/expr/VectorExprResultConsistencyVectorApiTest.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ *
+ *   http://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 org.apache.druid.math.expr;
+
+import org.junit.After;
+import org.junit.Before;
+
+/**
+ * Re-runs every {@link VectorExprResultConsistencyTest} case with the SIMD 
({@code jdk.incubator.vector}) expression
+ * vector processors enabled, ensuring the SIMD specializations agree with the 
non-vectorized reference.
+ */
+public class VectorExprResultConsistencyVectorApiTest extends 
VectorExprResultConsistencyTest
+{
+  @Before
+  public void enableVectorApi()
+  {
+    ExpressionProcessing.initializeForVectorApiTests();
+  }
+
+  @After
+  public void resetExpressionProcessing()
+  {
+    ExpressionProcessing.initializeForTests();
+  }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to