Files
Sprinter-SDCC/docs/bugs/sdcc-z80-cmp-store-a/REPORT-ru.md
T
snark13 138e783e72 mdview2: горизонтальный скролл по типу блока + фиксы fence/HR/SDCC
Горизонтальный скролл (Фаза 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>
2026-06-24 10:11:27 +03:00

4.7 KiB
Raw Blame History

[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».