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 87943a1ebfd feat: simd division expression (#19686)
87943a1ebfd is described below

commit 87943a1ebfd6013f51cc56aee0bf9dc40217e39f
Author: Clint Wylie <[email protected]>
AuthorDate: Wed Jul 15 14:51:28 2026 -0700

    feat: simd division expression (#19686)
---
 .../benchmark/query/SqlExpressionBenchmark.java    | 90 ++++++++++----------
 .../SimpleVectorMathBivariateProcessorFactory.java |  2 +-
 .../math/expr/vector/VectorMathProcessors.java     |  3 +-
 .../vector/simd/SimdDoubleDoubleDivProcessor.java  | 95 +++++++++++++++++++++
 .../vector/simd/SimdDoubleLongDivProcessor.java    | 96 ++++++++++++++++++++++
 .../vector/simd/SimdLongDoubleDivProcessor.java    | 96 ++++++++++++++++++++++
 .../math/expr/vector/simd/SimdProcessors.java      |  3 +
 .../expr/vector/simd/SimdSupportedBinaryOp.java    | 28 ++++++-
 8 files changed, 362 insertions(+), 51 deletions(-)

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 28a3ac8672a..5bc0019e723 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
@@ -63,87 +63,91 @@ public class SqlExpressionBenchmark extends 
SqlBaseQueryBenchmark
       "SELECT string2, SUM(long1) FROM expressions GROUP BY 1 ORDER BY 2",
       // 6: group by non-expr with 2 agg
       "SELECT string2, SUM(long1), SUM(double3) FROM expressions GROUP BY 1 
ORDER BY 2",
+      // 7: min/max on a nullable long column (exercises the null-aware SIMD 
path)
+      "SELECT MIN(long5), MAX(long5) FROM expressions",
+      // 8: min/max across long/double/float, non-null columns
+      "SELECT MIN(long1), MAX(long1), MIN(double1), MAX(double1), MIN(float3), 
MAX(float3) FROM expressions",
       // ===========================
       // expressions
       // ===========================
-      // 7: math op - 2 longs
+      // 9: math op - 2 longs
       "SELECT SUM(long1 * long2) FROM expressions",
-      // 8: mixed math - 2 longs, 1 double
+      // 10: mixed math - 2 longs, 1 double
       "SELECT SUM((long1 * long2) / double1) FROM expressions",
-      // 9: mixed math - 2 longs, 1 double, 1 float
+      // 11: mixed math - 2 longs, 1 double, 1 float
       "SELECT SUM(float3 + ((long1 * long4)/double1)) FROM expressions",
-      // 10: mixed math - 3 longs, 1 double, 1 float
+      // 12: mixed math - 3 longs, 1 double, 1 float
       "SELECT SUM(long5 - (float3 + ((long1 * long4)/double1))) FROM 
expressions",
-      // 11: all same math op - 3 longs, 1 double, 1 float
+      // 13: all same math op - 3 longs, 1 double, 1 float
       "SELECT SUM(long5 * float3 * long1 * long4 * double1) FROM expressions",
-      // 12: cos
+      // 14: cos
       "SELECT cos(double2) FROM expressions",
-      // 13: unary negate
+      // 15: unary negate
       "SELECT SUM(-long4) FROM expressions",
-      // 14: string long
+      // 16: string long
       "SELECT SUM(PARSE_LONG(string1)) FROM expressions",
-      // 15: string longer
+      // 17: string longer
       "SELECT SUM(PARSE_LONG(string3)) FROM expressions",
-      // 16: time floor, non-expr col + reg agg
+      // 18: time floor, non-expr col + reg agg
       "SELECT TIME_FLOOR(__time, 'PT1H'), string2, SUM(double4) FROM 
expressions GROUP BY 1,2 ORDER BY 3",
-      // 17: time floor, non-expr col + expr agg
+      // 19: time floor, non-expr col + expr agg
       "SELECT TIME_FLOOR(__time, 'PT1H'), string2, SUM(long1 * double4) FROM 
expressions GROUP BY 1,2 ORDER BY 3",
-      // 18: time floor + non-expr agg (timeseries) (non-expression reference)
+      // 20: time floor + non-expr agg (timeseries) (non-expression reference)
       "SELECT TIME_FLOOR(__time, 'PT1H'), SUM(long1) FROM expressions GROUP BY 
1 ORDER BY 1",
-      // 19: time floor + expr agg (timeseries)
+      // 21: time floor + expr agg (timeseries)
       "SELECT TIME_FLOOR(__time, 'PT1H'), SUM(long1 * long4) FROM expressions 
GROUP BY 1 ORDER BY 1",
-      // 20: time floor + non-expr agg (group by)
+      // 22: time floor + non-expr agg (group by)
       "SELECT TIME_FLOOR(__time, 'PT1H'), SUM(long1) FROM expressions GROUP BY 
1 ORDER BY 2",
-      // 21: time floor + expr agg (group by)
+      // 23: time floor + expr agg (group by)
       "SELECT TIME_FLOOR(__time, 'PT1H'), SUM(long1 * long4) FROM expressions 
GROUP BY 1 ORDER BY 2",
-      // 22: time floor offset by 1 day + non-expr agg (group by)
+      // 24: time floor offset by 1 day + non-expr agg (group by)
       "SELECT TIME_FLOOR(TIMESTAMPADD(DAY, -1, __time), 'PT1H'), SUM(long1) 
FROM expressions GROUP BY 1 ORDER BY 1",
-      // 23: time floor offset by 1 day + expr agg (group by)
+      // 25: time floor offset by 1 day + expr agg (group by)
       "SELECT TIME_FLOOR(TIMESTAMPADD(DAY, -1, __time), 'PT1H'), SUM(long1 * 
long4) FROM expressions GROUP BY 1 ORDER BY 1",
-      // 24: group by long expr with non-expr agg
+      // 26: group by long expr with non-expr agg
       "SELECT (long1 * long2), SUM(double1) FROM expressions GROUP BY 1 ORDER 
