diff --git a/applications/PoP/docs/resident_budget.md b/applications/PoP/docs/resident_budget.md deleted file mode 100644 index 3422b92..0000000 --- a/applications/PoP/docs/resident_budget.md +++ /dev/null @@ -1,111 +0,0 @@ -# Бюджет резидента 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 Б) в другой банк. diff --git a/applications/PoP/roomtest/Makefile b/applications/PoP/roomtest/Makefile index 6d74a0e..184b93c 100644 --- a/applications/PoP/roomtest/Makefile +++ b/applications/PoP/roomtest/Makefile @@ -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 выключает. # Каждая фаза кадра красит бордюр в свой цвет, высота полосы на скриншоте diff --git a/bin/sprinter-cc b/bin/sprinter-cc index a2d4f38..b3b84f7 100755 --- a/bin/sprinter-cc +++ b/bin/sprinter-cc @@ -54,10 +54,6 @@ # no writable data belongs in W3 (code + rodata only). From a # __banked context the resident W3 code is unreachable (but # resident -> __banked via the W1 trampoline is fine). -# --bank-data=SRC писучие данные ТОЛЬКО этого банкового модуля — в его -# страницу (repeatable). Безопасно лишь если ни один -# другой модуль не ссылается на его глобалы: проверяется -# по .rel (Def/Ref). Включает mkexe -p 0 (zero-init). # --mkexe FLAG extra mkexe flag (repeatable; e.g. --mkexe -p --mkexe 0) # --max-allocs N SDCC --max-allocs-per-node (default: 100000 — smaller/ # faster code at the cost of compile time; pass a lower @@ -115,8 +111,7 @@ MKEXE_EXTRA=() BANK_SPECS=() # entries like "1=engine.c" W3_SPECS=() # entries like "mod.c" — резидентные модули окна W3 (--w3) USER_DEFS=() # -DFOO / -DFOO=bar — пробрасываются в sdcc как есть -BANK_LOCAL_DATA=0 # 1 (--bank-data): писучие данные ВСЕХ банков — в страницу банка -BANK_DATA_SRCS=() # --bank-data=src.c: только эти модули (выборочно) +BANK_LOCAL_DATA=0 # 1 (--bank-data): писучие данные банков — В СТРАНИЦУ банка W3_RELS=() # заполняется при компиляции W3-модулей W3_LD_FLAGS=() # -Wl-b_W3CODE=0xC000, если есть --w3 USER_SET_MEMORY="" # непусто, если --memory задан явно (для --w3 авто-small) @@ -153,7 +148,6 @@ while [[ $# -gt 0 ]]; do -D*) USER_DEFS+=("$1"); shift;; --bank) BANK_SPECS+=("$2"); shift 2;; --bank-data) BANK_LOCAL_DATA=1; shift;; - --bank-data=*) BANK_DATA_SRCS+=("${1#*=}"); shift;; --mkexe) MKEXE_EXTRA+=("$2"); shift 2;; --max-allocs) MAX_ALLOCS="$2"; shift 2;; --gfx) GFX_MODE="$2"; shift 2;; @@ -442,17 +436,8 @@ if [[ "$CRT0_TYPE" == "banked" ]]; then # глобал банкового модуля лёг по 0x0001C000): это экономит # W1/W2, но требует ручного контроля видимости, поэтому # включается явно через --bank-data. - # Выборочный режим (--bank-data=src.c) точнее глобального: у - # большинства банковых модулей часть глобалов ЧИТАЮТ соседние - # банки и резидент, и такие данные обязаны остаться в W2. - # Проверять это надо по объектным файлам (кто Def, кто Ref), а - # не на глаз. bank_data_flags=() - bank_data_want=$BANK_LOCAL_DATA - for bd in "${BANK_DATA_SRCS[@]}"; do - [[ "$(basename "$bd")" == "$(basename "$bank_src")" ]] && bank_data_want=1 - done - [[ $bank_data_want -eq 1 ]] && bank_data_flags=(--dataseg "BANK${bank_n}") + [[ $BANK_LOCAL_DATA -eq 1 ]] && bank_data_flags=(--dataseg "BANK${bank_n}") run "$SDCC" "${CC_FLAGS[@]}" \ --codeseg "BANK${bank_n}" --constseg "BANK${bank_n}" \ "${bank_data_flags[@]}" \ @@ -509,16 +494,6 @@ MK_PREFIX=() [[ $VERBOSE -eq 1 ]] && MK_PREFIX+=(-v) [[ $BANK_W1 -eq 1 ]] && MK_PREFIX+=(-B 0x4000) [[ $W3_RESIDENT_HUGE -eq 1 ]] && MK_PREFIX+=(-W) -# Данные банка в его же странице обязаны стартовать НУЛЯМИ: crt0 зануляет -# только резидентный _DATA, до банковых страниц он не дотягивается. Даром -# это даёт паддинг образа нулём вместо дефолтного 0xFF — иначе вся банковая -# статика поднимется с 0xFF, а по правилам проекта её никто не инициализирует -# явно (crt0 зануляет _DATA, см. memory/sdcc_static_storage_gotcha). -if [[ $BANK_LOCAL_DATA -eq 1 || ${#BANK_DATA_SRCS[@]} -gt 0 ]]; then - mk_has_pad=0 - for f in "${MKEXE_EXTRA[@]}"; do [[ "$f" == "-p" ]] && mk_has_pad=1; done - [[ $mk_has_pad -eq 0 ]] && MK_PREFIX+=(-p 0) -fi MK_PREFIX+=("${MKEXE_EXTRA[@]}") MK_PREFIX+=(-L "$LOAD_ADDR" -E "$ENTRY_ADDR" -S "$STACK_ADDR" -o "$OUT") run "$MKEXE" "${MK_PREFIX[@]}" "$IHX"