Резидент W1/W2: данные pop_room и pop_trob — в свои банки (−2546 Б)
sprinter-cc: новый повторяемый --bank-data=SRC — писучие данные ОДНОГО банкового модуля в его же страницу. Прежний --bank-data был всё-или-ничего и потому неприменим: у большинства банковых модулей часть глобалов читают соседние банки и резидент (hitp_*, pop_upside, pop_loose_modif, pop_cd), и такие данные обязаны остаться замапленными всегда. Кандидаты отобраны по объектным файлам, а не на глаз: символ должен быть Def только в своём .rel и нигде не Ref. Прошли ровно двое — pop_room (_mobs не читает никто) и pop_trob (экспортируемых данных нет вовсе). При любом --bank-data sprinter-cc сам добавляет mkexe -p 0: crt0 зануляет только резидентный _DATA, а mkexe по умолчанию бьёт пустоты 0xFF — иначе вся банковая статика поднялась бы мусором. Итог: данные в W2 6774 -> 4228, свободно до стека 129 -> 2675 Б. BANK7 87 %, BANK6 31 %. Проверено в MAME: уровень 1, комнаты 1-2, фон, факелы, решётки, проваливающиеся полы, переход между комнатами. Метод замера и оставшиеся кандидаты — docs/resident_budget.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,85 @@
|
|||||||
|
# Бюджет резидента W1/W2 и как его освобождать
|
||||||
|
|
||||||
|
Резидент huge-режима — это окно `0x4100..0xBB00` (стек с 0xBB00): код в W1,
|
||||||
|
данные в W2, между концом данных и стеком остаётся куча. Всё, что туда не
|
||||||
|
влезло, обязано жить в банках.
|
||||||
|
|
||||||
|
## Как СМОТРЕТЬ, а не гадать
|
||||||
|
|
||||||
|
Атрибуция по карте (`roomtest.map`) ВРЁТ: file-static SDCC в карту не
|
||||||
|
попадает, и «дырка» между двумя именованными символами приписывается
|
||||||
|
предыдущему модулю целиком. На этом легко получить «у pop_bg 1529 байт
|
||||||
|
данных», когда на самом деле у него 289.
|
||||||
|
|
||||||
|
Правильный источник — объектные файлы: в `.rel` есть строки
|
||||||
|
`A <area> size <n> flags <f>` с ТОЧНЫМ размером каждой области модуля.
|
||||||
|
|
||||||
|
```python
|
||||||
|
# по каждому .rel: _DATA/_BSS/_INITIALIZED = резидентные данные модуля,
|
||||||
|
# _BANKn = его код в банке
|
||||||
|
re.match(r'A\s+(\S+)\s+size\s+([0-9A-Fa-f]+)\s+flags', line)
|
||||||
|
```
|
||||||
|
|
||||||
|
Кто на кого ссылается — оттуда же: `S <sym> Def...` — модуль определяет
|
||||||
|
символ, `S <sym> Ref...` — ссылается на него.
|
||||||
|
|
||||||
|
## Замер 2026-08-20 (до правки)
|
||||||
|
|
||||||
|
Данные банковых модулей, лежавшие в РЕЗИДЕНТЕ:
|
||||||
|
|
||||||
|
| модуль | данные в W2 | разделяемые символы |
|
||||||
|
|---|---:|---|
|
||||||
|
| `pop_room` (банк 7) | 1620 | нет (`_mobs` не читает никто) |
|
||||||
|
| `pop_trob` (банк 6) | 929 | НЕТ ВООБЩЕ |
|
||||||
|
| `pop_bg` (банк 2) | 289 | `pop_bg_obj_col/row` читает `pop_room` |
|
||||||
|
| `pop_map` (банк 3) | 227 | `hitp_*`, `pop_upside*`, `pop_loose_modif`, … — читают 6 модулей |
|
||||||
|
| `pop_cdraw` (банк 4) | 175 | `pop_cd` читают 4 модуля |
|
||||||
|
|
||||||
|
## Что сделано: данные банка — в страницу банка
|
||||||
|
|
||||||
|
`sprinter-cc --bank-data=SRC` (повторяемый) кладёт писучие данные ОДНОГО
|
||||||
|
банкового модуля в его же 16-КБ страницу. Выборочность принципиальна:
|
||||||
|
глобал, который читает соседний банк или резидент, обязан остаться
|
||||||
|
замапленным всегда, иначе чтение придёт из чужой страницы.
|
||||||
|
|
||||||
|
Включено для `pop_room.c` и `pop_trob.c` — единственных модулей, у которых
|
||||||
|
НИ ОДИН символ не Ref снаружи. Результат:
|
||||||
|
|
||||||
|
| | было | стало |
|
||||||
|
|---|---:|---:|
|
||||||
|
| данные в W2 | 6774 | **4228** |
|
||||||
|
| свободно до стека | 129 | **2675** |
|
||||||
|
| BANK6 | 4199 | 5128 (31 %) |
|
||||||
|
| BANK7 | 12684 | 14301 (87 %) |
|
||||||
|
|
||||||
|
**Проверено в MAME**: уровень 1, комнаты 1 и 2 — фон, факелы, решётки,
|
||||||
|
проваливающиеся полы и переход между комнатами работают.
|
||||||
|
|
||||||
|
### Два условия, без которых это молча ломается
|
||||||
|
|
||||||
|
1. **Нулевая инициализация.** crt0 зануляет только резидентный `_DATA`, до
|
||||||
|
банковых страниц он не дотягивается, а `mkexe` по умолчанию забивает
|
||||||
|
пустоты образа `0xFF`. По правилам проекта file-scope переменные никто
|
||||||
|
не инициализирует явно (crt0 же зануляет) — значит вся банковая статика
|
||||||
|
поднялась бы с `0xFF`. Поэтому `sprinter-cc` при любом `--bank-data`
|
||||||
|
САМ добавляет `mkexe -p 0`.
|
||||||
|
2. **`_INITIALIZED` остаётся в резиденте.** `--dataseg` переименовывает
|
||||||
|
только `_DATA`; инициализированные переменные и их `_INITIALIZER` живут
|
||||||
|
как раньше, и gsinit копирует их без участия банков. У `pop_room` таких
|
||||||
|
3 байта — они и остались в W2.
|
||||||
|
|
||||||
|
## Что осталось на столе
|
||||||
|
|
||||||
|
- `malloc` — **287 Б** резидента, который никто не зовёт: его тянет
|
||||||
|
`cbl_open`, где `malloc`/`free` стоят в мёртвой ветке
|
||||||
|
`CBL_UNDERRUN_SILENCE`. Вынести аллокацию буфера тишины в отдельный
|
||||||
|
модуль libc — и линкер выбросит malloc у всех приложений на
|
||||||
|
`CBL_UNDERRUN_APP`.
|
||||||
|
- `pop_bg` 289 Б — уедут, если вынести `pop_bg_obj_col/row` (2 байта!) в
|
||||||
|
общий резидентный модуль состояния.
|
||||||
|
- `pop_tile` — **5972 Б кода** в резиденте, самый крупный жилец. Он там
|
||||||
|
потому, что его листья зовут обе половины слоя фона из РАЗНЫХ банков.
|
||||||
|
Резать имеет смысл только по этому признаку: что зовут из одного банка —
|
||||||
|
туда и переносить.
|
||||||
|
- BANK7 после правки на 87 %: следующий кандидат на разгрузку —
|
||||||
|
`pop_redraw.c` (981 Б) в другой банк.
|
||||||
@@ -35,7 +35,16 @@ MEMORY ?= huge # small-раскладка + банки кода в W
|
|||||||
# ВАЖНО: любое сравнение занятости банков имеет смысл только при ОДНОМ и том
|
# ВАЖНО: любое сравнение занятости банков имеет смысл только при ОДНОМ и том
|
||||||
# же ALLOCS — иначе сравниваются не правки, а уровни оптимизации.
|
# же ALLOCS — иначе сравниваются не правки, а уровни оптимизации.
|
||||||
ALLOCS ?= 3000
|
ALLOCS ?= 3000
|
||||||
EXTRA_FLAGS ?= --gfx 256 -I $(CURDIR)/../poc/res/bg --max-allocs $(ALLOCS) --bank 2=pop_bg.c --bank 7=pop_room.c --bank 4=pop_cdraw.c --bank 4=pop_kdraw.c --bank 3=pop_map.c --bank 1=guards.c --bank 5=pop_ctrl.c --bank 6=pop_trob.c --bank 7=pop_redraw.c --bank 8=roomtest_cold.c --bank 8=pop_level_cold.c --bank 8=pop_kboot.c --bank 8=pop_guard_cold.c --bank 8=pop_shadow.c --bank 8=pop_sfx_cold.c $(PROF_FLAGS)
|
# Писучие данные ЭТИХ банковых модулей живут в их же странице, а не в
|
||||||
|
# резиденте W1/W2 — это 2549 Б, которые иначе съедала бы база. Список
|
||||||
|
# короткий не по лени: у остальных банковых модулей часть глобалов читают
|
||||||
|
# соседние банки и резидент (hitp_*, pop_upside, pop_loose_modif, pop_cd),
|
||||||
|
# и такие данные ОБЯЗАНЫ остаться замапленными всегда. Проверять новичка в
|
||||||
|
# списке — по объектным файлам: символ должен быть Def только в своём .rel и
|
||||||
|
# нигде не Ref (см. ../docs/resident_budget.md).
|
||||||
|
BANK_DATA := --bank-data=pop_room.c --bank-data=pop_trob.c
|
||||||
|
|
||||||
|
EXTRA_FLAGS ?= --gfx 256 -I $(CURDIR)/../poc/res/bg --max-allocs $(ALLOCS) --bank 2=pop_bg.c --bank 7=pop_room.c --bank 4=pop_cdraw.c --bank 4=pop_kdraw.c --bank 3=pop_map.c --bank 1=guards.c --bank 5=pop_ctrl.c --bank 6=pop_trob.c --bank 7=pop_redraw.c --bank 8=roomtest_cold.c --bank 8=pop_level_cold.c --bank 8=pop_kboot.c --bank 8=pop_guard_cold.c --bank 8=pop_shadow.c --bank 8=pop_sfx_cold.c $(BANK_DATA) $(PROF_FLAGS)
|
||||||
# Профилирование полосами бордюра (см. PROF() в roomtest.c) — ВКЛЮЧЕНО по
|
# Профилирование полосами бордюра (см. PROF() в roomtest.c) — ВКЛЮЧЕНО по
|
||||||
# умолчанию, пока идёт работа с производительностью; make PROF=0 выключает.
|
# умолчанию, пока идёт работа с производительностью; make PROF=0 выключает.
|
||||||
# Каждая фаза кадра красит бордюр в свой цвет, высота полосы на скриншоте
|
# Каждая фаза кадра красит бордюр в свой цвет, высота полосы на скриншоте
|
||||||
|
|||||||
+27
-2
@@ -54,6 +54,10 @@
|
|||||||
# no writable data belongs in W3 (code + rodata only). From a
|
# no writable data belongs in W3 (code + rodata only). From a
|
||||||
# __banked context the resident W3 code is unreachable (but
|
# __banked context the resident W3 code is unreachable (but
|
||||||
# resident -> __banked via the W1 trampoline is fine).
|
# resident -> __banked via the W1 trampoline is fine).
|
||||||
|
# --bank-data=SRC писучие данные ТОЛЬКО этого банкового модуля — в его
|
||||||
|
# страницу (repeatable). Безопасно лишь если ни один
|
||||||
|
# другой модуль не ссылается на его глобалы: проверяется
|
||||||
|
# по .rel (Def/Ref). Включает mkexe -p 0 (zero-init).
|
||||||
# --mkexe FLAG extra mkexe flag (repeatable; e.g. --mkexe -p --mkexe 0)
|
# --mkexe FLAG extra mkexe flag (repeatable; e.g. --mkexe -p --mkexe 0)
|
||||||
# --max-allocs N SDCC --max-allocs-per-node (default: 100000 — smaller/
|
# --max-allocs N SDCC --max-allocs-per-node (default: 100000 — smaller/
|
||||||
# faster code at the cost of compile time; pass a lower
|
# faster code at the cost of compile time; pass a lower
|
||||||
@@ -111,7 +115,8 @@ MKEXE_EXTRA=()
|
|||||||
BANK_SPECS=() # entries like "1=engine.c"
|
BANK_SPECS=() # entries like "1=engine.c"
|
||||||
W3_SPECS=() # entries like "mod.c" — резидентные модули окна W3 (--w3)
|
W3_SPECS=() # entries like "mod.c" — резидентные модули окна W3 (--w3)
|
||||||
USER_DEFS=() # -DFOO / -DFOO=bar — пробрасываются в sdcc как есть
|
USER_DEFS=() # -DFOO / -DFOO=bar — пробрасываются в sdcc как есть
|
||||||
BANK_LOCAL_DATA=0 # 1 (--bank-data): писучие данные банков — В СТРАНИЦУ банка
|
BANK_LOCAL_DATA=0 # 1 (--bank-data): писучие данные ВСЕХ банков — в страницу банка
|
||||||
|
BANK_DATA_SRCS=() # --bank-data=src.c: только эти модули (выборочно)
|
||||||
W3_RELS=() # заполняется при компиляции W3-модулей
|
W3_RELS=() # заполняется при компиляции W3-модулей
|
||||||
W3_LD_FLAGS=() # -Wl-b_W3CODE=0xC000, если есть --w3
|
W3_LD_FLAGS=() # -Wl-b_W3CODE=0xC000, если есть --w3
|
||||||
USER_SET_MEMORY="" # непусто, если --memory задан явно (для --w3 авто-small)
|
USER_SET_MEMORY="" # непусто, если --memory задан явно (для --w3 авто-small)
|
||||||
@@ -148,6 +153,7 @@ while [[ $# -gt 0 ]]; do
|
|||||||
-D*) USER_DEFS+=("$1"); shift;;
|
-D*) USER_DEFS+=("$1"); shift;;
|
||||||
--bank) BANK_SPECS+=("$2"); shift 2;;
|
--bank) BANK_SPECS+=("$2"); shift 2;;
|
||||||
--bank-data) BANK_LOCAL_DATA=1; shift;;
|
--bank-data) BANK_LOCAL_DATA=1; shift;;
|
||||||
|
--bank-data=*) BANK_DATA_SRCS+=("${1#*=}"); shift;;
|
||||||
--mkexe) MKEXE_EXTRA+=("$2"); shift 2;;
|
--mkexe) MKEXE_EXTRA+=("$2"); shift 2;;
|
||||||
--max-allocs) MAX_ALLOCS="$2"; shift 2;;
|
--max-allocs) MAX_ALLOCS="$2"; shift 2;;
|
||||||
--gfx) GFX_MODE="$2"; shift 2;;
|
--gfx) GFX_MODE="$2"; shift 2;;
|
||||||
@@ -436,8 +442,17 @@ if [[ "$CRT0_TYPE" == "banked" ]]; then
|
|||||||
# глобал банкового модуля лёг по 0x0001C000): это экономит
|
# глобал банкового модуля лёг по 0x0001C000): это экономит
|
||||||
# W1/W2, но требует ручного контроля видимости, поэтому
|
# W1/W2, но требует ручного контроля видимости, поэтому
|
||||||
# включается явно через --bank-data.
|
# включается явно через --bank-data.
|
||||||
|
# Выборочный режим (--bank-data=src.c) точнее глобального: у
|
||||||
|
# большинства банковых модулей часть глобалов ЧИТАЮТ соседние
|
||||||
|
# банки и резидент, и такие данные обязаны остаться в W2.
|
||||||
|
# Проверять это надо по объектным файлам (кто Def, кто Ref), а
|
||||||
|
# не на глаз.
|
||||||
bank_data_flags=()
|
bank_data_flags=()
|
||||||
[[ $BANK_LOCAL_DATA -eq 1 ]] && bank_data_flags=(--dataseg "BANK${bank_n}")
|
bank_data_want=$BANK_LOCAL_DATA
|
||||||
|
for bd in "${BANK_DATA_SRCS[@]}"; do
|
||||||
|
[[ "$(basename "$bd")" == "$(basename "$bank_src")" ]] && bank_data_want=1
|
||||||
|
done
|
||||||
|
[[ $bank_data_want -eq 1 ]] && bank_data_flags=(--dataseg "BANK${bank_n}")
|
||||||
run "$SDCC" "${CC_FLAGS[@]}" \
|
run "$SDCC" "${CC_FLAGS[@]}" \
|
||||||
--codeseg "BANK${bank_n}" --constseg "BANK${bank_n}" \
|
--codeseg "BANK${bank_n}" --constseg "BANK${bank_n}" \
|
||||||
"${bank_data_flags[@]}" \
|
"${bank_data_flags[@]}" \
|
||||||
@@ -494,6 +509,16 @@ MK_PREFIX=()
|
|||||||
[[ $VERBOSE -eq 1 ]] && MK_PREFIX+=(-v)
|
[[ $VERBOSE -eq 1 ]] && MK_PREFIX+=(-v)
|
||||||
[[ $BANK_W1 -eq 1 ]] && MK_PREFIX+=(-B 0x4000)
|
[[ $BANK_W1 -eq 1 ]] && MK_PREFIX+=(-B 0x4000)
|
||||||
[[ $W3_RESIDENT_HUGE -eq 1 ]] && MK_PREFIX+=(-W)
|
[[ $W3_RESIDENT_HUGE -eq 1 ]] && MK_PREFIX+=(-W)
|
||||||
|
# Данные банка в его же странице обязаны стартовать НУЛЯМИ: crt0 зануляет
|
||||||
|
# только резидентный _DATA, до банковых страниц он не дотягивается. Даром
|
||||||
|
# это даёт паддинг образа нулём вместо дефолтного 0xFF — иначе вся банковая
|
||||||
|
# статика поднимется с 0xFF, а по правилам проекта её никто не инициализирует
|
||||||
|
# явно (crt0 зануляет _DATA, см. memory/sdcc_static_storage_gotcha).
|
||||||
|
if [[ $BANK_LOCAL_DATA -eq 1 || ${#BANK_DATA_SRCS[@]} -gt 0 ]]; then
|
||||||
|
mk_has_pad=0
|
||||||
|
for f in "${MKEXE_EXTRA[@]}"; do [[ "$f" == "-p" ]] && mk_has_pad=1; done
|
||||||
|
[[ $mk_has_pad -eq 0 ]] && MK_PREFIX+=(-p 0)
|
||||||
|
fi
|
||||||
MK_PREFIX+=("${MKEXE_EXTRA[@]}")
|
MK_PREFIX+=("${MKEXE_EXTRA[@]}")
|
||||||
MK_PREFIX+=(-L "$LOAD_ADDR" -E "$ENTRY_ADDR" -S "$STACK_ADDR" -o "$OUT")
|
MK_PREFIX+=(-L "$LOAD_ADDR" -E "$ENTRY_ADDR" -S "$STACK_ADDR" -o "$OUT")
|
||||||
run "$MKEXE" "${MK_PREFIX[@]}" "$IHX"
|
run "$MKEXE" "${MK_PREFIX[@]}" "$IHX"
|
||||||
|
|||||||
Reference in New Issue
Block a user