1
0
Fork 0
mirror of https://github.com/imjasonh/nescript synced 2026-07-08 08:55:38 +00:00
nescript/examples/sha256/computing_state.ne
Claude f2623cb62b
sha256/computing: track byte offsets directly to skip per-iter shift
The phased compression driver was computing the schedule/round
*index* per iteration and then shifting it left by 2 to get the
byte offset (`schedule_one(i << 2)`, `round_one(r << 2)`). The
shift compiles to two ASLs per iteration — cheap, but pure dead
work since the byte offset is just the previous one + 4.

Track the byte offset as the loop counter and bump it by 4 each
iteration. The schedule and round APIs already wanted byte
offsets, so the call sites also get a touch shorter (no more
intermediate `var i: u8 = first_idx + step`).

Strict cycle savings are tiny — a handful per iteration — so
this is more about not leaving obviously redundant work in the
inner loop than a meaningful perf win. Hash output unchanged
(still AE9145DB…4E0D for "NES"); no other examples affected;
emulator harness 34/34.

https://claude.ai/code/session_01FRmSBruVWCufm3LsUVMs8v
2026-04-16 17:17:10 +00:00

76 lines
2.8 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// sha256/computing_state.ne — runs the SHA-256 block compression.
//
// The compression is split across frames so the main loop keeps
// responding to the NMI handshake. Each frame advances one
// "phase"; every phase does a batch of 4 iterations so the
// whole compression (48 schedule steps + 64 rounds + fold)
// finishes inside ~30 frames — roughly half a second of
// wall-clock wait between pressing Enter and the hash appearing.
//
// Phase map:
// 0..11 schedule W[16..63] in batches of 4 (12 × 4 = 48)
// 12..27 rounds 0..63 in batches of 4 (16 × 4 = 64)
// 28 fold wk[A..H] into h_state, transition to Showing
const SCHED_PHASES: u8 = 12 // 12 × 4 = 48 schedule steps
const ROUND_PHASES: u8 = 16 // 16 × 4 = 64 rounds
const FOLD_PHASE: u8 = 28 // SCHED + ROUND
const BATCH_SIZE: u8 = 4 // iterations per frame
state Computing {
on enter {
// Reset persistent hash state and build the padded
// block from the user's message. Phased work then runs
// on top of the freshly-initialised w[] / h_state /
// wk[A..H].
reset_hash_state()
build_padded_block()
init_abcdefgh()
cp_phase = 0
}
on frame {
if cp_phase < SCHED_PHASES {
// Each schedule phase handles BATCH_SIZE words. The
// schedule_one / round_one APIs both want a byte
// offset (= word_index * 4), so track that directly
// instead of recomputing `index << 2` per iteration:
// the byte offset is just incremented by 4 in lieu
// of the index by 1.
//
// First byte offset for this phase: (16 + phase*4)*4
// = 64 + phase * 16.
var w_byte: u8 = 64 + (cp_phase << 4)
var step: u8 = 0
while step < BATCH_SIZE {
schedule_one(w_byte)
w_byte += 4
step += 1
}
cp_phase += 1
} else if cp_phase < FOLD_PHASE {
// Round batch. First byte offset for this phase:
// (phase - SCHED_PHASES) * 4 * 4
// = (phase - SCHED_PHASES) << 4.
var kw_byte: u8 = (cp_phase - SCHED_PHASES) << 4
var step2: u8 = 0
while step2 < BATCH_SIZE {
round_one(kw_byte)
kw_byte += 4
step2 += 1
}
cp_phase += 1
} else {
// Fold a..h into h_state and hand off to Showing.
fold_abcdefgh()
transition Showing
}
// Draw the input buffer so the user can see what they
// typed while the hash is being computed. The cursor
// sprite is deliberately not drawn here — the keyboard
// is inactive during this phase.
draw_input()
}
}