Files
Sprinter-SDCC/applications/PoP/docs/resident_budget.md
T
snark13 90255737c2 Откат bank-data для pop_trob: указатель на его статику уходил наружу
Симптом: через несколько комнат живого прохода перезагружался DSS.

pop_trob_modif() возвращает указатель на room_modif[24][30], а зовут её из
банков 2, 3, 7 и резидента.  После переноса массив лежит по 0xC000+ в
странице банка 6, но разыменовывает указатель ЧУЖОЙ код — когда замаплена
его собственная страница.  Значит чтение и запись идут поверх кода соседнего
банка.

Анализ Def/Ref такое не ловит: снаружи нет ссылки на символ, есть ссылка на
функцию, которая отдаёт его адрес.  Условий для кандидата два, и второе
проверяется только чтением кода — ни один указатель на статику не должен
уходить наружу.

pop_room оба условия проходит (_mobs не читает никто; bake_copy статическая;
atlas_load(&pop_env[i]) берёт адрес глобала из резидентного pop_tile.c).
Остаётся −1620 Б: данные в W2 6774 -> 5157, свободно 129 -> 1746 Б.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 21:21:44 +03:00

112 lines
7.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Бюджет резидента 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`. Результат:
| | было | стало |
|---|---:|---:|
| данные в 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 Б) в другой банк.