mirror of
https://github.com/imjasonh/nescript
synced 2026-07-08 08:55:38 +00:00
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
76 lines
2.8 KiB
Text
76 lines
2.8 KiB
Text
// 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()
|
||
}
|
||
}
|