BY 2",
-      // 25: group by non-expr with expr agg
+      // 27: group by non-expr with expr agg
       "SELECT string2, SUM(long1 * long4) FROM expressions GROUP BY 1 ORDER BY 
2",
-      // 26: group by string expr with non-expr agg
+      // 28: group by string expr with non-expr agg
       "SELECT CONCAT(string2, '-', long2), SUM(double1) FROM expressions GROUP 
BY 1 ORDER BY 2",
-      // 27: group by string expr with expr agg
+      // 29: group by string expr with expr agg
       "SELECT CONCAT(string2, '-', long2), SUM(long1 * double4) FROM 
expressions GROUP BY 1 ORDER BY 2",
-      // 28: group by single input string low cardinality expr with expr agg
+      // 30: group by single input string low cardinality expr with expr agg
       "SELECT CONCAT(string2, '-', 'expressions'), SUM(long1 * long4) FROM 
expressions GROUP BY 1 ORDER BY 2",
-      // 29: group by single input string high cardinality expr with expr agg
+      // 31: group by single input string high cardinality expr with expr agg
       "SELECT CONCAT(string3, '-', 'expressions'), SUM(long1 * long4) FROM 
expressions GROUP BY 1 ORDER BY 2",
-      // 30: logical and operator
+      // 32: logical and operator
       "SELECT CAST(long1 as BOOLEAN) AND CAST (long2 as BOOLEAN), COUNT(*) 
FROM expressions GROUP BY 1 ORDER BY 2",
-      // 31: isnull, notnull
+      // 33: isnull, notnull
       "SELECT long5 IS NULL, long3 IS NOT NULL, count(*) FROM expressions 
GROUP BY 1,2 ORDER BY 3",
-      // 32: time shift, non-expr col + reg agg, regular
+      // 34: time shift, non-expr col + reg agg, regular
       "SELECT TIME_SHIFT(__time, 'PT1H', 3), string2, SUM(double4) FROM 
expressions GROUP BY 1,2 ORDER BY 3",
-      // 33: time shift, non-expr col + expr agg, sequential low cardinality
+      // 35: time shift, non-expr col + expr agg, sequential low cardinality
       "SELECT TIME_SHIFT(MILLIS_TO_TIMESTAMP(long1), 'PT1H', 1), string2, 
SUM(long1 * double4) FROM expressions GROUP BY 1,2 ORDER BY 3",
-      // 34: time shift + non-expr agg (timeseries) (non-expression 
reference), zipf distribution low cardinality
+      // 36: time shift + non-expr agg (timeseries) (non-expression 
reference), zipf distribution low cardinality
       "SELECT TIME_SHIFT(MILLIS_TO_TIMESTAMP(long2), 'PT1H', 1), string2, 
