mdview2: ускорение препроцессинга — asm fb() + инлайн рендер-цикла

Профилирование (52КБ README, раздельный замер): препроцессинг 24с, из них
bank I/O ~0с, cont-walk ~1с — всё время в посимвольных скан-циклах
(индексация 8с + рендер 13с, два прохода по файлу).

- fb() переписана на ассемблере (была закомментированная заготовка): убран
  вызов функции и 32-битная арифметика на каждый байт. Раскладка __sdcccall(1)
  для uint32 аргумента (p=HLDE) и возврат char в A сверены через sdcc -S;
  координация маппинга с bank_read держится на том, что _io_page_w3 — порт
  (__sfr 0xE2), пишем OUT — bank_read читает IN.
- render_line_to_cache: горячий путь обычного текста больше не вызывает
  handle_inline_marker (6 аргументов) на каждый символ — только на маркерах
  ` * _ ~; cc_put заинлайнен. Поведение идентично.

Итого 24→18с. Индекс-скан (8с) лёгкого инлайна не имеет (нет per-char
вызовов). Следующий шаг — merge двух проходов в один.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-24 11:23:21 +03:00
parent 138e783e72
commit 2f4eaf5b25
+46 -38
View File
@@ -350,60 +350,61 @@ static inline void map_page(uint8_t page)
/* Читает один байт логического файла по смещению p.
* При переходе между страницами автоматически переключает W3.
* Проверка границ выполняется вызывающей стороной.
*/
static char fb(uint32_t p)
*
* Эквивалент C-кода (оставлен для справки):
* uint8_t *pb = (uint8_t *)&p;
* uint8_t page = (pb[1] >> 6) | (pb[2] << 2);
* uint16_t off = ((pb[1] & 0x3F) << 8) | pb[0];
* map_page(page); // if (page!=cur_page){cur_page=page; OUT W3,file_phys[page];}
* return FILE_BUF[off]; // *(0xC000 + off)
*
* Ручной asm — это горячая примитивная операция (вызывается сотни тысяч раз
* при индексации 50КБ-файла); C-версия тратила вызов функции + 32-битную
* арифметику на КАЖДЫЙ байт. Раскладка __sdcccall(1): аргумент uint32_t в
* HLDE (E=pb[0], D=pb[1], L=pb[2], H=pb[3] — проверено sdcc -S), возврат
* char в A. Координация маппинга с bank_read/bank_write держится на том,
* что _io_page_w3 — это ПОРТ (__sfr 0xE2): здесь пишем его через OUT,
* bank_read читает через IN — общий аппаратный регистр, без теневой RAM.
* NB: __naked обязателен (внутри собственный ret); IX/IY не трогаем
* (callee-saved), BC/DE/HL/AF — caller-saved, можно клобберить. */
static char fb(uint32_t p) __naked
{
uint8_t *pb = (uint8_t *)&p;
uint8_t page = (uint8_t)((pb[1] >> 6) | (pb[2] << 2));
uint16_t off = (uint16_t)(((uint16_t)(pb[1] & 0x3F) << 8) | pb[0]);
map_page(page);
return FILE_BUF[off];
/*
// p = HLDE
(void)p;
(void)p; /* p = HLDE; доступ только из asm */
__asm
; uint16_t off = (uint16_t)(((uint16_t)(pb[1] | 0xC0) << 8) | pb[0]);
; off -> BC = 0xC000 + ((pb[1]&0x3F)<<8 | pb[0]) ; D=pb[1], E=pb[0]
ld a, d
or a, #0xC0
or a, #0xC0 ; (D|0xC0) == (D&0x3F)|0xC0 старший байт окна W3
ld b, a
ld c, e
ld c, e ; C = pb[0]
; uint8_t page = (uint8_t)((pb[1] >> 6) | (pb[2] << 2));
ld a, l ;; pb[2] << 2
; page = (pb[1]>>6) | (pb[2]<<2) ; L=pb[2]
ld a, l
add a, a
add a, a
ld e, a ;; e = pb[2] << 2
add a, a ; A = pb[2]<<2
ld e, a ; E свободен (pb[0] уже сохранён в C)
ld a, d ;; pb[1] >> 6
ld a, d
rlca
rlca
and a, #0x03
and a, #0x03 ; A = pb[1]>>6
or a, e ; A = page
ld e, a ; E = page
or a, e ;; a = (pb[2] << 2) | (pb[1] >> 6)
ld e, a
; map_page(page);
; if (page != cur_page) {
; map_page(page)
ld a, (_cur_page)
cp e
jr Z, __fbl0
; cur_page = page;
jr Z, fb_same
ld a, e
ld (_cur_page), a
; sprinter_page_w3(file_phys[page]);
ld hl, #_file_phys
ld d, #0x00
add hl, de
add hl, de ; hl = &file_phys[page]
ld a, (hl)
out (__io_page_w3), a
; }
__fbl0:
; return FILE_BUF[off];
ld a, (bc)
out (#0xE2), a ; OUT PORT_PAGE_W3 (== _io_page_w3 sfr)
fb_same:
ld a, (bc) ; A = *(0xC000 + off)
ret
__endasm;
*/
}
/* ESTEX WINREST (5Ah, RST 0x10) — копирует готовый буфер (char,attr)
@@ -1663,7 +1664,10 @@ static void render_line_to_cache(uint16_t line_idx)
ch = ' ';
}
if (parse_inline) {
/* handle_inline_marker реагирует только на ` * _ ~ — для остальных
* символов (подавляющее большинство) она возвращает 0 без side-эффектов.
* Пропускаем вызов функции (6 аргументов) на горячем пути обычного текста. */
if (parse_inline && (ch == '`' || ch == '*' || ch == '_' || ch == '~')) {
char lit_ch = 0;
uint8_t mk = handle_inline_marker(ch, &p, seg_end, prev_ch, &emph, &lit_ch);
if (mk == 1) {
@@ -1689,7 +1693,11 @@ static void render_line_to_cache(uint16_t line_idx)
cc = tgt;
prev_ch = ' ';
} else {
cc_put(cellbuf, &col, ch, cur_attr);
/* инлайн cc_put: col < MAX_CACHE_LINE_LEN гарантирован условием while */
uint16_t ci = (uint16_t)col * 2u;
cellbuf[ci] = (uint8_t)ch;
cellbuf[ci + 1] = cur_attr;
col++;
cc++;
prev_ch = ch;
}
@@ -1744,7 +1752,7 @@ static void draw_line_from_cache(uint16_t line_idx, uint8_t row)
* перерисовки видимой области после загрузки файла (Фаза 3-4). */
static void draw_viewport_from_cache(void)
{
for (uint8_t i = 0; i < VIEW_H - 1; i++) {
for (uint8_t i = 0; i < VIEW_H; i++) {
draw_line_from_cache((uint16_t)(top_line + i), (uint8_t)(VIEW_TOP_ROW + i));
}
}