For final target classes of which the source type is a unique non-
virtual base, we know that the dynamic_cast succeeding is equivalent to
the vptr pointing to the target's vtable. So check this if possible
instead of calling the more expensive `__dynamic_cast` runtime method.

This might be extended with the handling of virtual bases.

This optimization has been present in Clang since version 17 [1].

This might break[2] if the address of a particular vtable is not the
same throughout the program, i.e. when multiple shared libraries contain
it, but the loader does not canonicalize all references to the same
instance (because of hidden linkage or `-Bsymbolic`). For now, let's put
this behind an opt-in `-fassume-unique-vtables` flag.

[1] 
https://github.com/llvm/llvm-project/commit/9d525bf94b255df89587db955b5fa2d3c03c2c3e
[2] https://github.com/llvm/llvm-project/issues/120129

        PR c++/63164

gcc/c-family/ChangeLog:

        * c.opt: Add -fassume-unique-vtables.

gcc/cp/ChangeLog:

        * rtti.cc (build_dynamic_cast_node): New helper function.
        (build_dynamic_cast_1): Compare vptr directly to target vtable
        address when the target is a final class type.

gcc/testsuite/ChangeLog:

        * g++.dg/rtti/dyncast-exact.C: New test.
        * g++.dg/rtti/dyncast-non-exact.C: New test.
---
 gcc/c-family/c.opt                            |   4 +
 gcc/cp/rtti.cc                                | 120 ++++++++++++------
 gcc/testsuite/g++.dg/rtti/dyncast-exact.C     |  90 +++++++++++++
 gcc/testsuite/g++.dg/rtti/dyncast-non-exact.C |  42 ++++++
 4 files changed, 214 insertions(+), 42 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/rtti/dyncast-exact.C
 create mode 100644 gcc/testsuite/g++.dg/rtti/dyncast-non-exact.C

diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index 75b6531860e..dc0b2614a2e 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -1703,6 +1703,10 @@ fassume-sane-operators-new-delete
 C++ ObjC++ Optimization Var(flag_assume_sane_operators_new_delete) Init(1)
 Assume C++ replaceable global operators new, new[], delete, delete[] don't 
read or write visible global state.
 
+fassume-unique-vtables
+C++ ObjC++ Optimization Var(flag_assume_unique_vtables) Init(0)
+Assume that each class type has a unique vtable address across the entire 
program.
+
 ; Define extra predefined macros for use in libgcc.
 fbuilding-libgcc
 C ObjC C++ ObjC++ Undocumented Var(flag_building_libgcc)
diff --git a/gcc/cp/rtti.cc b/gcc/cp/rtti.cc
index c06a18b3ff1..169e8fd30d1 100644
--- a/gcc/cp/rtti.cc
+++ b/gcc/cp/rtti.cc
@@ -122,6 +122,7 @@ vec<tree, va_gc> *unemitted_tinfo_decls;
 static GTY (()) vec<tinfo_s, va_gc> *tinfo_descs;
 
 static tree tinfo_name (tree, bool);
+static tree build_dynamic_cast_node (void);
 static tree build_dynamic_cast_1 (location_t, tree, tree, tsubst_flags_t);
 static tree throw_bad_cast (void);
 static tree throw_bad_typeid (void);
@@ -559,6 +560,36 @@ build_if_nonnull (tree test, tree result, tsubst_flags_t 
complain)
   return cond;
 }
 
+static tree
+build_dynamic_cast_node (void)
+{
+  if (dynamic_cast_node)
+    return dynamic_cast_node;
+
+  unsigned flags = push_abi_namespace ();
+  tree tinfo_ptr = xref_tag (class_type, get_identifier ("__class_type_info"));
+  tinfo_ptr = cp_build_qualified_type (tinfo_ptr, TYPE_QUAL_CONST);
+  tinfo_ptr = build_pointer_type (tinfo_ptr);
+
+  const char *fn_name = "__dynamic_cast";
+  /* void *() (void const *, __class_type_info const *,
+              __class_type_info const *, ptrdiff_t)  */
+  tree fn_type
+    = build_function_type_list (ptr_type_node, const_ptr_type_node, tinfo_ptr,
+                                tinfo_ptr, ptrdiff_type_node, NULL_TREE);
+
+  tree dcast_fn = build_library_fn_ptr (fn_name, fn_type,
+                                        ECF_LEAF | ECF_PURE | ECF_NOTHROW);
+  /* As with __cxa_atexit in get_atexit_node.  */
+  DECL_CONTEXT (dcast_fn) = FROB_CONTEXT (current_namespace);
+  DECL_SOURCE_LOCATION (dcast_fn) = BUILTINS_LOCATION;
+  dcast_fn = pushdecl (dcast_fn, /*hiding=*/true);
+  pop_abi_namespace (flags);
+  dynamic_cast_node = dcast_fn;
+
+  return dynamic_cast_node;
+}
+
 /* Execute a dynamic cast, as described in section 5.2.6 of the 9/93 working
    paper.  */
 
