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

Reply via email to