From patchwork Sun Oct 5 16:34:49 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jan Hubicka X-Patchwork-Id: 396665 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from sourceware.org (server1.sourceware.org [209.132.180.131]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id ED6C314008C for ; Mon, 6 Oct 2014 03:35:04 +1100 (EST) DomainKey-Signature: a=rsa-sha1; c=nofws; d=gcc.gnu.org; h=list-id :list-unsubscribe:list-archive:list-post:list-help:sender:date :from:to:subject:message-id:mime-version:content-type; q=dns; s= default; b=s9BnLzJCjf1HlRU5op1DAX+RxWYfTC7ctnR8IA9K4dUa98oFxYl2q FLPYRPDRly8+j2P1qVRXVS2WD5MfR5+F85eEz1oK8AxvVZDndkKFbRcZTwD0ZfvE cy7lgPmNwyyskblL3iaCZ3C1pNIec+rBlIieh/ddpQnK4xzJTZDwZQ= DKIM-Signature: v=1; a=rsa-sha1; c=relaxed; d=gcc.gnu.org; h=list-id :list-unsubscribe:list-archive:list-post:list-help:sender:date :from:to:subject:message-id:mime-version:content-type; s= default; bh=dRUop+MImID0h9f+FeY7OnknY+0=; b=X1oRYkiygPVx7bp9LiIW B/+Az3H6vkavJdHN30iht2fHa8fg8+ywNLyFfUbwVY8zRx6NGSRpDOFkkD/TXyrg 8YqH5lVWAcsH6IxgM+hhp3KLBkLc2UZ1VsVs9aj02yaUWDP3s3EtQTSNGqjyeGzA ZEVaqIzG6ZxKqM9x8OTxV6I= Received: (qmail 19225 invoked by alias); 5 Oct 2014 16:34:57 -0000 Mailing-List: contact gcc-patches-help@gcc.gnu.org; run by ezmlm Precedence: bulk List-Id: List-Unsubscribe: List-Archive: List-Post: List-Help: Sender: gcc-patches-owner@gcc.gnu.org Delivered-To: mailing list gcc-patches@gcc.gnu.org Received: (qmail 19216 invoked by uid 89); 5 Oct 2014 16:34:56 -0000 Authentication-Results: sourceware.org; auth=none X-Virus-Found: No X-Spam-SWARE-Status: No, score=-2.1 required=5.0 tests=AWL, BAYES_00, T_RP_MATCHES_RCVD autolearn=ham version=3.3.2 X-HELO: nikam.ms.mff.cuni.cz Received: from nikam.ms.mff.cuni.cz (HELO nikam.ms.mff.cuni.cz) (195.113.20.16) by sourceware.org (qpsmtpd/0.93/v0.84-503-g423c35a) with (AES256-GCM-SHA384 encrypted) ESMTPS; Sun, 05 Oct 2014 16:34:53 +0000 Received: by nikam.ms.mff.cuni.cz (Postfix, from userid 16202) id 3C243542B34; Sun, 5 Oct 2014 18:34:49 +0200 (CEST) Date: Sun, 5 Oct 2014 18:34:49 +0200 From: Jan Hubicka To: gcc-patches@gcc.gnu.org Subject: Track dynamic type changes Message-ID: <20141005163449.GA31829@kam.mff.cuni.cz> MIME-Version: 1.0 Content-Disposition: inline User-Agent: Mutt/1.5.21 (2010-09-15) Hi, this patch makes ipa_polymorphic_call_context::get_dynamic_type to track if vptr has changed in between function entry and polymorphic call. The patch also add logic skipping if (a==0) checks in multiple inheritance so I can construct testcase more easily. I got my SVN tree into an inconsistent state yesterday by stopping in middle of commit. The patch went in in two parts, so i am attaching both changes. Quite few of devirt-4*.C was broken overnight, I apologize for that. Honza * ipa-polymorphic-call.c (walk_ssa_copies): Recognize NULL pointer checks. (ipa_polymorphic_call_context::get_dynamic_type): Return true if type doesn't change. * cgraph.h (cgraph_indirect_call_info): New flag. * cgraph.c (cgraph_node::create_indirect_edge): Initialize it. (cgraph_node::dump): Dump it. * ipa-prop.c (ipa_analyze_call_uses): Ignore return valud of context.get_dynamic_type. (ipa_make_edge_direct_to_target): Do not speculate edge that is already speuclative. (try_make_edge_direct_virtual_call): Use VPTR_CHANGED; Do not speculate to __builtin_unreachable (ipa_write_indirect_edge_info, ipa_read_indirect_edge_info): Stream vptr_changed. * ipa-cp.c (ipa_get_indirect_edge_target_1): Use vptr_changed. * g++.dg/ipa/devirt-47.C: New testcase. * ipa-polymorphic-call.c (possible_placement_new): Fix condition on size. (ipa_polymorphic_call_context::restrict_to_inner_type): Do not walk into vptr pointer. (ipa_polymorphic_call_context::dump): Fix formating. * ipa-prop.c (ipa_analyze_call_uses): Compute vptr_changed. * g++.dg/ipa/devirt-42.C: Update template. * g++.dg/ipa/devirt-44.C: Update template. * g++.dg/ipa/devirt-45.C: Update template. * g++.dg/ipa/devirt-46.C: Update template. * g++.dg/ipa/devirt-47.C: Update template. * g++.dg/ipa/devirt-48.C: New testcase. Index: ipa-polymorphic-call.c =================================================================== --- ipa-polymorphic-call.c (revision 215890) +++ ipa-polymorphic-call.c (working copy) @@ -760,11 +760,37 @@ walk_ssa_copies (tree op) while (TREE_CODE (op) == SSA_NAME&& !SSA_NAME_IS_DEFAULT_DEF (op) && SSA_NAME_DEF_STMT (op) - && gimple_assign_single_p (SSA_NAME_DEF_STMT (op))) + && (gimple_assign_single_p (SSA_NAME_DEF_STMT (op)) + || gimple_code (SSA_NAME_DEF_STMT (op)) == GIMPLE_PHI)) { - if (gimple_assign_load_p (SSA_NAME_DEF_STMT (op))) - return op; - op = gimple_assign_rhs1 (SSA_NAME_DEF_STMT (op)); + /* Special case + if (ptr == 0) + ptr = 0; + else + ptr = ptr.foo; + This pattern is implicitly produced for casts to non-primary + bases. When doing context analysis, we do not really care + about the case pointer is NULL, becuase the call will be + undefined anyway. */ + if (gimple_code (SSA_NAME_DEF_STMT (op)) == GIMPLE_PHI) + { + gimple phi = SSA_NAME_DEF_STMT (op); + + if (gimple_phi_num_args (phi) != 2) + return op; + if (integer_zerop (gimple_phi_arg_def (phi, 0))) + op = gimple_phi_arg_def (phi, 1); + else if (integer_zerop (gimple_phi_arg_def (phi, 1))) + op = gimple_phi_arg_def (phi, 0); + else + return op; + } + else + { + if (gimple_assign_load_p (SSA_NAME_DEF_STMT (op))) + return op; + op = gimple_assign_rhs1 (SSA_NAME_DEF_STMT (op)); + } STRIP_NOPS (op); } return op; @@ -1371,6 +1397,8 @@ check_stmt_for_type_change (ao_ref *ao A is set), try to walk memory writes and find the actual construction of the instance. + Return true if memory is unchanged from function entry. + We do not include this analysis in the context analysis itself, because it needs memory SSA to be fully built and the walk may be expensive. So it is not suitable for use withing fold_stmt and similar uses. */ @@ -1615,7 +1643,7 @@ ipa_polymorphic_call_context::get_dynami function_entry_reached ? " (multiple types encountered)" : ""); } - return true; + return false; } /* See if speculation given by SPEC_OUTER_TYPE, SPEC_OFFSET and SPEC_MAYBE_DERIVED_TYPE Index: cgraph.h =================================================================== --- cgraph.h (revision 215890) +++ cgraph.h (working copy) @@ -1393,6 +1393,9 @@ struct GTY(()) cgraph_indirect_call_info /* When the previous bit is set, this one determines whether the destination is loaded from a parameter passed by reference. */ unsigned by_ref : 1; + /* For polymorphic calls this specify whether the virtual table pointer + may have changed in between function entry and the call. */ + unsigned vptr_changed : 1; }; struct GTY((chain_next ("%h.next_caller"), chain_prev ("%h.prev_caller"))) cgraph_edge { Index: cgraph.c =================================================================== --- cgraph.c (revision 215890) +++ cgraph.c (working copy) @@ -883,6 +883,7 @@ cgraph_node::create_indirect_edge (gimpl edge->indirect_info = cgraph_allocate_init_indirect_info (); edge->indirect_info->ecf_flags = ecf_flags; + edge->indirect_info->vptr_changed = true; /* Record polymorphic call info. */ if (compute_indirect_info @@ -1988,6 +1989,8 @@ cgraph_node::dump (FILE *f) edge->indirect_info->member_ptr ? "member ptr" : "aggregate", edge->indirect_info->by_ref ? "passed by reference":"", (int)edge->indirect_info->offset); + if (edge->indirect_info->vptr_changed) + fprintf (f, " (vptr maybe changed)"); } fprintf (f, "\n"); if (edge->indirect_info->polymorphic) Index: ipa-prop.c =================================================================== --- ipa-prop.c (revision 215890) +++ ipa-prop.c (working copy) @@ -2371,10 +2371,10 @@ ipa_analyze_call_uses (struct func_body_ gcc_checking_assert (cs->indirect_info->otr_token == tree_to_shwi (OBJ_TYPE_REF_TOKEN (target))); - if (context.get_dynamic_type (instance, - OBJ_TYPE_REF_OBJECT (target), - obj_type_ref_class (target), call)) - cs->indirect_info->context = context; + context.get_dynamic_type (instance, + OBJ_TYPE_REF_OBJECT (target), + obj_type_ref_class (target), call); + cs->indirect_info->context = context; } if (TREE_CODE (target) == SSA_NAME) @@ -2878,6 +2878,38 @@ ipa_make_edge_direct_to_target (struct c callee = cgraph_node::get_create (target); } + /* If the edge is already speculated. */ + if (speculative && ie->speculative) + { + struct cgraph_edge *e2; + struct ipa_ref *ref; + ie->speculative_call_info (e2, ie, ref); + if (e2->callee->ultimate_alias_target () + != callee->ultimate_alias_target ()) + { + if (dump_file) + fprintf (dump_file, "ipa-prop: Discovered call to a speculative target " + "(%s/%i -> %s/%i) but the call is already speculated to %s/%i. Giving up.\n", + xstrdup (ie->caller->name ()), + ie->caller->order, + xstrdup (callee->name ()), + callee->order, + xstrdup (e2->callee->name ()), + e2->callee->order); + } + else + { + if (dump_file) + fprintf (dump_file, "ipa-prop: Discovered call to a speculative target " + "(%s/%i -> %s/%i) this agree with previous speculation.\n", + xstrdup (ie->caller->name ()), + ie->caller->order, + xstrdup (callee->name ()), + callee->order); + } + return NULL; + } + if (!dbg_cnt (devirt)) return NULL; @@ -3127,17 +3159,17 @@ try_make_edge_direct_virtual_call (struc ctx.offset_by (ie->indirect_info->offset); - /* TODO: We want to record if type change happens. - Old code did not do that that seems like a bug. */ - ctx.possible_dynamic_type_change (ie->in_polymorphic_cdtor, - ie->indirect_info->otr_type); + if (ie->indirect_info->vptr_changed) + ctx.possible_dynamic_type_change (ie->in_polymorphic_cdtor, + ie->indirect_info->otr_type); updated = ie->indirect_info->context.combine_with (ctx, ie->indirect_info->otr_type); } /* Try to do lookup via known virtual table pointer value. */ - if (!ie->indirect_info->by_ref) + if (!ie->indirect_info->by_ref + && (!ie->indirect_info->vptr_changed || flag_devirtualize_speculatively)) { tree vtable; unsigned HOST_WIDE_INT offset; @@ -3146,16 +3178,24 @@ try_make_edge_direct_virtual_call (struc true); if (t && vtable_pointer_value_to_vtable (t, &vtable, &offset)) { - target = gimple_get_virt_method_for_vtable (ie->indirect_info->otr_token, + t = gimple_get_virt_method_for_vtable (ie->indirect_info->otr_token, vtable, offset); - if (target) + if (t) { - if ((TREE_CODE (TREE_TYPE (target)) == FUNCTION_TYPE - && DECL_FUNCTION_CODE (target) == BUILT_IN_UNREACHABLE) + if ((TREE_CODE (TREE_TYPE (t)) == FUNCTION_TYPE + && DECL_FUNCTION_CODE (t) == BUILT_IN_UNREACHABLE) || !possible_polymorphic_call_target_p - (ie, cgraph_node::get (target))) - target = ipa_impossible_devirt_target (ie, target); - return ipa_make_edge_direct_to_target (ie, target); + (ie, cgraph_node::get (t))) + { + /* Do not speculate builtin_unreachable, it is stpid! */ + if (!ie->indirect_info->vptr_changed) + target = ipa_impossible_devirt_target (ie, target); + } + else + { + target = t; + speculative = ie->indirect_info->vptr_changed; + } } } } @@ -3188,7 +3228,7 @@ try_make_edge_direct_virtual_call (struc else target = ipa_impossible_devirt_target (ie, NULL_TREE); } - else if (flag_devirtualize_speculatively + else if (!target && flag_devirtualize_speculatively && !ie->speculative && ie->maybe_hot_p ()) { cgraph_node *n = try_speculative_devirtualization (ie->indirect_info->otr_type, @@ -3222,7 +3264,11 @@ try_make_edge_direct_virtual_call (struc if (target) { if (!possible_polymorphic_call_target_p (ie, cgraph_node::get_create (target))) - target = ipa_impossible_devirt_target (ie, target); + { + if (!speculative) + return NULL; + target = ipa_impossible_devirt_target (ie, target); + } return ipa_make_edge_direct_to_target (ie, target, speculative); } else @@ -4801,6 +4847,7 @@ ipa_write_indirect_edge_info (struct out bp_pack_value (&bp, ii->agg_contents, 1); bp_pack_value (&bp, ii->member_ptr, 1); bp_pack_value (&bp, ii->by_ref, 1); + bp_pack_value (&bp, ii->vptr_changed, 1); streamer_write_bitpack (&bp); if (ii->agg_contents || ii->polymorphic) streamer_write_hwi (ob, ii->offset); @@ -4832,6 +4879,7 @@ ipa_read_indirect_edge_info (struct lto_ ii->agg_contents = bp_unpack_value (&bp, 1); ii->member_ptr = bp_unpack_value (&bp, 1); ii->by_ref = bp_unpack_value (&bp, 1); + ii->vptr_changed = bp_unpack_value (&bp, 1); if (ii->agg_contents || ii->polymorphic) ii->offset = (HOST_WIDE_INT) streamer_read_hwi (ib); else Index: ipa-cp.c =================================================================== --- ipa-cp.c (revision 215890) +++ ipa-cp.c (working copy) @@ -1560,7 +1560,8 @@ ipa_get_indirect_edge_target_1 (struct c t = NULL; /* Try to work out value of virtual table pointer value in replacemnets. */ - if (!t && agg_reps && !ie->indirect_info->by_ref) + if (!t && agg_reps && !ie->indirect_info->by_ref + && !ie->indirect_info->vptr_changed) { while (agg_reps) { @@ -1578,7 +1579,8 @@ ipa_get_indirect_edge_target_1 (struct c /* Try to work out value of virtual table pointer value in known aggregate values. */ if (!t && known_aggs.length () > (unsigned int) param_index - && !ie->indirect_info->by_ref) + && !ie->indirect_info->by_ref + && !ie->indirect_info->vptr_changed) { struct ipa_agg_jump_function *agg; agg = known_aggs[param_index]; Index: testsuite/g++.dg/ipa/devirt-47.C =================================================================== --- testsuite/g++.dg/ipa/devirt-47.C (revision 0) +++ testsuite/g++.dg/ipa/devirt-47.C (revision 0) @@ -0,0 +1,31 @@ +/* { 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;} +}; +struct B { + virtual int bar(){return 4;} +}; +struct C:B,A { + virtual int foo(){return 2;} +}; +static void +test (struct A *a) +{ + if (a->foo() != 2) + __builtin_abort (); +} +int +m() +{ + struct A *a = new C; + test (a); + return 0; +} + +/* { dg-final { scan-ipa-dump-times "Discovered a virtual call to a speculative target\[^\\n\]*C::_ZTh" 1 "inline" } } */ +/* { dg-final { scan-ipa-dump-not "OBJ_TYPE_REF" "optimized" } } */ +/* FIXME: We ought to inline thunk. */ +/* { dg-final { scan-ipa-dump "C::_ZThn" "optimized" } } */ +/* { dg-final { cleanup-ipa-dump "inline" } } */ +/* { dg-final { cleanup-ipa-dump "optimized" } } */ Index: ipa-polymorphic-call.c =================================================================== --- ipa-polymorphic-call.c (revision 215901) +++ ipa-polymorphic-call.c (working copy) @@ -97,7 +97,7 @@ possible_placement_new (tree type, tree || !tree_fits_shwi_p (TYPE_SIZE (type)) || (cur_offset + (expected_type ? tree_to_uhwi (TYPE_SIZE (expected_type)) - : 1) + : GET_MODE_BITSIZE (Pmode)) <= tree_to_uhwi (TYPE_SIZE (type))))); } @@ -278,6 +278,14 @@ ipa_polymorphic_call_context::restrict_t pos = int_bit_position (fld); if (pos > (unsigned HOST_WIDE_INT)cur_offset) continue; + + /* Do not consider vptr itself. Not even for placement new. */ + if (!pos && DECL_ARTIFICIAL (fld) + && POINTER_TYPE_P (TREE_TYPE (fld)) + && TYPE_BINFO (type) + && polymorphic_type_binfo_p (TYPE_BINFO (type))) + continue; + if (!DECL_SIZE (fld) || !tree_fits_uhwi_p (DECL_SIZE (fld))) goto no_useful_type_info; size = tree_to_uhwi (DECL_SIZE (fld)); @@ -583,7 +591,7 @@ ipa_polymorphic_call_context::dump (FILE { fprintf (f, " "); if (invalid) - fprintf (f, "Call is known to be undefined\n"); + fprintf (f, "Call is known to be undefined"); else { if (useless_p ()) Index: testsuite/g++.dg/ipa/devirt-42.C =================================================================== --- testsuite/g++.dg/ipa/devirt-42.C (revision 215901) +++ testsuite/g++.dg/ipa/devirt-42.C (working copy) @@ -26,13 +26,9 @@ main() /* 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" } } */ + namespace type and therefore we know it has no derivations. */ +/* { dg-final { scan-ipa-dump-times "First type is base of second" 3 "inline" } } */ +/* { dg-final { scan-ipa-dump-times "Discovered a virtual call to a known target" 2 "inline" } } */ /* Verify that speculation is optimized by late optimizers. */ /* { dg-final { scan-ipa-dump-times "return 2" 2 "optimized" } } */ Index: testsuite/g++.dg/ipa/devirt-44.C =================================================================== --- testsuite/g++.dg/ipa/devirt-44.C (revision 215901) +++ testsuite/g++.dg/ipa/devirt-44.C (working copy) @@ -27,7 +27,6 @@ main() 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-46.C =================================================================== --- testsuite/g++.dg/ipa/devirt-46.C (revision 215901) +++ testsuite/g++.dg/ipa/devirt-46.C (working copy) @@ -20,7 +20,7 @@ m() return 0; } -/* { dg-final { scan-ipa-dump-times "Discovered a virtual call to a speculative target\[^\\n\]*B::foo" 1 "inline" } } */ +/* { dg-final { scan-ipa-dump-times "Discovered a virtual call to a known target\[^\\n\]*B::foo" 1 "inline" } } */ /* { dg-final { scan-ipa-dump-not "OBJ_TYPE_REF" "optimized" } } */ /* { dg-final { scan-ipa-dump-not "abort" "optimized" } } */ /* { dg-final { cleanup-ipa-dump "inline" } } */ Index: testsuite/g++.dg/ipa/devirt-48.C =================================================================== --- testsuite/g++.dg/ipa/devirt-48.C (revision 0) +++ testsuite/g++.dg/ipa/devirt-48.C (revision 0) @@ -0,0 +1,32 @@ +/* { dg-do compile } */ +/* { dg-options "-O3 -fno-ipa-cp -fdump-ipa-inline-details -fno-early-inlining" } */ +struct A { + virtual int foo(){return 1;} +}; +struct B:A { + virtual int foo(){return 2;} + int callfoo(){foo();} +}; +struct C:A { + virtual int foo(){return 3;} +}; +struct D:B { + virtual int foo(){return 4;} + int callfoo(){foo();} +}; +static void +test (struct A *a) +{ + if (a->foo() != 2) + __builtin_abort (); +} +int +m() +{ + struct A *a = new C; + static_cast(a)->callfoo(); + return 0; +} + +/* { dg-final { scan-ipa-dump-times "Discovered a virtual call to a known target\[^\\n\]*__builtin_unreachable" 1 "inline" } } */ +/* { dg-final { cleanup-ipa-dump "inline" } } */ Index: testsuite/g++.dg/ipa/devirt-45.C =================================================================== --- testsuite/g++.dg/ipa/devirt-45.C (revision 215901) +++ testsuite/g++.dg/ipa/devirt-45.C (working copy) @@ -37,7 +37,6 @@ main() } /* 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: testsuite/g++.dg/ipa/devirt-47.C =================================================================== --- testsuite/g++.dg/ipa/devirt-47.C (revision 215901) +++ testsuite/g++.dg/ipa/devirt-47.C (working copy) @@ -23,7 +23,7 @@ m() return 0; } -/* { dg-final { scan-ipa-dump-times "Discovered a virtual call to a speculative target\[^\\n\]*C::_ZTh" 1 "inline" } } */ +/* { dg-final { scan-ipa-dump-times "Discovered a virtual call to a known target\[^\\n\]*C::_ZTh" 1 "inline" } } */ /* { dg-final { scan-ipa-dump-not "OBJ_TYPE_REF" "optimized" } } */ /* FIXME: We ought to inline thunk. */ /* { dg-final { scan-ipa-dump "C::_ZThn" "optimized" } } */ Index: ipa-prop.c =================================================================== --- ipa-prop.c (revision 215901) +++ ipa-prop.c (working copy) @@ -2371,9 +2371,10 @@ ipa_analyze_call_uses (struct func_body_ gcc_checking_assert (cs->indirect_info->otr_token == tree_to_shwi (OBJ_TYPE_REF_TOKEN (target))); - context.get_dynamic_type (instance, - OBJ_TYPE_REF_OBJECT (target), - obj_type_ref_class (target), call); + cs->indirect_info->vptr_changed + = !context.get_dynamic_type (instance, + OBJ_TYPE_REF_OBJECT (target), + obj_type_ref_class (target), call); cs->indirect_info->context = context; }