perf: remove lazy delegates from ECPoint precomputed tables

Convert gOddTable, gLamTable, and combTable from `by lazy` to eager
initialization. These tables are accessed on every verify (mulDoubleG)
and sign (mulG) call.

On K/Native, each `by lazy` access goes through
SynchronizedLazyImpl.getValue() — a thread-safe lock check + memory
barrier. The native disassembly showed 2 SynchronizedLazyImpl calls
per mulDoubleG invocation.

On JVM, Kotlin's lazy delegates use double-checked locking via
SynchronizedLazyImpl, adding interface dispatch + volatile read on
every access.

Move the `scratch` (ScratchLocal) field declaration before the table
fields to satisfy initialization order — buildGOddTable() and
buildCombTable() use doublePoint/addPoints which call scratch.get().

https://claude.ai/code/session_015CtM5k88rF7WFgX8o2AGNR
This commit is contained in:
Claude
2026-04-09 18:01:15 +00:00
parent 39ce3ad260
commit 34bef2438e
@@ -68,6 +68,14 @@ internal object ECPoint {
/** Curve constant b = 7 in y² = x³ + 7. */
private val B = longArrayOf(7L, 0L, 0L, 0L)
// Thread-local scratch — declared before precomputed tables because
// buildGOddTable() and buildCombTable() use doublePoint/addPoints which
// call scratch.get(). Must be initialized before those table fields.
private val scratch = ScratchLocal { PointScratch() }
/** Get thread-local scratch. Call once at the top-level entry point. */
internal fun getScratch(): PointScratch = scratch.get()
/**
* wNAF window width for the G-side of scalar multiplication (mulDoubleG/verify).
* Width w uses a table of 2^(w-2) odd multiples. Larger windows mean fewer
@@ -84,14 +92,14 @@ internal object ECPoint {
/**
* Precomputed G odd-multiples for wNAF: gOddTable[i] = (2i+1)·G as affine, for i in 0..G_TABLE_SIZE-1.
* Used by mulG and mulDoubleG. Lazily initialized on first use.
* Used by mulG and mulDoubleG. Eagerly initialized to avoid SynchronizedLazyImpl
* dispatch on every verify call (~200 instructions of lock-check overhead per access).
*/
private val gOddTable: Array<AffinePoint> by lazy { buildGOddTable() }
private val gOddTable: Array<AffinePoint> = buildGOddTable()
/** Precomputed λ(G) odd-multiples for GLV: gLamTable[i] = λ((2i+1)·G) as affine. */
private val gLamTable: Array<AffinePoint> by lazy {
private val gLamTable: Array<AffinePoint> =
Array(G_TABLE_SIZE) { AffinePoint(FieldP.mul(gOddTable[it].x, Glv.BETA), gOddTable[it].y.copyOf()) }
}
// ==================== P-side wNAF table cache ====================
//
@@ -203,7 +211,7 @@ internal object ECPoint {
private const val COMB_SPACING = 4
private const val COMB_POINTS = 1 shl COMB_TEETH // 64
private val combTable: Array<AffinePoint> by lazy { buildCombTable() }
private val combTable: Array<AffinePoint> = buildCombTable()
private fun buildCombTable(): Array<AffinePoint> {
// Tooth base points: toothG[i] = 2^(i * SPACING) * G
@@ -247,13 +255,6 @@ internal object ECPoint {
}
}
// ==================== Thread-local scratch ====================
private val scratch = ScratchLocal { PointScratch() }
/** Get thread-local scratch. Call once at the top-level entry point. */
internal fun getScratch(): PointScratch = scratch.get()
// ==================== Point Doubling (3M + 4S) ====================
// Point doubling: out = 2·p.