This is a revised version of my earlier dynamic_cast final type patch:
https://gcc.gnu.org/pipermail/gcc-patches/2026-January/706155.html

The previous version added -fassume-unique-vtables and used a vptr
comparison for final targets when that option was enabled.  This version
drops the option and only optimizes the cases discussed as safe in
PR c++/63164:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63164

For dynamic_cast to a final target type, when the source static type is
an accessible unambiguous base of the final target and the target vtable
has external non-vague linkage, the ABI gives the vtable a single emitted
definition.  In that case the frontend can compare the source object's
vptr against the target vtable address to test whether the dynamic type
is exactly the final target type.

The patch also handles final targets with no base of the source static
type.  Such casts can never succeed, so pointer casts are folded to null
and reference casts are folded to std::bad_cast, while preserving
evaluation of the dynamic_cast operand.

gcc/cp/ChangeLog:
        PR c++/63164
        * rtti.cc (build_dynamic_cast_1): Fold impossible dynamic_casts
        to final target types.  Use a vptr comparison when the final target
        vtable has external non-vague linkage.

gcc/testsuite/ChangeLog:
        PR c++/63164
        * g++.dg/rtti/dyncast9.C: New test.
        * g++.dg/rtti/dyncast10.C: New test.
        * g++.dg/rtti/dyncast11.C: New test.

Signed-off-by: Thomas de Bock <[email protected]>
---
Changes since v1:
- Drop the -fassume-unique-vtables option.
- Restrict vptr comparison to final targets whose vtable has external
  non-vague linkage.
- Fold impossible casts when the final target has no base of the source
  static type.
- Add tests for the optimized cases and for fallback cases where
  __dynamic_cast is still needed.
 gcc/cp/rtti.cc                        | 107
+++++++++++++++++++++++++++++++---
 gcc/testsuite/g++.dg/rtti/dyncast10.C |  39 +++++++++++++
 gcc/testsuite/g++.dg/rtti/dyncast11.C |  43 ++++++++++++++
 gcc/testsuite/g++.dg/rtti/dyncast9.C  |  53 +++++++++++++++++
 4 files changed, 235 insertions(+), 7 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/rtti/dyncast10.C
 create mode 100644 gcc/testsuite/g++.dg/rtti/dyncast11.C
 create mode 100644 gcc/testsuite/g++.dg/rtti/dyncast9.C
