docs: document failed UMULH approaches on Android
Remove crypto-intrinsics module and API-tiered dispatch files. Revert to the pure-Kotlin fallback for Android field multiply. Three approaches to reach hardware UMULH were tested on Pixel 8 (Android 16, API 36) and all failed: 1. Direct Math.unsignedMultiplyHigh from Kotlin: D8 replaces with pure-Java backport when app minSdk=26 < 35. 2. MethodHandle.invokeExact via Java helper module: Bytecode correct (invoke-polymorphic JJ→J, zero boxing), but ART can't inline invoke-polymorphic. 25ns/call → 2.2x regression. 3. Full fieldMulReduce in Java, module with minSdk=35: D8 runs at APP level with app's minSdk=26, not library's minSdk=35. Trace confirmed: FieldMulIntrinsic$$ExternalSyntheticBackport0.m still generated. Plus Long.compareUnsigned (282ns/call) is slower than Kotlin's inlined XOR trick (~0ns). The Kotlin inline+crossinline pattern remains optimal for ART: - unsignedMultiplyHighFallback inlined at each call site - uLtInline uses XOR+compare (no method call) - Fused function fits ART's inlining budget UMULH on Android requires raising app minSdk to 35. https://claude.ai/code/session_015CtM5k88rF7WFgX8o2AGNR
This commit is contained in:
-46
@@ -1,46 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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
|
|
||||||
|
|
||||||
/**
|
|
||||||
* API 31-34 field multiply/square — PLACEHOLDER for Math.multiplyHigh intrinsic.
|
|
||||||
*
|
|
||||||
* Same D8 desugaring and Kotlin @PolymorphicSignature limitations as [FieldMulApi35].
|
|
||||||
* Uses the pure-Kotlin fallback until a Java helper module is available.
|
|
||||||
*/
|
|
||||||
internal object FieldMulApi31 {
|
|
||||||
fun fieldMulReduce(
|
|
||||||
out: LongArray,
|
|
||||||
a: LongArray,
|
|
||||||
b: LongArray,
|
|
||||||
w: LongArray,
|
|
||||||
) {
|
|
||||||
fieldMulReduceWith(out, a, b, w) { x, y -> unsignedMultiplyHighFallback(x, y) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun fieldSqrReduce(
|
|
||||||
out: LongArray,
|
|
||||||
a: LongArray,
|
|
||||||
w: LongArray,
|
|
||||||
) {
|
|
||||||
fieldSqrReduceWith(out, a, w) { x, y -> unsignedMultiplyHighFallback(x, y) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-60
@@ -1,60 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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
|
|
||||||
|
|
||||||
/**
|
|
||||||
* API 35+ field multiply/square — PLACEHOLDER for Math.unsignedMultiplyHigh intrinsic.
|
|
||||||
*
|
|
||||||
* Currently uses the pure-Kotlin fallback because both known approaches to reach
|
|
||||||
* the hardware UMULH intrinsic from Kotlin are blocked:
|
|
||||||
*
|
|
||||||
* 1. Direct call: D8 desugaring replaces Math.unsignedMultiplyHigh with a pure-Java
|
|
||||||
* synthetic backport (ExternalSyntheticBackport0.m) when minSdk < 35. The backport
|
|
||||||
* never reaches the hardware intrinsic — verified via dexdump.
|
|
||||||
*
|
|
||||||
* 2. MethodHandle.invokeExact: Kotlin cannot generate type-exact invoke-polymorphic
|
|
||||||
* with primitive signature (JJ)J. Only Java's javac has @PolymorphicSignature
|
|
||||||
* support. Kotlin compiles invokeExact as regular invokevirtual with Object[]
|
|
||||||
* boxing — 3 allocations per call × 21 calls per field mul.
|
|
||||||
*
|
|
||||||
* TO UNLOCK UMULH: Add a Java helper class (MulHighInvoker.java) in a separate
|
|
||||||
* Android module or via AGP's Java source set support. The Java class would call
|
|
||||||
* MethodHandle.invokeExact(long, long) → long with zero boxing, producing
|
|
||||||
* invoke-polymorphic that D8 cannot desugar and ART intrinsifies to UMULH.
|
|
||||||
*/
|
|
||||||
internal object FieldMulApi35 {
|
|
||||||
fun fieldMulReduce(
|
|
||||||
out: LongArray,
|
|
||||||
a: LongArray,
|
|
||||||
b: LongArray,
|
|
||||||
w: LongArray,
|
|
||||||
) {
|
|
||||||
fieldMulReduceWith(out, a, b, w) { x, y -> unsignedMultiplyHighFallback(x, y) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun fieldSqrReduce(
|
|
||||||
out: LongArray,
|
|
||||||
a: LongArray,
|
|
||||||
w: LongArray,
|
|
||||||
) {
|
|
||||||
fieldSqrReduceWith(out, a, w) { x, y -> unsignedMultiplyHighFallback(x, y) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+17
-31
@@ -21,54 +21,40 @@
|
|||||||
package com.vitorpamplona.quartz.utils.secp256k1
|
package com.vitorpamplona.quartz.utils.secp256k1
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Android field multiply/square — dispatches to the best available intrinsic.
|
* Uses the pure-Kotlin fused fallback for all API levels.
|
||||||
*
|
*
|
||||||
* Three implementations, selected once at class load:
|
* THREE approaches to reach hardware UMULH were tested and all failed:
|
||||||
* - API 35+ (Android 15): Math.unsignedMultiplyHigh via MethodHandle → UMULH on ARM64
|
|
||||||
* - API 31+ (Android 12): Math.multiplyHigh via MethodHandle + unsigned correction → SMULH
|
|
||||||
* - Below: pure-Kotlin fallback (4 Long multiplies + shifts)
|
|
||||||
*
|
*
|
||||||
* WHY MethodHandle instead of direct Math.xxx calls?
|
* 1. Direct Math.unsignedMultiplyHigh: D8 replaces with pure-Java backport
|
||||||
* D8 desugaring replaces ALL direct references to Math.unsignedMultiplyHigh and
|
* when app minSdk=26 < 35, even inside libraries with minSdk=35.
|
||||||
* Math.multiplyHigh with synthetic backport wrappers (ExternalSyntheticBackport0.m)
|
|
||||||
* when minSdk < the API that introduced them. These backports are pure-Java fallbacks
|
|
||||||
* that never reach the hardware intrinsic — even on devices running API 35+.
|
|
||||||
* MethodHandle.invokeExact uses DEX invoke-polymorphic which D8 cannot desugar,
|
|
||||||
* so ART resolves directly to the real Math method and intrinsifies it.
|
|
||||||
*
|
*
|
||||||
* The API level check happens ONCE here, not per-multiply-high call. Each fused
|
* 2. MethodHandle.invokeExact (crypto-intrinsics Java module): Bytecode correct
|
||||||
* fieldMulReduceWith inlines the intrinsic lambda at every call site (~16 per mul),
|
* (invoke-polymorphic JJ→J, zero boxing), but ART can't inline through
|
||||||
* producing tight bytecode with zero per-call dispatch overhead.
|
* invoke-polymorphic. 25ns/call × 12K calls = 2.2x regression.
|
||||||
|
*
|
||||||
|
* 3. Full fieldMulReduce in Java with minSdk=35 module: D8 desugaring runs
|
||||||
|
* at APP level with app's minSdk=26, not library's minSdk=35. All
|
||||||
|
* Math.unsignedMultiplyHigh calls still get backported. Plus Java's
|
||||||
|
* Long.compareUnsigned (282ns/call) is slower than Kotlin's inlined XOR trick.
|
||||||
|
*
|
||||||
|
* The Kotlin inline+crossinline pattern produces the best ART code:
|
||||||
|
* - unsignedMultiplyHighFallback is inlined at each call site (zero dispatch)
|
||||||
|
* - uLtInline uses XOR+compare (zero method call, ~0ns overhead)
|
||||||
|
* - The fused function stays within ART's inlining budget
|
||||||
*/
|
*/
|
||||||
|
|
||||||
private val HAS_UNSIGNED_MULTIPLY_HIGH = android.os.Build.VERSION.SDK_INT >= 35
|
|
||||||
private val HAS_MULTIPLY_HIGH = android.os.Build.VERSION.SDK_INT >= 31
|
|
||||||
|
|
||||||
internal actual fun fieldMulReduce(
|
internal actual fun fieldMulReduce(
|
||||||
out: LongArray,
|
out: LongArray,
|
||||||
a: LongArray,
|
a: LongArray,
|
||||||
b: LongArray,
|
b: LongArray,
|
||||||
w: LongArray,
|
w: LongArray,
|
||||||
) {
|
) {
|
||||||
if (HAS_UNSIGNED_MULTIPLY_HIGH) {
|
|
||||||
FieldMulApi35.fieldMulReduce(out, a, b, w)
|
|
||||||
} else if (HAS_MULTIPLY_HIGH) {
|
|
||||||
FieldMulApi31.fieldMulReduce(out, a, b, w)
|
|
||||||
} else {
|
|
||||||
fieldMulReduceWith(out, a, b, w) { x, y -> unsignedMultiplyHighFallback(x, y) }
|
fieldMulReduceWith(out, a, b, w) { x, y -> unsignedMultiplyHighFallback(x, y) }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
internal actual fun fieldSqrReduce(
|
internal actual fun fieldSqrReduce(
|
||||||
out: LongArray,
|
out: LongArray,
|
||||||
a: LongArray,
|
a: LongArray,
|
||||||
w: LongArray,
|
w: LongArray,
|
||||||
) {
|
) {
|
||||||
if (HAS_UNSIGNED_MULTIPLY_HIGH) {
|
|
||||||
FieldMulApi35.fieldSqrReduce(out, a, w)
|
|
||||||
} else if (HAS_MULTIPLY_HIGH) {
|
|
||||||
FieldMulApi31.fieldSqrReduce(out, a, w)
|
|
||||||
} else {
|
|
||||||
fieldSqrReduceWith(out, a, w) { x, y -> unsignedMultiplyHighFallback(x, y) }
|
fieldSqrReduceWith(out, a, w) { x, y -> unsignedMultiplyHighFallback(x, y) }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user