ARM64 RandomX JIT optimization, 2026-09-10
Status: the selected optimization is applied locally. ASan/UBSan full/light native regression and Rust integration tests passed; production is unchanged.
The existing algorithm remains RandomX v2.0.1. This experiment changes the
ARM64 JIT implementation, using the existing ARMv8-A + crypto build target.
It retains the secure JIT and all previously added destruction wiping. The
screening harness hashes a strictly dependent 68-byte chain, one worker, with
full memory. Its dataset key is test key 000.
Candidate implementation
- Replace scalar FP operand loads/sign extension and two integer-to-SIMD lane transfers with one SIMD load and vector signed widening, followed by the unchanged conversion to binary64. Every signed 32-bit integer is exactly representable in binary64, including both extreme values.
- Apply the same loads to the fixed loop's F/E register initialization.
- Replace three FP lane moves for FSWAP_R with one
EXT ... #8. - Fold the first three intermediate hardware AES XORs into the next AESE/AESD instruction's input XOR. Keep the final XOR explicit. Software AES and v1 mixing remain unchanged.
The combined.patch records the initial experimental candidate against the
archived baseline. selected.patch records the final source, including comments
and the sanitizer dispatch workaround described below. All earlier
native hardening and transparent huge page changes are part of this baseline,
so the measured gain is additional to the earlier huge page improvement.
Paired confirmation
The final selected Graviton result is 567.75 → 676.95 hashes/s, +19.23%. This is one strictly dependent chain on one pinned parent-host core, including secure JIT permission transitions. Two 30,000-hash runs per implementation were ordered baseline, selected, selected, baseline:
| Implementation | First run, H/s | Second run, H/s |
|---|---|---|
| Baseline | 567.772 | 567.723 |
| Selected | 677.148 | 676.752 |
All four final digests match. graviton-selected-long.jsonl contains these
final-source measurements. An earlier 30,000-hash paired confirmation of the
initial combined patch gave 570.47 → 677.51 H/s (+18.76%). A subsequent short
selected-source check recorded 654 then 676 H/s against a 569 H/s baseline;
that slower sample is retained in graviton-selected-confirm.jsonl. The longer
final check was added because this shorter check had unexplained variation.
The gain is additional to the earlier huge-page optimization. These rates were measured with the enclave running, but outside it; a future enclave build requires its own calibration. A practical summary is about a 19% gain, not a guarantee of an exact percentage under every workload or deployment.
The M4 four-pair confirmation was unstable: baseline ranged from 472 to 746
H/s and optimized from 615 to 772 H/s. pmset recorded battery power during
this phase. Normal user workloads and power/scheduler behavior were not
controlled, and the late slowdown affected both implementations. No Mac
percentage gain is claimed; the earlier short runs were near 770 H/s for
both. All Mac result digests still match. Both hosts' complete results are
retained in *-confirm.jsonl; none of the slow samples was dropped.
Screening
*-screen.jsonl: 5,000 dependent hashes per variant, two repetitions, forward
then reverse variant order. On Graviton, averages were 567.8 H/s baseline,
587.7 lane swap, 655.7 dynamic FP loads, 669.7 all FP loads, 574.2 AES-only,
and 677.1 combined. Every variant produced the same chain digest.
*-extra.jsonl: 10,000 hashes per variant, two repetitions. This isolates the
combined change without AES folding and tries three alternative dataset
prefetch hints. Graviton retained about 570 baseline versus 677 combined.
L1 keep/stream and L2 keep did not improve on the existing L2 streaming hint.
The prefetch variants are recorded but are not applied to repository source.
*-pairs.jsonl: compares the combined candidate with paired SIMD FP loads.
On Graviton both alternatives changed speed by less than 0.2%, so neither
was applied. These remain recorded experiments, not claimed improvements.
Mac screening has substantial time variation from active laptop workloads. It is not pinned to a physical core and P-core residency is not measured. Small differences in its screening results are not reliable evidence of a win. Graviton tests run on the native parent host, pinned to CPU 13 at nice 10, while the existing 12-core production enclave keeps running. These results are not an inside-enclave measurement or an upper bound on an attacker.
Native regression and sanitizer investigation
The final selected code passes the existing ASan and UBSan native fixture
on both ARM hosts in full and light modes. This checks 16 secure-JIT hashes
against the interpreter per mode and checks explicit VM register/program/hash
erasure. On Linux it additionally checks scratchpad and complete JIT wiping
immediately before release, and five injected permission failures must abort
before returning a hash. Final evidence is in m4-selected-native/ and
graviton-selected-native/, including every build command and exit code.
Mac Rust release tests also pass: the light upstream vector, entropy failure
and restart/tampering tests (3 tests), and the explicitly enabled full upstream
vector (1 test). m4-selected-rust-*.stdout/.stderr retain the results.
The initial Graviton GCC combined-sanitizer run failed while dispatching a
member-function pointer during light-mode compilation, before executing the
new generated code. An identical build of the unchanged baseline failed the
same way. This matches GCC's documented
member-pointer array UBSan miscompilation, PR c++/116449.
Materializing engine[instr.opcode] in a local member pointer before invoking
it fixes both full and light dispatch sites. No sanitizer checks are disabled.
Both failing investigative runs are preserved alongside the successful final
regression. The release Mac object files are byte-identical with and without
the workaround; GCC's release object differs, so final Graviton measurements
were repeated against selected as well.
Correctness gates and reproduction
Before timing, bench.cpp checks each variant against upstream v1 and v2
full-memory vectors with both hardware and software AES. It also compares
16 v2 inputs to the unchanged interpreter, then requires every timed dependent
chain to have the same final digest. Dataset initialization is outside timing.
The dataset is shared across sequentially tested VMs, not rebuilt per variant.
experiment-source.tar.gz contains the complete dirty baseline vendored
source snapshot and initial variant sources. extra-source.tar.gz and
pairs-source.tar.gz contain additional variants. selected-source.tar.gz
contains the final selected native files and build-selected.py. In a fresh experiment
directory, extract the first archive, copy bench.cpp, build.py and
variants.json beside source, and run python3 build.py. Extract each
additional archive there and run its build-extra.py, build-pairs.py, or
build-selected.py.
The build needs CMake and an ARM64 C/C++ compiler; on Linux the crypto target
must be executable by the host. Run ./bench . 10000 2 baseline,selected.
Linux measurements additionally use nice -n 10 taskset -c 13.
The result is an implementation optimization, not a cryptographic redesign. Both generators and solvers can use it. Existing work counts are unchanged, but previous wall-clock calibration must not be attributed to this build. No enclave, service, Mullvad device, kernel setting or production deployment was restarted or changed during this work.
Final job audit: no experiment benchmark processes remain on either host.
Experiment Docker containers are stopped and preserved. enclave-after.json
confirms the original production enclave ID and PCR0 are still running.
All evidence files are covered by this directory's SHA256SUMS.