1
0
Fork 0
mirror of https://github.com/imjasonh/nescript synced 2026-07-08 00:45:38 +00:00

sha256: specialize rotr_wk and shr_wk per rotation amount

The generic `rotr_wk(dst, n)` does its byte/bit decomposition
with two runtime `while` loops — necessary in the abstract, but
wasteful in SHA-256 where every rotation amount is one of ten
fixed compile-time constants (2, 6, 7, 11, 13, 17, 18, 19, 22,
25). The loop overhead alone is ~80 cycles of bookkeeping per
call, on top of the actual rotation work.

Replace each `rotr_wk(SIG, 6)` call inside the sigma helpers
with a dedicated `rotr_wk_6(SIG)` (and similarly for the other
amounts and for `shr_wk_3` / `shr_wk_10`). Each new helper just
chains the right number of `byte_rotr_wk` and `rotr1_wk` calls
inline — no `rem` variable, no `>= 8` / `> 0` checks.

The original `rotr_wk` / `shr_wk` wrappers stay defined for
runtime-amount callers; nothing else in the example uses them
today, but keeping them documents the general-purpose form.

Per SHA-256 block: ~45K cycles saved across 384 sigma rotations.
Combined with commit 0b5470b's leaf-function fix, the per-block
compression cost drops by ~3.5 frames at NTSC.

The hash output is unchanged (still
AE9145DB5CABC41FE34B54E34AF8881F462362EA20FD8F861B26532FFBB84E0D
for "NES"), no other examples are affected, and the emulator
harness stays at 34/34.

https://claude.ai/code/session_01FRmSBruVWCufm3LsUVMs8v
This commit is contained in:
Claude 2026-04-16 16:49:35 +00:00
parent 0b5470b054
commit 726faef538
No known key found for this signature in database
2 changed files with 132 additions and 13 deletions

Binary file not shown.

View file

