# Бюджет резидента W1/W2: как мерить и как освобождать Резидент huge-режима — окно `0x4100..0xBB00` (стек с 0xBB00): код в W1, данные в W2, между концом данных и стеком остаётся куча. Всё, что туда не влезло, живёт в банках. ## Как СМОТРЕТЬ, а не гадать **Карта линкера врёт.** File-static SDCC в неё не попадает, и «дырка» между двумя именованными символами приписывается предыдущему целиком. По карте выходило, что у `pop_bg` 1529 Б данных (на деле 289), а у `pop_t_win_clear` 1282 Б кода — при том, что это однострочник, а 1282 Б это два статических помощника соседнего `pop_blit_b`. Точный источник — объектные файлы: строки `A size flags ` в `.rel` дают ровный размер каждой области модуля, а `S Def/Ref` — кто символ определяет и кто на него ссылается. **Частоту вызовов мерить в MAME счётчиком**, а не оценивать по смыслу: ``` bpset ,1,{printf "F %d ...",temp0,...; temp0=0;...; g} bpset ,1,{temp0=temp0+1; g} ``` Обязательна **канарейка** — счётчик заведомо горячей функции в том же прогоне. Дважды спасала: один раз показала, что перехода комнаты в окне замера не было (все нули), другой — что зонды вообще не встали (в zsh `set -- $pair` НЕ разбивает строку на слова, и адрес уезжал в мусор). Полную перерисовку комнаты форсировать читом `+`/`-`, ходьбой ненадёжно. ## Сделано ### 1. malloc вон из резидента (−613 Б) `cbl_open` держал `malloc`/`free` в мёртвой ветке `CBL_UNDERRUN_SILENCE`, а линкер тянет `.rel` целиком — и куча приезжала каждому приложению. Разведены две публичные точки входа (`cbl_open` / `cbl_open_silence`) поверх общего `_cbl_open_raw`; `cbl_close` больше не зовёт `free`. ### 2. Разрез pop_tile: холодная половина в банк 5 (−1788 Б) `pop_tile.c` был крупнейшим жильцом резидента (5 972 Б кода). Целиком он не уедет: его const-таблицы (`POP_TILE_DIV/MOD`, `pop_tile_table`, таблицы кадров) читают банки 2, 3, 7 и 8, а таблица в чужом банке не видна. Отбирали ЗАМЕРОМ, на двух тайлсетах (подземелье ур. 1 и дворец ур. 4 — `pop_mem_b` рисует композитный кусок и мог оказаться дворцовым). Порог — пик не больше 3 вызовов на кадр. | уехало в банк 5 | пик/кадр | | осталось в резиденте | пик/кадр | |---|---:|---|---|---:| | `pop_mem_b` | 0 | | `pop_tile_code` | 296 | | `pop_cd_hit` (+`hit_rect`) | 0 | | `pop_cd_touch` | 198 | | `pop_t_win_set/clear` | 0..1 | | `pop_blit_b` (+2 статика) | 184 | | `pop_heal_off` | 0..1 | | `pop_wall_modifier` | 101 | | `pop_potion_flask` | 0..1 | | `pop_env_b` | 73 | | `pop_room_set_above/below` | 1 | | `pop_tile_mod` | 70 | | `pop_cd_init/clear` | 1 | | `pop_cd_batch_end` | 40 | | `pop_bar_black` | 3 | | `pop_fore_set_clip` | 2 | | `pop_cd_hit_slot` | 2..3 | | все const-таблицы | — | `pop_fore_set_clip` (88 Б) оставлен намеренно: не стоит отказа от прямого вызова из банка 4, ради которого он и заводился. **Цена трамплина замерена**: 252 такта пролог + 84 эпилог + ~50 на стороне вызывающего = **~410 тактов** на вызов. Итого ~1 000 тактов на кадр покоя (0,2 % работы) и ~3 700 на кадр редрава (0,009 растра). **Ключ, почему это безопасно:** вызов банк → резидент ПРЯМОЙ, трамплин не нужен (W1/W2 замаплены всегда). Поэтому `blit_b_clip` просто перестал быть `static` и объявлен в `_pop_tile.h`, а не переехал следом за `pop_mem_b`. ### Итог | | было | стало | |---|---:|---:| | `_CODE` резидента | 24 329 | **21 928** | | свободно до стека | **129 Б** | **2 535 Б** | | BANK5 | 2 080 (13 %) | 3 954 (24 %) | Проверено в MAME: уровень 1 (подземелье) и уровень 4 (дворец), переходы комнат читом `+`, ходьба — фон, факелы, решётки, гобелены, колонны без искажений. ## ЛОВУШКА: данные банка в его страницу — НЕ ДЕЛАТЬ без разбора Отдельная попытка (`--bank-data=SRC`, коммиты 3545826/9025573) **откачена**: перенос писучих данных банкового модуля в его 16-КБ страницу давал цветной мусор блоками и ронял DSS. У `pop_trob` причина найдена: `pop_trob_modif()` ВОЗВРАЩАЕТ УКАЗАТЕЛЬ на `room_modif[24][30]`, а зовут её из банков 2, 3, 7 и резидента — после переноса они пишут по 0xC000+ в СВОЮ страницу, поверх чужого кода. Def/Ref-анализ такого не видит: снаружи ссылки на символ нет, есть ссылка на функцию, отдающую его адрес. Но и `pop_room`, у которого утечки указателя найти не удалось, ломался так же — механизм понят не до конца. Нулевая инициализация при этом ни при чём: `mkexe -p 0` был проверен по образу (прогон нулей 14 304 Б, самый длинный прогон 0xFF — 14). **Перенос КОДА в банк — штатный путь, на нём стоят все наши банки. Ломался именно перенос ДАННЫХ.** ## Что осталось - `sprpop.c` 2 508 Б и `pop_kid.c` 2 418 Б — следующие по величине, но оба горячие (главный цикл и `play_seq`). - `pop_level.c` 956 Б кода + 660 Б данных (из них `pop_dl1`/`pop_dl2` по 256 Б — таблицы дверных связей). - BANK7 на 77 %: если понадобится место в нём — выносить `pop_redraw.c`.