; 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).
	; libgcc's __jsl_indir trampoline reads its indirect target DP-relative
	; ($B8/$BA), so NO operand patch is needed (it used to self-modify a
	; global operand to DP+$B8, which was not concurrency-safe).  Every
	; codegen DP slot lands in Loader-reserved memory no system code touches.

	; 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
