# [z80] `global = local` after `if (local != global)` stores `local - global` (A clobbered by comparison)

## Summary

On the z80 backend, the sequence

```c
if (n != g) { g = n; }
```

where `n` is in register `A` and `g` is a global, miscompiles. The compiler
evaluates the `!=` comparison with `SUB A,(HL)`, which destroys `A`, and then
emits the assignment `g = n` by storing `A` — which now holds `n - g`, not `n`.
As a result the global ends up holding `(unsigned char)(n - old_g)` instead of `n`.

It should either use `CP (HL)` (which leaves `A` intact) for the comparison, or
reload `n` before the store.

## Version

SDCC 4.5.0 #15242 (Mac OS X x86_64). Default options; also reproduces with
`--opt-code-speed` and with `--no-peep` (so this is a code-generator bug, not a
peephole-optimizer bug).

## Minimal reproducer

```c
unsigned char vx;

void update(unsigned char n)
{
    if (n != vx) {
        vx = n;
    }
}
```

Build:

```
sdcc -mz80 -S repro.c
```

## Generated assembly (wrong)

```asm
_update::
;repro.c: if (n != vx) {
	ld	hl, #_vx
	sub	a, (hl)        ; A (= n) is destroyed: A = n - vx
	ret	Z
;repro.c: vx = n;
	ld	(_vx+0), a     ; stores (n - vx) instead of n
;repro.c: }
	ret
```

With `--no-peep` the same defect is present (only the branch shape differs):

```asm
_update::
	ld	iy, #_vx
	sub	a, 0 (iy)      ; A (= n) destroyed
	jp	NZ, 00112$
	jp	00103$
00112$:
	ld	(_vx+0), a     ; stores (n - vx)
00103$:
	ret
```

## Why it happens

`n` arrives in `A` (sdcccall). The code generator picks `SUB A,(HL)` to evaluate
the relational `n != vx`. `SUB` overwrites `A` with the difference. The generator
then treats the still-live value `n` as if it were still in `A` and emits a bare
store `LD (_vx),A` for the assignment, without reloading `n` first. Because the
defect survives `--no-peep`, it is in code generation (register/lifetime tracking
across the comparison), not in the peephole optimizer.

The correct lowering for the comparison is `CP (HL)`, which sets the flags exactly
like `SUB` but preserves `A`, so the subsequent store would be correct with no
extra instructions.

## Variants that also reproduce

- `if (n == vx) return; vx = n;` (early-return form)
- `n` coming from a function call result instead of a parameter
- both `-mz80` default and `--opt-code-speed`

## Workaround

Store into the global *before* the comparison, so the destructive `SUB` is no
longer on the store path; compare a saved copy instead:

```c
void update(unsigned char n)
{
    unsigned char old = vx;
    vx = n;                 /* store first, A still holds n */
    if (n != old) {
        /* side effect */
    }
}
```

generates the correct:

```asm
_update::
	ld	(_vx+0), a
	ret
```

## Files in this directory

- `repro.c` — minimal reproducer
- `repro.asm` — generated output, default options (defect visible)
- `repro.nopeep.asm` — generated output with `--no-peep` (defect still present)
- `workaround.c` / `workaround.asm` — store-before-compare workaround (correct)

## Tracker search

A search of the SDCC bug tracker did not turn up an exact duplicate. The closest
version-matching report, #3834 "[Z80][SDCC 4.5] Compiler bug", is a *different*
defect (`genPointerSet`, swapped push/pop order), not this comparison-clobbers-A
case.
