libbgi: скролл-примитивы + --w3 + отчёт раскладки памяти

Скролл региона video->video (неактивная страница -> активная, банк 0x50:
копия = скролл + heal цели):
- gfx_scroll_h / _bgi_scroll_rows_raw — горизонтальный, построчно без
  страйдов (~54Т/строку), DI/EI бандами по 16 строк, h=0=>256;
- gfx_scroll_v / _bgi_scroll_cols_raw — верт. И/ИЛИ гориз. за один проход
  без буфера (колонка = accel-burst LD A,A, STOP между read/write делает
  промежуточный OUT Port_Y безопасным), банды по 16 колонок;
- _gfx_addr_shadow_base (адрес неактивной страницы) + gfx_rect_t.
Пример examples/scroll.

check_banks.py + sprinter-cc: отчёт раскладки памяти для ЛЮБОЙ модели
(W1/W2 код/данные, остаток кучи/стека, W3 при --w3, банки при --bank),
не только при --bank.  Док docs/memory-management.md §10.

--w3 (резидентный код окна W3) + сопутствующее: crt0_banked W3_RESIDENT,
mkexe -W, tests/w3probe.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-23 19:38:00 +03:00
parent d1bc97c589
commit 95c22be9bd
46 changed files with 4125 additions and 133 deletions
+71 -10
View File
@@ -15,8 +15,8 @@
# 0xA2 to auto-detect whether DSS already mapped W2
# (program > 16 KB) or whether we need to allocate
# it ourselves (program < 16 KB). Covers 0..30 KB.
# big: tiny + banked code in W1 [TODO]
# huge: small + banked code in W3 [TODO]
# big: tiny + banked code in W1
# huge: small + banked code in W3
# manual: explicit via --memory-manual / --code-loc / --data-loc
# --memory-manual SPEC placement spec, only with --memory manual.
# SPEC = comma-separated KEY=VAL list:
@@ -33,6 +33,18 @@
# -Wl FLAG extra linker flag (repeatable)
# --bank N=FILE.c compile FILE.c as bank N; repeatable; pulls crt0_banked
# automatically and adds -Wl-b_BANKN=0x{N}C000
# --w3 FILE.c place FILE.c resident in window 3 (0xC000), called
# DIRECTLY (no trampoline). Repeatable. Defaults to
# --memory small; works in tiny|small|big|huge. DSS maps
# all image pages incl. W3. Compiled with --codeseg/
# --constseg W3CODE (writable statics stay in W2). In huge
# the resident page SHARES W3 with trampoline banks: crt0
# captures/restores it (W3_RESIDENT), mkexe gets -W.
# Rules: W3 code must NOT switch the W3 page; W1/W2 code that
# swaps W3 to another page must restore it; no writable data
# belongs in W3 (code + rodata only). From a __banked
# context the resident W3 code is unreachable (but resident
# -> __banked via the W1 trampoline is fine).
# --mkexe FLAG extra mkexe flag (repeatable; e.g. --mkexe -p --mkexe 0)
# --max-allocs N SDCC --max-allocs-per-node (default: 100000 — smaller/
# faster code at the cost of compile time; pass a lower
@@ -88,6 +100,11 @@ SOURCES=()
LD_EXTRA=()
MKEXE_EXTRA=()
BANK_SPECS=() # entries like "1=engine.c"
W3_SPECS=() # entries like "mod.c" — резидентные модули окна W3 (--w3)
W3_RELS=() # заполняется при компиляции W3-модулей
W3_LD_FLAGS=() # -Wl-b_W3CODE=0xC000, если есть --w3
USER_SET_MEMORY="" # непусто, если --memory задан явно (для --w3 авто-small)
W3_RESIDENT_HUGE=0 # 1, если --w3 в режиме huge (резидент делит W3 с банками)
MAX_ALLOCS="100000" # sdcc --max-allocs-per-node; дефолт 100000 — агрессивная
# регистровая аллокация (медленнее компиляция, меньше/
# быстрее код; как fast-сборки библиотек). --max-allocs N
@@ -112,8 +129,9 @@ while [[ $# -gt 0 ]]; do
-S) STACK_ADDR="$2"; shift 2;;
--code-loc) USER_CODE_LOC="$2"; shift 2;;
--data-loc) USER_DATA_LOC="$2"; shift 2;;
--memory) MEMORY_MODE="$2"; shift 2;;
--memory) MEMORY_MODE="$2"; USER_SET_MEMORY=1; shift 2;;
--memory-manual) MEMORY_MANUAL="$2"; shift 2;;
--w3) W3_SPECS+=("$2"); shift 2;;
--stack-size) STACK_SIZE="$2"; shift 2;;
-Wl) LD_EXTRA+=("$2"); shift 2;;
--bank) BANK_SPECS+=("$2"); shift 2;;
@@ -132,6 +150,24 @@ done
[[ -z "$OUT" ]] && { echo "sprinter-cc: -o NAME is required" >&2; exit 1; }
[[ ${#SOURCES[@]} -eq 0 ]] && { echo "sprinter-cc: no input files" >&2; exit 1; }
# ------- --w3: резидентный код окна W3 (подход A) ----------------------------
# W3-модули едут в область W3CODE @0xC000 и вызываются напрямую (без
# трамплинов). Работает поверх small-лейаута: main-код с 0x4100 (W1/W2),
# W3 свободно, DSS маппит все страницы образа сам (проверено tests/w3probe).
# --w3 подразумевает small; с huge несовместим (там W3 занят банками).
if [[ ${#W3_SPECS[@]} -gt 0 ]]; then
[[ -z "$USER_SET_MEMORY" ]] && MEMORY_MODE="small" # --w3 → small по умолчанию
case "$MEMORY_MODE" in
tiny|small|big|huge) : ;; # поддержаны (huge — резидент делит W3 с банками)
*) echo "sprinter-cc: --w3 поддержан для tiny|small|big|huge (дано: $MEMORY_MODE)" >&2; exit 1;;
esac
# huge: резидентный W3-код (0xC000, HOME) сосуществует с трамплин-банками
# W3 — crt0_banked захватывает резидентную страницу и возвращает её
# дефолтом после загрузки банков (см. crt0_banked.s, W3_RESIDENT).
W3_RESIDENT_HUGE=0
[[ "$MEMORY_MODE" == "huge" ]] && W3_RESIDENT_HUGE=1
fi
# ------- BGI graphics driver selection (--gfx) -------------------------------
# graphics.h — mode-agnostic слой; конкретный видеорежим задаёт driver-
# архив. Одновременно только один. drv16 пока не реализован.
@@ -186,7 +222,8 @@ case "$MEMORY_MODE" in
huge)
# small layout + banked code in W3. crt0_banked auto-detects W2
# the same way crt0_small does, then loads banks from the .EXE.
MODE_CODE_LOC="0x4100"; MODE_DATA_LOC="0x8000"
# MODE_CODE_LOC="0x4100"; MODE_DATA_LOC="0x8000"
MODE_CODE_LOC="0x4100"; MODE_DATA_LOC="0"
;;
manual)
# Defaults if SPEC omits a key.
@@ -280,6 +317,7 @@ asm_runtime() {
local prefix=""
[[ $DEBUG_RT -eq 1 ]] && prefix+="DEBUG_RT = 1"$'\n'
[[ $BANK_W1 -eq 1 ]] && prefix+="BANK_W1 = 1"$'\n'
[[ $W3_RESIDENT_HUGE -eq 1 ]] && prefix+="W3_RESIDENT = 1"$'\n'
if [[ -n "$prefix" ]]; then
local patched="$WORK/$(basename "$src" .s)_patched.s"
{ printf "%s" "$prefix"; cat "$src"; } > "$patched"
@@ -305,9 +343,14 @@ if [[ -n "$STACK_SIZE" ]]; then
printf " .module sprinter_heap_top\n ___sdcc_heap_end = 0x%04X\n .globl ___sdcc_heap_end\n" \
"$_heap_top" > "$WORK/heap_top.s"
HEAP_TOP_SRC="$WORK/heap_top.s"
[[ $VERBOSE -eq 1 ]] && echo " heap_top: custom HEAP_TOP=$(printf '0x%04X' $_heap_top) (stack reserve $_stack_sz bytes)"
HEAP_TOP_VAL=$(printf '0x%04X' "$_heap_top")
[[ $VERBOSE -eq 1 ]] && echo " heap_top: custom HEAP_TOP=$HEAP_TOP_VAL (stack reserve $_stack_sz bytes)"
else
HEAP_TOP_SRC="$RUNTIME/heap_top.s"
# Дефолт из runtime/heap_top.s (для отчёта раскладки) — обычно 0xBB00.
HEAP_TOP_VAL=$(grep -oiE '___sdcc_heap_end[[:space:]]*=[[:space:]]*0x[0-9A-Fa-f]+' "$RUNTIME/heap_top.s" \
| grep -oiE '0x[0-9A-Fa-f]+' | head -1)
HEAP_TOP_VAL="${HEAP_TOP_VAL:-0xBB00}"
fi
run "$SDASZ80" -o "$HEAP_TOP_REL" "$HEAP_TOP_SRC"
@@ -322,6 +365,20 @@ for src in "${SOURCES[@]}"; do
USER_RELS+=("$rel")
done
# 2b. --w3 resident modules → .rel. --codeseg/--constseg W3CODE кладёт код
# и rodata в W3 (0xC000); --dataseg НЕ трогаем — писучие статики
# остаются в обычном _DATA (W2). Прямые вызовы, без трамплинов.
if [[ ${#W3_SPECS[@]} -gt 0 ]]; then
for src in "${W3_SPECS[@]}"; do
rel="$WORK/w3_$(basename "$src" .c).rel"
run "$SDCC" "${CC_FLAGS[@]}" --codeseg W3CODE --constseg W3CODE \
-c -o "$rel" "$src"
W3_RELS+=("$rel")
done
W3_LD_FLAGS+=("-Wl-b_W3CODE=0xC000")
[[ $VERBOSE -eq 1 ]] && echo " w3: ${#W3_SPECS[@]} module(s) resident @0xC000 (direct call)"
fi
# 3. bank infrastructure — required whenever crt0_banked is in play.
# Always assemble bank.s for _bank_pages + the bcall/bjump trampolines.
# If the user did not pass any --bank, generate a tiny stub providing
@@ -369,6 +426,7 @@ IHX="$WORK/$(basename "$OUT" .exe).ihx"
LINK_FLAGS=(-mz80 --no-std-crt0 --std-c99 --opt-code-size
--code-loc "$CODE_LOC" --data-loc "$DATA_LOC")
LINK_FLAGS+=("${BANK_LD_FLAGS[@]}")
LINK_FLAGS+=("${W3_LD_FLAGS[@]}")
for f in "${LD_EXTRA[@]}"; do LINK_FLAGS+=("$f"); done
# libsprinter.lib via -l/-L (sdcc passes -lsprinter through to sdldz80).
#
@@ -379,12 +437,12 @@ for f in "${LD_EXTRA[@]}"; do LINK_FLAGS+=("$f"); done
# the warning is just noise. In verbose mode show everything.
if [[ $VERBOSE -eq 1 ]]; then
run "$SDCC" "${LINK_FLAGS[@]}" -o "$IHX" \
"$CRT0_REL" "$HEAP_TOP_REL" "${USER_RELS[@]}" "${BANK_RELS[@]}" \
"$CRT0_REL" "$HEAP_TOP_REL" "${USER_RELS[@]}" "${BANK_RELS[@]}" "${W3_RELS[@]}" \
"-L$LIB_DIR" "${GFX_LD[@]}" "$LIBC_LD"
else
# Drop the warning line + its two follow-up "Library:" lines.
run "$SDCC" "${LINK_FLAGS[@]}" -o "$IHX" \
"$CRT0_REL" "$HEAP_TOP_REL" "${USER_RELS[@]}" "${BANK_RELS[@]}" \
"$CRT0_REL" "$HEAP_TOP_REL" "${USER_RELS[@]}" "${BANK_RELS[@]}" "${W3_RELS[@]}" \
"-L$LIB_DIR" "${GFX_LD[@]}" "$LIBC_LD" 2>&1 \
| awk '
/^\?ASlink-Warning-Definition of public symbol/ { skip = 3 }
@@ -393,9 +451,11 @@ else
'
fi
# Quick bank-size check (only meaningful if there are banks).
if [[ ${#BANK_SPECS[@]} -gt 0 ]] && [[ -f "${IHX%.ihx}.map" ]]; then
python3 "$CHECK_BANKS" "${IHX%.ihx}.map" || true
# Отчёт по раскладке памяти + проверка лимитов — для ЛЮБОЙ модели памяти
# (сколько свободно в W1/W2, W3 при --w3, и в каждом банке при --bank).
if [[ -f "${IHX%.ihx}.map" ]]; then
python3 "$CHECK_BANKS" "${IHX%.ihx}.map" \
--mode "$MEMORY_MODE" --heap-top "$HEAP_TOP_VAL" --stack "$STACK_ADDR" || true
fi
# 5. mkexe → .exe. In BIG mode tell mkexe banks live at 0x4000 (W1).
@@ -404,6 +464,7 @@ fi
MK_PREFIX=()
[[ $VERBOSE -eq 1 ]] && MK_PREFIX+=(-v)
[[ $BANK_W1 -eq 1 ]] && MK_PREFIX+=(-B 0x4000)
[[ $W3_RESIDENT_HUGE -eq 1 ]] && MK_PREFIX+=(-W)
MK_PREFIX+=("${MKEXE_EXTRA[@]}")
MK_PREFIX+=(-L "$LOAD_ADDR" -E "$ENTRY_ADDR" -S "$STACK_ADDR" -o "$OUT")
run "$MKEXE" "${MK_PREFIX[@]}" "$IHX"