Горизонтальный скролл (Фаза 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>
3.2 KiB
[z80] global = local after if (local != global) stores local - global (A clobbered by comparison)
Summary
On the z80 backend, the sequence
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
unsigned char vx;
void update(unsigned char n)
{
if (n != vx) {
vx = n;
}
}
Build:
sdcc -mz80 -S repro.c
Generated assembly (wrong)
_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):
_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)ncoming from a function call result instead of a parameter- both
-mz80default 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:
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:
_update::
ld (_vx+0), a
ret
Files in this directory
repro.c— minimal reproducerrepro.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.