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 (); +}
