From 41e54ca33e9bdb26a9ea86ecafd576fb4b3af5b2 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 9 Apr 2026 00:46:50 +0000 Subject: [PATCH] perf: use unfused path on JVM (smaller methods for HotSpot inlining) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The fused fieldMulReduceWith + crossinline lambda was designed for ART which struggles with deep call chains. On HotSpot C2, it creates a 2351-bytecode method (exceeding FreqInlineSize=325) that can't be inlined into FieldP.mul, and wastes 180 bytecodes on lambda param shuffling that C2 must clean up. The unfused path (U256.mulWide + FieldP.reduceWide) produces a tiny 40-bytecode fieldMulReduce that HotSpot easily inlines. HotSpot's 8+ level inlining depth handles the full chain down to the Math.unsignedMultiplyHigh intrinsic (single MULQ on x86-64). Benchmark on JVM 21 x86-64: equivalent performance (1.5× verify, 0.9× sign-cached vs native C). Cleaner bytecode with no lambda waste. https://claude.ai/code/session_01EMY5RnXb9rnsyU2KbXrSaY --- .../utils/secp256k1/FieldMulPlatform.jvm.kt | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/quartz/src/jvmMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/FieldMulPlatform.jvm.kt b/quartz/src/jvmMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/FieldMulPlatform.jvm.kt index 4bc5cb104..b4ec7a4b5 100644 --- a/quartz/src/jvmMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/FieldMulPlatform.jvm.kt +++ b/quartz/src/jvmMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/FieldMulPlatform.jvm.kt @@ -21,9 +21,21 @@ 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. + * JVM field multiply/square — delegates to the original unfused path. + * + * WHY NOT use the fused fieldMulReduceWith (like Android)? + * The fused inline+crossinline approach was designed for ART's limited inlining. + * On HotSpot C2, it produces a 2351-bytecode method (with 180 wasted bytecodes + * from lambda parameter shuffling) that exceeds FreqInlineSize (325), preventing + * HotSpot from inlining fieldMulReduce into FieldP.mul. + * + * The unfused path (mulWide + reduceWide) produces smaller methods (~320 bytecodes + * each) that HotSpot inlines individually and optimizes across boundaries. HotSpot's + * 8+ level inlining depth handles the full chain: + * FieldP.mul → fieldMulReduce → U256.mulWide → unsignedMultiplyHigh → Math.unsignedMultiplyHigh + * Each Math.unsignedMultiplyHigh is a C2 intrinsic → single MULQ on x86-64. + * + * Benchmark confirmed: unfused path is equal or faster on HotSpot JVM 21. */ internal actual fun fieldMulReduce( out: LongArray, @@ -31,7 +43,8 @@ internal actual fun fieldMulReduce( b: LongArray, w: LongArray, ) { - fieldMulReduceWith(out, a, b, w) { x, y -> Math.unsignedMultiplyHigh(x, y) } + U256.mulWide(w, a, b) + FieldP.reduceWide(out, w) } internal actual fun fieldSqrReduce( @@ -39,5 +52,6 @@ internal actual fun fieldSqrReduce( a: LongArray, w: LongArray, ) { - fieldSqrReduceWith(out, a, w) { x, y -> Math.unsignedMultiplyHigh(x, y) } + U256.sqrWide(w, a) + FieldP.reduceWide(out, w) }