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