https://gcc.gnu.org/bugzilla/show_bug.cgi?id=124160
Bug ID: 124160
Summary: Non efficient bit access to AVR I/O Register
Product: gcc
Version: 15.2.0
Status: UNCONFIRMED
Severity: normal
Priority: P3
Component: c
Assignee: unassigned at gcc dot gnu.org
Reporter: gabriel at gabotronics dot com
Target Milestone: ---
Created attachment 63723
--> https://gcc.gnu.org/bugzilla/attachment.cgi?id=63723&action=edit
Minimal code to reproduce bug
Hello GCC team,
I’m working on an embedded project using an AVR microcontroller.
I’ve noticed that version 15 generates non-efficient access to registers that
have single instruction opcodes available.
The AVR Instruction Set provides opcodes for I/O direct addressing:
SBIC: Skip if Bit in I/O Register Cleared
SBIS: Skip if Bit in I/O Register Set
This is a minimal example that generates the problem:
typedef __UINT8_TYPE__ uint8_t;
#define SFR (*(volatile uint8_t*) (1 + __AVR_SFR_OFFSET__))
char File1 (char angle, char scale);
void File2 (void)
{
if (!(SFR & (1 << 4)))
for (uint8_t i = 0; i < 3; ++i)
File1 (i, 1);
}
Compile with:
$ avr-gcc -mmcu=atmega8 -S -Os -dp x.c
---
Communication history from the GNU team through email:
---
Georg-Johann Lay wrote on February 13th 2026:
The generated code is:
File2:
push r28 ; 40 [c=4 l=1] pushqi1/0
in r24,0x1 ; 37 [c=4 l=1] movqi_insn/3
mov r28,r24 ; 38 [c=4 l=2] *andqi3/3
andi r28,1<<4
sbrc r24,4 ; 39 [c=4 l=2] *sbrx_branchqi
rjmp .L1
.L3:
ldi r22,lo8(1) ; 32 [c=4 l=1] movqi_insn/1
mov r24,r28 ; 33 [c=4 l=1] movqi_insn/0
call File1 ; 12 [c=12 l=2] call_value_insn/1
subi r28,lo8(-(1)) ; 34 [c=4 l=1] *addqi3/1
cpi r28,lo8(3) ; 35 [c=4 l=1] cmpqi3/0
brne .L3 ; 36 [c=4 l=1] branch
.L1:
pop r28 ; 43 [c=4 l=1] popqi
ret ; 44 [c=0 l=1] return_from_epilogue
What happens is that in the calculation of SFR & (1 << 4) a value of zero may
be produced, and that value is then used to initialize the loop variable i = 0.
Insn combine combines a SBRC insn 39 as expected but doesn't look further,
because is cannot combine the volatile load 37 as that load would still be
around after a combination to SBIC, breaking volatile correctness.
The pass that introduces the reuse of the zero value is .uncprop1, and indeed
with -fno-tree-dominator-opts the code is as expected (that's just an
observation and not a recommendation to use that option):
File2:
push r28 ; 39 [c=4 l=1] pushqi1/0
sbic 0x1,4 ; 38 [c=4 l=2] *sbix_branch
rjmp .L1
ldi r28,0 ; 37 [c=4 l=1] movqi_insn/0
.L3:
ldi r22,lo8(1) ; 32 [c=4 l=1] movqi_insn/1
mov r24,r28 ; 33 [c=4 l=1] movqi_insn/0
call File1 ; 13 [c=12 l=2] call_value_insn/1
subi r28,lo8(-(1)) ; 34 [c=4 l=1] *addqi3/1
cpi r28,lo8(3) ; 35 [c=4 l=1] cmpqi3/0
brne .L3 ; 36 [c=4 l=1] branch
.L1:
pop r28 ; 42 [c=4 l=1] popqi
ret ; 43 [c=0 l=1] return_from_epilogue
Now insn 38 is a SBIC on bit 4 of the SFR, and insn 37 just loads i = 0 in reg
28. The sequence is 2 instructions shorter.
Johann
---
Jeffrey Law wrote on February 15th 2026:
Recommended action would be to get a bug filed in gcc.gnu.org/bugzilla so that
it can be tracked over time. I doubt this is an easy code quality regression
to fix.
Based on Georg-Johann's analysis, the passes are basically behaving as
designed. DOM's job is to squash out redundancies, so it's going to do
aggressive constant propagation including conditional propagation of constants.
uncprop will undo some of those propagations late in the gimple passes,
essentially turning references to the constant 0 into an SSA_NAME object when
that object is known to have the value zero. This helps reduce on-edge
insertions of constant initializations as we leave gimple and lower to RTL.
The existence of volatile accesses is certainly going to inhibit both the
gimple and RTL optimizers in various ways. It's an additional wrinkle that
will need to be considered.
jeff
---
I'm reporting this as a bug since activating the "tree-dominator-opts"
optimization causes less efficient code in the AVR architecture.
Regards,
Gabriel