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