SUM(long1 * double4) FROM expressions GROUP BY 1,2 ORDER BY 3",
-      // 35: time shift + expr agg (timeseries), zipf distribution high 
cardinality
+      // 37: time shift + expr agg (timeseries), zipf distribution high 
cardinality
       "SELECT TIME_SHIFT(MILLIS_TO_TIMESTAMP(long3), 'PT1H', 1), string2, 
SUM(long1 * double4) FROM expressions GROUP BY 1,2 ORDER BY 3",
-      // 36: time shift + non-expr agg (group by), uniform distribution low 
cardinality
+      // 38: time shift + non-expr agg (group by), uniform distribution low 
cardinality
       "SELECT TIME_SHIFT(MILLIS_TO_TIMESTAMP(long4), 'PT1H', 1), string2, 
SUM(long1 * double4) FROM expressions GROUP BY 1,2 ORDER BY 3",
-      // 37: time shift + expr agg (group by), uniform distribution high 
cardinality
+      // 39: time shift + expr agg (group by), uniform distribution high 
cardinality
       "SELECT TIME_SHIFT(MILLIS_TO_TIMESTAMP(long5), 'PT1H', 1), string2, 
SUM(long1 * double4) FROM expressions GROUP BY 1,2 ORDER BY 3",
-      // 38,39: array element filtering
+      // 40,41: array element filtering
       "SELECT string1, long1 FROM expressions WHERE 
ARRAY_CONTAINS(\"multi-string3\", 100) GROUP BY 1,2",
       "SELECT string1, long1 FROM expressions WHERE 
ARRAY_OVERLAP(\"multi-string3\", ARRAY[100, 200]) GROUP BY 1,2",
-      // 40: regex filtering
+      // 42: regex filtering
       "SELECT string4, COUNT(*) FROM expressions WHERE REGEXP_EXTRACT(string1, 
'^1') IS NOT NULL OR REGEXP_EXTRACT('Z' || string2, '^Z2') IS NOT NULL GROUP BY 
1",
-      // 41: complicated filtering
+      // 43: complicated filtering
       "SELECT string2, SUM(long1) FROM expressions WHERE string1 = '1000' AND 
string5 LIKE '%1%' AND (string3 in ('1', '10', '20', '22', '32') AND long2 IN 
(1, 19, 21, 23, 25, 26, 46) AND double3 < 1010.0 AND double3 > 1000.0 AND 
(string4 = '1' OR REGEXP_EXTRACT(string1, '^1') IS NOT NULL OR 
REGEXP_EXTRACT('Z' || string2, '^Z2') IS NOT NULL)) GROUP BY 1 ORDER BY 2",
-      // 42: array_contains expr
+      // 44-47: array_contains / array_overlap exprs
       "SELECT ARRAY_CONTAINS(\"multi-string3\", 100) FROM expressions",
       "SELECT ARRAY_CONTAINS(\"multi-string3\", ARRAY[1, 2, 10, 11, 20, 22, 
30, 33, 40, 44, 50, 55, 100]) FROM expressions",
       "SELECT ARRAY_OVERLAP(\"multi-string3\", ARRAY[1, 100]) FROM 
expressions",
       "SELECT ARRAY_OVERLAP(\"multi-string3\", ARRAY[1, 2, 10, 11, 20, 22, 30, 
33, 40, 44, 50, 55, 100]) FROM expressions",
-      // 46: filters with random orders
+      // 48,49: filters with random orders
       "SELECT string2, SUM(long1) FROM expressions WHERE string5 LIKE '%1%' 
AND string1 = '1000' GROUP BY 1 ORDER BY 2",
       "SELECT string2, SUM(long1) FROM expressions WHERE string5 LIKE '%1%' 
AND (string3 in ('1', '10', '20', '22', '32') AND long2 IN (1, 19, 21, 23, 25, 
26, 46) AND double3 < 1010.0 AND double3 > 1000.0 AND (string4 = '1' OR 
REGEXP_EXTRACT(string1, '^1') IS NOT NULL OR REGEXP_EXTRACT('Z' || string2, 
'^Z2') IS NOT NULL)) AND string1 = '1000' GROUP BY 1 ORDER BY 2",
-      // 48-57 nvl tests
+      // 50-59: nvl tests
       // lower cardinality
       "SELECT NVL(string2, string1), SUM(double1) FROM expressions GROUP BY 1 
ORDER BY 2",
       "SELECT NVL(string2, CONCAT(string1, '-', long2)), SUM(double1) FROM 
expressions GROUP BY 1 ORDER BY 2",
@@ -159,18 +163,12 @@ public class SqlExpressionBenchmark extends 
SqlBaseQueryBenchmark
       // numeric no lhs null
       "SELECT NVL(long1, long3), SUM(double1) FROM expressions GROUP BY 1 
ORDER BY 2",
       "SELECT NVL(long1, long5 + long3), SUM(double1) FROM expressions GROUP 
BY 1 ORDER BY 2",
+      // 60: case
       "SELECT CASE WHEN MOD(long1, 2) = 0 THEN -1 WHEN MOD(long1, 2) = 1 THEN 
long2 / MOD(long1, 2) ELSE long3 END FROM expressions GROUP BY 1",
-      // 59-61 cast
+      // 61-63: cast
       "SELECT CAST(string1 as BIGINT) + CAST(string3 as DOUBLE) + long3, 
COUNT(*) FROM expressions GROUP BY 1 ORDER BY 2",
       "SELECT COUNT(*), SUM(CAST(string1 as BIGINT) + CAST(string3 as BIGINT)) 
FROM expressions WHERE double3 < 1010.0 AND double3 > 100.0",
-      "SELECT COUNT(*) FROM expressions WHERE __time >= TIMESTAMP '2000-01-01 
00:00:00' AND __time < TIMESTAMP '2000-01-02 00:00:00' AND 
(UPPER(COALESCE(string3,'')) LIKE '1%' OR TRIM(UPPER(COALESCE(string3,''))) 
LIKE '1%' OR SUBSTRING(UPPER(COALESCE(string3,'')),1,1) IN 
('1','2','3','4','5') OR ('X' || UPPER(COALESCE(string3,''))) LIKE 'X1%') AND 
(UPPER(COALESCE(string5,'')) LIKE '2%' OR TRIM(UPPER(COALESCE(string5,''))) 
LIKE '2%' OR SUBSTRING(UPPER(COALESCE(string5,'')),1,1) IN ('1','2 [...]
-      // ===========================
-      // more non-expression reference queries: min/max aggregators
-      // ===========================
-      // 62: min/max on a nullable long column (exercises the null-aware SIMD 
path)
-      "SELECT MIN(long5), MAX(long5) FROM expressions",
-      // 63: min/max across long/double/float, non-null columns
-      "SELECT MIN(long1), MAX(long1), MIN(double1), MAX(double1), MIN(float3), 
MAX(float3) FROM expressions"
+      "SELECT COUNT(*) FROM expressions WHERE __time >= TIMESTAMP '2000-01-01 
00:00:00' AND __time < TIMESTAMP '2000-01-02 00:00:00' AND 
(UPPER(COALESCE(string3,'')) LIKE '1%' OR TRIM(UPPER(COALESCE(string3,''))) 
LIKE '1%' OR SUBSTRING(UPPER(COALESCE(string3,'')),1,1) IN 
('1','2','3','4','5') OR ('X' || UPPER(COALESCE(string3,''))) LIKE 'X1%') AND 
(UPPER(COALESCE(string5,'')) LIKE '2%' OR TRIM(UPPER(COALESCE(string5,''))) 
LIKE '2%' OR SUBSTRING(UPPER(COALESCE(string5,'')),1,1) IN ('1','2 [...]
   );
 
   @Param({
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 deebbeb565f..42bf3e930a8 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
@@ -78,7 +78,7 @@ public class SimpleVectorMathBivariateProcessorFactory 
extends VectorMathBivaria
   @Override
   public final ExprVectorProcessor<long[]> 
longsProcessor(Expr.VectorInputBindingInspector inspector, Expr left, Expr 
right)
   {
-    if (simdOp != null && ExpressionProcessing.useVectorApi()) {
+    if (simdOp != null && simdOp.supportsLongLong() && 
ExpressionProcessing.useVectorApi()) {
       return SimdProcessors.makeLongLong(
           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 4a26f814153..3b4f3b8cb30 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
@@ -367,7 +367,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.DIV
       );
     }
   }
diff --git 
a/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdDoubleDoubleDivProcessor.java
 
b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdDoubleDoubleDivProcessor.java
new file mode 100644
index 00000000000..9f939c4775c
--- /dev/null
+++ 
b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdDoubleDoubleDivProcessor.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.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[]} division. 
The op is hardcoded to
+ * {@link DoubleVector#div} so the JIT statically resolves it to the 
platform's double-divide intrinsic.
+ * Division by zero produces the same {@code +/-Infinity} / {@code NaN} values 
as the scalar path, no
+ * exceptions are thrown for IEEE-754 double division.
+ */
+public final class SimdDoubleDoubleDivProcessor extends 
SimdDoubleDoubleProcessor
+{
+  public SimdDoubleDoubleDivProcessor(
+      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.div(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.div(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/SimdDoubleLongDivProcessor.java
 
b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdDoubleLongDivProcessor.java
new file mode 100644
index 00000000000..7abff42d0a4
--- /dev/null
+++ 
b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdDoubleLongDivProcessor.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[]} division. The 
right long lane is widened to
+ * {@link DoubleVector} via {@code castShape} before the hardcoded {@link 
DoubleVector#div} call.
+ */
+public final class SimdDoubleLongDivProcessor extends SimdDoubleLongProcessor
+{
+  public SimdDoubleLongDivProcessor(
+      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.div(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.div(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/SimdLongDoubleDivProcessor.java
 
b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdLongDoubleDivProcessor.java
new file mode 100644
index 00000000000..bc4792036a6
--- /dev/null
+++ 
b/processing/src/main/java/org/apache/druid/math/expr/vector/simd/SimdLongDoubleDivProcessor.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[]} division. The 
left long lane is widened to
+ * {@link DoubleVector} via {@code castShape} before the hardcoded {@link 
DoubleVector#div} call.
+ */
+public final class SimdLongDoubleDivProcessor extends SimdLongDoubleProcessor
+{
+  public SimdLongDoubleDivProcessor(
+      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.div(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.div(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
index d8d74021c7a..55ca4c81076 100644
--- 
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
@@ -62,6 +62,7 @@ public final class SimdProcessors
       case ADD -> new SimdDoubleDoubleAddProcessor(left, right, 
scalarFallback);
       case SUB -> new SimdDoubleDoubleSubProcessor(left, right, 
scalarFallback);
       case MUL -> new SimdDoubleDoubleMulProcessor(left, right, 
scalarFallback);
+      case DIV -> new SimdDoubleDoubleDivProcessor(left, right, 
scalarFallback);
       default -> throw DruidException.defensive("Unsupported SIMD binary 
op[%s]", op);
     };
   }
@@ -77,6 +78,7 @@ public final class SimdProcessors
       case ADD -> new SimdLongDoubleAddProcessor(left, right, scalarFallback);
       case SUB -> new SimdLongDoubleSubProcessor(left, right, scalarFallback);
       case MUL -> new SimdLongDoubleMulProcessor(left, right, scalarFallback);
+      case DIV -> new SimdLongDoubleDivProcessor(left, right, scalarFallback);
       default -> throw DruidException.defensive("Unsupported SIMD binary 
op[%s]", op);
     };
   }
@@ -92,6 +94,7 @@ public final class SimdProcessors
       case ADD -> new SimdDoubleLongAddProcessor(left, right, scalarFallback);
       case SUB -> new SimdDoubleLongSubProcessor(left, right, scalarFallback);
       case MUL -> new SimdDoubleLongMulProcessor(left, right, scalarFallback);
+      case DIV -> new SimdDoubleLongDivProcessor(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
index 953571ba4d3..1192d064214 100644
--- 
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
@@ -30,7 +30,29 @@ package org.apache.druid.math.expr.vector.simd;
  */
 public enum SimdSupportedBinaryOp
 {
-  ADD,
-  SUB,
-  MUL
+  ADD(true),
+  SUB(true),
+  MUL(true),
+  /**
+   * {@code jdk.incubator.vector} has no long-integer division intrinsic (SIMD 
hardware lacks it), and long
+   * division by zero would throw {@link ArithmeticException} in the middle of 
a chunk. So the {@code long × long}
+   * combo falls through to the scalar processor for DIV; only the 
double-output combos are SIMD-specialized.
+   */
+  DIV(false);
+
+  private final boolean supportsLongLong;
+
+  SimdSupportedBinaryOp(boolean supportsLongLong)
+  {
+    this.supportsLongLong = supportsLongLong;
+  }
+
+  /**
+   * Whether this op has a SIMD specialization for the {@code long × long -> 
long} type combo. Callers can use
+   * this to decide whether to route the {@code longsProcessor} path to SIMD 
or fall back to scalar.
+   */
+  public boolean supportsLongLong()
+  {
+    return supportsLongLong;
+  }
 }


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

Reply via email to