Hi, this patch fixes polymorphic call analysis in thunks. Unlike normal methods, thunks take THIS pointer offsetted by a known constant. This needs t be compensated for when calculating address of outer type.
Bootstrapped/regtested x86_64-linux, also tested with Firefox where this bug trigger misoptimization in spellchecker. I plan to backport it to release branches soon. Honza PR ipa/88561 * ipa-polymorphic-call.c (ipa_polymorphic_call_context::ipa_polymorphic_call_context): Handle arguments of thunks correctly. (ipa_polymorphic_call_context::get_dynamic_context): Be ready for NULL instance pinter. * lto-cgraph.c (lto_output_node): Always stream thunk info. * g++.dg/tree-prof/devirt.C: New testcase. Index: ipa-polymorphic-call.c =================================================================== --- ipa-polymorphic-call.c (revision 267325) +++ ipa-polymorphic-call.c (working copy) @@ -995,9 +995,22 @@ ipa_polymorphic_call_context::ipa_polymo { outer_type = TYPE_MAIN_VARIANT (TREE_TYPE (TREE_TYPE (base_pointer))); + cgraph_node *node = cgraph_node::get (current_function_decl); gcc_assert (TREE_CODE (outer_type) == RECORD_TYPE || TREE_CODE (outer_type) == UNION_TYPE); + /* Handle the case we inlined into a thunk. In this case + thunk has THIS pointer of type bar, but it really receives + address to its base type foo which sits in bar at + 0-thunk.fixed_offset. It starts with code that adds + think.fixed_offset to the pointer to compensate for this. + + Because we walked all the way to the begining of thunk, we now + see pointer &bar-thunk.fixed_offset and need to compensate + for it. */ + if (node->thunk.fixed_offset) + offset -= node->thunk.fixed_offset * BITS_PER_UNIT; + /* Dynamic casting has possibly upcasted the type in the hiearchy. In this case outer type is less informative than inner type and we should forget @@ -1005,7 +1018,11 @@ ipa_polymorphic_call_context::ipa_polymo if ((otr_type && !contains_type_p (outer_type, offset, otr_type)) - || !contains_polymorphic_type_p (outer_type)) + || !contains_polymorphic_type_p (outer_type) + /* If we compile thunk with virtual offset, the THIS pointer + is adjusted by unknown value. We can't thus use outer info + at all. */ + || node->thunk.virtual_offset_p) { outer_type = NULL; if (instance) @@ -1030,7 +1047,15 @@ ipa_polymorphic_call_context::ipa_polymo maybe_in_construction = false; } if (instance) - *instance = base_pointer; + { + /* If method is expanded thunk, we need to apply thunk offset + to instance pointer. */ + if (node->thunk.virtual_offset_p + || node->thunk.fixed_offset) + *instance = NULL; + else + *instance = base_pointer; + } return; } /* Non-PODs passed by value are really passed by invisible @@ -1547,6 +1572,9 @@ ipa_polymorphic_call_context::get_dynami HOST_WIDE_INT instance_offset = offset; tree instance_outer_type = outer_type; + if (!instance) + return false; + if (otr_type) otr_type = TYPE_MAIN_VARIANT (otr_type); Index: lto-cgraph.c =================================================================== --- lto-cgraph.c (revision 267325) +++ lto-cgraph.c (working copy) @@ -547,7 +547,11 @@ lto_output_node (struct lto_simple_outpu streamer_write_bitpack (&bp); streamer_write_data_stream (ob->main_stream, section, strlen (section) + 1); - if (node->thunk.thunk_p) + /* Stream thunk info always because we use it in + ipa_polymorphic_call_context::ipa_polymorphic_call_context + to properly interpret THIS pointers for thunks that has been converted + to Gimple. */ + if (node->definition) { streamer_write_uhwi_stream (ob->main_stream, @@ -1295,7 +1299,7 @@ input_node (struct lto_file_decl_data *f if (section) node->set_section_for_node (section); - if (node->thunk.thunk_p) + if (node->definition) { int type = streamer_read_uhwi (ib); HOST_WIDE_INT fixed_offset = streamer_read_uhwi (ib); Index: testsuite/g++.dg/tree-prof/devirt.C =================================================================== --- testsuite/g++.dg/tree-prof/devirt.C (nonexistent) +++ testsuite/g++.dg/tree-prof/devirt.C (working copy) @@ -0,0 +1,123 @@ +/* { dg-options "-O3 -fdump-tree-dom3" } */ +struct nsISupports +{ + virtual int QueryInterface (const int &aIID, void **aInstancePtr) = 0; + virtual __attribute__((noinline, noclone)) unsigned AddRef (void) = 0; + virtual unsigned Release (void) = 0; +}; + +struct nsIObserver : public nsISupports +{ + virtual int Observe (nsISupports * aSubject, const char *aTopic, const unsigned short *aData) = 0; +}; + +struct nsISupportsWeakReference : public nsISupports +{ + virtual int GetWeakReference (void **_retval) = 0; +}; + +struct nsSupportsWeakReference : public nsISupportsWeakReference +{ + nsSupportsWeakReference () : mProxy (0) {} + virtual int GetWeakReference (void **_retval) override { return 0; } + ~nsSupportsWeakReference () {} + void NoticeProxyDestruction () { mProxy = nullptr; } + void *mProxy; + void ClearWeakReferences (); + bool HasWeakReferences () const { return !!mProxy; } +}; + +struct mozIPersonalDictionary : public nsISupports +{ + virtual int Load (void) = 0; + virtual int Save (void) = 0; + virtual int GetWordList (void **aWordList) = 0; + virtual int Check (const int &word, bool * _retval) = 0; + virtual int AddWord (const int &word) = 0; + virtual int RemoveWord (const int &word) = 0; + virtual int IgnoreWord (const int &word) = 0; + virtual int EndSession (void) = 0; +}; + +struct mozPersonalDictionary final + : public mozIPersonalDictionary, public nsIObserver, public nsSupportsWeakReference +{ + virtual int QueryInterface (const int &aIID, void **aInstancePtr) override; + virtual __attribute__((noinline, noclone)) unsigned AddRef (void) override; + virtual unsigned Release (void) override; + unsigned long mRefCnt; + virtual int Load (void) override { return 0; } + virtual int Save (void) override { return 0; } + virtual int GetWordList (void **aWordList) override { return 0; } + virtual int Check (const int &word, bool * _retval) override { return 0; } + virtual int AddWord (const int &word) override { return 0; } + virtual int RemoveWord (const int &word) override { return 0; } + virtual int IgnoreWord (const int &word) override { return 0; } + virtual int EndSession (void) override { return 0; } + virtual int Observe (nsISupports * aSubject, const char *aTopic, const unsigned short *aData) override { return 0; } + mozPersonalDictionary () : mRefCnt(0) {} + int Init () { return 0; } + virtual ~mozPersonalDictionary () {} + bool mIsLoaded; + bool mSavePending; + void *mFile; + char mMonitor[96]; + char mMonitorSave[96]; + char mDictionaryTable[32]; + char mIgnoreTable[32]; +}; + +unsigned +mozPersonalDictionary::AddRef (void) +{ + unsigned count = ++mRefCnt; + return count; +} + +unsigned +mozPersonalDictionary::Release (void) +{ + unsigned count = --mRefCnt; + if (count == 0) + { + mRefCnt = 1; + delete (this); + return 0; + } + return count; +} + +int +mozPersonalDictionary::QueryInterface (const int &aIID, void **aInstancePtr) +{ + nsISupports *foundInterface; + if (aIID == 122) + foundInterface = static_cast <mozIPersonalDictionary *>(this); + else + foundInterface = static_cast <nsISupportsWeakReference *>(this); + int status; + foundInterface->AddRef (); + *aInstancePtr = foundInterface; + return status; +} + +__attribute__((noipa)) int +foo (nsISupports *p, const int &i) +{ + void *q; + return p->QueryInterface (i, &q); +} + +int +main () +{ + mozPersonalDictionary m; + int j = 123; + for (int i = 0; i < 100000; i++) + foo (static_cast <nsISupportsWeakReference *>(&m), j); + if (m.mRefCnt != 100000) + __builtin_abort (); +} + +/* { dg-final-use-not-autofdo { scan-ipa-dump-times 3 "folding virtual function call to virtual unsigned int mozPersonalDictionary::_ZThn16" "dom3" } } */ +/* { dg-final-use-not-autofdo { scan-ipa-dump-times 3 "folding virtual function call to virtual unsigned int mozPersonalDictionary::AddRef" "dom3" } } */