joeylib2/LLVM816-ASKS.md

141 lines
7.4 KiB
Markdown

# Asks for the llvm816 toolchain session
JoeyLib-side findings that need toolchain-side fixes. Context:
PERF-AUDIT.md (findings #79, #77) and PERF-AUDIT-PLAN.md Phase 1.
JoeyLib does not modify toolchains/iigs/llvm-mos per the ownership
split -- these are requests, with evidence.
## 1. MVN-based memset/memcpy in runtime/src/libc.c (PERF-AUDIT #79)
`memset`, `memcpy` (and `memmove`'s copy loops) in
`toolchains/iigs/llvm-mos/runtime/src/libc.c` are one-byte-at-a-time
loops through 24-bit pointers:
void *memset(void *dst, int c, size_t n) {
char *d = (char *)dst;
while (n--) *d++ = (char)c;
return dst;
}
That is ~30 cycles/byte on the 65816. The ORCA/C baseline these
replaced lowers both to MVN block moves (~7 cycles/byte after setup).
Measured impact on the reference platform (UBER, honest post-Phase-0
clock): jlScbSetRange -- a 200-byte memset plus a flag write --
measures 163 ops/sec on the clang build vs 1005 ops/sec on the
ORCA-era capture (~4x regression at the whole-op level). Every
memset/memcpy-based JoeyLib path pays it: surface copies, sprite
save/restore fallbacks, SCB range fills, stage-alloc clears, the log
buffer, and all example/game code.
Ask: MVN-based memset/memcpy in libc.c (or intrinsic lowering in the
backend). Requirements from the JoeyLib side:
- Handle bank-crossing source/destination (MVN wraps within a bank;
the routine must split runs at bank boundaries).
- memset needs a 1-byte seed write + overlapping MVN (classic
self-copy fill), or an unrolled STA loop for short fills.
- Keep the size_t=unsigned long (32-bit) signature -- JoeyLib call
sites pass 32-bit counts today and the current behavior is correct,
just slow.
- A short-count fast path (n < ~16) matters too: the sprite
interpreter's per-row copies are 4-17 bytes.
## 2. FYI: sprite-codegen corruption under investigation (PERF-AUDIT #77)
JoeyLib's IIgs runtime sprite code generation corrupts memory when
enabled (bisected; disabled by default in spriteCompile.c until
root-caused). The current suspect list is JoeyLib-side (emit-time
buffer handling), but if the hunt lands on codegen/ABI behavior of
clang-emitted calls into arena-resident routines, it becomes a
toolchain question. Nothing to do yet -- flagged so the context is not
a surprise if it arrives.
## 3. fflush -> FST commit semantics (PERF-AUDIT #80)
JoeyLib's logger (src/core/debug.c) fflush()es after every line so a
crash/hang leaves the log on disk. On the clang runtime the lines do
not reach the disk image until fclose: a run that executed 33 logged
operations left 3 lines on disk after a forced emulator exit. Question:
does the libc fflush propagate to a GS/OS FST flush for the file, or
only drain the userland buffer? If the latter, an FST-flush call in
fflush (or an exposed fsync-like hook) would restore crash-durable
logging. Not urgent -- JoeyLib works around it with a memory mailbox
under emulation -- but real-hardware crash forensics wants it.
## 4. malloc/calloc returns 1 (not NULL) at heap exhaustion (PERF-AUDIT #81) -- CRITICAL
Empirically confirmed under MAME with in-app probes: with the default
link-layout heap (665 bytes at $00:BC67-$00:BF00 in the JoeyLib UBER
build), `calloc(1, ~720)` returns **1**. The bump-path overflow branch
in libc.c malloc -- whose comment already records a "historical
`p + HDR_SZ + n > heapEnd` over-heap miscompile (only manifested for
oversized n)" and assumes the path is unexercised -- is still
miscompiled in exactly that oversized case. Downstream, JoeyLib ran for
weeks on a phantom struct at (void*)1 whose first field happened to
alias the real framebuffer pointer; see PERF-AUDIT.md #81 for the full
forensics. JoeyLib no longer allocates anything large on the C heap,
but ANY program that exhausts this heap gets silent corruption instead
of NULL. Minimal repro: link any GS/OS app with default heap, call
calloc(1, 720), observe the return value.
## 5. Segment BSS/heap spans are not reserved from the Memory Manager (PERF-AUDIT #81/#77/#85) -- CRITICAL, EMPIRICALLY PROVEN
Three independent confirmations, in escalating severity:
1. The bank-0 C heap (unreserved) -- #81's phantom-stage chain.
2. The pinned SHR framebuffer $01:2000-$9CFF (unreserved) -- an MM
handle landed at $01:8B04 inside the display (#85).
3. THE GENERAL CASE: a 32KB NewHandle was placed at $08:4200 -- inside
the app's own ENTRY-SEGMENT BANK, overlapping seg1's BSS span. The
loader only claims each segment's image bytes; everything above
(BSS, heap) is MM-free. ANY GS/OS allocation -- the app's own or
the system's -- can land on live program data.
JoeyLib has stopgap claims for cases 1-2 (jlpInit reserves the
framebuffer and $00:B900-$BEFF). Case 3 was attempted app-side on
2026-07-05 (spacetaxi acceptance test) and the attempt produced two
findings you should have:
(a) The app CAN learn its BSS spans: the linker-emitted
__bss_seg{0..3}_{lo16,bank,size} symbols (the ones crt0.s zeroes
BSS through) are readable from C via a `.long` DATA32 table in an
asm TU -- direct C IMM16 references fail to link ("cross-segment
reloc ... uses 2-byte form; only IMM24 / DATA32 supports cINTERSEG
patching"). The DATA32 entries resolve as RELOCATED FULL ADDRESSES:
the lo16 entry reads back as the 24-bit runtime span base
($08:4F00 observed for the spacetaxi build), the size entry
carries the length in its low 16 bits, empty segments read as
bank-base-only. Recipe verified working end-to-end under MAME.
(b) BUT actually claiming the span kills the app: NewHandle(
attrAddr|attrFixed|attrLocked, base=$08:4F00, size=$6619) from
jlpInit SUCCEEDS -- and the app then dies before reaching main's
first log line, with the PC profile showing GS/OS desktop idle
(silent return-to-Finder). Reproduced twice; reverting only the
claim restores the previous behavior. Mechanism unknown from the
app side -- presumably the Loader/MM bookkeeping objects to a
user handle overlapping a loaded segment's address range, or the
claim starves something during the remaining init. The reverted
implementation was never committed; the full code (asm table +
claim loop) is reproduced verbatim in the copy of this item handed
to the toolchain session at /home/scott/claude/llvm816/JOEYLIB-ASKS.md.
Ask: crt0/omfEmit/loader cooperation so every segment's FULL
in-memory span (image + BSS + heap) is Memory-Manager-reserved at
load time -- with (b) suggesting the reservation must be made BY the
loader glue (or before/while segments are registered), not by app
code after the fact. This single fix unblocks IIgs sprite codegen
(PERF-AUDIT #77), the Space Taxi IIgs port (a 32KB font-surface
jlpBigAlloc lands on live state layout-dependently -- three distinct
failure modes observed as the binary layout shifted), and removes the
whole corruption class.
link816 defines [__heap_start, __heap_end) as the leftover entry-bank
space above BSS, but crt0Gsos never claims that range from the GS/OS
Memory Manager, so NewHandle can legally place blocks on top of live
malloc data (a second, independent corruption channel -- and the
crt0Gsos comments already record a Phase-1.1 fopen-hang caused by this
class). Ask: reserve the heap range at startup (attrAddr|attrBank|
attrFixed|attrLocked), or reimplement malloc on top of an MM handle.
Related hardening: pass attrNoSpec on NewHandle calls in the runtime,
and consider reserving the pinned SHR stage range $01:2000-$9FFF that
JoeyLib's jlpStageAllocPixels hands out as a bare constant.