libc/irq: цепочка кадровых обработчиков + all-modes W1-remap

_irq_user → _irq_chain[4]+_irq_chain_n (W2-BSS); трамплин tr_frame
проходит слоты (один тяжёлый сейв на всю цепь, пустой слот пропуск).
API irq_chain_add (0/-1+ENOMEM) / irq_chain_remove(h); irq_install →
обёртка chain_add (EBUSY исчез), irq_remove() рвёт всю цепь; refcount
таблицы на первом/последнем слоте, мутации под IRQ_DISABLE. Лимит
4 кадровых + 1 CTC.

All-modes: трамплин copy-safe (только jr/djnz + литерал jp 0x0038),
_irq_table_ref копирует его в _irq_tramp_w2buf (W2) когда оригинал в W1
(small/huge), вектор → на копию; вокруг вызова хендлеров восстанавливает
базовую W1-страницу (_irq_app_w1_page = IN 0xA2). Хендлер может лежать
где угодно в плоском 0x4000-0xBFFF.

Проверено в MAME (tests/irqtest, 2 хендлера): tiny/big/huge — chain
h1=h2 → remove h2 → h1 жив/h2=0; huge = код в W1, remap работает.
Follow-up: CTC в small/huge = EINVAL (нужна W2-копия _irq_ctc_tramp);
small для мелких программ (BSS в W1) = irq_install EINVAL, safe.

