# Бюджет резидента W1/W2 и как его освобождать Резидент huge-режима — это окно `0x4100..0xBB00` (стек с 0xBB00): код в W1, данные в W2, между концом данных и стеком остаётся куча. Всё, что туда не влезло, обязано жить в банках. ## Как СМОТРЕТЬ, а не гадать Атрибуция по карте (`roomtest.map`) ВРЁТ: file-static SDCC в карту не попадает, и «дырка» между двумя именованными символами приписывается предыдущему модулю целиком. На этом легко получить «у pop_bg 1529 байт данных», когда на самом деле у него 289. Правильный источник — объектные файлы: в `.rel` есть строки `A size flags ` с ТОЧНЫМ размером каждой области модуля. ```python # по каждому .rel: _DATA/_BSS/_INITIALIZED = резидентные данные модуля, # _BANKn = его код в банке re.match(r'A\s+(\S+)\s+size\s+([0-9A-Fa-f]+)\s+flags', line) ``` Кто на кого ссылается — оттуда же: `S Def...` — модуль определяет символ, `S 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`. Результат: | | было | стало | |---|---:|---:| | данные в W2 | 6774 | **5157** | | свободно до стека | 129 | **1746** | | BANK7 | 12684 | 14301 (87 %) | **Проверено в MAME**: уровень 1, комнаты 1 и 2 — фон, факелы, решётки, проваливающиеся полы и переход между комнатами работают. ## ГЛАВНАЯ ЛОВУШКА: указатель на банковую статику Первый заход включал ещё и `pop_trob.c` — по Def/Ref он выглядел ИДЕАЛЬНЫМ кандидатом (экспортируемых данных нет вообще). Через несколько комнат живого прохода **перезагрузился DSS**. Причина: `pop_trob_modif()` ВОЗВРАЩАЕТ УКАЗАТЕЛЬ на `room_modif[24][30]`, а зовут её из банков 2, 3, 7 и резидента. После переноса массив лежит по 0xC000+ в странице банка 6, но разыменовывает указатель чужой код — когда замаплена ЕГО страница. То есть чтение и запись идут по тем же смещениям поверх кода соседнего банка. Отсюда и уход системы. **Анализ Def/Ref этого не видит**: снаружи нет ссылки на символ, есть ссылка на функцию, которая отдаёт его адрес. Поэтому условий для кандидата ДВА, и второе проверяется только чтением кода: 1. ни один чужой модуль не Ref его глобалы (по `.rel`); 2. **ни один указатель на его статику не уходит наружу** — ни возвратом, ни через out-параметр, ни записью в общий глобал. У `pop_room` второе выполнено: единственные `&`-выражения наружу — `atlas_load(&pop_env[i])`, но `pop_env` определён в `pop_tile.c`, то есть в резиденте; `bake_copy` статическая. Внутри своей страницы указатель безопасен всегда: любой вызов резидентной функции, которая перемапливает W3 (`atlas_load`, `pop_page_read`), восстанавливает окно перед возвратом. ### Два условия, без которых это молча ломается 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 Б) в другой банк.