perf: fuse field multiply+reduce to eliminate ART wrapper overhead

On Android (ART), the unsignedMultiplyHigh wrapper function has per-call
branching for API level detection that prevents ART's JIT from inlining
the intrinsic into the hot loop. This creates ~10,000 extra function
calls per signature verify (20 wrapper calls × 500 field muls).

This commit introduces fieldMulReduce/fieldSqrReduce as expect/actual
functions that fuse U256.mulWide + FieldP.reduceWide into a single
compilation unit. The multiply-high intrinsic is passed as a crossinline
lambda and inlined at each call site, producing platform-specific code
with zero wrapper overhead:

- Android API 35+: Math.unsignedMultiplyHigh inlined directly (UMULH)
- Android API 31-34: Math.multiplyHigh + correction inlined (SMULH)
- Android API <31: pure-Kotlin fallback inlined
- JVM: Math.unsignedMultiplyHigh inlined directly
- Native: pure-Kotlin fallback inlined

The API level check happens ONCE per fieldMulReduce call (outermost
branch) rather than per multiply-high call (innermost loop), so ART
profiles and JIT-compiles only the hot path.

https://claude.ai/code/session_01EMY5RnXb9rnsyU2KbXrSaY
This commit is contained in:
Claude
2026-04-08 21:51:16 +00:00
parent f81cbfd7e4
commit 084230937b
5 changed files with 657 additions and 8 deletions
@@ -0,0 +1,76 @@
/*
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.utils.secp256k1
/**
* Android field multiply/square with API-level-gated intrinsics.
*
* Dispatches ONCE per call to an API-level-specific implementation. Each implementation
* uses fieldMulReduceWith/fieldSqrReduceWith (inline) with the best available intrinsic
* inlined directly at the call site. This eliminates the per-multiply-high wrapper call
* overhead that hurts ART's JIT:
*
* Before: FieldP.mul → U256.mulWide → unsignedMultiplyHigh (branch) → Math.xxx
* = 2 extra function calls per multiply-high × 20 per field mul = 40 calls
*
* After: FieldP.mul → fieldMulReduce → (one of the inlined paths)
* = 1 function call total, Math.xxx is inlined directly into the loop
*
* The API level check is the outermost branch (not inside the hot loop), so ART
* profiles and JIT-compiles only the hot path.
*/
private val API = android.os.Build.VERSION.SDK_INT
@Suppress("NewApi")
internal actual fun fieldMulReduce(
out: LongArray,
a: LongArray,
b: LongArray,
w: LongArray,
) {
if (API >= 35) {
fieldMulReduceWith(out, a, b, w) { x, y -> Math.unsignedMultiplyHigh(x, y) }
} else if (API >= 31) {
fieldMulReduceWith(out, a, b, w) { x, y ->
Math.multiplyHigh(x, y) + (x and (y shr 63)) + (y and (x shr 63))
}
} else {
fieldMulReduceWith(out, a, b, w) { x, y -> unsignedMultiplyHighFallback(x, y) }
}
}
@Suppress("NewApi")
internal actual fun fieldSqrReduce(
out: LongArray,
a: LongArray,
w: LongArray,
) {
if (API >= 35) {
fieldSqrReduceWith(out, a, w) { x, y -> Math.unsignedMultiplyHigh(x, y) }
} else if (API >= 31) {
fieldSqrReduceWith(out, a, w) { x, y ->
Math.multiplyHigh(x, y) + (x and (y shr 63)) + (y and (x shr 63))
}
} else {
fieldSqrReduceWith(out, a, w) { x, y -> unsignedMultiplyHighFallback(x, y) }
}
}
@@ -0,0 +1,492 @@
/*
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.utils.secp256k1
// =====================================================================================
// FUSED FIELD MULTIPLY + REDUCE — PLATFORM-SPECIFIC INTRINSIC DISPATCH
// =====================================================================================
//
// On Android, the unsignedMultiplyHigh wrapper has per-call branching for API levels
// (API 35+: Math.unsignedMultiplyHigh, API 31-34: Math.multiplyHigh + correction,
// API <31: pure-Kotlin fallback). ART's JIT may not inline this wrapper due to:
// 1. Branch complexity exceeding inline-candidate size threshold
// 2. Limited inlining depth (~3 levels vs HotSpot's 8+)
//
// This fused approach eliminates ALL wrapper overhead by:
// 1. Dispatching once per call to fieldMulReduce (not per-multiply)
// 2. Inlining the chosen intrinsic into the hot loop via crossinline lambda
// 3. Combining mulWide + reduceWide into one compilation unit for the JIT
//
// The inline functions below contain the full schoolbook multiplication and mod-p
// reduction. Since they're inline, the crossinline lambda is substituted at each
// call site, producing platform-specific code with zero function call overhead
// for the multiply-high operation.
//
// Impact: eliminates ~20 wrapper calls per field multiply × ~500 field muls per
// verify = ~10,000 function calls removed from the hot path.
// =====================================================================================
/**
* Fused field multiplication: out = (a × b) mod p.
* Platform implementations call fieldMulReduceWith with the best available intrinsic.
*/
internal expect fun fieldMulReduce(
out: LongArray,
a: LongArray,
b: LongArray,
w: LongArray,
)
/**
* Fused field squaring: out = a² mod p.
* Platform implementations call fieldSqrReduceWith with the best available intrinsic.
*/
internal expect fun fieldSqrReduce(
out: LongArray,
a: LongArray,
w: LongArray,
)
// P[0] constant for reduceSelf (duplicated here because inline functions can't
// access private members of other objects).
private const val FIELD_P0 = -4294968273L // 0xFFFFFFFEFFFFFC2F
/**
* Fused 4×4 schoolbook multiplication + mod-p reduction.
*
* Combines U256.mulWide and FieldP.reduceWide into a single inline function.
* The umulh parameter is inlined at each call site, producing platform-specific
* code with direct intrinsic calls and zero wrapper overhead.
*
* @param umulh Returns the upper 64 bits of the unsigned 128-bit product.
*/
@Suppress("LongMethod")
internal inline fun fieldMulReduceWith(
out: LongArray,
a: LongArray,
b: LongArray,
w: LongArray,
umulh: (Long, Long) -> Long,
) {
// === Stage 1: 4×4 schoolbook multiplication (16 products) ===
val a0 = a[0]
val a1 = a[1]
val a2 = a[2]
val a3 = a[3]
val b0 = b[0]
val b1 = b[1]
val b2 = b[2]
val b3 = b[3]
var lo: Long
var hi: Long
var prev: Long
var s: Long
var c1: Long
var c2: Long
var carry: Long
// Row 0: a0 × [b0,b1,b2,b3]
lo = a0 * b0
w[0] = lo
carry = umulh(a0, b0)
lo = a0 * b1
s = lo + carry
c1 = if (s.toULong() < lo.toULong()) 1L else 0L
w[1] = s
carry = umulh(a0, b1) + c1
lo = a0 * b2
s = lo + carry
c1 = if (s.toULong() < lo.toULong()) 1L else 0L
w[2] = s
carry = umulh(a0, b2) + c1
lo = a0 * b3
s = lo + carry
c1 = if (s.toULong() < lo.toULong()) 1L else 0L
w[3] = s
w[4] = umulh(a0, b3) + c1
// Row 1: a1 × [b0,b1,b2,b3]
lo = a1 * b0
hi = umulh(a1, b0)
prev = w[1]
s = prev + lo
c1 = if (s.toULong() < prev.toULong()) 1L else 0L
w[1] = s
carry = hi + c1
lo = a1 * b1
hi = umulh(a1, b1)
prev = w[2]
s = prev + lo
c1 = if (s.toULong() < prev.toULong()) 1L else 0L
s += carry
c2 = if (s.toULong() < carry.toULong()) 1L else 0L
w[2] = s
carry = hi + c1 + c2
lo = a1 * b2
hi = umulh(a1, b2)
prev = w[3]
s = prev + lo
c1 = if (s.toULong() < prev.toULong()) 1L else 0L
s += carry
c2 = if (s.toULong() < carry.toULong()) 1L else 0L
w[3] = s
carry = hi + c1 + c2
lo = a1 * b3
hi = umulh(a1, b3)
prev = w[4]
s = prev + lo
c1 = if (s.toULong() < prev.toULong()) 1L else 0L
s += carry
c2 = if (s.toULong() < carry.toULong()) 1L else 0L
w[4] = s
w[5] = hi + c1 + c2
// Row 2: a2 × [b0,b1,b2,b3]
lo = a2 * b0
hi = umulh(a2, b0)
prev = w[2]
s = prev + lo
c1 = if (s.toULong() < prev.toULong()) 1L else 0L
w[2] = s
carry = hi + c1
lo = a2 * b1
hi = umulh(a2, b1)
prev = w[3]
s = prev + lo
c1 = if (s.toULong() < prev.toULong()) 1L else 0L
s += carry
c2 = if (s.toULong() < carry.toULong()) 1L else 0L
w[3] = s
carry = hi + c1 + c2
lo = a2 * b2
hi = umulh(a2, b2)
prev = w[4]
s = prev + lo
c1 = if (s.toULong() < prev.toULong()) 1L else 0L
s += carry
c2 = if (s.toULong() < carry.toULong()) 1L else 0L
w[4] = s
carry = hi + c1 + c2
lo = a2 * b3
hi = umulh(a2, b3)
prev = w[5]
s = prev + lo
c1 = if (s.toULong() < prev.toULong()) 1L else 0L
s += carry
c2 = if (s.toULong() < carry.toULong()) 1L else 0L
w[5] = s
w[6] = hi + c1 + c2
// Row 3: a3 × [b0,b1,b2,b3]
lo = a3 * b0
hi = umulh(a3, b0)
prev = w[3]
s = prev + lo
c1 = if (s.toULong() < prev.toULong()) 1L else 0L
w[3] = s
carry = hi + c1
lo = a3 * b1
hi = umulh(a3, b1)
prev = w[4]
s = prev + lo
c1 = if (s.toULong() < prev.toULong()) 1L else 0L
s += carry
c2 = if (s.toULong() < carry.toULong()) 1L else 0L
w[4] = s
carry = hi + c1 + c2
lo = a3 * b2
hi = umulh(a3, b2)
prev = w[5]
s = prev + lo
c1 = if (s.toULong() < prev.toULong()) 1L else 0L
s += carry
c2 = if (s.toULong() < carry.toULong()) 1L else 0L
w[5] = s
carry = hi + c1 + c2
lo = a3 * b3
hi = umulh(a3, b3)
prev = w[6]
s = prev + lo
c1 = if (s.toULong() < prev.toULong()) 1L else 0L
s += carry
c2 = if (s.toULong() < carry.toULong()) 1L else 0L
w[6] = s
w[7] = hi + c1 + c2
// === Stage 2: mod-p reduction (2^256 ≡ 2^32 + 977 mod p) ===
reduceWideInline(out, w, umulh)
}
/**
* Fused squaring + mod-p reduction (10 products via symmetry).
*
* @param umulh Returns the upper 64 bits of the unsigned 128-bit product.
*/
@Suppress("LongMethod")
internal inline fun fieldSqrReduceWith(
out: LongArray,
a: LongArray,
w: LongArray,
umulh: (Long, Long) -> Long,
) {
val a0 = a[0]
val a1 = a[1]
val a2 = a[2]
val a3 = a[3]
var lo: Long
var hi: Long
var prev: Long
var s: Long
var c1: Long
var c2: Long
var carry: Long
var v: Long
// Pass 1: cross-products a[i]*a[j] for i < j
w[0] = 0L
lo = a0 * a1
w[1] = lo
carry = umulh(a0, a1)
lo = a0 * a2
s = lo + carry
c1 = if (s.toULong() < lo.toULong()) 1L else 0L
w[2] = s
carry = umulh(a0, a2) + c1
lo = a0 * a3
s = lo + carry
c1 = if (s.toULong() < lo.toULong()) 1L else 0L
w[3] = s
w[4] = umulh(a0, a3) + c1
lo = a1 * a2
hi = umulh(a1, a2)
prev = w[3]
s = prev + lo
c1 = if (s.toULong() < prev.toULong()) 1L else 0L
w[3] = s
carry = hi + c1
lo = a1 * a3
hi = umulh(a1, a3)
prev = w[4]
s = prev + lo
c1 = if (s.toULong() < prev.toULong()) 1L else 0L
s += carry
c2 = if (s.toULong() < carry.toULong()) 1L else 0L
w[4] = s
w[5] = hi + c1 + c2
lo = a2 * a3
hi = umulh(a2, a3)
prev = w[5]
s = prev + lo
c1 = if (s.toULong() < prev.toULong()) 1L else 0L
w[5] = s
w[6] = hi + c1
// Pass 2: double all cross-products (shift left by 1 bit)
v = w[1]
w[1] = v shl 1
var shiftCarry = v ushr 63
v = w[2]
w[2] = (v shl 1) or shiftCarry
shiftCarry = v ushr 63
v = w[3]
w[3] = (v shl 1) or shiftCarry
shiftCarry = v ushr 63
v = w[4]
w[4] = (v shl 1) or shiftCarry
shiftCarry = v ushr 63
v = w[5]
w[5] = (v shl 1) or shiftCarry
shiftCarry = v ushr 63
v = w[6]
w[6] = (v shl 1) or shiftCarry
shiftCarry = v ushr 63
w[7] = shiftCarry
// Pass 3: add diagonal products a[i]²
lo = a0 * a0
hi = umulh(a0, a0)
w[0] = lo
s = w[1] + hi
c1 = if (s.toULong() < w[1].toULong()) 1L else 0L
w[1] = s
var dCarry = c1
lo = a1 * a1
hi = umulh(a1, a1)
s = w[2] + lo
c1 = if (s.toULong() < w[2].toULong()) 1L else 0L
s += dCarry
c2 = if (s.toULong() < dCarry.toULong()) 1L else 0L
w[2] = s
prev = w[3] + hi
val c3a = if (prev.toULong() < w[3].toULong()) 1L else 0L
prev += c1 + c2
val c4a = if (prev.toULong() < (c1 + c2).toULong()) 1L else 0L
w[3] = prev
dCarry = c3a + c4a
lo = a2 * a2
hi = umulh(a2, a2)
s = w[4] + lo
c1 = if (s.toULong() < w[4].toULong()) 1L else 0L
s += dCarry
c2 = if (s.toULong() < dCarry.toULong()) 1L else 0L
w[4] = s
prev = w[5] + hi
val c3b = if (prev.toULong() < w[5].toULong()) 1L else 0L
prev += c1 + c2
val c4b = if (prev.toULong() < (c1 + c2).toULong()) 1L else 0L
w[5] = prev
dCarry = c3b + c4b
lo = a3 * a3
hi = umulh(a3, a3)
s = w[6] + lo
c1 = if (s.toULong() < w[6].toULong()) 1L else 0L
s += dCarry
c2 = if (s.toULong() < dCarry.toULong()) 1L else 0L
w[6] = s
prev = w[7] + hi
prev += c1 + c2
w[7] = prev
// === Reduction ===
reduceWideInline(out, w, umulh)
}
/**
* Inline mod-p reduction of 512-bit value. Shared by fieldMulReduceWith and fieldSqrReduceWith.
*
* Uses 2^256 ≡ 2^32 + 977 (mod p) to fold high limbs into low limbs.
*/
@Suppress("LongMethod")
internal inline fun reduceWideInline(
out: LongArray,
w: LongArray,
umulh: (Long, Long) -> Long,
) {
val c = 4294968273L // 2^32 + 977
var hcLo: Long
var hcHi: Long
var s1: Long
var s2: Long
var c1: Long
var c2: Long
// Round 1: acc = lo + hi × C
hcLo = w[4] * c
hcHi = umulh(w[4], c)
s1 = w[0] + hcLo
c1 = if (s1.toULong() < w[0].toULong()) 1L else 0L
out[0] = s1
var carry = hcHi + c1
hcLo = w[5] * c
hcHi = umulh(w[5], c)
s1 = w[1] + hcLo
c1 = if (s1.toULong() < w[1].toULong()) 1L else 0L
s2 = s1 + carry
c2 = if (s2.toULong() < s1.toULong()) 1L else 0L
out[1] = s2
carry = hcHi + c1 + c2
hcLo = w[6] * c
hcHi = umulh(w[6], c)
s1 = w[2] + hcLo
c1 = if (s1.toULong() < w[2].toULong()) 1L else 0L
s2 = s1 + carry
c2 = if (s2.toULong() < s1.toULong()) 1L else 0L
out[2] = s2
carry = hcHi + c1 + c2
hcLo = w[7] * c
hcHi = umulh(w[7], c)
s1 = w[3] + hcLo
c1 = if (s1.toULong() < w[3].toULong()) 1L else 0L
s2 = s1 + carry
c2 = if (s2.toULong() < s1.toULong()) 1L else 0L
out[3] = s2
carry = hcHi + c1 + c2
// Round 2: fold carry × C
if (carry != 0L) {
val ccLo = carry * c
val ccHi = umulh(carry, c)
s1 = out[0] + ccLo
c1 = if (s1.toULong() < out[0].toULong()) 1L else 0L
out[0] = s1
var prop = ccHi + c1
if (prop != 0L) {
s1 = out[1] + prop
prop = if (s1.toULong() < out[1].toULong()) 1L else 0L
out[1] = s1
if (prop != 0L) {
s1 = out[2] + prop
prop = if (s1.toULong() < out[2].toULong()) 1L else 0L
out[2] = s1
if (prop != 0L) {
s1 = out[3] + prop
prop = if (s1.toULong() < out[3].toULong()) 1L else 0L
out[3] = s1
}
}
}
if (prop != 0L) {
s1 = out[0] + c
c1 = if (s1.toULong() < out[0].toULong()) 1L else 0L
out[0] = s1
if (c1 != 0L) {
out[1]++
if (out[1] == 0L) {
out[2]++
if (out[2] == 0L) out[3]++
}
}
}
}
// Final normalization: ensure out < p
if (out[3] == -1L && out[2] == -1L && out[1] == -1L &&
(out[0] xor Long.MIN_VALUE) >= (FIELD_P0 xor Long.MIN_VALUE)
) {
out[0] -= FIELD_P0
out[1] = 0L
out[2] = 0L
out[3] = 0L
}
}
@@ -124,8 +124,7 @@ internal object FieldP {
b: LongArray,
) {
val w = wide.get()
U256.mulWide(w, a, b)
reduceWide(out, w)
fieldMulReduce(out, a, b, w)
}
/** Multiply with caller-provided wide buffer (hot path — no ThreadLocal lookup). */
@@ -135,8 +134,7 @@ internal object FieldP {
b: LongArray,
w: LongArray,
) {
U256.mulWide(w, a, b)
reduceWide(out, w)
fieldMulReduce(out, a, b, w)
}
/** Square with ThreadLocal wide buffer (convenience for non-hot paths). */
@@ -145,8 +143,7 @@ internal object FieldP {
a: LongArray,
) {
val w = wide.get()
U256.sqrWide(w, a)
reduceWide(out, w)
fieldSqrReduce(out, a, w)
}
/** Square with caller-provided wide buffer (hot path — no ThreadLocal lookup). */
@@ -155,8 +152,7 @@ internal object FieldP {
a: LongArray,
w: LongArray,
) {
U256.sqrWide(w, a)
reduceWide(out, w)
fieldSqrReduce(out, a, w)
}
/**
@@ -0,0 +1,43 @@
/*
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.utils.secp256k1
/**
* JVM fused field multiply/square using Math.unsignedMultiplyHigh (Java 18+).
* HotSpot C2 already inlines unsignedMultiplyHigh well, but the fused path
* still helps by eliminating the mulWide → reduceWide call boundary.
*/
internal actual fun fieldMulReduce(
out: LongArray,
a: LongArray,
b: LongArray,
w: LongArray,
) {
fieldMulReduceWith(out, a, b, w) { x, y -> Math.unsignedMultiplyHigh(x, y) }
}
internal actual fun fieldSqrReduce(
out: LongArray,
a: LongArray,
w: LongArray,
) {
fieldSqrReduceWith(out, a, w) { x, y -> Math.unsignedMultiplyHigh(x, y) }
}
@@ -0,0 +1,42 @@
/*
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.utils.secp256k1
/**
* Native (iOS/macOS) fused field multiply/square using pure-Kotlin fallback.
* Kotlin/Native compiles ahead-of-time, so inline + direct call is optimal.
*/
internal actual fun fieldMulReduce(
out: LongArray,
a: LongArray,
b: LongArray,
w: LongArray,
) {
fieldMulReduceWith(out, a, b, w) { x, y -> unsignedMultiplyHighFallback(x, y) }
}
internal actual fun fieldSqrReduce(
out: LongArray,
a: LongArray,
w: LongArray,
) {
fieldSqrReduceWith(out, a, w) { x, y -> unsignedMultiplyHighFallback(x, y) }
}