Bug 109236 - [avr] Invalid code of signed 16-bit compare optimization
Summary: [avr] Invalid code of signed 16-bit compare optimization
Status: RESOLVED INVALID
Alias: None
Product: gcc
Classification: Unclassified
Component: target (show other bugs)
Version: 12.2.0
: P3 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2023-03-21 15:23 UTC by gandalf
Modified: 2023-03-21 21:32 UTC (History)
1 user (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail: 12.2.0, 13.0
Last reconfirmed:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description gandalf 2023-03-21 15:23:40 UTC
I'm seeing incorrect code when -O1 or higher is used on AVR (atmega1284p). The compiler generates code that compares "x" to "y", instead of performing the subtraction and comparing against 0. This subtraction matters since "x" and "y" are signed 16-bit variables, and the math in the expression "x-y <= 0" stays as 16-bit in AVR (as opposed to being promoted to 32-bit ints in e.g. x86).

gcc -v:

Using built-in specs.
Reading specs from /usr/local/avr/lib/gcc/avr/12.2.0/device-specs/specs-avr2
COLLECT_GCC=avr-gcc
COLLECT_LTO_WRAPPER=/usr/local/avr/libexec/gcc/avr/12.2.0/lto-wrapper
Target: avr
Configured with: ../configure --target=avr --prefix=/usr/local/avr --disable-nls --enable-languages=c --disable-bootstrap --disable-libssp
Thread model: single
Supported LTO compression algorithms: zlib zstd
gcc version 12.2.0 (GCC)

Sample code:

_Bool compare(short x, short y)
{
  return (x-y <= 0);
}

Compiled using: gcc -O1 -mmcu=atmega1284p -c -o test.o test.c

ASM result:

00000000 <compare>:
   0:   9c 01           movw    r18, r24
   2:   81 e0           ldi     r24, 0x01       ; 1
   4:   62 17           cp      r22, r18        <---- X compared directly to Y; no subtraction
   6:   73 07           cpc     r23, r19
   8:   04 f4           brge    .+0             ; 0xa <compare+0xa>
   a:   80 e0           ldi     r24, 0x00       ; 0

0000000c <.L2>:
   c:   08 95           ret

I instead expect to see a subtraction between r22:23 and r18:19, then a compare against r1 (zero).

The following testcase causes an incorrect computation:

compare(-30737, 24799) returns 1 on AVR; expected 0.  (-30737 - (24799)) as signed 16-bit = 10000, which is not <= 0.

For the record, changing the function to "return ((short)(x-y) <= 0)" produces the same incorrect code.

Compiling the function without optimization correctly returns 0.

It's possible other signed variable sizes besides 16-bit have the same problem, but I did not test this.
Comment 1 Dimitar Dimitrov 2023-03-21 19:02:18 UTC
Pass the -Wstrict-overflow=4 option to GCC and see that compiler assumes no integer overlow will happen:

test.c:3:12: warning: assuming signed overflow does not occur when simplifying 'X - Y <= 0' to 'X <= Y' [-Wstrict-overflow]
    3 |   return (x-y <= 0);


Consider using "__builtin_sub_overflow" for a strictly defined behaviour when integer overflow happens.
Comment 2 gandalf 2023-03-21 21:32:24 UTC
Thank you, I hadn't thought of that at all. That was precisely my problem and I developed a work-around. Not a bug.