65816-llvm-mos/runtime/src/crt0Gsos.s
Scott Duensing da095402ec Updated
2026-06-02 23:17:57 -05:00

154 lines
5.9 KiB
ArmAsm

; 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
; 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.
rep #0x30
lda #0
tcd
; 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).
; 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:
rep #0x20 ; restore M=16
; 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 TLStartUp/MMStartUp here. 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). Per-process tool init (MM/QD/EM/WM) is the
; program's responsibility; the desktop demos use startdesk(640)
; from runtime/include/iigs/desktop.h.
; 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