On Sun, Jun 14, 2020 at 12:18 PM Fangrui Song maskray@google.com wrote:
On 2020-06-12, Mehdi AMINI via llvm-dev wrote:
On Fri, Jun 12, 2020 at 1:05 AM Jerome Forissier via llvm-dev < llvm-dev@lists.llvm.org> wrote:
On 6/11/20 11:25 PM, James Y Knight wrote:
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+initiali...
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.htm... ) 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.