On Mon, Aug 17, 2026 at 11:48 AM 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.
>
> 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.

Why would we add a new pass for this rather than integrating this with
the existing devirtualization framework we have?

> 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.
>         * tree-pass.h (make_pass_ptrmemfunc): Make gimple-ptrmemfunc
>         pass.
>         * gimple-ptrmemfunc.cc: New file.
>         * gimple-ptrmemfunc.h: New file.
>
> 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.
>
> Signed-off-by: Chi Wang <[email protected]>
> ---
>  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              | 656 ++++++++++++++++++++++++++
>  gcc/gimple-ptrmemfunc.h               |  29 ++
>  gcc/internal-fn.cc                    |  11 +
>  gcc/internal-fn.def                   |   3 +
>  gcc/passes.def                        |   2 +
>  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                       |   1 +
>  17 files changed, 1103 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..8f83aded82c
> --- /dev/null
> +++ b/gcc/gimple-ptrmemfunc.cc
> @@ -0,0 +1,656 @@
> +/* 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;
> +    };
> +
> +  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;
> +
> +  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);
> +         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);
> +    };
> +
> +  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 *) final override
> +  {
> +    return optimize != 0 && flag_devirtualize;
> +  }
> +  unsigned int execute (function *) final override;
> +
> +}; // class pass_ptrmemfunc
> +
> +unsigned int
> +pass_ptrmemfunc::execute (function *fun)
> +{
> +  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..04486cf2496 100644
> --- a/gcc/passes.def
> +++ b/gcc/passes.def
> @@ -97,6 +97,7 @@ along with GCC; see the file COPYING3.  If not see
>           NEXT_PASS (pass_sccopy);
>           NEXT_PASS (pass_merge_phi);
>            NEXT_PASS (pass_dse);
> +         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 +233,7 @@ 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 */);
> +      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..7952e79ce7d 100644
> --- a/gcc/tree-pass.h
> +++ b/gcc/tree-pass.h
> @@ -510,6 +510,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);
> --
> 2.43.0
>

Reply via email to