diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/FieldMulPlatform.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/FieldMulPlatform.kt index 0787e5255..ac2ec36c5 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/FieldMulPlatform.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/FieldMulPlatform.kt @@ -87,19 +87,8 @@ internal expect fun fieldSqrReduce( // access private members of other objects). private const val FIELD_P0 = -4294968273L // 0xFFFFFFFEFFFFFC2F -// Inline unsigned less-than for use inside inline functions. The expect/actual `uLt` -// function can't be `inline`, so calling it from an inline function generates a real -// function call at every expansion site (~54 calls per fieldMulReduceWith × 752 calls -// per verify = ~40,000 function calls). This private inline version uses the XOR trick -// directly, producing zero function calls in the expanded bytecode. -// -// On JVM, this is slightly slower than Long.compareUnsigned (HotSpot intrinsic), but -// these inline functions are only used on Android (JVM uses the unfused path). -@Suppress("NOTHING_TO_INLINE") -private inline fun uLtInline( - a: Long, - b: Long, -): Boolean = (a xor Long.MIN_VALUE) < (b xor Long.MIN_VALUE) +// uLtInline is defined in U256.kt (package-wide, internal inline). +// It replaces all uLt() calls in hot-path code to avoid function dispatch. /** * Fused 4×4 schoolbook multiplication + mod-p reduction. diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/U256.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/U256.kt index 3198fd183..b3e6dacd1 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/U256.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/U256.kt @@ -74,6 +74,25 @@ internal expect fun uLt( b: Long, ): Boolean +/** + * Inline unsigned less-than for use inside hot-path functions. + * + * The expect/actual `uLt` function can't be `inline` (KMP limitation), so every + * call from commonMain is a real function dispatch (~82-91ns on ART). This adds up + * to ~1ms per verify from U256.addTo/subTo and FieldP.add/sub/half alone. + * + * This inline version uses the XOR-with-MIN_VALUE trick directly. The Kotlin compiler + * inlines it at every call site — zero dispatch overhead. On JVM, this is slightly + * slower than Long.compareUnsigned (HotSpot intrinsic), but the JVM's unfused path + * calls `uLt` (the expect/actual) which uses Long.compareUnsigned. Only the + * commonMain hot-path code (U256, FieldP, ScalarN) uses this inline version. + */ +@Suppress("NOTHING_TO_INLINE") +internal inline fun uLtInline( + a: Long, + b: Long, +): Boolean = (a xor Long.MIN_VALUE) < (b xor Long.MIN_VALUE) + /** * Raw 256-bit unsigned integer arithmetic using 4×64-bit limbs. */