Горизонтальный скролл (Фаза 5, финал): - Скроллим по ТИПУ, а не по длине: новый флаг IF_HSCROLL ставится только на код и таблицы; HR и границы fence (IF_NOWRAP без IF_HSCROLL) не двигаются. Блок едет целиком, включая строки короче 80. - Обход бага кодогенерации SDCC z80: `if (n!=g) g=n;` пишет (n-g) вместо n (SUB сравнения затирает A, store переиспользует испорченный A). Лечится записью viewport_x ДО сравнения. Минимальный репродьюсер и оба описания для трекера — в docs/bugs/sdcc-z80-cmp-store-a/ (воспроизводится на чистом sdcc 4.5, в т.ч. с --no-peep → это кодогенератор, не peephole). Рендеринг: - Отступленный fence (```c внутри списка) теперь распознаётся: is_fence_raw пропускает ведущие пробелы/табы; то же в рендере прячет строку-границу. - Строки-разделители (HR, ровно 80) больше не участвуют в скролле. Чистка: удалён мёртвый код (is_fence_delim, get_init_style[_raw], is_cont, seg_flags). Makefile (mdview/mdview2): iconv UTF-8→CP866 завершается ненулевым кодом при отбрасывании символов — игнорируем (|| true). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
4.7 KiB
[z80] global = local после if (local != global) пишет local - global (A затирается сравнением)
Кратко
На бэкенде z80 последовательность
if (n != g) { g = n; }
где n лежит в регистре A, а g — глобальная переменная, компилируется
неверно. Сравнение != вычисляется через SUB A,(HL), который разрушает A,
после чего присваивание g = n выполняется записью A — а там уже n - g, а не
n. В итоге в глобальную переменную попадает (unsigned char)(n - старое_g)
вместо n.
Корректно было бы использовать для сравнения CP (HL) (он не меняет A) либо
перезагрузить n перед записью.
Версия
SDCC 4.5.0 #15242 (Mac OS X x86_64). Опции по умолчанию; также воспроизводится с
--opt-code-speed и с --no-peep (то есть это баг кодогенератора, а не
peephole-оптимизатора).
Минимальный пример
unsigned char vx;
void update(unsigned char n)
{
if (n != vx) {
vx = n;
}
}
Сборка:
sdcc -mz80 -S repro.c
Сгенерированный ассемблер (неверный)
_update::
;repro.c: if (n != vx) {
ld hl, #_vx
sub a, (hl) ; A (= n) разрушается: A = n - vx
ret Z
;repro.c: vx = n;
ld (_vx+0), a ; пишет (n - vx) вместо n
;repro.c: }
ret
С --no-peep дефект тот же (отличается лишь форма ветвления):
_update::
ld iy, #_vx
sub a, 0 (iy) ; A (= n) разрушен
jp NZ, 00112$
jp 00103$
00112$:
ld (_vx+0), a ; пишет (n - vx)
00103$:
ret
Почему так происходит
n приходит в A (sdcccall). Кодогенератор выбирает SUB A,(HL) для вычисления
отношения n != vx. SUB перезаписывает A разностью. Далее генератор считает,
что ещё «живое» значение n по-прежнему в A, и для присваивания выдаёт голую
запись LD (_vx),A, не перезагрузив n. Поскольку дефект сохраняется при
--no-peep, он находится в кодогенерации (учёт регистров/времён жизни через
сравнение), а не в peephole-оптимизаторе.
Правильное преобразование сравнения — CP (HL): он выставляет флаги ровно как
SUB, но сохраняет A, поэтому последующая запись была бы корректной без единой
лишней инструкции.
Варианты, которые тоже воспроизводят
if (n == vx) return; vx = n;(форма с ранним выходом)nкак результат вызова функции, а не как параметр- и
-mz80по умолчанию, и--opt-code-speed
Обходной путь (workaround)
Записывать в глобальную переменную до сравнения, чтобы разрушающий SUB не
оказался на пути записи; сравнивать сохранённую копию:
void update(unsigned char n)
{
unsigned char old = vx;
vx = n; /* запись первой, A ещё держит n */
if (n != old) {
/* побочный эффект */
}
}
даёт корректное:
_update::
ld (_vx+0), a
ret
Файлы в этом каталоге
repro.c— минимальный примерrepro.asm— вывод с опциями по умолчанию (дефект виден)repro.nopeep.asm— вывод с--no-peep(дефект сохраняется)workaround.c/workaround.asm— обход «запись до сравнения» (корректно)
Поиск по трекеру
Поиск по баг-трекеру SDCC точного дубликата не нашёл. Ближайший по версии отчёт
#3834 «[Z80][SDCC 4.5] Compiler bug» — это другой дефект (genPointerSet,
переставленный порядок push/pop), а не данный случай «сравнение затирает A».