crt0Gsos: honor Loader DP + DP-aware __jsl_indir
This commit is contained in:
parent
c745821c7d
commit
6a90b2ec19
4 changed files with 125 additions and 22 deletions
Binary file not shown.
|
|
@ -34,18 +34,38 @@ __start:
|
|||
phk
|
||||
plb
|
||||
|
||||
; Set DP=0. The C compiler assumes DP=0 for all `sta dp` and
|
||||
; `[dp],y`-style accesses; GS/OS hands us a Memory-Manager-
|
||||
; allocated DP page that we discard.
|
||||
; HONOR the Loader-allocated DP. GS/OS' System Loader hands us a
|
||||
; Memory-Manager-reserved direct-page (via the ~Direct OMF segment,
|
||||
; or its 4 KB default) and points D at it on entry to __start.
|
||||
; Earlier this crt did `lda #0 ; tcd` to force DP=0 because the
|
||||
; libgcc `__jsl_indir` trampoline hard-coded `jmp ($00B8)` for
|
||||
; indirect dispatch (which only matches DP-relative `sta $B8` when
|
||||
; DP=0). That put every codegen DP slot — the indirect-call vector
|
||||
; at $B8, the PBR stash at $BE, the libcall scratch at $E0..$FF —
|
||||
; on top of bank-0 system zero page, which ROM IRQ handlers and
|
||||
; GS/OS dispatcher state freely scribble. Clang-built S16 apps
|
||||
; with C++ ctors crashed in the init_array indirect-dispatch loop
|
||||
; for exactly that reason.
|
||||
;
|
||||
; The fix: keep DP at the Loader's allocation (no `tcd #0` here),
|
||||
; AND patch `__jsl_indir_op` (the operand of the JMP (abs) inside
|
||||
; libgcc's __jsl_indir trampoline) to (DP + $B8) so the trampoline
|
||||
; reads the indirect target from the right bank-0 slot. See
|
||||
; libgcc.s commentary on the SMC anchor. Every codegen DP slot
|
||||
; now lands in Loader-reserved memory that no system code touches.
|
||||
rep #0x30
|
||||
lda #0
|
||||
tcd
|
||||
tdc
|
||||
clc
|
||||
adc #0xb8
|
||||
sta __jsl_indir_op
|
||||
|
||||
; Persistent "current data bank" byte at DP $BE. Set to PBR
|
||||
; (= our load bank) so the LDAptr/STAptr/STBptr inserters'
|
||||
; "LDA $BE; STA $E2" sequence puts pointer derefs in our bank,
|
||||
; matching DBR-relative absolute stores. $BE is outside the
|
||||
; libcall scratch range ($E0..$FF used by libgcc.s for i64 ops).
|
||||
; The DP form `sta $BE` writes to (DP+$BE) — Loader-allocated
|
||||
; memory under GS/OS, bank-0 zero page under bare-metal/GNO.
|
||||
; See crt0.s.
|
||||
sep #0x20
|
||||
phk
|
||||
|
|
|
|||
|
|
@ -34,19 +34,39 @@
|
|||
; ... arg pushes ...
|
||||
; jsl __jsl_indir
|
||||
;
|
||||
; __indirTarget MUST live at a fixed bank-0 storage slot. On the
|
||||
; 65816, JMP (abs) (opcode 0x6C) reads its vector from BANK 0
|
||||
; unconditionally — it ignores both PBR and DBR. If __indirTarget
|
||||
; were in normal BSS, GS/OS Loader / GNO would relocate the BSS
|
||||
; storage into the program's bank (e.g., $01:9180), but the JMP would
|
||||
; still read from $00:9180 — wherever, garbage, crash. Pinning it
|
||||
; at DP $B8 sidesteps the relocation: DP=0 in our runtime, so DP slot
|
||||
; $B8 IS bank-0 byte $00:00B8. Callers store via `sta $B8` (DP form),
|
||||
; which writes to the same $00:00B8 regardless of DBR; the trampoline
|
||||
; below reads via `jmp ($00B8)` (16-bit abs operand into bank-0
|
||||
; indirect). Single-bank assumption on the *target's* code remains
|
||||
; (JMP indirect transfers to current PBR), but program data location
|
||||
; no longer matters.
|
||||
; __indirTarget is a DP-relative slot at offset $B8. Callers store the
|
||||
; target's 16-bit address with `sta $B8` (DP-form, writes to (DP+$B8)
|
||||
; in bank 0). The trampoline below is a JMP (abs) whose operand has
|
||||
; been patched by the crt0 variant at startup to point at the runtime
|
||||
; DP+$B8 anchor; the JSL → JMP → target flow then takes a single CPU
|
||||
; instruction past the JSL, so A and X (which hold arg0 for the 65816
|
||||
; C ABI) survive the indirection unmodified.
|
||||
;
|
||||
; Why the SMC dispatch instead of a fixed `jmp ($00B8)`:
|
||||
; Under bare-metal (crt0.s) and GNO/ME (crt0Gno.s) the runtime sets
|
||||
; DP=0, so the patched operand stays $00B8 and the dispatch is byte-
|
||||
; identical to the legacy fixed form. Under the GS/OS Loader
|
||||
; (crt0Gsos.s) we now keep DP at the Loader-allocated value rather
|
||||
; than forcing DP=0 — that keeps every codegen scratch slot (the
|
||||
; indirect-call anchor at $B8, the PBR stash at $BE, the libcall
|
||||
; scratch at $E0..$FF) in Loader-reserved bank-0 memory instead of
|
||||
; on top of system zero page that ROM IRQ handlers and GS/OS
|
||||
; dispatcher state freely scribble. Forcing DP=0 was making clang-
|
||||
; built S16 apps with C++ ctors crash during the init_array
|
||||
; indirect-dispatch loop for exactly that reason.
|
||||
;
|
||||
; Constraint preserved: the JMP-indirect anchor must be in bank 0 (the
|
||||
; 65816 JMP (abs) opcode 0x6C reads its PC vector from bank-0
|
||||
; unconditionally). DP is always in bank 0 on the 65816, so
|
||||
; (DP+$B8) is automatically a bank-0 address. The target that gets
|
||||
; jumped to runs in the current PBR (intra-bank function pointer);
|
||||
; cross-bank function pointers need a different dispatch (the Phase
|
||||
; C.2 cINTERSEG path on the JSL, not on this vector).
|
||||
;
|
||||
; SMC patch site: crt0 variants compute (DP + $B8) at startup with
|
||||
; tdc ; clc ; adc #0xb8 ; sta __jsl_indir_op
|
||||
; The default operand below is $00B8 so a program that links without
|
||||
; patching (e.g. an asm-only test) still works under DP=0.
|
||||
; --------------------------------------------------------------------
|
||||
.globl __indirTarget
|
||||
__indirTarget = 0xb8
|
||||
|
|
@ -54,9 +74,14 @@ __indirTarget = 0xb8
|
|||
.text
|
||||
.globl __jsl_indir
|
||||
__jsl_indir:
|
||||
; PC <- mem[$00:00B8]. Indirect dispatch through the bank-0-
|
||||
; anchored function-pointer slot — opcode 0x6C (JMP (abs)).
|
||||
jmp (__indirTarget)
|
||||
.byte 0x6c ; JMP (abs) opcode (Apple 65816 uses 16-bit operand;
|
||||
; the resulting effective address is bank-0:operand,
|
||||
; from which the 16-bit target PC is read).
|
||||
.globl __jsl_indir_op
|
||||
__jsl_indir_op:
|
||||
.byte 0xb8, 0x00 ; operand: bank-0 address of the target slot.
|
||||
; Patched at startup by crt0 to (DP + $B8); default
|
||||
; $00B8 is correct when DP=0 (bare-metal, GNO).
|
||||
|
||||
; --------------------------------------------------------------------
|
||||
; __run_cxa_atexit — weak no-op fallback.
|
||||
|
|
|
|||
|
|
@ -6569,6 +6569,61 @@ EOF
|
|||
"${binPhc2%.bin}".seg*.bin "${relPhc2}".seg*.reloc
|
||||
fi
|
||||
|
||||
# crt0Gsos indirect-dispatch regression: ensure the __jsl_indir SMC patch
|
||||
# (`tdc; clc; adc #$B8; sta __jsl_indir_op` in crt0Gsos.s) lets a clang-
|
||||
# built S16 app survive an init_array-style indirect call under the
|
||||
# real GS/OS Loader. Before the fix, crt0Gsos forced DP=0 and the
|
||||
# indirect dispatcher hard-coded $00B8 — but bank-0 zero page is
|
||||
# scribbled by ROM IRQ handlers + GS/OS dispatcher state, so the first
|
||||
# indirect call (C++ static ctor, etc.) raced and crashed. The probe
|
||||
# below uses a function-pointer table to force the codegen indirect
|
||||
# path through __jsl_indir. Marker $70 = 0x0B = twice(7) + neg(3).
|
||||
if [ "${SMOKE_SKIP_GSOSINDIR:-0}" != "1" ] \
|
||||
&& [ -x "$CLANG" ] && [ -x "$CADIUS" ] && [ -f "$SYSDISK" ] \
|
||||
&& command -v mame >/dev/null 2>&1; then
|
||||
log "check: crt0Gsos indirect-dispatch under real GS/OS Loader"
|
||||
cGin="$(mktemp --suffix=.c)"
|
||||
oGin="$(mktemp --suffix=.o)"
|
||||
binGin="$(mktemp --suffix=.bin)"
|
||||
mapGin="$(mktemp --suffix=.map)"
|
||||
relGin="$(mktemp --suffix=.reloc)"
|
||||
omfGin="$(mktemp --suffix=.omf)"
|
||||
cat > "$cGin" <<'EOF'
|
||||
typedef int (*FnT)(int);
|
||||
static int twice(int x) { return x * 2; }
|
||||
static int neg(int x) { return -x; }
|
||||
volatile int gI = 0;
|
||||
int main(void) {
|
||||
FnT tab[2] = { twice, neg };
|
||||
int r = tab[gI](7) + tab[1](3);
|
||||
*(volatile unsigned char *)0x70 = (unsigned char)r;
|
||||
return 0;
|
||||
}
|
||||
EOF
|
||||
"$CLANG" --target=w65816 -I"$PROJECT_ROOT/runtime/include" -O2 \
|
||||
-ffunction-sections -c "$cGin" -o "$oGin"
|
||||
"$PROJECT_ROOT/tools/link816" -o "$binGin" --text-base 0x1000 \
|
||||
--bss-base 0xA000 --map "$mapGin" --reloc-out "$relGin" \
|
||||
"$PROJECT_ROOT/runtime/crt0Gsos.o" "$oGin" \
|
||||
"$PROJECT_ROOT/runtime/libc.o" "$PROJECT_ROOT/runtime/snprintf.o" \
|
||||
"$PROJECT_ROOT/runtime/extras.o" \
|
||||
"$PROJECT_ROOT/runtime/softFloat.o" "$PROJECT_ROOT/runtime/softDouble.o" \
|
||||
"$PROJECT_ROOT/runtime/iigsGsos.o" \
|
||||
"$PROJECT_ROOT/runtime/iigsToolbox.o" "$PROJECT_ROOT/runtime/iigsToolboxExtra.o" \
|
||||
"$PROJECT_ROOT/runtime/libgcc.o" >/dev/null 2>&1 \
|
||||
|| die "crt0Gsos indirect: link failed"
|
||||
"$PROJECT_ROOT/tools/omfEmit" --input "$binGin" --map "$mapGin" \
|
||||
--base 0x1000 --entry __start --output "$omfGin" \
|
||||
--name INDIR --expressload --relocs "$relGin" >/dev/null 2>&1
|
||||
if ! bash "$PROJECT_ROOT/scripts/runViaFinder.sh" "$omfGin" \
|
||||
--check 0x70=0x0b >/dev/null 2>&1; then
|
||||
bash "$PROJECT_ROOT/scripts/runViaFinder.sh" "$omfGin" \
|
||||
--check 0x70=0x0b 2>&1 | tail -5 >&2
|
||||
die "crt0Gsos indirect: marker != 0x0B (DP scribble?)"
|
||||
fi
|
||||
rm -f "$cGin" "$oGin" "$binGin" "$mapGin" "$relGin" "$omfGin"
|
||||
fi
|
||||
|
||||
# Stack-size end-to-end: omfEmit --stack-size must actually propagate a
|
||||
# larger DP/Stack chunk to the GS/OS Loader. Background: prior to the
|
||||
# 2026-06-02 fix, the ~Direct DP/Stack segment was appended to the OMF
|
||||
|
|
@ -6595,12 +6650,15 @@ if [ "${SMOKE_SKIP_STACKSIZE:-0}" != "1" ] \
|
|||
// store its high byte at $71 so the harness can verify Loader honored
|
||||
// --stack-size. $70 = 0x99 marker = program ran.
|
||||
int main(void) {
|
||||
// sta long $00:0071 — force bank-0 absolute (not DP form). crt0Gsos
|
||||
// now honors the Loader-allocated DP rather than forcing DP=0, so a
|
||||
// bare `sta $71` would land at (LoaderDP+$71) and miss bank-0:$71.
|
||||
__asm__ volatile (
|
||||
"rep #0x30\n"
|
||||
"tsc\n"
|
||||
"xba\n"
|
||||
"sep #0x20\n"
|
||||
"sta 0x71\n"
|
||||
".byte 0x8f, 0x71, 0x00, 0x00\n"
|
||||
"rep #0x20\n"
|
||||
);
|
||||
*(volatile unsigned char *)0x70 = 0x99;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue