181 lines
9.3 KiB
Markdown
181 lines
9.3 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.
|
|
|
|
## 6. Post-main exit path crashes to the monitor (COP $00 at $00:0002) -- EMPIRICALLY PROVEN, app-independent (2026-07-20)
|
|
|
|
Every JoeyLib IIgs example that RETURNS from main() dies in the
|
|
monitor instead of quitting to Finder. Reproduced identically for
|
|
KEYS and STAXI under MAME (deterministic, ~3 min): press the app's
|
|
exit key, and execution ends at `COP #$00` executing at $00:0002 with
|
|
S=$17FD. Nobody saw this before because every harness kills the
|
|
emulator after reading logs; an interactive ESC was the first real
|
|
exit ever exercised.
|
|
|
|
MAME debugger execution history at the crash (bp at $000002):
|
|
|
|
00:9EE0: sta $00c068 ; GS/OS call tail: state reg restore
|
|
00:9EE8: xce
|
|
00:9EE9: plp
|
|
00:9EEA: rtl ; returns to the app-side thunk:
|
|
08:0072: php ; <- pushes 1 byte...
|
|
08:0073: rtl ; <- ...then pulls 3: garbage return
|
|
00:0002: cop #$00 ; monitor
|
|
|
|
The thunk at segment offset $0072 (bank $08 at run time) executes
|
|
`php / rtl` after a GS/OS call returns to it -- the php mis-balances
|
|
the stack and the rtl returns to $00:0002. This is in the crt0Gsos /
|
|
runtime exit code (the offset is constant across different apps, so
|
|
it is runtime code, not app code). Suspects: a Quit stub whose body
|
|
was meant to sit between the php and the rtl, or a mis-assembled
|
|
epilogue.
|
|
|
|
Reproducer scripts (MAME, headless, breakpoint + history + RAM dump):
|
|
this session's scratchpad escCrash.sh / escCrashKeys.sh -- ask me and
|
|
I'll re-stage them anywhere durable.
|
|
|
|
WORKAROUND LANDED IN JOEYLIB (so this is no longer user-visible):
|
|
src/iigs/hal.c jlpShutdown now issues GS/OS QuitGS ($2029, pCount=0)
|
|
itself via a runtime-built jsl stub and never returns to the runtime
|
|
exit path. jlShutdown therefore does not return on IIgs. The root
|
|
cause still deserves a runtime fix: any app that returns from main
|
|
WITHOUT calling jlShutdown (or any non-JoeyLib program built on this
|
|
runtime) still crashes.
|