1: Sample code that reproduces the problem.
struct Item
{
unsigned char tag;
unsigned char padding[19];
unsigned short first;
unsigned short second;
unsigned short third;
unsigned char padding2[4];
unsigned short last;
};
struct Table
{
struct Item items[8];
};
static struct Table table;
static void
fill(unsigned short count, void *storage)
{
struct Table *p = storage;
for (unsigned char i = 0; i < count; ++i)
{
p->items[i].tag = i;
p->items[i].first = i + 1;
p->items[i].second = i + 2;
p->items[i].third = i + 3;
p->items[i].last = i + 4;
}
}
int
main(void)
{
fill(8, &table);
for (unsigned char i = 0; i < 8; ++i)
if (table.items[i].third != i + 3)
return 1;
return 0;
}
2: Exact command used to run SDCC on this sample code.
sdcc -mz80 --reserve-regs-iy --max-allocs-per-node 512 -S example.c
3: SDCC version tested.
Local builds with the shared compiler sources at r17035, r17036 and r17043, using the same Z80 backend and libraries.
The corresponding runtime regression test passes with r17035 and fails with r17036. The reduced runtime test also fails with r17043.
4: Copy of the error message or incorrect output, or a clear description of the observed versus expected behaviour.
The program should return zero. The generated code stores the two bytes of third in reverse order. Instead of the expected values 3 through 10, the corresponding runtime test reads 768, 1024, 1280, 1536, 1792, 2048, 2304 and 2560.
For p->items[i].third = i + 3, the compiler calculates the low byte in B and the high byte in A, then stores A followed by B. Z80 requires the low byte to be stored first.
Adding --no-peep exposes the incorrect attempt to exchange the registers:
push af
ld a, b
ld b, a
pop af
ld (hl), a
inc hl
ld (hl), b
The pop af restores the original high byte into A, while B still contains the original low byte.
This was discovered in the r17038 random regression results. It can also be reproduced using the existing regression test:
make -C support/regression -j2 random-test-7589958616919012411 \
TEST_STEMS=tst_bug-4003 \
RESULTS_DIR=results-4003-check \
TMP_DIR=gen-4003-check
With that configuration, support/regression/tests/bug-4003.c passes all 46 checks with r17035, but fails the eight checks of third with r17036. The separate support/valdiag/tests/bug-4003.c passes all 24 checks with both revisions.
The first failing adjacent revision is [r17036], which fixes [bugs:#4148] by allowing copy propagation through casts from pointer types. This appears to expose incorrect register-copy handling in the Z80 backend. The reproducer requires neither _Optional nor _Generic.
Replacing the incorrect register exchange in the generated assembly makes all 46 original runtime checks pass.
To bypass the random configuration generator (e.g. on MacOS, because
sedis not GNU's version):Last edit: Christopher Bazley 22 hours ago
I don't have the time nor the expertise to investigate this today, but I did ask Codex to do so and it produced the attached candidate patch, with the following explanation:
I hope this helps whoever picks up this bug. If not, feel free to ignore it.
Thanks. That fix would work. However, due to the importance of genCopy for efficient z80 code generation, I used a different approach for the fix. I used the test from your patch.
Fixed in [r17066].
Related
Commit: [r17066]