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>
5.5 KiB
Бюджет резидента W1/W2 и как его освобождать
Резидент huge-режима — это окно 0x4100..0xBB00 (стек с 0xBB00): код в W1,
данные в W2, между концом данных и стеком остаётся куча. Всё, что туда не
влезло, обязано жить в банках.
Как СМОТРЕТЬ, а не гадать
Атрибуция по карте (roomtest.map) ВРЁТ: file-static SDCC в карту не
попадает, и «дырка» между двумя именованными символами приписывается
предыдущему модулю целиком. На этом легко получить «у pop_bg 1529 байт
данных», когда на самом деле у него 289.
Правильный источник — объектные файлы: в .rel есть строки
A <area> size <n> flags <f> с ТОЧНЫМ размером каждой области модуля.
# по каждому .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 — фон, факелы, решётки, проваливающиеся полы и переход между комнатами работают.
Два условия, без которых это молча ломается
- Нулевая инициализация. crt0 зануляет только резидентный
_DATA, до банковых страниц он не дотягивается, аmkexeпо умолчанию забивает пустоты образа0xFF. По правилам проекта file-scope переменные никто не инициализирует явно (crt0 же зануляет) — значит вся банковая статика поднялась бы с0xFF. Поэтомуsprinter-ccпри любом--bank-dataСАМ добавляетmkexe -p 0. _INITIALIZEDостаётся в резиденте.--datasegпереименовывает только_DATA; инициализированные переменные и их_INITIALIZERживут как раньше, и gsinit копирует их без участия банков. Уpop_roomтаких 3 байта — они и остались в W2.
Что осталось на столе
malloc— 287 Б резидента, который никто не зовёт: его тянетcbl_open, гдеmalloc/freeстоят в мёртвой веткеCBL_UNDERRUN_SILENCE. Вынести аллокацию буфера тишины в отдельный модуль libc — и линкер выбросит malloc у всех приложений наCBL_UNDERRUN_APP.pop_bg289 Б — уедут, если вынестиpop_bg_obj_col/row(2 байта!) в общий резидентный модуль состояния.pop_tile— 5972 Б кода в резиденте, самый крупный жилец. Он там потому, что его листья зовут обе половины слоя фона из РАЗНЫХ банков. Резать имеет смысл только по этому признаку: что зовут из одного банка — туда и переносить.- BANK7 после правки на 87 %: следующий кандидат на разгрузку —
pop_redraw.c(981 Б) в другой банк.