Issue with __attribute__((constructor)) and -Os -fno-common

Hi,

I think that Clang erroneously discards a function annotated with
__attribute__((constructor)) when flags -Os -fno-common are given. Test
case below.

What do you think?

Thanks.

----8<--------8<--------8<--------8<--------8<--------8<--------
$ cat ctor.c
int val;

static void __attribute__((constructor)) init_fn(void)
{
        val = 1;
}

int main(int argc, char *argv[])
{
        return val;
}
----8<--------8<--------8<--------8<--------8<--------8<--------

Here is what I observed:

- Clang (10.0.0-4ubuntu1) with -Os -fno-common: function init_fn() is
NOT emitted,
- Clang (10.0.0-4ubuntu1) with no flag, or only -Os or -fno-common:
init_fn() is present as expected,
- GCC (Ubuntu 9.3.0-10ubuntu1) with the same flags: init_fn() is present
too,
- Since https://reviews.llvm.org/D75056, -fno-common is the default and
therefore -Os is enough to cause the issue.

----8<--------8<--------8<--------8<--------8<--------8<--------
$ clang --target=arm-linux-gnueabihf -Os -fno-common -S ctor.c \
  -o /dev/stdout | grep init_fn
$ clang --target=arm-linux-gnueabihf -Os -S ctor.c \
  -o /dev/stdout | grep init_fn
        .p2align 2 @ -- Begin function init_fn
        .type init_fn,%function
        .code 32 @ @init_fn
init_fn:
        .size init_fn, .Lfunc_end0-init_fn
        .long init_fn(target1)
        .addrsig_sym init_fn
$ clang --target=arm-linux-gnueabihf -fno-common -S ctor.c \
  -o /dev/stdout | grep init_fn
        .p2align 2 @ -- Begin function init_fn
        .type init_fn,%function
        .code 32 @ @init_fn
init_fn:
        .size init_fn, .Lfunc_end0-init_fn
        .long init_fn(target1)
        .addrsig_sym init_fn
$ arm-linux-gnueabihf-gcc -Os -fno-common -S ctor.c \
  -o /dev/stdout | grep init_fn
        .type init_fn, %function
init_fn:
        .size init_fn, .-init_fn
        .word init_fn(target1)
----8<--------8<--------8<--------8<--------8<--------8<--------

The global constructor was removed by setting the initial value of “val” to 1 instead of 0. So, the behavior of this program is preserved. Doesn’t look like erroneous behavior.

OK, my example is too simplified indeed. Please consider the following
instead:

int val;

static void __attribute__((constructor)) init_fn(void)
{
  val++;
}

int main(int argc, char *argv[])
{
  return val;
}

With this, clang -Os -fno-common generates a global variable initialized
to 1 and discards init_fn().

Now, what happens if the executable is later linked against a shared
library which has its own constructor and sets "val" to some non-zero
value? I mean this for instance:

extern int val;

static void __attribute__((constructor)) so_init_fn(void)
{
  val = 1;
}

I would expect the main program to return 2, not 1.

Last thing, if I define "val" as volatile, the programs behaves as expected.

Are we in "unspecified, just don't do this" territory here?

Thanks,

The global constructor was removed by setting the initial value of “val” to
1 instead of 0. So, the behavior of this program is preserved. Doesn’t look
like erroneous behavior.

OK, my example is too simplified indeed. Please consider the following
instead:

int val;

static void attribute((constructor)) init_fn(void)
{
val++;
}

int main(int argc, char *argv)
{
return val;
}

With this, clang -Os -fno-common generates a global variable initialized
to 1 and discards init_fn().

Now, what happens if the executable is later linked against a shared
library which has its own constructor and sets “val” to some non-zero
value? I mean this for instance:

extern int val;

static void attribute((constructor)) so_init_fn(void)
{
val = 1;
}

I would expect the main program to return 2, not 1.

It seems to me that there is no ordering guarantee that your so_init_fn() would run before init_fn(), isn’t this a case of “static initialization order fiasco”?

> The global constructor was removed by setting the initial value of "val"
to
> 1 instead of 0. So, the behavior of this program is preserved. Doesn't
look
> like erroneous behavior.

OK, my example is too simplified indeed. Please consider the following
instead:

int val;

static void __attribute__((constructor)) init_fn(void)
{
        val++;
}

