; crt0Gsos.s — GS/OS S16 application crt0. ; ; Use this INSTEAD OF crt0.s when building an OMF that the real ; GS/OS Loader will launch. Differences from crt0.s: ; - No SEI / interrupt-source clearing (GS/OS owns the IRQ chain). ; - No language-card RAM enable (GS/OS configures memory). ; - No stack base reset (GS/OS allocated and set our SP). ; - Honors GS/OS's DBR=our-bank, DP=allocated-page setup. ; - On main() return, calls GS/OS QUIT(pcount=0) to return control to ; the launching program (Finder / shell / boot chain) — the standard ; GS/OS application exit. ; ; Entry from the System Loader (per Apple IIgs Toolbox Reference): ; E=0 (native), M=0 (16-bit accumulator), X=0 (16-bit index) ; DBR = the bank into which our entry segment was placed ; DP = pointer to a Memory-Manager-allocated DP page ; Stack at entry, top-down: ; PCL PCH PBR (3 bytes — JSL return addr to launcher) ; flags-lo flags-hi (2 bytes — launch flags) ; path-low path-mid path-bank pad (4 bytes — pathname long ptr) ; ; QUIT discards the entire stack so we never need to pop the launch ; frame ourselves. .text .globl __start __start: ; Set DBR := PBR so absolute (DBR-relative) loads/stores reach ; symbols within our segment. The Loader's documented contract is ; "DBR set to your bank" but verified empirically NOT always true ; for KIND=0x1000 segments — `lda absConst` was reading from the ; wrong bank without this. PHK + PLB copies PBR into DBR. phk plb ; 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 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 pla sta 0xbe stz 0xbf ; pad byte so `lda 0xbe` in 16-bit M reads $00PBR ; (codegen uses this as the bank-half of `&symbol` ; 32-bit pointers — see W65816AsmPrinter ; LDAi16imm_bank expansion) rep #0x20 ; BSS zero-init: NOT NEEDED under GS/OS. omfEmit embeds the BSS ; region as zeros inside the LCONST data, so the GS/OS Loader ; allocates+fills our BSS during segment load — by the time __start ; runs, BSS is already zero. A redundant `stz` loop here was found ; to HANG fopen / gsosOpen (Phase 1.1 root cause, 2026-06-02): when ; BSS extends past runtime offset ~$9E00 in the placed bank, ; re-zeroing that region corrupts GS/OS Memory-Manager / dispatcher ; state that lives in our allocated chunk between the Loader's ; LCONST-fill and our __start entry. Skipping the redundant zero ; eliminates the corruption; semantics are preserved because the ; Loader already did it. ; See feedback_gsos_fopen_partial_diagnosis (root-caused this session). .Lbss_done: ; (M is already 16-bit; the heap-init block below re-asserts rep #0x30 ; before it needs a 16-bit index, so no width fixup is needed here.) ; Back the C malloc heap with a locked Memory-Manager handle so the ; heap span is MM-owned -- otherwise a later NewHandle (ours or the ; system's) can place a block on top of live malloc data (JoeyLib ; item 5, heap channel). MMStartUp ($0202) returns our Memory-Manager ; user ID in A; pass it as arg0 to __heapInitMM (strong impl in libc.o; ; weak no-op in libgcc.s for programs that don't link libc). crt0Gno.s ; does the SAME (it reuses the MM user ID GNO hands it in A -- stashed at ; DP $B6 -- instead of calling MMStartUp, since GNO already started the ; Memory Manager). Only crt0.s (bare-metal, no Memory Manager) skips this ; and keeps malloc's link-time window. rep #0x30 pha ; MMStartUp result space (word) ldx #0x0202 ; MMStartUp call number jsl 0xe10000 ; Tool Locator dispatch -> A = our MM user ID pla jsl __heapInitMM ; __heapInitMM(userID); arg0 in A ; Walk .init_array (C++ ctors). The `jsl __jsl_indir` and the ; `jsl main` below are relocated by the GS/OS Loader: omfEmit emits ; a cRELOC (0xF5) IMM24 site for each intra-segment JSL, so the ; Loader patches the full 24-bit operand (bank included) to the ; placed address. Non-zero-bank placement works as long as the OMF ; is built with --relocs (demos/build.sh / buildGno.sh do). rep #0x30 ldx #__init_array_start .Linit_loop: cpx #__init_array_end bcs .Linit_done stx 0xe0 ldy #0 lda (0xe0), y sta 0xb8 ; __indirTarget pinned at DP $B8 (bank-0 fixed; see libgcc.s) phx jsl __jsl_indir plx ; Step by 4 bytes (sizeof(void*) under p:32:16). inx inx inx inx bra .Linit_loop .Linit_done: ; NOTE: do NOT add per-process TOOL startup here (TLStartUp, or a full ; MM/QD/EM/WM StartUp chain). The GS/OS Loader has already initialised ; the Tool Locator before transferring control to __start (helloBeep ; proves this — it does a SysBeep JSL $E10000 with zero init code), and ; per-process tool init is the program's responsibility (the desktop ; demos use startdesk(640) from runtime/include/iigs/desktop.h). The ; bare MMStartUp above is NOT that: it only fetches our existing MM user ; ID for __heapInitMM and starts no tool set. ; Seed rand() from the IIgs RTC via ReadTimeHex ($0D03). Without ; this, srand defaults to seed=1 and every run produces an identical ; PRNG sequence -- a correctness bug for mkstemp / tmpnam and any ; user code relying on Monte-Carlo-style uniqueness. TL is up ; (Loader brought it up), so the JSL inside __srandInitFromTime is ; safe. Linker drops the symbol when no rand-consuming code is in ; the link, so this costs ~0 bytes for non-PRNG programs (the ; reference is one weak-resolved JSL). rep #0x30 jsl __srandInitFromTime ; Call main. Standard W65816 C ABI: arg0 in A; we pass none. rep #0x30 jsl main ; Run global dtors registered via __cxa_atexit (LIFO order) before ; QUIT. A holds main()'s return value; QUIT itself doesn't consume ; it (we set up the parm block immediately below), but keep the ; save/restore for parity with crt0Gno. pha jsl __run_cxa_atexit pla ; ---- QUIT (pcount=0): return to the launching program ---------- ; The standard GS/OS application exit. A pcount=0 parm block (just ; a zero pcount word) tells QUIT to return control to whoever ; launched us (Finder, a shell, the boot chain) with default flags ; (non-restartable) — no hardcoded chain path needed. A program ; that wants to chain elsewhere can provide its own _exit/crt0. rep #0x30 stz 0x80 ; pcount = 0 (parm block at DP $0080, bank 0) tdc clc adc #0x80 pha ; parm-block ptr: low 16 (offset) pea 0 ; parm-block ptr: high 16 (bank 0) ldx #0x2029 ; QUIT class-1 call number jsl 0xe100a8 ; GS/OS dispatcher ; QUIT only returns on failure. Clean up the pushed pointer and ; spin (don't fall into a BRK, which headless MAME mis-vectors). pla pla .Lhang: bra .Lhang .size __start, . - __start