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

zeroshade pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-go.git


The following commit(s) were added to refs/heads/main by this push:
     new f92c33ad perf(compute): add ARM64 NEON constant multiplication (#1268)
f92c33ad is described below

commit f92c33ad58813c7fa88079b3142be0c84cb12241
Author: Minh Vu <[email protected]>
AuthorDate: Thu Sep 3 18:03:20 2026 +0200

    perf(compute): add ARM64 NEON constant multiplication (#1268)
    
    This adds an ARM64 NEON fast path for constant multiplication in the
    compute kernels.
    
    **What changed**
    
    - adds NEON kernels for int32/int64 input and output combinations
    - keeps the existing generic implementation when NEON is unavailable
    - adds boundary and overflow correctness coverage for short and
    non-multiple-of-four inputs
    - adds benchmarks for all four conversion combinations
    
    **Benchmark**
    
    Apple M1 Pro, darwin/arm64, 1 MiB input:
    
    | operation | generic | NEON dispatch | speedup |
    | --- | ---: | ---: | ---: |
    | int32 -> int32 | 421,874 ns/op | 87,556 ns/op | 4.82x |
    | int32 -> int64 | 423,170 ns/op | 296,760 ns/op | 1.43x |
    | int64 -> int32 | 422,409 ns/op | 135,615 ns/op | 3.11x |
    | int64 -> int64 | 438,196 ns/op | 302,779 ns/op | 1.45x |
    
    **Tests**
    
    - go test -count=1 ./arrow/compute/internal/kernels
    - go test -run ^$ -bench ^BenchmarkMultiplyConstant$ -benchmem -count=1
    ./arrow/compute/internal/kernels
---
 arrow/compute/internal/kernels/Makefile            |   6 +-
 .../internal/kernels/_lib/constant_factor_neon.c   | 108 +++++++++
 .../internal/kernels/_lib/constant_factor_neon.s   | 189 ++++++++++++++++
 .../internal/kernels/constant_factor_neon_arm64.go |  99 +++++++++
 .../internal/kernels/constant_factor_neon_arm64.s  | 218 +++++++++++++++++++
 .../kernels/constant_factor_neon_arm64_native.go   |  33 +++
 .../kernels/constant_factor_neon_arm64_test.go     |  43 ++++
 .../internal/kernels/constant_factor_test.go       | 241 +++++++++++++++++++++
 8 files changed, 935 insertions(+), 2 deletions(-)

diff --git a/arrow/compute/internal/kernels/Makefile 
b/arrow/compute/internal/kernels/Makefile
index 1edf1270..91a12e62 100644
--- a/arrow/compute/internal/kernels/Makefile
+++ b/arrow/compute/internal/kernels/Makefile
@@ -30,6 +30,8 @@ ASM_FLAGS_POPCNT=-mpopcnt
 
 C_FLAGS_NEON=-O3 -fvectorize -mllvm -force-vector-width=16 
-fno-asynchronous-unwind-tables -mno-red-zone -mstackrealign -fno-exceptions \
        -fno-rtti -fno-builtin -fno-math-errno -funsafe-math-optimizations 
-fno-rounding-math -fno-trapping-math -fno-jump-tables -I_lib 
-I../../../../internal/utils/_lib
+C_FLAGS_NEON_CONSTANT_FACTOR=-O2 -fno-unroll-loops -fno-vectorize 
-fno-slp-vectorize -fno-asynchronous-unwind-tables -mno-red-zone 
-fno-exceptions \
+       -fno-rtti -fno-builtin -fno-math-errno -funsafe-math-optimizations 
-fno-rounding-math -fno-trapping-math -fno-jump-tables -I_lib 
-I../../../../internal/utils/_lib
 
 GO_SOURCES  := $(shell find . -path ./_lib -prune -o -name '*.go' -not -name 
'*_test.go')
 ALL_SOURCES := $(shell find . -path ./_lib -prune -o -name '*.go' -name '*.s' 
-not -name '*_test.go')
@@ -82,8 +84,8 @@ _lib/constant_factor_avx2_amd64.s: _lib/constant_factor.c
 _lib/constant_factor_sse4_amd64.s: _lib/constant_factor.c
        $(CC) -S $(C_FLAGS) $(ASM_FLAGS_SSE4) $^ -o $@ ; $(PERL_FIXUP_ROTATE) $@
 
-_lib/constant_factor_neon.s: _lib/constant_factor.c
-       $(CC) -S $(C_FLAGS_NEON) $^ -o $@ ; $(PERL_FIXUP_ROTATE) $@
+_lib/constant_factor_neon.s: _lib/constant_factor_neon.c
+       $(CC) -S $(C_FLAGS_NEON_CONSTANT_FACTOR) $^ -o $@ ; 
$(PERL_FIXUP_ROTATE) $@
 
 cast_numeric_avx2_amd64.s: _lib/cast_numeric_avx2_amd64.s
        $(C2GOASM) -a -f $^ $@
diff --git a/arrow/compute/internal/kernels/_lib/constant_factor_neon.c 
b/arrow/compute/internal/kernels/_lib/constant_factor_neon.c
new file mode 100644
index 00000000..78b4bc6d
--- /dev/null
+++ b/arrow/compute/internal/kernels/_lib/constant_factor_neon.c
@@ -0,0 +1,108 @@
+// 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.
+
+#include <arm_neon.h>
+#include <stdint.h>
+
+static inline uint64x2_t multiply_u64x2_by_u64(const uint64x2_t values,
+                                               const uint64_t factor) {
+    const uint32x4_t values32 = vreinterpretq_u32_u64(values);
+    const uint32x2_t values_lo =
+        vuzp1_u32(vget_low_u32(values32), vget_high_u32(values32));
+    const uint32x2_t values_hi =
+        vuzp2_u32(vget_low_u32(values32), vget_high_u32(values32));
+    const uint32x2_t factor_lo = vdup_n_u32((uint32_t)factor);
+    const uint32x2_t factor_hi = vdup_n_u32((uint32_t)(factor >> 32));
+    const uint64x2_t low = vmull_u32(values_lo, factor_lo);
+    const uint64x2_t cross =
+        vmlal_u32(vmull_u32(values_lo, factor_hi), values_hi, factor_lo);
+
+    return vaddq_u64(low, vshlq_n_u64(cross, 32));
+}
+
+void multiply_constant_int32_int32_neon(const int32_t* src, int32_t* dest,
+                                        const int len, const int64_t factor) {
+    int i = 0;
+    const uint32x4_t factor_vec = vdupq_n_u32((uint32_t)factor);
+
+    for (; i + 4 <= len; i += 4) {
+        const uint32x4_t values = vld1q_u32((const uint32_t*)(src + i));
+        vst1q_u32((uint32_t*)(dest + i), vmulq_u32(values, factor_vec));
+    }
+
+    for (; i < len; ++i) {
+        dest[i] = (int32_t)((uint32_t)src[i] * (uint32_t)factor);
+    }
+}
+
+void multiply_constant_int32_int64_neon(const int32_t* src, int64_t* dest,
+                                        const int len, const int64_t factor) {
+    int i = 0;
+
+    for (; i + 4 <= len; i += 4) {
+        const int32x4_t values = vld1q_s32(src + i);
+        const uint64x2_t low =
+            vreinterpretq_u64_s64(vmovl_s32(vget_low_s32(values)));
+        const uint64x2_t high =
+            vreinterpretq_u64_s64(vmovl_s32(vget_high_s32(values)));
+        vst1q_u64((uint64_t*)(dest + i), multiply_u64x2_by_u64(low, factor));
+        vst1q_u64((uint64_t*)(dest + i + 2),
+                  multiply_u64x2_by_u64(high, factor));
+    }
+
+    for (; i < len; ++i) {
+        dest[i] = (int64_t)((uint64_t)(int64_t)src[i] * (uint64_t)factor);
+    }
+}
+
+void multiply_constant_int64_int32_neon(const int64_t* src, int32_t* dest,
+                                        const int len, const int64_t factor) {
+    int i = 0;
+    const uint32x4_t factor_vec = vdupq_n_u32((uint32_t)factor);
+
+    for (; i + 4 <= len; i += 4) {
+        const uint64x2_t low_values =
+            vld1q_u64((const uint64_t*)(src + i));
+        const uint64x2_t high_values =
+            vld1q_u64((const uint64_t*)(src + i + 2));
+        const uint32x4_t low_values32 = vreinterpretq_u32_u64(low_values);
+        const uint32x4_t high_values32 = vreinterpretq_u32_u64(high_values);
+        const uint32x2_t low =
+            vuzp1_u32(vget_low_u32(low_values32), vget_high_u32(low_values32));
+        const uint32x2_t high = vuzp1_u32(vget_low_u32(high_values32),
+                                          vget_high_u32(high_values32));
+        const uint32x4_t values = vcombine_u32(low, high);
+        vst1q_u32((uint32_t*)(dest + i), vmulq_u32(values, factor_vec));
+    }
+
+    for (; i < len; ++i) {
+        dest[i] = (int32_t)((uint32_t)src[i] * (uint32_t)factor);
+    }
+}
+
+void multiply_constant_int64_int64_neon(const int64_t* src, int64_t* dest,
+                                        const int len, const int64_t factor) {
+    int i = 0;
+
+    for (; i + 2 <= len; i += 2) {
+        const uint64x2_t values = vld1q_u64((const uint64_t*)(src + i));
+        vst1q_u64((uint64_t*)(dest + i), multiply_u64x2_by_u64(values, 
factor));
+    }
+
+    for (; i < len; ++i) {
+        dest[i] = (int64_t)((uint64_t)src[i] * (uint64_t)factor);
+    }
+}
diff --git a/arrow/compute/internal/kernels/_lib/constant_factor_neon.s 
b/arrow/compute/internal/kernels/_lib/constant_factor_neon.s
new file mode 100644
index 00000000..f9f4ea53
--- /dev/null
+++ b/arrow/compute/internal/kernels/_lib/constant_factor_neon.s
@@ -0,0 +1,189 @@
+       .build_version macos, 26, 0     sdk_version 26, 5
+       .section        __TEXT,__text,regular,pure_instructions
+       .globl  _multiply_constant_int32_int32_neon ; -- Begin function 
multiply_constant_int32_int32_neon
+       .p2align        2
+_multiply_constant_int32_int32_neon:    ; @multiply_constant_int32_int32_neon
+; %bb.0:
+       cmp     w2, #4
+       b.ge    LBB0_2
+; %bb.1:
+       mov     w8, #0                          ; =0x0
+       cmp     w8, w2
+       b.lt    LBB0_5
+       b       LBB0_7
+LBB0_2:
+       fmov    s0, w3
+       mov     w8, #4                          ; =0x4
+       mov     x9, x0
+       mov     x10, x1
+       mov     w11, w2
+LBB0_3:                                 ; =>This Inner Loop Header: Depth=1
+       ldr     q1, [x9], #16
+       mul.4s  v1, v1, v0[0]
+       str     q1, [x10], #16
+       add     x8, x8, #4
+       cmp     x8, x11
+       b.ls    LBB0_3
+; %bb.4:
+       and     w8, w2, #0x7ffffffc
+       cmp     w8, w2
+       b.ge    LBB0_7
+LBB0_5:
+       mov     w8, w8
+LBB0_6:                                 ; =>This Inner Loop Header: Depth=1
+       ldr     w9, [x0, x8, lsl #2]
+       mul     w9, w9, w3
+       str     w9, [x1, x8, lsl #2]
+       add     x8, x8, #1
+       cmp     w2, w8
+       b.gt    LBB0_6
+LBB0_7:
+       ret
+                                        ; -- End function
+       .globl  _multiply_constant_int32_int64_neon ; -- Begin function 
multiply_constant_int32_int64_neon
+       .p2align        2
+_multiply_constant_int32_int64_neon:    ; @multiply_constant_int32_int64_neon
+; %bb.0:
+       cmp     w2, #4
+       b.ge    LBB1_2
+; %bb.1:
+       mov     w8, #0                          ; =0x0
+       cmp     w8, w2
+       b.lt    LBB1_5
+       b       LBB1_7
+LBB1_2:
+       fmov    s0, w3
+       lsr     x8, x3, #32
+       fmov    s1, w8
+       mov     w8, w2
+       add     x9, x1, #16
+       mov     w10, #4                         ; =0x4
+       mov     x11, x0
+LBB1_3:                                 ; =>This Inner Loop Header: Depth=1
+       ldr     q2, [x11], #16
+       sshll.2d        v3, v2, #0
+       sshll2.2d       v4, v2, #0
+       ext.16b v5, v3, v3, #8
+       zip2.2s v3, v3, v5
+       umull.2d        v3, v3, v0[0]
+       umlal.2d        v3, v2, v1[0]
+       shl.2d  v3, v3, #32
+       umlal.2d        v3, v2, v0[0]
+       ext.16b v5, v4, v4, #8
+       zip2.2s v4, v4, v5
+       umull.2d        v4, v4, v0[0]
+       umlal2.2d       v4, v2, v1[0]
+       shl.2d  v4, v4, #32
+       umlal2.2d       v4, v2, v0[0]
+       stp     q3, q4, [x9, #-16]
+       add     x10, x10, #4
+       add     x9, x9, #32
+       cmp     x10, x8
+       b.ls    LBB1_3
+; %bb.4:
+       and     w8, w2, #0x7ffffffc
+       cmp     w8, w2
+       b.ge    LBB1_7
+LBB1_5:
+       mov     w8, w8
+LBB1_6:                                 ; =>This Inner Loop Header: Depth=1
+       ldrsw   x9, [x0, x8, lsl #2]
+       mul     x9, x3, x9
+       str     x9, [x1, x8, lsl #3]
+       add     x8, x8, #1
+       cmp     w2, w8
+       b.gt    LBB1_6
+LBB1_7:
+       ret
+                                        ; -- End function
+       .globl  _multiply_constant_int64_int32_neon ; -- Begin function 
multiply_constant_int64_int32_neon
+       .p2align        2
+_multiply_constant_int64_int32_neon:    ; @multiply_constant_int64_int32_neon
+; %bb.0:
+       cmp     w2, #4
+       b.ge    LBB2_2
+; %bb.1:
+       mov     w8, #0                          ; =0x0
+       cmp     w8, w2
+       b.lt    LBB2_5
+       b       LBB2_7
+LBB2_2:
+       fmov    s0, w3
+       add     x8, x0, #16
+       mov     w9, #4                          ; =0x4
+       mov     x10, x1
+       mov     w11, w2
+LBB2_3:                                 ; =>This Inner Loop Header: Depth=1
+       ldp     q1, q2, [x8, #-16]
+       uzp1.4s v1, v1, v2
+       mul.4s  v1, v1, v0[0]
+       str     q1, [x10], #16
+       add     x9, x9, #4
+       add     x8, x8, #32
+       cmp     x9, x11
+       b.ls    LBB2_3
+; %bb.4:
+       and     w8, w2, #0x7ffffffc
+       cmp     w8, w2
+       b.ge    LBB2_7
+LBB2_5:
+       mov     w8, w8
+LBB2_6:                                 ; =>This Inner Loop Header: Depth=1
+       ldr     x9, [x0, x8, lsl #3]
+       mul     w9, w9, w3
+       str     w9, [x1, x8, lsl #2]
+       add     x8, x8, #1
+       cmp     w2, w8
+       b.gt    LBB2_6
+LBB2_7:
+       ret
+                                        ; -- End function
+       .globl  _multiply_constant_int64_int64_neon ; -- Begin function 
multiply_constant_int64_int64_neon
+       .p2align        2
+_multiply_constant_int64_int64_neon:    ; @multiply_constant_int64_int64_neon
+; %bb.0:
+       cmp     w2, #2
+       b.ge    LBB3_2
+; %bb.1:
+       mov     w8, #0                          ; =0x0
+       cmp     w8, w2
+       b.lt    LBB3_5
+       b       LBB3_7
+LBB3_2:
+       fmov    s0, w3
+       lsr     x8, x3, #32
+       fmov    s1, w8
+       mov     w8, w2
+       mov     w9, #2                          ; =0x2
+       mov     x10, x0
+       mov     x11, x1
+LBB3_3:                                 ; =>This Inner Loop Header: Depth=1
+       ldr     q2, [x10], #16
+       xtn.2s  v3, v2
+       ext.16b v4, v2, v2, #8
+       zip2.2s v2, v2, v4
+       umull.2d        v2, v2, v0[0]
+       umlal.2d        v2, v3, v1[0]
+       shl.2d  v2, v2, #32
+       umlal.2d        v2, v3, v0[0]
+       str     q2, [x11], #16
+       add     x9, x9, #2
+       cmp     x9, x8
+       b.ls    LBB3_3
+; %bb.4:
+       and     w8, w2, #0x7ffffffe
+       cmp     w8, w2
+       b.ge    LBB3_7
+LBB3_5:
+       mov     w8, w8
+LBB3_6:                                 ; =>This Inner Loop Header: Depth=1
+       ldr     x9, [x0, x8, lsl #3]
+       mul     x9, x9, x3
+       str     x9, [x1, x8, lsl #3]
+       add     x8, x8, #1
+       cmp     w2, w8
+       b.gt    LBB3_6
+LBB3_7:
+       ret
+                                        ; -- End function
+.subsections_via_symbols
diff --git a/arrow/compute/internal/kernels/constant_factor_neon_arm64.go 
b/arrow/compute/internal/kernels/constant_factor_neon_arm64.go
new file mode 100644
index 00000000..0e8cc691
--- /dev/null
+++ b/arrow/compute/internal/kernels/constant_factor_neon_arm64.go
@@ -0,0 +1,99 @@
+// 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.
+
+//go:build go1.18 && !noasm && !appengine
+
+package kernels
+
+import (
+       "math"
+       "unsafe"
+
+       "golang.org/x/sys/cpu"
+)
+
+// The generated assembly reads the length through a 32-bit register. Larger
+// Go slices must stay on the scalar path.
+const maxNeonLength = int(math.MaxInt32)
+
+func neonLengthFitsAssembly(length int) bool {
+       return length <= maxNeonLength
+}
+
+//go:noescape
+func _multiply_constant_int32_int32_neon(src, dest unsafe.Pointer, len int, 
factor int64)
+
+func multiplyConstantInt32Int32Neon(in []int32, out []int32, factor int64) {
+       if len(out) == 0 {
+               return
+       }
+       if !neonLengthFitsAssembly(len(out)) {
+               multiplyConstantGo(in, out, factor)
+               return
+       }
+       _multiply_constant_int32_int32_neon(unsafe.Pointer(&in[0]), 
unsafe.Pointer(&out[0]), len(out), factor)
+}
+
+//go:noescape
+func _multiply_constant_int32_int64_neon(src, dest unsafe.Pointer, len int, 
factor int64)
+
+func multiplyConstantInt32Int64Neon(in []int32, out []int64, factor int64) {
+       if len(out) == 0 {
+               return
+       }
+       if !neonLengthFitsAssembly(len(out)) {
+               multiplyConstantGo(in, out, factor)
+               return
+       }
+       _multiply_constant_int32_int64_neon(unsafe.Pointer(&in[0]), 
unsafe.Pointer(&out[0]), len(out), factor)
+}
+
+//go:noescape
+func _multiply_constant_int64_int32_neon(src, dest unsafe.Pointer, len int, 
factor int64)
+
+func multiplyConstantInt64Int32Neon(in []int64, out []int32, factor int64) {
+       if len(out) == 0 {
+               return
+       }
+       if !neonLengthFitsAssembly(len(out)) {
+               multiplyConstantGo(in, out, factor)
+               return
+       }
+       _multiply_constant_int64_int32_neon(unsafe.Pointer(&in[0]), 
unsafe.Pointer(&out[0]), len(out), factor)
+}
+
+//go:noescape
+func _multiply_constant_int64_int64_neon(src, dest unsafe.Pointer, len int, 
factor int64)
+
+func multiplyConstantInt64Int64Neon(in []int64, out []int64, factor int64) {
+       if len(out) == 0 {
+               return
+       }
+       if !neonLengthFitsAssembly(len(out)) {
+               multiplyConstantGo(in, out, factor)
+               return
+       }
+       _multiply_constant_int64_int64_neon(unsafe.Pointer(&in[0]), 
unsafe.Pointer(&out[0]), len(out), factor)
+}
+
+func init() {
+       if cpu.ARM64.HasASIMD {
+               multiplyConstantInt32Int32 = multiplyConstantInt32Int32Neon
+               multiplyConstantInt32Int64 = multiplyConstantInt32Int64Neon
+               multiplyConstantInt64Int32 = multiplyConstantInt64Int32Neon
+               multiplyConstantInt64Int64 = multiplyConstantInt64Int64Neon
+       }
+}
diff --git a/arrow/compute/internal/kernels/constant_factor_neon_arm64.s 
b/arrow/compute/internal/kernels/constant_factor_neon_arm64.s
new file mode 100644
index 00000000..ac1a660d
--- /dev/null
+++ b/arrow/compute/internal/kernels/constant_factor_neon_arm64.s
@@ -0,0 +1,218 @@
+//go:build go1.18 && !noasm && !appengine
+// AUTO-GENERATED BY C2GOASM -- DO NOT EDIT
+
+DATA gclocals·untyped(SB)/8, $1
+GLOBL gclocals·untyped(SB), 8, $8
+
+TEXT ·_c2goasm_native_multiply_constant_int32_int32_neon(SB), 516, $0-32
+
+    CMPW $4, R2 // cmp w2, #4
+    BGE multiply_constant_int32_int32_neon_LBB0_2
+    MOVW $0, R8 // mov w8, #0
+    CMPW R2, R8 // cmp w8, w2
+    BLT multiply_constant_int32_int32_neon_LBB0_5
+    B multiply_constant_int32_int32_neon_LBB0_7
+multiply_constant_int32_int32_neon_LBB0_2:
+    WORD $0x1e270060 // fmov   s0, w3
+    MOVW $4, R8 // mov w8, #4
+    MOVD R0, R9 // mov x9, x0
+    MOVD R1, R10 // mov        x10, x1
+    MOVWU R2, R11 // mov       w11, w2
+multiply_constant_int32_int32_neon_LBB0_3:
+    WORD $0x3cc10521 // ldr    q1, [x9], #16
+    WORD $0x4f808021 // mul.4s v1, v1, v0[0]
+    WORD $0x3c810541 // str    q1, [x10], #16
+    ADD $4, R8, R8 // add      x8, x8, #4
+    CMP R11, R8 // cmp x8, x11
+    BLS multiply_constant_int32_int32_neon_LBB0_3
+    ANDW $2147483644, R2, R8 // and    w8, w2, #0x7ffffffc
+    CMPW R2, R8 // cmp w8, w2
+    BGE multiply_constant_int32_int32_neon_LBB0_7
+multiply_constant_int32_int32_neon_LBB0_5:
+    MOVWU R8, R8 // mov        w8, w8
+multiply_constant_int32_int32_neon_LBB0_6:
+    MOVWU (R0)(R8<<2), R9 // ldr       w9, [x0, x8, lsl #2]
+    MULW R3, R9, R9 // mul     w9, w9, w3
+    MOVW R9, (R1)(R8<<2) // str        w9, [x1, x8, lsl #2]
+    ADD $1, R8, R8 // add      x8, x8, #1
+    CMPW R8, R2 // cmp w2, w8
+    BGT multiply_constant_int32_int32_neon_LBB0_6
+multiply_constant_int32_int32_neon_LBB0_7:
+    RET
+
+
+TEXT ·_multiply_constant_int32_int32_neon(SB), 4, $0-32
+
+    MOVD src+0(FP), R0
+    MOVD dest+8(FP), R1
+    MOVD len+16(FP), R2
+    MOVD factor+24(FP), R3
+    CALL ·_c2goasm_native_multiply_constant_int32_int32_neon(SB)
+    RET
+
+
+TEXT ·_c2goasm_native_multiply_constant_int32_int64_neon(SB), 516, $0-32
+
+    CMPW $4, R2 // cmp w2, #4
+    BGE multiply_constant_int32_int64_neon_LBB1_2
+    MOVW $0, R8 // mov w8, #0
+    CMPW R2, R8 // cmp w8, w2
+    BLT multiply_constant_int32_int64_neon_LBB1_5
+    B multiply_constant_int32_int64_neon_LBB1_7
+multiply_constant_int32_int64_neon_LBB1_2:
+    WORD $0x1e270060 // fmov   s0, w3
+    LSR $32, R3, R8 // lsr     x8, x3, #32
+    WORD $0x1e270101 // fmov   s1, w8
+    MOVWU R2, R8 // mov        w8, w2
+    ADD $16, R1, R9 // add     x9, x1, #16
+    MOVW $4, R10 // mov        w10, #4
+    MOVD R0, R11 // mov        x11, x0
+multiply_constant_int32_int64_neon_LBB1_3:
+    WORD $0x3cc10562 // ldr    q2, [x11], #16
+    WORD $0x0f20a443 // sshll.2d       v3, v2, #0
+    WORD $0x4f20a444 // sshll2.2d      v4, v2, #0
+    VEXT $8, V3.B16, V3.B16, V5.B16 // ext.16b v5, v3, v3, #8
+    VZIP2 V5.S2, V3.S2, V3.S2 // zip2.2s       v3, v3, v5
+    WORD $0x2f80a063 // umull.2d       v3, v3, v0[0]
+    WORD $0x2f812043 // umlal.2d       v3, v2, v1[0]
+    VSHL $32, V3.D2, V3.D2 // shl.2d   v3, v3, #32
+    WORD $0x2f802043 // umlal.2d       v3, v2, v0[0]
+    VEXT $8, V4.B16, V4.B16, V5.B16 // ext.16b v5, v4, v4, #8
+    VZIP2 V5.S2, V4.S2, V4.S2 // zip2.2s       v4, v4, v5
+    WORD $0x2f80a084 // umull.2d       v4, v4, v0[0]
+    WORD $0x6f812044 // umlal2.2d      v4, v2, v1[0]
+    VSHL $32, V4.D2, V4.D2 // shl.2d   v4, v4, #32
+    WORD $0x6f802044 // umlal2.2d      v4, v2, v0[0]
+    WORD $0xad3f9123 // stp    q3, q4, [x9, #-16]
+    ADD $4, R10, R10 // add    x10, x10, #4
+    ADD $32, R9, R9 // add     x9, x9, #32
+    CMP R8, R10 // cmp x10, x8
+    BLS multiply_constant_int32_int64_neon_LBB1_3
+    ANDW $2147483644, R2, R8 // and    w8, w2, #0x7ffffffc
+    CMPW R2, R8 // cmp w8, w2
+    BGE multiply_constant_int32_int64_neon_LBB1_7
+multiply_constant_int32_int64_neon_LBB1_5:
+    MOVWU R8, R8 // mov        w8, w8
+multiply_constant_int32_int64_neon_LBB1_6:
+    MOVW (R0)(R8<<2), R9 // ldrsw      x9, [x0, x8, lsl #2]
+    MUL R9, R3, R9 // mul      x9, x3, x9
+    MOVD R9, (R1)(R8<<3) // str        x9, [x1, x8, lsl #3]
+    ADD $1, R8, R8 // add      x8, x8, #1
+    CMPW R8, R2 // cmp w2, w8
+    BGT multiply_constant_int32_int64_neon_LBB1_6
+multiply_constant_int32_int64_neon_LBB1_7:
+    RET
+
+
+TEXT ·_multiply_constant_int32_int64_neon(SB), 4, $0-32
+
+    MOVD src+0(FP), R0
+    MOVD dest+8(FP), R1
+    MOVD len+16(FP), R2
+    MOVD factor+24(FP), R3
+    CALL ·_c2goasm_native_multiply_constant_int32_int64_neon(SB)
+    RET
+
+
+TEXT ·_c2goasm_native_multiply_constant_int64_int32_neon(SB), 516, $0-32
+
+    CMPW $4, R2 // cmp w2, #4
+    BGE multiply_constant_int64_int32_neon_LBB2_2
+    MOVW $0, R8 // mov w8, #0
+    CMPW R2, R8 // cmp w8, w2
+    BLT multiply_constant_int64_int32_neon_LBB2_5
+    B multiply_constant_int64_int32_neon_LBB2_7
+multiply_constant_int64_int32_neon_LBB2_2:
+    WORD $0x1e270060 // fmov   s0, w3
+    ADD $16, R0, R8 // add     x8, x0, #16
+    MOVW $4, R9 // mov w9, #4
+    MOVD R1, R10 // mov        x10, x1
+    MOVWU R2, R11 // mov       w11, w2
+multiply_constant_int64_int32_neon_LBB2_3:
+    WORD $0xad7f8901 // ldp    q1, q2, [x8, #-16]
+    VUZP1 V2.S4, V1.S4, V1.S4 // uzp1.4s       v1, v1, v2
+    WORD $0x4f808021 // mul.4s v1, v1, v0[0]
+    WORD $0x3c810541 // str    q1, [x10], #16
+    ADD $4, R9, R9 // add      x9, x9, #4
+    ADD $32, R8, R8 // add     x8, x8, #32
+    CMP R11, R9 // cmp x9, x11
+    BLS multiply_constant_int64_int32_neon_LBB2_3
+    ANDW $2147483644, R2, R8 // and    w8, w2, #0x7ffffffc
+    CMPW R2, R8 // cmp w8, w2
+    BGE multiply_constant_int64_int32_neon_LBB2_7
+multiply_constant_int64_int32_neon_LBB2_5:
+    MOVWU R8, R8 // mov        w8, w8
+multiply_constant_int64_int32_neon_LBB2_6:
+    MOVD (R0)(R8<<3), R9 // ldr        x9, [x0, x8, lsl #3]
+    MULW R3, R9, R9 // mul     w9, w9, w3
+    MOVW R9, (R1)(R8<<2) // str        w9, [x1, x8, lsl #2]
+    ADD $1, R8, R8 // add      x8, x8, #1
+    CMPW R8, R2 // cmp w2, w8
+    BGT multiply_constant_int64_int32_neon_LBB2_6
+multiply_constant_int64_int32_neon_LBB2_7:
+    RET
+
+
+TEXT ·_multiply_constant_int64_int32_neon(SB), 4, $0-32
+
+    MOVD src+0(FP), R0
+    MOVD dest+8(FP), R1
+    MOVD len+16(FP), R2
+    MOVD factor+24(FP), R3
+    CALL ·_c2goasm_native_multiply_constant_int64_int32_neon(SB)
+    RET
+
+
+TEXT ·_c2goasm_native_multiply_constant_int64_int64_neon(SB), 516, $0-32
+
+    CMPW $2, R2 // cmp w2, #2
+    BGE multiply_constant_int64_int64_neon_LBB3_2
+    MOVW $0, R8 // mov w8, #0
+    CMPW R2, R8 // cmp w8, w2
+    BLT multiply_constant_int64_int64_neon_LBB3_5
+    B multiply_constant_int64_int64_neon_LBB3_7
+multiply_constant_int64_int64_neon_LBB3_2:
+    WORD $0x1e270060 // fmov   s0, w3
+    LSR $32, R3, R8 // lsr     x8, x3, #32
+    WORD $0x1e270101 // fmov   s1, w8
+    MOVWU R2, R8 // mov        w8, w2
+    MOVW $2, R9 // mov w9, #2
+    MOVD R0, R10 // mov        x10, x0
+    MOVD R1, R11 // mov        x11, x1
+multiply_constant_int64_int64_neon_LBB3_3:
+    WORD $0x3cc10542 // ldr    q2, [x10], #16
+    WORD $0x0ea12843 // xtn.2s v3, v2
+    VEXT $8, V2.B16, V2.B16, V4.B16 // ext.16b v4, v2, v2, #8
+    VZIP2 V4.S2, V2.S2, V2.S2 // zip2.2s       v2, v2, v4
+    WORD $0x2f80a042 // umull.2d       v2, v2, v0[0]
+    WORD $0x2f812062 // umlal.2d       v2, v3, v1[0]
+    VSHL $32, V2.D2, V2.D2 // shl.2d   v2, v2, #32
+    WORD $0x2f802062 // umlal.2d       v2, v3, v0[0]
+    WORD $0x3c810562 // str    q2, [x11], #16
+    ADD $2, R9, R9 // add      x9, x9, #2
+    CMP R8, R9 // cmp  x9, x8
+    BLS multiply_constant_int64_int64_neon_LBB3_3
+    ANDW $2147483646, R2, R8 // and    w8, w2, #0x7ffffffe
+    CMPW R2, R8 // cmp w8, w2
+    BGE multiply_constant_int64_int64_neon_LBB3_7
+multiply_constant_int64_int64_neon_LBB3_5:
+    MOVWU R8, R8 // mov        w8, w8
+multiply_constant_int64_int64_neon_LBB3_6:
+    MOVD (R0)(R8<<3), R9 // ldr        x9, [x0, x8, lsl #3]
+    MUL R3, R9, R9 // mul      x9, x9, x3
+    MOVD R9, (R1)(R8<<3) // str        x9, [x1, x8, lsl #3]
+    ADD $1, R8, R8 // add      x8, x8, #1
+    CMPW R8, R2 // cmp w2, w8
+    BGT multiply_constant_int64_int64_neon_LBB3_6
+multiply_constant_int64_int64_neon_LBB3_7:
+    RET
+
+
+TEXT ·_multiply_constant_int64_int64_neon(SB), 4, $0-32
+
+    MOVD src+0(FP), R0
+    MOVD dest+8(FP), R1
+    MOVD len+16(FP), R2
+    MOVD factor+24(FP), R3
+    CALL ·_c2goasm_native_multiply_constant_int64_int64_neon(SB)
+    RET
diff --git 
a/arrow/compute/internal/kernels/constant_factor_neon_arm64_native.go 
b/arrow/compute/internal/kernels/constant_factor_neon_arm64_native.go
new file mode 100644
index 00000000..4da2f7d7
--- /dev/null
+++ b/arrow/compute/internal/kernels/constant_factor_neon_arm64_native.go
@@ -0,0 +1,33 @@
+// 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.
+
+//go:build go1.18 && arm64 && !noasm && !appengine
+
+package kernels
+
+import "unsafe"
+
+//go:noescape
+func _c2goasm_native_multiply_constant_int32_int32_neon(src, dest 
unsafe.Pointer, len int, factor int64)
+
+//go:noescape
+func _c2goasm_native_multiply_constant_int32_int64_neon(src, dest 
unsafe.Pointer, len int, factor int64)
+
+//go:noescape
+func _c2goasm_native_multiply_constant_int64_int32_neon(src, dest 
unsafe.Pointer, len int, factor int64)
+
+//go:noescape
+func _c2goasm_native_multiply_constant_int64_int64_neon(src, dest 
unsafe.Pointer, len int, factor int64)
diff --git a/arrow/compute/internal/kernels/constant_factor_neon_arm64_test.go 
b/arrow/compute/internal/kernels/constant_factor_neon_arm64_test.go
new file mode 100644
index 00000000..cf208af7
--- /dev/null
+++ b/arrow/compute/internal/kernels/constant_factor_neon_arm64_test.go
@@ -0,0 +1,43 @@
+// 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.
+
+//go:build go1.18 && arm64 && !noasm && !appengine
+
+package kernels
+
+import (
+       "math"
+       "testing"
+)
+
+func TestNeonLengthFitsAssembly(t *testing.T) {
+       tests := []struct {
+               name   string
+               length int
+               want   bool
+       }{
+               {name: "maximum assembly length", length: math.MaxInt32, want: 
true},
+               {name: "above maximum assembly length", length: math.MaxInt32 + 
1, want: false},
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       if got := neonLengthFitsAssembly(tt.length); got != 
tt.want {
+                               t.Fatalf("neonLengthFitsAssembly(%d) = %t, want 
%t", tt.length, got, tt.want)
+                       }
+               })
+       }
+}
diff --git a/arrow/compute/internal/kernels/constant_factor_test.go 
b/arrow/compute/internal/kernels/constant_factor_test.go
new file mode 100644
index 00000000..da290fd7
--- /dev/null
+++ b/arrow/compute/internal/kernels/constant_factor_test.go
@@ -0,0 +1,241 @@
+// 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 kernels
+
+import (
+       "fmt"
+       "math/rand"
+       "reflect"
+       "runtime"
+       "strconv"
+       "testing"
+)
+
+const (
+       maxInt32Value = int32(1<<31 - 1)
+       minInt32Value = int32(-1 << 31)
+       maxInt64Value = int64(1<<63 - 1)
+       minInt64Value = int64(-1 << 63)
+)
+
+func TestMultiplyConstant(t *testing.T) {
+       lengths := []int{1, 2, 3, 4, 5, 7, 8, 15, 16, 31, 32, 33, 63, 64, 65}
+       factors := []int64{
+               0,
+               1,
+               -1,
+               3,
+               -3,
+               1_000_000,
+               1 << 31,
+               -(1 << 31),
+               1 << 40,
+               -(1 << 40),
+               maxInt64Value,
+               minInt64Value,
+       }
+
+       for _, length := range lengths {
+               input32 := makeInt32Values(length)
+               input64 := makeInt64Values(length)
+               for _, factor := range factors {
+                       name := fmt.Sprintf("length_%d/factor_%s", length, 
strconv.FormatInt(factor, 10))
+                       t.Run(name, func(t *testing.T) {
+                               assertMultiplyInt32Int32(t, input32, factor)
+                               assertMultiplyInt32Int64(t, input32, factor)
+                               assertMultiplyInt64Int32(t, input64, factor)
+                               assertMultiplyInt64Int64(t, input64, factor)
+                       })
+               }
+       }
+}
+
+func TestMultiplyConstantRandomized(t *testing.T) {
+       rng := rand.New(rand.NewSource(23))
+       for iteration := 0; iteration < 64; iteration++ {
+               for _, length := range []int{1, 2, 3, 4, 5, 7, 8, 9, 15, 16, 
17, 65, 257} {
+                       input32, input64 := make([]int32, length), 
make([]int64, length)
+                       for i := range input32 {
+                               input32[i] = int32(rng.Uint32())
+                               input64[i] = int64(rng.Uint64())
+                       }
+                       factor := int64(rng.Uint64())
+                       assertMultiplyInt32Int32(t, input32, factor)
+                       assertMultiplyInt32Int64(t, input32, factor)
+                       assertMultiplyInt64Int32(t, input64, factor)
+                       assertMultiplyInt64Int64(t, input64, factor)
+               }
+       }
+}
+
+func assertMultiplyInt32Int32(t *testing.T, input []int32, factor int64) {
+       t.Helper()
+       want := make([]int32, len(input))
+       multiplyConstantGo(input, want, factor)
+       got := make([]int32, len(input))
+       multiplyConstant(input, got, factor)
+       if !reflect.DeepEqual(got, want) {
+               t.Fatalf("int32 -> int32 mismatch for factor %d: got %v, want 
%v", factor, got, want)
+       }
+}
+
+func assertMultiplyInt32Int64(t *testing.T, input []int32, factor int64) {
+       t.Helper()
+       want := make([]int64, len(input))
+       multiplyConstantGo(input, want, factor)
+       got := make([]int64, len(input))
+       multiplyConstant(input, got, factor)
+       if !reflect.DeepEqual(got, want) {
+               t.Fatalf("int32 -> int64 mismatch for factor %d: got %v, want 
%v", factor, got, want)
+       }
+}
+
+func assertMultiplyInt64Int32(t *testing.T, input []int64, factor int64) {
+       t.Helper()
+       want := make([]int32, len(input))
+       multiplyConstantGo(input, want, factor)
+       got := make([]int32, len(input))
+       multiplyConstant(input, got, factor)
+       if !reflect.DeepEqual(got, want) {
+               t.Fatalf("int64 -> int32 mismatch for factor %d: got %v, want 
%v", factor, got, want)
+       }
+}
+
+func assertMultiplyInt64Int64(t *testing.T, input []int64, factor int64) {
+       t.Helper()
+       want := make([]int64, len(input))
+       multiplyConstantGo(input, want, factor)
+       got := make([]int64, len(input))
+       multiplyConstant(input, got, factor)
+       if !reflect.DeepEqual(got, want) {
+               t.Fatalf("int64 -> int64 mismatch for factor %d: got %v, want 
%v", factor, got, want)
+       }
+}
+
+func makeInt32Values(length int) []int32 {
+       values := []int32{
+               0,
+               1,
+               -1,
+               2,
+               -2,
+               123456789,
+               -123456789,
+               maxInt32Value,
+               minInt32Value,
+       }
+       result := make([]int32, length)
+       for i := range result {
+               result[i] = values[i%len(values)]
+       }
+       return result
+}
+
+func makeInt64Values(length int) []int64 {
+       values := []int64{
+               0,
+               1,
+               -1,
+               2,
+               -2,
+               1 << 40,
+               -(1 << 40),
+               maxInt64Value,
+               minInt64Value,
+       }
+       result := make([]int64, length)
+       for i := range result {
+               result[i] = values[i%len(values)]
+       }
+       return result
+}
+
+func BenchmarkMultiplyConstant(b *testing.B) {
+       for _, size := range []int{1 << 10, 1 << 14, 1 << 20} {
+               b.Run(fmt.Sprintf("int32_int32/%d/generic", size), func(b 
*testing.B) {
+                       input := makeInt32Values(size)
+                       output := make([]int32, size)
+                       benchmarkMultiplyConstant(b, int64(size*4), output, 
func() {
+                               multiplyConstantGo(input, output, 1_000_000)
+                       })
+               })
+               b.Run(fmt.Sprintf("int32_int32/%d/dispatch", size), func(b 
*testing.B) {
+                       input := makeInt32Values(size)
+                       output := make([]int32, size)
+                       benchmarkMultiplyConstant(b, int64(size*4), output, 
func() {
+                               multiplyConstant(input, output, 1_000_000)
+                       })
+               })
+
+               b.Run(fmt.Sprintf("int32_int64/%d/generic", size), func(b 
*testing.B) {
+                       input := makeInt32Values(size)
+                       output := make([]int64, size)
+                       benchmarkMultiplyConstant(b, int64(size*4), output, 
func() {
+                               multiplyConstantGo(input, output, 1_000_000)
+                       })
+               })
+               b.Run(fmt.Sprintf("int32_int64/%d/dispatch", size), func(b 
*testing.B) {
+                       input := makeInt32Values(size)
+                       output := make([]int64, size)
+                       benchmarkMultiplyConstant(b, int64(size*4), output, 
func() {
+                               multiplyConstant(input, output, 1_000_000)
+                       })
+               })
+
+               b.Run(fmt.Sprintf("int64_int32/%d/generic", size), func(b 
*testing.B) {
+                       input := makeInt64Values(size)
+                       output := make([]int32, size)
+                       benchmarkMultiplyConstant(b, int64(size*8), output, 
func() {
+                               multiplyConstantGo(input, output, 1_000_000)
+                       })
+               })
+               b.Run(fmt.Sprintf("int64_int32/%d/dispatch", size), func(b 
*testing.B) {
+                       input := makeInt64Values(size)
+                       output := make([]int32, size)
+                       benchmarkMultiplyConstant(b, int64(size*8), output, 
func() {
+                               multiplyConstant(input, output, 1_000_000)
+                       })
+               })
+
+               b.Run(fmt.Sprintf("int64_int64/%d/generic", size), func(b 
*testing.B) {
+                       input := makeInt64Values(size)
+                       output := make([]int64, size)
+                       benchmarkMultiplyConstant(b, int64(size*8), output, 
func() {
+                               multiplyConstantGo(input, output, 1_000_000)
+                       })
+               })
+               b.Run(fmt.Sprintf("int64_int64/%d/dispatch", size), func(b 
*testing.B) {
+                       input := makeInt64Values(size)
+                       output := make([]int64, size)
+                       benchmarkMultiplyConstant(b, int64(size*8), output, 
func() {
+                               multiplyConstant(input, output, 1_000_000)
+                       })
+               })
+       }
+}
+
+func benchmarkMultiplyConstant(b *testing.B, bytes int64, output any, fn 
func()) {
+       b.Helper()
+       b.ReportAllocs()
+       b.SetBytes(bytes)
+       b.ResetTimer()
+       for i := 0; i < b.N; i++ {
+               fn()
+       }
+       b.StopTimer()
+       runtime.KeepAlive(output)
+}

Reply via email to