Add an x86_64 Pasta field assembly backend - #53
Conversation
And two more auto-invalidated findings. Analyzed nine files, diff |
| #[cfg(all(target_feature = "adx", target_feature = "bmi2"))] | ||
| #[inline(always)] | ||
| fn adx_available() -> bool { | ||
| true | ||
| } | ||
|
|
||
| #[cfg(not(all(target_feature = "adx", target_feature = "bmi2")))] | ||
| #[inline] | ||
| fn adx_available() -> bool { |
There was a problem hiding this comment.
ACK runtime CPUID dispatch which is what i'd expect for ADX/BMI2. i'm on aarch64-apple-darwin so couldn't actually execute assembly and compare against rust without a docker env, so dispatched agents to do a static analysis instead.
There was a problem hiding this comment.
actually also did differential executions between assembly / rust on my linux box with ryzen cpu that has both ADX/BMI2. i know x86_64_asm_matches_portable_arithmetic already runs in CI, but wanted to scrutinize it more.
| env: | ||
| RUSTFLAGS: -Ctarget-feature=+adx,+bmi2 -Dwarnings | ||
| run: >- | ||
| cargo +stable check -p pasta_curves |
There was a problem hiding this comment.
few CI commands still use pasta_curves, but current main renamed it to zakura-pasta-curves.
| # Copyright Supranational LLC | ||
| # Licensed under the Apache License, Version 2.0; see LICENSE-APACHE. | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| # | ||
| # Adapted from Semolina v0.1.4, commit | ||
| # 13ffc78074a6fbec44a4fd12b7f585a0bc1dc154: | ||
| # https://github.com/supranational/semolina |
There was a problem hiding this comment.
oh supranational's code and the assembly was written before AI :)
| #[cfg(all( | ||
| test, | ||
| feature = "x86_64-asm", | ||
| target_arch = "x86_64", | ||
| any(unix, windows) | ||
| ))] | ||
| #[test] | ||
| fn x86_64_asm_matches_portable_arithmetic() { |
There was a problem hiding this comment.
something i noticed; CI does't deterministically execute ADX/BMI2. CI compiles the assembly path, but runtime tests can detect that the runner lacks those CPU instructions and use portable rust instead, so like green CI isn't actually a guarantee the assembly was executed. this is a test coverage gap imo.
|
Tested on my linux box: The variants match — everywhere I could push themI checked out PR #53 into a worktree ( Scrutiny of
|
Restore Semolina v0.1.4's sqr_n_mul_mont_pasta routine (dropped when the backend was first vendored) and route the Fp/Fq exponentiation chains through it. The routine squares its input n times and then multiplies by a second operand, keeping the accumulator in registers for the whole chain instead of paying a call, four stores, and four reloads per squaring. The transcription is instruction-for-instruction identical to upstream; only the symbol, the loop label, and the comments differ. pow_vartime now walks the exponent MSB-first and fuses each run of squarings with the multiplication that follows, and the sqrt chains in pow_by_t_minus1_over2 map their sqr-then-multiply steps directly onto the fused primitive. Both perform exactly the same sequence of field operations as before, so the variable-time profile (which depends only on the public exponent) is unchanged. On other targets the fused helper falls back to the portable square-and-multiply loop. On an Apple M-series machine, back-to-back criterion runs show Fp/invert improving from 8.36us to 5.73us (-31%) and Fp/sqrt from 6.55us to 4.84us (-26%). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace the extern-"C" calls for runtime Fp/Fq multiplication and squaring with inline asm! blocks in the aarch64_asm module. The instruction sequences are register-renamed transcriptions of the vendored Semolina routines (mul_mont_pasta, and the squaring loop body of sqr_n_mul_mont_pasta), with rhs limbs and modulus constants supplied in registers; only modulus[0], modulus[1], and inv vary between Fp and Fq, so one implementation serves both fields. Because the blocks use plain register operands and are declared options(pure, nomem, nostack), LLVM inlines the wrappers into callers and keeps field values in registers between operations, eliminating the per-operation call, out-pointer round trip, and ABI clobber traffic of the FFI boundary. Point arithmetic in curves.rs picks this up through Field::square and Mul with no changes. The fused sqr_n_mul chain and from_mont conversion remain in the .S file: the fused loop skips mid-chain canonicalization, and measurement shows it still beats a composed loop of inline-asm squarings by 5-10% on the invert/sqrt chains. The .S file is unchanged in this commit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
e386dce to
717ff5a
Compare
3f4a29a to
293c625
Compare
|
Need to redo with new data |
What changed
This adds an opt-in
pasta_curves/x86_64-asmfeature for Pasta fieldmultiplication and squaring on x86-64.
use checked-in Montgomery multiplication and squaring assembly when both are
available.
directly.
The feature is non-default and adds no C or C++ code.
Why
Field multiplication is a large part of Orchard proof generation. The ADX/BMI2
instructions provide two carry chains and a non-destructive multiply, which the
portable Rust arithmetic does not reliably expose to LLVM from its current
serial
u128dataflow.On the dedicated Linux/x86-64 benchmark host, against exact
mainatbcf22f33, a genuine one-Action Orchard proof atk = 11improved from a2.155071707 s geometric center to 2.068251146 s:
The balanced A/B/B/A run used Rust 1.88, locked dependencies, byte-identical
harnesses,
RAYON_NUM_THREADS=1, and CPU pinning. Every leg recorded zero CPUsteal, zero swap activity, and no competing benchmark/compiler process. Both
binaries generated and verified the proof before timing.
Implementation and provenance
The four object-format-specific assembly files are generated from Semolina
v0.1.4 (
supranational/semolina@13ffc780…) under Apache-2.0. They retain theupstream copyright and SPDX header and document the local symbol-prefixing and
routine-pruning adaptations.
The Rust boundary is private. It caches the public CPU-capability result in an
atomic byte and calls only two prefixed native entry points. The ELF symbols are
hidden, Mach-O symbols are private externs, and helpers are local/private where
the object format permits. The ELF object also marks a non-executable stack.
Validation
and compile-time
+adx,+bmi2: 36 boundary pairs plus 1,024 deterministicrandom pairs per field, covering multiplication and squaring.
x86_64-asmenabled, includingcoexistence with
aarch64-asm.non-executable stack, and exact
mulx/adcx/adoxinstruction census.cargo package --list, formatting, diff hygiene, and focused Orchard proofgeneration/verification.
Commit
e386dceadds CI coverage only; the benchmarked production sources arethe byte-identical parent commit
c91e923. CI explicitly covers generic andstatic-feature Linux builds, Windows MSVC and GNU objects, Intel macOS, and the
portable fallback on Apple AArch64.
API surface
The only downstream-visible change is the new non-default Cargo feature
x86_64-asm. There are no new or changed Rustpuborpub(crate)items,function signatures, trait methods, enum variants, type aliases, constants, or
constructors. The implementation adds two private prefixed native link symbols
and confines unsafe code to the private CPUID/assembly binding module.
Benchmark artifacts:
pasta-field-x86-asm-main-c91e923-benchmark-20260815.tar.gz, SHA-256d2379f5bf01a142efc30da6e2ffbd5c2add4fa76835a4f8189747ea4a16adc30.Current final-MSM stack benchmark (2026-08-19)
The x86 backend was replayed without source changes (apart from additive
changelog composition) over the accepted GLV/XYZZ/multicore/c=10 stack at PR
#93. The exact incremental production boundary was
a6b10fa->12e8971.Standard balanced A/B/B/A geometric centers of means on the same Xeon 8358
host were:
The serial B64 mean had 1.20% candidate drift; its median center was a cleaner
1.830772% reduction, with both paired directions positive. The eight-worker
B64 paired mean reductions were 3.584677% and 2.592805%.
This run used the authenticated 64-proof corpus, native Fp/Fq differentials,
genuine proof preflights, Rust 1.88, locked dependencies, and the standard
telemetry gates. The combined x86-backend plus #56 specialized-square ratios
are 5.956128% for proving, 2.239169% for serial B64 verification, and
4.165871% for eight-worker B64 verification over PR #93.
Current-stack archive SHA-256:
f35c6c98d9e1aa88f2280c4310ebef573b79ae6f621f59be0f47bd306013521f.