feat(cfg): ip65 refit — un-break BACKEND=ip65 via cert_buf/verify-scratch union - #68
Merged
Conversation
…atch union
Plain `make BACKEND=ip65` has failed since the nistcurves bump with
`BSS overflows CRYPTO_COLD_SHADOW by 1406 bytes`. The gap is exactly
LIB_NISTCURVES_P256_BSS (1,312 B) — pure ECDSA-verify-time scratch
(fp_wide/fp_r0/fp_inv_*/ec_*/ecdsa_* in libs/nistcurves data_p256.s).
Close it by time-sharing that scratch with cert_buf's 1,536 B, whose
lifetime is disjoint:
- cert_buf is written+parsed only inside x509_parse_cert /
x509_extract_pubkey (via tls_handle_certificate); the pubkey is
extracted into CRYPTO_BSS slots at Certificate-processing time.
- cert_tbs_ptr/cert_tbs_len point into cert_buf but have zero
in-tree readers (exported, never imported).
- tls_handle_cert_verify (where the lib scratch is live) reads only
tls_rec_buf + the transcript + the pubkey slots — audited
src/tls_cert.s lines 535-743: sig parse from tls_rec_buf+8,
signed-content rebuild from transcript hash.
- tools/test_x509.py rewrites cert_buf before every parse use; its
verify tests (3a-3d) stage inputs directly and never re-read
cert_buf. tools/test_x25519.py doesn't touch it.
Changes:
- cfg/c64-https-ip65.cfg: new SCRATCH_UNION region ($A000, $600,
non-file) overlapping CRYPTO_COLD_SHADOW's head;
LIB_NISTCURVES_P256_BSS routed there; new CERT_BUF_BSS segment
pinned start=$A000 (tls_rec_buf stays in BSS_TAIL at $A600 —
it is live during verify and must not be in the union span);
TABLES_BSS declared last so it packs at $BA00-$BFFF, putting
mul_dma at $BA00/$BB00 and sqtab at $BC00 — the onchip profile's
sqtab_reserved=$BC00 bake invariant holds with no extra pinning.
- src/der_decode.s: cert_buf moved BSS_TAIL -> CERT_BUF_BSS with the
lifetime contract documented at the declaration.
- cfg/c64-https-uci{,-onchip}.cfg: declare CERT_BUF_BSS first in
CRYPTO_COLD_SHADOW (same total occupancy; UCI needs no union).
- src/net/ip65/exports.s: link-time .assert cert_buf=$A000 so the
union anchor cannot silently drift.
Verified:
- make BACKEND=ip65 LINK OK (47,105 B)
- make BACKEND=ip65 USE_NISTCURVES_ONCHIP=1 LINK OK, sqtab guard OK
- make BACKEND=uci / +ONCHIP / +ONCHIP_COMB all LINK OK (62,977 B,
byte-count identical to pre-change)
- ip65 map: code tops out at $9EE8 (<$A000), cold shadow ends $BFFF,
union layered at $A000-$A51F under CERT_BUF_BSS $A000-$A5FF
- VICE KATs on the ip65 build (-reu): test_x25519.py 71/71,
test_x509.py 11/11 (3c/3d full verifies scribble the union between
the parse tests — the exact hazard the union design must survive)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>JC-000 added a commit
that referenced
this pull request
Aug 12, 2026
The benchmark tables predated three merged changes and one open fix, and several status claims had gone stale. Measured numbers only — no extrapolated figures. **e2e wall-clock.** The 2026-07-20 campaign rows are kept as history and labelled as such; a new "Post-#74 e2e numbers" block records HEAD: device profile clock pre-#71 post-#71 post-#74 C64U onchip 48 MHz 51.0 s 125.4 s 44.6 s C64U onchip 64 MHz 39.7 s (unmeas.) 33.7 s U64E REU 48 MHz 82.1 s 161.0 s 82.1 s Both onchip rows land BELOW their pre-regression baselines and the REU row lands exactly AT it — a REU build cannot contain #69 (its change is inside .ifdef USE_NISTCURVES_ONCHIP), so the pair is a clean control showing #69 is a SPEEDUP at turbo, not a cost. The doc now states that explicitly, including why the sign is easy to get wrong: the profile's 1 MHz penalty exists only because REU DMA is cheap relative to the CPU down there, and inverts above the crossover. (I got this backwards during the campaign; recording the reasoning so the next reader doesn't.) **New: ip65 / stock-C64 wall-clock**, the first ip65 e2e figures we have — 36.0 min honest 1 MHz REU-less, with the phase breakdown, plus the accelerated runs. Notes that the verify stretch came in 1.4% off the T(f)=D+C/f prediction three orders of magnitude from where that model was fit, and that ip65's drain budget is byte-identically unchanged by #74 so the numbers stand at HEAD. **Corrected stale claims:** - "ip65 is NOT packaged: does not link" — it links (#68). Explains the SCRATCH_UNION lifetime argument and its guards, notes packaging it is now a live option since a stock C64 + RR-Net has no shipped PRG today, and demotes c64-nist-curves#54 from blocker to optional headroom. - The CRYPTO_COLD_SHADOW "1,662 B overflow, cfg relief exhausted" entry, same fix. - The X25519-sibling entry claimed the old BSS overflow. Re-measured 2026-07-29: USE_X25519_SIBLING=1 under ip65 still fails, but on a DIFFERENT problem — X25519_RODATA over CRYPTO_OVERLAY by 2,048 B and LIB_NISTCURVES_P256_CODE over CRYPTO_RESIDENT by 103 B, i.e. code/rodata placement (ip65's overlay slot is 4,212 B vs UCI's 7.5 KB), not BSS. Better to state the measured failure than leave a fixed one on the page. **New design note** for the post-ServerHello drain: the ip65 property that motivates it (no MSS in SYN + ACK-only-when-polled), the offline-verify failure signature it prevents, why the budget must be per-backend (~40 ms per UCI net_poll vs a cheap ip65 pump — the #73 regression), current values, and the two open follow-ups (in-crypto polling for large flights; a wall-clock/idle bound instead of an iteration count, which is what the section's own rule actually demands). **New Smoke-tests subsection** for the hardware-free VICE ip65 rig, with the two prerequisites that are easiest to lose: the patched ethernet-capable VICE (stock macOS builds gate pcap on geteuid()==0) and the /dev/bpf permissions that reset every reboot. Also replaces the "blocked on an upstream ip65 bug (see lost memory note)" line with what is actually known now. Stacked on fix/drain-backend-budget: the post-#74 rows describe that PR's tree, not master's. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
JC-000 added a commit
that referenced
this pull request
Aug 13, 2026
Two doc-drift nits from the branch review: - net_tuning.inc and tls13.s quoted the per-UCI-net_poll cost as ~37 ms (~2.8 ms fence, ~70 s for ip65's 2000-poll budget). CLAUDE.md derives ~40 ms from the measured regression (80.8 s / 1984 polls C64U onchip, 78.9 s / 1984 U64E REU) and is authoritative; the .s comments were stale by one round of measurement. Anyone sizing NET_SH_DRAIN_INNER against a wall-clock target now reads one number. - CLAUDE.md's Variables entry for USE_X25519_SIBLING=1 still pointed at a "tracked BSS overflow", contradicting the Known-issues entry this branch rewrote: that BSS overflow was fixed by the #68 refit and the remaining ip65 failure is a code/rodata placement problem. Comment-only; both backends rebuild clean (ip65 47,105 B, uci 62,977 B). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Aug 13, 2026
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 freeto 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.
Summary
Plain
make BACKEND=ip65has failed since the nistcurves bump withBSS overflows CRYPTO_COLD_SHADOW by 1406 bytes. The gap is exactlyLIB_NISTCURVES_P256_BSS(1,312 B) — pure ECDSA-verify-time scratch, whoselifetime is disjoint from
cert_buf's (dead once the Certificate handler hasextracted the pubkey). This PR time-shares the two:
cfg/c64-https-ip65.cfg: new non-fileSCRATCH_UNIONregion at $A000(size $600 — capped at cert_buf's span so lib-BSS growth is a link error,
never silent corruption);
LIB_NISTCURVES_P256_BSSrouted there; newCERT_BUF_BSSsegment pinnedstart=$A000;TABLES_BSSdeclared last soit packs at exactly $BA00-$BFFF (sqtab_reserved lands $BC00 — the onchip
bake invariant holds with no extra pinning).
src/der_decode.s: cert_buf moved toCERT_BUF_BSSwith the lifetimecontract documented at the declaration (future chain-validation work must
break the union first).
src/net/ip65/exports.s: link-time.assert cert_buf = $A000drift guard(ip65-only TU).
CERT_BUF_BSSdeclared first in cold shadow — layout-neutral(62,977 B PRG, byte-count identical).
Unblocks BOTH ip65 profiles (REU + onchip). c64-nist-curves#54
(minimal-archive) becomes optional headroom, not a blocker. Groundwork for
the REU-less stock-C64 story (with #67's fe25519 fix, branch in flight).
Validation
make BACKEND=ip65→ links, 47,105 B PRG; labels: cert_buf=$A000=fp_wide(union live), tls_rec_buf=$A600 (outside union, live during verify)
make BACKEND=ip65 USE_NISTCURVES_ONCHIP=1→ links; sqtab_reserved=$BC00grep guard passes
incl. 3c/3d parse→verify→parse sequences that scribble the union between
cert parses (the exact hazard the design must survive)
transcript + pubkey slots; cert_tbs_ptr/len have zero in-tree importers)
🤖 Generated with Claude Code