Skip to content

crypto: AES-NI counter blocks by byte additions (parity item 10, G3) - #1018

Closed
Japabu wants to merge 1 commit into
mainfrom
wt/toyos-crypto-perf-9
Closed

Japabu wants to merge 1 commit into
mainfrom
wt/toyos-crypto-perf-9

Conversation

@Japabu

@Japabu Japabu commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

Based on main 18a3873. One commit, ffd3be5.

Problem

AES-GCM bulk on the AMD bench (AES-NI) is 1.03–1.16x ring. The track records the stitched seal as instruction-bound, at about 277 instructions per 128 B. Each counter block cost five instructions in the shipped code: lea (the addition), bswap, movd, pslldq and the pxor of round key 0.

What changes

This is the analysis's G3.

  • The fast path. Within a run whose counter low byte (the block's byte 15) does not carry, block j is block 0 plus j by a vector byte addition, then the key XOR.
    • The shipped code is three instructions a block: movdqa, paddb with a constant, pxor.
    • Each run also builds block 0 once and tests for the carry once. That test reads only the public counter.
    • A run that would carry takes the old per-block construction.
  • Where it applies. The stitched seal, CTR (the open path) and the short path all use it. The VAES-512 path builds its own counters and is unchanged.
  • Expected gain: about 8–10 instructions fewer per 128-byte batch, in line with the analysis's estimate for G3. Whether that moves the rows is for the bench to say.

Checks

All at ffd3be5. Logs: https://github.com/ToyOSOrg/evidence/tree/fdba4666aa7be286eb9a2c6a202979e551dba9c8/pr-1018/r1

  • cargo run -- --ci host: exit 0, 93 green.
  • Crate clippy (adopted lints, -D warnings) in the three forms for the host and --target x86_64-apple-darwin, and for x86_64-unknown-uefi: exit 0.
  • Rosetta: cargo test -p toyos-crypto --features std,sign,wpa2,rustls-provider --release --target x86_64-apple-darwin exits 0. Rosetta admits AES-NI and PCLMULQDQ, so this runs the changed paths. The AEAD differential's 4 KiB and 16 KiB cases reach runs that carry at low byte 250, so both the byte-addition path and the per-block path run against ring and RustCrypto.
  • New test counter_runs_match_the_per_block_construction: every low byte, 8- and 15-block runs, three high counters.
  • Negative controls: each is a checked patch, built, tested under Rosetta, and reverted in the same run.
    • Steps off by one on odd blocks: red, in the new test, gcm::tests::counters_continue_across_the_stitched_and_tail_paths and the provider's interop tests.
    • The carry test loosened by one: green on the crate's other tests, because no call site starts a run at low byte 249. Red in the new test, which was written for that gap.
  • crypto_instrument_prints_its_table (QEMU x86_64): exit 0.

Measurements

Readback staged at ffd3be5 (crypto_instrument, cryptocase; exit 2 = Staged). The rows to read are AES-GCM 1 KiB and 16 KiB, seal and open, on the AMD bench. The T14 takes VAES-512 above 224 B, so there only its one-batch short path changes.

Metal

crypto_instrument passed at ffd3be5 on both benches (orchestrator's report).

row main this PR
aes-128-gcm seal/1024 1.098 1.108
aes-128-gcm seal/16384 1.131 1.139
aes-256-gcm seal/16384 1.031 1.033
aes-128-gcm open/1024 1.050 1.076

Every difference is within the rows' spread (0.3–2.0%). About 8–10 fewer instructions per 128-byte batch did not move the stitched loop. So instruction count alone is not what bounds it on Zen 2, or the new path's round-key-0 reload from the stack costs what the counters saved. This PR should not land on its present evidence; its remaining value is the test, which pins the counter construction.

Not in this PR

  • G2 (hashing the ciphertext of two batches back) and the batch-shape question (G4: 6 against 8 blocks, schoolbook against Karatsuba) need bench iterations.
  • The shipped code reloads round key 0 from the stack in the new path (register pressure). A fix there would also be bench-driven.

🤖 Generated with Claude Code

https://claude.ai/code/session_01PXEk8BBMDT8tXu4AnENB9Q

The stitched AES-NI seal is bound by instruction count on the AMD bench
(about 277 instructions per 128 bytes), and every counter block cost five
of them: an addition, a byte swap, a move to a vector, a shift and the XOR
of round key 0. Within a run whose counter low byte, the block's byte 15,
does not carry, block j is block 0 plus j by a vector byte addition; the
shipped code is three instructions a block (a copy, the addition and the
key XOR) and one block build per run. A run that would carry takes the
per-block construction; the test reads only the public counter. The
seal, CTR and short paths all take it; the VAES-512 path builds its own.

A unit test holds the runs to the per-block construction from every low
byte, for 8- and 15-block runs and three high counters, including the
runs that end exactly at byte 15's carry.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PXEk8BBMDT8tXu4AnENB9Q
@Japabu

Japabu commented Oct 22, 2026

Copy link
Copy Markdown
Collaborator Author

Closing without landing. On the AMD bench, where this path runs, G3's counter construction made no measurable difference against main's AES-NI code on the same day. Every GCM row was within its 0.3–2.0% spread (https://github.com/ToyOSOrg/evidence/tree/81880a889faf9e0db0409f7984d3338fa6375b0f/pr-1018/r1/metal). On the T14, GCM above 224 B runs on VAES-512, which this PR doesn't touch. Fewer instructions per batch don't move the stitched loop on Zen 2, so the next GCM work (G2/G4 and the round-key-0 reload) starts from main's code. If that work changes the counter construction, the pinning unit test can come with it.

@Japabu Japabu closed this Oct 22, 2026
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.

1 participant