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