Откат оптимизации резидента: перенос данных банка в его страницу ломает картинку
Возврат к состоянию после звуковых правок (c6828c0). Отменяются3545826и9025573целиком: --bank-data=SRC в sprinter-cc, его включение в PoP и docs/resident_budget.md. Что выяснено и почему откат, а не доводка. Перенос писучих данных банкового модуля в его 16-КБ страницу даёт цветной мусор блоками и уводит DSS. У pop_trob причина найдена: pop_trob_modif() возвращает указатель на room_modif[24][30], и его разыменовывают банки 2/3/7 и резидент — то есть пишут по 0xC000+ в СВОЮ страницу, поверх чужого кода. Def/Ref-анализ такое не ловит: снаружи ссылки на символ нет, есть ссылка на функцию, отдающую его адрес. Но и один pop_room, у которого явной утечки указателя найти не удалось, ломается так же — значит механизм понят не до конца. Пока не понят, включать нельзя. Нулевая инициализация при этом ни при чём: mkexe -p 0 проверен по образу (прогон нулей 14304 Б, самый длинный прогон 0xFF — 14). Место в резиденте искать другими путями: malloc (287 Б, требует раздельных cbl_open для APP и SILENCE) и код pop_tile (5972 Б). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,111 +0,0 @@
|
||||
# Бюджет резидента 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 Б) в другой банк.
|
||||
@@ -35,23 +35,7 @@ MEMORY ?= huge # small-раскладка + банки кода в W
|
||||
# ВАЖНО: любое сравнение занятости банков имеет смысл только при ОДНОМ и том
|
||||
# же ALLOCS — иначе сравниваются не правки, а уровни оптимизации.
|
||||
ALLOCS ?= 3000
|
||||
# Писучие данные ЭТОГО банкового модуля живут в его же странице, а не в
|
||||
# резиденте W1/W2 (−1620 Б базы). Список из одного файла — не лень, а
|
||||
# результат проверки; ДВА условия, и второе стоило перезагрузки DSS:
|
||||
#
|
||||
# 1) ни один чужой модуль не ссылается на его глобалы (проверяется по
|
||||
# .rel: символ Def только в своём объекте и нигде не Ref);
|
||||
# 2) НИ ОДИН УКАЗАТЕЛЬ на его статику не уходит наружу. Def/Ref этого не
|
||||
# видит. Так провалился pop_trob: pop_trob_modif() возвращает указатель
|
||||
# на room_modif[24][30], и его разыменовывают банки 2/3/7 и резидент —
|
||||
# то есть пишут по 0xC000+ в СВОЮ страницу, поверх чужого кода.
|
||||
#
|
||||
# У pop_room оба условия выполнены: _mobs не читает никто, bake_copy
|
||||
# статическая, а atlas_load(&pop_env[i]) берёт адрес глобала из pop_tile.c
|
||||
# (резидент), не своего. Подробности — ../docs/resident_budget.md.
|
||||
BANK_DATA := --bank-data=pop_room.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)
|
||||
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)
|
||||
# Профилирование полосами бордюра (см. PROF() в roomtest.c) — ВКЛЮЧЕНО по
|
||||
# умолчанию, пока идёт работа с производительностью; make PROF=0 выключает.
|
||||
# Каждая фаза кадра красит бордюр в свой цвет, высота полосы на скриншоте
|
||||
|
||||
Reference in New Issue
Block a user