From 5add793abea6c00230962e08ddd51e5e6dfa18ff Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 8 Apr 2026 22:05:36 +0000 Subject: [PATCH] perf: split Android fieldMulReduce into per-API methods for ART JIT The previous approach inlined all 3 API-level branches into a single fieldMulReduce method (~600 DEX instructions). ART's register allocator produces suboptimal code for such large methods, causing stack spills that negate the benefit of eliminating wrapper calls. Split each API-level path into its own private function (~200 DEX instructions each). ART JIT-compiles only the hot one (fieldMulApi35 on API 35+) with full optimization, while the tiny dispatch function (fieldMulReduce) is easily devirtualized and inlined. https://claude.ai/code/session_01EMY5RnXb9rnsyU2KbXrSaY --- .../secp256k1/FieldMulPlatform.android.kt | 97 ++++++++++++++----- 1 file changed, 75 insertions(+), 22 deletions(-) diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/FieldMulPlatform.android.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/FieldMulPlatform.android.kt index 393ebd13d..c309c66f7 100644 --- a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/FieldMulPlatform.android.kt +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/FieldMulPlatform.android.kt @@ -23,23 +23,51 @@ 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: + * Each API level gets its OWN private function with the inline expansion, + * so ART's JIT can fully optimize each as a standalone ~200 DEX-instruction + * method. Previously, having all 3 branches inline-expanded in a single + * function created ~600 DEX instructions, causing ART's register allocator + * to produce suboptimal code with excessive stack spills. * - * 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. + * The dispatch functions (fieldMulReduce/fieldSqrReduce) are tiny (~15 DEX + * instructions) and always take the same branch, so ART profiles and + * devirtualizes them efficiently. */ private val API = android.os.Build.VERSION.SDK_INT +// ==================== Multiply: separate methods per API level ==================== + +@Suppress("NewApi") +private fun fieldMulApi35( + out: LongArray, + a: LongArray, + b: LongArray, + w: LongArray, +) { + fieldMulReduceWith(out, a, b, w) { x, y -> Math.unsignedMultiplyHigh(x, y) } +} + +private fun fieldMulApi31( + out: LongArray, + a: LongArray, + b: LongArray, + w: LongArray, +) { + fieldMulReduceWith(out, a, b, w) { x, y -> + Math.multiplyHigh(x, y) + (x and (y shr 63)) + (y and (x shr 63)) + } +} + +private fun fieldMulFallback( + out: LongArray, + a: LongArray, + b: LongArray, + w: LongArray, +) { + fieldMulReduceWith(out, a, b, w) { x, y -> unsignedMultiplyHighFallback(x, y) } +} + @Suppress("NewApi") internal actual fun fieldMulReduce( out: LongArray, @@ -48,16 +76,43 @@ internal actual fun fieldMulReduce( w: LongArray, ) { if (API >= 35) { - fieldMulReduceWith(out, a, b, w) { x, y -> Math.unsignedMultiplyHigh(x, y) } + fieldMulApi35(out, a, b, w) } 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)) - } + fieldMulApi31(out, a, b, w) } else { - fieldMulReduceWith(out, a, b, w) { x, y -> unsignedMultiplyHighFallback(x, y) } + fieldMulFallback(out, a, b, w) } } +// ==================== Square: separate methods per API level ==================== + +@Suppress("NewApi") +private fun fieldSqrApi35( + out: LongArray, + a: LongArray, + w: LongArray, +) { + fieldSqrReduceWith(out, a, w) { x, y -> Math.unsignedMultiplyHigh(x, y) } +} + +private fun fieldSqrApi31( + out: LongArray, + a: LongArray, + w: LongArray, +) { + fieldSqrReduceWith(out, a, w) { x, y -> + Math.multiplyHigh(x, y) + (x and (y shr 63)) + (y and (x shr 63)) + } +} + +private fun fieldSqrFallback( + out: LongArray, + a: LongArray, + w: LongArray, +) { + fieldSqrReduceWith(out, a, w) { x, y -> unsignedMultiplyHighFallback(x, y) } +} + @Suppress("NewApi") internal actual fun fieldSqrReduce( out: LongArray, @@ -65,12 +120,10 @@ internal actual fun fieldSqrReduce( w: LongArray, ) { if (API >= 35) { - fieldSqrReduceWith(out, a, w) { x, y -> Math.unsignedMultiplyHigh(x, y) } + fieldSqrApi35(out, a, w) } else if (API >= 31) { - fieldSqrReduceWith(out, a, w) { x, y -> - Math.multiplyHigh(x, y) + (x and (y shr 63)) + (y and (x shr 63)) - } + fieldSqrApi31(out, a, w) } else { - fieldSqrReduceWith(out, a, w) { x, y -> unsignedMultiplyHighFallback(x, y) } + fieldSqrFallback(out, a, w) } }