Files
snark13 138e783e72 mdview2: горизонтальный скролл по типу блока + фиксы fence/HR/SDCC
Горизонтальный скролл (Фаза 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>
2026-06-24 10:11:27 +03:00

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)
  • 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:

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 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)

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.