Hi, this patch add basic testcaes for new devirtualization features (more to come) and also fixes bug I introduced while refactoring the speculative context merging code - that most likely explains why I observed number of speculative devirt to drop considerably after finishing the patch.
Jason, can you, please, double check the second FIXME in testcases? Will commit it once bootstrap/regtest of x86_64-linux finishes. * testsuite/g++.dg/ipa/devirt-42.C: New testcase. * testsuite/g++.dg/ipa/devirt-43.C: New testcase. * testsuite/g++.dg/ipa/devirt-44.C: New testcase. * testsuite/g++.dg/ipa/devirt-45.C: New testcase. * ipa-polymorphic-call.c (ipa_polymorphic_call_context::ipa_polymorphic_call_context): Fix code determining speculative type. (ipa_polymorphic_call_context::combine_with): Fix speculation merge. Index: testsuite/g++.dg/ipa/devirt-42.C =================================================================== --- testsuite/g++.dg/ipa/devirt-42.C (revision 0) +++ testsuite/g++.dg/ipa/devirt-42.C (revision 0) @@ -0,0 +1,42 @@ +/* { dg-do compile } */ +/* { dg-options "-O3 -fno-ipa-cp -fdump-ipa-inline-details -fno-early-inlining -fdump-tree-optimized" } */ +struct A { + virtual int foo () {return 1;} + int bar () {return foo();} + int barbar (); +}; +namespace { + struct B:A {virtual int foo () {return 2;} + int barbar () {return bar();}}; +} + +int +A::barbar() +{ + return static_cast<B*>(this)->barbar(); +} + +main() +{ + struct B b; + struct A *a = &b; + return a->barbar (); +} + +/* Inlining everything into main makes type clear from type of variable b. + However devirtualization is also possible for offline copy of A::barbar. Invoking + B's barbar makes it clear the type is at least B and B is an anonymous + namespace type and therefore we know it has no derivations. + FIXME: Currently we devirtualize speculatively only because we do not track + dynamic type changes well. */ +/* { dg-final { scan-ipa-dump-times "First type is base of second" 1 "inline" } } */ +/* { dg-final { scan-ipa-dump-times "Outer types match, merging flags" 2 "inline" } } */ +/* { dg-final { scan-ipa-dump-times "Discovered a virtual call to a known target" 1 "inline" } } */ +/* { dg-final { scan-ipa-dump-times "Discovered a virtual call to a speculative target" 1 "inline" } } */ + +/* Verify that speculation is optimized by late optimizers. */ +/* { dg-final { scan-ipa-dump-times "return 2" 2 "optimized" } } */ +/* { dg-final { scan-ipa-dump-not "OBJ_TYPE_REF" "optimized" } } */ + +/* { dg-final { cleanup-ipa-dump "inline" } } */ +/* { dg-final { cleanup-ipa-dump "optimized" } } */ Index: testsuite/g++.dg/ipa/devirt-44.C =================================================================== --- testsuite/g++.dg/ipa/devirt-44.C (revision 0) +++ testsuite/g++.dg/ipa/devirt-44.C (revision 0) @@ -0,0 +1,33 @@ +/* { dg-do compile } */ +/* { dg-options "-O3 -fno-ipa-cp -fdump-ipa-inline-details -fno-early-inlining -fdump-tree-optimized" } */ +struct A { + virtual int foo () {return 1;} + int wrapfoo () {foo();} + A() {wrapfoo();} +}; +struct B:A {virtual int foo () {return 2;}}; + +void dostuff(struct A *); + +static void +test (struct A *a) +{ + dostuff (a); + if (a->foo ()!= 2) + __builtin_abort (); +} + +main() +{ + struct B a; + dostuff (&a); + test (&a); +} +/* Here one invocation of foo is while type is in construction, while other is not. + Check that we handle that. */ + +/* { dg-final { scan-ipa-dump-times "First type is base of second" 1 "inline" } } */ +/* { dg-final { scan-ipa-dump "(maybe in construction)" "inline" } } */ +/* { dg-final { scan-ipa-dump-times "Discovered a virtual call to a known target\[^\\n\]*A::foo" 1 "inline" } } */ +/* { dg-final { scan-ipa-dump-times "Discovered a virtual call to a known target\[^\\n\]*B::foo" 1 "inline" } } */ +/* { dg-final { cleanup-ipa-dump "inline" } } */ Index: testsuite/g++.dg/ipa/devirt-41.C =================================================================== --- testsuite/g++.dg/ipa/devirt-41.C (revision 0) +++ testsuite/g++.dg/ipa/devirt-41.C (revision 0) @@ -0,0 +1,31 @@ +/* { dg-do compile } */ +/* { dg-options "-O3 -fdump-ipa-inline-details -fno-early-inlining -fno-ipa-cp" } */ +struct A {virtual int foo () {return 1;}}; +struct B:A {virtual int foo () {return 2;}}; + +void dostuff(struct A *); + +static void +test (struct A *a) +{ + dostuff (a); + if (a->foo ()!= 2) + __builtin_abort (); +} + +main() +{ + struct B a; + dostuff (&a); + test (&a); +} +/* Inlining of dostuff into main should combine polymorphic context + specifying Outer type:struct B offset 0 + with Outer type (dynamic):struct A (or a derived type) offset 0 + and enable devirtualization. + + Because the type is in static storage, we know it won't change type in dostuff + and from callstack we can tell that is is not in construction/destruction. */ +/* { dg-final { scan-ipa-dump-times "First type is base of second" 1 "inline" } } */ +/* { dg-final { scan-ipa-dump-times "Discovered a virtual call to a known target" 1 "inline" } } */ +/* { dg-final { cleanup-ipa-dump "inline" } } */ Index: testsuite/g++.dg/ipa/devirt-43.C =================================================================== --- testsuite/g++.dg/ipa/devirt-43.C (revision 0) +++ testsuite/g++.dg/ipa/devirt-43.C (revision 0) @@ -0,0 +1,27 @@ +/* { dg-do compile } */ +/* { dg-options "-O3 -fdump-ipa-inline-details -fno-ipa-cp -fno-early-inlining" } */ +struct A {virtual int foo () {return 1;}}; +struct B {int i; struct A a;}; +struct C:A {virtual int foo () {return 2;}}; + +void dostuff(struct A *); + +static void +test (struct A *a) +{ + dostuff (a); + if (a->foo ()!= 2) + __builtin_abort (); +} + +void +t(struct B *b) +{ + test(&b->a); +} +/* Here b comes externally, but we take speculative hint from type of the pointer that it is + of type B. This makes A fully specified and we know C::foo is unlikely. + FIXME: We could most probably can devirtualize unconditonally because dereference of b in + &b->a makes the type known. GIMPLE does not represent this. */ +/* { dg-final { scan-ipa-dump-times "Discovered a virtual call to a speculative target" 1 "inline" } } */ +/* { dg-final { cleanup-ipa-dump "inline" } } */ Index: testsuite/g++.dg/ipa/devirt-45.C =================================================================== --- testsuite/g++.dg/ipa/devirt-45.C (revision 0) +++ testsuite/g++.dg/ipa/devirt-45.C (revision 0) @@ -0,0 +1,43 @@ +/* { dg-do compile } */ +/* { dg-options "-O3 -fno-ipa-cp -fdump-ipa-inline-details -fno-early-inlining -fdump-tree-optimized" } */ +struct A { + virtual int foo () {return 1;} + int wrapfoo () {foo();} + A() {wrapfoo();} +}; +inline void* operator new(__SIZE_TYPE__ s, void* buf) throw() { + return buf; +} +struct B:A {virtual int foo () {return 2;}}; + +void dostuff(struct A *); + +static void +test2 (struct A *a) +{ + dostuff (a); + if (a->foo ()!= 2) + __builtin_abort (); +} + +static void +test (struct A *a) +{ + dostuff (a); + static_cast<B*>(a)->~B(); + new(a) B(); + test2(a); +} + +main() +{ + struct B a; + dostuff (&a); + test (&a); +} + +/* One invocation is A::foo () other is B::foo () even though the type is destroyed and rebuilt in test() */ +/* { dg-final { scan-ipa-dump "(maybe in construction)" "inline" } } */ +/* { dg-final { scan-ipa-dump-times "Discovered a virtual call to a known target\[^\\n\]*A::foo" 1 "inline" } } */ +/* { dg-final { scan-ipa-dump-times "Discovered a virtual call to a known target\[^\\n\]*B::foo" 1 "inline" } } */ +/* { dg-final { cleanup-ipa-dump "inline" } } */ Index: ipa-polymorphic-call.c =================================================================== --- ipa-polymorphic-call.c (revision 215876) +++ ipa-polymorphic-call.c (working copy) @@ -820,8 +820,7 @@ ipa_polymorphic_call_context::ipa_polymo &offset2, &size, &max_size); if (max_size != -1 && max_size == size) - combine_speculation_with (TYPE_MAIN_VARIANT - (TREE_TYPE (TREE_TYPE (base_pointer))), + combine_speculation_with (TYPE_MAIN_VARIANT (TREE_TYPE (base)), offset + offset2, true, NULL /* Do not change outer type. */); @@ -1970,7 +1969,7 @@ ipa_polymorphic_call_context::combine_wi updated |= combine_speculation_with (ctx.speculative_outer_type, ctx.speculative_offset, - ctx.maybe_in_construction, + ctx.speculative_maybe_derived_type, otr_type); if (updated && dump_file && (dump_flags & TDF_DETAILS))