@@ -568,7 +599,6 @@ build_dynamic_cast_1 (location_t loc, tree type, tree expr,
 {
   enum tree_code tc = TREE_CODE (type);
   tree exprtype;
-  tree dcast_fn;
   tree old_expr = expr;
   const char *errstr = NULL;
 
@@ -694,8 +724,7 @@ build_dynamic_cast_1 (location_t loc, tree type, tree expr,
       else
        {
          tree retval;
-         tree result, td2, td3;
-         tree elems[4];
+         tree result;
          tree static_type, target_type, boff;
 
          /* If we got here, we can't convert statically.  Therefore,
@@ -742,14 +771,6 @@ 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));
-         td2 = get_tinfo_decl (target_type);
-         if (!mark_used (td2, complain) && !(complain & tf_error))
-           return error_mark_node;
-         td2 = cp_build_addr_expr (td2, complain);
-         td3 = get_tinfo_decl (static_type);
-         if (!mark_used (td3, complain) && !(complain & tf_error))
-           return error_mark_node;
-         td3 = cp_build_addr_expr (td3, complain);
 
          /* Determine how T and V are related.  */
          boff = dcast_base_hint (static_type, target_type);
@@ -761,39 +782,54 @@ build_dynamic_cast_1 (location_t loc, tree type, tree 
expr,
          if (tc == REFERENCE_TYPE)
            expr1 = cp_build_addr_expr (expr1, complain);
 
-         elems[0] = expr1;
-         elems[1] = td3;
-         elems[2] = td2;
-         elems[3] = boff;
-
-         dcast_fn = dynamic_cast_node;
-         if (!dcast_fn)
+         /* If the target is a final class, and the static type is a unique
+            non-virtual base of it, we can directly compare the vptr with the
+            corresponding vtable address.  */
+         if (flag_assume_unique_vtables && CLASSTYPE_FINAL (target_type)
+             && int_cst_value (boff) >= 0)
            {
-             unsigned flags = push_abi_namespace ();
-             tree tinfo_ptr = xref_tag (class_type,
-                                        get_identifier ("__class_type_info"));
-             tinfo_ptr = cp_build_qualified_type (tinfo_ptr, TYPE_QUAL_CONST);
-             tinfo_ptr = build_pointer_type (tinfo_ptr);
-
-             const char *fn_name = "__dynamic_cast";
-             /* void *() (void const *, __class_type_info const *,
-                          __class_type_info const *, ptrdiff_t)  */
-             tree fn_type = (build_function_type_list
-                             (ptr_type_node, const_ptr_type_node,
-                              tinfo_ptr, tinfo_ptr, ptrdiff_type_node,
-                              NULL_TREE));
-             dcast_fn = (build_library_fn_ptr
-                         (fn_name, fn_type, ECF_LEAF | ECF_PURE | 
ECF_NOTHROW));
-             /* As with __cxa_atexit in get_atexit_node.  */
-             DECL_CONTEXT (dcast_fn) = FROB_CONTEXT (current_namespace);
-             DECL_SOURCE_LOCATION (dcast_fn) = BUILTINS_LOCATION;
-             dcast_fn = pushdecl (dcast_fn, /*hiding=*/true);
-             pop_abi_namespace (flags);
-             dynamic_cast_node = dcast_fn;
+             tree binfo = lookup_base (target_type, static_type, ba_check,
+                                       NULL, complain);
+             if (!binfo || binfo == error_mark_node)
+               return error_mark_node;
+
+             tree target_vtbl = build_vtbl_address (
+               BINFO_VTABLE (binfo) ? binfo : TYPE_BINFO (target_type));
+             tree expr_vtbl
+               = build_vfield_ref (cp_build_fold_indirect_ref (expr),
+                                   static_type);
+
+             tree cmp
+               = build2 (EQ_EXPR, boolean_type_node, target_vtbl, expr_vtbl);
+
+             /* Effectively do a base-to-derived static_cast but without vptr
+                sanitizer checks.  */
+             tree converted = build_base_path (MINUS_EXPR, expr1, binfo,
+                                               /*nonnull=*/false, complain);
+
+             result = build3 (COND_EXPR, ptr_type_node, cmp, converted,
+                              nullptr_node);
            }
-         if (dcast_fn == error_mark_node)
-           return error_mark_node;
-         result = build_cxx_call (dcast_fn, 4, elems, complain);
+         else
+           {
+             tree td2 = get_tinfo_decl (target_type);
+             if (!mark_used (td2, complain) && !(complain & tf_error))
+               return error_mark_node;
+             td2 = cp_build_addr_expr (td2, complain);
+
+             tree td3 = get_tinfo_decl (static_type);
+             if (!mark_used (td3, complain) && !(complain & tf_error))
+               return error_mark_node;
+             td3 = cp_build_addr_expr (td3, complain);
+
+             tree elems[4] = { expr1, td3, td2, boff };
+
+             tree dcast_fn = build_dynamic_cast_node ();
+             if (dcast_fn == error_mark_node)
+               return error_mark_node;
+             result = build_cxx_call (dcast_fn, 4, elems, complain);
+           }
+
          SET_EXPR_LOCATION (result, loc);
 
          if (tc == REFERENCE_TYPE)
diff --git a/gcc/testsuite/g++.dg/rtti/dyncast-exact.C 
b/gcc/testsuite/g++.dg/rtti/dyncast-exact.C
new file mode 100644
index 00000000000..c59a3ccf081
--- /dev/null
+++ b/gcc/testsuite/g++.dg/rtti/dyncast-exact.C
@@ -0,0 +1,90 @@
+// { dg-do run { target c++11 } }
+// { dg-options "-fassume-unique-vtables -fdump-tree-original" }
+// { dg-final { scan-tree-dump-not "__dynamic_cast" "original" } }
+
+struct A
+{
+  virtual void f () {}
+};
+
+struct B final : A { };
+
+bool
+test_single (A *a)
+{
+  return dynamic_cast<B *> (a) != nullptr;
+}
+
+bool
+test_ref (A &a)
+{
+  try
+    {
+      B &b = dynamic_cast<B &> (a);
+      return true;
+    }
+  catch (...)
+    {
+      return false;
+    }
+}
+
+struct C
+{
+  virtual void g () {}
+};
+
+struct D final : A, C { };
+
+bool
+test_multi (C *c)
+{
+  return dynamic_cast<D *> (c) != nullptr;
+}
+
+struct E : A { };
+
+struct F final : E { };
+
+bool
+test_chain (E *e)
+{
+  return dynamic_cast<F *> (e) != nullptr;
+}
+
+int
+main ()
+{
+  A a;
+  B b;
+
+  if (test_single (&a))
+    __builtin_abort ();
+
+  if (test_ref (a))
+    __builtin_abort ();
+
+  if (!test_single (&b))
+    __builtin_abort ();
+
+  if (!test_ref (b))
+    __builtin_abort ();
+
+  C c;
+  D d;
+
+  if (test_multi (&c))
+    __builtin_abort ();
+
+  if (!test_multi (&d))
+    __builtin_abort ();
+
+  E e;
+  F f;
+
+  if (test_chain (&e))
+    __builtin_abort ();
+
+  if (!test_chain (&f))
+    __builtin_abort ();
+}
diff --git a/gcc/testsuite/g++.dg/rtti/dyncast-non-exact.C 
b/gcc/testsuite/g++.dg/rtti/dyncast-non-exact.C
new file mode 100644
index 00000000000..364e2888a2c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/rtti/dyncast-non-exact.C
@@ -0,0 +1,42 @@
+// { dg-do run { target c++11 } }
+// { dg-options "-fassume-unique-vtables -fdump-tree-gimple" }
+// { dg-final { scan-assembler-times "__dynamic_cast" 2 "gimple" } }
+
+struct A
+{
+  virtual void f () {}
+};
+
+struct B : A { };
+
+bool
+test_ptr (A *a)
+{
+  return dynamic_cast<B *> (a) != nullptr;
+}
+
+bool
+test_ref (A &a)
+{
+  try
+    {
+      B &b = dynamic_cast<B &> (a);
+      return true;
+    }
+  catch (...)
+    {
+      return false;
+    }
+}
+
+int
+main ()
+{
+  B b;
+
+  if (!test_ptr (&b))
+    __builtin_abort ();
+
+  if (!test_ref (b))
+    __builtin_abort ();
+}
-- 
2.39.5 (Apple Git-154)


Reply via email to