Repository navigation
perf(compiler): lower bulk copies and zeroing to sol_memmove_ and sol_memset_ - #22
Merged
Merged
Conversation
memmoveHelper prepends only a u8 typedef to libraryMemory, whose overlap test casts pointers to u64, so linking the helper always failed with "use of undeclared identifier 'u64'". Every library-free program for which LLVM emits a memmove call, for example a large struct assigned through two pointers that may alias, could not be built. Tests: the helper compiles on its own under -Werror, and a probe whose pointer-to-pointer copies of a 545-byte value (including a self-assignment) lower to memmove builds and produces the expected account bytes on fogo-v0 and solana-v3. Both fail before this change.
goSVM moved bytes with C loops: gosvm_move for copy, append and string
building, and the hidden memcpy/memset/memmove LLVM calls for aggregate
assignment and zeroing. On SBF the byte loop costs about 8 CU per byte and
the word loop 6 CU per 8 bytes; the memory syscalls cost max(10, n/250).
On fogo-v0 and solana-v3, SBF builds now define GOSVM_MEMORY_SYSCALLS and
those routines hand every move of at least one byte to sol_memmove_ or
sol_memset_ (measured: the syscall wins from 1 byte at every alignment).
The entrypoint's duplicate-descriptor copy and ResizeAccount's zero fill do
the same, and two zeroing loops (for i := range s { s[i] = 0 } and
for j := e; j < uint64(len(s)); j++ { s[j] = 0 }) are emitted as a memset.
sol_memcpy_ is never used: the runtime rejects overlapping ranges, including
src == dst. Zero-length moves never reach the VM. Host C and target-less
legacy builds compile the original loops in #else branches, so the legacy
ELFs are unchanged.
Tests: examples/memory-values against native Go as host C (13,660 vectors,
SDK 1 and 2 routines) and on both SBF profiles (5,480 vectors) built with
threshold 1, threshold 64 and loops only; runtime, read-only write and
legacy-identity SBF tests; the corpus check now proves the emitted C is the
recorded C plus only the syscall blocks.
GO_ALLOCATION gains the bulk-memory and zeroing-loop sections, BACKEND_INTERFACE says why a backend must not use sol_memcpy_ for copy, ERROR_CONTRACT records the change as CU/ELF only, and HANDOFF and TARGET_PROFILES point at the new reference ELFs, which verify_rebuild.py now uses by default. results/compiler/2026-10-19-memory-syscalls holds the threshold curves, before/after CU for the Hyperlane Go mailbox (Init 25,182 -> 13,372 CU on fogo-v0, all 64 steps -33.5%), the goSVM examples (-1.4% to -13%, no step worse) and the port patterns (1 KiB tail zeroing 5,232 -> 124 CU), plus the fresh-rebuild check.
The memset branch of a rewritten zeroing loop took temporary numbers, so the #else branch (host and legacy builds) named its temporaries one higher than earlier compilers did. The branch now reuses the numbers inside its own block. examples/memory-values' C minus the syscall blocks is now the previous compiler's C, and its legacy ELF and the full swap's are unchanged; SBF ELFs are unchanged by this commit.
This was referenced Oct 19, 2026
Draft
This was referenced Oct 19, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
copy, large struct assignment and zeroing compile to byte loops. On account-heavy programs that's the largest CU overhead we measured, e.g. 5,232 CU to zero a 1 KiB tail.Solution
On fogo-v0 and solana-v3, lower bulk moves to
sol_memmove_and zeroing tosol_memset_. That coverscopy,append, string building, struct and array copies of 48 bytes or more, zeroed values of 96 bytes or more, the entry duplicate-account copy, theResizeAccountfill, and two exact zeroing-loop forms.sol_memcpy_is never used: it fails on overlap, including self-assignment.Benchmarks
copyResizeAccountto 10 KiBThe goSVM examples get 1.4% to 13% cheaper, with no fixture step worse. On solana-v3, 35 of 5,480 probe vectors got up to 1,006 CU slower from changed inlining in unrelated code.
Tests
examples/memory-valuesagainst native Go: 13,660 host vectors and 5,480 per SBF profile, built three ways (threshold 1, threshold 64, loops only). It covers overlap, aliasing, zero length, nil, threshold edges and near-miss loops that must not be rewritten. The frozen corpus's C is byte-identical once the new syscall blocks are stripped. The full-swap and clock2 reference ELFs change.Linked work
Closes #5. Includes #12's commit. Used by hyperlane-xyz/hyperlane-monorepo#9852.