int main(int argc, char *argv)
{
        return val;
}

With this, clang -Os -fno-common generates a global variable initialized
to 1 and discards init_fn().

Now, what happens if the executable is later linked against a shared
library which has its own constructor and sets "val" to some non-zero
value? I mean this for instance:

extern int val;

static void __attribute__((constructor)) so_init_fn(void)
{
        val = 1;
}

I would expect the main program to return 2, not 1.

It seems to me that there is no ordering guarantee that your so_init_fn()
would run before init_fn(), isn't this a case of "static initialization
order fiasco"?
<static initialization order fiasco - Google Search>

I tend to agree that clang has a bug.

__attribute__((constructor)) is an extension to the C++ standard.
Even if C++ defined this and said this were an undefined behavior,
an implementation can augment the C++ standard by providing a definition
of an undefined behavior.

On ELF, the implementation uses Initialization and Termination
Functions, which are subject to
http://www.sco.com/developers/gabi/latest/ch5.dynamic.html#init_fini

"Before the initialization functions for any object A is called, the
initialization functions for any other objects that object A depends on
are called." The ld.so implementations use a post-order traversal of DT_NEEDED
dependencies.
I tend to think the intention of the function attribute
(Common Function Attributes (Using the GNU Compiler Collection (GCC)))
is to conform to the ELF specification. This is a property users can
reliably depend on.

In Jerome's example, it is guaranteed that the initialization function
in the shared object runs before the one in the main executable.
clang should not optimize out the initialization function to alter the
observed program behavior.

The global constructor was removed by setting the initial value of “val”
to
1 instead of 0. So, the behavior of this program is preserved. Doesn’t
look
like erroneous behavior.

OK, my example is too simplified indeed. Please consider the following
instead:

int val;

static void attribute((constructor)) init_fn(void)
{
val++;
}

int main(int argc, char *argv)
{
return val;
}

With this, clang -Os -fno-common generates a global variable initialized
to 1 and discards init_fn().

Now, what happens if the executable is later linked against a shared
library which has its own constructor and sets “val” to some non-zero
value? I mean this for instance:

extern int val;

static void attribute((constructor)) so_init_fn(void)
{
val = 1;
}

I would expect the main program to return 2, not 1.

It seems to me that there is no ordering guarantee that your so_init_fn()
would run before init_fn(), isn’t this a case of “static initialization
order fiasco”?
<https://www.google.com/search?client=safari&rls=en&q=static+initialization+order+fiasco&ie=UTF-8&oe=UTF-8>

I tend to agree that clang has a bug.

attribute((constructor)) is an extension to the C++ standard.
Even if C++ defined this and said this were an undefined behavior,
an implementation can augment the C++ standard by providing a definition
of an undefined behavior.

On ELF, the implementation uses Initialization and Termination
Functions, which are subject to
http://www.sco.com/developers/gabi/latest/ch5.dynamic.html#init_fini

“Before the initialization functions for any object A is called, the
initialization functions for any other objects that object A depends on
are called.”

In this case though the “object” does not depend on any other object here I believe. Even if it did, the behavior of the loader does not put constraint on the source/language semantics that the compiler operates with: it only puts guarantee on the output of the compiler.

The ld.so implementations use a post-order traversal of DT_NEEDED
dependencies.
I tend to think the intention of the function attribute
(https://gcc.gnu.org/onlinedocs/gcc-10.1.0/gcc/Common-Function-Attributes.html#Common-Function-Attributes)
is to conform to the ELF specification. This is a property users can
reliably depend on.

I don’t read it the same way: this doc to me indicates that it is an extension to C that provides the same behavior as a C++ global constructor. I don’t see here anything that put a restriction on the cross-object initialization order.
The fact that the platform (ELF or another) provides a restriction does not automatically translate to the higher-level.

In Jerome’s example, it is guaranteed that the initialization function
in the shared object runs before the one in the main executable.
clang should not optimize out the initialization function to alter the
observed program behavior.

I see it similarly to the different choice clang and gcc are making on the “interposible” property and assumptions, isn’t it? See: http://lists.llvm.org/pipermail/llvm-dev/2016-November/107625.html

Actually taking the example above, adding -fsemantic-interposition shows the difference: with the flag it behaves as you’d expect: https://godbolt.org/z/fC5yzY ; however not for the reason you’re arguing for.