https://gcc.gnu.org/bugzilla/show_bug.cgi?id=126179

            Bug ID: 126179
           Summary: avr-gcc -Os: __flash disregarded for some struct array
                    accesses
           Product: gcc
           Version: 15.2.1
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: target
          Assignee: unassigned at gcc dot gnu.org
          Reporter: k4be at k4be dot pl
  Target Milestone: ---

At -Os, avr-gcc generates a plain ld (RAM load) instead of lpm (flash load) for
the first field of a const __flash-qualified struct array element, while
correctly emitting lpm for the second field of the same element. The struct's
first field ends up reading garbage off RAM space instead of the intended
flash-resident constant.

Reproduced with:
- avr-gcc (Gentoo 15.2.1_p20260214 p5) 15.2.1
- avr-gcc 13.2.1
- also reproduced on git revision b6825a7ce698b65657e952e437ab9edd655fe5a5
(2026-07-06)

Not reproduced with -O0, -O1, -O2, only -Os.

Bug not present in a 2020 build (unknown avr-gcc version).

Minimal reproduction (preprocessed code from test.i; there's no preprocessor
directive used in source)

# 0 "test.c"
# 0 "<built-in>"
# 0 "<command-line>"
# 1 "test.c"
struct pair { unsigned char data; unsigned char cmd; };
const __flash struct pair table[] = { {0x11, 1}, {0x33, 0}, {0x55,1} };

void write_data(unsigned char d);
void write_cmd(unsigned char c);

void test(void){
    unsigned char i;
    for(i=0; i<3; i++){
        if(table[i].cmd) write_data(table[i].data); else
write_cmd(table[i].data);
    }
}

Build:
bin/avr-gcc -v -save-temps -Os -mmcu=atmega8 -Wall -Wextra -S -o test.s test.c

avr-gcc output:
Using built-in specs.
Reading specs from
/home/k4be/avr-gcc-test/lib/gcc/avr/17.0.0/device-specs/specs-atmega8
COLLECT_GCC=bin/avr-gcc
Target: avr
Configured with: ./configure --prefix=/home/k4be/avr-gcc-test --target=avr
-enable-languages=c --disable-nls
Thread model: single
Supported LTO compression algorithms: zlib zstd
gcc version 17.0.0 20260706 (experimental) (GCC)
COLLECT_GCC_OPTIONS='-v' '-save-temps' '-Os'  '-Wall' '-Wextra' '-S' '-o'
'test.s' '-mdouble=32' '-mlong-double=64' '-specs=device-specs/specs-atmega8'
'-mmcu=avr4'
 /home/k4be/avr-gcc-test/libexec/gcc/avr/17.0.0/cc1 -E -quiet -v -imultilib
avr4 -D__AVR_ATmega8__ -D__AVR_RODATA_IN_RAM__=1 -D__AVR_DEVICE_NAME__=atmega8
test.c -mn-flash=1 -mno-skip-bug -mdouble=32 -mlong-double=64 -mmcu=avr4 -Wall
-Wextra -Os -fpch-preprocess -o test.i
ignoring nonexistent directory
"/home/k4be/avr-gcc-test/lib/gcc/avr/17.0.0/../../../../avr/sys-include"
ignoring nonexistent directory
"/home/k4be/avr-gcc-test/lib/gcc/avr/17.0.0/../../../../avr/include"
#include "..." search starts here:
#include <...> search starts here:
 /home/k4be/avr-gcc-test/lib/gcc/avr/17.0.0/include
 /home/k4be/avr-gcc-test/lib/gcc/avr/17.0.0/include-fixed
End of search list.
COLLECT_GCC_OPTIONS='-v' '-save-temps' '-Os'  '-Wall' '-Wextra' '-S' '-o'
'test.s' '-mdouble=32' '-mlong-double=64' '-specs=device-specs/specs-atmega8'
'-mmcu=avr4'
 /home/k4be/avr-gcc-test/libexec/gcc/avr/17.0.0/cc1 -fpreprocessed test.i
-mn-flash=1 -mno-skip-bug -quiet -dumpbase test.c -dumpbase-ext .c -mdouble=32
-mlong-double=64 -mmcu=avr4 -Os -Wall -Wextra -version -o test.s
GNU C23 (GCC) version 17.0.0 20260706 (experimental) (avr)
        compiled by GNU C version 15.2.1 20260214, GMP version 6.3.0, MPFR
version 4.2.2, MPC version 1.3.1, isl version none
GGC heuristics: --param ggc-min-expand=30 --param ggc-min-heapsize=4096
Compiler executable checksum: 6fc2eca1b0d5191d5c94a63fb2b31282
COMPILER_PATH=/home/k4be/avr-gcc-test/libexec/gcc/avr/17.0.0/:/home/k4be/avr-gcc-test/libexec/gcc/avr/17.0.0/:/home/k4be/avr-gcc-test/libexec/gcc/avr/:/home/k4be/avr-gcc-test/lib/gcc/avr/17.0.0/:/home/k4be/avr-gcc-test/lib/gcc/avr/
LIBRARY_PATH=/home/k4be/avr-gcc-test/lib/gcc/avr/17.0.0/
COLLECT_GCC_OPTIONS='-v' '-save-temps' '-Os'  '-Wall' '-Wextra' '-S' '-o'
'test.s' '-mdouble=32' '-mlong-double=64' '-specs=device-specs/specs-atmega8'
'-mmcu=avr4' '-dumpdir' 'test.'

Relevant output snippet:
test:
    push r28
    push r29
    ldi r28,lo8(table)
    ldi r29,hi8(table)
.L4:
    movw r30,r28
    ld r24,Z+          ; <-- BUG: reads table[i].data from DATA SPACE (SRAM),
not flash
    lpm r25,Z           ; correct: reads table[i].cmd from flash
    cpi r25,lo8(0)
    breq .L2
    rcall write_data    ; called with garbage r24 (SRAM byte at address
`table`), not 0x11/0x33/0x55
.L3:
    adiw r28,2
    ldi r24,hi8(table+6)
    cpi r28,lo8(table+6)
    cpc r29,r24
    brne .L4
    pop r29
    pop r28
    ret
.L2:
    rcall write_cmd
    rjmp .L3

A single, non-looping access to the same kind of struct correctly uses lpm at
every optimization level tested, so this may be specific to the
loop/pointer-increment shape above, not __flash struct access in general:

struct pair { unsigned char a; unsigned char b; };
const __flash struct pair table[] = { {0x11, 0x22}, {0x33, 0x44} };

unsigned char out;
void test(unsigned char i){
    out = table[i].a;   // correctly compiles to lpm at -Os
}

Workaround: replace const __flash with PROGMEM, use pgm_read_* for access.

Reply via email to