194 lines
8.2 KiB
ArmAsm
194 lines
8.2 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
|
|
|
|
; 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
|