pasta: x86-64 MULX/ADX assembly field arithmetic behind x86_64-asm - #222
pasta: x86-64 MULX/ADX assembly field arithmetic behind x86_64-asm#222ebfull wants to merge 4 commits into
x86_64-asm#222Conversation
A transcription of the aarch64-asm backend's five-limb CIOS rounds into one inline asm! block using MULX with dual ADCX/ADOX carry chains, exploiting the shared Pasta modulus shape (p[2] = 0, p[3] = 2^62) so a single routine serves Fp and Fq. Operand limbs are addressed through readonly pointers rather than pinned registers (twelve pinned limbs plus staging temporaries exceed x86-64's allocatable set); squaring routes through the multiplication (measured on Apple silicon and Skylake-X, a dedicated squaring block does not beat it). The sqr_n_mul fallback now routes through the runtime dispatchers so chains square and multiply in assembly on backends without a fused chain. Measured on i9-7960X (Skylake-X): Fp mul 25.96 -> 21.01 ns (-19.8%), Fq mul 20.46 ns; square unchanged. Differential tests: 100k random canonical pairs per field against the portable path, 100k unreduced-lhs near-modulus stress per field, edge matrices, and debug canonicity panics; full 200-test suite green with multicore,orbits,x86_64-asm. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The fp/fq benches' square cell called the inherent (always-portable) Fp::square, which shadows Field::square, the runtime-dispatched production path — so the cell measured the portable squaring even under the assembly features. That artifact mis-sized an assembly squaring decision twice: it made the AArch64 inline squaring look no better than portable (it is in fact 5-6% faster than the assembly multiplication; a square-through-mul routing proposed on the bad reading was correctly rejected by measurement on M4 Max), and it made the x86-64 backend's mul-routed squaring look like a wash (with the fixed cell it measures 21.94 -> 20.97 ns, ~1.05x over portable). The cell now calls the trait method explicitly; the x86_64_asm module docs and changelog now state the corrected picture, with a dedicated x86-64 assembly squaring noted as measured headroom. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Transcribes the AArch64 backend's squaring into the x86-64 backend: the 512-bit square as cross products, one doubling pass, and the diagonals (ten MULX against the multiplication's sixteen), four two-sweep Montgomery cancellations on a rotating window (the consumed input pointer's register is reclaimed as the carried fifth limb), the high half folded in under the below-2p bound, and a CMOV conditional subtraction. Measured 2-5% ahead of squaring through the multiplication on Skylake-X (20.0-20.7 vs 21.0 ns across runs), mirroring the AArch64 backend's own square-over-mul margin. Two slower schedulings are pinned in the module docs so they are not retried on this microarchitecture family: squaring routed through the multiplication, and a merged interleaved-ADCX/ADOX Montgomery reduction (staged q*p operands, TEST-cleared dual chains) that measured ~10% slower than the two short sequential sweeps (22.3 vs 20.2 ns) despite the shorter nominal dependency length. Differential tests (100k random canonical squares per field against the portable path, plus edge matrices) and the full suite pass in release and debug under multicore,orbits,x86_64-asm. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Correct the x86_64_asm module's canonicity contract: only the canonical-rhs/canonical-input preconditions are debug-asserted; the unreduced-lhs limb bound is not (and cannot be), no current caller passes an unreduced lhs, and the differential tests pin the allowance. - Drop the stale "(squaring routes through it)" changelog parenthetical that contradicted the dedicated-squaring text in the same entry. - Note in sqr_n_mul_runtime's doc that only the AArch64 backend fuses the chain, and note in CI that the pasta --all-features lanes execute the x86-64 assembly and therefore require ADX/BMI2 runners. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
will review this and retro #217 for my own curiosity. |
|
retro reviewed #218 and #217. now that #218 has landed, we should be careful during rebase since it could replace the faster lazy chains on main with assembly squares. will benchmark, but hard to say what the factor improvement this assembly backend currently provides since this impl partially conflicts with #218. |
|
didn’t have access to my linux box, so installed linux/amd64 container in orbstack (docker env running rosetta-emulated x86 with BMI2 / ADX extensions, but learned that doesn’t work since rosetta path doesn’t expose/emulate the required BMI2/ADX instructions). so ran a hosted runner workflow (against rebased version of this branch) which has BMI2/ADX and found:
|
|
Low spec bug: the contract says no production caller passes an unreduced lhs, but posted with help from fable 5. |
|
@v12sec pls review |
|
Note Complete: Audit complete. V12 did not find any issues that need review. Open the full results here. Analyzed six files, diff |


Pulled out of #217 (per review there) so the orbits calibration can land without entangling the x86-64 field arithmetic with #218's in-flight squaring work. This is the
x86_64-asmbackend exactly as reviewed on that branch, rebased onto current main. It touches the same files as #218 (fields.rs,fields/fp.rs,fields/fq.rs, the pasta changelog), so whichever of the two lands second rebases — happy for that to be this one.What this adds
MULX/ADCX/ADOX Montgomery multiplication plus a dedicated assembly squaring behind a new opt-in
x86_64-asmfeature: a transcription of the aarch64 backend's five-limb CIOS rounds with the same canonicity contract and bounds analysis. Requires BMI2+ADX (Broadwell / Zen or newer; documented to fault on older CPUs); no-op on other architectures. Measured on Skylake-X: mul 25.96 → 21.01 ns, square 21.94 → ~20.3 ns; end to end (within the #217 stack) +11–41% verifier and +2–21% prover. Two slower schedulings are pinned in the module docs so they are not retried: square-through-mul (2–5% behind the dedicated square) and interleaved-ADCX/ADOX reduction sweeps (~10% behind two short sequential sweeps).Alongside it, a bench fix: the fp/fq
squarecell called the inherent always-portablesquare, which shadowsField::square— so it measured the portable path under every asm feature and had mis-sized one squaring decision per host before this series. It now calls the trait method, i.e. the cell measures whichever backend is compiled in (relevant to benchmarking #218's portable squaring too: portable is what it measures unless an asm feature is on).Validation
multicore,orbits,x86_64-asmon an ADX/BMI2 host; rustfmt clean.--all-featureslanes execute the assembly, so they require ADX/BMI2 runners (true of GitHub's current fleet).🤖 Generated with Claude Code