8531b25e75
Регрессия от 961cfb7 (gsinit зануляет _DATA, 2026-07-04): crt0_banked
заполняет таблицу физических страниц _bank_pages ДО gsinit, а тот её
стирал — трамплины banked-вызовов читали нули и прыгали в незамапленную
страницу. Висли ВСЕ banked-программы (banked/bankedbg/banklocl/
banktest); найдено MAME-смоуком. Тот коммит перенёс crt0-приватные
_estex_* в _CODE, но _bank_pages в runtime/bank.s пропустил.
- runtime/bank.s: _bank_pages → .area _CODE (RAM, всегда замаплен —
это же условие нужно и трамплину); +16 Б _CODE у программ с bank.s
- app.mk: exe теперь зависит от runtime/*.s — правка crt0/bank.s
перелинковывает тесты без make clean (фикс иначе не подхватывался)
- эталон размеров обновлён (+16 Б у banked/bankedbg/banklocl/
banktest/openenv — size-check поймал ровно их)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
75 lines
3.3 KiB
ArmAsm
75 lines
3.3 KiB
ArmAsm
;; ----------------------------------------------------------------------
|
||
;; 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
|