Patch below fixes a miscompilation observed whem building uclibc libpthread on
a mips-linux system.
The story start with the ipa-split optimization, which turns:
void fn()
{
if (cond) {
DO_STUFF;
}
}
into:
static void fn_helper()
{
DO_STUFF;
}
void fn()
{
if (cond)
fn_helper();
}
The idea is that the new fn() wrapper is a good candidate for inlining,
whereas the original fn is not.
The optimization uses cgraph function versioning. The problem is that when we
clone the cgraph node we propagate the DECL_STATIC_CONSTRUCTOR bit. Thus both
fn() and fn_helper() get called on startup.
When fn happens to be pthread_initialize we end up calling both the original
and a clone with the have-I-already-done-this check removed. Much
badness ensues.
Patch below fixes this by clearing the DECL_STATIC_{CON,DES}TRUCTOR bit when
cloning a cgraph node - there's already logic to make sure we keep the
original. My guess is this bug is probably latent in other IPA passes.
Tested on mips-linux and bootstrap+test x86_64-linux
Ok?
Paul
2011-10-27 Paul Brook <[email protected]>
gcc/
* cgraphunit.c: Don't mark clones as static constructors.
gcc/testsuite/
* gcc.dg/constructor-1.c: New test.
Index: gcc/cgraphunit.c
===================================================================
--- gcc/cgraphunit.c (revision 180439)
+++ gcc/cgraphunit.c (working copy)
@@ -2386,6 +2386,8 @@ cgraph_function_versioning (struct cgrap
new_version_node->local.externally_visible = 0;
new_version_node->local.local = 1;
new_version_node->lowered = true;
+ DECL_STATIC_CONSTRUCTOR(new_version_node->decl) = 0;
+ DECL_STATIC_DESTRUCTOR(new_version_node->decl) = 0;
/* Update the call_expr on the edges to call the new version node. */
update_call_expr (new_version_node);
Index: gcc/testsuite/gcc.dg/constructor-1.c
===================================================================
--- gcc/testsuite/gcc.dg/constructor-1.c (revision 0)
+++ gcc/testsuite/gcc.dg/constructor-1.c (revision 0)
@@ -0,0 +1,37 @@
+/* { dg-do run } */
+/* { dg-options "-O2" } */
+
+/* The ipa-split pass pulls the body of the if(!x) block
+ into a separate function to make foo a better inlining
+ candidate. Make sure this new function isn't also run
+ as a static constructor. */
+
+#include <stdlib.h>
+
+int x, y;
+
+void __attribute__((noinline))
+bar(void)
+{
+ y++;
+}
+
+void __attribute__((constructor))
+foo(void)
+{
+ if (!x)
+ {
+ bar();
+ y++;
+ }
+}
+
+int main()
+{
+ x = 1;
+ foo();
+ foo();
+ if (y != 2)
+ abort();
+ exit(0);
+}