Откат 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>
This commit is contained in:
2026-08-20 21:21:44 +03:00
parent 354582662c
commit 90255737c2
2 changed files with 46 additions and 13 deletions
+31 -5
View File
@@ -42,19 +42,45 @@ re.match(r'A\s+(\S+)\s+size\s+([0-9A-Fa-f]+)\s+flags', line)
глобал, который читает соседний банк или резидент, обязан остаться глобал, который читает соседний банк или резидент, обязан остаться
замапленным всегда, иначе чтение придёт из чужой страницы. замапленным всегда, иначе чтение придёт из чужой страницы.
Включено для `pop_room.c` и `pop_trob.c` — единственных модулей, у которых Включено для `pop_room.c`. Результат:
НИ ОДИН символ не Ref снаружи. Результат:
| | было | стало | | | было | стало |
|---|---:|---:| |---|---:|---:|
| данные в W2 | 6774 | **4228** | | данные в W2 | 6774 | **5157** |
| свободно до стека | 129 | **2675** | | свободно до стека | 129 | **1746** |
| BANK6 | 4199 | 5128 (31 %) |
| BANK7 | 12684 | 14301 (87 %) | | BANK7 | 12684 | 14301 (87 %) |
**Проверено в MAME**: уровень 1, комнаты 1 и 2 — фон, факелы, решётки, **Проверено в 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`, до 1. **Нулевая инициализация.** crt0 зануляет только резидентный `_DATA`, до
+15 -8
View File
@@ -35,14 +35,21 @@ MEMORY ?= huge # small-раскладка + банки кода в W
# ВАЖНО: любое сравнение занятости банков имеет смысл только при ОДНОМ и том # ВАЖНО: любое сравнение занятости банков имеет смысл только при ОДНОМ и том
# же ALLOCS — иначе сравниваются не правки, а уровни оптимизации. # же ALLOCS — иначе сравниваются не правки, а уровни оптимизации.
ALLOCS ?= 3000 ALLOCS ?= 3000
# Писучие данные ЭТИХ банковых модулей живут в их же странице, а не в # Писучие данные ЭТОГО банкового модуля живут в его же странице, а не в
# резиденте W1/W2 — это 2549 Б, которые иначе съедала бы база. Список # резиденте W1/W2 (1620 Б базы). Список из одного файла — не лень, а
# короткий не по лени: у остальных банковых модулей часть глобалов читают # результат проверки; ДВА условия, и второе стоило перезагрузки DSS:
# соседние банки и резидент (hitp_*, pop_upside, pop_loose_modif, pop_cd), #
# и такие данные ОБЯЗАНЫ остаться замапленными всегда. Проверять новичка в # 1) ни один чужой модуль не ссылается на его глобалы (проверяется по
# списке — по объектным файлам: символ должен быть Def только в своём .rel и # .rel: символ Def только в своём объекте и нигде не Ref);
# нигде не Ref (см. ../docs/resident_budget.md). # 2) НИ ОДИН УКАЗАТЕЛЬ на его статику не уходит наружу. Def/Ref этого не
BANK_DATA := --bank-data=pop_room.c --bank-data=pop_trob.c # видит. Так провалился 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 $(BANK_DATA) $(PROF_FLAGS)
# Профилирование полосами бордюра (см. PROF() в roomtest.c) — ВКЛЮЧЕНО по # Профилирование полосами бордюра (см. PROF() в roomtest.c) — ВКЛЮЧЕНО по