Skip to content

perf(runtime): read, write and compare whole words on SBF - #27

Draft
paulbalaji wants to merge 4 commits into
CrocSwap:mainfrom
paulbalaji:opt-runtime-word-access
Draft

paulbalaji wants to merge 4 commits into
CrocSwap:mainfrom
paulbalaji:opt-runtime-word-access

Conversation

@paulbalaji

@paulbalaji paulbalaji commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Problem

The C runtime moves and compares bytes one at a time: sol_read64 and the data-length stores, the 32-byte owner/key checks behind ResizeAccount, CloseAccount, IsPDA and IsTokenProgram, and a staging copy of every CreateAddress result. On SBF that is ~9 instructions per copied byte and ~12 per compared byte. In the Hyperlane Go programs a CreateAddress cost 1,944 CU: 1,500 for the syscall, 288 for the copy. Programs that search for a bump pay the copy on every try.

Solution

On SBF builds only (__bpf__):

  • sol_read64/sol_write64 and 32-bit forms are a fixed-size __builtin_memcpy, which clang lowers to one ldxdw/stxdw. SBF allows any alignment.
  • 32-byte key comparisons fold four word XORs.
  • sol_CreateAddress passes the caller's output straight to sol_create_program_address. The syscall reads every seed before it writes and writes only on success, so a failed derivation still leaves the output untouched, and an output that aliases the seeds gets the same result.

Results, error codes and check order don't change. Host C keeps the byte loops.

Benchmarks

TestRuntimeWordsSBF, whole transaction against main 939b215, same on both profiles:

case main this PR
CreateAddress success 2,439 2,103
CreateAddress success, output aliasing the seeds 2,147 1,805
ResizeAccount + CloseAccount + Rent + IsPDA 2,559 1,897

14% to 26% less. The savings are within 11 CU of the pre-#25 numbers; #22's syscalls only moved the test program's own copies.

Hyperlane Go programs (hyperlane-xyz/hyperlane-monorepo#9852), measured before #25: the same source built with hyperlane-integration-2 with and without this branch, all 23 Rust functional suites (584/584 both ways): per-call median 6% less CU, total 4% to 10% less per program, none more expensive. Details in results/compiler/2026-10-19-runtime-word-access/.

Tests

  • TestRuntimeWordsSBF on fogo-v0 and solana-v3, 49 cases against host-computed results: CreateAddress success and on-curve failure (output must survive) under 16 program keys, output aliasing the seed buffer, ResizeAccount, CloseAccount onto itself (2013), Rent (per-runner values) and IsPDA.
  • TestCorpusMatchesPreCheckCompiler undoes exactly these template edits (and perf(compiler): lower bulk copies and zeroing to sol_memmove_ and sol_memset_ #22's memory syscall blocks) and still matches every recorded C hash, so nothing else in the emitted C moved.
  • make test: same failures as main 939b215 on this machine (stdlib provenance/features tests with Go 1.23.2, TestFullSwapFacts under /tmp).
  • verify_rebuild.py + check_rebuild_reference.py pass locally: the full swap and clock2 reference ELFs change, so results/compiler/2026-10-19-runtime-word-access/rebuild/metrics.json is the new default reference.

Linked work

Closes #26, together with #28, #29, #30 and #31. If this merges first, #26 should stay open until the rest land.

Rebased onto main after #25. Complements #22 (merged in #25), which moved Go-level copies to sol_memmove_/sol_memset_; this covers the runtime's own C. Used by hyperlane-xyz/hyperlane-monorepo#9852 through hyperlane-integration-4.

The C runtime moved and compared bytes one at a time: sol_read64 and the
data-length stores, the 32-byte owner/key comparisons behind
ResizeAccount, CloseAccount, IsPDA and IsTokenProgram, and a staging copy
of every CreateAddress result. On SBF these cost ~10 instructions per
byte. SBF builds now use fixed-size __builtin_memcpy, which clang lowers
to one ldxdw/stxdw per word, fold key comparisons over four words, and
let sol_create_program_address write the caller's output directly (it
reads every seed first and writes only on success). Host C keeps the
loops. Results, codes and check order are unchanged.
The change moves the full-swap reference ELFs, so CI's fresh-rebuild check now compares against this branch's rebuild/metrics.json, the default reference in scripts/verify_rebuild.py.
paulbalaji added a commit to hyperlane-xyz/hyperlane-monorepo that referenced this pull request Oct 19, 2026
hyperlane-integration-4 (3ddeff0) is upstream main 939b215, which merged
our earlier PRs #9 to #24 in CrocSwap/goSVM#25, plus the open draft PRs:
whole-word runtime loads, stores and key comparisons (CrocSwap/goSVM#27),
solana.FindAddress on sol_try_find_program_address, also behind
pda.Seeds.FindAddress (CrocSwap/goSVM#28), word-at-a-time == on byte
arrays (CrocSwap/goSVM#29), the exact MinimumBalanceFor fast path at
thresholds 2.0 and 1.0 (CrocSwap/goSVM#30), and word compares in front of
hand-written byte compare loops (CrocSwap/goSVM#31). The SDK snapshot is
copied from the new CLI.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Byte-at-a-time runtime and codegen add 0.3k to 4k CU to small instructions

1 participant