diff --git a/gcc/cp/rtti.cc b/gcc/cp/rtti.cc
index 1e787157b7b..802c012f9b1 100644
--- a/gcc/cp/rtti.cc
+++ b/gcc/cp/rtti.cc
@@ -745,6 +745,106 @@ build_dynamic_cast_1 (location_t loc, tree type, tree
expr,

          target_type = TYPE_MAIN_VARIANT (TREE_TYPE (type));
          static_type = TYPE_MAIN_VARIANT (TREE_TYPE (exprtype));
+
+         /* Since expr is used twice below, save it.  */
+         expr = save_expr (expr);
+
+         expr1 = expr;
+         if (tc == REFERENCE_TYPE)
+           expr1 = cp_build_addr_expr (expr1, complain);
+
+         /* A final class, or a class with a final virtual destructor,
cannot
+            be further derived.  */
+         bool final_p = TYPE_FINAL_P (target_type);
+         for (tree v = BINFO_VIRTUALS (TYPE_BINFO (target_type));
+              v && !final_p; v = TREE_CHAIN (v))
+           if (DECL_DESTRUCTOR_P (BV_FN (v)) && DECL_FINAL_P (BV_FN (v)))
+             final_p = true;
+
+         if (final_p)
+           {
+             /* If source static type is not a base of target type, the
cast
+                can never succeed.  */
+             tree binfo = lookup_base (target_type, static_type, ba_any,
+                                         NULL, tf_none);
+             if (!binfo)
+               {
+                 if (complain & tf_warning)
+                   warning_at (loc, 0,
+                               "%<dynamic_cast<%#T>(%E)%> can never
succeed",
+                               type, old_expr);
+                 if (tc == REFERENCE_TYPE)
+                   {
+                     tree bad = throw_bad_cast ();
+                     TREE_TYPE (bad) = type;
+                     return cp_build_compound_expr (expr, bad, complain);
+                   }
+                 return build_if_nonnull (expr, build_zero_cst (type),
+                                          complain);
+               }
+
+             binfo = lookup_base (target_type, static_type, ba_check,
+                                  NULL, tf_none);
+             if (binfo && binfo != error_mark_node)
+               {
+                 tree vtbl_binfo = BINFO_VTABLE (binfo) ? binfo
+                                                   : TYPE_BINFO
(target_type);
+                 tree vtbl = get_vtbl_decl_for_binfo (vtbl_binfo);
+                 bool unique_vtable = false;
+                 if (!CLASSTYPE_USE_TEMPLATE (target_type))
+                   {
+                     tree cdecl = TYPE_NAME (target_type);
+                     if (DECL_LANG_SPECIFIC (cdecl)
+                         && DECL_MODULE_ATTACH_P (cdecl))
+                       unique_vtable = true;
+                     else
+                       {
+                         tree key_method = CLASSTYPE_KEY_METHOD
(target_type);
+                         if (key_method
+                             && !DECL_DECLARED_INLINE_P (key_method))
+                           unique_vtable = true;
+                       }
+                   }
+
+                 /* For a final target, the ba_check lookup found an
+                    accessible unambiguous STATIC_TYPE base.  With an
external
+                    non-vague target vtable, vptr equality tests whether
the
+                    dynamic type is exactly TARGET_TYPE.  */
+                 if (vtbl && TREE_PUBLIC (vtbl) && unique_vtable)
+                   {
+                     tree succ = build_base_path (MINUS_EXPR, expr1, binfo,
+                                                  true, tf_none);
+                     if (succ != error_mark_node)
+                       {
+                         tree trgt_vptr = build_vtbl_address (vtbl_binfo);
+                         tree src_obj = cp_build_fold_indirect_ref (expr1);
+                         tree src_vptr = build_vfield_ref (src_obj,
+                                                         static_type);
+                         tree cond = cp_build_binary_op (loc, EQ_EXPR,
+                                                         trgt_vptr,
src_vptr,
+                                                         complain);
+                         if (cond == error_mark_node)
+                           return error_mark_node;
+                         if (tc != REFERENCE_TYPE)
+                           succ = cp_convert (type, succ, complain);
+                         if (succ == error_mark_node)
+                           return error_mark_node;
+
+                         tree fail = (tc == REFERENCE_TYPE
+                                      ? throw_bad_cast ()
+                                      : build_zero_cst (type));
+                         tree result = build3 (COND_EXPR, TREE_TYPE (succ),
+                                               cond, succ, fail);
+                         SET_EXPR_LOCATION (result, loc);
+
+                         if (tc == REFERENCE_TYPE)
+                           return cp_convert (type, result, complain);
+                         return build_if_nonnull (expr, result, complain);
+                       }
+                   }
+               }
+           }
+
          td2 = get_tinfo_decl (target_type);
          if (!mark_used (td2, complain) && !(complain & tf_error))
            return error_mark_node;
@@ -757,13 +857,6 @@ build_dynamic_cast_1 (location_t loc, tree type, tree
expr,
          /* Determine how T and V are related.  */
          boff = dcast_base_hint (static_type, target_type);

-         /* Since expr is used twice below, save it.  */
-         expr = save_expr (expr);
-
-         expr1 = expr;
-         if (tc == REFERENCE_TYPE)
-           expr1 = cp_build_addr_expr (expr1, complain);
-
          elems[0] = expr1;
          elems[1] = td3;
          elems[2] = td2;
diff --git a/gcc/testsuite/g++.dg/rtti/dyncast10.C
b/gcc/testsuite/g++.dg/rtti/dyncast10.C
new file mode 100644
index 00000000000..d96b16b8e91
--- /dev/null
+++ b/gcc/testsuite/g++.dg/rtti/dyncast10.C
@@ -0,0 +1,39 @@
+// PR c++/63164
+// { dg-do run { target c++11 } }
+// { dg-additional-options "-fdump-tree-original" }
+// { dg-final { scan-tree-dump-not "__dynamic_cast" "original" } }
+
+extern "C" void abort ();
+
+int calls;
+
+struct A { virtual ~A (); };
+A::~A () {}
+
+struct D final { virtual ~D (); };
+D::~D () {}
+
+A *get (A *a) { ++calls; return a; }
+A& getr (A& a) { ++calls; return a; }
+
+bool f (A *a)
+{
+  return dynamic_cast<D *> (get (a)); // { dg-warning "can never succeed" }
+}
+
+bool g (A& a)
+{
+  try
+    {
+      dynamic_cast<D&> (getr (a)); // { dg-warning "can never succeed" }
+      return true;
+    }
+  catch (...) { return false; }
+}
+
+int main ()
+{
+  A a;
+  if (f (&a) || g (a) || calls != 2)
+    abort ();
+}
diff --git a/gcc/testsuite/g++.dg/rtti/dyncast11.C
b/gcc/testsuite/g++.dg/rtti/dyncast11.C
new file mode 100644
index 00000000000..9a62a815d85
--- /dev/null
+++ b/gcc/testsuite/g++.dg/rtti/dyncast11.C
@@ -0,0 +1,43 @@
+// PR c++/63164
+// { dg-do compile { target c++11 } }
+// { dg-additional-options "-fdump-tree-original" }
+// { dg-final { scan-tree-dump-times "__dynamic_cast" 6 "original" } }
+
+struct A { virtual ~A () {} };
+
+/* No key function, so the vtable has vague linkage.  */
+struct Vague final : A { };
+Vague *f1 (A *a) { return dynamic_cast<Vague *> (a); }
+
+/* A vptr comparison against a single A subobject is not enough when the
+   target has multiple A bases.  */
+struct X : A { };
+struct Y : A { };
+struct Amb final : X, Y { virtual void key (); };
+void Amb::key () {}
+Amb *f2 (A *a) { return dynamic_cast<Amb *> (a); }
+
+/* The source type must be a public base of the target.  */
+struct Priv final : private A { virtual void key (); };
+void Priv::key () {}
+Priv *f3 (A *a) { return dynamic_cast<Priv *> (a); }
+
+/* Nor can we use a fixed offset from a virtual base.  */
+struct VBase final : virtual A { virtual void key (); };
+void VBase::key () {}
+VBase *f4 (A *a) { return dynamic_cast<VBase *> (a); }
+
+/* Template class vtables are not uniquely emitted.  */
+template <class T>
+struct Tmpl final : A { virtual void key (); };
+template <class T>
+void Tmpl<T>::key () {}
+Tmpl<int> *f5 (A *a) { return dynamic_cast<Tmpl<int> *> (a); }
+
+/* The target vtable must have external linkage.  */
+namespace
+{
+struct Local final : A { virtual void key (); };
+void Local::key () {}
+}
+Local *f6 (A *a) { return dynamic_cast<Local *> (a); }
diff --git a/gcc/testsuite/g++.dg/rtti/dyncast9.C
b/gcc/testsuite/g++.dg/rtti/dyncast9.C
new file mode 100644
index 00000000000..d593048df38
--- /dev/null
+++ b/gcc/testsuite/g++.dg/rtti/dyncast9.C
@@ -0,0 +1,53 @@
+// PR c++/63164
+// { dg-do run { target c++11 } }
+// { dg-additional-options "-fdump-tree-original" }
+// { dg-final { scan-tree-dump-not "__dynamic_cast" "original" } }
+
+extern "C" void abort ();
+
+struct A { virtual ~A (); };
+A::~A () {}
+
+struct B : A
+{
+  ~B () final;
+  void p () { ++i; }
+  int i = 0;
+};
+B::~B () {}
+
+struct C final : A { ~C (); };
+C::~C () {}
+
+struct X { virtual ~X (); int x; };
+X::~X () {}
+
+struct D final : X, A { virtual void key (); };
+void D::key () {}
+
+B *f (A *a) { return dynamic_cast<B *> (a); }
+C *h (A *a) { return dynamic_cast<C *> (a); }
+D *k (A *a) { return dynamic_cast<D *> (a); }
+
+bool g (A& a)
+{
+  try { dynamic_cast<B&> (a).p (); return true; }
+  catch (...) { return false; }
+}
+
+int main ()
+{
+  A a;
+  B b;
+  C c;
+  D d;
+
+  if (f (&a) || h (&a) || k (&a) || g (a))
+    abort ();
+  if (f (&b) != &b || h (&b) || k (&b) || !g (b) || b.i != 1)
+    abort ();
+  if (f (&c) || h (&c) != &c || k (&c) || g (c))
+    abort ();
+  if (f (&d) || h (&d) || k (&d) != &d || g (d))
+    abort ();
+}

Reply via email to