Re: [PATCH v2 1/3] Add a pass to devirtualize polymorphic PMF calls
Andrea Pinski <[email protected]>
| Newsgroups | gmane.comp.gcc.patches |
|---|---|
| Message-ID | <CALvbMcDkAQvcS6QEfp5xnUo+ieGWmA9KQk+9MNsWU9U4FVhAUQ@mail.gmail.com> |
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 >