; crt0 — C runtime startup for the W65816 backend.
;
; Entry point invoked by the loader (or the OMF dispatcher).  Sets up
; the processor mode the rest of the runtime expects, zeroes BSS,
; calls main, and halts on return.
;
; Conventions:
;   - Native mode (E=0), 16-bit M and X (REP #$30) on entry to main.
;   - DP=0, DBR=0 — assumed by the C runtime.
;   - Linker-emitted symbols: __bss_start, __bss_end (16-bit addrs).

	.text

	.globl __start
__start:
	; Disable IRQ first — the IIgs ROM hands a vsync IRQ on every frame,
	; and its handler runs in 8-bit M/X mode, corrupting our state if
	; we leave I clear.  SEI is fine in either emulation or native
	; mode and is always 1 byte / 2 cycles.
	sei
	; Native mode + 16-bit registers.
	clc
	xce
	rep #0x30
	; Disable IIgs peripheral interrupt sources at the chip level —
	; SEI alone leaves the hardware lines asserted, and the IRQ trap
	; in ROM keeps re-firing if the source isn't quiesced.  STZ
	; stores zero without going through A; in M=8 it stores 1 byte
	; (matching the 8-bit registers), so no LDA #0 prelude is needed.
	sep #0x20
	stz 0xc041               ; INTEN = 0  (clear AN3/mouse/0.25s/VBL/mouse-IRQ enables)
	stz 0xc023               ; VGCINT = 0 (clear external/1-sec/scan-line IRQ enables)
	stz 0xc032               ; SCANINT clear
	rep #0x20

	; Top-of-stack at $0FFF.  Native-mode S is 16-bit, so we don't need
	; to stay in page 1.  Soft-double frames can be ~170 bytes plus the
	; usual call-chain overhead — at $01FF stack growth wraps into the
	; direct page ($0000-$00FF) which holds our libcall scratch
	; ($E0-$F4) and IMG slots ($D0-$DE), corrupting them.  $0FFF gives
	; ~3.5 KB of headroom and stays below the text base ($1000).
	lda #0x0fff
	tcs

	; Enable Language Card RAM at $D000-$FFFF for read+write.  This
	; is 12KB (4KB at $D000-$DFFF in LC bank 1, plus 8KB at
	; $E000-$FFFF common LC area).  The IIgs LC area defaults to
	; ROM-mapped; two reads of $C083 enable bank-1 RAM read AND
	; write for the whole $D000-$FFFF range.  link816 may auto-
	; relocate BSS / heap into this area when text+rodata grows
	; past $BFFF — without this enable, writes drop on the floor
	; and reads return ROM bytes.  The reads must be 8-bit (one
	; byte at a time) — a 16-bit M read at $C083 would also touch
	; $C084 (a different soft switch), wiping the LC enable.
	sep #0x20
	lda 0xc083
	lda 0xc083
	rep #0x20

	; Persistent "current data bank" byte at DP $BE.  The LDAptr/
	; STAptr/STBptr inserters load this into $E2 before each [dp],Y
	; deref so pointer-deref lands in the user's bank, matching where
	; DBR-relative absolute stores go.  Under MAME (no Loader), DBR=0
	; and PBR=0 here, so $BE=0 — equivalent to the prior STZ $E2
	; behavior.  Under GS/OS Loader, crt0Gsos.s sets it to PBR.
	;
	; $BE chosen because it's outside both the libcall scratch range
	; ($E0..$FF used by libgcc.s for i64 ops) and the IMG slot range
	; ($C0..$DE).  PHK pushes 1 byte; PLA in M=8 to pull just 1 byte.
	sep #0x20
	phk
	pla                       ; A's low byte = current PBR
	sta 0xbe                  ; persistent data bank
	stz 0xbf                  ; pad: `lda 0xbe` in 16-bit M loads $00PBR
	                          ; — used by codegen for &symbol bank halves
	rep #0x20

	; Zero BSS.  Up to 4 segments — linker emits __bss_seg{0..3}_lo16
	; / _bank / _size symbols.  Segments with size=0 are skipped.
	; Each segment is cleared with DBR-relative STZ abs,X after
	; setting DBR to the segment's bank.  Original DBR restored at
	; end via PLB.
	rep #0x10                ; ensure X is 16-bit
	phb                      ; save current DBR

	; ---- segment 0 ----
	; The `sep #0x20` that sets the segment's data bank also puts us in
	; M=8 for the whole store loop (X stays 16-bit), so we don't flip
	; SEP/REP per byte; each segment label below re-enters via `rep`.
	rep #0x20
	ldx #__bss_seg0_size
	beq .Lbss_seg1
	sep #0x20
	.a8                       ; following lda #imm8 uses 1-byte immediate
	lda #__bss_seg0_bank
	pha
	plb
	ldx #0
.Lbss_loop0:
	cpx #__bss_seg0_size
	bcs .Lbss_seg1
	stz __bss_seg0_lo16, x
	inx
	bra .Lbss_loop0
.Lbss_seg1:
	; ---- segment 1 ----
	rep #0x20
	ldx #__bss_seg1_size
	beq .Lbss_seg2
	sep #0x20
	.a8                       ; following lda #imm8 uses 1-byte immediate
	lda #__bss_seg1_bank
	pha
	plb
	ldx #0
.Lbss_loop1:
	cpx #__bss_seg1_size
	bcs .Lbss_seg2
	stz __bss_seg1_lo16, x
	inx
	bra .Lbss_loop1
.Lbss_seg2:
	; ---- segment 2 ----
	rep #0x20
	ldx #__bss_seg2_size
	beq .Lbss_seg3
	sep #0x20
	.a8                       ; following lda #imm8 uses 1-byte immediate
	lda #__bss_seg2_bank
	pha
	plb
	ldx #0
.Lbss_loop2:
	cpx #__bss_seg2_size
	bcs .Lbss_seg3
	stz __bss_seg2_lo16, x
	inx
	bra .Lbss_loop2
.Lbss_seg3:
	; ---- segment 3 ----
	rep #0x20
	ldx #__bss_seg3_size
	beq .Lbss_done
	sep #0x20
	.a8                       ; following lda #imm8 uses 1-byte immediate
	lda #__bss_seg3_bank
	pha
	plb
	ldx #0
.Lbss_loop3:
	cpx #__bss_seg3_size
	bcs .Lbss_done
	stz __bss_seg3_lo16, x
	inx
	bra .Lbss_loop3
.Lbss_done:
	rep #0x20                ; back to M=16 after the M=8 store loop
	plb                      ; restore caller's DBR

	; Run static constructors.  The linker emits
	; __init_array_start / __init_array_end around the .init_array
	; section; under p:32:16 each entry is a 32-bit function pointer
	; (low 16 bits = function offset, high 16 bits = bank, 0 for our
	; single-bank programs).  Walk in 4-byte stride and JSL each via
	; __jsl_indir using only the low half.
	rep #0x30                ; native, 16-bit M and X
	ldx #__init_array_start
.Linit_loop:
	cpx #__init_array_end
	bcs .Linit_done
	; __jsl_indir does `JMP (__indirTarget)` — reads a 16-bit ptr
	; from __indirTarget and JMPs there.  So __indirTarget must
	; hold the function pointer itself (NOT the address of the
	; init_array slot).  Dereference the entry: ($E0)→A.
	stx 0xe0                 ; entry addr -> DP scratch
	ldy #0
	lda (0xe0), y            ; A = mem[X] (DP-indirect-Y, opcode 0xb1)
	sta 0xb8                 ; __indirTarget pinned at DP $B8 (bank-0 fixed; see libgcc.s)
	phx                      ; preserve X across the call
	jsl __jsl_indir
	plx
	; Step by 4 bytes (sizeof(void*) under p:32:16).
	inx
	inx
	inx
	inx
	bra .Linit_loop
.Linit_done:

	; Tool Locator init is the OS's job, not crt0's:
	;   - GS/OS Loader (crt0Gsos.s path) pre-inits TL before transferring
	;     control to __start.  helloBeep proves this: it does `SysBeep`
	;     (JSL $E10000 dispatch) with zero init in user code and works.
	;   - GNO kernel (crt0Gno.s path) uses JSL $E10000 itself, so TL is
	;     up before __start runs.
	;   - Bare metal (this crt0.s): ROM pre-boot has already run
	;     TLBootInit/MMBootInit; libc.c's iigsToolboxInit() does a per-
	;     process TLStartUp + explicit SEI for clients of time()/clock().
	;     Do NOT inline TLStartUp here — it CLI's internally, and the ROM
	;     VBL handler runs in M=X=8 and would corrupt our in-progress
	;     M=16 register state (the most plausible root of the earlier
	;     attempt's "MAME segfault" mystery).
	; Programs that use per-process tools (MM/QD/EM/WM) must call
	; MMStartUp() and per-tool StartUps themselves — typically via
	; iigs/desktop.h's startdesk(640) helper.

	; Set DBR to the BSS bank before calling main.  Without this, the
	; user has to manually switch the data bank register (with a
	; `pha;plb` inline-asm idiom) before doing absolute data
	; accesses.  Calypsi's "small data model" assumes DBR is
	; pre-set; we now follow the same convention.
	;
	; Linker emits __bss_seg0_bank as the bank byte of the first
	; BSS segment (typically 2 with the default --bss-base 0x020000).
	; Programs that need bank-0 access (e.g., for $C0xx soft
	; switches) can use STA_Long or temporarily switch DBR back.
	sep #0x20
	.a8                      ; following lda #imm8 uses 1-byte immediate
	lda #__bss_seg0_bank
	pha
	plb
	rep #0x20
	.a16                     ; restore M=16 for the code that follows

	; Call main.  Standard W65816 ABI: i16 first arg in A; we pass
	; nothing.  After return, A holds the exit code.
	jsl main

	; Run global dtors registered via __cxa_atexit (LIFO order).  No
	; OS exit on bare metal — they run before we spin.  A holds main()'s
	; return; preserve it (harmless on bare metal but keeps the
	; convention uniform across crt0 variants).
	pha
	jsl __run_cxa_atexit
	pla

	; main returned.  Bare metal has no OS to return to, so spin.
	; (Previously a BRK $00 here, but headless MAME mis-vectors BRK to
	; $0000 and wild-jumps, which reads as a crash.  A tight loop is the
	; conventional bare-metal halt; IRQs are already masked above, so
	; this just idles.)
.Lhalt:
	bra .Lhalt

	.size __start, . - __start
