https://gcc.gnu.org/bugzilla/show_bug.cgi?id=79835
Bug ID: 79835 Summary: load to a variable outside the scope of a function is optimized out Product: gcc Version: 6.3.0 Status: UNCONFIRMED Severity: normal Priority: P3 Component: c Assignee: unassigned at gcc dot gnu.org Reporter: bugreporting at gmx dot com Target Milestone: --- We use tcmalloc. Tcmalloc allows hooks that can be called during malloc. I was using a static __thread thread-local variable to prevent re-entrance. I.e., in_malloc_hook = 1; ...do work that might call malloc... in_malloc_hook = 0; Sometimes the line setting in_malloc_hook = 1 would be optimized away. I worked up an example that shows this problem without using tcmalloc. First, create mymalloc.o that supplies a malloc() function from this mymalloc.c file: /* * gcc -g -O2 -Wall mymalloc.c -c -o mymalloc.o */ #include <unistd.h> #include <malloc.h> static void (*mymalloc_hook)(const void *ptr, size_t size); void mymalloc_addhook(void (*f)(const void *ptr, size_t size)) { mymalloc_hook = f; } void *malloc(size_t count) { // Will work the same way regardless of fno-tree-dse if this is changed to mymalloc. void *ptr = sbrk(count); mymalloc_hook(ptr, count); return ptr; } Next, link this .o into the following program, example.c: /* $ gcc -g -O2 -Wall example.c mymalloc.o -o example $ ./example 0x55d73b3a4000 64 0x55d73b3a4040 0x55d73b3a4000 $ gcc -g -O2 -fno-tree-dse -Wall example.c mymalloc.o -o example $ ./example 0x55d31f3ea000 64 (nil) 0x55d31f3ea000 */ #include <stdio.h> #include <malloc.h> void mymalloc_addhook(void (*f)(const void *ptr, size_t size)); // Will work the same way regardless of fno-tree-dse if void *malloc(size_t) is declared here. static int in_hook = 0; static int allocating = 0; static void *buffer = NULL; void *foo(void); void hook(const void *ptr, size_t size) { void *current; if (in_hook) { return; } in_hook = 1; current = foo(); printf("%p %ld %p\n", ptr, size, current); in_hook = 0; } void *foo(void) { if (!buffer) { if (allocating) { return NULL; } allocating = 1; // Without -fno-tree-dse, this is optimized out. buffer = malloc(64); allocating = 0; } return buffer; } int main() { mymalloc_addhook(&hook); printf("%p\n", foo()); return 0; } Then run the program. Note that the expectation is that, when we print out current, it will always be (nil) because we guard against calling malloc more than once. However, this only happens with older versions of gcc (4.4.7) but not with newer versions (4.9.2 and up). Using fno-tree-dse will get the correct behavior. The problem seems to be that gcc thinks that malloc is a builtin, even when it is being provided by an external library (e.g., mymalloc.o or tcmalloc). It therefore thinks that malloc cannot change a file-local variable, so it can optimize out the load. However, in this case, we have replaced malloc, and it can call back into functions that change or test this file-local (or global? didn't test that) variable. If the name of the function "malloc" is changed, then (nil) is always returned for the value of current, as expected. The gcc command is in the comments. This works with many different versions of gcc, from 4.9.2 and up. $ gcc --version gcc (Debian 6.3.0-5) 6.3.0 20170124 $ uname -a Linux xxxx 4.9.0-1-amd64 #1 SMP Debian 4.9.2-2 (2017-01-12) x86_64 GNU/Linux