Re: [PATCH v2 1/3] Add a pass to devirtualize polymorphic PMF calls
Chi Wang <[email protected]>
| Newsgroups | gmane.comp.gcc.patches |
|---|---|
| Message-ID | <CA+deYMUsaYu5mM6=ZZ2hbvbUOrpUSQw5XnzpEra0bxRY4p+aEg@mail.gmail.com> |
> That seems wrong and the wrong place for this. I Know richi suggested > moving that part of the check somewhere but I think he meant to move > much more than just scanning part to see if there is possible part. > I am not sure why you can't have this fully as part of forwprop in general or maybe gimple fold. > I think this is even heavy weight even for its own pass even if there is pointer to member functions. For this issue, early inlining is the problem. Before submitting v2, I tried making this pass in forwprop, but early inline can introduce PMF call from other functions (for example, devirtpmf7.C in patch 2/3) and cause a branch in the way. The pass cannot optimize and IPA cannot recognize it. As a pass it is better to be placed after these propagation and simplification passes. I specifically want to get this right because it is not just missed opportunity, it can regress in the sense that O2 can optimize while O3 cannot because of early inlining. I didn't implement it in gimple-fold because it duplicates statements. It is cleaner with DCE, and if the IFN marker is CSE-ed then gimple-fold can technically produce more code if no DCE runs after. But if it is fine then that seems better. > Since this lambda is NOT used as a call back and has no captures, it > definitely should be seperate local function. > You could just use auto_bitmap I see (for both of them). > I am trying to understand why you need info.subst here? Why base it on > stmt? Rather than the ssa name? Since it seems like you always lookup > SSA_NAME_DEF_STMT and always use it with gimple_get_lhs. > So you just need a map from tree to tree it seems. Also is that to > avoid needing to recreate things? It was for preventing duplicated statement rebuild. But you are right it should be sufficient for a tree to tree mapping. On Tue, Aug 18, 2026 at 2:43 PM Andrea Pinski < [email protected]> wrote: > On Mon, Aug 17, 2026 at 11:13 PM Chi Wang <[email protected]> > wrote: > > > > Add a tree pass gimple-ptrmemfunc to convert trivial polymorphic call > > via PMF to a direct target. This pass identifies the pattern when a PMF > > call is reduced to the virtual branch by folding and/or constant > > propagation. The branch identification is based on a vtable indexing and > > a vtable load from an object pointer tagged with IFN_PTRMEMFUNC_OBJ. For > > instance, in > > > > struct X { /* ... */ }; > > struct Y : P, X { /* ... */ }; > > struct Z : Q, Y { /* ... */ }; > > Z* obj; > > void (Y::* func) (); > > (obj->*func) (); > > > > The PMF call is through two adjustments: one via front end casting Z* to > > Y* (suppose it is 16 bytes), and another, if func is a virtual method of > > X, then another *this adjustment is made (suppose it is 8 bytes). The > > generated GIMPLE will be: > > > > _7 = &obj_16(D)->D.2726; > > _6 = .PTRMEMFUNC_OBJ (_7, 0B, 16B); > > _8 = MEM[(int (*) () * *)_6 + 8B]; > > _10 = *_8; > > _11 = &obj_16(D)->D.2726; > > _12 = _11 + 8B; > > _10 (_12); > > > > where 0B is typed pointer to method type void (Y::*) (Y*), and 16B is > > typed Z*, essentially preserving the front end cast information. Here > > the second argument is 0B because Z to Y is not through any virtual > > base; it is 1B if otherwise. > > > > The pass will walk from the call _10, find the index is 0 bytes into the > > vtable, and the vtable is loaded from the secondary vtable of _6 (offset > > by 24 bytes from type Z). It will then extract type information from the > > PTRMEMFUNC_OBJ internal call, and look up the exact function entry in > > that vtable. > > > > If the function is final or the type is final, the call is made direct. > > This is analogous to how the C++ FE emits a direct call when it is known > > to be final. In addition, if the vtable lookup is out of bounds on > > secondary or primary final vtable, the index is invalid and it changes > > the call to __builtin_unreachable. If no definite direct target exists, > > we emit an OBJ_TYPE_REF token to reuse the existing polymorphic call > > devirtualization. In all recognized cases, where the offsets are > > constants, the IFN_PTRMEMFUNC_OBJ is removed. > > > > The pass is run twice, once before IPA and after early inline to > > discover PMF direct calls, and once after IPA. Each time it only scans > > functions marked with PENDING_TODO_ptrmemfunc_devirt, which is currently > > set by tree-ssa-forwprop. > > That seems wrong and the wrong place for this. I Know richi suggested > moving that part of the check somewhere but I think he meant to move > much more than just scanning part to see if there is possible part. > The question > > > > > This pass also adjusts the thunk property propagation to also process > > DECL_FINAL_P. This enables the pass to still inspect the property in the > > middle end. > > > > To identify a PMF call, the C++ front end attaches the PMF marker > > IFN_PTRMEMFUNC_OBJ onto the virtual branch. This internal function wraps > > the object pointer and is a no-op when converted to RTL. Technically > > this can obscure an indirect call data flow with internal function, but > > it should not be a problem since the data flow from the > > IFN_PTRMEMFUNC_OBJ only propagates to the polymorphic method lookup, > > and once a target exists, the pass will transform the call into a > > direct or polymorphic call and remove the marker, so it should have no > > effect on that. > > > > gcc/ChangeLog: > > > > * Makefile.in: Add gimple-ptrmemfunc.o. > > * internal-fn.cc (expand_PTRMEMFUNC_OBJ): Expand > > IFN_PTRMEMFUNC_OBJ. > > * internal-fn.def (PTRMEMFUNC_OBJ): Define > > IFN_PTRMEMFUNC_OBJ. > > * doc/ifn.texi: Document it. > > * passes.def: Add two gimple-ptrmemfunc passes. > > * timevar.def (TV_GIMPLE_PTRMEMFUNC): Add timevar for gimple- > > ptrmemfunc. > > * gimple-ptrmemfunc.cc: New file. > > * gimple-ptrmemfunc.h: New file. > > * tree-pass.h (make_pass_ptrmemfunc): Make gimple-ptrmemfunc > > pass. > > (PENDING_TODO_ptrmemfunc_devirt): Add flag for gimple-ptrmemfunc > > to run for a function. > > * tree-ssa-forwprop.cc (pass_forwprop::execute): Add flag when > > IFN_PTRMEMFUNC_OBJ is found in a function. > > > > gcc/cp/ChangeLog: > > > > * constexpr.cc (cxx_eval_internal_function): Consider > > IFN_PTRMEMFUNC_OBJ as a no-op constexpr function. > > (potential_constant_expression_1): Propagate through > > IFN_PTRMEMFUNC_OBJ. > > * method.cc (make_thunk): Make thunk also inherit DECL_FINAL_P > > from the function. > > * typeck.cc (get_member_function_from_ptrfunc): Attach > > IFN_PTRMEMFUNC_OBJ marker to the virtual branch of a PMF call. > > > > gcc/testsuite/ChangeLog: > > > > * g++.dg/opt/devirtpmf1.C: New test. > > * g++.dg/opt/devirtpmf2.C: New test. > > * g++.dg/opt/devirtpmf3.C: New test. > > * g++.dg/opt/devirtpmf4.C: New test. > > * g++.dg/opt/devirtpmf5.C: New test. > > > > --- > > > Preparatory work can be done as part of another pass that does general > > > folding, for example like forwprop. We should avoid repeated full IL > walks > > > for each and every tiny pattern that might eventually appear somewhere. > > > > Added a PENDING_TODO_ptrmemfunc_devirt to prevent walking IL of every > function. > > The flag is currently set by forwprop on function level. I am uncertain > if it is > > possible to flag, or keep a working list of basic blocks or statements, > but > > previous passes seem to change them a lot and I don't find existing > places to > > handle them. > > I am not sure why you can't have this fully as part of forwprop in > general or maybe gimple fold. > I think this is even heavy weight even for its own pass even if there > is pointer to member functions. > > > > > > Patch 2/3 and 3/3 are unchanged. Bootstrapped and regression tested for > the > > full series on C and C++ on x86_64-pc-linux-gnu, on unix and unix/-m32. > > > > --- > > gcc/Makefile.in | 1 + > > gcc/cp/constexpr.cc | 2 + > > gcc/cp/method.cc | 1 + > > gcc/cp/typeck.cc | 32 +- > > gcc/doc/ifn.texi | 5 + > > gcc/gimple-ptrmemfunc.cc | 662 ++++++++++++++++++++++++++ > > gcc/gimple-ptrmemfunc.h | 29 ++ > > gcc/internal-fn.cc | 11 + > > gcc/internal-fn.def | 3 + > > gcc/passes.def | 6 + > > gcc/testsuite/g++.dg/opt/devirtpmf1.C | 128 +++++ > > gcc/testsuite/g++.dg/opt/devirtpmf2.C | 63 +++ > > gcc/testsuite/g++.dg/opt/devirtpmf3.C | 57 +++ > > gcc/testsuite/g++.dg/opt/devirtpmf4.C | 45 ++ > > gcc/testsuite/g++.dg/opt/devirtpmf5.C | 72 +++ > > gcc/timevar.def | 1 + > > gcc/tree-pass.h | 3 + > > gcc/tree-ssa-forwprop.cc | 4 + > > 18 files changed, 1119 insertions(+), 6 deletions(-) > > create mode 100644 gcc/gimple-ptrmemfunc.cc > > create mode 100644 gcc/gimple-ptrmemfunc.h > > create mode 100644 gcc/testsuite/g++.dg/opt/devirtpmf1.C > > create mode 100644 gcc/testsuite/g++.dg/opt/devirtpmf2.C > > create mode 100644 gcc/testsuite/g++.dg/opt/devirtpmf3.C > > create mode 100644 gcc/testsuite/g++.dg/opt/devirtpmf4.C > > create mode 100644 gcc/testsuite/g++.dg/opt/devirtpmf5.C > > > > diff --git a/gcc/Makefile.in b/gcc/Makefile.in > > index ee2f9022eab..40af4ad44e4 100644 > > --- a/gcc/Makefile.in > > +++ b/gcc/Makefile.in > > @@ -1531,6 +1531,7 @@ OBJS = \ > > gimple-lower-bitint.o \ > > gimple-predicate-analysis.o \ > > gimple-pretty-print.o \ > > + gimple-ptrmemfunc.o \ > > gimple-range.o \ > > gimple-range-cache.o \ > > gimple-range-edge.o \ > > diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc > > index e16c878d725..e6ccd308f69 100644 > > --- a/gcc/cp/constexpr.cc > > +++ b/gcc/cp/constexpr.cc > > @@ -3253,6 +3253,7 @@ cxx_eval_internal_function (const constexpr_ctx > *ctx, tree t, > > break; > > > > case IFN_LAUNDER: > > + case IFN_PTRMEMFUNC_OBJ: > > return cxx_eval_constant_expression (ctx, CALL_EXPR_ARG (t, 0), > > vc_prvalue, non_constant_p, > > overflow_p, jump_target); > > @@ -12080,6 +12081,7 @@ potential_constant_expression_1 (tree t, bool > want_rval, bool strict, bool now, > > case IFN_VEC_CONVERT: > > case IFN_BSWAP: > > case IFN_BITREVERSE: > > + case IFN_PTRMEMFUNC_OBJ: > > bail = false; > > break; > > > > diff --git a/gcc/cp/method.cc b/gcc/cp/method.cc > > index bf882b9619c..3c755d8377c 100644 > > --- a/gcc/cp/method.cc > > +++ b/gcc/cp/method.cc > > @@ -106,6 +106,7 @@ make_thunk (tree function, bool this_adjusting, > > DECL_LANG_SPECIFIC (thunk) = DECL_LANG_SPECIFIC (function); > > cxx_dup_lang_specific_decl (thunk); > > DECL_VIRTUAL_P (thunk) = true; > > + DECL_FINAL_P (thunk) = DECL_FINAL_P (function); > > SET_DECL_THUNKS (thunk, NULL_TREE); > > > > DECL_CONTEXT (thunk) = DECL_CONTEXT (function); > > diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc > > index 5bde051b9b3..808694b3ec7 100644 > > --- a/gcc/cp/typeck.cc > > +++ b/gcc/cp/typeck.cc > > @@ -4505,24 +4505,44 @@ get_member_function_from_ptrfunc (tree > *instance_ptrptr, tree function, > > if (e1 == error_mark_node) > > return error_mark_node; > > > > + /* Before adding delta, compute the wrapper tokens. */ > > + tree outer_type = TREE_TYPE (TREE_TYPE (instance_ptr)); > > + tree outer_type_token; > > + tree method_type_token = build_int_cst (fntype, 0); > > /* Convert down to the right base before using the instance. A > > special case is that in a pointer to member of class C, C may > > be incomplete. In that case, the function will of course be > > a member of C, and no conversion is required. In fact, > > lookup_base will fail in that case, because incomplete > > classes do not have BINFOs. */ > > - if (!same_type_ignoring_top_level_qualifiers_p > > - (basetype, TREE_TYPE (TREE_TYPE (instance_ptr)))) > > + if (!same_type_ignoring_top_level_qualifiers_p (basetype, > outer_type)) > > { > > - basetype = lookup_base (TREE_TYPE (TREE_TYPE (instance_ptr)), > > - basetype, ba_check, NULL, complain); > > + base_kind bk; > > + basetype > > + = lookup_base (outer_type, basetype, ba_check, &bk, > complain); > > instance_ptr = build_base_path (PLUS_EXPR, instance_ptr, > basetype, > > 1, complain); > > if (instance_ptr == error_mark_node) > > return error_mark_node; > > + outer_type_token = fold_convert (build_pointer_type > (outer_type), > > + BINFO_OFFSET (basetype)); > > + method_type_token = build_int_cst (fntype, bk == > bk_via_virtual); > > } > > - /* ...and then the delta in the PMF. */ > > + else > > + { > > + outer_type_token = build_int_cst (build_pointer_type > (outer_type), 0); > > + method_type_token = build_int_cst (fntype, 0); > > + } > > + /* Wrap the base converted object IFN_PTRMEMFUNC_OBJ. */ > > + tree wrapped_inst_ptr > > + = build_call_expr_internal_loc (input_location, > IFN_PTRMEMFUNC_OBJ, > > + TREE_TYPE (instance_ptr), 3, > > + instance_ptr, method_type_token, > > + outer_type_token); > > + /* ...and then the delta in the PMF. Do this separately so we > will not > > + let IFN_PTRMEMFUNC_OBJ slip out of this branch. */ > > instance_ptr = fold_build_pointer_plus (instance_ptr, delta); > > + wrapped_inst_ptr = fold_build_pointer_plus (wrapped_inst_ptr, > delta); > > > > /* Hand back the adjusted 'this' argument to our caller. */ > > *instance_ptrptr = instance_ptr; > > @@ -4533,7 +4553,7 @@ get_member_function_from_ptrfunc (tree > *instance_ptrptr, tree function, > > > > /* Next extract the vtable pointer from the object. */ > > vtbl = build1 (NOP_EXPR, build_pointer_type (vtbl_ptr_type_node), > > - instance_ptr); > > + wrapped_inst_ptr); > > vtbl = cp_build_fold_indirect_ref (vtbl); > > if (vtbl == error_mark_node) > > return error_mark_node; > > diff --git a/gcc/doc/ifn.texi b/gcc/doc/ifn.texi > > index 7cfc80eecf2..2aa17495b76 100644 > > --- a/gcc/doc/ifn.texi > > +++ b/gcc/doc/ifn.texi > > @@ -1546,6 +1546,11 @@ The @code{IFN_FLOATTOBITINT} internal function is > expanded by the > > The @code{IFN_BITINTTOFLOAT} internal function is expanded by the > > @code{expand_BITINTTOFLOAT} function. > > > > +@cindex @code{IFN_PTRMEMFUNC_OBJ} internal function > > +@item @samp{IFN_PTRMEMFUNC_OBJ} > > +The @code{IFN_PTRMEMFUNC_OBJ} internal function is expanded by the > > +@code{expand_PTRMEMFUNC_OBJ} function. > > + > > @cindex @code{IFN_VARYING} internal function > > @item @samp{IFN_VARYING} > > Use @code{IFN_VARYING} as a temporary placeholder for a value whose > > diff --git a/gcc/gimple-ptrmemfunc.cc b/gcc/gimple-ptrmemfunc.cc > > new file mode 100644 > > index 00000000000..6c53a4befb7 > > --- /dev/null > > +++ b/gcc/gimple-ptrmemfunc.cc > > @@ -0,0 +1,662 @@ > > +/* Optimize polymorphic constant pointer to member function calls. > > + Copyright (C) 2026 Free Software Foundation, Inc. > > + > > + This file is part of GCC. > > + > > +GCC is free software; you can redistribute it and/or modify it under > > +the terms of the GNU General Public License as published by the Free > > +Software Foundation; either version 3, or (at your option) any later > > +version. > > + > > +GCC is distributed in the hope that it will be useful, but WITHOUT ANY > > +WARRANTY; without even the implied warranty of MERCHANTABILITY or > > +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License > > +for more details. > > + > > +You should have received a copy of the GNU General Public License > > +along with GCC; see the file COPYING3. If not see > > +<http://www.gnu.org/licenses/>. */ > > + > > +/* PMF = pointer-to-member function. > > + For each virtual branch of a PMF call, > > + > > + (obj->*member)(); > > + > > + the call can be either a simple indirect or a polymorphic call. > When MEMBER > > + is later found to be a constant, we can apply optimization according > to the > > + type of the call. For the polymorphic case, suppose OBJ is typed > O*, and > > + member is B::*, then there are two type conversions: front end casts > OBJ from > > + O* to B*, and an implicit type conversion happens when PMF delta is > applied > > + to adjust *this (which is typed B*). In the following context, we > refer to > > + O as the outer type, B as the base type, and the final adjusted > *this type as > > + the inner type. Because GIMPLE pointer type may be unreliable, C++ > FE will > > + attach an object marker via an internal function call: > > + > > + .PTRMEMFUNC_OBJ (obj, method_type_token, outer_type_token) > > + > > + where > > + 1 OBJ is the casted C++ PMF member function calling object pointer. > > + 2 METHOD_TYPE_TOKEN has the type of pointer to the calling method > type. > > + 3 OUTER_TYPE_TOKEN has pointer to the outer type. > > + 4 METHOD_TYPE_TOKEN is 0 or 1: whether the outer to base conversion > is > > + through a virtual base cast. This is equivalent to whether the > offset > > + described by the next entry is valid for every derived outer type. > > + 5 OUTER_TYPE_TOKEN has the value of the *this adjustment offset from > outer > > + type to base type. > > + > > + Based on the information, the pass can therefore identify the > polymorphic > > + call case, since OBJ is now known to point to an object that is a > subobject > > + of a known offset inside type O instead of an arbitrary pointer. > > + > > + Note that, in many cases, inner type is a (possibly ambiguous or > unreachable) > > + base of the outer type, but it is also possible that inner is a > derived class > > + of outer class, or entirely unrelated. For example: > > + > > + struct A { virtual void foo (); }; > > + struct B { virtual void bar (); }; > > + struct C : A, B {}; > > + > > + B* b; > > + (b->*(static_cast<void (B::*) ()> > > + (static_cast<void (C::*) ()> (&A::foo)))) (); > > + > > + is a perfectly valid call provided that the runtime type of *b is > always at > > + least C*. In this case it is not possible to have a meaningful > polymorphic > > + context to devirtualize. > > + > > + This file implements a pass to optimize constant polymorphic PMFs > that can > > + resolve to direct targets. It also exposes helpers to match the > > + PTRMEMFUNC_OBJ call pattern as well as other direct target lookup > functions > > + for IPA. > > +*/ > > + > > +#include "config.h" > > +#include "system.h" > > +#include "coretypes.h" > > +#include "backend.h" > > +#include "tree.h" > > +#include "gimple.h" > > +#include "tree-pass.h" > > +#include "stringpool.h" > > +#include "gimple-ssa.h" > > +#include "tree-ssanames.h" > > +#include "cgraph.h" > > +#include "gimple-pretty-print.h" > > +#include "tree-cfg.h" > > +#include "tree-cfgcleanup.h" > > +#include "tree-ssa-dce.h" > > +#include "tree-dfa.h" > > +#include "ipa-utils.h" > > +#include "gimple-iterator.h" > > +#include "gimplify-me.h" > > +#include "gimple-fold.h" > > +#include "fold-const.h" > > +#include "gimple-ptrmemfunc.h" > > +#include "dbgcnt.h" > > +#include "langhooks.h" > > + > > +/* Matches virtual branch of call through pointer to member function. > The > > + folded GIMPLE should roughly be: > > + > > + _2 = .PTRMEMFUNC_OBJ (_1, 0B, 0B); > > + _3 = MEM[(int (*) () * *)_2 + vt_offset]; > > + _4 = MEM[_3 + vt_index]; > > + > > + where _4 is the returned function pointer. If it is the virtual > branch of a > > + ptrmemfunc_call, returns true, adds vt_offset to OFFSET and vt_index > to > > + BYTE_IDX if they are non-null, and set VT to be the SSA of the > extracted > > + vtable and PMF_OBJ to be the SSA in the LHS of IFN_PTRMEMFUNC_OBJ > call. If > > + BYTE_IDX and OFFSET are supplied, their corresponding component must > also > > + fit into poly_int64. The passed arguments might be modified even > though the > > + function returns false. */ > > + > > +static bool > > +gimple_analyze_virtual_ptrmemfunc_branch (tree vfn, tree &vt, tree > &pmf_obj, > > + poly_int64 *byte_idx, > > + poly_int64 *offset) > > +{ > > + auto peel > > + = [] (gimple *ssa_def, enum tree_code code, tree &base, poly_int64 > *offset) > > + { > > + tree base_op, off_op; > > + if (!is_gimple_assign (ssa_def) > > + || gimple_assign_rhs_code (ssa_def) != code) > > + return false; > > + > > + if (code == MEM_REF) > > + { > > + base_op = TREE_OPERAND (gimple_assign_rhs1 (ssa_def), 0); > > + off_op = TREE_OPERAND (gimple_assign_rhs1 (ssa_def), 1); > > + } > > + else if (code == POINTER_PLUS_EXPR) > > + { > > + base_op = gimple_assign_rhs1 (ssa_def); > > + off_op = gimple_assign_rhs2 (ssa_def); > > + } > > + else > > + gcc_unreachable (); > > + > > + if (TREE_CODE (base_op) != SSA_NAME) > > + return false; > > + if (offset) > > + { > > + if (!poly_int_tree_p (off_op)) > > + return false; > > + poly_offset_int wide > > + = poly_offset_int::from (wi::to_poly_wide (off_op), > SIGNED); > > + poly_int64 d; > > + if (!wide.to_shwi (&d)) > > + return false; > > + *offset += d; > > + } > > + base = base_op; > > + return true; > > + }; > > Since this lambda is NOT used as a call back and has no captures, it > definitely should be seperate local function. > > > > + > > + gimple *vfn_def = SSA_NAME_DEF_STMT (vfn); > > + /* If target uses descriptors, expect a cast instead of a MEM_REF. */ > > + if (TARGET_VTABLE_USES_DESCRIPTORS) > > + { > > + if (is_gimple_assign (vfn_def) > > + && CONVERT_EXPR_CODE_P (gimple_assign_rhs_code (vfn_def))) > > + { > > + vt = gimple_assign_rhs1 (vfn_def); > > + if (TREE_CODE (vt) != SSA_NAME) > > + return false; > > + } > > + else > > + vt = vfn; > > + } > > + else if (!peel (vfn_def, MEM_REF, vt, byte_idx)) > > + return false; > > + peel (SSA_NAME_DEF_STMT (vt), POINTER_PLUS_EXPR, vt, byte_idx); > > + > > + gimple *vt_def = SSA_NAME_DEF_STMT (vt); > > + if (!peel (vt_def, MEM_REF, pmf_obj, offset)) > > + return false; > > + peel (SSA_NAME_DEF_STMT (pmf_obj), POINTER_PLUS_EXPR, pmf_obj, > offset); > > + > > + gimple *pmf_obj_def = SSA_NAME_DEF_STMT (pmf_obj); > > + if (!gimple_call_internal_p (pmf_obj_def, IFN_PTRMEMFUNC_OBJ) > > + || gimple_call_num_args (pmf_obj_def) != 3) > > + return false; > > + return true; > > +} > > + > > +bool > > +gimple_analyze_virtual_ptrmemfunc_branch (tree vfn, tree &pmf_obj) > > +{ > > + tree vt; > > + return gimple_analyze_virtual_ptrmemfunc_branch (vfn, vt, pmf_obj, > NULL, > > + NULL); > > +} > > + > > +/* Return a complete type of the outermost base subobject that begins > at byte > > + OFFSET inside TYPE (TYPE itself when OFFSET is 0), or NULL_TREE > > + if no base subobject starts exactly at OFFSET. */ > > + > > +tree > > +find_outermost_base_at_offset (tree type, HOST_WIDE_INT offset) > > +{ > > + HOST_WIDE_INT delta = offset * BITS_PER_UNIT; > > + while (true) > > + { > > + if (in_lto_p) > > + type = prevailing_odr_type (type); > > + if (!COMPLETE_TYPE_P (type)) > > + return NULL; > > + > > + HOST_WIDE_INT pos, size; > > + tree fld; > > + > > + if (delta < 0) > > + return NULL_TREE; > > + if (delta == 0) > > + return type; > > + for (fld = TYPE_FIELDS (type); fld; fld = DECL_CHAIN (fld)) > > + { > > + if (TREE_CODE (fld) != FIELD_DECL || !DECL_ARTIFICIAL (fld) > > + || TREE_CODE (TREE_TYPE (fld)) != RECORD_TYPE) > > + continue; > > + > > + pos = int_bit_position (fld); > > + size = tree_to_uhwi (DECL_SIZE (fld)); > > + if (known_in_range_p (delta, pos, size)) > > + break; > > + } > > + if (!fld) > > + return NULL_TREE; > > + > > + type = TREE_TYPE (fld); > > + delta -= pos; > > + } > > +} > > + > > +static bool > > +is_virtual_fndecl_direct (tree &fn, tree type, bool is_secondary) > > +{ > > + /* Primary vtable with final type or secondary vtable cannot be > extended. */ > > + if (fndecl_built_in_p (fn, BUILT_IN_UNREACHABLE, > BUILT_IN_UNREACHABLE_TRAP)) > > + return is_secondary || TYPE_FINAL_P (type); > > + /* Or, we land on a dtor. It is not possible to call dtor from PMF. > */ > > + if (DECL_CXX_DESTRUCTOR_P (fn)) > > + { > > + fn = builtin_decl_unreachable (); > > + return true; > > + } > > + > > + /* Check type is final or the method is final. */ > > + return TYPE_FINAL_P (type) > > + || (TREE_CODE (TREE_TYPE (fn)) == METHOD_TYPE && DECL_FINAL_P > (fn)); > > +} > > + > > +/* Return the polymorphic target of type INNER located at type BASE at > offset > > + OFFSET at vtable index IDX if the target can be turned into a direct > call, > > + or impossible for a PMF polymorphic call. */ > > +tree > > +get_ptrmemfunc_polymorphic_direct_target (tree base, tree inner, > > + poly_int64 offset, > HOST_WIDE_INT idx) > > +{ > > + if (in_lto_p) > > + base = prevailing_odr_type (base); > > + if (TYPE_BINFO (base) == NULL) > > + return NULL_TREE; > > + tree binfo = get_binfo_at_offset (TYPE_BINFO (base), offset, inner); > > + if (!binfo) > > + return NULL_TREE; > > + > > + bool can_refer = false; > > + tree fndecl = gimple_get_virt_method_for_binfo (idx, binfo, > &can_refer); > > + if (dump_file) > > + { > > + fprintf (dump_file, "Looking up virtual function *(base + "); > > + print_dec (offset, dump_file, SIGNED); > > + fprintf (dump_file, " bits)[" HOST_WIDE_INT_PRINT_DEC "] on type > ", idx); > > + print_generic_expr (dump_file, base, TDF_SLIM); > > + fprintf (dump_file, " gives "); > > + print_generic_expr (dump_file, fndecl, TDF_SLIM); > > + fprintf (dump_file, can_refer ? ", can refer" : ", cannot refer"); > > + } > > + if (!fndecl || !can_refer) > > + { > > + if (dump_file) > > + fprintf (dump_file, "\n"); > > + return NULL_TREE; > > + } > > + > > + bool is_direct > > + = is_virtual_fndecl_direct (fndecl, base, known_ne (offset, 0)); > > + if (dump_file) > > + fprintf (dump_file, ", %s direct\n", is_direct ? "is" : "may not > be"); > > + return is_direct ? fndecl : NULL_TREE; > > +} > > + > > +namespace { > > + > > +struct pass_ptrmemfunc_func_info > > +{ > > + hash_map<gimple *, gimple *> subst; > > + auto_vec<gimple *> unreachables; > > + bitmap dead_ssas; > > + bitmap dead_eh; > > You could just use auto_bitmap > > > + > > + pass_ptrmemfunc_func_info () > > + : dead_ssas (BITMAP_ALLOC (NULL)), dead_eh (BITMAP_ALLOC (NULL)) > > + {} > > + > > + ~pass_ptrmemfunc_func_info () > > + { > > + BITMAP_FREE (dead_ssas); > > + BITMAP_FREE (dead_eh); > > + } > > +}; > > + > > +/* Matches the case when a pointer to member function call loses its > non-virtual > > + branch. If both the vtable index and the *this adjustment offset are > > + constants, optimize the call. > > + > > + If the call is a direct final call or an impossible call, change the > call > > + target directly. Otherwise, emit an OBJ_TYPE_REF into the call: > > + > > + OBJ_TYPE_REF(_4;_1(D)->vt_index) (_5, ...); > > + > > + The vtable load will also be changed to bypass the PTRMEMFUNC_OBJ > marker. > > + The marker should be DCE-ed when all dependent PMF calls are > optimized or > > + with OTR token attached. */ > > + > > +void > > +fold_virtual_ptrmemfunc_call (gcall *call, pass_ptrmemfunc_func_info > &info) > > +{ > > + tree vfn = gimple_call_fn (call); > > + if (!vfn || TREE_CODE (vfn) != SSA_NAME) > > + return; > > + > > + poly_int64 byte_idx_poly = 0, offset_poly = 0; > > + tree vt, pmf_obj; > > + if (!gimple_analyze_virtual_ptrmemfunc_branch (vfn, vt, pmf_obj, > > + &byte_idx_poly, > &offset_poly)) > > + return; > > + if (!byte_idx_poly.is_constant () || !offset_poly.is_constant ()) > > + return; > > + if (dump_file) > > + fprintf (dump_file, "\nFound constant polymorphic call from PMF\n"); > > + > > + gimple *pmf_obj_def = SSA_NAME_DEF_STMT (pmf_obj); > > + HOST_WIDE_INT byte_idx = byte_idx_poly.to_constant (); > > + HOST_WIDE_INT offset = offset_poly.to_constant (); > > + tree method_type = TREE_TYPE (TREE_TYPE (gimple_call_arg > (pmf_obj_def, 1))); > > + tree base_type = TYPE_METHOD_BASETYPE (method_type); > > + > > + if (dump_file) > > + { > > + fprintf (dump_file, "Constant PMF based on type "); > > + print_generic_expr (dump_file, base_type, TDF_SLIM); > > + fprintf (dump_file, "\n"); > > + } > > + > > + /* Find the vtable array index and try to resolve into a direct > target. */ > > + unsigned HOST_WIDE_INT vt_entry_size > > + = tree_to_uhwi (TYPE_SIZE_UNIT (TREE_TYPE (vt))); > > + HOST_WIDE_INT idx = -1; > > + if (byte_idx < 0 || vt_entry_size == 0) > > + return; > > + > > + bool give_up = false; > > + tree fndecl = NULL_TREE; > > + tree inner_type = NULL; > > + if (byte_idx % vt_entry_size != 0) > > + { > > + if (dump_file) > > + { > > + fprintf (dump_file, > > + "byte index " HOST_WIDE_INT_PRINT_DEC > > + " is not divisible by the vtable size " > > + HOST_WIDE_INT_PRINT_DEC "\n", > > + byte_idx, vt_entry_size); > > + } > > + fndecl = builtin_decl_unreachable (); > > + } > > + else > > + { > > + idx = byte_idx / vt_entry_size; > > + > > + /* See if we can find a method in the TYPE_BINFO of the inner > type. > > + We try to use the outer type if it is usable. Since we only > produce a > > + target when it is final or unreachable, we don't care whether > the outer > > + to base cast is through any virtual base. */ > > + tree outer_type_token = gimple_call_arg (pmf_obj_def, 2); > > + if (tree_fits_poly_int64_p (outer_type_token)) > > + { > > + tree outer_type = TREE_TYPE (TREE_TYPE (outer_type_token)); > > + auto outer_offset_poly = tree_to_poly_int64 (outer_type_token); > > + outer_offset_poly += offset_poly; > > + if (known_lt (outer_offset_poly, 0)) > > + give_up = true; > > + else if (outer_offset_poly.is_constant ()) > > + { > > + inner_type > > + = find_outermost_base_at_offset > > + (outer_type, outer_offset_poly.to_constant ()); > > + } > > + outer_offset_poly *= BITS_PER_UNIT; > > + if (inner_type) > > + fndecl = get_ptrmemfunc_polymorphic_direct_target > > + (outer_type, inner_type, outer_offset_poly, idx); > > + } > > + else > > + { > > + if (offset < 0) > > + give_up = true; > > + else > > + inner_type = find_outermost_base_at_offset (base_type, > offset); > > + if (inner_type) > > + fndecl = get_ptrmemfunc_polymorphic_direct_target > > + (base_type, inner_type, offset_poly, idx); > > + } > > + } > > + > > + if (!fndecl && !inner_type && !give_up) > > + return; > > + if (inner_type && dump_file) > > + { > > + fprintf (dump_file, "Calling subobject type "); > > + print_generic_expr (dump_file, inner_type, TDF_SLIM); > > + fprintf (dump_file, "\n"); > > + } > > + > > + if (!dbg_cnt (devirt)) > > + return; > > + > > + /* If the call is direct, change the calling target and type of the > function > > + if necessary. Otherwise, we emit an OTR so it is recognized as a > virtual > > + call. */ > > + bitmap_set_bit (info.dead_ssas, SSA_NAME_VERSION (vt)); > > + bitmap_set_bit (info.dead_ssas, SSA_NAME_VERSION (vfn)); > > + bitmap_set_bit (info.dead_ssas, SSA_NAME_VERSION (pmf_obj)); > > + > > + if (fndecl > > + && fndecl_built_in_p (fndecl, BUILT_IN_UNREACHABLE, > > + BUILT_IN_UNREACHABLE_TRAP)) > > + { > > + if (dump_file) > > + fprintf (dump_file, > > + "Impossible call, giving __builtin_unreachable ()\n"); > > + if (dump_enabled_p ()) > > + { > > + dump_printf_loc (MSG_OPTIMIZED_LOCATIONS, call, > > + "converting virtual pointer-to-member call to > " > > + "__builtin_unreachable\n"); > > + } > > + gimple_stmt_iterator gsi = gsi_for_stmt (call); > > + gimple *new_call > > + = gimple_build_builtin_unreachable (gimple_location (call)); > > + info.unreachables.safe_push (new_call); > > + if (gsi_replace (&gsi, new_call, true)) > > + bitmap_set_bit (info.dead_eh, gimple_bb (new_call)->index); > > + return; > > + } > > + > > + if (fndecl) > > + { > > + if (dump_file) > > + { > > + fprintf (dump_file, "Direct call, calling to "); > > + print_generic_expr (dump_file, fndecl, TDF_SLIM); > > + fprintf (dump_file, "\n"); > > + } > > + if (dump_enabled_p ()) > > + { > > + dump_printf_loc (MSG_OPTIMIZED_LOCATIONS, call, > > + "converting virtual pointer-to-member call to > %s\n", > > + lang_hooks.decl_printable_name (fndecl, 2)); > > + } > > + gimple_call_set_fndecl (call, fndecl); > > + update_stmt (call); > > + return; > > + } > > + > > + auto rebuild_memref = [&info] (gimple *stmt, tree op0, HOST_WIDE_INT > op1) > > + { > > + gimple *&slot = info.subst.get_or_insert (stmt); > > + if (slot == NULL) > > + { > > + gimple_stmt_iterator gsi = gsi_for_stmt (stmt); > > + op0 = force_gimple_operand_gsi_1 (&gsi, op0, > is_gimple_mem_ref_addr, > > + NULL_TREE, false, > > + GSI_CONTINUE_LINKING); > > I don't like the use of force_gimple_operand_gsi_1 here at all. Isn't > op0 always a gimple_invariant/ssa name? Do you need to handle > something different here? > > > + tree lhs = make_ssa_name (TREE_TYPE (gimple_get_lhs (stmt))); > > + tree rhs = gimple_assign_rhs1 (stmt); > > + rhs = build2 (MEM_REF, TREE_TYPE (rhs), op0, > > + build_int_cst (TREE_TYPE (TREE_OPERAND (rhs, > 1)), op1)); > > + gimple *new_stmt = gimple_build_assign (lhs, rhs); > > + > > + gimple_set_vuse (new_stmt, gimple_vuse (stmt)); > > + gsi_insert_after (&gsi, new_stmt, GSI_NEW_STMT); > > + slot = new_stmt; > > + } > > + return gimple_get_lhs (slot); > > + }; > > I am trying to understand why you need info.subst here? Why base it on > stmt? Rather than the ssa name? Since it seems like you always lookup > SSA_NAME_DEF_STMT and always use it with gimple_get_lhs. > So you just need a map from tree to tree it seems. Also is that to > avoid needing to recreate things? > > > + > > + gcc_checking_assert (idx >= 0); > > + > > + tree new_vt = rebuild_memref (SSA_NAME_DEF_STMT (vt), > > + gimple_call_arg (pmf_obj_def, 0), > offset); > > + tree new_vfn; > > + /* If target uses descriptors, rebuild a cast instead of MEM_REF. */ > > + if (TARGET_VTABLE_USES_DESCRIPTORS) > > + { > > + gimple *stmt = SSA_NAME_DEF_STMT (vfn); > > + gimple *&slot = info.subst.get_or_insert (stmt); > > + if (slot == NULL) > > + { > > + gimple_stmt_iterator gsi = gsi_for_stmt (stmt); > > + tree new_desc = new_vt; > > + if (byte_idx) > > + { > > + new_desc = make_ssa_name (TREE_TYPE (vt)); > > + tree rhs = build2 (POINTER_PLUS_EXPR, TREE_TYPE (new_vt), > new_vt, > > + build_int_cst (sizetype, byte_idx)); > > + gsi_insert_after (&gsi, gimple_build_assign (new_desc, > rhs), > > + GSI_NEW_STMT); > > + } > > + new_vfn = make_ssa_name (TREE_TYPE (vfn)); > > + gimple *cast = gimple_build_assign (new_vfn, NOP_EXPR, > new_desc); > > + gsi_insert_after (&gsi, cast, GSI_SAME_STMT); > > + slot = cast; > > + } > > + new_vfn = gimple_get_lhs (slot); > > + } > > + else > > + new_vfn = rebuild_memref (SSA_NAME_DEF_STMT (vfn), new_vt, > byte_idx); > > + if (give_up) > > + { > > + if (dump_file) > > + fprintf (dump_file, > > + "Giving up: negative offset cannot resolve to a > type\n"); > > + gimple_call_set_fn (call, new_vfn); > > + update_stmt (call); > > + } > > + else > > + { > > + if (TYPE_BINFO (inner_type) > > + && polymorphic_type_binfo_p (TYPE_BINFO (inner_type))) > > + { > > + tree otr_token = build_int_cst (integer_type_node, idx); > > + tree new_method_type > > + = build_method_type_directly (inner_type, TREE_TYPE > (method_type), > > + TREE_CHAIN > > + (TYPE_ARG_TYPES > (method_type))); > > + tree otr = build3 (OBJ_TYPE_REF, build_pointer_type > (new_method_type), > > + new_vfn, gimple_call_arg (call, 0), > > + fold_convert (build_pointer_type > (inner_type), > > + otr_token)); > > + if (dump_file) > > + { > > + fprintf (dump_file, "Recognized virtual call, emitting OTR > "); > > + print_generic_expr (dump_file, otr, TDF_SLIM); > > + fprintf (dump_file, "\n"); > > + } > > + gimple_call_set_fn (call, otr); > > + } > > + else > > + { > > + if (dump_file) > > + { > > + fprintf (dump_file, "Giving up: inner type "); > > + print_generic_expr (dump_file, inner_type, TDF_SLIM); > > + fprintf (dump_file, " is not polymorphic\n"); > > + } > > + gimple_call_set_fn (call, new_vfn); > > + } > > + update_stmt (call); > > + } > > + return; > > +} > > + > > +const pass_data pass_data_ptrmemfunc = > > +{ > > + GIMPLE_PASS, /* type */ > > + "ptrmemfunc", /* name */ > > + OPTGROUP_NONE, /* optinfo_flags */ > > + TV_GIMPLE_PTRMEMFUNC, /* tv_id */ > > + ( PROP_cfg | PROP_ssa ), /* properties_required */ > > + 0, /* properties_provided */ > > + 0, /* properties_destroyed */ > > + 0, /* todo_flags_start */ > > + 0, /* todo_flags_finish */ > > +}; > > + > > +class pass_ptrmemfunc : public gimple_opt_pass > > +{ > > +public: > > + pass_ptrmemfunc (gcc::context *ctxt) > > + : gimple_opt_pass (pass_data_ptrmemfunc, ctxt) > > + {} > > + > > + /* opt_pass methods: */ > > + opt_pass * clone () final override { return new pass_ptrmemfunc > (m_ctxt); } > > + bool gate (function *fun) final override > > + { > > + /* Skip IL scan if the flag is not set. It is set by forwprop > currently so > > + this pass must run after forwprop to actually do any work. */ > > + return optimize != 0 > > + && flag_devirtualize > > + && (fun->pending_TODOs & PENDING_TODO_ptrmemfunc_devirt); > > + } > > + unsigned int execute (function *) final override; > > + > > +}; // class pass_ptrmemfunc > > + > > +unsigned int > > +pass_ptrmemfunc::execute (function *fun) > > +{ > > + fun->pending_TODOs &= ~PENDING_TODO_ptrmemfunc_devirt; > > + > > + basic_block bb; > > + pass_ptrmemfunc_func_info info; > > + > > + unsigned int todo_flags = 0; > > + FOR_EACH_BB_FN (bb, fun) > > + { > > + for (gimple_stmt_iterator gsi = gsi_start_bb (bb); !gsi_end_p > (gsi);) > > + { > > + gimple *stmt = gsi_stmt (gsi); > > + if (!is_gimple_call (stmt)) > > + { > > + gsi_next (&gsi); > > + continue; > > + } > > + > > + fold_virtual_ptrmemfunc_call (as_a<gcall *> (stmt), info); > > + gsi_next (&gsi); > > + } > > + } > > + > > + while (!info.unreachables.is_empty ()) > > + { > > + gimple *stmt = info.unreachables.pop (); > > + if (fixup_noreturn_call (stmt)) > > + todo_flags |= TODO_cleanup_cfg; > > + } > > + > > + bool do_eh_cleanup = !bitmap_empty_p (info.dead_eh); > > + bool do_dce = !bitmap_empty_p (info.dead_ssas); > > + > > + if (do_eh_cleanup) > > + { > > + gimple_purge_all_dead_eh_edges (info.dead_eh); > > + todo_flags |= TODO_cleanup_cfg; > > + } > > + if (do_dce) > > + simple_dce_from_worklist (info.dead_ssas); > > + > > + return todo_flags; > > +} > > + > > +} // anon namespace > > + > > +gimple_opt_pass * > > +make_pass_ptrmemfunc (gcc::context *ctxt) > > +{ > > + return new pass_ptrmemfunc (ctxt); > > +} > > diff --git a/gcc/gimple-ptrmemfunc.h b/gcc/gimple-ptrmemfunc.h > > new file mode 100644 > > index 00000000000..e6bfa256f10 > > --- /dev/null > > +++ b/gcc/gimple-ptrmemfunc.h > > @@ -0,0 +1,29 @@ > > +/* Header file for gimple constant pointer to member function calls. > > + Copyright (C) 2026 Free Software Foundation, Inc. > > + > > + This file is part of GCC. > > + > > +GCC is free software; you can redistribute it and/or modify it under > > +the terms of the GNU General Public License as published by the Free > > +Software Foundation; either version 3, or (at your option) any later > > +version. > > + > > +GCC is distributed in the hope that it will be useful, but WITHOUT ANY > > +WARRANTY; without even the implied warranty of MERCHANTABILITY or > > +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License > > +for more details. > > + > > +You should have received a copy of the GNU General Public License > > +along with GCC; see the file COPYING3. If not see > > +<http://www.gnu.org/licenses/>. */ > > + > > +#ifndef GCC_GIMPLE_PTRMEMFUNC_H > > +#define GCC_GIMPLE_PTRMEMFUNC_H > > + > > +bool gimple_analyze_virtual_ptrmemfunc_branch (tree vfn, tree &pmf_obj); > > +tree find_outermost_base_at_offset (tree type, HOST_WIDE_INT offset); > > +tree get_ptrmemfunc_polymorphic_direct_target (tree base, tree inner, > > + poly_int64 offset, > > + HOST_WIDE_INT idx); > > + > > +#endif // GCC_GIMPLE_PTRMEMFUNC_H > > diff --git a/gcc/internal-fn.cc b/gcc/internal-fn.cc > > index 0138c6f7ef0..80d65094318 100644 > > --- a/gcc/internal-fn.cc > > +++ b/gcc/internal-fn.cc > > @@ -456,6 +456,17 @@ expand_ANNOTATE (internal_fn, gcall *) > > gcc_unreachable (); > > } > > > > +static void > > +expand_PTRMEMFUNC_OBJ (internal_fn, gcall *call) > > +{ > > + tree lhs = gimple_call_lhs (call); > > + > > + if (!lhs) > > + return; > > + > > + expand_assignment (lhs, gimple_call_arg (call, 0), false); > > +} > > + > > /* This should get expanded in omp_device_lower pass. */ > > > > static void > > diff --git a/gcc/internal-fn.def b/gcc/internal-fn.def > > index af9f92950c7..47db056400f 100644 > > --- a/gcc/internal-fn.def > > +++ b/gcc/internal-fn.def > > @@ -649,6 +649,9 @@ DEF_INTERNAL_FN (BITINTTOFLOAT, ECF_PURE | ECF_LEAF, > ". R . ") > > DEF_INTERNAL_OPTAB_FN (BIT_ANDN, ECF_CONST, andn, binary) > > DEF_INTERNAL_OPTAB_FN (BIT_IORN, ECF_CONST, iorn, binary) > > > > +/* Pointer-to-member calling object wrapper. See > gimple-ptrmemfunc.cc. */ > > +DEF_INTERNAL_FN (PTRMEMFUNC_OBJ, ECF_CONST | ECF_LEAF | ECF_NOTHROW, > NULL) > > + > > #undef DEF_INTERNAL_WIDENING_OPTAB_FN > > #undef DEF_INTERNAL_SIGNED_COND_FN > > #undef DEF_INTERNAL_COND_FN > > diff --git a/gcc/passes.def b/gcc/passes.def > > index 9095c134f49..78ce50d3282 100644 > > --- a/gcc/passes.def > > +++ b/gcc/passes.def > > @@ -97,6 +97,9 @@ along with GCC; see the file COPYING3. If not see > > NEXT_PASS (pass_sccopy); > > NEXT_PASS (pass_merge_phi); > > NEXT_PASS (pass_dse); > > + /* This pass must be after forwprop since it only scans > functions with > > + PENDING_TODO_ptrmemfunc_devirt. */ > > + NEXT_PASS (pass_ptrmemfunc); > > NEXT_PASS (pass_cd_dce, false /* update_address_taken_p */, > true /* remove_unused_locals */); > > NEXT_PASS (pass_phiopt, true /* early_p */); > > /* Cleanup eh is done before tail recusision to remove empty > (only clobbers) > > @@ -232,6 +235,9 @@ along with GCC; see the file COPYING3. If not see > > NEXT_PASS (pass_build_alias); > > NEXT_PASS (pass_return_slot); > > NEXT_PASS (pass_fre, true /* may_iterate */); > > + /* This pass must be after forwprop since it only scans functions > with > > + PENDING_TODO_ptrmemfunc_devirt. */ > > + NEXT_PASS (pass_ptrmemfunc); > > NEXT_PASS (pass_merge_phi); > > NEXT_PASS (pass_thread_jumps_full, /*first=*/true); > > NEXT_PASS (pass_vrp, false /* final_p*/); > > diff --git a/gcc/testsuite/g++.dg/opt/devirtpmf1.C > b/gcc/testsuite/g++.dg/opt/devirtpmf1.C > > new file mode 100644 > > index 00000000000..aad7f43a5fc > > --- /dev/null > > +++ b/gcc/testsuite/g++.dg/opt/devirtpmf1.C > > @@ -0,0 +1,128 @@ > > +// GIMPLE tree devirtualizable cases > > +// { dg-do compile { target c++11 } } > > +// { dg-options "-O2 -Wno-inaccessible-base" } > > + > > +// Plain > > +struct SA > > +{ > > + virtual void f1 (); > > + virtual void f2 (); > > + virtual void f3 (); > > +}; > > +struct SB > > +{ > > + virtual void g1 (); > > + virtual void g2 (); > > + virtual void g3 (); > > +}; > > +struct S final : SA, SB > > +{ > > + virtual void h1 (); > > + virtual void h2 (); > > + virtual void h3 (); > > +}; > > + > > +void > > +func1 (S *obj, S &ref) > > +{ > > + // { dg-final { scan-assembler-times "_ZN2SA2f1Ev" 2 } } > > + // { dg-final { scan-assembler-times "_ZN2SB2g2Ev" 2 } } > > + // { dg-final { scan-assembler-times "_ZN1S2h3Ev" 2 } } > > + (ref.*&SA::f1) (); > > + (ref.*&SB::g2) (); > > + (ref.*&S::h3) (); > > + (obj->*&SA::f1) (); > > + (obj->*&SB::g2) (); > > + (obj->*&S::h3) (); > > +} > > + > > +// Virtual inheritance > > +struct TB > > +{ > > + virtual void foo (); > > + virtual void bar (); > > +}; > > +struct TL : virtual TB { virtual void foo (); }; > > +struct TR : virtual TB { virtual void bar (); }; > > +struct T final : TL, TR {}; > > + > > +void > > +func2 (T *obj) > > +{ > > + // { dg-final { scan-assembler-times "_ZN2TL3fooEv" 1 } } > > + // { dg-final { scan-assembler-times "_ZTv0_n\\d+_N2TR3barEv" 2 } } > > + (obj->*(&TB::foo)) (); > > + (obj->*(&TB::bar)) (); > > + (obj->*(&TL::bar)) (); > > +} > > + > > +// Ambiguous base > > +struct U0 > > +{ > > + virtual void foo (); > > +}; > > +struct U1 : U0 {}; > > +struct U2 : U0, U1 > > +{ > > + virtual void foo () final; > > +}; > > + > > +void > > +func3 (U2 *obj) > > +{ > > + // { dg-final { scan-assembler "_ZN2U23fooEv" } } > > + // { dg-final { scan-assembler "_ZThn\\d+_N2U23fooEv" } } > > + (obj->*(&U2::foo)) (); > > + (obj->*static_cast<void (U2::*) ()> (static_cast<void (U1::*) ()> > (&U0::foo))) (); > > +} > > + > > +// Duplicated base > > +struct VB { virtual void f (); }; > > +struct VL : VB { void f () final; }; > > +struct VR : VB { void f () final; }; > > +struct V : VL, VR {}; > > + > > +void > > +func4 (V *obj) > > +{ > > + // { dg-final { scan-assembler "_ZN2VL1fEv" } } > > + (obj->*static_cast<void (V::*) ()> (&VL::f)) (); > > +} > > + > > +// Covariant return, secondary base > > +struct WL { virtual void plain (); }; > > +struct WR { virtual WR *clone (); }; > > +struct W : WL, WR { virtual W *clone () final; }; > > + > > +WR* > > +func5 (W *obj) > > +{ > > + // { dg-final { scan-assembler "_ZTchn\\d+_h\\d+_N1W5cloneEv" } } > > + return (obj->*&WR::clone) (); > > +} > > + > > +// Covariant return, primary base > > +struct XL { virtual XL *clone (); }; > > +struct XR { virtual void plain (); }; > > +struct X : XL, XR { virtual X *clone () final; }; > > + > > +XL* > > +func6 (X *obj) > > +{ > > + // { dg-final { scan-assembler "_ZN1X5cloneEv" } } > > + return (obj->*&XL::clone) (); > > +} > > + > > +// Empty base > > +struct YE {}; > > +struct Y1 { virtual void foo () final; }; > > +struct Y2 { virtual void bar () final; }; > > +struct Y : Y1, YE, Y2 {}; > > + > > +void > > +func7 (Y *obj) > > +{ > > + // { dg-final { scan-assembler "_ZN2Y23barEv" } } > > + (obj->*(&Y2::bar)) (); > > +} > > + > > diff --git a/gcc/testsuite/g++.dg/opt/devirtpmf2.C > b/gcc/testsuite/g++.dg/opt/devirtpmf2.C > > new file mode 100644 > > index 00000000000..16fead19ef0 > > --- /dev/null > > +++ b/gcc/testsuite/g++.dg/opt/devirtpmf2.C > > @@ -0,0 +1,63 @@ > > +// GIMPLE tree impossible (ill-formed) calls > > +// { dg-do compile { target c++11 } } > > +// { dg-options "-O3 -fdump-tree-ptrmemfunc" } > > +// { dg-final { scan-tree-dump-times "giving __builtin_unreachable > \\\(\\\)" 5 "ptrmemfunc1" } } > > +// { dg-final { scan-tree-dump "Removing basic block" "ptrmemfunc1" } } > > + > > + > > +struct A { virtual void foo (int); }; > > +struct B { virtual void bar (int); }; > > +struct C : A, B > > +{ > > + virtual void foo (int) final; > > + virtual void bar (int) final; > > +}; > > +struct D : B { virtual void bar2 (int); }; > > + > > +void > > +func (C *obj, int arg) > > +{ > > + (obj > > + ->*(static_cast<void (C::*) (int)> ( > > + static_cast<void (B::*) (int)> (&D::bar2)))) (arg); > > +} > > + > > +void > > +func2 (C *obj, int arg) > > +{ > > + (obj > > + ->*(static_cast<void (C::*) (int)> ( > > + static_cast<void (B::*) (int)> (&D::bar2)))) (arg); > > + obj->foo (arg); > > +} > > + > > +struct X { virtual void foo (int); }; > > +struct Y final : X { virtual void foo (int) override; }; > > +struct Z : X { virtual void bar (int); }; > > + > > +void > > +func3 (Y *obj, int arg) > > +{ > > + (obj->*(static_cast<void (X::*) (int)> (&Z::bar))) (arg); > > +} > > + > > +struct P { virtual void foo (int); }; > > + > > +struct Q : P { virtual ~Q(); }; > > + > > +struct R : P > > +{ > > + virtual void fake_dtor1 (); > > + virtual void fake_dtor2 (); > > +}; > > + > > +void > > +func4 (Q *obj) > > +{ > > + (obj->*(static_cast<void (P::*) ()> (&R::fake_dtor1))) (); > > +} > > +void > > +func5 (Q *obj) > > +{ > > + (obj->*(static_cast<void (P::*) ()> (&R::fake_dtor2))) (); > > +} > > diff --git a/gcc/testsuite/g++.dg/opt/devirtpmf3.C > b/gcc/testsuite/g++.dg/opt/devirtpmf3.C > > new file mode 100644 > > index 00000000000..2deace7b2c6 > > --- /dev/null > > +++ b/gcc/testsuite/g++.dg/opt/devirtpmf3.C > > @@ -0,0 +1,57 @@ > > +// GIMPLE tree not devirtualizable tests > > +// { dg-do compile { target c++11 } } > > +// { dg-options "-O3 -fdump-tree-ptrmemfunc" } > > +// { dg-final { scan-tree-dump-not "giving __builtin_unreachable > \\\(\\\)" "ptrmemfunc1" } } > > +// { dg-final { scan-tree-dump-not "Direct call, calling to" > "ptrmemfunc1" } } > > +// { dg-final { scan-tree-dump-not "\\.PTRMEMFUNC_OBJ" "ptrmemfunc1" } } > > +// { dg-final { scan-tree-dump "Giving up: inner type.*X.*is not > polymorphic" "ptrmemfunc1" } } > > + > > + > > +// Not final > > +struct S { virtual void foo (); }; > > + > > +void > > +func1 (S *obj) > > +{ > > + (obj->*(&S::foo)) (); > > +} > > + > > +// Not final, from primary derived > > +struct T1 { virtual void foo (); }; > > +struct T2 : T1 { virtual void bar (); }; > > + > > +void > > +func2 () > > +{ > > + T2 obj; > > + T1 &base = obj; > > + (base.*(static_cast<void (T1::*) ()> (&T2::bar))) (); > > +} > > + > > +void > > +func3 (T1 *obj) > > +{ > > + (obj->*(static_cast<void (T1::*) ()> (&T2::bar))) (); > > +} > > + > > +// Negative offset > > +struct A { virtual void foo (); }; > > +struct B { virtual void bar (); }; > > +struct C : A, B {}; > > + > > +void > > +func4 (B *b) > > +{ > > + (b->*(static_cast<void (B::*) ()> > > + (static_cast<void (C::*) ()> (&A::foo)))) (); > > +} > > + > > +// Non-polymorphic base > > +struct X {}; > > +struct Y : X { virtual void bar () final; }; > > + > > +void > > +func5 (X *x) > > +{ > > + (x->*(static_cast<void (X::*) ()> (&Y::bar))) (); > > +} > > diff --git a/gcc/testsuite/g++.dg/opt/devirtpmf4.C > b/gcc/testsuite/g++.dg/opt/devirtpmf4.C > > new file mode 100644 > > index 00000000000..4dbe5c799b3 > > --- /dev/null > > +++ b/gcc/testsuite/g++.dg/opt/devirtpmf4.C > > @@ -0,0 +1,45 @@ > > +// GIMPLE tree devirtualizable tests, with offset adjusting operations > > +// { dg-do compile { target c++11 } } > > +// { dg-options "-O2" } > > +// { dg-final { scan-assembler-times "_ZN1B6abcabcEv" 8 } } > > + > > + > > +struct A { virtual void abcabc (); }; > > +struct B : A { void abcabc () final; }; > > + > > +void > > +func1 (B *arr[16]) > > +{ > > + (arr[6]->*&B::abcabc) (); > > +} > > + > > +void > > +func2 (B *arr[16]) > > +{ > > + (*(arr + 7)->*&B::abcabc) (); > > +} > > + > > +struct C > > +{ > > + int a[16]; > > + B b; > > + int c[32]; > > + B d[2]; > > + B* e; > > +}; > > + > > +void > > +func3 (C *c) > > +{ > > + (&(c->b)->*&B::abcabc) (); > > + ((c->d + 1)->*&B::abcabc) (); > > + ((c->e)->*&B::abcabc) (); > > +} > > + > > +void > > +func4 (C c[16]) > > +{ > > + (&((c + 6)->b)->*&A::abcabc) (); > > + (((c + 7)->d + 1)->*&A::abcabc) (); > > + (((c + 8)->e)->*&A::abcabc) (); > > +} > > diff --git a/gcc/testsuite/g++.dg/opt/devirtpmf5.C > b/gcc/testsuite/g++.dg/opt/devirtpmf5.C > > new file mode 100644 > > index 00000000000..afacc3b90f5 > > --- /dev/null > > +++ b/gcc/testsuite/g++.dg/opt/devirtpmf5.C > > @@ -0,0 +1,72 @@ > > +// GIMPLE tree devirtualizable tests, with template expansions > > +// { dg-do compile { target c++11 } } > > +// { dg-options "-O2" } > > + > > + > > +template<typename PMF, PMF mem, typename Cast = PMF, typename T> > > +[[gnu::always_inline]] inline void > > +dispatch (T *obj) { (obj->*static_cast<Cast> (mem)) (); } > > + > > +// Plain > > +struct SA > > +{ > > + virtual void f1 (); > > + virtual void f2 (); > > + virtual void f3 (); > > +}; > > +struct SB > > +{ > > + virtual void g1 (); > > + virtual void g2 (); > > + virtual void g3 (); > > +}; > > +struct S final : SA, SB > > +{ > > + virtual void h1 (); > > + virtual void h2 (); > > + virtual void h3 (); > > +}; > > + > > +void > > +func1 (S *obj) > > +{ > > + // { dg-final { scan-assembler "_ZN2SA2f1Ev" } } > > + // { dg-final { scan-assembler "_ZN2SB2g2Ev" } } > > + // { dg-final { scan-assembler "_ZN1S2h3Ev" } } > > + dispatch <decltype (&SA::f1), &SA::f1> (obj); > > + dispatch <decltype (&SB::g2), &SB::g2> (obj); > > + dispatch <decltype (&S::h3), &S::h3> (obj); > > +} > > + > > +struct TB > > +{ > > + virtual void foo (); > > + virtual void bar (); > > +}; > > +struct TL : virtual TB { virtual void foo (); }; > > +struct TR : virtual TB { virtual void bar (); }; > > +struct T final : TL, TR {}; > > + > > +void > > +func2 (T *obj) > > +{ > > + // { dg-final { scan-assembler-times "_ZN2TL3fooEv" 1 } } > > + // { dg-final { scan-assembler-times "_ZTv0_n\\d+_N2TR3barEv" 2 } } > > + dispatch <decltype (&TB::foo), &TB::foo> (obj); > > + dispatch <decltype (&TB::bar), &TB::bar> (obj); > > + dispatch <decltype (&TL::bar), &TL::bar> (obj); > > +} > > + > > +// Duplicated base > > +struct UB { virtual void f (); }; > > +struct UL : UB { void f () final; }; > > +struct UR : UB { void f () final; }; > > +struct U : UL, UR {}; > > + > > +void > > +func4 (U *obj) > > +{ > > + // { dg-final { scan-assembler "_ZN2UL1fEv" } } > > + dispatch<decltype (&UL::f), &UL::f> (obj); > > + dispatch<decltype (&UL::f), &UL::f, void (U::*) ()> (obj); > > +} > > diff --git a/gcc/timevar.def b/gcc/timevar.def > > index fc78600b652..ea20ffebde6 100644 > > --- a/gcc/timevar.def > > +++ b/gcc/timevar.def > > @@ -324,6 +324,7 @@ DEFTIMEVAR (TV_WARN_ACCESS , "access > analysis") > > DEFTIMEVAR (TV_GIMPLE_CRC_OPTIMIZATION, "crc optimization") > > DEFTIMEVAR (TV_EXT_DCE , "ext dce") > > DEFTIMEVAR (TV_FOLD_MEM_OFFSETS , "fold mem offsets") > > +DEFTIMEVAR (TV_GIMPLE_PTRMEMFUNC , "polymorphic pmf devirt") > > > > /* Everything else in rest_of_compilation not included above. */ > > DEFTIMEVAR (TV_EARLY_LOCAL , "early local passes") > > diff --git a/gcc/tree-pass.h b/gcc/tree-pass.h > > index a3b35e009e0..4d0b6c42a71 100644 > > --- a/gcc/tree-pass.h > > +++ b/gcc/tree-pass.h > > @@ -322,6 +322,8 @@ protected: > > /* Tell the next scalar cleanup pass that there is > > work for it to do. */ > > #define PENDING_TODO_force_next_scalar_cleanup (1 << 1) > > +/* Tell the gimple-ptrmemfunc pass that there is work for it to do. */ > > +#define PENDING_TODO_ptrmemfunc_devirt (1 << 2) > > > > /* Register pass info. */ > > > > @@ -510,6 +512,7 @@ extern gimple_opt_pass *make_pass_modref > (gcc::context *ctxt); > > extern gimple_opt_pass *make_pass_coroutine_lower_builtins > (gcc::context *ctxt); > > extern gimple_opt_pass *make_pass_coroutine_early_expand_ifns > (gcc::context *ctxt); > > extern gimple_opt_pass *make_pass_adjust_alignment (gcc::context *ctxt); > > +extern gimple_opt_pass *make_pass_ptrmemfunc (gcc::context *ctxt); > > > > /* IPA Passes */ > > extern simple_ipa_opt_pass *make_pass_ipa_lower_emutls (gcc::context > *ctxt); > > diff --git a/gcc/tree-ssa-forwprop.cc b/gcc/tree-ssa-forwprop.cc > > index 75f06c6ba41..57b5350ba23 100644 > > --- a/gcc/tree-ssa-forwprop.cc > > +++ b/gcc/tree-ssa-forwprop.cc > > @@ -5919,6 +5919,10 @@ pass_forwprop::execute (function *fun) > > if (callee != NULL_TREE > > && fndecl_built_in_p (callee, BUILT_IN_NORMAL)) > > changed |= simplify_builtin_call (&gsi, callee, > full_walk); > > + /* Tag a pending_TODO to avoid walking unnecessary > functions > > + in gimple-ptrmemfunc. */ > > + if (gimple_call_internal_p (stmt, > IFN_PTRMEMFUNC_OBJ)) > > + fun->pending_TODOs |= > PENDING_TODO_ptrmemfunc_devirt; > > break; > > } > > > > -- > > 2.43.0 > > >