Доки: im2_isr_design (цепочка), sprite-api §9е (делитель поверх цепи),
fast_ram §8. rpgprof: gfx_sprite_ysort в профиль (+230 Б baseline).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-15 10:23:39 +03:00
parent 6b6986d9b6
commit 2c6f4e33c3
17 changed files with 700 additions and 128 deletions
+94
View File
@@ -193,3 +193,97 @@ _intWaitVsyncSys
Если будем добавлять поддержку — делать это отдельным opt-in механизмом
(по аналогии с banked-режимами), с обязательной обёрткой off/restore вокруг всех
точек входа в систему.
---
## 8. Разбор двух предложений по спрайтовому движку (обсуждение 2026-07-14)
Итог двух обсуждений (эмуляция FastRAM в dev-MAME подтверждена
разработчиками MAME как полная).
### 8.1. Предложение «W0/FastRAM как код-банк `__banked`»
Идея: выделить 1-2 FastRAM-страницы в crt0, научиться размещать в них
`__banked`-код с целью W0 и контрактом «такой код не зовёт DSS/BIOS»;
критичным функциям указывать размещение в W0/FastRAM.
**Framing правильный** (переиспользовать трамплин `__banked` +
контракт), но W0-банк — НЕ «ещё один bank id», а тяжелее по трём осям:
1. **Своп W0 сносит всё окно 0 с RST-векторами** (ESTEX RST10, BIOS
RST8, IM1 RST38, RST0). Пока FastRAM в W0: сисколлы невозможны (это
и есть контракт — ок), но прерывание фатально (`#0038` → мусор
FastRAM) → обязателен `DI` на весь интервал. `DI` бьёт ISR-счётчик
кадров (см. FPS-делитель, [[sprite-engine-perf]]).
2. **Загрузку нельзя сделать как в `crt0_banked`** (там `ESTEX READ`
прямо в окно, строки 165-173): сам READ вектрится через W0. Нужен
2-шаг — READ в DRAM-буфер, затем `DI` + FastRAM в W0 + **`LD`-копия**
(акселератор с FastRAM не работает!) + выкл. Плюс FastRAM затирается
запуском дочернего DSS-процесса — нужна перезагрузка.
3. **16 КБ окно против 64 КБ**: кросс-страничные вызовы внутри FastRAM
тянут вложенный W0-своп. Вызовы в W1/W2/W3 — свободны (те окна
замаплены).
Плюс **пер-вызовная такса трамплина** (~40-60Т: DI/toggle/EI/restore)
съедает выигрыш именно на горячих inner-листьях, которые от FastRAM
выигрывают больше всего. Per-function `__banked` — неправильная
гранулярность.
**Выгода** мала: FastRAM ускоряет только выборку инструкций. Из бюджета
спрайта 19.5К (blit 9.7 + heal 6.4 + тик 3.0) blit и heal —
акселератор/видео-ОЗУ, не выигрывают; выигрывает только тик+сорт по
fetch: ~15-20К/кадр ≈ +1 спрайт (3-4% бюджета в самом узком месте).
**Рекомендация:** если вообще трогать — модель «горячий остров», а не
«функция-в-банке»: одна 16-КБ FastRAM-страница с самодостаточным
кластером (тик + сортировщик + их листья), вход один раз за кадр через
единственную обёртку `DI / своп-в / вызов / своп-обратно / EI`, внутри
острова обычные `call`, никаких RST. Убирает таксу, сводит связку с
прерываниями к одному DI-интервалу на кадр (мы и так HALT-ждём между
кадрами; ISR-счётчик кадров тикает во время HALT-ожидания ПОСЛЕ EI, а
не во время острова → совместимо с FPS-делителем, пока остров < 1
кадра).
### 8.2. Данные в FastRAM вместо кода?
Вывод неожиданный: **для нашей нагрузки данные — более СЛАБЫЙ рычаг,
чем код.** Wait-state вставляется на КАЖДЫЙ доступ к DRAM, а на Z80
большинство обращений к памяти — выборка инструкций, не операндов. Тик
= IX-индексный код (`ld a,(ix+d)` — DD-префикс: 3 байта fetch + 1 байт
данных) → ¾ штрафа на fetch, ¼ на операнде. Значит `sprite_t` в FastRAM
убирает лишь ¼, а код тика — ¾.
По данным движка:
- **Пиксели/атласы — жёсткое НЕТ** (их читает акселератор; с FastRAM не
работает → блит откатился бы на LD-циклы).
- **Массив `sprite_t` — не стоит:** тик его трогает, но выигрыш мал (см.
выше), а хуже того — его читают и heal/blit под акселератор, которым
нужна нормальная карта памяти. `sprite_t` в W0-FastRAM → весь
`sprite_update` под `DI` + непроверенный вопрос «работает ли
акселератор при W0=FastRAM». Не кандидат, пока это не подтверждено
артефактом в MAME.
- **Таблица Y-сорта + чисто-CPU скретч — да, но не отдельное решение:**
живут в той же 16-КБ странице «горячего острова» с кодом сортировщика
(сорт не трогает ни акселератор, ни сисколлы). Отдельного рычага
«данные» тут нет.
**Единственный сильный независимый кейс для данных-в-FastRAM:**
CPU-bound алгоритм с большим LUT случайного доступа (fetch внутреннего
цикла крошечный, чтений таблицы — тьма). У движка такого нет (тяжёлая
графика = акселератор). Но если добавим CPU-side эффект (программный
per-pixel шейдинг, палитровый remap, софт-скейлер) — его LUT станет
главным кандидатом на FastRAM-данные. На будущее.
**Ограничения именно для FastRAM-данных:** лежат в окне 0
(#0000-#3FFF) → буфер под BIOS/ESTEX туда нельзя (нужно #4000-#BFFF);
стек остаётся в W2; не переживают дочерний DSS-процесс; заполняются
только `LD`-копией (акселератор мимо).
### 8.3. Общий вердикт
Оба варианта реализуемы, но бьют по 3-4% бюджета в самом узком месте
(тик+сорт fetch), мимо доминирующих blit/heal. Остаётся **последним
резервом** ([[fast_ram]] в памяти). Перед любой реализацией — дешёвый
бенч в dev-MAME: один и тот же CPU-loop в DRAM vs FastRAM по
`totalcycles`, чтобы измерить реальный коэффициент wait-state, который
эмулятор моделирует, и подтвердить, что овчинка стоит выделки.