;; ----------------------------------------------------------------------
;; bank.s — Sprinter banked-call trampolines for SDCC __banked functions.
;;
;; ABI (verified empirically with SDCC 4.5, see memory/sdcc_banking.md):
;;   On entry to ___sdcc_bcall_ehl:
;;     E  = bank id (1..15), HL = target address (low 16 bits).
;;     Args already on stack, pushed by the caller right-to-left.
;;   Stack picture once the trampoline finishes set-up and jp(hl) fires:
;;     SP+0..1 = bjump return (callee's "ret address")
;;     SP+2    = saved physical page (1 byte)
;;     SP+3..4 = bcall caller return
;;     SP+5..  = args  ← __banked callees read from here
;;
;; The 3-byte spacer between callee ret and args is the contract SDCC bakes
;; into its codegen — touching it breaks all __banked calls.
;; ----------------------------------------------------------------------

    .module bank

    .globl  _bank_pages
    .globl  ___sdcc_bcall_ehl
    .globl  ___sdcc_bjump_ehl

;; Choose between W3 (HUGE mode, default) and W1 (BIG mode, sprinter-cc
;; prepends `BANK_W1 = 1`).  Keep in sync with crt0_banked.s.
.ifdef BANK_W1
BANK_PORT = 0xA2
.else
BANK_PORT = 0xE2
.endif

;; HOME area — always mapped, so the trampoline is reachable from any bank.
    .area _CODE

___sdcc_bcall_ehl::
    ;; The 3-byte spacer between bcall-ret and args (1-byte saved page
    ;; + 2-byte bjump return address) is baked into SDCC's __banked
    ;; codegen — callees access their args at fixed offsets from SP.
    ;; We MUST keep that contract intact.
    ;;
    ;; The old `pop af; out (n), a` restore sequence clobbered A,
    ;; dropping uint8_t return values.  We use the `OUT (C), r` form
    ;; (port via C, byte via B) so A is preserved.  BC is scratch in
    ;; SDCC __sdcccall(1), so clobbering it here is harmless.
    in    a, (#BANK_PORT)   ; A = current bank-window page
    push  af                ; push A:F (2 bytes)
    inc   sp                ; keep only the A byte (1-byte spacer)
    call  ___sdcc_bjump_ehl
    ;; Callee returned.  A may hold a uint8_t return value.
    dec   sp                ; widen back to 2 bytes
    pop   bc                ; B = saved page (C = whatever F was)
    ld    c, #BANK_PORT     ; C = bank-window select port
    out   (c), b            ; OUT (port), B — A and F untouched
    ret                     ; back to bcall caller

___sdcc_bjump_ehl::
    push  hl                ; preserve target address
    ld    d, #0             ; D:E = 0:bank_id
    ld    hl, #_bank_pages
    add   hl, de            ; HL = &_bank_pages[bank_id]
    ld    a, (hl)           ; A = physical page for this bank
    pop   hl                ; restore target address
    out   (#BANK_PORT), a   ; map bank into banking window
    jp    (hl)              ; tail-jump to callee

;; Per-bank physical page table. Index = bank id (1-based; [0] is unused).
;; Filled at startup by crt0_banked.s ДО gsinit, а gsinit зануляет
;; _DATA (с 2026-07-04) — поэтому таблица живёт в _CODE (RAM на
;; Sprinter, всегда замаплен: это же нужно и трамплину выше).
;; Регрессия «bank_pages[]=0 → трамплин прыгает в мусор» была именно
;; из-за .area _DATA здесь.
    .area _CODE
_bank_pages::
    .ds 16
