Repository navigation
perf(compiler): compare hand-written byte loops a word at a time on SBF - #31
Draft
paulbalaji wants to merge 4 commits into
Draft
paulbalaji wants to merge 4 commits into
paulbalaji wants to merge 4 commits into
Conversation
A loop like `for i := 0; i < 32; i++ { if a[i] != b[i] { ... } }` stays
a byte loop on SBF, about 10 CU per byte. When the bounds are constants,
the variable is the loop's own, the body is only that if, and the
operands are byte slices, arrays or array pointers held in variables,
SBF builds now XOR the bytes a word at a time first and skip the loop
when they are all equal. Otherwise the unchanged loop runs, so the first
different byte, short operands and nil pointers behave as before. Host
C keeps only the loop.
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.
This was referenced Oct 19, 2026
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.
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
for i := 0; i < 32; i++ { if a[i] != b[i] { return false } }stays a byte loop on SBF, about 10 CU per byte, so an equal 32-byte key check costs ~320 CU. #29 covers==on arrays, but programs write the loop to compare a slice with an array, or to get the first difference (Rust's derivedOrd).Solution
When the start and bound are constants, the variable is declared by the loop, the body is only
if a[i] != b[i] { ... }with noelse, andaandbare variables of type[]E,[N]Eor*[N]E(Ea one-byte integer), SBF builds put a word compare in front of the unchanged loop. If both operands hold the bytes (a pointer is non-nil) and they are all equal, the loop is skipped: it would have run to the end without entering theifor trapping. Otherwise the loop runs as before, so the first different byte decides and a short operand or nil pointer traps at the same index. A tail shorter than a word is compared as the last 8 bytes, overlapping the word before. Host C keeps only the loop;GOSVM_BYTE_LOOP_WORDSturns the path on in host C for tests.The other idiom in #26, little-endian assembly (
v |= uint64(b[i]) << (8*i)), needs nothing: clang already combines it into oneldxdw, with a constant or a variable offset. A word path for it compiled to the same SBF code in isolation and cost 3 to 7 CU more in the example program, so it is left out and noted inGO_ARRAYS.md.Benchmarks
One small program per shape comparing input bytes 0..31 with 32..63 against main
939b215, whole transaction, same on both profiles (the empty probe costs 98 CU):[]byte, 32 bytes[]bytevs*[32]byte[32]bytearrays,Ordbody (plus two copies)[]byte, 13 bytes[]byte, 8 bytesAn equal 32-byte compare goes from ~320 CU to ~15. A difference costs 3 to 17 CU more, since the word compare runs before the loop finds it.
Tests
TestByteLoopDifferential:examples/byte-loop-valuesnatively and as host C with and without the word paths, 2,050 vectors over five compare shapes (arrays, slices, an array pointer, bytes 4 to 12,int8with a body that breaks) and five load shapes: equal, bit 0 and bit 7 flipped at every byte, every operand length 0 to 32 with and without an earlier difference, nil pointers. Outputs and panics match Go.TestByteLoopSBFruns the same vectors on fogo-v0 and solana-v3: 1,741 complete with Go's output, 309 trap and roll back, as in Go.TestByteLoopsC: the five compares get the path; nine near-misses (offset index,lenbound, outer variable, extra statement, wider elements, short array,==,else, non-variable operand) don't.TestCorpusMatchesPreCheckCompilerstrips the paths and matches every recorded hash.make test: same failures asmain939b215on this machine (stdlib provenance/features tests with Go 1.23.2,TestFullSwapFactsunder/tmp).verify_rebuild.py+check_rebuild_reference.pypass locally: the full-swap reference ELFs change (clock2doesn't), soresults/compiler/2026-10-19-byte-loops/rebuild/metrics.jsonis the new default reference.Linked work
Closes #26 (the hand-written byte loop row). #27, #28, #29 and #30 cover the other rows; if this merges first, #26 should stay open until the rest land.
Same family as #29. Used by hyperlane-xyz/hyperlane-monorepo#9852 through
hyperlane-integration-4.