@ -210,6 +210,13 @@ fun byte_rotr_wk(dst: u8) {
// 0..31 by first rotating whole bytes (each call is cheaper
// than 8 ROR chains) and then finishing with up to 7 single-
// bit ROR chains.
//
// The SHA-256 sigmas only need a fixed set of rotation amounts
// (2, 6, 7, 11, 13, 17, 18, 19, 22, 25), so the per-amount
// helpers below skip this loop's runtime byte/bit decomposition
// and unroll the right number of `byte_rotr_wk` / `rotr1_wk`
// calls. This `rotr_wk` wrapper stays available for the rare
// caller that needs a runtime amount.
fun rotr_wk(dst: u8, n: u8) {
var rem: u8 = n
while rem >= 8 {
@ -222,6 +229,102 @@ fun rotr_wk(dst: u8, n: u8) {
}
}
// ── Per-amount rotate helpers ───────────────────────────────
//
// Each `rotr_wk_<N>` rotates wk[dst..dst+4] right by exactly N
// bits with no loop overhead. Calling these directly from the
// sigma helpers replaces ~80 cycles of `rem >= 8` / `rem > 0`
// loop bookkeeping with the bare sequence of byte + bit
// rotations the analyzer-level constant rotation always reduces
// to. Per SHA-256 block, ~45K cycles saved across 384 sigma
// rotations.
fun rotr_wk_2(dst: u8) {
rotr1_wk(dst)
rotr1_wk(dst)
}
fun rotr_wk_6(dst: u8) {
rotr1_wk(dst)
rotr1_wk(dst)
rotr1_wk(dst)
rotr1_wk(dst)
rotr1_wk(dst)
rotr1_wk(dst)
}
fun rotr_wk_7(dst: u8) {
rotr1_wk(dst)
rotr1_wk(dst)
rotr1_wk(dst)
rotr1_wk(dst)
rotr1_wk(dst)
rotr1_wk(dst)
rotr1_wk(dst)
}
// 11 = 1 byte + 3 bits
fun rotr_wk_11(dst: u8) {
byte_rotr_wk(dst)
rotr1_wk(dst)
rotr1_wk(dst)
rotr1_wk(dst)
}
// 13 = 1 byte + 5 bits
fun rotr_wk_13(dst: u8) {
byte_rotr_wk(dst)
rotr1_wk(dst)
rotr1_wk(dst)
rotr1_wk(dst)
rotr1_wk(dst)
rotr1_wk(dst)
}
// 17 = 2 bytes + 1 bit
fun rotr_wk_17(dst: u8) {
byte_rotr_wk(dst)
byte_rotr_wk(dst)
rotr1_wk(dst)
}
// 18 = 2 bytes + 2 bits
fun rotr_wk_18(dst: u8) {
byte_rotr_wk(dst)
byte_rotr_wk(dst)
rotr1_wk(dst)
rotr1_wk(dst)
}
// 19 = 2 bytes + 3 bits
fun rotr_wk_19(dst: u8) {
byte_rotr_wk(dst)
byte_rotr_wk(dst)
rotr1_wk(dst)
rotr1_wk(dst)
rotr1_wk(dst)
}
// 22 = 2 bytes + 6 bits
fun rotr_wk_22(dst: u8) {
byte_rotr_wk(dst)
byte_rotr_wk(dst)
rotr1_wk(dst)
rotr1_wk(dst)
rotr1_wk(dst)
rotr1_wk(dst)
rotr1_wk(dst)
rotr1_wk(dst)
}
// 25 = 3 bytes + 1 bit
fun rotr_wk_25(dst: u8) {
byte_rotr_wk(dst)
byte_rotr_wk(dst)
byte_rotr_wk(dst)
rotr1_wk(dst)
}
// Shift wk[dst..dst+4] right by 1 bit (logical — top bit
// becomes 0).
fun shr1_wk(dst: u8) {
@ -265,7 +368,9 @@ fun byte_shr_wk(dst: u8) {
}
}
// Shift wk[dst..dst+4] right by `n` bits (logical).
// Shift wk[dst..dst+4] right by `n` bits (logical). Generic
// runtime-amount form; the SHA-256 sigmas use the per-amount
// helpers below instead.
fun shr_wk(dst: u8, n: u8) {
var rem: u8 = n
while rem >= 8 {
@ -278,6 +383,20 @@ fun shr_wk(dst: u8, n: u8) {
}
}
// 3 bits — used by σ0(x) = ... ^ (x >> 3)
fun shr_wk_3(dst: u8) {
shr1_wk(dst)
shr1_wk(dst)
shr1_wk(dst)
}
// 10 bits = 1 byte + 2 bits — used by σ1(x) = ... ^ (x >> 10)
fun shr_wk_10(dst: u8) {
byte_shr_wk(dst)
shr1_wk(dst)
shr1_wk(dst)
}
// ── Cross-array primitives ──────────────────────────────────
// wk[dst..dst+4] = w[w_ofs..w_ofs+4]
@ -414,48 +533,48 @@ fun add_k_to_wk(dst: u8, k_ofs: u8) {
// Σ0(src) = rotr(src, 2) ^ rotr(src, 13) ^ rotr(src, 22)
fun big_sigma0(src: u8) {
cp_wk(OFS_SIG, src)
rotr_wk(OFS_SIG, 2)
rotr_wk_2(OFS_SIG)
cp_wk(OFS_TMP, src)
rotr_wk(OFS_TMP, 13)
rotr_wk_13(OFS_TMP)
xor_wk(OFS_SIG, OFS_TMP)
cp_wk(OFS_TMP, src)
rotr_wk(OFS_TMP, 22)
rotr_wk_22(OFS_TMP)
xor_wk(OFS_SIG, OFS_TMP)
}
// Σ1(src) = rotr(src, 6) ^ rotr(src, 11) ^ rotr(src, 25)
fun big_sigma1(src: u8) {
cp_wk(OFS_SIG, src)
rotr_wk(OFS_SIG, 6)
rotr_wk_6(OFS_SIG)
cp_wk(OFS_TMP, src)
rotr_wk(OFS_TMP, 11)
rotr_wk_11(OFS_TMP)
xor_wk(OFS_SIG, OFS_TMP)
cp_wk(OFS_TMP, src)
rotr_wk(OFS_TMP, 25)
rotr_wk_25(OFS_TMP)
xor_wk(OFS_SIG, OFS_TMP)
}
// σ0(src) = rotr(src, 7) ^ rotr(src, 18) ^ (src >> 3)
fun small_sigma0(src: u8) {
cp_wk(OFS_SIG, src)
rotr_wk(OFS_SIG, 7)
rotr_wk_7(OFS_SIG)
cp_wk(OFS_TMP, src)
rotr_wk(OFS_TMP, 18)
rotr_wk_18(OFS_TMP)
xor_wk(OFS_SIG, OFS_TMP)
cp_wk(OFS_TMP, src)
shr_wk(OFS_TMP, 3)
shr_wk_3(OFS_TMP)
xor_wk(OFS_SIG, OFS_TMP)
}
// σ1(src) = rotr(src, 17) ^ rotr(src, 19) ^ (src >> 10)
fun small_sigma1(src: u8) {
cp_wk(OFS_SIG, src)
rotr_wk(OFS_SIG, 17)
rotr_wk_17(OFS_SIG)
cp_wk(OFS_TMP, src)
rotr_wk(OFS_TMP, 19)
rotr_wk_19(OFS_TMP)
xor_wk(OFS_SIG, OFS_TMP)
cp_wk(OFS_TMP, src)
shr_wk(OFS_TMP, 10)
shr_wk_10(OFS_TMP)
xor_wk(OFS_SIG, OFS_TMP)
}