GCC and Clang generate different code for this snippet:
extern volatile unsigned WATCHDOG;
void reset_watchdog() {
unsigned t = WATCHDOG;
WATCHDOG = t;
}
Consider WATCHDOG as a memory-mapped I/O register.
When compiling with -O3 Clang 14 generates pretty straight forward code:
reset_watchdog():
lui a0, %hi(WATCHDOG)
lw a1, %lo(WATCHDOG)(a0)
sw a1, %lo(WATCHDOG)(a0)
ret
However, GCC 10.0.2 inserts a superfluous sign-extend instruction after the load:
reset_watchdog():
lui a4,%hi(WATCHDOG)
lw a5,%lo(WATCHDOG)(a4)
sext.w a5,a5
sw a5,%lo(WATCHDOG)(a4)
ret
See also: https://godbolt.org/z/zK1ss3Wsz
The sext.w is superfluous in two ways:
lwalready sign-extends the destination register under RV64I (lwuwould zero-extend the destination, instead)- for the
swinstruction the content of the 4 most-significant bytes of the source register is irrelevant
So why does GCC generate such (suboptimal?) code?
GCC gets even more interesting when the function returns the original watchdog value:
extern volatile unsigned WATCHDOG;
unsigned long reset_watchdog() {
unsigned long t = WATCHDOG;
WATCHDOG = t;
return t;
}
As expected, Clang basically just replaces the lw instruction with lwu to avoid the sign extension:
reset_watchdog():
lui a1, %hi(WATCHDOG)
lwu a0, %lo(WATCHDOG)(a1)
sw a0, %lo(WATCHDOG)(a1)
ret
While GCC generates more convoluted code:
reset_watchdog():
lui a5,%hi(WATCHDOG)
lw a0,%lo(WATCHDOG)(a5)
sext.w a0,a0
sw a0,%lo(WATCHDOG)(a5)
slli a0,a0,32
srli a0,a0,32
ret
Basically GCC really sticks with sign-extending the loaded 4 byte value and thus has to emit two additional instruction to undo a possible extension and clear the most significant 4 bytes. It's as if GCC doesn't know that RV64 has the lwu instruction and that lw already sign-extends the destination.