# [z80] `global = local` после `if (local != global)` пишет `local - global` (A затирается сравнением) ## Кратко На бэкенде z80 последовательность ```c 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-оптимизатора). ## Минимальный пример ```c unsigned char vx; void update(unsigned char n) { if (n != vx) { vx = n; } } ``` Сборка: ``` sdcc -mz80 -S repro.c ``` ## Сгенерированный ассемблер (неверный) ```asm _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` дефект тот же (отличается лишь форма ветвления): ```asm _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` не оказался на пути записи; сравнивать сохранённую копию: ```c void update(unsigned char n) { unsigned char old = vx; vx = n; /* запись первой, A ещё держит n */ if (n != old) { /* побочный эффект */ } } ``` даёт корректное: ```asm _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».