This is GCC Bugzilla
This is GCC Bugzilla Version 2.20+
View Bug Activity | Format For Printing | Clone This Bug
Using GCC 3.4 I got a failure in the glibc testsuite. The problem can be shown with the appended simplified program: gromit:~/tmp:[0]$ /opt/gcc/3.4-devel/bin/gcc -O2 inl.c && ./a.out memrchr flunked test 1 1 errors. Release: 3.4 20030103 (experimental) Environment: System: Linux gromit 2.4.18 #1 Sat Apr 6 22:05:01 CEST 2002 i686 unknown Architecture: i686 host: i686-pc-linux-gnu build: i686-pc-linux-gnu target: i686-pc-linux-gnu configured with: How-To-Repeat: Compile this program (this is preproccessed): ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ typedef unsigned int size_t; extern int printf (__const char *__restrict __format, ...) ; extern int puts (__const char *__s) ; extern __inline void *__memrchr (__const void *__s, int __c, size_t __n); extern __inline void * __memrchr (__const void *__s, int __c, size_t __n) { register unsigned long int __d0; register void *__res; if (__n == 0) return ((void *)0); __asm__ __volatile__ ("std\n\t" "repne; scasb\n\t" "je 1f\n\t" "orl $-1,%0\n" "1:\tcld" : "=D" (__res), "=&c" (__d0) : "a" (__c), "0" (__s + __n - 1), "1" (__n), "m" ( *(struct { __extension__ char __x[__n]; } *)__s) : "cc"); return __res + 1; } const char *it = "<UNSET>"; size_t errors = 0; static void check (int thing, int number) { if (!thing) { printf("%s flunked test %d\n", it, number); ++errors; } } char one[50]; char two[50]; char *cp; static void test_memrchr (void) { it = "memrchr"; check (__memrchr ("abcd", 'z', 5) == ((void *)0), 1); } int main (void) { int status; test_memrchr (); if (errors == 0) { status = 0; puts("No errors."); } else { status = 1; printf("%Zd errors.\n", errors); } return status; } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Note that GCC 3.3 and earlier releases optimized this correctly, this is therefore a regression. objdump -dr of the binary shows that the test_memrchr function is misoptimized: 080483d0 <test_memrchr>: 80483d0: 55 push %ebp 80483d1: ba e0 84 04 08 mov $0x80484e0,%edx 80483d6: 89 e5 mov %esp,%ebp 80483d8: 57 push %edi 80483d9: b8 7a 00 00 00 mov $0x7a,%eax 80483de: 83 ec 14 sub $0x14,%esp 80483e1: 89 15 14 95 04 08 mov %edx,0x8049514 80483e7: b9 05 00 00 00 mov $0x5,%ecx 80483ec: bf ec 84 04 08 mov $0x80484ec,%edi 80483f1: fd std 80483f2: f2 ae repnz scas %es:(%edi),%al 80483f4: 74 03 je 80483f9 <test_memrchr+0x29> 80483f6: 83 cf ff or $0xffffffff,%edi 80483f9: fc cld 80483fa: c7 44 24 04 01 00 00 movl $0x1,0x4(%esp,1) 8048401: 00 8048402: c7 04 24 00 00 00 00 movl $0x0,(%esp,1) 8048409: e8 82 ff ff ff call 8048390 <check> The result of memrchr is not used - especially not compared against NULL and the result passed to check - and instead a $0x0 is passed as first argument to check (at address 8048402).
Fix: No fix known for GCC itself. The program can be fixed to work by changing the program to use (pseudo patch): - register void *__res; + register int __res; - return __res + 1; + return (void *) __res + 1; But this should not be necessary, the arithmetic on void is a GNU extension and therefore legal GNU C code.
State-Changed-From-To: open->analyzed State-Changed-Why: Regression from 3.3 according to Andreas.
Moving up priority because it effects glibc.
Bug confirmed with gcc (GCC) 3.4 20030604 (experimental).
still happens on the mainline (20030704).
Still happens on the mainline (20030727).
It is not the char * arithmetic that is being miscompiled as if I replace the void* inside __memrchr with char*, I still get the miscompile. Also the code is wrong it uses D when it really meant to use d and it should be using "+" instead of "=" aqnd it has enouns additions of accesing the memory. __inline void * __memrchr (__const char *__s, int __c, size_t __n) { register unsigned long int __d0; register char *__res; if (__n == 0) return ((void *)0); __res = __s + __n - 1; __d0 = __n; __asm__ __volatile__ ("std\n\t" "repne; scasb\n\t" "je 1f\n\t" "orl $-1,%0\n" "1:\tcld" : "+d" (__res), "+c" (__d0) : "a" (__c) : "cc"); return (void*)(__res + 1); } If I change "register char *__res;" to "register int __res;", it works so it looks like it is the pointer which causes this. Also it goes wrong in combine (GCC sets __res dead after asm for some reason).
Change summary based on my anlysis last night.
The inner has nothing to do with this bug as I can reproduce it when I inlined it myself: static void test_memrchr (void) { it = "memrchr"; void *ret; { __const char *__s = "abcd"; int __c = 'z'; size_t __n = 5; { register unsigned long int __d0; register void *__res; if (__n == 0) { ret = (void*)0; goto end; } __res = __s + __n - 1; __d0 = __n; __asm__ __volatile__ ("std\n\t" "repne; scasb\n\t" "je 1f\n\t" "orl $-1,%0\n" "1:\tcld" : "+d" (__res), "+c" (__d0) : "a" (__c) : "cc"); ret = (void*)(__res + 1); } goto end; } end: check (ret == ((void *)0), 1); }
The C semantics for pointer arithmetic mean that GCC is allowed to assume that this line return __res + 1; never causes a null pointer to be returned. The "mis-optimized" code has been optimized based on that assumption. In short, the bug is in glibc. The simplest fix is to write "incl %0" as the last instruction of the assembly block rather than trying to do this calculation in C.