Inclusion of iostream affects devirtualization in release_35

Hello,

I’ve stumbled upon the following behavior in branches/release_35 (as of 218689) under ubuntu 14.04 LTS amd64: whenever I include the (system-wise) gnu iostream header, clang++ stops devirtualizing the following code:

#if BREAKAGE_ENSUES
#include
#endif

struct Base {
virtual int foo() const = 0;
};

struct A : Base {
int a;

A(const int a)
: a(a) {
}

int foo() const {
return a;
}
};

struct B : Base {
const Base* b;

B(const Base* const base)
: b(base) {
}

int foo() const {
return b->foo();
}
};

const A a(42);
const B b(&a);

int main(int, char**) {
return b.foo();
}

Is that a known issue, and if not, under what category should I file a ticket for it?

Best regards,
Martin

This isn’t really devirtualization so much as global opt plus basic constant propagation. My theory is that iostream injects static initializers for std::cout / std::cerr into your TU. LLVM’s global opt pass will fail when those are present.

This isn't really devirtualization so much as global opt plus basic
constant propagation. My theory is that iostream injects static
initializers for std::cout / std::cerr into your TU. LLVM's global opt pass
will fail when those are present.

Yikes, that's a limitation right there isn't it. Should we have a PR or is
there nothing for the optimizer can do?

This isn't really devirtualization so much as global opt plus basic constant propagation. My theory is that iostream injects static initializers for std::cout / std::cerr into your TU. LLVM's global opt pass will fail when those are present.

I just verified your theory - it's correct. The same effect is
exhibited by the following version of the original code:

#include <stdio.h>

struct Base {
    virtual int foo() const = 0;
};

struct A : Base {
    int a;

    A(const int a)
    : a(a) {
    }

    int foo() const {
        return a;
    }
};

struct B : Base {
    const Base* b;

    B(const Base* const base)
    : b(base) {
    }

    int foo() const {
        return b->foo();
    }
};

struct C {
    C() {
#if BREAKAGE_ENSUES
       printf("");

#endif
    }
};

const A a(42);
const B b(&a);
const C c;

int main(int, char**) {
    return b.foo();
}

To get the (un)desired effect, it suffices to have a static
initializer with an (implied) side effect (e.g. a call external to the
TU). In the case of iostream - their introduce a static (__ioinit)
with a ctor external to our TU.

Yikes, that's a limitation right there isn't it. Should we have a PR or is there nothing for the optimizer can do?

IMHO, it's an issue that might warrant at least some feedback to the
developer upon detection. Some warning along the lines of 'Global opt
pass is disabled due to such-and-such static initializer with side
effects.' After all it is easy for a TU to unintentionally 'catch' a
wrong include.

Best regards,
Martin