Files
Sprinter-SDCC/docs/bugs/sdcc-z80-ret-const-lost/REPORT-ru.md
T
snark13 5d31ed086f Резидент: pop_redraw + холодная половина pop_level в банки (902 -> 3525 Б кучи)
MEM-COLD3, шаг 1-2 из плана разгрузки резидента:

* pop_redraw.c целиком -> банк 7 (к pop_room.c: он и есть единственный
  потребитель разбора пометок).  Два модуля в одном банке линкуются в один
  сегмент — проверено на .map (BANK7 7928 -> 8556).
* pop_level.c расколот по частоте вызова: горячая половина (чтение тайлов,
  связи, дверные таблицы, живые стражи — зовут все банки, местами на каждый
  тайл) осталась в резиденте, холодная (чтение файла уровня, разбор комнаты,
  рестарт, потабличные различия) уехала в pop_level_cold.c -> банк 8.
  Общее состояние объявлено в _pop_level.h: данные банковых модулей всё
  равно линкуются в общий _DATA, в банк уехал только КОД.

Побочно найден баг кодогена SDCC: `return 1;` из ветки не кладёт 1 в A —
до __banked он маскировался случайным ненулевым остатком в A.  Репро и
разбор — docs/bugs/sdcc-z80-ret-const-lost/, стаб t_char починен одним
выходом через переменную.  Аудит всех .asm roomtest: других мест нет.

_CODE 25628 -> 23005, куча 902 -> 3525 Б.  tests-host 6/6.
2026-08-12 21:53:08 +03:00

89 lines
3.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# SDCC 4.5 z80: `return <константа>` из ветки теряется — функция возвращает мусор
**Компилятор:** SDCC 4.5.0 (z80), флаги `-mz80 --std-c99 --opt-code-size`
(воспроизводится и с `--no-std-crt0`, и в `__banked`, и без него).
**Симптом.** У функции, возвращающей `uint8_t`, ветка с `return 1;` НЕ кладёт
1 в A. Вызывающий читает A (`__sdcccall(1)`: uint8 возвращается в A) и
получает то, что там осталось от предыдущей операции — то есть мусор.
## Репро
`repro.c` (полный текст рядом; собран в `repro.asm`):
```c
uint8_t pop_level_guard(uint8_t room, uint8_t *tile, int8_t *dir,
uint8_t *color, uint8_t *skill) __banked
{
if (room < 1 || room > TK_ROOMS) return 0;
if (tk_gs_tile[room - 1] < 30) {
*tile = tk_gs_tile[room - 1]; *dir = 0; *color = 0; *skill = 1;
return 1; /* <-- значение теряется */
}
if (room == tk_guard_room) {
*tile = 11; *dir = 0; *color = 0; *skill = 1;
return 1; /* <-- и здесь */
}
return 0;
}
```
Сгенерированный хвост второй ветки (`repro.asm`):
```
;repro.c:15: *tile = 11; *dir = 0; *color = 0; *skill = 1;
ld a, #0x0b
ld (de), a
xor a, a ; A := 0 (это *dir = 0)
ld (bc), a
pop hl
ld (hl), #0x00
;repro.c:16: return 1;
ex de,hl
pop hl
push hl
push de
ld (hl), #0x01 ; это *skill = 1, НЕ возврат
jr 00108$
00107$:
xor a, a
00108$:
ld sp, ix
pop ix
ret ; A = 0 — вернулась ЛОЖЬ вместо 1
```
Кода `ld a, #0x01` для самого `return 1` нет ни в одной из двух веток.
## Когда прячется, когда стреляет
Баг присутствует и БЕЗ `__banked`, но там часто маскируется: если последним
в ветке A случайно оказался ненулевой байт (например `ld a,#0x0b` для
`*tile = 11`), вызывающий с проверкой «истина/ложь» получает правильный
ответ. `__banked` меняет распределение регистров (указатели уезжают в
IX-кадр, `*dir = 0` компилируется в `xor a,a`), A обнуляется — и та же
функция начинает возвращать 0 вместо 1.
Именно так это и вылезло у нас: набор `tests-host/t_char` годами был зелёным
и покраснел ровно в тот момент, когда стаб пометили `__banked` — хотя код
стаба не менялся.
## Обход
Один выход и явная переменная результата (`workaround.c` / `workaround.asm`):
```c
uint8_t res = 0;
...
res = 1;
...
return res; /* -> ld a, -5 (ix) — корректно */
```
## Признак для аудита
Грепать функции, у которых `return <константа>` стоит в ветке, где
последней операцией была запись по указателю или `xor a,a`. Проверять по
сгенерированному `.asm`: перед `jr` на общий выход обязан быть `ld a,#…`
(или загрузка результата), иначе возврат — мусор.