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:
+76
@@ -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) }
|
||||
}
|
||||
}
|
||||
+492
@@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+43
@@ -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) }
|
||||
}
|
||||
+42
@@ -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) }
|
||||
}
|
||||
Reference in New Issue
Block a user