[clang] 1b8ab2f - [clang] Implement pointer authentication for C++ virtual functions, v-tables, and VTTs (#94056)

via cfe-commits cfe-commits at lists.llvm.org
Wed Jun 26 18:35:15 PDT 2024


Author: Oliver Hunt
Date: 2024-06-26T18:35:10-07:00
New Revision: 1b8ab2f08998d3220e5d95003d47bb3d7cac966b

URL: https://github.com/llvm/llvm-project/commit/1b8ab2f08998d3220e5d95003d47bb3d7cac966b
DIFF: https://github.com/llvm/llvm-project/commit/1b8ab2f08998d3220e5d95003d47bb3d7cac966b.diff

LOG:  [clang] Implement pointer authentication for C++ virtual functions, v-tables, and VTTs (#94056)

Virtual function pointer entries in v-tables are signed with address
discrimination in addition to declaration-based discrimination, where an
integer discriminator the string hash (see
`ptrauth_string_discriminator`) of the mangled name of the overridden
method. This notably provides diversity based on the full signature of
the overridden method, including the method name and parameter types.
This patch introduces ItaniumVTableContext logic to find the original
declaration of the overridden method.
On AArch64, these pointers are signed using the `IA` key (the
process-independent code key.)

V-table pointers can be signed with either no discrimination, or a
similar scheme using address and decl-based discrimination. In this
case, the integer discriminator is the string hash of the mangled
v-table identifier of the class that originally introduced the vtable
pointer.
On AArch64, these pointers are signed using the `DA` key (the
process-independent data key.)

Not using discrimination allows attackers to simply copy valid v-table
pointers from one object to another. However, using a uniform
discriminator of 0 does have positive performance and code-size
implications on AArch64, and diversity for the most important v-table
access pattern (virtual dispatch) is already better assured by the
signing schemas used on the virtual functions. It is also known that
some code in practice copies objects containing v-tables with `memcpy`,
and while this is not permitted formally, it is something that may be
invasive to eliminate.

This is controlled by:
```
  -fptrauth-vtable-pointer-type-discrimination
  -fptrauth-vtable-pointer-address-discrimination
```

In addition, this provides fine-grained controls in the
ptrauth_vtable_pointer attribute, which allows overriding the default
ptrauth schema for vtable pointers on a given class hierarchy, e.g.:
```
  [[clang::ptrauth_vtable_pointer(no_authentication, no_address_discrimination, 
                                  no_extra_discrimination)]]
  [[clang::ptrauth_vtable_pointer(default_key, default_address_discrimination,
                                  custom_discrimination, 0xf00d)]]
```

The override is then mangled as a parametrized vendor extension:
```
"__vtptrauth" I
 <key>
 <addressDiscriminated>
 <extraDiscriminator>
E
```

To support this attribute, this patch adds a small extension to the
attribute-emitter tablegen backend.

Note that there are known areas where signing is either missing
altogether or can be strengthened. Some will be addressed in later
changes (e.g., member function pointers, some RTTI).
`dynamic_cast` in particular is handled by emitting an artificial
v-table pointer load (in a way that always authenticates it) before the
runtime call itself, as the runtime doesn't have enough information
today to properly authenticate it. Instead, the runtime is currently
expected to strip the v-table pointer.

---------

Co-authored-by: John McCall <rjmccall at apple.com>
Co-authored-by: Ahmed Bougacha <ahmed at bougacha.org>

Added: 
    clang/test/CodeGen/ptrauth-ubsan-vptr.cpp
    clang/test/CodeGenCXX/ptrauth-apple-kext-indirect-call-2.cpp
    clang/test/CodeGenCXX/ptrauth-apple-kext-indirect-call.cpp
    clang/test/CodeGenCXX/ptrauth-apple-kext-indirect-virtual-dtor-call.cpp
    clang/test/CodeGenCXX/ptrauth-explicit-vtable-pointer-control.cpp
    clang/test/CodeGenCXX/ptrauth-rtti-layout.cpp
    clang/test/CodeGenCXX/ptrauth-thunks.cpp
    clang/test/CodeGenCXX/ptrauth-virtual-function.cpp
    clang/test/CodeGenCXX/ptrauth-vtable-virtual-inheritance-thunk.cpp
    clang/test/SemaCXX/ptrauth-incomplete-virtual-member-function-return-arg-type.cpp
    clang/test/SemaCXX/vtable_pointer_authentication_attribute.cpp

Modified: 
    clang/include/clang/AST/ASTContext.h
    clang/include/clang/AST/GlobalDecl.h
    clang/include/clang/AST/Mangle.h
    clang/include/clang/AST/VTableBuilder.h
    clang/include/clang/Basic/Attr.td
    clang/include/clang/Basic/DiagnosticSemaKinds.td
    clang/include/clang/Basic/PointerAuthOptions.h
    clang/include/clang/Basic/Thunk.h
    clang/include/clang/CodeGen/CodeGenABITypes.h
    clang/include/clang/CodeGen/ConstantInitBuilder.h
    clang/include/clang/InstallAPI/Visitor.h
    clang/include/clang/Sema/Sema.h
    clang/lib/AST/ASTContext.cpp
    clang/lib/AST/ItaniumMangle.cpp
    clang/lib/AST/Mangle.cpp
    clang/lib/AST/MicrosoftMangle.cpp
    clang/lib/AST/VTableBuilder.cpp
    clang/lib/CodeGen/CGCXX.cpp
    clang/lib/CodeGen/CGCXXABI.h
    clang/lib/CodeGen/CGClass.cpp
    clang/lib/CodeGen/CGExpr.cpp
    clang/lib/CodeGen/CGExprConstant.cpp
    clang/lib/CodeGen/CGPointerAuth.cpp
    clang/lib/CodeGen/CGVTT.cpp
    clang/lib/CodeGen/CGVTables.cpp
    clang/lib/CodeGen/CodeGenFunction.cpp
    clang/lib/CodeGen/CodeGenFunction.h
    clang/lib/CodeGen/CodeGenModule.h
    clang/lib/CodeGen/ConstantEmitter.h
    clang/lib/CodeGen/ConstantInitBuilder.cpp
    clang/lib/CodeGen/ItaniumCXXABI.cpp
    clang/lib/CodeGen/MicrosoftCXXABI.cpp
    clang/lib/Frontend/CompilerInvocation.cpp
    clang/lib/Headers/ptrauth.h
    clang/lib/InstallAPI/Visitor.cpp
    clang/lib/Parse/ParseDecl.cpp
    clang/lib/Sema/SemaDeclAttr.cpp
    clang/lib/Sema/SemaDeclCXX.cpp
    clang/lib/Sema/SemaExpr.cpp
    clang/test/CodeGenCXX/catch-undef-behavior.cpp
    clang/test/CodeGenCXX/ubsan-vtable-checks.cpp
    clang/test/Misc/pragma-attribute-supported-attributes-list.test
    clang/utils/TableGen/ClangAttrEmitter.cpp

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index 7aa1357b9aad0..a99f2dc6eb3f2 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -37,6 +37,7 @@
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/StringMap.h"
 #include "llvm/ADT/StringRef.h"
+#include "llvm/ADT/StringSet.h"
 #include "llvm/ADT/TinyPtrVector.h"
 #include "llvm/Support/TypeSize.h"
 #include <optional>
@@ -1277,6 +1278,11 @@ class ASTContext : public RefCountedBase<ASTContext> {
   /// space.
   QualType removeAddrSpaceQualType(QualType T) const;
 
+  /// Return the "other" discriminator used for the pointer auth schema used for
+  /// vtable pointers in instances of the requested type.
+  uint16_t
+  getPointerAuthVTablePointerDiscriminator(const CXXRecordDecl *RD);
+
   /// Apply Objective-C protocol qualifiers to the given type.
   /// \param allowOnPointerType specifies if we can apply protocol
   /// qualifiers on ObjCObjectPointerType. It can be set to true when
@@ -3438,12 +3444,21 @@ OPT_LIST(V)
   /// Whether a C++ static variable or CUDA/HIP kernel should be externalized.
   bool shouldExternalize(const Decl *D) const;
 
+  /// Resolve the root record to be used to derive the vtable pointer
+  /// authentication policy for the specified record.
+  const CXXRecordDecl *
+  baseForVTableAuthentication(const CXXRecordDecl *ThisClass);
+  bool useAbbreviatedThunkName(GlobalDecl VirtualMethodDecl,
+                               StringRef MangledName);
+
   StringRef getCUIDHash() const;
 
 private:
   /// All OMPTraitInfo objects live in this collection, one per
   /// `pragma omp [begin] declare variant` directive.
   SmallVector<std::unique_ptr<OMPTraitInfo>, 4> OMPTraitInfoVector;
+
+  llvm::DenseMap<GlobalDecl, llvm::StringSet<>> ThunksToBeAbbreviated;
 };
 
 /// Insertion operator for diagnostics.

diff  --git a/clang/include/clang/AST/GlobalDecl.h b/clang/include/clang/AST/GlobalDecl.h
index 88abba28c991d..386693cabb1fb 100644
--- a/clang/include/clang/AST/GlobalDecl.h
+++ b/clang/include/clang/AST/GlobalDecl.h
@@ -145,6 +145,10 @@ class GlobalDecl {
            LHS.MultiVersionIndex == RHS.MultiVersionIndex;
   }
 
+  bool operator!=(const GlobalDecl &Other) const {
+    return !(*this == Other);
+  }
+
   void *getAsOpaquePtr() const { return Value.getOpaqueValue(); }
 
   explicit operator bool() const { return getAsOpaquePtr(); }

diff  --git a/clang/include/clang/AST/Mangle.h b/clang/include/clang/AST/Mangle.h
index e586b0cec43df..d5f6c0f6cc67d 100644
--- a/clang/include/clang/AST/Mangle.h
+++ b/clang/include/clang/AST/Mangle.h
@@ -130,15 +130,15 @@ class MangleContext {
   // FIXME: consider replacing raw_ostream & with something like SmallString &.
   void mangleName(GlobalDecl GD, raw_ostream &);
   virtual void mangleCXXName(GlobalDecl GD, raw_ostream &) = 0;
-  virtual void mangleThunk(const CXXMethodDecl *MD,
-                          const ThunkInfo &Thunk,
-                          raw_ostream &) = 0;
+  virtual void mangleThunk(const CXXMethodDecl *MD, const ThunkInfo &Thunk,
+                           bool ElideOverrideInfo, raw_ostream &) = 0;
   virtual void mangleCXXDtorThunk(const CXXDestructorDecl *DD, CXXDtorType Type,
-                                  const ThisAdjustment &ThisAdjustment,
-                                  raw_ostream &) = 0;
+                                  const ThunkInfo &Thunk,
+                                  bool ElideOverrideInfo, raw_ostream &) = 0;
   virtual void mangleReferenceTemporary(const VarDecl *D,
                                         unsigned ManglingNumber,
                                         raw_ostream &) = 0;
+  virtual void mangleCXXVTable(const CXXRecordDecl *RD, raw_ostream &) = 0;
   virtual void mangleCXXRTTI(QualType T, raw_ostream &) = 0;
   virtual void mangleCXXRTTIName(QualType T, raw_ostream &,
                                  bool NormalizeIntegers = false) = 0;
@@ -192,7 +192,6 @@ class ItaniumMangleContext : public MangleContext {
                                 bool IsAux = false)
       : MangleContext(C, D, MK_Itanium, IsAux) {}
 
-  virtual void mangleCXXVTable(const CXXRecordDecl *RD, raw_ostream &) = 0;
   virtual void mangleCXXVTT(const CXXRecordDecl *RD, raw_ostream &) = 0;
   virtual void mangleCXXCtorVTable(const CXXRecordDecl *RD, int64_t Offset,
                                    const CXXRecordDecl *Type,

diff  --git a/clang/include/clang/AST/VTableBuilder.h b/clang/include/clang/AST/VTableBuilder.h
index fbf6c041a1ec1..a5de41dbc22f1 100644
--- a/clang/include/clang/AST/VTableBuilder.h
+++ b/clang/include/clang/AST/VTableBuilder.h
@@ -361,6 +361,10 @@ class VTableContextBase {
 };
 
 class ItaniumVTableContext : public VTableContextBase {
+public:
+  typedef llvm::DenseMap<const CXXMethodDecl *, const CXXMethodDecl *>
+      OriginalMethodMapTy;
+
 private:
 
   /// Contains the index (relative to the vtable address point)
@@ -384,6 +388,10 @@ class ItaniumVTableContext : public VTableContextBase {
     VirtualBaseClassOffsetOffsetsMapTy;
   VirtualBaseClassOffsetOffsetsMapTy VirtualBaseClassOffsetOffsets;
 
+  /// Map from a virtual method to the nearest method in the primary base class
+  /// chain that it overrides.
+  OriginalMethodMapTy OriginalMethodMap;
+
   void computeVTableRelatedInformation(const CXXRecordDecl *RD) override;
 
 public:
@@ -425,6 +433,27 @@ class ItaniumVTableContext : public VTableContextBase {
   CharUnits getVirtualBaseOffsetOffset(const CXXRecordDecl *RD,
                                        const CXXRecordDecl *VBase);
 
+  /// Return the method that added the v-table slot that will be used to call
+  /// the given method.
+  ///
+  /// In the Itanium ABI, where overrides always cause methods to be added to
+  /// the primary v-table if they're not already there, this will be the first
+  /// declaration in the primary base class chain for which the return type
+  /// adjustment is trivial.
+  GlobalDecl findOriginalMethod(GlobalDecl GD);
+
+  const CXXMethodDecl *findOriginalMethodInMap(const CXXMethodDecl *MD) const;
+
+  void setOriginalMethod(const CXXMethodDecl *Key, const CXXMethodDecl *Val) {
+    OriginalMethodMap[Key] = Val;
+  }
+
+  /// This method is reserved for the implementation and shouldn't be used
+  /// directly.
+  const OriginalMethodMapTy &getOriginalMethodMap() {
+    return OriginalMethodMap;
+  }
+
   static bool classof(const VTableContextBase *VT) {
     return !VT->isMicrosoft();
   }

diff  --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 0c469e389eff0..452cd1810f653 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -686,6 +686,10 @@ class Attr {
   bit PragmaAttributeSupport;
   // Set to true if this attribute accepts parameter pack expansion expressions.
   bit AcceptsExprPack = 0;
+  // To support multiple enum parameters to an attribute without breaking
+  // our existing general parsing we need to have a separate flag that
+  // opts an attribute into strict parsing of attribute parameters
+  bit StrictEnumParameters = 0;
   // Lists language options, one of which is required to be true for the
   // attribute to be applicable. If empty, no language options are required.
   list<LangOpt> LangOpts = [];
@@ -4576,6 +4580,31 @@ def NoRandomizeLayout : InheritableAttr {
 }
 def : MutualExclusions<[RandomizeLayout, NoRandomizeLayout]>;
 
+def VTablePointerAuthentication : InheritableAttr {
+  let Spellings = [Clang<"ptrauth_vtable_pointer">];
+  let Subjects = SubjectList<[CXXRecord]>;
+  let Documentation = [Undocumented];
+  let StrictEnumParameters = 1;
+  let Args = [EnumArgument<"Key", "VPtrAuthKeyType", /*is_string=*/ true,
+                ["default_key", "no_authentication", "process_dependent",
+                 "process_independent"],
+                ["DefaultKey", "NoKey", "ProcessDependent",
+                 "ProcessIndependent"]>,
+              EnumArgument<"AddressDiscrimination", "AddressDiscriminationMode",
+                /*is_string=*/ true,
+                ["default_address_discrimination", "no_address_discrimination",
+                 "address_discrimination"],
+                ["DefaultAddressDiscrimination", "NoAddressDiscrimination",
+                 "AddressDiscrimination"]>,
+              EnumArgument<"ExtraDiscrimination", "ExtraDiscrimination",
+                /*is_string=*/ true,
+                ["default_extra_discrimination", "no_extra_discrimination",
+                 "type_discrimination", "custom_discrimination"],
+                ["DefaultExtraDiscrimination", "NoExtraDiscrimination",
+                 "TypeDiscrimination", "CustomDiscrimination"]>,
+              IntArgument<"CustomDiscriminationValue", 1>];
+}
+
 def FunctionReturnThunks : InheritableAttr,
     TargetSpecificAttr<TargetAnyX86> {
   let Spellings = [GCC<"function_return">];

diff  --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 79cc9c61f7fd3..33412e14897b6 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -940,6 +940,13 @@ def warn_ptrauth_auth_null_pointer :
 def err_ptrauth_string_not_literal : Error<
   "argument must be a string literal%select{| of char type}0">;
 
+def note_ptrauth_virtual_function_pointer_incomplete_arg_ret :
+  Note<"cannot take an address of a virtual member function if its return or "
+       "argument types are incomplete">;
+def note_ptrauth_virtual_function_incomplete_arg_ret_type :
+  Note<"%0 is incomplete">;
+
+
 /// main()
 // static main() is not an error in C, just in C++.
 def warn_static_main : Warning<"'main' should not be declared static">,
@@ -12220,6 +12227,30 @@ def warn_cuda_maxclusterrank_sm_90 : Warning<
   "maxclusterrank requires sm_90 or higher, CUDA arch provided: %0, ignoring "
   "%1 attribute">, InGroup<IgnoredAttributes>;
 
+// VTable pointer authentication errors
+def err_non_polymorphic_vtable_pointer_auth : Error<
+  "cannot set vtable pointer authentication on monomorphic type %0">;
+def err_incomplete_type_vtable_pointer_auth : Error<
+  "cannot set vtable pointer authentication on an incomplete type %0">;
+def err_non_top_level_vtable_pointer_auth : Error<
+  "cannot set vtable pointer authentication on %0 which is a subclass of polymorphic type %1">;
+def err_duplicated_vtable_pointer_auth : Error<
+  "multiple vtable pointer authentication policies on %0">;
+def err_invalid_authentication_key : Error<
+  "invalid authentication key %0">;
+def err_invalid_address_discrimination : Error<
+  "invalid address discrimination mode %0">;
+def err_invalid_extra_discrimination : Error<
+  "invalid extra discrimination selection %0">;
+def err_invalid_custom_discrimination : Error<
+  "invalid custom discrimination">;
+def err_missing_custom_discrimination : Error<
+  "missing custom discrimination">;
+def err_no_default_vtable_pointer_auth : Error<
+  "cannot specify a default vtable pointer authentication "
+  "%select{key|address discrimination mode|discriminator}0 with no default set"
+>;
+
 def err_bit_int_bad_size : Error<"%select{signed|unsigned}0 _BitInt must "
                                  "have a bit size of at least %select{2|1}0">;
 def err_bit_int_max_size : Error<"%select{signed|unsigned}0 _BitInt of bit "

diff  --git a/clang/include/clang/Basic/PointerAuthOptions.h b/clang/include/clang/Basic/PointerAuthOptions.h
index aaad4a2b2b5ae..197d63642ca6d 100644
--- a/clang/include/clang/Basic/PointerAuthOptions.h
+++ b/clang/include/clang/Basic/PointerAuthOptions.h
@@ -47,6 +47,12 @@ class PointerAuthSchema {
     /// No additional discrimination.
     None,
 
+    /// Include a hash of the entity's type.
+    Type,
+
+    /// Include a hash of the entity's identity.
+    Decl,
+
     /// Discriminate using a constant value.
     Constant,
   };
@@ -150,6 +156,25 @@ class PointerAuthSchema {
 struct PointerAuthOptions {
   /// The ABI for C function pointers.
   PointerAuthSchema FunctionPointers;
+
+  /// The ABI for C++ virtual table pointers (the pointer to the table
+  /// itself) as installed in an actual class instance.
+  PointerAuthSchema CXXVTablePointers;
+
+  /// TypeInfo has external ABI requirements and is emitted without
+  /// actually having parsed the libcxx definition, so we can't simply
+  /// perform a look up. The settings for this should match the exact
+  /// specification in type_info.h
+  PointerAuthSchema CXXTypeInfoVTablePointer;
+
+  /// The ABI for C++ virtual table pointers as installed in a VTT.
+  PointerAuthSchema CXXVTTVTablePointers;
+
+  /// The ABI for most C++ virtual function pointers, i.e. v-table entries.
+  PointerAuthSchema CXXVirtualFunctionPointers;
+
+  /// The ABI for variadic C++ virtual function pointers.
+  PointerAuthSchema CXXVirtualVariadicFunctionPointers;
 };
 
 } // end namespace clang

diff  --git a/clang/include/clang/Basic/Thunk.h b/clang/include/clang/Basic/Thunk.h
index 0247e279408f0..af4afb2d2ac4d 100644
--- a/clang/include/clang/Basic/Thunk.h
+++ b/clang/include/clang/Basic/Thunk.h
@@ -162,20 +162,24 @@ struct ThunkInfo {
 
   /// Holds a pointer to the overridden method this thunk is for,
   /// if needed by the ABI to distinguish 
diff erent thunks with equal
-  /// adjustments. Otherwise, null.
+  /// adjustments.
+  /// In the Itanium ABI, this field can hold the method that created the
+  /// vtable entry for this thunk.
+  /// Otherwise, null.
   /// CAUTION: In the unlikely event you need to sort ThunkInfos, consider using
   /// an ABI-specific comparator.
   const CXXMethodDecl *Method;
+  const Type *ThisType;
 
-  ThunkInfo() : Method(nullptr) {}
+  ThunkInfo() : Method(nullptr), ThisType(nullptr) {}
 
   ThunkInfo(const ThisAdjustment &This, const ReturnAdjustment &Return,
-            const CXXMethodDecl *Method = nullptr)
-      : This(This), Return(Return), Method(Method) {}
+            const Type *ThisT, const CXXMethodDecl *Method = nullptr)
+      : This(This), Return(Return), Method(Method), ThisType(ThisT) {}
 
   friend bool operator==(const ThunkInfo &LHS, const ThunkInfo &RHS) {
     return LHS.This == RHS.This && LHS.Return == RHS.Return &&
-           LHS.Method == RHS.Method;
+           LHS.Method == RHS.Method && LHS.ThisType == RHS.ThisType;
   }
 
   bool isEmpty() const {

diff  --git a/clang/include/clang/CodeGen/CodeGenABITypes.h b/clang/include/clang/CodeGen/CodeGenABITypes.h
index fda0855dc8683..d4822dc160082 100644
--- a/clang/include/clang/CodeGen/CodeGenABITypes.h
+++ b/clang/include/clang/CodeGen/CodeGenABITypes.h
@@ -42,6 +42,7 @@ class CXXConstructorDecl;
 class CXXDestructorDecl;
 class CXXRecordDecl;
 class CXXMethodDecl;
+class GlobalDecl;
 class ObjCMethodDecl;
 class ObjCProtocolDecl;
 
@@ -104,6 +105,9 @@ llvm::Type *convertTypeForMemory(CodeGenModule &CGM, QualType T);
 unsigned getLLVMFieldNumber(CodeGenModule &CGM,
                             const RecordDecl *RD, const FieldDecl *FD);
 
+/// Return a declaration discriminator for the given global decl.
+uint16_t getPointerAuthDeclDiscriminator(CodeGenModule &CGM, GlobalDecl GD);
+
 /// Given the language and code-generation options that Clang was configured
 /// with, set the default LLVM IR attributes for a function definition.
 /// The attributes set here are mostly global target-configuration and

diff  --git a/clang/include/clang/CodeGen/ConstantInitBuilder.h b/clang/include/clang/CodeGen/ConstantInitBuilder.h
index 498acfd380131..28d4764b6d60b 100644
--- a/clang/include/clang/CodeGen/ConstantInitBuilder.h
+++ b/clang/include/clang/CodeGen/ConstantInitBuilder.h
@@ -25,8 +25,11 @@
 #include <vector>
 
 namespace clang {
-namespace CodeGen {
+class GlobalDecl;
+class PointerAuthSchema;
+class QualType;
 
+namespace CodeGen {
 class CodeGenModule;
 
 /// A convenience builder class for complex constant initializers,
@@ -199,6 +202,11 @@ class ConstantAggregateBuilderBase {
     add(llvm::ConstantInt::get(intTy, value, isSigned));
   }
 
+  /// Add a signed pointer using the given pointer authentication schema.
+  void addSignedPointer(llvm::Constant *Pointer,
+                        const PointerAuthSchema &Schema, GlobalDecl CalleeDecl,
+                        QualType CalleeType);
+
   /// Add a null pointer of a specific type.
   void addNullPointer(llvm::PointerType *ptrTy) {
     add(llvm::ConstantPointerNull::get(ptrTy));

diff  --git a/clang/include/clang/InstallAPI/Visitor.h b/clang/include/clang/InstallAPI/Visitor.h
index 9ac948ded3e33..3680ee566ca87 100644
--- a/clang/include/clang/InstallAPI/Visitor.h
+++ b/clang/include/clang/InstallAPI/Visitor.h
@@ -60,8 +60,8 @@ class InstallAPIVisitor final : public ASTConsumer,
   std::string getMangledName(const NamedDecl *D) const;
   std::string getBackendMangledName(llvm::Twine Name) const;
   std::string getMangledCXXVTableName(const CXXRecordDecl *D) const;
-  std::string getMangledCXXThunk(const GlobalDecl &D,
-                                 const ThunkInfo &Thunk) const;
+  std::string getMangledCXXThunk(const GlobalDecl &D, const ThunkInfo &Thunk,
+                                 bool ElideOverrideInfo) const;
   std::string getMangledCXXRTTI(const CXXRecordDecl *D) const;
   std::string getMangledCXXRTTIName(const CXXRecordDecl *D) const;
   std::string getMangledCtorDtor(const CXXMethodDecl *D, int Type) const;

diff  --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 2e7af0f691cbb..ab3cbd19e1ff0 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -4566,6 +4566,10 @@ class Sema final : public SemaBase {
   /// conditions that are needed for the attribute to have an effect.
   void checkIllFormedTrivialABIStruct(CXXRecordDecl &RD);
 
+  /// Check that VTable Pointer authentication is only being set on the first
+  /// first instantiation of the vtable
+  void checkIncorrectVTablePointerAuthenticationAttribute(CXXRecordDecl &RD);
+
   void ActOnFinishCXXMemberSpecification(Scope *S, SourceLocation RLoc,
                                          Decl *TagDecl, SourceLocation LBrac,
                                          SourceLocation RBrac,

diff  --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 1b5d16bd176f3..84deaf5429df7 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -86,6 +86,7 @@
 #include "llvm/Support/ErrorHandling.h"
 #include "llvm/Support/MD5.h"
 #include "llvm/Support/MathExtras.h"
+#include "llvm/Support/SipHash.h"
 #include "llvm/Support/raw_ostream.h"
 #include "llvm/TargetParser/AArch64TargetParser.h"
 #include "llvm/TargetParser/Triple.h"
@@ -3128,6 +3129,17 @@ QualType ASTContext::removeAddrSpaceQualType(QualType T) const {
     return QualType(TypeNode, Quals.getFastQualifiers());
 }
 
+uint16_t
+ASTContext::getPointerAuthVTablePointerDiscriminator(const CXXRecordDecl *RD) {
+  assert(RD->isPolymorphic() &&
+         "Attempted to get vtable pointer discriminator on a monomorphic type");
+  std::unique_ptr<MangleContext> MC(createMangleContext());
+  SmallString<256> Str;
+  llvm::raw_svector_ostream Out(Str);
+  MC->mangleCXXVTable(RD, Out);
+  return llvm::getPointerAuthStableSipHash(Str);
+}
+
 QualType ASTContext::getObjCGCQualType(QualType T,
                                        Qualifiers::GC GCAttr) const {
   QualType CanT = getCanonicalType(T);
@@ -13894,3 +13906,74 @@ StringRef ASTContext::getCUIDHash() const {
   CUIDHash = llvm::utohexstr(llvm::MD5Hash(LangOpts.CUID), /*LowerCase=*/true);
   return CUIDHash;
 }
+
+const CXXRecordDecl *
+ASTContext::baseForVTableAuthentication(const CXXRecordDecl *ThisClass) {
+  assert(ThisClass);
+  assert(ThisClass->isPolymorphic());
+  const CXXRecordDecl *PrimaryBase = ThisClass;
+  while (1) {
+    assert(PrimaryBase);
+    assert(PrimaryBase->isPolymorphic());
+    auto &Layout = getASTRecordLayout(PrimaryBase);
+    auto Base = Layout.getPrimaryBase();
+    if (!Base || Base == PrimaryBase || !Base->isPolymorphic())
+      break;
+    PrimaryBase = Base;
+  }
+  return PrimaryBase;
+}
+
+bool ASTContext::useAbbreviatedThunkName(GlobalDecl VirtualMethodDecl,
+                                         StringRef MangledName) {
+  auto *Method = cast<CXXMethodDecl>(VirtualMethodDecl.getDecl());
+  assert(Method->isVirtual());
+  bool DefaultIncludesPointerAuth =
+      LangOpts.PointerAuthCalls || LangOpts.PointerAuthIntrinsics;
+
+  if (!DefaultIncludesPointerAuth)
+    return true;
+
+  auto Existing = ThunksToBeAbbreviated.find(VirtualMethodDecl);
+  if (Existing != ThunksToBeAbbreviated.end())
+    return Existing->second.contains(MangledName.str());
+
+  std::unique_ptr<MangleContext> Mangler(createMangleContext());
+  llvm::StringMap<llvm::SmallVector<std::string, 2>> Thunks;
+  auto VtableContext = getVTableContext();
+  if (const auto *ThunkInfos = VtableContext->getThunkInfo(VirtualMethodDecl)) {
+    auto *Destructor = dyn_cast<CXXDestructorDecl>(Method);
+    for (const auto &Thunk : *ThunkInfos) {
+      SmallString<256> ElidedName;
+      llvm::raw_svector_ostream ElidedNameStream(ElidedName);
+      if (Destructor)
+        Mangler->mangleCXXDtorThunk(Destructor, VirtualMethodDecl.getDtorType(),
+                                    Thunk, /* elideOverrideInfo */ true,
+                                    ElidedNameStream);
+      else
+        Mangler->mangleThunk(Method, Thunk, /* elideOverrideInfo */ true,
+                             ElidedNameStream);
+      SmallString<256> MangledName;
+      llvm::raw_svector_ostream mangledNameStream(MangledName);
+      if (Destructor)
+        Mangler->mangleCXXDtorThunk(Destructor, VirtualMethodDecl.getDtorType(),
+                                    Thunk, /* elideOverrideInfo */ false,
+                                    mangledNameStream);
+      else
+        Mangler->mangleThunk(Method, Thunk, /* elideOverrideInfo */ false,
+                             mangledNameStream);
+
+      if (Thunks.find(ElidedName) == Thunks.end())
+        Thunks[ElidedName] = {};
+      Thunks[ElidedName].push_back(std::string(MangledName));
+    }
+  }
+  llvm::StringSet<> SimplifiedThunkNames;
+  for (auto &ThunkList : Thunks) {
+    llvm::sort(ThunkList.second);
+    SimplifiedThunkNames.insert(ThunkList.second[0]);
+  }
+  bool Result = SimplifiedThunkNames.contains(MangledName);
+  ThunksToBeAbbreviated[VirtualMethodDecl] = std::move(SimplifiedThunkNames);
+  return Result;
+}

diff  --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp
index 2cac03ba163f9..5444dcf027fe2 100644
--- a/clang/lib/AST/ItaniumMangle.cpp
+++ b/clang/lib/AST/ItaniumMangle.cpp
@@ -99,11 +99,10 @@ class ItaniumMangleContextImpl : public ItaniumMangleContext {
   }
 
   void mangleCXXName(GlobalDecl GD, raw_ostream &) override;
-  void mangleThunk(const CXXMethodDecl *MD, const ThunkInfo &Thunk,
+  void mangleThunk(const CXXMethodDecl *MD, const ThunkInfo &Thunk, bool,
                    raw_ostream &) override;
   void mangleCXXDtorThunk(const CXXDestructorDecl *DD, CXXDtorType Type,
-                          const ThisAdjustment &ThisAdjustment,
-                          raw_ostream &) override;
+                          const ThunkInfo &Thunk, bool, raw_ostream &) override;
   void mangleReferenceTemporary(const VarDecl *D, unsigned ManglingNumber,
                                 raw_ostream &) override;
   void mangleCXXVTable(const CXXRecordDecl *RD, raw_ostream &) override;
@@ -468,6 +467,7 @@ class CXXNameMangler {
   void mangleNameOrStandardSubstitution(const NamedDecl *ND);
   void mangleLambdaSig(const CXXRecordDecl *Lambda);
   void mangleModuleNamePrefix(StringRef Name, bool IsPartition = false);
+  void mangleVendorQualifier(StringRef Name);
 
 private:
 
@@ -559,7 +559,6 @@ class CXXNameMangler {
                                       StringRef Prefix = "");
   void mangleOperatorName(DeclarationName Name, unsigned Arity);
   void mangleOperatorName(OverloadedOperatorKind OO, unsigned Arity);
-  void mangleVendorQualifier(StringRef qualifier);
   void mangleQualifiers(Qualifiers Quals, const DependentAddressSpaceType *DAST = nullptr);
   void mangleRefQualifier(RefQualifierKind RefQualifier);
 
@@ -7044,8 +7043,78 @@ void ItaniumMangleContextImpl::mangleCXXDtorComdat(const CXXDestructorDecl *D,
   Mangler.mangle(GlobalDecl(D, Dtor_Comdat));
 }
 
+/// Mangles the pointer authentication override attribute for classes
+/// that have explicit overrides for the vtable authentication schema.
+///
+/// The override is mangled as a parameterized vendor extension as follows
+///
+///   <type> ::= U "__vtptrauth" I
+///                 <key>
+///                 <addressDiscriminated>
+///                 <extraDiscriminator>
+///              E
+///
+/// The extra discriminator encodes the explicit value derived from the
+/// override schema, e.g. if the override has specified type based
+/// discrimination the encoded value will be the discriminator derived from the
+/// type name.
+static void mangleOverrideDiscrimination(CXXNameMangler &Mangler,
+                                         ASTContext &Context,
+                                         const ThunkInfo &Thunk) {
+  auto &LangOpts = Context.getLangOpts();
+  const CXXRecordDecl *ThisRD = Thunk.ThisType->getPointeeCXXRecordDecl();
+  const CXXRecordDecl *PtrauthClassRD =
+      Context.baseForVTableAuthentication(ThisRD);
+  unsigned TypedDiscriminator =
+      Context.getPointerAuthVTablePointerDiscriminator(ThisRD);
+  Mangler.mangleVendorQualifier("__vtptrauth");
+  auto &ManglerStream = Mangler.getStream();
+  ManglerStream << "I";
+  if (const auto *ExplicitAuth =
+          PtrauthClassRD->getAttr<VTablePointerAuthenticationAttr>()) {
+    ManglerStream << "Lj" << ExplicitAuth->getKey();
+
+    if (ExplicitAuth->getAddressDiscrimination() ==
+        VTablePointerAuthenticationAttr::DefaultAddressDiscrimination)
+      ManglerStream << "Lb" << LangOpts.PointerAuthVTPtrAddressDiscrimination;
+    else
+      ManglerStream << "Lb"
+                    << (ExplicitAuth->getAddressDiscrimination() ==
+                        VTablePointerAuthenticationAttr::AddressDiscrimination);
+
+    switch (ExplicitAuth->getExtraDiscrimination()) {
+    case VTablePointerAuthenticationAttr::DefaultExtraDiscrimination: {
+      if (LangOpts.PointerAuthVTPtrTypeDiscrimination)
+        ManglerStream << "Lj" << TypedDiscriminator;
+      else
+        ManglerStream << "Lj" << 0;
+      break;
+    }
+    case VTablePointerAuthenticationAttr::TypeDiscrimination:
+      ManglerStream << "Lj" << TypedDiscriminator;
+      break;
+    case VTablePointerAuthenticationAttr::CustomDiscrimination:
+      ManglerStream << "Lj" << ExplicitAuth->getCustomDiscriminationValue();
+      break;
+    case VTablePointerAuthenticationAttr::NoExtraDiscrimination:
+      ManglerStream << "Lj" << 0;
+      break;
+    }
+  } else {
+    ManglerStream << "Lj"
+                  << (unsigned)VTablePointerAuthenticationAttr::DefaultKey;
+    ManglerStream << "Lb" << LangOpts.PointerAuthVTPtrAddressDiscrimination;
+    if (LangOpts.PointerAuthVTPtrTypeDiscrimination)
+      ManglerStream << "Lj" << TypedDiscriminator;
+    else
+      ManglerStream << "Lj" << 0;
+  }
+  ManglerStream << "E";
+}
+
 void ItaniumMangleContextImpl::mangleThunk(const CXXMethodDecl *MD,
                                            const ThunkInfo &Thunk,
+                                           bool ElideOverrideInfo,
                                            raw_ostream &Out) {
   //  <special-name> ::= T <call-offset> <base encoding>
   //                      # base is the nominal target function of thunk
@@ -7071,21 +7140,28 @@ void ItaniumMangleContextImpl::mangleThunk(const CXXMethodDecl *MD,
                              Thunk.Return.Virtual.Itanium.VBaseOffsetOffset);
 
   Mangler.mangleFunctionEncoding(MD);
+  if (!ElideOverrideInfo)
+    mangleOverrideDiscrimination(Mangler, getASTContext(), Thunk);
 }
 
-void ItaniumMangleContextImpl::mangleCXXDtorThunk(
-    const CXXDestructorDecl *DD, CXXDtorType Type,
-    const ThisAdjustment &ThisAdjustment, raw_ostream &Out) {
+void ItaniumMangleContextImpl::mangleCXXDtorThunk(const CXXDestructorDecl *DD,
+                                                  CXXDtorType Type,
+                                                  const ThunkInfo &Thunk,
+                                                  bool ElideOverrideInfo,
+                                                  raw_ostream &Out) {
   //  <special-name> ::= T <call-offset> <base encoding>
   //                      # base is the nominal target function of thunk
   CXXNameMangler Mangler(*this, Out, DD, Type);
   Mangler.getStream() << "_ZT";
 
+  auto &ThisAdjustment = Thunk.This;
   // Mangle the 'this' pointer adjustment.
   Mangler.mangleCallOffset(ThisAdjustment.NonVirtual,
                            ThisAdjustment.Virtual.Itanium.VCallOffsetOffset);
 
   Mangler.mangleFunctionEncoding(GlobalDecl(DD, Type));
+  if (!ElideOverrideInfo)
+    mangleOverrideDiscrimination(Mangler, getASTContext(), Thunk);
 }
 
 /// Returns the mangled name for a guard variable for the passed in VarDecl.

diff  --git a/clang/lib/AST/Mangle.cpp b/clang/lib/AST/Mangle.cpp
index 4fbf0e3b42dbc..75f6e2161a633 100644
--- a/clang/lib/AST/Mangle.cpp
+++ b/clang/lib/AST/Mangle.cpp
@@ -513,10 +513,20 @@ class ASTNameGenerator::Implementation {
       }
     } else if (const auto *MD = dyn_cast_or_null<CXXMethodDecl>(ND)) {
       Manglings.emplace_back(getName(ND));
-      if (MD->isVirtual())
-        if (const auto *TIV = Ctx.getVTableContext()->getThunkInfo(MD))
-          for (const auto &T : *TIV)
-            Manglings.emplace_back(getMangledThunk(MD, T));
+      if (MD->isVirtual()) {
+        if (const auto *TIV = Ctx.getVTableContext()->getThunkInfo(MD)) {
+          for (const auto &T : *TIV) {
+            std::string ThunkName;
+            std::string ContextualizedName =
+                getMangledThunk(MD, T, /* ElideOverrideInfo */ false);
+            if (Ctx.useAbbreviatedThunkName(MD, ContextualizedName))
+              ThunkName = getMangledThunk(MD, T, /* ElideOverrideInfo */ true);
+            else
+              ThunkName = ContextualizedName;
+            Manglings.emplace_back(ThunkName);
+          }
+        }
+      }
     }
 
     return Manglings;
@@ -569,11 +579,12 @@ class ASTNameGenerator::Implementation {
     return BOS.str();
   }
 
-  std::string getMangledThunk(const CXXMethodDecl *MD, const ThunkInfo &T) {
+  std::string getMangledThunk(const CXXMethodDecl *MD, const ThunkInfo &T,
+                              bool ElideOverrideInfo) {
     std::string FrontendBuf;
     llvm::raw_string_ostream FOS(FrontendBuf);
 
-    MC->mangleThunk(MD, T, FOS);
+    MC->mangleThunk(MD, T, ElideOverrideInfo, FOS);
 
     std::string BackendBuf;
     llvm::raw_string_ostream BOS(BackendBuf);

diff  --git a/clang/lib/AST/MicrosoftMangle.cpp b/clang/lib/AST/MicrosoftMangle.cpp
index 8cbaad62bf9d7..7f1e9ab02ec26 100644
--- a/clang/lib/AST/MicrosoftMangle.cpp
+++ b/clang/lib/AST/MicrosoftMangle.cpp
@@ -159,9 +159,9 @@ class MicrosoftMangleContextImpl : public MicrosoftMangleContext {
                                 const MethodVFTableLocation &ML,
                                 raw_ostream &Out) override;
   void mangleThunk(const CXXMethodDecl *MD, const ThunkInfo &Thunk,
-                   raw_ostream &) override;
+                   bool ElideOverrideInfo, raw_ostream &) override;
   void mangleCXXDtorThunk(const CXXDestructorDecl *DD, CXXDtorType Type,
-                          const ThisAdjustment &ThisAdjustment,
+                          const ThunkInfo &Thunk, bool ElideOverrideInfo,
                           raw_ostream &) override;
   void mangleCXXVFTable(const CXXRecordDecl *Derived,
                         ArrayRef<const CXXRecordDecl *> BasePath,
@@ -169,6 +169,8 @@ class MicrosoftMangleContextImpl : public MicrosoftMangleContext {
   void mangleCXXVBTable(const CXXRecordDecl *Derived,
                         ArrayRef<const CXXRecordDecl *> BasePath,
                         raw_ostream &Out) override;
+
+  void mangleCXXVTable(const CXXRecordDecl *, raw_ostream &) override;
   void mangleCXXVirtualDisplacementMap(const CXXRecordDecl *SrcRD,
                                        const CXXRecordDecl *DstRD,
                                        raw_ostream &Out) override;
@@ -3747,6 +3749,7 @@ void MicrosoftMangleContextImpl::mangleVirtualMemPtrThunk(
 
 void MicrosoftMangleContextImpl::mangleThunk(const CXXMethodDecl *MD,
                                              const ThunkInfo &Thunk,
+                                             bool /*ElideOverrideInfo*/,
                                              raw_ostream &Out) {
   msvc_hashing_ostream MHO(Out);
   MicrosoftCXXNameMangler Mangler(*this, MHO);
@@ -3768,9 +3771,11 @@ void MicrosoftMangleContextImpl::mangleThunk(const CXXMethodDecl *MD,
       DeclForFPT->getType()->castAs<FunctionProtoType>(), MD);
 }
 
-void MicrosoftMangleContextImpl::mangleCXXDtorThunk(
-    const CXXDestructorDecl *DD, CXXDtorType Type,
-    const ThisAdjustment &Adjustment, raw_ostream &Out) {
+void MicrosoftMangleContextImpl::mangleCXXDtorThunk(const CXXDestructorDecl *DD,
+                                                    CXXDtorType Type,
+                                                    const ThunkInfo &Thunk,
+                                                    bool /*ElideOverrideInfo*/,
+                                                    raw_ostream &Out) {
   // FIXME: Actually, the dtor thunk should be emitted for vector deleting
   // dtors rather than scalar deleting dtors. Just use the vector deleting dtor
   // mangling manually until we support both deleting dtor types.
@@ -3779,6 +3784,7 @@ void MicrosoftMangleContextImpl::mangleCXXDtorThunk(
   MicrosoftCXXNameMangler Mangler(*this, MHO, DD, Type);
   Mangler.getStream() << "??_E";
   Mangler.mangleName(DD->getParent());
+  auto &Adjustment = Thunk.This;
   mangleThunkThisAdjustment(DD->getAccess(), Adjustment, Mangler, MHO);
   Mangler.mangleFunctionType(DD->getType()->castAs<FunctionProtoType>(), DD);
 }
@@ -3803,6 +3809,12 @@ void MicrosoftMangleContextImpl::mangleCXXVFTable(
   Mangler.getStream() << '@';
 }
 
+void MicrosoftMangleContextImpl::mangleCXXVTable(const CXXRecordDecl *Derived,
+                                                 raw_ostream &Out) {
+  // TODO: Determine appropriate mangling for MSABI
+  mangleCXXVFTable(Derived, {}, Out);
+}
+
 void MicrosoftMangleContextImpl::mangleCXXVBTable(
     const CXXRecordDecl *Derived, ArrayRef<const CXXRecordDecl *> BasePath,
     raw_ostream &Out) {

diff  --git a/clang/lib/AST/VTableBuilder.cpp b/clang/lib/AST/VTableBuilder.cpp
index a956ca5b37acf..e941c3bedb0a7 100644
--- a/clang/lib/AST/VTableBuilder.cpp
+++ b/clang/lib/AST/VTableBuilder.cpp
@@ -1147,11 +1147,41 @@ void ItaniumVTableBuilder::ComputeThisAdjustments() {
       continue;
 
     // Add it.
-    VTableThunks[VTableIndex].This = ThisAdjustment;
+    auto SetThisAdjustmentThunk = [&](uint64_t Idx) {
+      // If a this pointer adjustment is required, record the method that
+      // created the vtable entry. MD is not necessarily the method that
+      // created the entry since derived classes overwrite base class
+      // information in MethodInfoMap, hence findOriginalMethodInMap is called
+      // here.
+      //
+      // For example, in the following class hierarchy, if MD = D1::m and
+      // Overrider = D2:m, the original method that created the entry is B0:m,
+      // which is what findOriginalMethodInMap(MD) returns:
+      //
+      // struct B0 { int a; virtual void m(); };
+      // struct D0 : B0 { int a; void m() override; };
+      // struct D1 : B0 { int a; void m() override; };
+      // struct D2 : D0, D1 { int a; void m() override; };
+      //
+      // We need to record the method because we cannot
+      // call findOriginalMethod to find the method that created the entry if
+      // the method in the entry requires adjustment.
+      //
+      // Do not set ThunkInfo::Method if Idx is already in VTableThunks. This
+      // can happen when covariant return adjustment is required too.
+      if (!VTableThunks.count(Idx)) {
+        const CXXMethodDecl *Method = VTables.findOriginalMethodInMap(MD);
+        VTableThunks[Idx].Method = Method;
+        VTableThunks[Idx].ThisType = Method->getThisType().getTypePtr();
+      }
+      VTableThunks[Idx].This = ThisAdjustment;
+    };
+
+    SetThisAdjustmentThunk(VTableIndex);
 
     if (isa<CXXDestructorDecl>(MD)) {
       // Add an adjustment for the deleting destructor as well.
-      VTableThunks[VTableIndex + 1].This = ThisAdjustment;
+      SetThisAdjustmentThunk(VTableIndex + 1);
     }
   }
 
@@ -1509,6 +1539,8 @@ void ItaniumVTableBuilder::AddMethods(
           FindNearestOverriddenMethod(MD, PrimaryBases)) {
       if (ComputeReturnAdjustmentBaseOffset(Context, MD,
                                             OverriddenMD).isEmpty()) {
+        VTables.setOriginalMethod(MD, OverriddenMD);
+
         // Replace the method info of the overridden method with our own
         // method.
         assert(MethodInfoMap.count(OverriddenMD) &&
@@ -1547,7 +1579,8 @@ void ItaniumVTableBuilder::AddMethods(
 
             // This is a virtual thunk for the most derived class, add it.
             AddThunk(Overrider.Method,
-                     ThunkInfo(ThisAdjustment, ReturnAdjustment));
+                     ThunkInfo(ThisAdjustment, ReturnAdjustment,
+                               OverriddenMD->getThisType().getTypePtr()));
           }
         }
 
@@ -1615,6 +1648,15 @@ void ItaniumVTableBuilder::AddMethods(
     ReturnAdjustment ReturnAdjustment =
       ComputeReturnAdjustment(ReturnAdjustmentOffset);
 
+    // If a return adjustment is required, record the method that created the
+    // vtable entry. We need to record the method because we cannot call
+    // findOriginalMethod to find the method that created the entry if the
+    // method in the entry requires adjustment.
+    if (!ReturnAdjustment.isEmpty()) {
+      VTableThunks[Components.size()].Method = MD;
+      VTableThunks[Components.size()].ThisType = MD->getThisType().getTypePtr();
+    }
+
     AddMethod(Overrider.Method, ReturnAdjustment);
   }
 }
@@ -1890,11 +1932,31 @@ void ItaniumVTableBuilder::LayoutVTablesForVirtualBases(
   }
 }
 
+static void printThunkMethod(const ThunkInfo &Info, raw_ostream &Out) {
+  if (!Info.Method)
+    return;
+  std::string Str = PredefinedExpr::ComputeName(
+      PredefinedIdentKind::PrettyFunctionNoVirtual, Info.Method);
+  Out << " method: " << Str;
+}
+
 /// dumpLayout - Dump the vtable layout.
 void ItaniumVTableBuilder::dumpLayout(raw_ostream &Out) {
   // FIXME: write more tests that actually use the dumpLayout output to prevent
   // ItaniumVTableBuilder regressions.
 
+  Out << "Original map\n";
+
+  for (const auto &P : VTables.getOriginalMethodMap()) {
+    std::string Str0 =
+        PredefinedExpr::ComputeName(PredefinedIdentKind::PrettyFunctionNoVirtual,
+                                    P.first);
+    std::string Str1 =
+        PredefinedExpr::ComputeName(PredefinedIdentKind::PrettyFunctionNoVirtual,
+                                    P.second);
+    Out << " " << Str0 << " -> " << Str1 << "\n";
+  }
+
   if (isBuildingConstructorVTable()) {
     Out << "Construction vtable for ('";
     MostDerivedClass->printQualifiedName(Out);
@@ -1978,6 +2040,7 @@ void ItaniumVTableBuilder::dumpLayout(raw_ostream &Out) {
           }
 
           Out << ']';
+          printThunkMethod(Thunk, Out);
         }
 
         // If this function pointer has a 'this' pointer adjustment, dump it.
@@ -1991,6 +2054,7 @@ void ItaniumVTableBuilder::dumpLayout(raw_ostream &Out) {
           }
 
           Out << ']';
+          printThunkMethod(Thunk, Out);
         }
       }
 
@@ -2027,6 +2091,7 @@ void ItaniumVTableBuilder::dumpLayout(raw_ostream &Out) {
 
           Out << ']';
         }
+        printThunkMethod(Thunk, Out);
       }
 
       break;
@@ -2125,7 +2190,6 @@ void ItaniumVTableBuilder::dumpLayout(raw_ostream &Out) {
 
       ThunkInfoVectorTy ThunksVector = Thunks[MD];
       llvm::sort(ThunksVector, [](const ThunkInfo &LHS, const ThunkInfo &RHS) {
-        assert(LHS.Method == nullptr && RHS.Method == nullptr);
         return std::tie(LHS.This, LHS.Return) < std::tie(RHS.This, RHS.Return);
       });
 
@@ -2314,6 +2378,35 @@ ItaniumVTableContext::getVirtualBaseOffsetOffset(const CXXRecordDecl *RD,
   return I->second;
 }
 
+GlobalDecl ItaniumVTableContext::findOriginalMethod(GlobalDecl GD) {
+  const auto *MD = cast<CXXMethodDecl>(GD.getDecl());
+  computeVTableRelatedInformation(MD->getParent());
+  const CXXMethodDecl *OriginalMD = findOriginalMethodInMap(MD);
+
+  if (const auto *DD = dyn_cast<CXXDestructorDecl>(OriginalMD))
+    return GlobalDecl(DD, GD.getDtorType());
+  return OriginalMD;
+}
+
+const CXXMethodDecl *
+ItaniumVTableContext::findOriginalMethodInMap(const CXXMethodDecl *MD) const {
+  // Traverse the chain of virtual methods until we find the method that added
+  // the v-table slot.
+  while (true) {
+    auto I = OriginalMethodMap.find(MD);
+
+    // MD doesn't exist in OriginalMethodMap, so it must be the method we are
+    // looking for.
+    if (I == OriginalMethodMap.end())
+      break;
+
+    // Set MD to the overridden method.
+    MD = I->second;
+  }
+
+  return MD;
+}
+
 static std::unique_ptr<VTableLayout>
 CreateVTableLayout(const ItaniumVTableBuilder &Builder) {
   SmallVector<VTableLayout::VTableThunkTy, 1>
@@ -3094,9 +3187,9 @@ void VFTableBuilder::AddMethods(BaseSubobject Base, unsigned BaseDepth,
                                     ReturnAdjustmentOffset.VirtualBase);
       }
     }
-
+    auto ThisType = (OverriddenMD ? OverriddenMD : MD)->getThisType().getTypePtr();
     AddMethod(FinalOverriderMD,
-              ThunkInfo(ThisAdjustmentOffset, ReturnAdjustment,
+              ThunkInfo(ThisAdjustmentOffset, ReturnAdjustment, ThisType,
                         ForceReturnAdjustmentMangling ? MD : nullptr));
   }
 }

diff  --git a/clang/lib/CodeGen/CGCXX.cpp b/clang/lib/CodeGen/CGCXX.cpp
index e95a735f92f74..23ebbee19bf79 100644
--- a/clang/lib/CodeGen/CGCXX.cpp
+++ b/clang/lib/CodeGen/CGCXX.cpp
@@ -263,7 +263,16 @@ static CGCallee BuildAppleKextVirtualCall(CodeGenFunction &CGF,
     CGF.Builder.CreateConstInBoundsGEP1_64(Ty, VTable, VTableIndex, "vfnkxt");
   llvm::Value *VFunc = CGF.Builder.CreateAlignedLoad(
       Ty, VFuncPtr, llvm::Align(CGF.PointerAlignInBytes));
-  CGCallee Callee(GD, VFunc);
+
+  CGPointerAuthInfo PointerAuth;
+  if (auto &Schema =
+          CGM.getCodeGenOpts().PointerAuth.CXXVirtualFunctionPointers) {
+    GlobalDecl OrigMD =
+        CGM.getItaniumVTableContext().findOriginalMethod(GD.getCanonicalDecl());
+    PointerAuth = CGF.EmitPointerAuthInfo(Schema, VFuncPtr, OrigMD, QualType());
+  }
+
+  CGCallee Callee(GD, VFunc, PointerAuth);
   return Callee;
 }
 

diff  --git a/clang/lib/CodeGen/CGCXXABI.h b/clang/lib/CodeGen/CGCXXABI.h
index 104a20db8efaf..7dcc539111996 100644
--- a/clang/lib/CodeGen/CGCXXABI.h
+++ b/clang/lib/CodeGen/CGCXXABI.h
@@ -504,13 +504,15 @@ class CGCXXABI {
   virtual void setThunkLinkage(llvm::Function *Thunk, bool ForVTable,
                                GlobalDecl GD, bool ReturnAdjustment) = 0;
 
-  virtual llvm::Value *performThisAdjustment(CodeGenFunction &CGF,
-                                             Address This,
-                                             const ThisAdjustment &TA) = 0;
+  virtual llvm::Value *
+  performThisAdjustment(CodeGenFunction &CGF, Address This,
+                        const CXXRecordDecl *UnadjustedClass,
+                        const ThunkInfo &TI) = 0;
 
-  virtual llvm::Value *performReturnAdjustment(CodeGenFunction &CGF,
-                                               Address Ret,
-                                               const ReturnAdjustment &RA) = 0;
+  virtual llvm::Value *
+  performReturnAdjustment(CodeGenFunction &CGF, Address Ret,
+                          const CXXRecordDecl *UnadjustedClass,
+                          const ReturnAdjustment &RA) = 0;
 
   virtual void EmitReturnFromThunk(CodeGenFunction &CGF,
                                    RValue RV, QualType ResultType);

diff  --git a/clang/lib/CodeGen/CGClass.cpp b/clang/lib/CodeGen/CGClass.cpp
index 5a032bdbf9379..0a595bb998d26 100644
--- a/clang/lib/CodeGen/CGClass.cpp
+++ b/clang/lib/CodeGen/CGClass.cpp
@@ -2588,6 +2588,11 @@ void CodeGenFunction::InitializeVTablePointer(const VPtr &Vptr) {
   // the same addr space. Note that this might not be LLVM address space 0.
   VTableField = VTableField.withElementType(PtrTy);
 
+  if (auto AuthenticationInfo = CGM.getVTablePointerAuthInfo(
+          this, Vptr.Base.getBase(), VTableField.emitRawPointer(*this)))
+    VTableAddressPoint =
+        EmitPointerAuthSign(*AuthenticationInfo, VTableAddressPoint);
+
   llvm::StoreInst *Store = Builder.CreateStore(VTableAddressPoint, VTableField);
   TBAAAccessInfo TBAAInfo = CGM.getTBAAVTablePtrAccessInfo(PtrTy);
   CGM.DecorateInstructionWithTBAA(Store, TBAAInfo);
@@ -2681,12 +2686,35 @@ void CodeGenFunction::InitializeVTablePointers(const CXXRecordDecl *RD) {
 
 llvm::Value *CodeGenFunction::GetVTablePtr(Address This,
                                            llvm::Type *VTableTy,
-                                           const CXXRecordDecl *RD) {
+                                           const CXXRecordDecl *RD,
+                                           VTableAuthMode AuthMode) {
   Address VTablePtrSrc = This.withElementType(VTableTy);
   llvm::Instruction *VTable = Builder.CreateLoad(VTablePtrSrc, "vtable");
   TBAAAccessInfo TBAAInfo = CGM.getTBAAVTablePtrAccessInfo(VTableTy);
   CGM.DecorateInstructionWithTBAA(VTable, TBAAInfo);
 
+  if (auto AuthenticationInfo =
+          CGM.getVTablePointerAuthInfo(this, RD, This.emitRawPointer(*this))) {
+    if (AuthMode != VTableAuthMode::UnsafeUbsanStrip) {
+      VTable = cast<llvm::Instruction>(
+          EmitPointerAuthAuth(*AuthenticationInfo, VTable));
+      if (AuthMode == VTableAuthMode::MustTrap) {
+        // This is clearly suboptimal but until we have an ability
+        // to rely on the authentication intrinsic trapping and force
+        // an authentication to occur we don't really have a choice.
+        VTable =
+            cast<llvm::Instruction>(Builder.CreateBitCast(VTable, Int8PtrTy));
+        Builder.CreateLoad(RawAddress(VTable, Int8Ty, CGM.getPointerAlign()),
+                           /* IsVolatile */ true);
+      }
+    } else {
+      VTable = cast<llvm::Instruction>(EmitPointerAuthAuth(
+          CGPointerAuthInfo(0, PointerAuthenticationMode::Strip, false, false,
+                            nullptr),
+          VTable));
+    }
+  }
+
   if (CGM.getCodeGenOpts().OptimizationLevel > 0 &&
       CGM.getCodeGenOpts().StrictVTablePointers)
     CGM.DecorateInstructionWithInvariantGroup(VTable, RD);

diff  --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index 534f46da74862..23e5deee32581 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -830,8 +830,14 @@ void CodeGenFunction::EmitTypeCheck(TypeCheckKind TCK, SourceLocation Loc,
       // Load the vptr, and mix it with TypeHash.
       llvm::Value *TypeHash =
           llvm::ConstantInt::get(Int64Ty, xxh3_64bits(Out.str()));
+
+      llvm::Type *VPtrTy = llvm::PointerType::get(IntPtrTy, 0);
       Address VPtrAddr(Ptr, IntPtrTy, getPointerAlign());
-      llvm::Value *VPtrVal = Builder.CreateLoad(VPtrAddr);
+      llvm::Value *VPtrVal = GetVTablePtr(VPtrAddr, VPtrTy,
+                                          Ty->getAsCXXRecordDecl(),
+                                          VTableAuthMode::UnsafeUbsanStrip);
+      VPtrVal = Builder.CreateBitOrPointerCast(VPtrVal, IntPtrTy);
+
       llvm::Value *Hash =
           emitHashMix(Builder, TypeHash, Builder.CreateZExt(VPtrVal, Int64Ty));
       Hash = Builder.CreateTrunc(Hash, IntPtrTy);

diff  --git a/clang/lib/CodeGen/CGExprConstant.cpp b/clang/lib/CodeGen/CGExprConstant.cpp
index 0f3e29707cb79..1fec587b5c4c7 100644
--- a/clang/lib/CodeGen/CGExprConstant.cpp
+++ b/clang/lib/CodeGen/CGExprConstant.cpp
@@ -803,6 +803,13 @@ bool ConstStructBuilder::Build(const APValue &Val, const RecordDecl *RD,
       llvm::Constant *VTableAddressPoint =
           CGM.getCXXABI().getVTableAddressPoint(BaseSubobject(CD, Offset),
                                                 VTableClass);
+      if (auto Authentication =
+              CGM.getVTablePointerAuthentication(VTableClass)) {
+        VTableAddressPoint = Emitter.tryEmitConstantSignedPointer(
+            VTableAddressPoint, *Authentication);
+        if (!VTableAddressPoint)
+          return false;
+      }
       if (!AppendBytes(Offset, VTableAddressPoint))
         return false;
     }
@@ -1647,7 +1654,7 @@ namespace {
       // messing around with llvm::Constant structures, which never itself
       // does anything that should be visible in compiler output.
       for (auto &entry : Locations) {
-        assert(entry.first->getParent() == nullptr && "not a placeholder!");
+        assert(entry.first->getName() == "" && "not a placeholder!");
         entry.first->replaceAllUsesWith(entry.second);
         entry.first->eraseFromParent();
       }
@@ -1811,6 +1818,43 @@ llvm::Constant *ConstantEmitter::tryEmitPrivateForMemory(const APValue &value,
   return (C ? emitForMemory(C, destType) : nullptr);
 }
 
+/// Try to emit a constant signed pointer, given a raw pointer and the
+/// destination ptrauth qualifier.
+///
+/// This can fail if the qualifier needs address discrimination and the
+/// emitter is in an abstract mode.
+llvm::Constant *
+ConstantEmitter::tryEmitConstantSignedPointer(llvm::Constant *UnsignedPointer,
+                                              PointerAuthQualifier Schema) {
+  assert(Schema && "applying trivial ptrauth schema");
+
+  if (Schema.hasKeyNone())
+    return UnsignedPointer;
+
+  unsigned Key = Schema.getKey();
+
+  // Create an address placeholder if we're using address discrimination.
+  llvm::GlobalValue *StorageAddress = nullptr;
+  if (Schema.isAddressDiscriminated()) {
+    // We can't do this if the emitter is in an abstract state.
+    if (isAbstract())
+      return nullptr;
+
+    StorageAddress = getCurrentAddrPrivate();
+  }
+
+  llvm::ConstantInt *Discriminator =
+      llvm::ConstantInt::get(CGM.IntPtrTy, Schema.getExtraDiscriminator());
+
+  llvm::Constant *SignedPointer = CGM.getConstantSignedPointer(
+      UnsignedPointer, Key, StorageAddress, Discriminator);
+
+  if (Schema.isAddressDiscriminated())
+    registerCurrentAddrPrivate(SignedPointer, StorageAddress);
+
+  return SignedPointer;
+}
+
 llvm::Constant *ConstantEmitter::emitForMemory(CodeGenModule &CGM,
                                                llvm::Constant *C,
                                                QualType destType) {

diff  --git a/clang/lib/CodeGen/CGPointerAuth.cpp b/clang/lib/CodeGen/CGPointerAuth.cpp
index f0819b0467489..673f6e60bfc31 100644
--- a/clang/lib/CodeGen/CGPointerAuth.cpp
+++ b/clang/lib/CodeGen/CGPointerAuth.cpp
@@ -11,12 +11,56 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include "CodeGenFunction.h"
 #include "CodeGenModule.h"
 #include "clang/CodeGen/CodeGenABITypes.h"
+#include "clang/CodeGen/ConstantInitBuilder.h"
+#include "llvm/Support/SipHash.h"
 
 using namespace clang;
 using namespace CodeGen;
 
+/// Given a pointer-authentication schema, return a concrete "other"
+/// discriminator for it.
+llvm::ConstantInt *CodeGenModule::getPointerAuthOtherDiscriminator(
+    const PointerAuthSchema &Schema, GlobalDecl Decl, QualType Type) {
+  switch (Schema.getOtherDiscrimination()) {
+  case PointerAuthSchema::Discrimination::None:
+    return nullptr;
+
+  case PointerAuthSchema::Discrimination::Type:
+    llvm_unreachable("type discrimination not implemented yet");
+
+  case PointerAuthSchema::Discrimination::Decl:
+    assert(Decl.getDecl() &&
+           "declaration not provided for decl-discriminated schema");
+    return llvm::ConstantInt::get(IntPtrTy,
+                                  getPointerAuthDeclDiscriminator(Decl));
+
+  case PointerAuthSchema::Discrimination::Constant:
+    return llvm::ConstantInt::get(IntPtrTy, Schema.getConstantDiscrimination());
+  }
+  llvm_unreachable("bad discrimination kind");
+}
+
+uint16_t CodeGen::getPointerAuthDeclDiscriminator(CodeGenModule &CGM,
+                                                  GlobalDecl Declaration) {
+  return CGM.getPointerAuthDeclDiscriminator(Declaration);
+}
+
+/// Return the "other" decl-specific discriminator for the given decl.
+uint16_t
+CodeGenModule::getPointerAuthDeclDiscriminator(GlobalDecl Declaration) {
+  uint16_t &EntityHash = PtrAuthDiscriminatorHashes[Declaration];
+
+  if (EntityHash == 0) {
+    StringRef Name = getMangledName(Declaration);
+    EntityHash = llvm::getPointerAuthStableSipHash(Name);
+  }
+
+  return EntityHash;
+}
+
 /// Return the abstract pointer authentication schema for a pointer to the given
 /// function type.
 CGPointerAuthInfo CodeGenModule::getFunctionPointerAuthInfo(QualType T) {
@@ -35,6 +79,41 @@ CGPointerAuthInfo CodeGenModule::getFunctionPointerAuthInfo(QualType T) {
                            /*Discriminator=*/nullptr);
 }
 
+llvm::Value *
+CodeGenFunction::EmitPointerAuthBlendDiscriminator(llvm::Value *StorageAddress,
+                                                   llvm::Value *Discriminator) {
+  StorageAddress = Builder.CreatePtrToInt(StorageAddress, IntPtrTy);
+  auto Intrinsic = CGM.getIntrinsic(llvm::Intrinsic::ptrauth_blend);
+  return Builder.CreateCall(Intrinsic, {StorageAddress, Discriminator});
+}
+
+/// Emit the concrete pointer authentication informaton for the
+/// given authentication schema.
+CGPointerAuthInfo CodeGenFunction::EmitPointerAuthInfo(
+    const PointerAuthSchema &Schema, llvm::Value *StorageAddress,
+    GlobalDecl SchemaDecl, QualType SchemaType) {
+  if (!Schema)
+    return CGPointerAuthInfo();
+
+  llvm::Value *Discriminator =
+      CGM.getPointerAuthOtherDiscriminator(Schema, SchemaDecl, SchemaType);
+
+  if (Schema.isAddressDiscriminated()) {
+    assert(StorageAddress &&
+           "address not provided for address-discriminated schema");
+
+    if (Discriminator)
+      Discriminator =
+          EmitPointerAuthBlendDiscriminator(StorageAddress, Discriminator);
+    else
+      Discriminator = Builder.CreatePtrToInt(StorageAddress, IntPtrTy);
+  }
+
+  return CGPointerAuthInfo(Schema.getKey(), Schema.getAuthenticationMode(),
+                           Schema.isIsaPointer(),
+                           Schema.authenticatesNullValues(), Discriminator);
+}
+
 llvm::Constant *
 CodeGenModule::getConstantSignedPointer(llvm::Constant *Pointer, unsigned Key,
                                         llvm::Constant *StorageAddress,
@@ -60,6 +139,29 @@ CodeGenModule::getConstantSignedPointer(llvm::Constant *Pointer, unsigned Key,
                                     IntegerDiscriminator, AddressDiscriminator);
 }
 
+/// Does a given PointerAuthScheme require us to sign a value
+bool CodeGenModule::shouldSignPointer(const PointerAuthSchema &Schema) {
+  auto AuthenticationMode = Schema.getAuthenticationMode();
+  return AuthenticationMode == PointerAuthenticationMode::SignAndStrip ||
+         AuthenticationMode == PointerAuthenticationMode::SignAndAuth;
+}
+
+/// Sign a constant pointer using the given scheme, producing a constant
+/// with the same IR type.
+llvm::Constant *CodeGenModule::getConstantSignedPointer(
+    llvm::Constant *Pointer, const PointerAuthSchema &Schema,
+    llvm::Constant *StorageAddress, GlobalDecl SchemaDecl,
+    QualType SchemaType) {
+  assert(shouldSignPointer(Schema));
+  llvm::ConstantInt *OtherDiscriminator =
+      getPointerAuthOtherDiscriminator(Schema, SchemaDecl, SchemaType);
+
+  return getConstantSignedPointer(Pointer, Schema.getKey(), StorageAddress,
+                                  OtherDiscriminator);
+}
+
+/// If applicable, sign a given constant function pointer with the ABI rules for
+/// functionType.
 llvm::Constant *CodeGenModule::getFunctionPointer(llvm::Constant *Pointer,
                                                   QualType FunctionType) {
   assert(FunctionType->isFunctionType() ||
@@ -80,3 +182,113 @@ llvm::Constant *CodeGenModule::getFunctionPointer(GlobalDecl GD,
   QualType FuncType = FD->getType();
   return getFunctionPointer(getRawFunctionPointer(GD, Ty), FuncType);
 }
+
+std::optional<PointerAuthQualifier>
+CodeGenModule::computeVTPointerAuthentication(const CXXRecordDecl *ThisClass) {
+  auto DefaultAuthentication = getCodeGenOpts().PointerAuth.CXXVTablePointers;
+  if (!DefaultAuthentication)
+    return std::nullopt;
+  const CXXRecordDecl *PrimaryBase =
+      Context.baseForVTableAuthentication(ThisClass);
+
+  unsigned Key = DefaultAuthentication.getKey();
+  bool AddressDiscriminated = DefaultAuthentication.isAddressDiscriminated();
+  auto DefaultDiscrimination = DefaultAuthentication.getOtherDiscrimination();
+  unsigned TypeBasedDiscriminator =
+      Context.getPointerAuthVTablePointerDiscriminator(PrimaryBase);
+  unsigned Discriminator;
+  if (DefaultDiscrimination == PointerAuthSchema::Discrimination::Type) {
+    Discriminator = TypeBasedDiscriminator;
+  } else if (DefaultDiscrimination ==
+             PointerAuthSchema::Discrimination::Constant) {
+    Discriminator = DefaultAuthentication.getConstantDiscrimination();
+  } else {
+    assert(DefaultDiscrimination == PointerAuthSchema::Discrimination::None);
+    Discriminator = 0;
+  }
+  if (auto ExplicitAuthentication =
+          PrimaryBase->getAttr<VTablePointerAuthenticationAttr>()) {
+    auto ExplicitAddressDiscrimination =
+        ExplicitAuthentication->getAddressDiscrimination();
+    auto ExplicitDiscriminator =
+        ExplicitAuthentication->getExtraDiscrimination();
+
+    unsigned ExplicitKey = ExplicitAuthentication->getKey();
+    if (ExplicitKey == VTablePointerAuthenticationAttr::NoKey)
+      return std::nullopt;
+
+    if (ExplicitKey != VTablePointerAuthenticationAttr::DefaultKey) {
+      if (ExplicitKey == VTablePointerAuthenticationAttr::ProcessIndependent)
+        Key = (unsigned)PointerAuthSchema::ARM8_3Key::ASDA;
+      else {
+        assert(ExplicitKey ==
+               VTablePointerAuthenticationAttr::ProcessDependent);
+        Key = (unsigned)PointerAuthSchema::ARM8_3Key::ASDB;
+      }
+    }
+
+    if (ExplicitAddressDiscrimination !=
+        VTablePointerAuthenticationAttr::DefaultAddressDiscrimination)
+      AddressDiscriminated =
+          ExplicitAddressDiscrimination ==
+          VTablePointerAuthenticationAttr::AddressDiscrimination;
+
+    if (ExplicitDiscriminator ==
+        VTablePointerAuthenticationAttr::TypeDiscrimination)
+      Discriminator = TypeBasedDiscriminator;
+    else if (ExplicitDiscriminator ==
+             VTablePointerAuthenticationAttr::CustomDiscrimination)
+      Discriminator = ExplicitAuthentication->getCustomDiscriminationValue();
+    else if (ExplicitDiscriminator ==
+             VTablePointerAuthenticationAttr::NoExtraDiscrimination)
+      Discriminator = 0;
+  }
+  return PointerAuthQualifier::Create(Key, AddressDiscriminated, Discriminator,
+                                      PointerAuthenticationMode::SignAndAuth,
+                                      /* IsIsaPointer */ false,
+                                      /* AuthenticatesNullValues */ false);
+}
+
+std::optional<PointerAuthQualifier>
+CodeGenModule::getVTablePointerAuthentication(const CXXRecordDecl *Record) {
+  if (!Record->getDefinition() || !Record->isPolymorphic())
+    return std::nullopt;
+
+  auto Existing = VTablePtrAuthInfos.find(Record);
+  std::optional<PointerAuthQualifier> Authentication;
+  if (Existing != VTablePtrAuthInfos.end()) {
+    Authentication = Existing->getSecond();
+  } else {
+    Authentication = computeVTPointerAuthentication(Record);
+    VTablePtrAuthInfos.insert(std::make_pair(Record, Authentication));
+  }
+  return Authentication;
+}
+
+std::optional<CGPointerAuthInfo>
+CodeGenModule::getVTablePointerAuthInfo(CodeGenFunction *CGF,
+                                        const CXXRecordDecl *Record,
+                                        llvm::Value *StorageAddress) {
+  auto Authentication = getVTablePointerAuthentication(Record);
+  if (!Authentication)
+    return std::nullopt;
+
+  llvm::Value *Discriminator = nullptr;
+  if (auto ExtraDiscriminator = Authentication->getExtraDiscriminator())
+    Discriminator = llvm::ConstantInt::get(IntPtrTy, ExtraDiscriminator);
+
+  if (Authentication->isAddressDiscriminated()) {
+    assert(StorageAddress &&
+           "address not provided for address-discriminated schema");
+    if (Discriminator)
+      Discriminator =
+          CGF->EmitPointerAuthBlendDiscriminator(StorageAddress, Discriminator);
+    else
+      Discriminator = CGF->Builder.CreatePtrToInt(StorageAddress, IntPtrTy);
+  }
+
+  return CGPointerAuthInfo(Authentication->getKey(),
+                           PointerAuthenticationMode::SignAndAuth,
+                           /* IsIsaPointer */ false,
+                           /* AuthenticatesNullValues */ false, Discriminator);
+}

diff  --git a/clang/lib/CodeGen/CGVTT.cpp b/clang/lib/CodeGen/CGVTT.cpp
index 4cebb750c89e8..20bd2c2fc2c64 100644
--- a/clang/lib/CodeGen/CGVTT.cpp
+++ b/clang/lib/CodeGen/CGVTT.cpp
@@ -90,6 +90,11 @@ CodeGenVTables::EmitVTTDefinition(llvm::GlobalVariable *VTT,
      llvm::Constant *Init = llvm::ConstantExpr::getGetElementPtr(
          VTable->getValueType(), VTable, Idxs, /*InBounds=*/true, InRange);
 
+     if (const auto &Schema =
+             CGM.getCodeGenOpts().PointerAuth.CXXVTTVTablePointers)
+       Init = CGM.getConstantSignedPointer(Init, Schema, nullptr, GlobalDecl(),
+                                           QualType());
+
      VTTComponents.push_back(Init);
   }
 

diff  --git a/clang/lib/CodeGen/CGVTables.cpp b/clang/lib/CodeGen/CGVTables.cpp
index 55c3032dc9332..3e88cd73e8c1f 100644
--- a/clang/lib/CodeGen/CGVTables.cpp
+++ b/clang/lib/CodeGen/CGVTables.cpp
@@ -95,7 +95,7 @@ static RValue PerformReturnAdjustment(CodeGenFunction &CGF,
       CGF,
       Address(ReturnValue, CGF.ConvertTypeForMem(ResultType->getPointeeType()),
               ClassAlign),
-      Thunk.Return);
+      ClassDecl, Thunk.Return);
 
   if (NullCheckValue) {
     CGF.Builder.CreateBr(AdjustEnd);
@@ -219,8 +219,10 @@ CodeGenFunction::GenerateVarArgsThunk(llvm::Function *Fn,
          "Store of this should be in entry block?");
   // Adjust "this", if necessary.
   Builder.SetInsertPoint(&*ThisStore);
-  llvm::Value *AdjustedThisPtr =
-      CGM.getCXXABI().performThisAdjustment(*this, ThisPtr, Thunk.This);
+
+  const CXXRecordDecl *ThisValueClass = Thunk.ThisType->getPointeeCXXRecordDecl();
+  llvm::Value *AdjustedThisPtr = CGM.getCXXABI().performThisAdjustment(
+      *this, ThisPtr, ThisValueClass, Thunk);
   AdjustedThisPtr = Builder.CreateBitCast(AdjustedThisPtr,
                                           ThisStore->getOperand(0)->getType());
   ThisStore->setOperand(0, AdjustedThisPtr);
@@ -307,10 +309,15 @@ void CodeGenFunction::EmitCallAndReturnForThunk(llvm::FunctionCallee Callee,
   const CXXMethodDecl *MD = cast<CXXMethodDecl>(CurGD.getDecl());
 
   // Adjust the 'this' pointer if necessary
+  const CXXRecordDecl *ThisValueClass =
+      MD->getThisType()->getPointeeCXXRecordDecl();
+  if (Thunk)
+    ThisValueClass = Thunk->ThisType->getPointeeCXXRecordDecl();
+
   llvm::Value *AdjustedThisPtr =
-    Thunk ? CGM.getCXXABI().performThisAdjustment(
-                          *this, LoadCXXThisAddress(), Thunk->This)
-          : LoadCXXThis();
+      Thunk ? CGM.getCXXABI().performThisAdjustment(*this, LoadCXXThisAddress(),
+                                                    ThisValueClass, *Thunk)
+            : LoadCXXThis();
 
   // If perfect forwarding is required a variadic method, a method using
   // inalloca, or an unprototyped thunk, use musttail. Emit an error if this
@@ -504,10 +511,22 @@ llvm::Constant *CodeGenVTables::maybeEmitThunk(GlobalDecl GD,
   SmallString<256> Name;
   MangleContext &MCtx = CGM.getCXXABI().getMangleContext();
   llvm::raw_svector_ostream Out(Name);
-  if (const CXXDestructorDecl *DD = dyn_cast<CXXDestructorDecl>(MD))
-    MCtx.mangleCXXDtorThunk(DD, GD.getDtorType(), TI.This, Out);
-  else
-    MCtx.mangleThunk(MD, TI, Out);
+
+  if (const CXXDestructorDecl *DD = dyn_cast<CXXDestructorDecl>(MD)) {
+    MCtx.mangleCXXDtorThunk(DD, GD.getDtorType(), TI,
+                            /* elideOverrideInfo */ false, Out);
+  } else
+    MCtx.mangleThunk(MD, TI, /* elideOverrideInfo */ false, Out);
+
+  if (CGM.getContext().useAbbreviatedThunkName(GD, Name.str())) {
+    Name = "";
+    if (const CXXDestructorDecl *DD = dyn_cast<CXXDestructorDecl>(MD))
+      MCtx.mangleCXXDtorThunk(DD, GD.getDtorType(), TI,
+                              /* elideOverrideInfo */ true, Out);
+    else
+      MCtx.mangleThunk(MD, TI, /* elideOverrideInfo */ true, Out);
+  }
+
   llvm::Type *ThunkVTableTy = CGM.getTypes().GetFunctionTypeForVTable(GD);
   llvm::Constant *Thunk = CGM.GetAddrOfThunk(Name, ThunkVTableTy, GD);
 
@@ -819,11 +838,17 @@ void CodeGenVTables::addVTableComponent(ConstantArrayBuilder &builder,
 
       nextVTableThunkIndex++;
       fnPtr = maybeEmitThunk(GD, thunkInfo, /*ForVTable=*/true);
+      if (CGM.getCodeGenOpts().PointerAuth.CXXVirtualFunctionPointers) {
+        assert(thunkInfo.Method &&  "Method not set");
+        GD = GD.getWithDecl(thunkInfo.Method);
+      }
 
     // Otherwise we can use the method definition directly.
     } else {
       llvm::Type *fnTy = CGM.getTypes().GetFunctionTypeForVTable(GD);
       fnPtr = CGM.GetAddrOfFunction(GD, fnTy, /*ForVTable=*/true);
+      if (CGM.getCodeGenOpts().PointerAuth.CXXVirtualFunctionPointers)
+        GD = getItaniumVTableContext().findOriginalMethod(GD);
     }
 
     if (useRelativeLayout()) {
@@ -841,6 +866,9 @@ void CodeGenVTables::addVTableComponent(ConstantArrayBuilder &builder,
       if (FnAS != GVAS)
         fnPtr =
             llvm::ConstantExpr::getAddrSpaceCast(fnPtr, CGM.GlobalsInt8PtrTy);
+      if (const auto &Schema =
+          CGM.getCodeGenOpts().PointerAuth.CXXVirtualFunctionPointers)
+        return builder.addSignedPointer(fnPtr, Schema, GD, QualType());
       return builder.add(fnPtr);
     }
   }

diff  --git a/clang/lib/CodeGen/CodeGenFunction.cpp b/clang/lib/CodeGen/CodeGenFunction.cpp
index 650c5662539f9..26deeca95d326 100644
--- a/clang/lib/CodeGen/CodeGenFunction.cpp
+++ b/clang/lib/CodeGen/CodeGenFunction.cpp
@@ -3063,3 +3063,66 @@ void CodeGenFunction::EmitPointerAuthOperandBundle(
   llvm::Value *Args[] = {Key, Discriminator};
   Bundles.emplace_back("ptrauth", Args);
 }
+
+static llvm::Value *EmitPointerAuthCommon(CodeGenFunction &CGF,
+                                          const CGPointerAuthInfo &PointerAuth,
+                                          llvm::Value *Pointer,
+                                          unsigned IntrinsicID) {
+  if (!PointerAuth)
+    return Pointer;
+
+  auto Key = CGF.Builder.getInt32(PointerAuth.getKey());
+
+  llvm::Value *Discriminator = PointerAuth.getDiscriminator();
+  if (!Discriminator) {
+    Discriminator = CGF.Builder.getSize(0);
+  }
+
+  // Convert the pointer to intptr_t before signing it.
+  auto OrigType = Pointer->getType();
+  Pointer = CGF.Builder.CreatePtrToInt(Pointer, CGF.IntPtrTy);
+
+  // call i64 @llvm.ptrauth.sign.i64(i64 %pointer, i32 %key, i64 %discriminator)
+  auto Intrinsic = CGF.CGM.getIntrinsic(IntrinsicID);
+  Pointer = CGF.EmitRuntimeCall(Intrinsic, {Pointer, Key, Discriminator});
+
+  // Convert back to the original type.
+  Pointer = CGF.Builder.CreateIntToPtr(Pointer, OrigType);
+  return Pointer;
+}
+
+llvm::Value *
+CodeGenFunction::EmitPointerAuthSign(const CGPointerAuthInfo &PointerAuth,
+                                     llvm::Value *Pointer) {
+  if (!PointerAuth.shouldSign())
+    return Pointer;
+  return EmitPointerAuthCommon(*this, PointerAuth, Pointer,
+                               llvm::Intrinsic::ptrauth_sign);
+}
+
+static llvm::Value *EmitStrip(CodeGenFunction &CGF,
+                              const CGPointerAuthInfo &PointerAuth,
+                              llvm::Value *Pointer) {
+  auto StripIntrinsic = CGF.CGM.getIntrinsic(llvm::Intrinsic::ptrauth_strip);
+
+  auto Key = CGF.Builder.getInt32(PointerAuth.getKey());
+  // Convert the pointer to intptr_t before signing it.
+  auto OrigType = Pointer->getType();
+  Pointer = CGF.EmitRuntimeCall(
+      StripIntrinsic, {CGF.Builder.CreatePtrToInt(Pointer, CGF.IntPtrTy), Key});
+  return CGF.Builder.CreateIntToPtr(Pointer, OrigType);
+}
+
+llvm::Value *
+CodeGenFunction::EmitPointerAuthAuth(const CGPointerAuthInfo &PointerAuth,
+                                     llvm::Value *Pointer) {
+  if (PointerAuth.shouldStrip()) {
+    return EmitStrip(*this, PointerAuth, Pointer);
+  }
+  if (!PointerAuth.shouldAuth()) {
+    return Pointer;
+  }
+
+  return EmitPointerAuthCommon(*this, PointerAuth, Pointer,
+                               llvm::Intrinsic::ptrauth_auth);
+}

diff  --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index a9c497bde6871..13f12b5d878a6 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -2453,10 +2453,20 @@ class CodeGenFunction : public CodeGenTypeCache {
 
   void InitializeVTablePointers(const CXXRecordDecl *ClassDecl);
 
+  // VTableTrapMode - whether we guarantee that loading the
+  // vtable is guaranteed to trap on authentication failure,
+  // even if the resulting vtable pointer is unused.
+  enum class VTableAuthMode {
+    Authenticate,
+    MustTrap,
+    UnsafeUbsanStrip // Should only be used for Vptr UBSan check
+  };
   /// GetVTablePtr - Return the Value of the vtable pointer member pointed
   /// to by This.
-  llvm::Value *GetVTablePtr(Address This, llvm::Type *VTableTy,
-                            const CXXRecordDecl *VTableClass);
+  llvm::Value *
+  GetVTablePtr(Address This, llvm::Type *VTableTy,
+               const CXXRecordDecl *VTableClass,
+               VTableAuthMode AuthMode = VTableAuthMode::Authenticate);
 
   enum CFITypeCheckKind {
     CFITCK_VCall,
@@ -4417,6 +4427,19 @@ class CodeGenFunction : public CodeGenTypeCache {
 
   bool isPointerKnownNonNull(const Expr *E);
 
+  /// Create the discriminator from the storage address and the entity hash.
+  llvm::Value *EmitPointerAuthBlendDiscriminator(llvm::Value *StorageAddress,
+                                                 llvm::Value *Discriminator);
+  CGPointerAuthInfo EmitPointerAuthInfo(const PointerAuthSchema &Schema,
+                                        llvm::Value *StorageAddress,
+                                        GlobalDecl SchemaDecl,
+                                        QualType SchemaType);
+  llvm::Value *EmitPointerAuthSign(QualType PointeeType, llvm::Value *Pointer);
+  llvm::Value *EmitPointerAuthSign(const CGPointerAuthInfo &Info,
+                                   llvm::Value *Pointer);
+  llvm::Value *EmitPointerAuthAuth(const CGPointerAuthInfo &Info,
+                                   llvm::Value *Pointer);
+
   void EmitPointerAuthOperandBundle(
       const CGPointerAuthInfo &Info,
       SmallVectorImpl<llvm::OperandBundleDef> &Bundles);

diff  --git a/clang/lib/CodeGen/CodeGenModule.h b/clang/lib/CodeGen/CodeGenModule.h
index 9913304757caa..22b2b314c316c 100644
--- a/clang/lib/CodeGen/CodeGenModule.h
+++ b/clang/lib/CodeGen/CodeGenModule.h
@@ -609,6 +609,13 @@ class CodeGenModule : public CodeGenTypeCache {
   std::pair<std::unique_ptr<CodeGenFunction>, const TopLevelStmtDecl *>
       GlobalTopLevelStmtBlockInFlight;
 
+  llvm::DenseMap<GlobalDecl, uint16_t> PtrAuthDiscriminatorHashes;
+
+  llvm::DenseMap<const CXXRecordDecl *, std::optional<PointerAuthQualifier>>
+      VTablePtrAuthInfos;
+  std::optional<PointerAuthQualifier>
+  computeVTPointerAuthentication(const CXXRecordDecl *ThisClass);
+
 public:
   CodeGenModule(ASTContext &C, IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS,
                 const HeaderSearchOptions &headersearchopts,
@@ -957,11 +964,33 @@ class CodeGenModule : public CodeGenTypeCache {
 
   CGPointerAuthInfo getFunctionPointerAuthInfo(QualType T);
 
+  bool shouldSignPointer(const PointerAuthSchema &Schema);
+  llvm::Constant *getConstantSignedPointer(llvm::Constant *Pointer,
+                                           const PointerAuthSchema &Schema,
+                                           llvm::Constant *StorageAddress,
+                                           GlobalDecl SchemaDecl,
+                                           QualType SchemaType);
+
   llvm::Constant *
   getConstantSignedPointer(llvm::Constant *Pointer, unsigned Key,
                            llvm::Constant *StorageAddress,
                            llvm::ConstantInt *OtherDiscriminator);
 
+  llvm::ConstantInt *
+  getPointerAuthOtherDiscriminator(const PointerAuthSchema &Schema,
+                                   GlobalDecl SchemaDecl, QualType SchemaType);
+
+  uint16_t getPointerAuthDeclDiscriminator(GlobalDecl GD);
+  std::optional<CGPointerAuthInfo>
+  getVTablePointerAuthInfo(CodeGenFunction *Context,
+                           const CXXRecordDecl *Record,
+                           llvm::Value *StorageAddress);
+
+  std::optional<PointerAuthQualifier>
+  getVTablePointerAuthentication(const CXXRecordDecl *thisClass);
+
+  CGPointerAuthInfo EmitPointerAuthInfo(const RecordDecl *RD);
+
   // Return whether RTTI information should be emitted for this target.
   bool shouldEmitRTTI(bool ForEH = false) {
     return (ForEH || getLangOpts().RTTI) && !getLangOpts().CUDAIsDevice &&

diff  --git a/clang/lib/CodeGen/ConstantEmitter.h b/clang/lib/CodeGen/ConstantEmitter.h
index a55da0dcad792..eff0a8d52891f 100644
--- a/clang/lib/CodeGen/ConstantEmitter.h
+++ b/clang/lib/CodeGen/ConstantEmitter.h
@@ -113,6 +113,9 @@ class ConstantEmitter {
   llvm::Constant *tryEmitAbstract(const APValue &value, QualType T);
   llvm::Constant *tryEmitAbstractForMemory(const APValue &value, QualType T);
 
+  llvm::Constant *tryEmitConstantSignedPointer(llvm::Constant *Ptr,
+                                               PointerAuthQualifier Auth);
+
   llvm::Constant *tryEmitConstantExpr(const ConstantExpr *CE);
 
   llvm::Constant *emitNullForMemory(QualType T) {

diff  --git a/clang/lib/CodeGen/ConstantInitBuilder.cpp b/clang/lib/CodeGen/ConstantInitBuilder.cpp
index 3cf69f3b6415e..549d5dd66b123 100644
--- a/clang/lib/CodeGen/ConstantInitBuilder.cpp
+++ b/clang/lib/CodeGen/ConstantInitBuilder.cpp
@@ -296,3 +296,21 @@ ConstantAggregateBuilderBase::finishStruct(llvm::StructType *ty) {
   buffer.erase(buffer.begin() + Begin, buffer.end());
   return constant;
 }
+
+/// Sign the given pointer and add it to the constant initializer
+/// currently being built.
+void ConstantAggregateBuilderBase::addSignedPointer(
+    llvm::Constant *Pointer, const PointerAuthSchema &Schema,
+    GlobalDecl CalleeDecl, QualType CalleeType) {
+  if (!Schema || !Builder.CGM.shouldSignPointer(Schema))
+    return add(Pointer);
+
+  llvm::Constant *StorageAddress = nullptr;
+  if (Schema.isAddressDiscriminated()) {
+    StorageAddress = getAddrOfCurrentPosition(Pointer->getType());
+  }
+
+  llvm::Constant *SignedPointer = Builder.CGM.getConstantSignedPointer(
+      Pointer, Schema, StorageAddress, CalleeDecl, CalleeType);
+  add(SignedPointer);
+}

diff  --git a/clang/lib/CodeGen/ItaniumCXXABI.cpp b/clang/lib/CodeGen/ItaniumCXXABI.cpp
index 01a735c1437e1..63e36e1b83893 100644
--- a/clang/lib/CodeGen/ItaniumCXXABI.cpp
+++ b/clang/lib/CodeGen/ItaniumCXXABI.cpp
@@ -23,6 +23,7 @@
 #include "CGVTables.h"
 #include "CodeGenFunction.h"
 #include "CodeGenModule.h"
+#include "ConstantEmitter.h"
 #include "TargetInfo.h"
 #include "clang/AST/Attr.h"
 #include "clang/AST/Mangle.h"
@@ -336,9 +337,11 @@ class ItaniumCXXABI : public CodeGen::CGCXXABI {
   bool exportThunk() override { return true; }
 
   llvm::Value *performThisAdjustment(CodeGenFunction &CGF, Address This,
-                                     const ThisAdjustment &TA) override;
+                                     const CXXRecordDecl *UnadjustedThisClass,
+                                     const ThunkInfo &TI) override;
 
   llvm::Value *performReturnAdjustment(CodeGenFunction &CGF, Address Ret,
+                                       const CXXRecordDecl *UnadjustedRetClass,
                                        const ReturnAdjustment &RA) override;
 
   size_t getSrcArgforCopyCtor(const CXXConstructorDecl *,
@@ -1477,10 +1480,22 @@ llvm::Value *ItaniumCXXABI::emitDynamicCastCall(
       computeOffsetHint(CGF.getContext(), SrcDecl, DestDecl).getQuantity());
 
   // Emit the call to __dynamic_cast.
-  llvm::Value *Args[] = {ThisAddr.emitRawPointer(CGF), SrcRTTI, DestRTTI,
-                         OffsetHint};
-  llvm::Value *Value =
-      CGF.EmitNounwindRuntimeCall(getItaniumDynamicCastFn(CGF), Args);
+  llvm::Value *Value = ThisAddr.emitRawPointer(CGF);
+  if (CGM.getCodeGenOpts().PointerAuth.CXXVTablePointers) {
+    // We perform a no-op load of the vtable pointer here to force an
+    // authentication. In environments that do not support pointer
+    // authentication this is a an actual no-op that will be elided. When
+    // pointer authentication is supported and enforced on vtable pointers this
+    // load can trap.
+    llvm::Value *Vtable =
+        CGF.GetVTablePtr(ThisAddr, CGM.Int8PtrTy, SrcDecl,
+                         CodeGenFunction::VTableAuthMode::MustTrap);
+    assert(Vtable);
+    (void)Vtable;
+  }
+
+  llvm::Value *args[] = {Value, SrcRTTI, DestRTTI, OffsetHint};
+  Value = CGF.EmitNounwindRuntimeCall(getItaniumDynamicCastFn(CGF), args);
 
   /// C++ [expr.dynamic.cast]p9:
   ///   A failed cast to reference type throws std::bad_cast
@@ -1955,8 +1970,18 @@ llvm::Value *ItaniumCXXABI::getVTableAddressPointInStructorWithVTT(
                                                  VirtualPointerIndex);
 
   // And load the address point from the VTT.
-  return CGF.Builder.CreateAlignedLoad(CGF.GlobalsVoidPtrTy, VTT,
-                                       CGF.getPointerAlign());
+  llvm::Value *AP =
+      CGF.Builder.CreateAlignedLoad(CGF.GlobalsVoidPtrTy, VTT,
+                                    CGF.getPointerAlign());
+
+  if (auto &Schema = CGF.CGM.getCodeGenOpts().PointerAuth.CXXVTTVTablePointers) {
+    CGPointerAuthInfo PointerAuth = CGF.EmitPointerAuthInfo(Schema, VTT,
+                                                            GlobalDecl(),
+                                                            QualType());
+    AP = CGF.EmitPointerAuthAuth(PointerAuth, AP);
+  }
+
+  return AP;
 }
 
 llvm::GlobalVariable *ItaniumCXXABI::getAddrOfVTable(const CXXRecordDecl *RD,
@@ -2008,8 +2033,9 @@ CGCallee ItaniumCXXABI::getVirtualFunctionPointer(CodeGenFunction &CGF,
   llvm::Value *VTable = CGF.GetVTablePtr(This, PtrTy, MethodDecl->getParent());
 
   uint64_t VTableIndex = CGM.getItaniumVTableContext().getMethodVTableIndex(GD);
-  llvm::Value *VFunc;
-  if (CGF.ShouldEmitVTableTypeCheckedLoad(MethodDecl->getParent())) {
+  llvm::Value *VFunc, *VTableSlotPtr = nullptr;
+  auto &Schema = CGM.getCodeGenOpts().PointerAuth.CXXVirtualFunctionPointers;
+  if (!Schema && CGF.ShouldEmitVTableTypeCheckedLoad(MethodDecl->getParent())) {
     VFunc = CGF.EmitVTableTypeCheckedLoad(
         MethodDecl->getParent(), VTable, PtrTy,
         VTableIndex *
@@ -2024,7 +2050,7 @@ CGCallee ItaniumCXXABI::getVirtualFunctionPointer(CodeGenFunction &CGF,
           CGM.getIntrinsic(llvm::Intrinsic::load_relative, {CGM.Int32Ty}),
           {VTable, llvm::ConstantInt::get(CGM.Int32Ty, 4 * VTableIndex)});
     } else {
-      llvm::Value *VTableSlotPtr = CGF.Builder.CreateConstInBoundsGEP1_64(
+      VTableSlotPtr = CGF.Builder.CreateConstInBoundsGEP1_64(
           PtrTy, VTable, VTableIndex, "vfn");
       VFuncLoad = CGF.Builder.CreateAlignedLoad(PtrTy, VTableSlotPtr,
                                                 CGF.getPointerAlign());
@@ -2048,7 +2074,13 @@ CGCallee ItaniumCXXABI::getVirtualFunctionPointer(CodeGenFunction &CGF,
     VFunc = VFuncLoad;
   }
 
-  CGCallee Callee(GD, VFunc);
+  CGPointerAuthInfo PointerAuth;
+  if (Schema) {
+    assert(VTableSlotPtr && "virtual function pointer not set");
+    GD = CGM.getItaniumVTableContext().findOriginalMethod(GD.getCanonicalDecl());
+    PointerAuth = CGF.EmitPointerAuthInfo(Schema, VTableSlotPtr, GD, QualType());
+  }
+  CGCallee Callee(GD, VFunc, PointerAuth);
   return Callee;
 }
 
@@ -2147,6 +2179,7 @@ bool ItaniumCXXABI::canSpeculativelyEmitVTable(const CXXRecordDecl *RD) const {
 }
 static llvm::Value *performTypeAdjustment(CodeGenFunction &CGF,
                                           Address InitialPtr,
+                                          const CXXRecordDecl *UnadjustedClass,
                                           int64_t NonVirtualAdjustment,
                                           int64_t VirtualAdjustment,
                                           bool IsReturnAdjustment) {
@@ -2164,8 +2197,8 @@ static llvm::Value *performTypeAdjustment(CodeGenFunction &CGF,
   // Perform the virtual adjustment if we have one.
   llvm::Value *ResultPtr;
   if (VirtualAdjustment) {
-    Address VTablePtrPtr = V.withElementType(CGF.Int8PtrTy);
-    llvm::Value *VTablePtr = CGF.Builder.CreateLoad(VTablePtrPtr);
+    llvm::Value *VTablePtr =
+        CGF.GetVTablePtr(V, CGF.Int8PtrTy, UnadjustedClass);
 
     llvm::Value *Offset;
     llvm::Value *OffsetPtr = CGF.Builder.CreateConstInBoundsGEP1_64(
@@ -2200,18 +2233,20 @@ static llvm::Value *performTypeAdjustment(CodeGenFunction &CGF,
   return ResultPtr;
 }
 
-llvm::Value *ItaniumCXXABI::performThisAdjustment(CodeGenFunction &CGF,
-                                                  Address This,
-                                                  const ThisAdjustment &TA) {
-  return performTypeAdjustment(CGF, This, TA.NonVirtual,
-                               TA.Virtual.Itanium.VCallOffsetOffset,
+llvm::Value *
+ItaniumCXXABI::performThisAdjustment(CodeGenFunction &CGF, Address This,
+                                     const CXXRecordDecl *UnadjustedClass,
+                                     const ThunkInfo &TI) {
+  return performTypeAdjustment(CGF, This, UnadjustedClass, TI.This.NonVirtual,
+                               TI.This.Virtual.Itanium.VCallOffsetOffset,
                                /*IsReturnAdjustment=*/false);
 }
 
 llvm::Value *
 ItaniumCXXABI::performReturnAdjustment(CodeGenFunction &CGF, Address Ret,
+                                       const CXXRecordDecl *UnadjustedClass,
                                        const ReturnAdjustment &RA) {
-  return performTypeAdjustment(CGF, Ret, RA.NonVirtual,
+  return performTypeAdjustment(CGF, Ret, UnadjustedClass, RA.NonVirtual,
                                RA.Virtual.Itanium.VBaseOffsetOffset,
                                /*IsReturnAdjustment=*/true);
 }
@@ -3694,6 +3729,10 @@ void ItaniumRTTIBuilder::BuildVTablePointer(const Type *Ty) {
                                                           VTable, Two);
   }
 
+  if (auto &Schema = CGM.getCodeGenOpts().PointerAuth.CXXTypeInfoVTablePointer)
+    VTable = CGM.getConstantSignedPointer(VTable, Schema, nullptr, GlobalDecl(),
+                                          QualType(Ty, 0));
+
   Fields.push_back(VTable);
 }
 

diff  --git a/clang/lib/CodeGen/MicrosoftCXXABI.cpp b/clang/lib/CodeGen/MicrosoftCXXABI.cpp
index 9ab634fa6ce2e..cc6740edabcd3 100644
--- a/clang/lib/CodeGen/MicrosoftCXXABI.cpp
+++ b/clang/lib/CodeGen/MicrosoftCXXABI.cpp
@@ -415,9 +415,11 @@ class MicrosoftCXXABI : public CGCXXABI {
   bool exportThunk() override { return false; }
 
   llvm::Value *performThisAdjustment(CodeGenFunction &CGF, Address This,
-                                     const ThisAdjustment &TA) override;
+                                     const CXXRecordDecl * /*UnadjustedClass*/,
+                                     const ThunkInfo &TI) override;
 
   llvm::Value *performReturnAdjustment(CodeGenFunction &CGF, Address Ret,
+                                       const CXXRecordDecl * /*UnadjustedClass*/,
                                        const ReturnAdjustment &RA) override;
 
   void EmitThreadLocalInitFuncs(
@@ -2223,9 +2225,10 @@ void MicrosoftCXXABI::emitVBTableDefinition(const VPtrInfo &VBT,
     GV->setLinkage(llvm::GlobalVariable::AvailableExternallyLinkage);
 }
 
-llvm::Value *MicrosoftCXXABI::performThisAdjustment(CodeGenFunction &CGF,
-                                                    Address This,
-                                                    const ThisAdjustment &TA) {
+llvm::Value *MicrosoftCXXABI::performThisAdjustment(
+    CodeGenFunction &CGF, Address This,
+    const CXXRecordDecl * /*UnadjustedClass*/, const ThunkInfo &TI) {
+  const ThisAdjustment &TA = TI.This;
   if (TA.isEmpty())
     return This.emitRawPointer(CGF);
 
@@ -2275,9 +2278,10 @@ llvm::Value *MicrosoftCXXABI::performThisAdjustment(CodeGenFunction &CGF,
   return V;
 }
 
-llvm::Value *
-MicrosoftCXXABI::performReturnAdjustment(CodeGenFunction &CGF, Address Ret,
-                                         const ReturnAdjustment &RA) {
+llvm::Value *MicrosoftCXXABI::performReturnAdjustment(
+    CodeGenFunction &CGF, Address Ret,
+    const CXXRecordDecl * /*UnadjustedClass*/, const ReturnAdjustment &RA) {
+
   if (RA.isEmpty())
     return Ret.emitRawPointer(CGF);
 

diff  --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp
index a6d9f42ace9cc..f42e28ba7e629 100644
--- a/clang/lib/Frontend/CompilerInvocation.cpp
+++ b/clang/lib/Frontend/CompilerInvocation.cpp
@@ -1468,6 +1468,17 @@ void CompilerInvocation::setDefaultPointerAuthOptions(
     // If you change anything here, be sure to update <ptrauth.h>.
     Opts.FunctionPointers =
         PointerAuthSchema(Key::ASIA, false, Discrimination::None);
+
+    Opts.CXXVTablePointers = PointerAuthSchema(
+        Key::ASDA, LangOpts.PointerAuthVTPtrAddressDiscrimination,
+        LangOpts.PointerAuthVTPtrTypeDiscrimination ? Discrimination::Type
+                                                    : Discrimination::None);
+    Opts.CXXTypeInfoVTablePointer =
+        PointerAuthSchema(Key::ASDA, false, Discrimination::None);
+    Opts.CXXVTTVTablePointers =
+        PointerAuthSchema(Key::ASDA, false, Discrimination::None);
+    Opts.CXXVirtualFunctionPointers = Opts.CXXVirtualVariadicFunctionPointers =
+        PointerAuthSchema(Key::ASIA, true, Discrimination::Decl);
   }
 }
 

diff  --git a/clang/lib/Headers/ptrauth.h b/clang/lib/Headers/ptrauth.h
index 1a4bd02933ea2..40ac6dcac2ab8 100644
--- a/clang/lib/Headers/ptrauth.h
+++ b/clang/lib/Headers/ptrauth.h
@@ -32,6 +32,10 @@ typedef enum {
      The extra data is always 0. */
   ptrauth_key_function_pointer = ptrauth_key_process_independent_code,
 
+  /* The key used to sign C++ v-table pointers.
+     The extra data is always 0. */
+  ptrauth_key_cxx_vtable_pointer = ptrauth_key_process_independent_data,
+
   /* Other pointers signed under the ABI use private ABI rules. */
 
 } ptrauth_key;
@@ -205,6 +209,12 @@ typedef __UINTPTR_TYPE__ ptrauth_generic_signature_t;
 #define ptrauth_sign_generic_data(__value, __data)                             \
   __builtin_ptrauth_sign_generic_data(__value, __data)
 
+/* C++ vtable pointer signing class attribute */
+#define ptrauth_cxx_vtable_pointer(key, address_discrimination,                \
+                                   extra_discrimination...)                    \
+  [[clang::ptrauth_vtable_pointer(key, address_discrimination,                 \
+                                  extra_discrimination)]]
+
 #else
 
 #define ptrauth_strip(__value, __key)                                          \
@@ -271,6 +281,10 @@ typedef __UINTPTR_TYPE__ ptrauth_generic_signature_t;
     ((ptrauth_generic_signature_t)0);                                          \
   })
 
+
+#define ptrauth_cxx_vtable_pointer(key, address_discrimination,                \
+                                   extra_discrimination...)
+
 #endif /* __has_feature(ptrauth_intrinsics) */
 
 #endif /* __PTRAUTH_H */

diff  --git a/clang/lib/InstallAPI/Visitor.cpp b/clang/lib/InstallAPI/Visitor.cpp
index 367ae53b208b6..a73ea0b0d124c 100644
--- a/clang/lib/InstallAPI/Visitor.cpp
+++ b/clang/lib/InstallAPI/Visitor.cpp
@@ -447,16 +447,16 @@ InstallAPIVisitor::getMangledCXXVTableName(const CXXRecordDecl *D) const {
   return getBackendMangledName(Name);
 }
 
-std::string
-InstallAPIVisitor::getMangledCXXThunk(const GlobalDecl &D,
-                                      const ThunkInfo &Thunk) const {
+std::string InstallAPIVisitor::getMangledCXXThunk(
+    const GlobalDecl &D, const ThunkInfo &Thunk, bool ElideOverrideInfo) const {
   SmallString<256> Name;
   raw_svector_ostream NameStream(Name);
   const auto *Method = cast<CXXMethodDecl>(D.getDecl());
   if (const auto *Dtor = dyn_cast<CXXDestructorDecl>(Method))
-    MC->mangleCXXDtorThunk(Dtor, D.getDtorType(), Thunk.This, NameStream);
+    MC->mangleCXXDtorThunk(Dtor, D.getDtorType(), Thunk, ElideOverrideInfo,
+                           NameStream);
   else
-    MC->mangleThunk(Method, Thunk, NameStream);
+    MC->mangleThunk(Method, Thunk, ElideOverrideInfo, NameStream);
 
   return getBackendMangledName(Name);
 }
@@ -500,7 +500,8 @@ void InstallAPIVisitor::emitVTableSymbols(const CXXRecordDecl *D,
             return;
 
           for (const auto &Thunk : *Thunks) {
-            const std::string Name = getMangledCXXThunk(GD, Thunk);
+            const std::string Name =
+                getMangledCXXThunk(GD, Thunk, /*ElideOverrideInfo=*/true);
             auto [GR, FA] = Ctx.Slice->addGlobal(Name, RecordLinkage::Exported,
                                                  GlobalRecord::Kind::Function,
                                                  Avail, GD.getDecl(), Access);

diff  --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index c528917437332..a07f7ad2233fe 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -368,6 +368,27 @@ static bool attributeIsTypeArgAttr(const IdentifierInfo &II) {
 #undef CLANG_ATTR_TYPE_ARG_LIST
 }
 
+/// Determine whether the given attribute takes identifier arguments.
+static bool attributeHasStrictIdentifierArgs(const IdentifierInfo &II) {
+#define CLANG_ATTR_STRICT_IDENTIFIER_ARG_AT_INDEX_LIST
+  return (llvm::StringSwitch<uint64_t>(normalizeAttrName(II.getName()))
+#include "clang/Parse/AttrParserStringSwitches.inc"
+              .Default(0)) != 0;
+#undef CLANG_ATTR_STRICT_IDENTIFIER_ARG_AT_INDEX_LIST
+}
+
+/// Determine whether the given attribute takes an identifier argument at a
+/// specific index
+static bool attributeHasStrictIdentifierArgAtIndex(const IdentifierInfo &II,
+                                                   size_t argIndex) {
+#define CLANG_ATTR_STRICT_IDENTIFIER_ARG_AT_INDEX_LIST
+  return (llvm::StringSwitch<uint64_t>(normalizeAttrName(II.getName()))
+#include "clang/Parse/AttrParserStringSwitches.inc"
+              .Default(0)) &
+         (1ull << argIndex);
+#undef CLANG_ATTR_STRICT_IDENTIFIER_ARG_AT_INDEX_LIST
+}
+
 /// Determine whether the given attribute requires parsing its arguments
 /// in an unevaluated context or not.
 static bool attributeParsedArgsUnevaluated(const IdentifierInfo &II) {
@@ -546,7 +567,8 @@ unsigned Parser::ParseAttributeArgsCommon(
       }
       if (T.isUsable())
         TheParsedType = T.get();
-    } else if (AttributeHasVariadicIdentifierArg) {
+    } else if (AttributeHasVariadicIdentifierArg ||
+               attributeHasStrictIdentifierArgs(*AttrName)) {
       // Parse variadic identifier arg. This can either consume identifiers or
       // expressions. Variadic identifier args do not support parameter packs
       // because those are typically used for attributes with enumeration
@@ -557,6 +579,12 @@ unsigned Parser::ParseAttributeArgsCommon(
         if (ChangeKWThisToIdent && Tok.is(tok::kw_this))
           Tok.setKind(tok::identifier);
 
+        if (Tok.is(tok::identifier) && attributeHasStrictIdentifierArgAtIndex(
+                                           *AttrName, ArgExprs.size())) {
+          ArgExprs.push_back(ParseIdentifierLoc());
+          continue;
+        }
+
         ExprResult ArgExpr;
         if (Tok.is(tok::identifier)) {
           ArgExprs.push_back(ParseIdentifierLoc());

diff  --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 99b400307d644..7e6c7d776588a 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -6281,6 +6281,116 @@ EnforceTCBLeafAttr *Sema::mergeEnforceTCBLeafAttr(
       *this, D, AL);
 }
 
+static void handleVTablePointerAuthentication(Sema &S, Decl *D,
+                                              const ParsedAttr &AL) {
+  CXXRecordDecl *Decl = cast<CXXRecordDecl>(D);
+  const uint32_t NumArgs = AL.getNumArgs();
+  if (NumArgs > 4) {
+    S.Diag(AL.getLoc(), diag::err_attribute_too_many_arguments) << AL << 4;
+    AL.setInvalid();
+  }
+
+  if (NumArgs == 0) {
+    S.Diag(AL.getLoc(), diag::err_attribute_too_few_arguments) << AL;
+    AL.setInvalid();
+    return;
+  }
+
+  if (D->getAttr<VTablePointerAuthenticationAttr>()) {
+    S.Diag(AL.getLoc(), diag::err_duplicated_vtable_pointer_auth) << Decl;
+    AL.setInvalid();
+  }
+
+  auto KeyType = VTablePointerAuthenticationAttr::VPtrAuthKeyType::DefaultKey;
+  if (AL.isArgIdent(0)) {
+    IdentifierLoc *IL = AL.getArgAsIdent(0);
+    if (!VTablePointerAuthenticationAttr::ConvertStrToVPtrAuthKeyType(
+            IL->Ident->getName(), KeyType)) {
+      S.Diag(IL->Loc, diag::err_invalid_authentication_key) << IL->Ident;
+      AL.setInvalid();
+    }
+    if (KeyType == VTablePointerAuthenticationAttr::DefaultKey &&
+        !S.getLangOpts().PointerAuthCalls) {
+      S.Diag(AL.getLoc(), diag::err_no_default_vtable_pointer_auth) << 0;
+      AL.setInvalid();
+    }
+  } else {
+    S.Diag(AL.getLoc(), diag::err_attribute_argument_type)
+        << AL << AANT_ArgumentIdentifier;
+    return;
+  }
+
+  auto AddressDiversityMode = VTablePointerAuthenticationAttr::
+      AddressDiscriminationMode::DefaultAddressDiscrimination;
+  if (AL.getNumArgs() > 1) {
+    if (AL.isArgIdent(1)) {
+      IdentifierLoc *IL = AL.getArgAsIdent(1);
+      if (!VTablePointerAuthenticationAttr::
+              ConvertStrToAddressDiscriminationMode(IL->Ident->getName(),
+                                                    AddressDiversityMode)) {
+        S.Diag(IL->Loc, diag::err_invalid_address_discrimination) << IL->Ident;
+        AL.setInvalid();
+      }
+      if (AddressDiversityMode ==
+              VTablePointerAuthenticationAttr::DefaultAddressDiscrimination &&
+          !S.getLangOpts().PointerAuthCalls) {
+        S.Diag(IL->Loc, diag::err_no_default_vtable_pointer_auth) << 1;
+        AL.setInvalid();
+      }
+    } else {
+      S.Diag(AL.getLoc(), diag::err_attribute_argument_type)
+          << AL << AANT_ArgumentIdentifier;
+    }
+  }
+
+  auto ED = VTablePointerAuthenticationAttr::ExtraDiscrimination::
+      DefaultExtraDiscrimination;
+  if (AL.getNumArgs() > 2) {
+    if (AL.isArgIdent(2)) {
+      IdentifierLoc *IL = AL.getArgAsIdent(2);
+      if (!VTablePointerAuthenticationAttr::ConvertStrToExtraDiscrimination(
+              IL->Ident->getName(), ED)) {
+        S.Diag(IL->Loc, diag::err_invalid_extra_discrimination) << IL->Ident;
+        AL.setInvalid();
+      }
+      if (ED == VTablePointerAuthenticationAttr::DefaultExtraDiscrimination &&
+          !S.getLangOpts().PointerAuthCalls) {
+        S.Diag(AL.getLoc(), diag::err_no_default_vtable_pointer_auth) << 2;
+        AL.setInvalid();
+      }
+    } else {
+      S.Diag(AL.getLoc(), diag::err_attribute_argument_type)
+          << AL << AANT_ArgumentIdentifier;
+    }
+  }
+
+  uint32_t CustomDiscriminationValue = 0;
+  if (ED == VTablePointerAuthenticationAttr::CustomDiscrimination) {
+    if (NumArgs < 4) {
+      S.Diag(AL.getLoc(), diag::err_missing_custom_discrimination) << AL << 4;
+      AL.setInvalid();
+      return;
+    }
+    if (NumArgs > 4) {
+      S.Diag(AL.getLoc(), diag::err_attribute_too_many_arguments) << AL << 4;
+      AL.setInvalid();
+    }
+
+    if (!AL.isArgExpr(3) || !S.checkUInt32Argument(AL, AL.getArgAsExpr(3),
+                                                   CustomDiscriminationValue)) {
+      S.Diag(AL.getLoc(), diag::err_invalid_custom_discrimination);
+      AL.setInvalid();
+    }
+  } else if (NumArgs > 3) {
+    S.Diag(AL.getLoc(), diag::err_attribute_too_many_arguments) << AL << 3;
+    AL.setInvalid();
+  }
+
+  Decl->addAttr(::new (S.Context) VTablePointerAuthenticationAttr(
+      S.Context, AL, KeyType, AddressDiversityMode, ED,
+      CustomDiscriminationValue));
+}
+
 //===----------------------------------------------------------------------===//
 // Top Level Sema Entry Points
 //===----------------------------------------------------------------------===//
@@ -7150,6 +7260,10 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
   case ParsedAttr::AT_TypeNullable:
     handleNullableTypeAttr(S, D, AL);
     break;
+
+  case ParsedAttr::AT_VTablePointerAuthentication:
+    handleVTablePointerAuthentication(S, D, AL);
+    break;
   }
 }
 

diff  --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 9b220103247dd..ffa6bcac44cbd 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -7116,6 +7116,10 @@ void Sema::CheckCompletedCXXClass(Scope *S, CXXRecordDecl *Record) {
     return false;
   };
 
+  if (!Record->isInvalidDecl() &&
+      Record->hasAttr<VTablePointerAuthenticationAttr>())
+    checkIncorrectVTablePointerAuthenticationAttribute(*Record);
+
   auto CompleteMemberFunction = [&](CXXMethodDecl *M) {
     // Check whether the explicitly-defaulted members are valid.
     bool Incomplete = CheckForDefaultedFunction(M);
@@ -10500,6 +10504,39 @@ void Sema::checkIllFormedTrivialABIStruct(CXXRecordDecl &RD) {
   }
 }
 
+void Sema::checkIncorrectVTablePointerAuthenticationAttribute(
+    CXXRecordDecl &RD) {
+  if (RequireCompleteType(RD.getLocation(), Context.getRecordType(&RD),
+                          diag::err_incomplete_type_vtable_pointer_auth))
+    return;
+
+  const CXXRecordDecl *PrimaryBase = &RD;
+  if (PrimaryBase->hasAnyDependentBases())
+    return;
+
+  while (1) {
+    assert(PrimaryBase);
+    const CXXRecordDecl *Base = nullptr;
+    for (auto BasePtr : PrimaryBase->bases()) {
+      if (!BasePtr.getType()->getAsCXXRecordDecl()->isDynamicClass())
+        continue;
+      Base = BasePtr.getType()->getAsCXXRecordDecl();
+      break;
+    }
+    if (!Base || Base == PrimaryBase || !Base->isPolymorphic())
+      break;
+    Diag(RD.getAttr<VTablePointerAuthenticationAttr>()->getLocation(),
+         diag::err_non_top_level_vtable_pointer_auth)
+        << &RD << Base;
+    PrimaryBase = Base;
+  }
+
+  if (!RD.isPolymorphic())
+    Diag(RD.getAttr<VTablePointerAuthenticationAttr>()->getLocation(),
+         diag::err_non_polymorphic_vtable_pointer_auth)
+        << &RD;
+}
+
 void Sema::ActOnFinishCXXMemberSpecification(
     Scope *S, SourceLocation RLoc, Decl *TagDecl, SourceLocation LBrac,
     SourceLocation RBrac, const ParsedAttributesView &AttrList) {

diff  --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 4a2f3a65eac96..db44cfe1288b6 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -14217,6 +14217,39 @@ QualType Sema::CheckAddressOfOperand(ExprResult &OrigOp, SourceLocation OpLoc) {
 
     QualType MPTy = Context.getMemberPointerType(
         op->getType(), Context.getTypeDeclType(MD->getParent()).getTypePtr());
+
+    if (getLangOpts().PointerAuthCalls && MD->isVirtual() &&
+        !isUnevaluatedContext() && !MPTy->isDependentType()) {
+      // When pointer authentication is enabled, argument and return types of
+      // vitual member functions must be complete. This is because vitrual
+      // member function pointers are implemented using virtual dispatch
+      // thunks and the thunks cannot be emitted if the argument or return
+      // types are incomplete.
+      auto ReturnOrParamTypeIsIncomplete = [&](QualType T,
+                                               SourceLocation DeclRefLoc,
+                                               SourceLocation RetArgTypeLoc) {
+        if (RequireCompleteType(DeclRefLoc, T, diag::err_incomplete_type)) {
+          Diag(DeclRefLoc,
+               diag::note_ptrauth_virtual_function_pointer_incomplete_arg_ret);
+          Diag(RetArgTypeLoc,
+               diag::note_ptrauth_virtual_function_incomplete_arg_ret_type)
+              << T;
+          return true;
+        }
+        return false;
+      };
+      QualType RetTy = MD->getReturnType();
+      bool IsIncomplete =
+          !RetTy->isVoidType() &&
+          ReturnOrParamTypeIsIncomplete(
+              RetTy, OpLoc, MD->getReturnTypeSourceRange().getBegin());
+      for (auto *PVD : MD->parameters())
+        IsIncomplete |= ReturnOrParamTypeIsIncomplete(PVD->getType(), OpLoc,
+                                                      PVD->getBeginLoc());
+      if (IsIncomplete)
+        return QualType();
+    }
+
     // Under the MS ABI, lock down the inheritance model now.
     if (Context.getTargetInfo().getCXXABI().isMicrosoft())
       (void)isCompleteType(OpLoc, MPTy);

diff  --git a/clang/test/CodeGen/ptrauth-ubsan-vptr.cpp b/clang/test/CodeGen/ptrauth-ubsan-vptr.cpp
new file mode 100644
index 0000000000000..6c36004641477
--- /dev/null
+++ b/clang/test/CodeGen/ptrauth-ubsan-vptr.cpp
@@ -0,0 +1,30 @@
+// RUN: %clang_cc1 -triple arm64e-apple-ios15 -fsanitize=vptr -O0 -emit-llvm -o - %s | FileCheck %s
+// RUN: %clang_cc1 -triple arm64e-apple-ios15 -fsanitize=vptr -O2 -disable-llvm-passes -emit-llvm -o - %s | FileCheck %s
+
+struct S {
+  S() {}
+  ~S() {}
+  virtual int v() { return 0; }
+  int a;
+};
+
+struct T : public S {
+  virtual int v();
+};
+
+// CHECK-LABEL: foo1
+int foo1(void* Buffer) {
+  T *p = reinterpret_cast<T*>(Buffer);
+  return p->v();
+}
+// CHECK-NOT: call {{.*}} @llvm.ptrauth.auth{{.*}}
+// CHECK-NOT: call {{.*}} @llvm.ptrauth.strip{{.*}}
+
+// CHECK-LABEL: foo2
+int foo2(S* s) {
+  T *p = dynamic_cast<T*>(s);
+  return p->v();
+}
+
+// CHECK-NOT: call {{.*}} @llvm.ptrauth.auth{{.*}}
+// CHECK-NOT: call {{.*}} @llvm.ptrauth.strip{{.*}}

diff  --git a/clang/test/CodeGenCXX/catch-undef-behavior.cpp b/clang/test/CodeGenCXX/catch-undef-behavior.cpp
index 1a0e98a661375..0c14c97dacca9 100644
--- a/clang/test/CodeGenCXX/catch-undef-behavior.cpp
+++ b/clang/test/CodeGenCXX/catch-undef-behavior.cpp
@@ -56,8 +56,8 @@ void member_access(S *p) {
 
   // (1b) Check that 'p' actually points to an 'S'.
 
-  // CHECK: %[[VPTR:.*]] = load i64, ptr
-  //
+  // CHECK: %[[VTABLE:.*]] = load ptr, ptr %0
+  // CHECK: %[[VPTR:.*]] = ptrtoint ptr %[[VTABLE]] to i64
   // hash_16_bytes:
   //
   // If this number changes, it indicates that either the mangled name of ::S
@@ -110,7 +110,8 @@ void member_access(S *p) {
 
   // (3b) Check that 'p' actually points to an 'S'
 
-  // CHECK: load i64, ptr
+  // CHECK: [[VTABLE2:%.*]] = load ptr, ptr
+  // CHECK: ptrtoint ptr [[VTABLE2]] to i64
   // CHECK-NEXT: mul i64 %[[#]], -4658895280553007687, !nosanitize
   // [...]
   // CHECK: getelementptr inbounds [128 x i64], ptr @__ubsan_vptr_type_cache, i32 0, i64 %

diff  --git a/clang/test/CodeGenCXX/ptrauth-apple-kext-indirect-call-2.cpp b/clang/test/CodeGenCXX/ptrauth-apple-kext-indirect-call-2.cpp
new file mode 100644
index 0000000000000..c2f20d56b0a6b
--- /dev/null
+++ b/clang/test/CodeGenCXX/ptrauth-apple-kext-indirect-call-2.cpp
@@ -0,0 +1,105 @@
+// RUN: %clang_cc1 -triple arm64-apple-ios -fptrauth-calls -fapple-kext -fno-rtti -emit-llvm -o - %s | FileCheck %s
+
+// CHECK: @_ZTV1A = unnamed_addr constant { [4 x ptr] } { [4 x ptr] [ptr null, ptr null, ptr ptrauth (ptr @_ZNK1A3abcEv, i32 0, i64 12401, ptr getelementptr inbounds ({ [4 x ptr] }, ptr @_ZTV1A, i32 0, i32 0, i32 2)), ptr null] }, align 8
+// CHECK: @_ZTV4Base = unnamed_addr constant { [4 x ptr] } { [4 x ptr] [ptr null, ptr null, ptr ptrauth (ptr @_ZNK4Base3abcEv, i32 0, i64 64320, ptr getelementptr inbounds ({ [4 x ptr] }, ptr @_ZTV4Base, i32 0, i32 0, i32 2)), ptr null] }, align 8
+// CHECK: @_ZTV8Derived2 = unnamed_addr constant { [5 x ptr] } { [5 x ptr] [ptr null, ptr null, ptr null, ptr ptrauth (ptr @_ZNK8Derived23efgEv, i32 0, i64 36603, ptr getelementptr inbounds ({ [5 x ptr] }, ptr @_ZTV8Derived2, i32 0, i32 0, i32 3)), ptr null] }, align 8
+// CHECK: @_ZTV2D2 = unnamed_addr constant { [5 x ptr] } { [5 x ptr] [ptr null, ptr null, ptr null, ptr ptrauth (ptr @_ZNK2D23abcEv, i32 0, i64 20222, ptr getelementptr inbounds ({ [5 x ptr] }, ptr @_ZTV2D2, i32 0, i32 0, i32 3)), ptr null] }, align 8
+
+struct A {
+  virtual const char* abc(void) const;
+};
+
+const char* A::abc(void) const {return "A"; };
+
+struct B : virtual A {
+  virtual void VF();
+};
+
+void B::VF() {}
+
+void FUNC(B* p) {
+// CHECK: [[T1:%.*]] = load ptr, ptr getelementptr inbounds (ptr, ptr @_ZTV1A, i64 2)
+// CHECK-NEXT:  [[BT1:%.*]] = call i64 @llvm.ptrauth.blend(i64 ptrtoint (ptr getelementptr inbounds (ptr, ptr @_ZTV1A, i64 2) to i64), i64 12401)
+// CHECK-NEXT:  [[T2:%.*]] = call noundef ptr [[T1]](ptr noundef {{.*}}) [ "ptrauth"(i32 0, i64 [[BT1]]) ]
+  const char* c = p->A::abc();
+}
+
+
+// Test2
+struct Base { virtual char* abc(void) const; };
+
+char* Base::abc() const { return 0; }
+
+struct Derived : public Base {
+};
+
+void FUNC1(Derived* p) {
+// CHECK: [[U1:%.*]] = load ptr, ptr getelementptr inbounds (ptr, ptr @_ZTV4Base, i64 2)
+// CHECK-NEXT: [[BU1:%.*]] = call i64 @llvm.ptrauth.blend(i64 ptrtoint (ptr getelementptr inbounds (ptr, ptr @_ZTV4Base, i64 2) to i64), i64 64320)
+// CHECK-NEXT:  [[U2:%.*]] = call noundef ptr [[U1]](ptr noundef {{.*}}) [ "ptrauth"(i32 0, i64 [[BU1]]) ]
+  char* c = p->Base::abc();
+}
+
+
+// Test3
+struct Base2 { };
+
+struct Derived2 : virtual Base2 {
+  virtual char* efg(void) const;
+};
+
+char* Derived2::efg(void) const { return 0; }
+
+void FUNC2(Derived2* p) {
+// CHECK: [[V1:%.*]] = load ptr, ptr getelementptr inbounds (ptr, ptr @_ZTV8Derived2, i64 3)
+// CHECK-NEXT:  [[BV1:%.*]] = call i64 @llvm.ptrauth.blend(i64 ptrtoint (ptr getelementptr inbounds (ptr, ptr @_ZTV8Derived2, i64 3) to i64), i64 36603)
+// CHECK-NEXT:  [[V2:%.*]] = call noundef ptr [[V1]](ptr noundef {{.*}}) [ "ptrauth"(i32 0, i64 [[BV1]]) ]
+  char* c = p->Derived2::efg();
+}
+
+// Test4
+struct Base3 { };
+
+struct D1 : virtual Base3 {
+};
+
+struct D2 : virtual Base3 {
+ virtual char *abc(void) const;
+};
+
+struct Sub : D1, D2 {
+};
+
+char* D2::abc(void) const { return 0; }
+
+void FUNC3(Sub* p) {
+// CHECK: [[W1:%.*]] = load ptr, ptr getelementptr inbounds (ptr, ptr @_ZTV2D2, i64 3)
+// CHECK-NEXT:  [[BW1:%.*]] = call i64 @llvm.ptrauth.blend(i64 ptrtoint (ptr getelementptr inbounds (ptr, ptr @_ZTV2D2, i64 3) to i64), i64 20222)
+// CHECK-NEXT:  [[W2:%.*]] = call noundef ptr [[W1]](ptr noundef {{.*}}) [ "ptrauth"(i32 0, i64 [[BW1]]) ]
+  char* c = p->D2::abc();
+}
+
+
+// Test4
+struct Base4 { virtual void abc(); };
+
+void Base4::abc() {}
+
+struct Derived4 : public Base4 {
+  void abc() override;
+};
+
+void Derived4::abc() {}
+
+void FUNC4(Derived4* p) {
+// CHECK: %[[VTABLE:[a-z]+]] = load ptr, ptr %{{.*}}
+// CHECK: %[[T0:[0-9]+]] = ptrtoint ptr %[[VTABLE]] to i64
+// CHECK: %[[T3:[0-9]+]] = call i64 @llvm.ptrauth.auth(i64 %[[T0]], i32 2, i64 0)
+// CHECK: %[[T4:[0-9]+]] = inttoptr i64 %[[T3]] to ptr
+// CHECK: %[[VFN:[a-z]+]] = getelementptr inbounds ptr, ptr %[[T4]], i64 0
+// CHECK: %[[T5:[0-9]+]] = load ptr, ptr %[[VFN]]
+// CHECK: %[[T6:[0-9]+]] = ptrtoint ptr %[[VFN]] to i64
+// CHECK: %[[T7:[0-9]+]] = call i64 @llvm.ptrauth.blend(i64 %[[T6]], i64 426)
+// CHECK: call void %[[T5]](ptr noundef nonnull align {{[0-9]+}} dereferenceable(8) %{{.*}}) [ "ptrauth"(i32 0, i64 %[[T7]]) ]
+  p->abc();
+}

diff  --git a/clang/test/CodeGenCXX/ptrauth-apple-kext-indirect-call.cpp b/clang/test/CodeGenCXX/ptrauth-apple-kext-indirect-call.cpp
new file mode 100644
index 0000000000000..996829a14d7d1
--- /dev/null
+++ b/clang/test/CodeGenCXX/ptrauth-apple-kext-indirect-call.cpp
@@ -0,0 +1,42 @@
+// RUN: %clang_cc1 -triple arm64-apple-ios -fptrauth-calls -fapple-kext -emit-llvm -o - %s | FileCheck %s
+
+// CHECK: @_ZTV5TemplIiE = internal unnamed_addr constant { [5 x ptr] } { [5 x ptr] [ptr null, ptr @_ZTI5TemplIiE, ptr ptrauth (ptr @_ZN5TemplIiE1fEv, i32 0, i64 22189, ptr getelementptr inbounds ({ [5 x ptr] }, ptr @_ZTV5TemplIiE, i32 0, i32 0, i32 2)), ptr ptrauth (ptr @_ZN5TemplIiE1gEv, i32 0, i64 9912, ptr getelementptr inbounds ({ [5 x ptr] }, ptr @_ZTV5TemplIiE, i32 0, i32 0, i32 3)), ptr null] }, align 8
+
+struct Base {
+  virtual void abc(void) const;
+};
+
+void Base::abc(void) const {}
+
+void FUNC(Base* p) {
+  p->Base::abc();
+}
+
+// CHECK: getelementptr inbounds (ptr, ptr @_ZTV4Base, i64 2)
+// CHECK-NOT: call void @_ZNK4Base3abcEv
+
+template<class T>
+struct Templ {
+  virtual void f() {}
+  virtual void g() {}
+};
+template<class T>
+struct SubTempl : public Templ<T> {
+  virtual void f() {} // override
+  virtual void g() {} // override
+};
+
+void f(SubTempl<int>* t) {
+  // Qualified calls go through the (qualified) vtable in apple-kext mode.
+  // Since t's this pointer points to SubTempl's vtable, the call needs
+  // to load Templ<int>'s vtable.  Hence, Templ<int>::g needs to be
+  // instantiated in this TU, for it's referenced by the vtable.
+  // (This happens only in apple-kext mode; elsewhere virtual calls can always
+  // use the vtable pointer off this instead of having to load the vtable
+  // symbol.)
+  t->Templ::f();
+}
+
+// CHECK: getelementptr inbounds (ptr, ptr @_ZTV5TemplIiE, i64 2)
+// CHECK: define internal void @_ZN5TemplIiE1fEv(ptr noundef nonnull align {{[0-9]+}} dereferenceable(8) %this)
+// CHECK: define internal void @_ZN5TemplIiE1gEv(ptr noundef nonnull align {{[0-9]+}} dereferenceable(8) %this)

diff  --git a/clang/test/CodeGenCXX/ptrauth-apple-kext-indirect-virtual-dtor-call.cpp b/clang/test/CodeGenCXX/ptrauth-apple-kext-indirect-virtual-dtor-call.cpp
new file mode 100644
index 0000000000000..7bcf1fbfdb9de
--- /dev/null
+++ b/clang/test/CodeGenCXX/ptrauth-apple-kext-indirect-virtual-dtor-call.cpp
@@ -0,0 +1,50 @@
+// RUN: %clang_cc1 -triple arm64-apple-ios -std=c++98 -fptrauth-calls -fapple-kext -fno-rtti -disable-O0-optnone -emit-llvm -o - %s | FileCheck %s
+
+// CHECK: @_ZTV5TemplIiE = internal unnamed_addr constant { [7 x ptr] } { [7 x ptr] [ptr null, ptr null, ptr ptrauth (ptr @_ZN5TemplIiED1Ev, i32 0, i64 57986, ptr getelementptr inbounds ({ [7 x ptr] }, ptr @_ZTV5TemplIiE, i32 0, i32 0, i32 2)), ptr ptrauth (ptr @_ZN5TemplIiED0Ev, i32 0, i64 22856, ptr getelementptr inbounds ({ [7 x ptr] }, ptr @_ZTV5TemplIiE, i32 0, i32 0, i32 3)), ptr ptrauth (ptr @_ZN5TemplIiE1fEv, i32 0, i64 22189, ptr getelementptr inbounds ({ [7 x ptr] }, ptr @_ZTV5TemplIiE, i32 0, i32 0, i32 4)), ptr ptrauth (ptr @_ZN5TemplIiE1gEv, i32 0, i64 9912, ptr getelementptr inbounds ({ [7 x ptr] }, ptr @_ZTV5TemplIiE, i32 0, i32 0, i32 5)), ptr null] }, align 8
+
+struct B1 {
+  virtual ~B1();
+};
+
+B1::~B1() {}
+
+void DELETE(B1 *pb1) {
+  pb1->B1::~B1();
+}
+// CHECK-LABEL: define void @_ZN2B1D0Ev
+// CHECK: [[T1:%.*]] = load ptr, ptr getelementptr inbounds (ptr, ptr @_ZTV2B1, i64 2)
+// CHECK-NEXT: [[B1:%.*]] = call i64 @llvm.ptrauth.blend(i64 ptrtoint (ptr getelementptr inbounds (ptr, ptr @_ZTV2B1, i64 2) to i64), i64 14635)
+// CHECK-NEXT: call noundef ptr [[T1]](ptr noundef nonnull align 8 dereferenceable(8) [[T2:%.*]]) [ "ptrauth"(i32 0, i64 [[B1]]) ]
+// CHECK-LABEL: define void @_Z6DELETEP2B1
+// CHECK: [[T3:%.*]] = load ptr, ptr getelementptr inbounds (ptr, ptr @_ZTV2B1, i64 2)
+// CHECK-NEXT: [[B3:%.*]] = call i64 @llvm.ptrauth.blend(i64 ptrtoint (ptr getelementptr inbounds (ptr, ptr @_ZTV2B1, i64 2) to i64), i64 14635)
+// CHECK-NEXT:  call noundef ptr [[T3]](ptr noundef nonnull align 8 dereferenceable(8) [[T4:%.*]]) [ "ptrauth"(i32 0, i64 [[B3]])
+
+template<class T>
+struct Templ {
+  virtual ~Templ(); // Out-of-line so that the destructor doesn't cause a vtable
+  virtual void f() {}
+  virtual void g() {}
+};
+template<class T>
+struct SubTempl : public Templ<T> {
+  virtual ~SubTempl() {} // override
+  virtual void f() {} // override
+  virtual void g() {} // override
+};
+
+void f(SubTempl<int>* t) {
+  // Qualified calls go through the (qualified) vtable in apple-kext mode.
+  // Since t's this pointer points to SubTempl's vtable, the call needs
+  // to load Templ<int>'s vtable.  Hence, Templ<int>::g needs to be
+  // instantiated in this TU, for it's referenced by the vtable.
+  // (This happens only in apple-kext mode; elsewhere virtual calls can always
+  // use the vtable pointer off this instead of having to load the vtable
+  // symbol.)
+  t->Templ::~Templ();
+}
+
+// CHECK: getelementptr inbounds (ptr, ptr @_ZTV5TemplIiE, i64 2)
+// CHECK: declare void @_ZN5TemplIiED0Ev(ptr noundef nonnull align 8 dereferenceable(8))
+// CHECK: define internal void @_ZN5TemplIiE1fEv(ptr noundef nonnull align 8 dereferenceable(8) %this)
+// CHECK: define internal void @_ZN5TemplIiE1gEv(ptr noundef nonnull align 8 dereferenceable(8) %this)

diff  --git a/clang/test/CodeGenCXX/ptrauth-explicit-vtable-pointer-control.cpp b/clang/test/CodeGenCXX/ptrauth-explicit-vtable-pointer-control.cpp
new file mode 100644
index 0000000000000..e6497b3f152aa
--- /dev/null
+++ b/clang/test/CodeGenCXX/ptrauth-explicit-vtable-pointer-control.cpp
@@ -0,0 +1,564 @@
+// RUN: %clang_cc1 %s -x c++ -std=c++11 -triple arm64-apple-ios -fptrauth-calls -fptrauth-intrinsics \
+// RUN:   -emit-llvm -o - | FileCheck --check-prefixes=CHECK,NODISC %s
+
+// RUN: %clang_cc1 %s -x c++ -std=c++11 -triple arm64-apple-ios -fptrauth-calls -fptrauth-intrinsics \
+// RUN:   -fptrauth-vtable-pointer-type-discrimination \
+// RUN:   -emit-llvm -o - | FileCheck --check-prefixes=CHECK,TYPE %s
+
+// RUN: %clang_cc1 %s -x c++ -std=c++11 -triple arm64-apple-ios -fptrauth-calls -fptrauth-intrinsics \
+// RUN:   -fptrauth-vtable-pointer-address-discrimination \
+// RUN:   -emit-llvm -o - | FileCheck --check-prefixes=CHECK,ADDR %s
+
+// RUN: %clang_cc1 %s -x c++ -std=c++11 -triple arm64-apple-ios -fptrauth-calls -fptrauth-intrinsics \
+// RUN:   -fptrauth-vtable-pointer-type-discrimination \
+// RUN:   -fptrauth-vtable-pointer-address-discrimination \
+// RUN:   -emit-llvm -o - | FileCheck --check-prefixes=CHECK,BOTH %s
+
+#include <ptrauth.h>
+
+namespace test1 {
+
+#define authenticated(a...) ptrauth_cxx_vtable_pointer(a)
+
+struct NoExplicitAuth {
+  virtual ~NoExplicitAuth();
+  virtual void f();
+  virtual void g();
+};
+
+struct authenticated(no_authentication, no_address_discrimination, no_extra_discrimination) ExplicitlyDisableAuth {
+  virtual ~ExplicitlyDisableAuth();
+  virtual void f();
+  virtual void g();
+};
+
+struct authenticated(default_key, address_discrimination, default_extra_discrimination) ExplicitAddressDiscrimination {
+  virtual ~ExplicitAddressDiscrimination();
+  virtual void f();
+  virtual void g();
+};
+
+struct authenticated(default_key, no_address_discrimination, default_extra_discrimination) ExplicitNoAddressDiscrimination {
+  virtual ~ExplicitNoAddressDiscrimination();
+  virtual void f();
+  virtual void g();
+};
+
+struct authenticated(default_key, default_address_discrimination, no_extra_discrimination) ExplicitNoExtraDiscrimination {
+  virtual ~ExplicitNoExtraDiscrimination();
+  virtual void f();
+  virtual void g();
+};
+
+struct authenticated(default_key, default_address_discrimination, type_discrimination) ExplicitTypeDiscrimination {
+  virtual ~ExplicitTypeDiscrimination();
+  virtual void f();
+  virtual void g();
+};
+
+struct authenticated(default_key, default_address_discrimination, custom_discrimination, 42424) ExplicitCustomDiscrimination {
+  virtual ~ExplicitCustomDiscrimination();
+  virtual void f();
+  virtual void g();
+};
+
+template <typename T>
+struct SubClass : T {
+  virtual void g();
+  virtual T *h();
+};
+
+template <typename T>
+SubClass<T> *make_subclass(T *);
+
+struct authenticated(default_key, address_discrimination, type_discrimination) BasicStruct {
+  virtual ~BasicStruct();
+};
+
+template <typename T>
+struct PrimaryBasicStruct : BasicStruct, T {};
+template <typename T>
+struct PrimaryBasicStruct<T> *make_multiple_primary(T *);
+
+template <typename T>
+struct VirtualSubClass : virtual T {
+  virtual void g();
+  virtual T *h();
+};
+template <typename T>
+struct VirtualPrimaryStruct : virtual T, VirtualSubClass<T> {};
+template <typename T>
+struct VirtualPrimaryStruct<T> *make_virtual_primary(T *);
+
+extern "C" {
+
+// CHECK: @TVDisc_NoExplicitAuth = global i32 [[DISC_DEFAULT:49565]], align 4
+int TVDisc_NoExplicitAuth = ptrauth_string_discriminator("_ZTVN5test114NoExplicitAuthE");
+
+// CHECK: @TVDisc_ExplicitlyDisableAuth = global i32 [[DISC_DISABLED:24369]], align 4
+int TVDisc_ExplicitlyDisableAuth = ptrauth_string_discriminator("_ZTVN5test121ExplicitlyDisableAuthE");
+
+// CHECK: @TVDisc_ExplicitAddressDiscrimination = global i32 [[DISC_ADDR:56943]], align 4
+int TVDisc_ExplicitAddressDiscrimination = ptrauth_string_discriminator("_ZTVN5test129ExplicitAddressDiscriminationE");
+
+// CHECK: @TVDisc_ExplicitNoAddressDiscrimination = global i32 [[DISC_NO_ADDR:6022]], align 4
+int TVDisc_ExplicitNoAddressDiscrimination = ptrauth_string_discriminator("_ZTVN5test131ExplicitNoAddressDiscriminationE");
+
+// CHECK: @TVDisc_ExplicitNoExtraDiscrimination = global i32 [[DISC_NO_EXTRA:9072]], align 4
+int TVDisc_ExplicitNoExtraDiscrimination = ptrauth_string_discriminator("_ZTVN5test129ExplicitNoExtraDiscriminationE");
+
+// CHECK: @TVDisc_ExplicitTypeDiscrimination = global i32 [[DISC_TYPE:6177]], align 4
+int TVDisc_ExplicitTypeDiscrimination = ptrauth_string_discriminator("_ZTVN5test126ExplicitTypeDiscriminationE");
+
+
+// CHECK-LABEL: define void @test_default(ptr noundef {{%.*}}) {{#.*}} {
+// CHECK:         [[VTADDR:%.*]] = load ptr, ptr {{%.*}}, align 8
+// CHECK:         [[VTABLE:%.*]] = load ptr, ptr [[VTADDR]], align 8
+//
+// NODISC:        [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// NODISC:        [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 0)
+//
+// TYPE:          [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// TYPE:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 [[DISC_DEFAULT]])
+//
+// ADDR:          [[VTADDRI64:%.*]] = ptrtoint ptr [[VTADDR]] to i64
+// ADDR:          [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// ADDR:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 [[VTADDRI64]])
+//
+// BOTH:          [[VTADDRI64:%.*]] = ptrtoint ptr [[VTADDR]] to i64
+// BOTH:          [[BLEND:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[VTADDRI64]], i64 [[DISC_DEFAULT]])
+// BOTH:          [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// BOTH:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 [[BLEND]])
+void test_default(NoExplicitAuth *a) {
+  a->f();
+}
+
+// CHECK-LABEL: define void @test_disabled(ptr noundef {{%.*}}) {{#.*}} {
+// CHECK:         [[VTADDR:%.*]] = load ptr, ptr {{%.*}}, align 8
+// CHECK:         [[VTABLE:%.*]] = load ptr, ptr [[VTADDR]], align 8
+// CHECK-NOT:     call i64 @llvm.ptrauth.auth
+void test_disabled(ExplicitlyDisableAuth *a) {
+  a->f();
+}
+
+// CHECK-LABEL: define void @test_addr_disc(ptr noundef {{%.*}}) {{#.*}} {
+// CHECK:         [[VTADDR:%.*]] = load ptr, ptr {{%.*}}, align 8
+// CHECK:         [[VTABLE:%.*]] = load ptr, ptr [[VTADDR]], align 8
+//
+// NODISC:        [[VTADDRI64:%.*]] = ptrtoint ptr [[VTADDR]] to i64
+// NODISC:        [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// NODISC:        [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 [[VTADDRI64]])
+//
+// TYPE:          [[VTADDRI64:%.*]] = ptrtoint ptr [[VTADDR]] to i64
+// TYPE:          [[BLEND:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[VTADDRI64]], i64 [[DISC_ADDR]])
+// TYPE:          [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// TYPE:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 [[BLEND]])
+//
+// ADDR:          [[VTADDRI64:%.*]] = ptrtoint ptr [[VTADDR]] to i64
+// ADDR:          [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// ADDR:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 [[VTADDRI64]])
+//
+// BOTH:          [[VTADDRI64:%.*]] = ptrtoint ptr [[VTADDR]] to i64
+// BOTH:          [[BLEND:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[VTADDRI64]], i64 [[DISC_ADDR]])
+// BOTH:          [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// BOTH:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 [[BLEND]])
+void test_addr_disc(ExplicitAddressDiscrimination *a) {
+  a->f();
+}
+
+// CHECK-LABEL: define void @test_no_addr_disc(ptr noundef {{%.*}}) {{#.*}} {
+// CHECK:         [[VTADDR:%.*]] = load ptr, ptr {{%.*}}, align 8
+// CHECK:         [[VTABLE:%.*]] = load ptr, ptr [[VTADDR]], align 8
+//
+// NODISC:        [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// NODISC:        [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 0)
+//
+// TYPE:          [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// TYPE:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 [[DISC_NO_ADDR]])
+//
+// ADDR:          [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// ADDR:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 0)
+//
+// BOTH:          [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// BOTH:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 [[DISC_NO_ADDR]])
+void test_no_addr_disc(ExplicitNoAddressDiscrimination *a) {
+  a->f();
+}
+
+// CHECK-LABEL: define void @test_no_extra_disc(ptr noundef {{%.*}}) {{#.*}} {
+// CHECK:         [[VTADDR:%.*]] = load ptr, ptr {{%.*}}, align 8
+// CHECK:         [[VTABLE:%.*]] = load ptr, ptr [[VTADDR]], align 8
+//
+// NODISC:        [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// NODISC:        [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 0)
+//
+// TYPE:          [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// TYPE:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 0)
+//
+// ADDR:          [[VTADDRI64:%.*]] = ptrtoint ptr [[VTADDR]] to i64
+// ADDR:          [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// ADDR:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 [[VTADDRI64]])
+//
+// BOTH:          [[VTADDRI64:%.*]] = ptrtoint ptr [[VTADDR]] to i64
+// BOTH:          [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// BOTH:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 [[VTADDRI64]])
+void test_no_extra_disc(ExplicitNoExtraDiscrimination *a) {
+  a->f();
+}
+
+// CHECK-LABEL: define void @test_type_disc(ptr noundef {{%.*}}) {{#.*}} {
+// CHECK:         [[VTADDR:%.*]] = load ptr, ptr {{%.*}}, align 8
+// CHECK:         [[VTABLE:%.*]] = load ptr, ptr [[VTADDR]], align 8
+//
+// NODISC:        [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// NODISC:        [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 [[DISC_TYPE]])
+//
+// TYPE:          [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// TYPE:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 [[DISC_TYPE]])
+//
+// ADDR:          [[VTADDRI64:%.*]] = ptrtoint ptr [[VTADDR]] to i64
+// ADDR:          [[BLEND:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[VTADDRI64]], i64 [[DISC_TYPE]])
+// ADDR:          [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// ADDR:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 [[BLEND]])
+//
+// BOTH:          [[VTADDRI64:%.*]] = ptrtoint ptr [[VTADDR]] to i64
+// BOTH:          [[BLEND:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[VTADDRI64]], i64 [[DISC_TYPE]])
+// BOTH:          [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// BOTH:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 [[BLEND]])
+void test_type_disc(ExplicitTypeDiscrimination *a) {
+  a->f();
+}
+
+// CHECK-LABEL: define void @test_custom_disc(ptr noundef {{%.*}}) {{#.*}} {
+// CHECK:         [[VTADDR:%.*]] = load ptr, ptr {{%.*}}, align 8
+// CHECK:         [[VTABLE:%.*]] = load ptr, ptr [[VTADDR]], align 8
+//
+// NODISC:        [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// NODISC:        [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 42424)
+//
+// TYPE:          [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// TYPE:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 42424)
+//
+// ADDR:          [[VTADDRI64:%.*]] = ptrtoint ptr [[VTADDR]] to i64
+// ADDR:          [[BLEND:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[VTADDRI64]], i64 42424)
+// ADDR:          [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// ADDR:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 [[BLEND]])
+//
+// BOTH:          [[VTADDRI64:%.*]] = ptrtoint ptr [[VTADDR]] to i64
+// BOTH:          [[BLEND:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[VTADDRI64]], i64 42424)
+// BOTH:          [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// BOTH:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 [[BLEND]])
+void test_custom_disc(ExplicitCustomDiscrimination *a) {
+  a->f();
+}
+
+//
+// Test some simple single inheritance cases.
+// Codegen should be the same as the simple cases above once we have a vtable.
+//
+
+// CHECK-LABEL: define void @test_subclass_default(ptr noundef {{%.*}}) {{#.*}} {
+// CHECK:         [[VTADDR:%.*]] = call noundef ptr @_ZN5test113make_subclass
+// CHECK:         [[VTABLE:%.*]] = load ptr, ptr [[VTADDR]], align 8
+//
+// NODISC:        [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// NODISC:        [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 0)
+//
+// TYPE:          [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// TYPE:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 [[DISC_DEFAULT]])
+//
+// ADDR:          [[VTADDRI64:%.*]] = ptrtoint ptr [[VTADDR]] to i64
+// ADDR:          [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// ADDR:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 [[VTADDRI64]])
+//
+// BOTH:          [[VTADDRI64:%.*]] = ptrtoint ptr [[VTADDR]] to i64
+// BOTH:          [[BLEND:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[VTADDRI64]], i64 [[DISC_DEFAULT]])
+// BOTH:          [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// BOTH:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 [[BLEND]])
+void test_subclass_default(NoExplicitAuth *a) {
+  make_subclass(a)->f();
+}
+
+// CHECK-LABEL: define void @test_subclass_disabled(ptr noundef {{%.*}}) {{#.*}} {
+// CHECK:         [[VTADDR:%.*]] = call noundef ptr @_ZN5test113make_subclass
+// CHECK:         [[VTABLE:%.*]] = load ptr, ptr [[VTADDR]], align 8
+// CHECK-NOT:     call i64 @llvm.ptrauth.auth
+void test_subclass_disabled(ExplicitlyDisableAuth *a) {
+  make_subclass(a)->f();
+}
+
+// CHECK-LABEL: define void @test_subclass_addr_disc(ptr noundef {{%.*}}) {{#.*}} {
+// CHECK:         [[VTADDR:%.*]] = call noundef ptr @_ZN5test113make_subclass
+// CHECK:         [[VTABLE:%.*]] = load ptr, ptr [[VTADDR]], align 8
+//
+// NODISC:        [[VTADDRI64:%.*]] = ptrtoint ptr [[VTADDR]] to i64
+// NODISC:        [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// NODISC:        [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 [[VTADDRI64]])
+//
+// TYPE:          [[VTADDRI64:%.*]] = ptrtoint ptr [[VTADDR]] to i64
+// TYPE:          [[BLEND:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[VTADDRI64]], i64 [[DISC_ADDR]])
+// TYPE:          [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// TYPE:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 [[BLEND]])
+//
+// ADDR:          [[VTADDRI64:%.*]] = ptrtoint ptr [[VTADDR]] to i64
+// ADDR:          [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// ADDR:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 [[VTADDRI64]])
+//
+// BOTH:          [[VTADDRI64:%.*]] = ptrtoint ptr [[VTADDR]] to i64
+// BOTH:          [[BLEND:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[VTADDRI64]], i64 [[DISC_ADDR]])
+// BOTH:          [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// BOTH:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 [[BLEND]])
+void test_subclass_addr_disc(ExplicitAddressDiscrimination *a) {
+  make_subclass(a)->f();
+}
+
+// CHECK-LABEL: define void @test_subclass_no_addr_disc(ptr noundef {{%.*}}) {{#.*}} {
+// CHECK:         [[VTADDR:%.*]] = call noundef ptr @_ZN5test113make_subclass
+// CHECK:         [[VTABLE:%.*]] = load ptr, ptr [[VTADDR]], align 8
+//
+// NODISC:        [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// NODISC:        [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 0)
+//
+// TYPE:          [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// TYPE:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 [[DISC_NO_ADDR]])
+//
+// ADDR:          [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// ADDR:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 0)
+//
+// BOTH:          [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// BOTH:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 [[DISC_NO_ADDR]])
+void test_subclass_no_addr_disc(ExplicitNoAddressDiscrimination *a) {
+  make_subclass(a)->f();
+}
+
+// CHECK-LABEL: define void @test_subclass_no_extra_disc(ptr noundef {{%.*}}) {{#.*}} {
+// CHECK:         [[VTADDR:%.*]] = call noundef ptr @_ZN5test113make_subclass
+// CHECK:         [[VTABLE:%.*]] = load ptr, ptr [[VTADDR]], align 8
+//
+// NODISC:        [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// NODISC:        [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 0)
+//
+// TYPE:          [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// TYPE:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 0)
+//
+// ADDR:          [[VTADDRI64:%.*]] = ptrtoint ptr [[VTADDR]] to i64
+// ADDR:          [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// ADDR:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 [[VTADDRI64]])
+//
+// BOTH:          [[VTADDRI64:%.*]] = ptrtoint ptr [[VTADDR]] to i64
+// BOTH:          [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// BOTH:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 [[VTADDRI64]])
+void test_subclass_no_extra_disc(ExplicitNoExtraDiscrimination *a) {
+  make_subclass(a)->f();
+}
+
+// CHECK-LABEL: define void @test_subclass_type_disc(ptr noundef {{%.*}}) {{#.*}} {
+// CHECK:         [[VTADDR:%.*]] = call noundef ptr @_ZN5test113make_subclass
+// CHECK:         [[VTABLE:%.*]] = load ptr, ptr [[VTADDR]], align 8
+//
+// NODISC:        [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// NODISC:        [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 [[DISC_TYPE]])
+//
+// TYPE:          [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// TYPE:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 [[DISC_TYPE]])
+//
+// ADDR:          [[VTADDRI64:%.*]] = ptrtoint ptr [[VTADDR]] to i64
+// ADDR:          [[BLEND:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[VTADDRI64]], i64 [[DISC_TYPE]])
+// ADDR:          [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// ADDR:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 [[BLEND]])
+//
+// BOTH:          [[VTADDRI64:%.*]] = ptrtoint ptr [[VTADDR]] to i64
+// BOTH:          [[BLEND:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[VTADDRI64]], i64 [[DISC_TYPE]])
+// BOTH:          [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// BOTH:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 [[BLEND]])
+void test_subclass_type_disc(ExplicitTypeDiscrimination *a) {
+  make_subclass(a)->f();
+}
+
+// CHECK-LABEL: define void @test_subclass_custom_disc(ptr noundef {{%.*}}) {{#.*}} {
+// CHECK:         [[VTADDR:%.*]] = call noundef ptr @_ZN5test113make_subclass
+// CHECK:         [[VTABLE:%.*]] = load ptr, ptr [[VTADDR]], align 8
+//
+// NODISC:        [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// NODISC:        [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 42424)
+//
+// TYPE:          [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// TYPE:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 42424)
+//
+// ADDR:          [[VTADDRI64:%.*]] = ptrtoint ptr [[VTADDR]] to i64
+// ADDR:          [[BLEND:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[VTADDRI64]], i64 42424)
+// ADDR:          [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// ADDR:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 [[BLEND]])
+//
+// BOTH:          [[VTADDRI64:%.*]] = ptrtoint ptr [[VTADDR]] to i64
+// BOTH:          [[BLEND:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[VTADDRI64]], i64 42424)
+// BOTH:          [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// BOTH:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 [[BLEND]])
+void test_subclass_custom_disc(ExplicitCustomDiscrimination *a) {
+  make_subclass(a)->f();
+}
+
+
+//
+// Test some simple multiple inheritance cases.
+// Codegen should be the same as the simple cases above once we have a vtable.
+//
+
+// CHECK-LABEL: define void @test_multiple_default(ptr noundef {{%.*}}) {{#.*}} {
+// CHECK:         [[CALL:%.*]] = call noundef ptr @_ZN5test121make_multiple_primary
+// CHECK:         [[VTADDR:%.*]] = getelementptr inbounds i8, ptr [[CALL]], i64 8
+// CHECK:         [[VTABLE:%.*]] = load ptr, ptr [[VTADDR]], align 8
+//
+// NODISC:        [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// NODISC:        [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 0)
+//
+// TYPE:          [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// TYPE:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 [[DISC_DEFAULT]])
+//
+// ADDR:          [[VTADDRI64:%.*]] = ptrtoint ptr [[VTADDR]] to i64
+// ADDR:          [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// ADDR:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 [[VTADDRI64]])
+//
+// BOTH:          [[VTADDRI64:%.*]] = ptrtoint ptr [[VTADDR]] to i64
+// BOTH:          [[BLEND:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[VTADDRI64]], i64 [[DISC_DEFAULT]])
+// BOTH:          [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// BOTH:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 [[BLEND]])
+void test_multiple_default(NoExplicitAuth *a) {
+  make_multiple_primary(a)->f();
+}
+
+// CHECK-LABEL: define void @test_multiple_disabled(ptr noundef {{%.*}}) {{#.*}} {
+// CHECK:         [[CALL:%.*]] = call noundef ptr @_ZN5test121make_multiple_primary
+// CHECK:         [[VTADDR:%.*]] = getelementptr inbounds i8, ptr [[CALL]], i64 8
+// CHECK:         [[VTABLE:%.*]] = load ptr, ptr [[VTADDR]], align 8
+// CHECK-NOT:     call i64 @llvm.ptrauth.auth
+void test_multiple_disabled(ExplicitlyDisableAuth *a) {
+  make_multiple_primary(a)->f();
+}
+
+// CHECK-LABEL: define void @test_multiple_custom_disc(ptr noundef {{%.*}}) {{#.*}} {
+// CHECK:         [[CALL:%.*]] = call noundef ptr @_ZN5test121make_multiple_primary
+// CHECK:         [[VTADDR:%.*]] = getelementptr inbounds i8, ptr [[CALL]], i64 8
+// CHECK:         [[VTABLE:%.*]] = load ptr, ptr [[VTADDR]], align 8
+//
+// NODISC:        [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// NODISC:        [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 42424)
+//
+// TYPE:          [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// TYPE:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 42424)
+//
+// ADDR:          [[VTADDRI64:%.*]] = ptrtoint ptr [[VTADDR]] to i64
+// ADDR:          [[BLEND:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[VTADDRI64]], i64 42424)
+// ADDR:          [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// ADDR:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 [[BLEND]])
+//
+// BOTH:          [[VTADDRI64:%.*]] = ptrtoint ptr [[VTADDR]] to i64
+// BOTH:          [[BLEND:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[VTADDRI64]], i64 42424)
+// BOTH:          [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// BOTH:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 [[BLEND]])
+void test_multiple_custom_disc(ExplicitCustomDiscrimination *a) {
+  make_multiple_primary(a)->f();
+}
+
+//
+// Test some virtual inheritance cases.
+// Codegen should be the same as the simple cases above once we have a vtable,
+// but twice for vtt/vtable.  The names in the vtt version have "VTT" prefixes.
+//
+
+// CHECK-LABEL: define void @test_virtual_default(ptr noundef {{%.*}}) {{#.*}} {
+// CHECK:         [[VTTADDR:%.*]] = call noundef ptr @_ZN5test120make_virtual_primary
+// CHECK:         [[VTTABLE:%.*]] = load ptr, ptr [[VTTADDR]], align 8
+//
+// NODISC:        [[VTTABLEI64:%.*]] = ptrtoint ptr [[VTTABLE]] to i64
+// NODISC:        [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTTABLEI64]], i32 2, i64 0)
+//
+// TYPE:          [[VTTABLEI64:%.*]] = ptrtoint ptr [[VTTABLE]] to i64
+// TYPE:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTTABLEI64]], i32 2, i64 [[DISC_DEFAULT]])
+//
+// ADDR:          [[VTTADDRI64:%.*]] = ptrtoint ptr [[VTTADDR]] to i64
+// ADDR:          [[VTTABLEI64:%.*]] = ptrtoint ptr [[VTTABLE]] to i64
+// ADDR:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTTABLEI64]], i32 2, i64 [[VTTADDRI64]])
+//
+// BOTH:          [[VTTADDRI64:%.*]] = ptrtoint ptr [[VTTADDR]] to i64
+// BOTH:          [[BLEND:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[VTTADDRI64]], i64 [[DISC_DEFAULT]])
+// BOTH:          [[VTTABLEI64:%.*]] = ptrtoint ptr [[VTTABLE]] to i64
+// BOTH:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTTABLEI64]], i32 2, i64 [[BLEND]])
+
+// CHECK:         [[AUTHEDPTR:%.*]] = inttoptr i64 [[AUTHED]] to ptr
+// CHECK:         [[VBOFFPTR:%.*]] = getelementptr i8, ptr [[AUTHEDPTR]], i64 -48
+// CHECK:         [[VBOFFSET:%.*]] = load i64, ptr [[VBOFFPTR]]
+// CHECK:         [[VTADDR:%.*]] = getelementptr inbounds i8, ptr [[VTTADDR]], i64 [[VBOFFSET]]
+// CHECK:         [[VTABLE:%.*]] = load ptr, ptr [[VTADDR]], align 8
+//
+// NODISC:        [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// NODISC:        [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 0)
+//
+// TYPE:          [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// TYPE:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 [[DISC_DEFAULT]])
+//
+// ADDR:          [[VTADDRI64:%.*]] = ptrtoint ptr [[VTADDR]] to i64
+// ADDR:          [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// ADDR:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 [[VTADDRI64]])
+//
+// BOTH:          [[VTADDRI64:%.*]] = ptrtoint ptr [[VTADDR]] to i64
+// BOTH:          [[BLEND:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[VTADDRI64]], i64 [[DISC_DEFAULT]])
+// BOTH:          [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// BOTH:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 [[BLEND]])
+void test_virtual_default(NoExplicitAuth *a) {
+  make_virtual_primary(a)->f();
+}
+
+// CHECK-LABEL: define void @test_virtual_disabled(ptr noundef {{%.*}}) {{#.*}} {
+// CHECK-NOT:     call i64 @llvm.ptrauth.auth
+void test_virtual_disabled(ExplicitlyDisableAuth *a) {
+  make_virtual_primary(a)->f();
+}
+
+// CHECK-LABEL: define void @test_virtual_custom_disc(ptr noundef {{%.*}}) {{#.*}} {
+// CHECK:         [[VTTADDR:%.*]] = call noundef ptr @_ZN5test120make_virtual_primary
+// CHECK:         [[VTTABLE:%.*]] = load ptr, ptr [[VTTADDR]], align 8
+//
+// NODISC:        [[VTTABLEI64:%.*]] = ptrtoint ptr [[VTTABLE]] to i64
+// NODISC:        [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTTABLEI64]], i32 2, i64 42424)
+//
+// TYPE:          [[VTTABLEI64:%.*]] = ptrtoint ptr [[VTTABLE]] to i64
+// TYPE:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTTABLEI64]], i32 2, i64 42424)
+//
+// ADDR:          [[VTTADDRI64:%.*]] = ptrtoint ptr [[VTTADDR]] to i64
+// ADDR:          [[BLEND:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[VTTADDRI64]], i64 42424)
+// ADDR:          [[VTTABLEI64:%.*]] = ptrtoint ptr [[VTTABLE]] to i64
+// ADDR:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTTABLEI64]], i32 2, i64 [[BLEND]])
+//
+// BOTH:          [[VTTADDRI64:%.*]] = ptrtoint ptr [[VTTADDR]] to i64
+// BOTH:          [[BLEND:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[VTTADDRI64]], i64 42424)
+// BOTH:          [[VTTABLEI64:%.*]] = ptrtoint ptr [[VTTABLE]] to i64
+// BOTH:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTTABLEI64]], i32 2, i64 [[BLEND]])
+
+// CHECK:         [[AUTHEDPTR:%.*]] = inttoptr i64 [[AUTHED]] to ptr
+// CHECK:         [[VBOFFPTR:%.*]] = getelementptr i8, ptr [[AUTHEDPTR]], i64 -48
+// CHECK:         [[VBOFFSET:%.*]] = load i64, ptr [[VBOFFPTR]]
+// CHECK:         [[VTADDR:%.*]] = getelementptr inbounds i8, ptr [[VTTADDR]], i64 [[VBOFFSET]]
+// CHECK:         [[VTABLE:%.*]] = load ptr, ptr [[VTADDR]], align 8
+//
+// NODISC:        [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// NODISC:        [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 42424)
+//
+// TYPE:          [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// TYPE:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 42424)
+//
+// ADDR:          [[VTADDRI64:%.*]] = ptrtoint ptr [[VTADDR]] to i64
+// ADDR:          [[BLEND:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[VTADDRI64]], i64 42424)
+// ADDR:          [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// ADDR:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 [[BLEND]])
+//
+// BOTH:          [[VTADDRI64:%.*]] = ptrtoint ptr [[VTADDR]] to i64
+// BOTH:          [[BLEND:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[VTADDRI64]], i64 42424)
+// BOTH:          [[VTABLEI64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// BOTH:          [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI64]], i32 2, i64 [[BLEND]])
+void test_virtual_custom_disc(ExplicitCustomDiscrimination *a) {
+  make_virtual_primary(a)->f();
+}
+
+} // extern "C"
+} // namespace test1

diff  --git a/clang/test/CodeGenCXX/ptrauth-rtti-layout.cpp b/clang/test/CodeGenCXX/ptrauth-rtti-layout.cpp
new file mode 100644
index 0000000000000..b4a8784a33d8c
--- /dev/null
+++ b/clang/test/CodeGenCXX/ptrauth-rtti-layout.cpp
@@ -0,0 +1,10 @@
+// RUN: %clang_cc1 %s -I%S -triple=arm64-apple-ios -fptrauth-calls -std=c++11 -emit-llvm -o - | FileCheck %s
+#include <typeinfo>
+
+struct A { int a; };
+
+// CHECK: @_ZTVN10__cxxabiv117__class_type_infoE = external global [0 x ptr]
+// CHECK: @_ZTS1A = linkonce_odr hidden constant [3 x i8] c"1A\00"
+// CHECK: @_ZTI1A = linkonce_odr hidden constant { ptr, ptr } { ptr ptrauth (ptr getelementptr inbounds (ptr, ptr @_ZTVN10__cxxabiv117__class_type_infoE, i64 2), i32 2), ptr inttoptr (i64 add (i64 ptrtoint (ptr @_ZTS1A to i64), i64 -9223372036854775808) to ptr) }
+
+auto ATI = typeid(A);

diff  --git a/clang/test/CodeGenCXX/ptrauth-thunks.cpp b/clang/test/CodeGenCXX/ptrauth-thunks.cpp
new file mode 100644
index 0000000000000..a85c8c4c065c4
--- /dev/null
+++ b/clang/test/CodeGenCXX/ptrauth-thunks.cpp
@@ -0,0 +1,28 @@
+// RUN: %clang_cc1 -triple arm64-apple-ios -fptrauth-calls -emit-llvm -std=c++11 %s -o - | FileCheck %s
+
+namespace Test1 {
+  struct B1 {
+    virtual void* foo1() {
+      return 0;
+    }
+  };
+  struct Pad1 {
+    virtual ~Pad1() {}
+  };
+  struct Proxy1 : Pad1, B1 {
+    virtual ~Proxy1() {}
+  };
+  struct D : virtual Proxy1 {
+    virtual ~D() {}
+    virtual void* foo1();
+  };
+  void* D::foo1() {
+    return (void*)this;
+  }
+}
+
+// CHECK-LABEL: define linkonce_odr void @_ZTv0_n24_N5Test11DD0Ev(ptr noundef %this)
+// CHECK: %[[This:.*]] = load ptr
+// CHECK: %[[SignedVTable:.*]] = load ptr, ptr %[[This]], align 8
+// CHECK: %[[SignedVTableAsInt:.*]] = ptrtoint ptr %[[SignedVTable]] to i64
+// CHECK: %[[VTable:.*]] = call i64 @llvm.ptrauth.auth(i64 %[[SignedVTableAsInt]], i32 2, i64 0)

diff  --git a/clang/test/CodeGenCXX/ptrauth-virtual-function.cpp b/clang/test/CodeGenCXX/ptrauth-virtual-function.cpp
new file mode 100644
index 0000000000000..563d60780769b
--- /dev/null
+++ b/clang/test/CodeGenCXX/ptrauth-virtual-function.cpp
@@ -0,0 +1,581 @@
+// RUN: %clang_cc1 -triple arm64-apple-ios -fptrauth-calls -emit-llvm -std=c++11 %s -o - | FileCheck %s
+
+// Check virtual function pointers in vtables are signed.
+
+// CHECK: %[[CLASS_B1:.*]] = type { ptr }
+
+// CHECK: @_ZTV2B1 = unnamed_addr constant { [3 x ptr] } { [3 x ptr] [ptr null, ptr @_ZTI2B1, ptr ptrauth (ptr @_ZN2B12m0Ev, i32 0, i64 58196, ptr getelementptr inbounds ({ [3 x ptr] }, ptr @_ZTV2B1, i32 0, i32 0, i32 2))] }, align 8
+
+// CHECK: @g_B1 = global %class.B1 { ptr ptrauth (ptr getelementptr inbounds inrange(-16, 8) ({ [3 x ptr] }, ptr @_ZTV2B1, i32 0, i32 0, i32 2), i32 2) }, align 8
+
+// CHECK: @_ZTV2B0 = unnamed_addr constant { [7 x ptr] } { [7 x ptr] [ptr null, ptr @_ZTI2B0,
+// CHECK-SAME: ptr ptrauth (ptr @_ZN2B02m0Ev, i32 0, i64 53119, ptr getelementptr inbounds ({ [7 x ptr] }, ptr @_ZTV2B0, i32 0, i32 0, i32 2)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN2B02m1Ev, i32 0, i64 15165, ptr getelementptr inbounds ({ [7 x ptr] }, ptr @_ZTV2B0, i32 0, i32 0, i32 3)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN2B02m2Ev, i32 0, i64 43073, ptr getelementptr inbounds ({ [7 x ptr] }, ptr @_ZTV2B0, i32 0, i32 0, i32 4)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN2B0D1Ev, i32 0, i64 25525, ptr getelementptr inbounds ({ [7 x ptr] }, ptr @_ZTV2B0, i32 0, i32 0, i32 5)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN2B0D0Ev, i32 0, i64 21295, ptr getelementptr inbounds ({ [7 x ptr] }, ptr @_ZTV2B0, i32 0, i32 0, i32 6))] }, align 8
+
+// CHECK: @_ZTV2D0 = unnamed_addr constant { [9 x ptr] } { [9 x ptr] [ptr null, ptr @_ZTI2D0,
+// CHECK-SAME: ptr ptrauth (ptr @_ZN2D02m0Ev, i32 0, i64 53119, ptr getelementptr inbounds ({ [9 x ptr] }, ptr @_ZTV2D0, i32 0, i32 0, i32 2)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZTch0_h4_N2D02m1Ev, i32 0, i64 15165, ptr getelementptr inbounds ({ [9 x ptr] }, ptr @_ZTV2D0, i32 0, i32 0, i32 3)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN2B02m2Ev, i32 0, i64 43073, ptr getelementptr inbounds ({ [9 x ptr] }, ptr @_ZTV2D0, i32 0, i32 0, i32 4)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN2D0D1Ev, i32 0, i64 25525, ptr getelementptr inbounds ({ [9 x ptr] }, ptr @_ZTV2D0, i32 0, i32 0, i32 5)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN2D0D0Ev, i32 0, i64 21295, ptr getelementptr inbounds ({ [9 x ptr] }, ptr @_ZTV2D0, i32 0, i32 0, i32 6)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN2D02m1Ev, i32 0, i64 35045, ptr getelementptr inbounds ({ [9 x ptr] }, ptr @_ZTV2D0, i32 0, i32 0, i32 7)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN2D02m3Ev, i32 0, i64 10565, ptr getelementptr inbounds ({ [9 x ptr] }, ptr @_ZTV2D0, i32 0, i32 0, i32 8))] }, align 8
+
+// CHECK: @_ZTV2D1 = unnamed_addr constant { [8 x ptr] } { [8 x ptr] [ptr null, ptr @_ZTI2D1,
+// CHECK-SAME: ptr ptrauth (ptr @_ZN2D12m0Ev, i32 0, i64 53119, ptr getelementptr inbounds ({ [8 x ptr] }, ptr @_ZTV2D1, i32 0, i32 0, i32 2)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZTch0_h4_N2D12m1Ev, i32 0, i64 15165, ptr getelementptr inbounds ({ [8 x ptr] }, ptr @_ZTV2D1, i32 0, i32 0, i32 3)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN2B02m2Ev, i32 0, i64 43073, ptr getelementptr inbounds ({ [8 x ptr] }, ptr @_ZTV2D1, i32 0, i32 0, i32 4)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN2D1D1Ev, i32 0, i64 25525, ptr getelementptr inbounds ({ [8 x ptr] }, ptr @_ZTV2D1, i32 0, i32 0, i32 5)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN2D1D0Ev, i32 0, i64 21295, ptr getelementptr inbounds ({ [8 x ptr] }, ptr @_ZTV2D1, i32 0, i32 0, i32 6)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN2D12m1Ev, i32 0, i64 52864, ptr getelementptr inbounds ({ [8 x ptr] }, ptr @_ZTV2D1, i32 0, i32 0, i32 7))] }, align 8
+
+// CHECK: @_ZTV2D2 = unnamed_addr constant { [9 x ptr], [8 x ptr] } { [9 x ptr] [ptr null, ptr @_ZTI2D2,
+// CHECK-SAME: ptr ptrauth (ptr @_ZN2D22m0Ev, i32 0, i64 53119, ptr getelementptr inbounds ({ [9 x ptr], [8 x ptr] }, ptr @_ZTV2D2, i32 0, i32 0, i32 2)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZTch0_h4_N2D22m1Ev, i32 0, i64 15165, ptr getelementptr inbounds ({ [9 x ptr], [8 x ptr] }, ptr @_ZTV2D2, i32 0, i32 0, i32 3)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN2B02m2Ev, i32 0, i64 43073, ptr getelementptr inbounds ({ [9 x ptr], [8 x ptr] }, ptr @_ZTV2D2, i32 0, i32 0, i32 4)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN2D2D1Ev, i32 0, i64 25525, ptr getelementptr inbounds ({ [9 x ptr], [8 x ptr] }, ptr @_ZTV2D2, i32 0, i32 0, i32 5)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN2D2D0Ev, i32 0, i64 21295, ptr getelementptr inbounds ({ [9 x ptr], [8 x ptr] }, ptr @_ZTV2D2, i32 0, i32 0, i32 6)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN2D22m1Ev, i32 0, i64 35045, ptr getelementptr inbounds ({ [9 x ptr], [8 x ptr] }, ptr @_ZTV2D2, i32 0, i32 0, i32 7)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN2D22m3Ev, i32 0, i64 10565, ptr getelementptr inbounds ({ [9 x ptr], [8 x ptr] }, ptr @_ZTV2D2, i32 0, i32 0, i32 8))],
+// CHECK-SAME: [8 x ptr] [ptr inttoptr (i64 -16 to ptr), ptr @_ZTI2D2,
+// CHECK-SAME: ptr ptrauth (ptr @_ZThn16_N2D22m0Ev, i32 0, i64 53119, ptr getelementptr inbounds ({ [9 x ptr], [8 x ptr] }, ptr @_ZTV2D2, i32 0, i32 1, i32 2)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZTchn16_h4_N2D22m1Ev, i32 0, i64 15165, ptr getelementptr inbounds ({ [9 x ptr], [8 x ptr] }, ptr @_ZTV2D2, i32 0, i32 1, i32 3)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN2B02m2Ev, i32 0, i64 43073, ptr getelementptr inbounds ({ [9 x ptr], [8 x ptr] }, ptr @_ZTV2D2, i32 0, i32 1, i32 4)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZThn16_N2D2D1Ev, i32 0, i64 25525, ptr getelementptr inbounds ({ [9 x ptr], [8 x ptr] }, ptr @_ZTV2D2, i32 0, i32 1, i32 5)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZThn16_N2D2D0Ev, i32 0, i64 21295, ptr getelementptr inbounds ({ [9 x ptr], [8 x ptr] }, ptr @_ZTV2D2, i32 0, i32 1, i32 6)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZThn16_N2D22m1Ev, i32 0, i64 52864, ptr getelementptr inbounds ({ [9 x ptr], [8 x ptr] }, ptr @_ZTV2D2, i32 0, i32 1, i32 7))] }, align 8
+
+// CHECK: @_ZTV2D3 = unnamed_addr constant { [7 x ptr], [7 x ptr], [11 x ptr] } { [7 x ptr] [ptr inttoptr (i64 32 to ptr), ptr null, ptr @_ZTI2D3,
+// CHECK-SAME: ptr ptrauth (ptr @_ZN2D32m0Ev, i32 0, i64 44578, ptr getelementptr inbounds ({ [7 x ptr], [7 x ptr], [11 x ptr] }, ptr @_ZTV2D3, i32 0, i32 0, i32 3)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN2D32m1Ev, i32 0, i64 30766, ptr getelementptr inbounds ({ [7 x ptr], [7 x ptr], [11 x ptr] }, ptr @_ZTV2D3, i32 0, i32 0, i32 4)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN2D3D1Ev, i32 0, i64 57279, ptr getelementptr inbounds ({ [7 x ptr], [7 x ptr], [11 x ptr] }, ptr @_ZTV2D3, i32 0, i32 0, i32 5)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN2D3D0Ev, i32 0, i64 62452, ptr getelementptr inbounds ({ [7 x ptr], [7 x ptr], [11 x ptr] }, ptr @_ZTV2D3, i32 0, i32 0, i32 6))],
+// CHECK-SAME: [7 x ptr] [ptr inttoptr (i64 16 to ptr), ptr inttoptr (i64 -16 to ptr), ptr @_ZTI2D3,
+// CHECK-SAME: ptr ptrauth (ptr @_ZThn16_N2D32m0Ev, i32 0, i64 49430, ptr getelementptr inbounds ({ [7 x ptr], [7 x ptr], [11 x ptr] }, ptr @_ZTV2D3, i32 0, i32 1, i32 3)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZThn16_N2D32m1Ev, i32 0, i64 57119, ptr getelementptr inbounds ({ [7 x ptr], [7 x ptr], [11 x ptr] }, ptr @_ZTV2D3, i32 0, i32 1, i32 4)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZThn16_N2D3D1Ev, i32 0, i64 60799, ptr getelementptr inbounds ({ [7 x ptr], [7 x ptr], [11 x ptr] }, ptr @_ZTV2D3, i32 0, i32 1, i32 5)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZThn16_N2D3D0Ev, i32 0, i64 52565, ptr getelementptr inbounds ({ [7 x ptr], [7 x ptr], [11 x ptr] }, ptr @_ZTV2D3, i32 0, i32 1, i32 6))],
+// CHECK-SAME: [11 x ptr] [ptr inttoptr (i64 -32 to ptr), ptr null, ptr inttoptr (i64 -32 to ptr), ptr inttoptr (i64 -32 to ptr), ptr inttoptr (i64 -32 to ptr), ptr @_ZTI2D3,
+// CHECK-SAME: ptr ptrauth (ptr @_ZTv0_n24_N2D32m0Ev, i32 0, i64 53119, ptr getelementptr inbounds ({ [7 x ptr], [7 x ptr], [11 x ptr] }, ptr @_ZTV2D3, i32 0, i32 2, i32 6)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZTcv0_n32_h4_N2D32m1Ev, i32 0, i64 15165, ptr getelementptr inbounds ({ [7 x ptr], [7 x ptr], [11 x ptr] }, ptr @_ZTV2D3, i32 0, i32 2, i32 7)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN2B02m2Ev, i32 0, i64 43073, ptr getelementptr inbounds ({ [7 x ptr], [7 x ptr], [11 x ptr] }, ptr @_ZTV2D3, i32 0, i32 2, i32 8)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZTv0_n48_N2D3D1Ev, i32 0, i64 25525, ptr getelementptr inbounds ({ [7 x ptr], [7 x ptr], [11 x ptr] }, ptr @_ZTV2D3, i32 0, i32 2, i32 9)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZTv0_n48_N2D3D0Ev, i32 0, i64 21295, ptr getelementptr inbounds ({ [7 x ptr], [7 x ptr], [11 x ptr] }, ptr @_ZTV2D3, i32 0, i32 2, i32 10))] }, align 8
+
+// CHECK: @_ZTC2D30_2V0 = unnamed_addr constant { [7 x ptr], [11 x ptr] } { [7 x ptr] [ptr inttoptr (i64 32 to ptr), ptr null, ptr @_ZTI2V0,
+// CHECK-SAME: ptr ptrauth (ptr @_ZN2V02m0Ev, i32 0, i64 44578, ptr getelementptr inbounds ({ [7 x ptr], [11 x ptr] }, ptr @_ZTC2D30_2V0, i32 0, i32 0, i32 3)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN2V02m1Ev, i32 0, i64 30766, ptr getelementptr inbounds ({ [7 x ptr], [11 x ptr] }, ptr @_ZTC2D30_2V0, i32 0, i32 0, i32 4)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN2V0D1Ev, i32 0, i64 57279, ptr getelementptr inbounds ({ [7 x ptr], [11 x ptr] }, ptr @_ZTC2D30_2V0, i32 0, i32 0, i32 5)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN2V0D0Ev, i32 0, i64 62452, ptr getelementptr inbounds ({ [7 x ptr], [11 x ptr] }, ptr @_ZTC2D30_2V0, i32 0, i32 0, i32 6))],
+// CHECK-SAME: [11 x ptr] [ptr inttoptr (i64 -32 to ptr), ptr null, ptr inttoptr (i64 -32 to ptr), ptr inttoptr (i64 -32 to ptr), ptr inttoptr (i64 -32 to ptr), ptr @_ZTI2V0,
+// CHECK-SAME: ptr ptrauth (ptr @_ZTv0_n24_N2V02m0Ev, i32 0, i64 53119, ptr getelementptr inbounds ({ [7 x ptr], [11 x ptr] }, ptr @_ZTC2D30_2V0, i32 0, i32 1, i32 6)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZTcv0_n32_h4_N2V02m1Ev, i32 0, i64 15165, ptr getelementptr inbounds ({ [7 x ptr], [11 x ptr] }, ptr @_ZTC2D30_2V0, i32 0, i32 1, i32 7)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN2B02m2Ev, i32 0, i64 43073, ptr getelementptr inbounds ({ [7 x ptr], [11 x ptr] }, ptr @_ZTC2D30_2V0, i32 0, i32 1, i32 8)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZTv0_n48_N2V0D1Ev, i32 0, i64 25525, ptr getelementptr inbounds ({ [7 x ptr], [11 x ptr] }, ptr @_ZTC2D30_2V0, i32 0, i32 1, i32 9)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZTv0_n48_N2V0D0Ev, i32 0, i64 21295, ptr getelementptr inbounds ({ [7 x ptr], [11 x ptr] }, ptr @_ZTC2D30_2V0, i32 0, i32 1, i32 10))] }, align 8
+
+// CHECK: @_ZTC2D316_2V1 = unnamed_addr constant { [7 x ptr], [11 x ptr] } { [7 x ptr] [ptr inttoptr (i64 16 to ptr), ptr null, ptr @_ZTI2V1,
+// CHECK-SAME: ptr ptrauth (ptr @_ZN2V12m0Ev, i32 0, i64 49430, ptr getelementptr inbounds ({ [7 x ptr], [11 x ptr] }, ptr @_ZTC2D316_2V1, i32 0, i32 0, i32 3)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN2V12m1Ev, i32 0, i64 57119, ptr getelementptr inbounds ({ [7 x ptr], [11 x ptr] }, ptr @_ZTC2D316_2V1, i32 0, i32 0, i32 4)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN2V1D1Ev, i32 0, i64 60799, ptr getelementptr inbounds ({ [7 x ptr], [11 x ptr] }, ptr @_ZTC2D316_2V1, i32 0, i32 0, i32 5)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN2V1D0Ev, i32 0, i64 52565, ptr getelementptr inbounds ({ [7 x ptr], [11 x ptr] }, ptr @_ZTC2D316_2V1, i32 0, i32 0, i32 6))],
+// CHECK-SAME: [11 x ptr] [ptr inttoptr (i64 -16 to ptr), ptr null, ptr inttoptr (i64 -16 to ptr), ptr inttoptr (i64 -16 to ptr), ptr inttoptr (i64 -16 to ptr), ptr @_ZTI2V1,
+// CHECK-SAME: ptr ptrauth (ptr @_ZTv0_n24_N2V12m0Ev, i32 0, i64 53119, ptr getelementptr inbounds ({ [7 x ptr], [11 x ptr] }, ptr @_ZTC2D316_2V1, i32 0, i32 1, i32 6)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZTcv0_n32_h4_N2V12m1Ev, i32 0, i64 15165, ptr getelementptr inbounds ({ [7 x ptr], [11 x ptr] }, ptr @_ZTC2D316_2V1, i32 0, i32 1, i32 7)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN2B02m2Ev, i32 0, i64 43073, ptr getelementptr inbounds ({ [7 x ptr], [11 x ptr] }, ptr @_ZTC2D316_2V1, i32 0, i32 1, i32 8)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZTv0_n48_N2V1D1Ev, i32 0, i64 25525, ptr getelementptr inbounds ({ [7 x ptr], [11 x ptr] }, ptr @_ZTC2D316_2V1, i32 0, i32 1, i32 9)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZTv0_n48_N2V1D0Ev, i32 0, i64 21295, ptr getelementptr inbounds ({ [7 x ptr], [11 x ptr] }, ptr @_ZTC2D316_2V1, i32 0, i32 1, i32 10))] }, align 8
+
+
+struct S0 {
+  int f;
+};
+
+struct S1 {
+  int f;
+};
+
+struct S2 : S0, S1 {
+  int f;
+};
+
+class B0 {
+public:
+  virtual void m0();
+  virtual S1 *m1();
+  virtual void m2();
+  virtual ~B0();
+  int f;
+};
+
+class B1 {
+public:
+  virtual void m0();
+};
+
+class D0 : public B0 {
+public:
+  void m0() override;
+  S2 *m1() override;
+  virtual void m3();
+  int f;
+};
+
+class D1 : public B0 {
+public:
+  void m0() override;
+  S2 *m1() override;
+  int f;
+};
+
+class D2 : public D0, public D1 {
+public:
+  void m0() override;
+  S2 *m1() override;
+  void m3() override;
+  int f;
+};
+
+class V0 : public virtual B0 {
+public:
+  void m0() override;
+  S2 *m1() override;
+  int f;
+};
+
+class V1 : public virtual B0 {
+public:
+  void m0() override;
+  S2 *m1() override;
+  ~V1();
+  int f;
+};
+
+class D3 : public V0, public V1 {
+public:
+  void m0() override;
+  S2 *m1() override;
+  int f;
+};
+
+B1 g_B1;
+
+void B0::m0() {}
+
+void B1::m0() {}
+
+void D0::m0() {}
+
+void D1::m0() {}
+
+void D2::m0() {}
+
+void D3::m0() {}
+
+V1::~V1() {
+  m1();
+}
+
+// Check sign/authentication of vtable pointers and authentication of virtual
+// functions.
+
+// CHECK-LABEL: define noundef ptr @_ZN2V1D2Ev(
+// CHECK: %[[THIS1:.*]] = load ptr, ptr %{{.*}}
+// CHECK: %[[T0:[0-9]+]] = load ptr, ptr %{{.*}}
+// CHECK: %[[T1:[0-9]+]] = ptrtoint ptr %[[T0]] to i64
+// CHECK: %[[T2:[0-9]+]] = call i64 @llvm.ptrauth.auth(i64 %[[T1]], i32 2, i64 0)
+// CHECK: %[[T3:[0-9]+]] = inttoptr i64 %[[T2]] to ptr
+// CHECK: %[[T6:[0-9]+]] = ptrtoint ptr %[[T3]] to i64
+// CHECK: %[[T7:[0-9]+]] = call i64 @llvm.ptrauth.sign(i64 %[[T6]], i32 2, i64 0)
+// CHECK: %[[SIGNED_VTADDR:[0-9]+]] = inttoptr i64 %[[T7]] to ptr
+// CHECK: store ptr %[[SIGNED_VTADDR]], ptr %[[THIS1]]
+
+// CHECK-LABEL: define void @_Z8testB0m0P2B0(
+// CHECK: %[[VTABLE:[a-z]+]] = load ptr, ptr %{{.*}}
+// CHECK: %[[T0:[0-9]+]] = ptrtoint ptr %[[VTABLE]] to i64
+// CHECK: %[[T3:[0-9]+]] = call i64 @llvm.ptrauth.auth(i64 %[[T0]], i32 2, i64 0)
+// CHECK: %[[T4:[0-9]+]] = inttoptr i64 %[[T3]] to ptr
+// CHECK: %[[VFN:[a-z]+]] = getelementptr inbounds ptr, ptr %[[T4]], i64 0
+// CHECK: %[[T5:[0-9]+]] = load ptr, ptr %[[VFN]]
+// CHECK: %[[T6:[0-9]+]] = ptrtoint ptr %[[VFN]] to i64
+// CHECK: %[[T7:[0-9]+]] = call i64 @llvm.ptrauth.blend(i64 %[[T6]], i64 53119)
+// CHECK: call void %[[T5]](ptr noundef nonnull align {{[0-9]+}} dereferenceable(12) %{{.*}}) [ "ptrauth"(i32 0, i64 %[[T7]]) ]
+
+void testB0m0(B0 *a) {
+  a->m0();
+}
+
+// CHECK-LABEL: define void @_Z8testB0m1P2B0(
+// CHECK: %[[VTABLE:[a-z]+]] = load ptr, ptr %{{.*}}
+// CHECK: %[[T0:[0-9]+]] = ptrtoint ptr %[[VTABLE]] to i64
+// CHECK: %[[T3:[0-9]+]] = call i64 @llvm.ptrauth.auth(i64 %[[T0]], i32 2, i64 0)
+// CHECK: %[[T4:[0-9]+]] = inttoptr i64 %[[T3]] to ptr
+// CHECK: %[[VFN:[a-z]+]] = getelementptr inbounds ptr, ptr %[[T4]], i64 1
+// CHECK: %[[T5:[0-9]+]] = load ptr, ptr %[[VFN]]
+// CHECK: %[[T6:[0-9]+]] = ptrtoint ptr %[[VFN]] to i64
+// CHECK: %[[T7:[0-9]+]] = call i64 @llvm.ptrauth.blend(i64 %[[T6]], i64 15165)
+// CHECK: call noundef ptr %[[T5]](ptr noundef nonnull align {{[0-9]+}} dereferenceable(12) %{{.*}}) [ "ptrauth"(i32 0, i64 %[[T7]]) ]
+
+void testB0m1(B0 *a) {
+  a->m1();
+}
+
+// CHECK-LABEL: define void @_Z8testB0m2P2B0(
+// CHECK: %[[VTABLE:[a-z]+]] = load ptr, ptr %{{.*}}
+// CHECK: %[[T0:[0-9]+]] = ptrtoint ptr %[[VTABLE]] to i64
+// CHECK: %[[T3:[0-9]+]] = call i64 @llvm.ptrauth.auth(i64 %[[T0]], i32 2, i64 0)
+// CHECK: %[[T4:[0-9]+]] = inttoptr i64 %[[T3]] to ptr
+// CHECK: %[[VFN:[a-z]+]] = getelementptr inbounds ptr, ptr %[[T4]], i64 2
+// CHECK: %[[T5:[0-9]+]] = load ptr, ptr %[[VFN]]
+// CHECK: %[[T6:[0-9]+]] = ptrtoint ptr %[[VFN]] to i64
+// CHECK: %[[T7:[0-9]+]] = call i64 @llvm.ptrauth.blend(i64 %[[T6]], i64 43073)
+// CHECK: call void %[[T5]](ptr noundef nonnull align {{[0-9]+}} dereferenceable(12) %{{.*}}) [ "ptrauth"(i32 0, i64 %[[T7]]) ]
+
+void testB0m2(B0 *a) {
+  a->m2();
+}
+
+// CHECK-LABEL: define void @_Z8testD0m0P2D0(
+// CHECK: %[[VTABLE:[a-z]+]] = load ptr, ptr %{{.*}}
+// CHECK: %[[T0:[0-9]+]] = ptrtoint ptr %[[VTABLE]] to i64
+// CHECK: %[[T3:[0-9]+]] = call i64 @llvm.ptrauth.auth(i64 %[[T0]], i32 2, i64 0)
+// CHECK: %[[T4:[0-9]+]] = inttoptr i64 %[[T3]] to ptr
+// CHECK: %[[VFN:[a-z]+]] = getelementptr inbounds ptr, ptr %[[T4]], i64 0
+// CHECK: %[[T5:[0-9]+]] = load ptr, ptr %[[VFN]]
+// CHECK: %[[T6:[0-9]+]] = ptrtoint ptr %[[VFN]] to i64
+// CHECK: %[[T7:[0-9]+]] = call i64 @llvm.ptrauth.blend(i64 %[[T6]], i64 53119)
+// CHECK: call void %[[T5]](ptr noundef nonnull align {{[0-9]+}} dereferenceable(16) %{{.*}}) [ "ptrauth"(i32 0, i64 %[[T7]]) ]
+
+void testD0m0(D0 *a) {
+  a->m0();
+}
+
+// CHECK-LABEL: define void @_Z8testD0m1P2D0(
+// CHECK: %[[VTABLE:[a-z]+]] = load ptr, ptr %{{.*}}
+// CHECK: %[[T0:[0-9]+]] = ptrtoint ptr %[[VTABLE]] to i64
+// CHECK: %[[T3:[0-9]+]] = call i64 @llvm.ptrauth.auth(i64 %[[T0]], i32 2, i64 0)
+// CHECK: %[[T4:[0-9]+]] = inttoptr i64 %[[T3]] to ptr
+// CHECK: %[[VFN:[a-z]+]] = getelementptr inbounds ptr, ptr %[[T4]], i64 5
+// CHECK: %[[T5:[0-9]+]] = load ptr, ptr %[[VFN]]
+// CHECK: %[[T6:[0-9]+]] = ptrtoint ptr %[[VFN]] to i64
+// CHECK: %[[T7:[0-9]+]] = call i64 @llvm.ptrauth.blend(i64 %[[T6]], i64 35045)
+// CHECK: call noundef ptr %[[T5]](ptr noundef nonnull align {{[0-9]+}} dereferenceable(16) %{{.*}}) [ "ptrauth"(i32 0, i64 %[[T7]]) ]
+
+void testD0m1(D0 *a) {
+  a->m1();
+}
+
+// CHECK-LABEL: define void @_Z8testD0m2P2D0(
+// CHECK: %[[VTABLE:[a-z]+]] = load ptr, ptr %{{.*}}
+// CHECK: %[[T0:[0-9]+]] = ptrtoint ptr %[[VTABLE]] to i64
+// CHECK: %[[T3:[0-9]+]] = call i64 @llvm.ptrauth.auth(i64 %[[T0]], i32 2, i64 0)
+// CHECK: %[[T4:[0-9]+]] = inttoptr i64 %[[T3]] to ptr
+// CHECK: %[[VFN:[a-z]+]] = getelementptr inbounds ptr, ptr %[[T4]], i64 2
+// CHECK: %[[T5:[0-9]+]] = load ptr, ptr %[[VFN]]
+// CHECK: %[[T6:[0-9]+]] = ptrtoint ptr %[[VFN]] to i64
+// CHECK: %[[T7:[0-9]+]] = call i64 @llvm.ptrauth.blend(i64 %[[T6]], i64 43073)
+// CHECK: call void %[[T5]](ptr noundef nonnull align {{[0-9]+}} dereferenceable(12) %{{.*}}) [ "ptrauth"(i32 0, i64 %[[T7]]) ]
+
+void testD0m2(D0 *a) {
+  a->m2();
+}
+
+// CHECK-LABEL: define void @_Z8testD0m3P2D0(
+// CHECK: %[[VTABLE:[a-z]+]] = load ptr, ptr %{{.*}}
+// CHECK: %[[T0:[0-9]+]] = ptrtoint ptr %[[VTABLE]] to i64
+// CHECK: %[[T3:[0-9]+]] = call i64 @llvm.ptrauth.auth(i64 %[[T0]], i32 2, i64 0)
+// CHECK: %[[T4:[0-9]+]] = inttoptr i64 %[[T3]] to ptr
+// CHECK: %[[VFN:[a-z]+]] = getelementptr inbounds ptr, ptr %[[T4]], i64 6
+// CHECK: %[[T5:[0-9]+]] = load ptr, ptr %[[VFN]]
+// CHECK: %[[T6:[0-9]+]] = ptrtoint ptr %[[VFN]] to i64
+// CHECK: %[[T7:[0-9]+]] = call i64 @llvm.ptrauth.blend(i64 %[[T6]], i64 10565)
+// CHECK: call void %[[T5]](ptr noundef nonnull align {{[0-9]+}} dereferenceable(16) %{{.*}}) [ "ptrauth"(i32 0, i64 %[[T7]]) ]
+
+void testD0m3(D0 *a) {
+  a->m3();
+}
+
+
+// CHECK-LABEL: define void @_Z8testD1m0P2D1(
+// CHECK: %[[VTABLE:[a-z]+]] = load ptr, ptr %{{.*}}
+// CHECK: %[[T0:[0-9]+]] = ptrtoint ptr %[[VTABLE]] to i64
+// CHECK: %[[T3:[0-9]+]] = call i64 @llvm.ptrauth.auth(i64 %[[T0]], i32 2, i64 0)
+// CHECK: %[[T4:[0-9]+]] = inttoptr i64 %[[T3]] to ptr
+// CHECK: %[[VFN:[a-z]+]] = getelementptr inbounds ptr, ptr %[[T4]], i64 0
+// CHECK: %[[T5:[0-9]+]] = load ptr, ptr %[[VFN]]
+// CHECK: %[[T6:[0-9]+]] = ptrtoint ptr %[[VFN]] to i64
+// CHECK: %[[T7:[0-9]+]] = call i64 @llvm.ptrauth.blend(i64 %[[T6]], i64 53119)
+// CHECK: call void %[[T5]](ptr noundef nonnull align {{[0-9]+}} dereferenceable(16) %{{.*}}) [ "ptrauth"(i32 0, i64 %[[T7]]) ]
+
+void testD1m0(D1 *a) {
+  a->m0();
+}
+
+// CHECK-LABEL: define void @_Z8testD1m1P2D1(
+// CHECK: %[[VTABLE:[a-z]+]] = load ptr, ptr %{{.*}}
+// CHECK: %[[T0:[0-9]+]] = ptrtoint ptr %[[VTABLE]] to i64
+// CHECK: %[[T3:[0-9]+]] = call i64 @llvm.ptrauth.auth(i64 %[[T0]], i32 2, i64 0)
+// CHECK: %[[T4:[0-9]+]] = inttoptr i64 %[[T3]] to ptr
+// CHECK: %[[VFN:[a-z]+]] = getelementptr inbounds ptr, ptr %[[T4]], i64 5
+// CHECK: %[[T5:[0-9]+]] = load ptr, ptr %[[VFN]]
+// CHECK: %[[T6:[0-9]+]] = ptrtoint ptr %[[VFN]] to i64
+// CHECK: %[[T7:[0-9]+]] = call i64 @llvm.ptrauth.blend(i64 %[[T6]], i64 52864)
+// CHECK: call noundef ptr %[[T5]](ptr noundef nonnull align {{[0-9]+}} dereferenceable(16) %{{.*}}) [ "ptrauth"(i32 0, i64 %[[T7]]) ]
+
+void testD1m1(D1 *a) {
+  a->m1();
+}
+
+// CHECK-LABEL: define void @_Z8testD1m2P2D1(
+// CHECK: %[[VTABLE:[a-z]+]] = load ptr, ptr %{{.*}}
+// CHECK: %[[T0:[0-9]+]] = ptrtoint ptr %[[VTABLE]] to i64
+// CHECK: %[[T3:[0-9]+]] = call i64 @llvm.ptrauth.auth(i64 %[[T0]], i32 2, i64 0)
+// CHECK: %[[T4:[0-9]+]] = inttoptr i64 %[[T3]] to ptr
+// CHECK: %[[VFN:[a-z]+]] = getelementptr inbounds ptr, ptr %[[T4]], i64 2
+// CHECK: %[[T5:[0-9]+]] = load ptr, ptr %[[VFN]]
+// CHECK: %[[T6:[0-9]+]] = ptrtoint ptr %[[VFN]] to i64
+// CHECK: %[[T7:[0-9]+]] = call i64 @llvm.ptrauth.blend(i64 %[[T6]], i64 43073)
+// CHECK: call void %[[T5]](ptr noundef nonnull align {{[0-9]+}} dereferenceable(12) %{{.*}}) [ "ptrauth"(i32 0, i64 %[[T7]]) ]
+
+void testD1m2(D1 *a) {
+  a->m2();
+}
+
+
+// CHECK-LABEL: define void @_Z8testD2m0P2D2(
+// CHECK: %[[VTABLE:[a-z]+]] = load ptr, ptr %{{.*}}
+// CHECK: %[[T0:[0-9]+]] = ptrtoint ptr %[[VTABLE]] to i64
+// CHECK: %[[T3:[0-9]+]] = call i64 @llvm.ptrauth.auth(i64 %[[T0]], i32 2, i64 0)
+// CHECK: %[[T4:[0-9]+]] = inttoptr i64 %[[T3]] to ptr
+// CHECK: %[[VFN:[a-z]+]] = getelementptr inbounds ptr, ptr %[[T4]], i64 0
+// CHECK: %[[T5:[0-9]+]] = load ptr, ptr %[[VFN]]
+// CHECK: %[[T6:[0-9]+]] = ptrtoint ptr %[[VFN]] to i64
+// CHECK: %[[T7:[0-9]+]] = call i64 @llvm.ptrauth.blend(i64 %[[T6]], i64 53119)
+// CHECK: call void %[[T5]](ptr noundef nonnull align {{[0-9]+}} dereferenceable(36) %{{.*}}) [ "ptrauth"(i32 0, i64 %[[T7]]) ]
+
+void testD2m0(D2 *a) {
+  a->m0();
+}
+
+// CHECK-LABEL: define void @_Z8testD2m1P2D2(
+// CHECK: %[[VTABLE:[a-z]+]] = load ptr, ptr %{{.*}}
+// CHECK: %[[T0:[0-9]+]] = ptrtoint ptr %[[VTABLE]] to i64
+// CHECK: %[[T3:[0-9]+]] = call i64 @llvm.ptrauth.auth(i64 %[[T0]], i32 2, i64 0)
+// CHECK: %[[T4:[0-9]+]] = inttoptr i64 %[[T3]] to ptr
+// CHECK: %[[VFN:[a-z]+]] = getelementptr inbounds ptr, ptr %[[T4]], i64 5
+// CHECK: %[[T5:[0-9]+]] = load ptr, ptr %[[VFN]]
+// CHECK: %[[T6:[0-9]+]] = ptrtoint ptr %[[VFN]] to i64
+// CHECK: %[[T7:[0-9]+]] = call i64 @llvm.ptrauth.blend(i64 %[[T6]], i64 35045)
+// CHECK: call noundef ptr %[[T5]](ptr noundef nonnull align {{[0-9]+}} dereferenceable(36) %{{.*}}) [ "ptrauth"(i32 0, i64 %[[T7]]) ]
+
+void testD2m1(D2 *a) {
+  a->m1();
+}
+
+// CHECK-LABEL: define void @_Z10testD2m2D0P2D2(
+// CHECK: call void @_ZN2B02m2Ev(ptr noundef nonnull align {{[0-9]+}} dereferenceable(12) %{{.*}}){{$}}
+
+void testD2m2D0(D2 *a) {
+  a->D0::m2();
+}
+
+// CHECK-LABEL: define void @_Z10testD2m2D1P2D2(
+// CHECK: call void @_ZN2B02m2Ev(ptr noundef nonnull align {{[0-9]+}} dereferenceable(12) %{{.*}}){{$}}
+
+void testD2m2D1(D2 *a) {
+  a->D1::m2();
+}
+
+// CHECK-LABEL: define void @_Z8testD2m3P2D2(
+// CHECK: %[[VTABLE:[a-z]+]] = load ptr, ptr %{{.*}}
+// CHECK: %[[T0:[0-9]+]] = ptrtoint ptr %[[VTABLE]] to i64
+// CHECK: %[[T3:[0-9]+]] = call i64 @llvm.ptrauth.auth(i64 %[[T0]], i32 2, i64 0)
+// CHECK: %[[T4:[0-9]+]] = inttoptr i64 %[[T3]] to ptr
+// CHECK: %[[VFN:[a-z]+]] = getelementptr inbounds ptr, ptr %[[T4]], i64 6
+// CHECK: %[[T5:[0-9]+]] = load ptr, ptr %[[VFN]]
+// CHECK: %[[T6:[0-9]+]] = ptrtoint ptr %[[VFN]] to i64
+// CHECK: %[[T7:[0-9]+]] = call i64 @llvm.ptrauth.blend(i64 %[[T6]], i64 10565)
+// CHECK: call void %[[T5]](ptr noundef nonnull align {{[0-9]+}} dereferenceable(36) %{{.*}}) [ "ptrauth"(i32 0, i64 %[[T7]]) ]
+
+void testD2m3(D2 *a) {
+  a->m3();
+}
+
+// CHECK-LABEL: define void @_Z8testD3m0P2D3(
+// CHECK: %[[VTABLE:[a-z]+]] = load ptr, ptr %{{.*}}
+// CHECK: %[[T0:[0-9]+]] = ptrtoint ptr %[[VTABLE]] to i64
+// CHECK: %[[T3:[0-9]+]] = call i64 @llvm.ptrauth.auth(i64 %[[T0]], i32 2, i64 0)
+// CHECK: %[[T4:[0-9]+]] = inttoptr i64 %[[T3]] to ptr
+// CHECK: %[[VFN:[a-z]+]] = getelementptr inbounds ptr, ptr %[[T4]], i64 0
+// CHECK: %[[T5:[0-9]+]] = load ptr, ptr %[[VFN]]
+// CHECK: %[[T6:[0-9]+]] = ptrtoint ptr %[[VFN]] to i64
+// CHECK: %[[T7:[0-9]+]] = call i64 @llvm.ptrauth.blend(i64 %[[T6]], i64 44578)
+// CHECK: call void %[[T5]](ptr noundef nonnull align {{[0-9]+}} dereferenceable(32) %{{.*}}) [ "ptrauth"(i32 0, i64 %[[T7]]) ]
+
+void testD3m0(D3 *a) {
+  a->m0();
+}
+
+// CHECK-LABEL: define void @_Z8testD3m1P2D3(
+// CHECK: %[[VTABLE:[a-z]+]] = load ptr, ptr %{{.*}}
+// CHECK: %[[T0:[0-9]+]] = ptrtoint ptr %[[VTABLE]] to i64
+// CHECK: %[[T3:[0-9]+]] = call i64 @llvm.ptrauth.auth(i64 %[[T0]], i32 2, i64 0)
+// CHECK: %[[T4:[0-9]+]] = inttoptr i64 %[[T3]] to ptr
+// CHECK: %[[VFN:[a-z]+]] = getelementptr inbounds ptr, ptr %[[T4]], i64 1
+// CHECK: %[[T5:[0-9]+]] = load ptr, ptr %[[VFN]]
+// CHECK: %[[T6:[0-9]+]] = ptrtoint ptr %[[VFN]] to i64
+// CHECK: %[[T7:[0-9]+]] = call i64 @llvm.ptrauth.blend(i64 %[[T6]], i64 30766)
+// CHECK: call noundef ptr %[[T5]](ptr noundef nonnull align {{[0-9]+}} dereferenceable(32) %{{.*}}) [ "ptrauth"(i32 0, i64 %[[T7]]) ]
+
+void testD3m1(D3 *a) {
+  a->m1();
+}
+
+// CHECK: define void @_Z8testD3m2P2D3(ptr noundef %[[A:.*]])
+// CHECK: %[[A_ADDR:.*]] = alloca ptr, align 8
+// CHECK: store ptr %[[A]], ptr %[[A_ADDR]], align 8
+// CHECK: %[[V0:.*]] = load ptr, ptr %[[A_ADDR]], align 8
+// CHECK: %[[VTABLE:.*]] = load ptr, ptr %[[V0]], align 8
+// CHECK: %[[V1:.*]] = ptrtoint ptr %[[VTABLE]] to i64
+// CHECK: %[[V2:.*]] = call i64 @llvm.ptrauth.auth(i64 %[[V1]], i32 2, i64 0)
+// CHECK: %[[V3:.*]] = inttoptr i64 %[[V2]] to ptr
+// CHECK: %[[VBASE_OFFSET_PTR:.*]] = getelementptr i8, ptr %[[V3]], i64 -24
+// CHECK: %[[VBASE_OFFSET:.*]] = load i64, ptr %[[VBASE_OFFSET_PTR]], align 8
+// CHECK: %[[ADD_PTR:.*]] = getelementptr inbounds i8, ptr %[[V0]], i64 %[[VBASE_OFFSET]]
+// CHECK: %[[VTABLE1:.*]] = load ptr, ptr %[[ADD_PTR]], align 8
+// CHECK: %[[V4:.*]] = ptrtoint ptr %[[VTABLE1]] to i64
+// CHECK: %[[V5:.*]] = call i64 @llvm.ptrauth.auth(i64 %[[V4]], i32 2, i64 0)
+// CHECK: %[[V6:.*]] = inttoptr i64 %[[V5]] to ptr
+// CHECK: %[[VFN:.*]] = getelementptr inbounds ptr, ptr %[[V6]], i64 2
+// CHECK: %[[V7:.*]] = load ptr, ptr %[[VFN]], align 8
+// CHECK: %[[V8:.*]] = ptrtoint ptr %[[VFN]] to i64
+// CHECK: %[[V9:.*]] = call i64 @llvm.ptrauth.blend(i64 %[[V8]], i64 43073)
+// CHECK: call void %[[V7]](ptr noundef nonnull align 8 dereferenceable(12) %[[ADD_PTR]]) [ "ptrauth"(i32 0, i64 %[[V9]]) ]
+
+void testD3m2(D3 *a) {
+  a->m2();
+}
+
+// CHECK-LABEL: define void @_Z17testD3Destructor0P2D3(
+// CHECK: load ptr, ptr
+// CHECK: %[[VTABLE:.*]] = load ptr, ptr %{{.*}}
+// CHECK: %[[T2:[0-9]+]] = ptrtoint ptr %[[VTABLE]] to i64
+// CHECK: %[[T3:[0-9]+]] = call i64 @llvm.ptrauth.auth(i64 %[[T2]], i32 2, i64 0)
+// CHECK: %[[T4:[0-9]+]] = inttoptr i64 %[[T3]] to ptr
+// CHECK: %[[VFN:[a-z]+]] = getelementptr inbounds ptr, ptr %[[T4]], i64 3
+// CHECK: %[[T5:[0-9]+]] = load ptr, ptr %[[VFN]]
+// CHECK: %[[T6:[0-9]+]] = ptrtoint ptr %[[VFN]] to i64
+// CHECK: %[[T7:[0-9]+]] = call i64 @llvm.ptrauth.blend(i64 %[[T6]], i64 62452)
+// CHECK: call void %[[T5]](ptr noundef nonnull align {{[0-9]+}} dereferenceable(32) %{{.*}}) #{{.*}} [ "ptrauth"(i32 0, i64 %[[T7]]) ]
+
+void testD3Destructor0(D3 *a) {
+  delete a;
+}
+
+// CHECK-LABEL: define void @_Z17testD3Destructor1P2D3(
+// CHECK: %[[T6:.*]] = load ptr, ptr %
+// CHECK: %[[VTABLE0:[a-z0-9]+]] = load ptr, ptr %
+// CHECK: %[[T2:[0-9]+]] = ptrtoint ptr %[[VTABLE0]] to i64
+// CHECK: %[[T3:[0-9]+]] = call i64 @llvm.ptrauth.auth(i64 %[[T2]], i32 2, i64 0)
+// CHECK: %[[T4:[0-9]+]] = inttoptr i64 %[[T3]] to ptr
+// CHECK: %[[COMPLETE_OFFSET_PTR:.*]] = getelementptr inbounds i64, ptr %[[T4]], i64 -2
+// CHECK: %[[T5:[0-9]+]] = load i64, ptr %[[COMPLETE_OFFSET_PTR]]
+// CHECK: %[[T7:[0-9]+]] = getelementptr inbounds i8, ptr %[[T6]], i64 %[[T5]]
+// CHECK: %[[VTABLE1:[a-z0-9]+]] = load ptr, ptr %[[T6]]
+// CHECK: %[[T9:[0-9]+]] = ptrtoint ptr %[[VTABLE1]] to i64
+// CHECK: %[[T10:[0-9]+]] = call i64 @llvm.ptrauth.auth(i64 %[[T9]], i32 2, i64 0)
+// CHECK: %[[T11:[0-9]+]] = inttoptr i64 %[[T10]] to ptr
+// CHECK: %[[VFN:[a-z0-9]+]] = getelementptr inbounds ptr, ptr %[[T11]], i64 2
+// CHECK: %[[T12:[0-9]+]] = load ptr, ptr %[[VFN]]
+// CHECK: %[[T13:[0-9]+]] = ptrtoint ptr %[[VFN]] to i64
+// CHECK: %[[T14:[0-9]+]] = call i64 @llvm.ptrauth.blend(i64 %[[T13]], i64 57279)
+// CHECK: %call = call noundef ptr %[[T12]](ptr noundef nonnull align {{[0-9]+}} dereferenceable(32) %{{.*}}) #{{.*}} [ "ptrauth"(i32 0, i64 %[[T14]]) ]
+// CHECK: call void @_ZdlPv(ptr noundef %[[T7]])
+
+void testD3Destructor1(D3 *a) {
+  ::delete a;
+}
+
+// CHECK-LABEL: define void @_Z17testD3Destructor2P2D3(
+// CHECK: load ptr, ptr
+// CHECK: %[[VTABLE:.*]] = load ptr, ptr %
+// CHECK: %[[T2:.*]] = ptrtoint ptr %[[VTABLE]] to i64
+// CHECK: %[[T3:.*]] = call i64 @llvm.ptrauth.auth(i64 %[[T2]], i32 2, i64 0)
+// CHECK: %[[T4:.*]] = inttoptr i64 %[[T3]] to ptr
+// CHECK: %[[VFN:.*]] = getelementptr inbounds ptr, ptr %[[T4]], i64 2
+// CHECK: %[[T5:.*]] = load ptr, ptr %[[VFN]]
+// CHECK: %[[T6:.*]] = ptrtoint ptr %[[VFN]] to i64
+// CHECK: %[[T7:.*]] = call i64 @llvm.ptrauth.blend(i64 %[[T6]], i64 57279)
+// CHECK: %call = call noundef ptr %[[T5]](ptr noundef nonnull align {{[0-9]+}} dereferenceable(32) %{{.*}}) #{{.*}} [ "ptrauth"(i32 0, i64 %[[T7]]) ]
+
+void testD3Destructor2(D3 *a) {
+  a->~D3();
+}
+
+void materializeConstructors() {
+  B0 B0;
+  B1 B1;
+  D0 D0;
+  D1 D1;
+  D2 D2;
+  D3 D3;
+  V0 V0;
+  V1 V1;
+}
+
+// CHECK-LABEL: define linkonce_odr noundef ptr @_ZN2B0C2Ev(
+// CHECK: %[[THIS:.*]] = load ptr, ptr %
+// CHECK: %[[T0:[0-9]+]] = call i64 @llvm.ptrauth.sign(i64 ptrtoint (ptr getelementptr inbounds inrange(-16, 40) ({ [7 x ptr] }, ptr @_ZTV2B0, i32 0, i32 0, i32 2) to i64), i32 2, i64 0)
+// CHECK: %[[SIGNED_VTADDR:[0-9]+]] = inttoptr i64 %[[T0]] to ptr
+// CHECK: store ptr %[[SIGNED_VTADDR]], ptr %[[THIS]]
+
+// CHECK-LABEL: define linkonce_odr noundef ptr @_ZN2D0C2Ev(
+// CHECK: %[[T0:[0-9]+]] = call i64 @llvm.ptrauth.sign(i64 ptrtoint (ptr getelementptr inbounds inrange(-16, 56) ({ [9 x ptr] }, ptr @_ZTV2D0, i32 0, i32 0, i32 2) to i64), i32 2, i64 0)
+// CHECK: %[[SIGNED_VTADDR:[0-9]+]] = inttoptr i64 %[[T0]] to ptr
+// CHECK: store ptr %[[SIGNED_VTADDR]], ptr %[[THIS]]
+
+// CHECK-LABEL: define linkonce_odr noundef ptr @_ZN2D1C2Ev(
+// CHECK: %[[T0:[0-9]+]] = call i64 @llvm.ptrauth.sign(i64 ptrtoint (ptr getelementptr inbounds inrange(-16, 48) ({ [8 x ptr] }, ptr @_ZTV2D1, i32 0, i32 0, i32 2) to i64), i32 2, i64 0)
+// CHECK: %[[SIGNED_VTADDR:[0-9]+]] = inttoptr i64 %[[T0]] to ptr
+// CHECK: store ptr %[[SIGNED_VTADDR]], ptr %[[THIS]]
+
+// CHECK-LABEL: define linkonce_odr noundef ptr @_ZN2D2C2Ev(
+// CHECK: %[[SLOT0:.*]] = load ptr, ptr
+// CHECK: %[[SIGN_VTADDR0:[0-9]+]] = call i64 @llvm.ptrauth.sign(i64 ptrtoint (ptr getelementptr inbounds inrange(-16, 56) ({ [9 x ptr], [8 x ptr] }, ptr @_ZTV2D2, i32 0, i32 0, i32 2) to i64), i32 2, i64 0)
+// CHECK: %[[T1:[0-9]+]] = inttoptr i64 %[[SIGN_VTADDR0]] to ptr
+// CHECK: store ptr %[[T1]], ptr %[[SLOT0]]
+// CHECK: %[[T3:[a-z0-9.]+]] = getelementptr inbounds i8, ptr %[[SLOT0]], i64 16
+// CHECK: %[[SIGN_VTADDR1:[0-9]+]] = call i64 @llvm.ptrauth.sign(i64 ptrtoint (ptr getelementptr inbounds inrange(-16, 48) ({ [9 x ptr], [8 x ptr] }, ptr @_ZTV2D2, i32 0, i32 1, i32 2) to i64), i32 2, i64 0)
+// CHECK: %[[T5:[0-9]+]] = inttoptr i64 %[[SIGN_VTADDR1]] to ptr
+// CHECK: store ptr %[[T5]], ptr %[[T3]]
+
+// CHECK-LABEL: define linkonce_odr noundef ptr @_ZN2V0C2Ev(
+// CHECK: %[[THIS1]] = load ptr, ptr %
+// CHECK: %[[VTT:[a-z0-9]+]] = load ptr, ptr %{{.*}}
+// CHECK: %[[T0:[0-9]+]] = load ptr, ptr %[[VTT]]
+// CHECK: %[[T1:[0-9]+]] = ptrtoint ptr %[[T0]] to i64
+// CHECK: %[[T2:[0-9]+]] = call i64 @llvm.ptrauth.auth(i64 %[[T1]], i32 2, i64 0)
+// CHECK: %[[T3:[0-9]+]] = inttoptr i64 %[[T2]] to ptr
+// CHECK: %[[VTADDR0:[0-9]+]] = ptrtoint ptr %[[T3]] to i64
+// CHECK: %[[T7:[0-9]+]] = call i64 @llvm.ptrauth.sign(i64 %[[VTADDR0]], i32 2, i64 0)
+// CHECK: %[[SIGN_VTADDR0:[0-9]+]] = inttoptr i64 %[[T7]] to ptr
+// CHECK: store ptr %[[SIGN_VTADDR0]], ptr %[[SLOT0]]
+// CHECK: %[[T9:[0-9]+]] = getelementptr inbounds ptr, ptr %[[VTT]], i64 1
+// CHECK: %[[T10:[0-9]+]] = load ptr, ptr %[[T9]]
+// CHECK: %[[T11:[0-9]+]] = ptrtoint ptr %[[T10]] to i64
+// CHECK: %[[T12:[0-9]+]] = call i64 @llvm.ptrauth.auth(i64 %[[T11]], i32 2, i64 0)
+// CHECK: %[[T13:[0-9]+]] = inttoptr i64 %[[T12]] to ptr
+// CHECK: %[[VTABLE:[a-z]+]] = load ptr, ptr %[[THIS1]]
+// CHECK: %[[T15:[0-9]+]] = ptrtoint ptr %[[VTABLE]] to i64
+// CHECK: %[[T16:[0-9]+]] = call i64 @llvm.ptrauth.auth(i64 %[[T15]], i32 2, i64 0)
+// CHECK: %[[T17:[0-9]+]] = inttoptr i64 %[[T16]] to ptr
+// CHECK: %[[VBASE_OFFSET_PTR:[a-z.]+]] = getelementptr i8, ptr %[[T17]], i64 -24
+// CHECK: %[[VBASE_OFFSET:[a-z.]+]] = load i64, ptr %[[VBASE_OFFSET_PTR]]
+// CHECK: %[[T20:[a-z.]+]] = getelementptr inbounds i8, ptr %[[THIS1]], i64 %[[VBASE_OFFSET]]
+// CHECK: %[[VTADDR1:[0-9]+]] = ptrtoint ptr %[[T13]] to i64
+// CHECK: %[[T23:[0-9]+]] = call i64 @llvm.ptrauth.sign(i64 %[[VTADDR1]], i32 2, i64 0)
+// CHECK: %[[SIGN_VTADDR1:[0-9]+]] = inttoptr i64 %[[T23]] to ptr
+// CHECK: store ptr %[[SIGN_VTADDR1]], ptr %[[T20]]

diff  --git a/clang/test/CodeGenCXX/ptrauth-vtable-virtual-inheritance-thunk.cpp b/clang/test/CodeGenCXX/ptrauth-vtable-virtual-inheritance-thunk.cpp
new file mode 100644
index 0000000000000..00b1cbd06e0f0
--- /dev/null
+++ b/clang/test/CodeGenCXX/ptrauth-vtable-virtual-inheritance-thunk.cpp
@@ -0,0 +1,309 @@
+// RUN: %clang_cc1 %s -x c++ -std=c++11 -triple arm64-apple-ios -fptrauth-intrinsics -fptrauth-calls -fptrauth-vtable-pointer-type-discrimination -emit-llvm -O0 -disable-llvm-passes -o - | FileCheck --check-prefix=CHECK %s
+
+// The actual vtable construction
+
+// CHECK: @_ZTV1C = unnamed_addr constant { [5 x ptr], [11 x ptr] } { [5 x ptr] [ptr inttoptr (i64 8 to ptr), ptr null, ptr @_ZTI1C,
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1CD1Ev, i32 0, i64 31214, ptr getelementptr inbounds ({ [5 x ptr], [11 x ptr] }, ptr @_ZTV1C, i32 0, i32 0, i32 3)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1CD0Ev, i32 0, i64 8507, ptr getelementptr inbounds ({ [5 x ptr], [11 x ptr] }, ptr @_ZTV1C, i32 0, i32 0, i32 4))],
+// CHECK-SAME: [11 x ptr] [ptr inttoptr (i64 -8 to ptr), ptr null, ptr null, ptr null, ptr inttoptr (i64 -8 to ptr), ptr @_ZTI1C,
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1A1fEv, i32 0, i64 55636, ptr getelementptr inbounds ({ [5 x ptr], [11 x ptr] }, ptr @_ZTV1C, i32 0, i32 1, i32 6)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1A1gEv, i32 0, i64 19402, ptr getelementptr inbounds ({ [5 x ptr], [11 x ptr] }, ptr @_ZTV1C, i32 0, i32 1, i32 7)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1A1hEz, i32 0, i64 31735, ptr getelementptr inbounds ({ [5 x ptr], [11 x ptr] }, ptr @_ZTV1C, i32 0, i32 1, i32 8)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZTv0_n48_N1CD1Ev, i32 0, i64 2043, ptr getelementptr inbounds ({ [5 x ptr], [11 x ptr] }, ptr @_ZTV1C, i32 0, i32 1, i32 9)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZTv0_n48_N1CD0Ev, i32 0, i64 63674, ptr getelementptr inbounds ({ [5 x ptr], [11 x ptr] }, ptr @_ZTV1C, i32 0, i32 1, i32 10))] }, align 8
+
+// CHECK: @_ZTT1C = unnamed_addr constant [2 x ptr] [ptr ptrauth (ptr getelementptr inbounds inrange(-24, 16) ({ [5 x ptr], [11 x ptr] }, ptr @_ZTV1C, i32 0, i32 0, i32 3), i32 2),
+// CHECK-SAME: ptr ptrauth (ptr getelementptr inbounds inrange(-48, 40) ({ [5 x ptr], [11 x ptr] }, ptr @_ZTV1C, i32 0, i32 1, i32 6), i32 2)], align 8
+
+// CHECK: @_ZTV1D = unnamed_addr constant { [7 x ptr], [11 x ptr] } { [7 x ptr] [ptr inttoptr (i64 8 to ptr), ptr null, ptr @_ZTI1D,
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1DD1Ev, i32 0, i64 59423, ptr getelementptr inbounds ({ [7 x ptr], [11 x ptr] }, ptr @_ZTV1D, i32 0, i32 0, i32 3)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1DD0Ev, i32 0, i64 25900, ptr getelementptr inbounds ({ [7 x ptr], [11 x ptr] }, ptr @_ZTV1D, i32 0, i32 0, i32 4)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1D1gEv, i32 0, i64 59070, ptr getelementptr inbounds ({ [7 x ptr], [11 x ptr] }, ptr @_ZTV1D, i32 0, i32 0, i32 5)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1D1hEz, i32 0, i64 65100, ptr getelementptr inbounds ({ [7 x ptr], [11 x ptr] }, ptr @_ZTV1D, i32 0, i32 0, i32 6))],
+// CHECK-SAME: [11 x ptr] [ptr inttoptr (i64 -8 to ptr), ptr inttoptr (i64 -8 to ptr), ptr inttoptr (i64 -8 to ptr), ptr null, ptr inttoptr (i64 -8 to ptr), ptr @_ZTI1D,
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1A1fEv, i32 0, i64 55636, ptr getelementptr inbounds ({ [7 x ptr], [11 x ptr] }, ptr @_ZTV1D, i32 0, i32 1, i32 6)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZTv0_n32_N1D1gEv, i32 0, i64 19402, ptr getelementptr inbounds ({ [7 x ptr], [11 x ptr] }, ptr @_ZTV1D, i32 0, i32 1, i32 7)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZTv0_n40_N1D1hEz, i32 0, i64 31735, ptr getelementptr inbounds ({ [7 x ptr], [11 x ptr] }, ptr @_ZTV1D, i32 0, i32 1, i32 8)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZTv0_n48_N1DD1Ev, i32 0, i64 2043, ptr getelementptr inbounds ({ [7 x ptr], [11 x ptr] }, ptr @_ZTV1D, i32 0, i32 1, i32 9)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZTv0_n48_N1DD0Ev, i32 0, i64 63674, ptr getelementptr inbounds ({ [7 x ptr], [11 x ptr] }, ptr @_ZTV1D, i32 0, i32 1, i32 10))] }, align 8
+
+// CHECK: @_ZTT1D = unnamed_addr constant [2 x ptr] [ptr ptrauth (ptr getelementptr inbounds inrange(-24, 32) ({ [7 x ptr], [11 x ptr] }, ptr @_ZTV1D, i32 0, i32 0, i32 3), i32 2),
+// CHECK-SAME: ptr ptrauth (ptr getelementptr inbounds inrange(-48, 40) ({ [7 x ptr], [11 x ptr] }, ptr @_ZTV1D, i32 0, i32 1, i32 6), i32 2)], align 8
+
+// CHECK: @_ZTV1F = unnamed_addr constant { [6 x ptr], [7 x ptr], [11 x ptr], [11 x ptr] } { [6 x ptr] [ptr inttoptr (i64 32 to ptr), ptr inttoptr (i64 16 to ptr), ptr null, ptr @_ZTI1F,
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1FD1Ev, i32 0, i64 31214, ptr getelementptr inbounds ({ [6 x ptr], [7 x ptr], [11 x ptr], [11 x ptr] }, ptr @_ZTV1F, i32 0, i32 0, i32 4)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1FD0Ev, i32 0, i64 8507, ptr getelementptr inbounds ({ [6 x ptr], [7 x ptr], [11 x ptr], [11 x ptr] }, ptr @_ZTV1F, i32 0, i32 0, i32 5))], [7 x ptr] [ptr inttoptr (i64 8 to ptr), ptr inttoptr (i64 -8 to ptr), ptr @_ZTI1F,
+// CHECK-SAME: ptr ptrauth (ptr @_ZThn8_N1FD1Ev, i32 0, i64 59423, ptr getelementptr inbounds ({ [6 x ptr], [7 x ptr], [11 x ptr], [11 x ptr] }, ptr @_ZTV1F, i32 0, i32 1, i32 3)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZThn8_N1FD0Ev, i32 0, i64 25900, ptr getelementptr inbounds ({ [6 x ptr], [7 x ptr], [11 x ptr], [11 x ptr] }, ptr @_ZTV1F, i32 0, i32 1, i32 4)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1D1gEv, i32 0, i64 59070, ptr getelementptr inbounds ({ [6 x ptr], [7 x ptr], [11 x ptr], [11 x ptr] }, ptr @_ZTV1F, i32 0, i32 1, i32 5)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1D1hEz, i32 0, i64 65100, ptr getelementptr inbounds ({ [6 x ptr], [7 x ptr], [11 x ptr], [11 x ptr] }, ptr @_ZTV1F, i32 0, i32 1, i32 6))], [11 x ptr] [ptr inttoptr (i64 -16 to ptr), ptr inttoptr (i64 -8 to ptr), ptr inttoptr (i64 -8 to ptr), ptr null, ptr inttoptr (i64 -16 to ptr), ptr @_ZTI1F,
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1A1fEv, i32 0, i64 55636, ptr getelementptr inbounds ({ [6 x ptr], [7 x ptr], [11 x ptr], [11 x ptr] }, ptr @_ZTV1F, i32 0, i32 2, i32 6)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZTv0_n32_N1D1gEv, i32 0, i64 19402, ptr getelementptr inbounds ({ [6 x ptr], [7 x ptr], [11 x ptr], [11 x ptr] }, ptr @_ZTV1F, i32 0, i32 2, i32 7)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZTv0_n40_N1D1hEz, i32 0, i64 31735, ptr getelementptr inbounds ({ [6 x ptr], [7 x ptr], [11 x ptr], [11 x ptr] }, ptr @_ZTV1F, i32 0, i32 2, i32 8)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZTv0_n48_N1FD1EvU11__vtptrauthILj0Lb0Lj62866E, i32 0, i64 2043, ptr getelementptr inbounds ({ [6 x ptr], [7 x ptr], [11 x ptr], [11 x ptr] }, ptr @_ZTV1F, i32 0, i32 2, i32 9)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZTv0_n48_N1FD0EvU11__vtptrauthILj0Lb0Lj62866E, i32 0, i64 63674, ptr getelementptr inbounds ({ [6 x ptr], [7 x ptr], [11 x ptr], [11 x ptr] }, ptr @_ZTV1F, i32 0, i32 2, i32 10))], [11 x ptr] [ptr inttoptr (i64 -32 to ptr), ptr null, ptr null, ptr null, ptr inttoptr (i64 -32 to ptr), ptr @_ZTI1F,
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1E1fEv, i32 0, i64 28408, ptr getelementptr inbounds ({ [6 x ptr], [7 x ptr], [11 x ptr], [11 x ptr] }, ptr @_ZTV1F, i32 0, i32 3, i32 6)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1E1gEv, i32 0, i64 22926, ptr getelementptr inbounds ({ [6 x ptr], [7 x ptr], [11 x ptr], [11 x ptr] }, ptr @_ZTV1F, i32 0, i32 3, i32 7)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1E1hEz, i32 0, i64 9832, ptr getelementptr inbounds ({ [6 x ptr], [7 x ptr], [11 x ptr], [11 x ptr] }, ptr @_ZTV1F, i32 0, i32 3, i32 8)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZTv0_n48_N1FD1Ev, i32 0, i64 5817, ptr getelementptr inbounds ({ [6 x ptr], [7 x ptr], [11 x ptr], [11 x ptr] }, ptr @_ZTV1F, i32 0, i32 3, i32 9)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZTv0_n48_N1FD0Ev, i32 0, i64 26464, ptr getelementptr inbounds ({ [6 x ptr], [7 x ptr], [11 x ptr], [11 x ptr] }, ptr @_ZTV1F, i32 0, i32 3, i32 10))] }, align 8
+
+// CHECK: @_ZTT1F = unnamed_addr constant [8 x ptr] [ptr ptrauth (ptr getelementptr inbounds inrange(-32, 16) ({ [6 x ptr], [7 x ptr], [11 x ptr], [11 x ptr] }, ptr @_ZTV1F, i32 0, i32 0, i32 4), i32 2),
+// CHECK-SAME: ptr ptrauth (ptr getelementptr inbounds inrange(-24, 16) ({ [5 x ptr], [11 x ptr] }, ptr @_ZTC1F0_1C, i32 0, i32 0, i32 3), i32 2),
+// CHECK-SAME: ptr ptrauth (ptr getelementptr inbounds inrange(-48, 40) ({ [5 x ptr], [11 x ptr] }, ptr @_ZTC1F0_1C, i32 0, i32 1, i32 6), i32 2),
+// CHECK-SAME: ptr ptrauth (ptr getelementptr inbounds inrange(-24, 32) ({ [7 x ptr], [11 x ptr] }, ptr @_ZTC1F8_1D, i32 0, i32 0, i32 3), i32 2),
+// CHECK-SAME: ptr ptrauth (ptr getelementptr inbounds inrange(-48, 40) ({ [7 x ptr], [11 x ptr] }, ptr @_ZTC1F8_1D, i32 0, i32 1, i32 6), i32 2),
+// CHECK-SAME: ptr ptrauth (ptr getelementptr inbounds inrange(-48, 40) ({ [6 x ptr], [7 x ptr], [11 x ptr], [11 x ptr] }, ptr @_ZTV1F, i32 0, i32 2, i32 6), i32 2),
+// CHECK-SAME: ptr ptrauth (ptr getelementptr inbounds inrange(-24, 32) ({ [6 x ptr], [7 x ptr], [11 x ptr], [11 x ptr] }, ptr @_ZTV1F, i32 0, i32 1, i32 3), i32 2),
+// CHECK-SAME: ptr ptrauth (ptr getelementptr inbounds inrange(-48, 40) ({ [6 x ptr], [7 x ptr], [11 x ptr], [11 x ptr] }, ptr @_ZTV1F, i32 0, i32 3, i32 6), i32 2)], align 8
+
+// CHECK: @_ZTV1G = unnamed_addr constant { [6 x ptr], [7 x ptr], [11 x ptr], [11 x ptr] } { [6 x ptr] [ptr inttoptr (i64 16 to ptr), ptr inttoptr (i64 24 to ptr), ptr null, ptr @_ZTI1G,
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1GD1Ev, i32 0, i64 31214, ptr getelementptr inbounds ({ [6 x ptr], [7 x ptr], [11 x ptr], [11 x ptr] }, ptr @_ZTV1G, i32 0, i32 0, i32 4)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1GD0Ev, i32 0, i64 8507, ptr getelementptr inbounds ({ [6 x ptr], [7 x ptr], [11 x ptr], [11 x ptr] }, ptr @_ZTV1G, i32 0, i32 0, i32 5))], [7 x ptr] [ptr inttoptr (i64 16 to ptr), ptr inttoptr (i64 -8 to ptr), ptr @_ZTI1G,
+// CHECK-SAME: ptr ptrauth (ptr @_ZThn8_N1GD1Ev, i32 0, i64 59423, ptr getelementptr inbounds ({ [6 x ptr], [7 x ptr], [11 x ptr], [11 x ptr] }, ptr @_ZTV1G, i32 0, i32 1, i32 3)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZThn8_N1GD0Ev, i32 0, i64 25900, ptr getelementptr inbounds ({ [6 x ptr], [7 x ptr], [11 x ptr], [11 x ptr] }, ptr @_ZTV1G, i32 0, i32 1, i32 4)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1D1gEv, i32 0, i64 59070, ptr getelementptr inbounds ({ [6 x ptr], [7 x ptr], [11 x ptr], [11 x ptr] }, ptr @_ZTV1G, i32 0, i32 1, i32 5)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1D1hEz, i32 0, i64 65100, ptr getelementptr inbounds ({ [6 x ptr], [7 x ptr], [11 x ptr], [11 x ptr] }, ptr @_ZTV1G, i32 0, i32 1, i32 6))], [11 x ptr] [ptr inttoptr (i64 -16 to ptr), ptr null, ptr null, ptr null, ptr inttoptr (i64 -16 to ptr), ptr @_ZTI1G,
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1E1fEv, i32 0, i64 28408, ptr getelementptr inbounds ({ [6 x ptr], [7 x ptr], [11 x ptr], [11 x ptr] }, ptr @_ZTV1G, i32 0, i32 2, i32 6)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1E1gEv, i32 0, i64 22926, ptr getelementptr inbounds ({ [6 x ptr], [7 x ptr], [11 x ptr], [11 x ptr] }, ptr @_ZTV1G, i32 0, i32 2, i32 7)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1E1hEz, i32 0, i64 9832, ptr getelementptr inbounds ({ [6 x ptr], [7 x ptr], [11 x ptr], [11 x ptr] }, ptr @_ZTV1G, i32 0, i32 2, i32 8)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZTv0_n48_N1GD1Ev, i32 0, i64 5817, ptr getelementptr inbounds ({ [6 x ptr], [7 x ptr], [11 x ptr], [11 x ptr] }, ptr @_ZTV1G, i32 0, i32 2, i32 9)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZTv0_n48_N1GD0Ev, i32 0, i64 26464, ptr getelementptr inbounds ({ [6 x ptr], [7 x ptr], [11 x ptr], [11 x ptr] }, ptr @_ZTV1G, i32 0, i32 2, i32 10))], [11 x ptr] [ptr inttoptr (i64 -24 to ptr), ptr inttoptr (i64 -16 to ptr), ptr inttoptr (i64 -16 to ptr), ptr null, ptr inttoptr (i64 -24 to ptr), ptr @_ZTI1G,
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1A1fEv, i32 0, i64 55636, ptr getelementptr inbounds ({ [6 x ptr], [7 x ptr], [11 x ptr], [11 x ptr] }, ptr @_ZTV1G, i32 0, i32 3, i32 6)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZTv0_n32_N1D1gEv, i32 0, i64 19402, ptr getelementptr inbounds ({ [6 x ptr], [7 x ptr], [11 x ptr], [11 x ptr] }, ptr @_ZTV1G, i32 0, i32 3, i32 7)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZTv0_n40_N1D1hEz, i32 0, i64 31735, ptr getelementptr inbounds ({ [6 x ptr], [7 x ptr], [11 x ptr], [11 x ptr] }, ptr @_ZTV1G, i32 0, i32 3, i32 8)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZTv0_n48_N1GD1EvU11__vtptrauthILj0Lb0Lj62866E, i32 0, i64 2043, ptr getelementptr inbounds ({ [6 x ptr], [7 x ptr], [11 x ptr], [11 x ptr] }, ptr @_ZTV1G, i32 0, i32 3, i32 9)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZTv0_n48_N1GD0EvU11__vtptrauthILj0Lb0Lj62866E, i32 0, i64 63674, ptr getelementptr inbounds ({ [6 x ptr], [7 x ptr], [11 x ptr], [11 x ptr] }, ptr @_ZTV1G, i32 0, i32 3, i32 10))] }, align 8
+
+// CHECK: @_ZTT1G = unnamed_addr constant [8 x ptr] [ptr ptrauth (ptr getelementptr inbounds inrange(-32, 16) ({ [6 x ptr], [7 x ptr], [11 x ptr], [11 x ptr] }, ptr @_ZTV1G, i32 0, i32 0, i32 4), i32 2),
+// CHECK-SAME: ptr ptrauth (ptr getelementptr inbounds inrange(-24, 16) ({ [5 x ptr], [11 x ptr] }, ptr @_ZTC1G0_1C, i32 0, i32 0, i32 3), i32 2),
+// CHECK-SAME: ptr ptrauth (ptr getelementptr inbounds inrange(-48, 40) ({ [5 x ptr], [11 x ptr] }, ptr @_ZTC1G0_1C, i32 0, i32 1, i32 6), i32 2),
+// CHECK-SAME: ptr ptrauth (ptr getelementptr inbounds inrange(-24, 32) ({ [7 x ptr], [11 x ptr] }, ptr @_ZTC1G8_1D, i32 0, i32 0, i32 3), i32 2),
+// CHECK-SAME: ptr ptrauth (ptr getelementptr inbounds inrange(-48, 40) ({ [7 x ptr], [11 x ptr] }, ptr @_ZTC1G8_1D, i32 0, i32 1, i32 6), i32 2),
+// CHECK-SAME: ptr ptrauth (ptr getelementptr inbounds inrange(-48, 40) ({ [6 x ptr], [7 x ptr], [11 x ptr], [11 x ptr] }, ptr @_ZTV1G, i32 0, i32 2, i32 6), i32 2),
+// CHECK-SAME: ptr ptrauth (ptr getelementptr inbounds inrange(-48, 40) ({ [6 x ptr], [7 x ptr], [11 x ptr], [11 x ptr] }, ptr @_ZTV1G, i32 0, i32 3, i32 6), i32 2),
+// CHECK-SAME: ptr ptrauth (ptr getelementptr inbounds inrange(-24, 32) ({ [6 x ptr], [7 x ptr], [11 x ptr], [11 x ptr] }, ptr @_ZTV1G, i32 0, i32 1, i32 3), i32 2)], align 8
+
+// CHECK: @_ZTV1A = unnamed_addr constant { [7 x ptr] } { [7 x ptr] [ptr null, ptr @_ZTI1A,
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1A1fEv, i32 0, i64 55636, ptr getelementptr inbounds ({ [7 x ptr] }, ptr @_ZTV1A, i32 0, i32 0, i32 2)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1A1gEv, i32 0, i64 19402, ptr getelementptr inbounds ({ [7 x ptr] }, ptr @_ZTV1A, i32 0, i32 0, i32 3)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1A1hEz, i32 0, i64 31735, ptr getelementptr inbounds ({ [7 x ptr] }, ptr @_ZTV1A, i32 0, i32 0, i32 4)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1AD1Ev, i32 0, i64 2043, ptr getelementptr inbounds ({ [7 x ptr] }, ptr @_ZTV1A, i32 0, i32 0, i32 5)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1AD0Ev, i32 0, i64 63674, ptr getelementptr inbounds ({ [7 x ptr] }, ptr @_ZTV1A, i32 0, i32 0, i32 6))] }, align 8
+
+// CHECK: @_ZTVN10__cxxabiv117__class_type_infoE = external global [0 x ptr]
+
+// CHECK: @_ZTS1A = constant [3 x i8] c"1A\00", align 1
+
+// CHECK: @_ZTI1A = constant { ptr, ptr } { ptr ptrauth (ptr getelementptr inbounds (ptr, ptr @_ZTVN10__cxxabiv117__class_type_infoE, i64 2), i32 2), ptr @_ZTS1A }, align 8
+
+// CHECK: @_ZTVN10__cxxabiv121__vmi_class_type_infoE = external global [0 x ptr]
+
+// CHECK: @_ZTS1C = constant [3 x i8] c"1C\00", align 1
+
+// CHECK: @_ZTVN10__cxxabiv120__si_class_type_infoE = external global [0 x ptr]
+
+// CHECK: @_ZTS1B = linkonce_odr hidden constant [3 x i8] c"1B\00", align 1
+
+// CHECK: @_ZTI1B = linkonce_odr hidden constant { ptr, ptr, ptr } { ptr ptrauth (ptr getelementptr inbounds (ptr, ptr @_ZTVN10__cxxabiv120__si_class_type_infoE, i64 2), i32 2), ptr inttoptr (i64 add (i64 ptrtoint (ptr @_ZTS1B to i64), i64 -9223372036854775808) to ptr), ptr @_ZTI1A }, align 8
+
+// CHECK: @_ZTI1C = constant { ptr, ptr, i32, i32, ptr, i64 } { ptr ptrauth (ptr getelementptr inbounds (ptr, ptr @_ZTVN10__cxxabiv121__vmi_class_type_infoE, i64 2), i32 2), ptr @_ZTS1C, i32 0, i32 1, ptr @_ZTI1B, i64 -6141 }, align 8
+
+// CHECK: @_ZTS1D = constant [3 x i8] c"1D\00", align 1
+
+// CHECK: @_ZTI1D = constant { ptr, ptr, i32, i32, ptr, i64 } { ptr ptrauth (ptr getelementptr inbounds (ptr, ptr @_ZTVN10__cxxabiv121__vmi_class_type_infoE, i64 2), i32 2), ptr @_ZTS1D, i32 0, i32 1, ptr @_ZTI1B, i64 -6141 }, align 8
+
+// CHECK: @_ZTV1E = unnamed_addr constant { [7 x ptr] } { [7 x ptr] [ptr null, ptr @_ZTI1E,
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1E1fEv, i32 0, i64 28408, ptr getelementptr inbounds ({ [7 x ptr] }, ptr @_ZTV1E, i32 0, i32 0, i32 2)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1E1gEv, i32 0, i64 22926, ptr getelementptr inbounds ({ [7 x ptr] }, ptr @_ZTV1E, i32 0, i32 0, i32 3)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1E1hEz, i32 0, i64 9832, ptr getelementptr inbounds ({ [7 x ptr] }, ptr @_ZTV1E, i32 0, i32 0, i32 4)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1ED1Ev, i32 0, i64 5817, ptr getelementptr inbounds ({ [7 x ptr] }, ptr @_ZTV1E, i32 0, i32 0, i32 5)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1ED0Ev, i32 0, i64 26464, ptr getelementptr inbounds ({ [7 x ptr] }, ptr @_ZTV1E, i32 0, i32 0, i32 6))] }, align 8
+
+// CHECK: @_ZTS1E = constant [3 x i8] c"1E\00", align 1
+
+// CHECK: @_ZTI1E = constant { ptr, ptr } { ptr ptrauth (ptr getelementptr inbounds (ptr, ptr @_ZTVN10__cxxabiv117__class_type_infoE, i64 2), i32 2), ptr @_ZTS1E }, align 8
+
+// CHECK: @_ZTC1F0_1C = unnamed_addr constant { [5 x ptr], [11 x ptr] } { [5 x ptr] [ptr inttoptr (i64 16 to ptr), ptr null, ptr @_ZTI1C,
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1CD1Ev, i32 0, i64 31214, ptr getelementptr inbounds ({ [5 x ptr], [11 x ptr] }, ptr @_ZTC1F0_1C, i32 0, i32 0, i32 3)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1CD0Ev, i32 0, i64 8507, ptr getelementptr inbounds ({ [5 x ptr], [11 x ptr] }, ptr @_ZTC1F0_1C, i32 0, i32 0, i32 4))], [11 x ptr] [ptr inttoptr (i64 -16 to ptr), ptr null, ptr null, ptr null, ptr inttoptr (i64 -16 to ptr), ptr @_ZTI1C,
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1A1fEv, i32 0, i64 55636, ptr getelementptr inbounds ({ [5 x ptr], [11 x ptr] }, ptr @_ZTC1F0_1C, i32 0, i32 1, i32 6)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1A1gEv, i32 0, i64 19402, ptr getelementptr inbounds ({ [5 x ptr], [11 x ptr] }, ptr @_ZTC1F0_1C, i32 0, i32 1, i32 7)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1A1hEz, i32 0, i64 31735, ptr getelementptr inbounds ({ [5 x ptr], [11 x ptr] }, ptr @_ZTC1F0_1C, i32 0, i32 1, i32 8)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZTv0_n48_N1CD1Ev, i32 0, i64 2043, ptr getelementptr inbounds ({ [5 x ptr], [11 x ptr] }, ptr @_ZTC1F0_1C, i32 0, i32 1, i32 9)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZTv0_n48_N1CD0Ev, i32 0, i64 63674, ptr getelementptr inbounds ({ [5 x ptr], [11 x ptr] }, ptr @_ZTC1F0_1C, i32 0, i32 1, i32 10))] }, align 8
+
+// CHECK: @_ZTC1F8_1D = unnamed_addr constant { [7 x ptr], [11 x ptr] } { [7 x ptr] [ptr inttoptr (i64 8 to ptr), ptr null, ptr @_ZTI1D,
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1DD1Ev, i32 0, i64 59423, ptr getelementptr inbounds ({ [7 x ptr], [11 x ptr] }, ptr @_ZTC1F8_1D, i32 0, i32 0, i32 3)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1DD0Ev, i32 0, i64 25900, ptr getelementptr inbounds ({ [7 x ptr], [11 x ptr] }, ptr @_ZTC1F8_1D, i32 0, i32 0, i32 4)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1D1gEv, i32 0, i64 59070, ptr getelementptr inbounds ({ [7 x ptr], [11 x ptr] }, ptr @_ZTC1F8_1D, i32 0, i32 0, i32 5)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1D1hEz, i32 0, i64 65100, ptr getelementptr inbounds ({ [7 x ptr], [11 x ptr] }, ptr @_ZTC1F8_1D, i32 0, i32 0, i32 6))], [11 x ptr] [ptr inttoptr (i64 -8 to ptr), ptr inttoptr (i64 -8 to ptr), ptr inttoptr (i64 -8 to ptr), ptr null, ptr inttoptr (i64 -8 to ptr), ptr @_ZTI1D,
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1A1fEv, i32 0, i64 55636, ptr getelementptr inbounds ({ [7 x ptr], [11 x ptr] }, ptr @_ZTC1F8_1D, i32 0, i32 1, i32 6)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZTv0_n32_N1D1gEv, i32 0, i64 19402, ptr getelementptr inbounds ({ [7 x ptr], [11 x ptr] }, ptr @_ZTC1F8_1D, i32 0, i32 1, i32 7)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZTv0_n40_N1D1hEz, i32 0, i64 31735, ptr getelementptr inbounds ({ [7 x ptr], [11 x ptr] }, ptr @_ZTC1F8_1D, i32 0, i32 1, i32 8)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZTv0_n48_N1DD1Ev, i32 0, i64 2043, ptr getelementptr inbounds ({ [7 x ptr], [11 x ptr] }, ptr @_ZTC1F8_1D, i32 0, i32 1, i32 9)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZTv0_n48_N1DD0Ev, i32 0, i64 63674, ptr getelementptr inbounds ({ [7 x ptr], [11 x ptr] }, ptr @_ZTC1F8_1D, i32 0, i32 1, i32 10))] }, align 8
+
+// CHECK: @_ZTS1F = constant [3 x i8] c"1F\00", align 1
+
+// CHECK: @_ZTI1F = constant { ptr, ptr, i32, i32, ptr, i64, ptr, i64, ptr, i64 } { ptr ptrauth (ptr getelementptr inbounds (ptr, ptr @_ZTVN10__cxxabiv121__vmi_class_type_infoE, i64 2), i32 2), ptr @_ZTS1F, i32 3, i32 3, ptr @_ZTI1C, i64 2, ptr @_ZTI1D, i64 2050, ptr @_ZTI1E, i64 -8189 }, align 8
+
+// CHECK: @_ZTC1G0_1C = unnamed_addr constant { [5 x ptr], [11 x ptr] } { [5 x ptr] [ptr inttoptr (i64 24 to ptr), ptr null, ptr @_ZTI1C,
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1CD1Ev, i32 0, i64 31214, ptr getelementptr inbounds ({ [5 x ptr], [11 x ptr] }, ptr @_ZTC1G0_1C, i32 0, i32 0, i32 3)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1CD0Ev, i32 0, i64 8507, ptr getelementptr inbounds ({ [5 x ptr], [11 x ptr] }, ptr @_ZTC1G0_1C, i32 0, i32 0, i32 4))], [11 x ptr] [ptr inttoptr (i64 -24 to ptr), ptr null, ptr null, ptr null, ptr inttoptr (i64 -24 to ptr), ptr @_ZTI1C,
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1A1fEv, i32 0, i64 55636, ptr getelementptr inbounds ({ [5 x ptr], [11 x ptr] }, ptr @_ZTC1G0_1C, i32 0, i32 1, i32 6)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1A1gEv, i32 0, i64 19402, ptr getelementptr inbounds ({ [5 x ptr], [11 x ptr] }, ptr @_ZTC1G0_1C, i32 0, i32 1, i32 7)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1A1hEz, i32 0, i64 31735, ptr getelementptr inbounds ({ [5 x ptr], [11 x ptr] }, ptr @_ZTC1G0_1C, i32 0, i32 1, i32 8)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZTv0_n48_N1CD1Ev, i32 0, i64 2043, ptr getelementptr inbounds ({ [5 x ptr], [11 x ptr] }, ptr @_ZTC1G0_1C, i32 0, i32 1, i32 9)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZTv0_n48_N1CD0Ev, i32 0, i64 63674, ptr getelementptr inbounds ({ [5 x ptr], [11 x ptr] }, ptr @_ZTC1G0_1C, i32 0, i32 1, i32 10))] }, align 8
+
+// CHECK: @_ZTC1G8_1D = unnamed_addr constant { [7 x ptr], [11 x ptr] } { [7 x ptr] [ptr inttoptr (i64 16 to ptr), ptr null, ptr @_ZTI1D,
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1DD1Ev, i32 0, i64 59423, ptr getelementptr inbounds ({ [7 x ptr], [11 x ptr] }, ptr @_ZTC1G8_1D, i32 0, i32 0, i32 3)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1DD0Ev, i32 0, i64 25900, ptr getelementptr inbounds ({ [7 x ptr], [11 x ptr] }, ptr @_ZTC1G8_1D, i32 0, i32 0, i32 4)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1D1gEv, i32 0, i64 59070, ptr getelementptr inbounds ({ [7 x ptr], [11 x ptr] }, ptr @_ZTC1G8_1D, i32 0, i32 0, i32 5)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1D1hEz, i32 0, i64 65100, ptr getelementptr inbounds ({ [7 x ptr], [11 x ptr] }, ptr @_ZTC1G8_1D, i32 0, i32 0, i32 6))], [11 x ptr] [ptr inttoptr (i64 -16 to ptr), ptr inttoptr (i64 -16 to ptr), ptr inttoptr (i64 -16 to ptr), ptr null, ptr inttoptr (i64 -16 to ptr), ptr @_ZTI1D,
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1A1fEv, i32 0, i64 55636, ptr getelementptr inbounds ({ [7 x ptr], [11 x ptr] }, ptr @_ZTC1G8_1D, i32 0, i32 1, i32 6)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZTv0_n32_N1D1gEv, i32 0, i64 19402, ptr getelementptr inbounds ({ [7 x ptr], [11 x ptr] }, ptr @_ZTC1G8_1D, i32 0, i32 1, i32 7)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZTv0_n40_N1D1hEz, i32 0, i64 31735, ptr getelementptr inbounds ({ [7 x ptr], [11 x ptr] }, ptr @_ZTC1G8_1D, i32 0, i32 1, i32 8)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZTv0_n48_N1DD1Ev, i32 0, i64 2043, ptr getelementptr inbounds ({ [7 x ptr], [11 x ptr] }, ptr @_ZTC1G8_1D, i32 0, i32 1, i32 9)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZTv0_n48_N1DD0Ev, i32 0, i64 63674, ptr getelementptr inbounds ({ [7 x ptr], [11 x ptr] }, ptr @_ZTC1G8_1D, i32 0, i32 1, i32 10))] }, align 8
+
+// CHECK: @_ZTS1G = constant [3 x i8] c"1G\00", align 1
+
+// CHECK: @_ZTI1G = constant { ptr, ptr, i32, i32, ptr, i64, ptr, i64, ptr, i64 } { ptr ptrauth (ptr getelementptr inbounds (ptr, ptr @_ZTVN10__cxxabiv121__vmi_class_type_infoE, i64 2), i32 2), ptr @_ZTS1G, i32 3, i32 3, ptr @_ZTI1E, i64 -8189, ptr @_ZTI1C, i64 2, ptr @_ZTI1D, i64 2050 }, align 8
+
+// CHECK: @_ZTV1B = linkonce_odr unnamed_addr constant { [7 x ptr] } { [7 x ptr] [ptr null, ptr @_ZTI1B,
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1A1fEv, i32 0, i64 55636, ptr getelementptr inbounds ({ [7 x ptr] }, ptr @_ZTV1B, i32 0, i32 0, i32 2)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1A1gEv, i32 0, i64 19402, ptr getelementptr inbounds ({ [7 x ptr] }, ptr @_ZTV1B, i32 0, i32 0, i32 3)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1A1hEz, i32 0, i64 31735, ptr getelementptr inbounds ({ [7 x ptr] }, ptr @_ZTV1B, i32 0, i32 0, i32 4)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1BD1Ev, i32 0, i64 2043, ptr getelementptr inbounds ({ [7 x ptr] }, ptr @_ZTV1B, i32 0, i32 0, i32 5)),
+// CHECK-SAME: ptr ptrauth (ptr @_ZN1BD0Ev, i32 0, i64 63674, ptr getelementptr inbounds ({ [7 x ptr] }, ptr @_ZTV1B, i32 0, i32 0, i32 6))] }, align 8
+
+extern "C" int printf(const char *format, ...);
+
+class A {
+public:
+  A() {}
+  virtual int f();
+  virtual int g();
+  virtual int h(...);
+  virtual ~A() {}
+
+public:
+  bool necessary_field;
+};
+
+class B : public A {
+public:
+  B() : A() {}
+  virtual ~B() {}
+};
+
+class C : public virtual B {
+public:
+  C() : B() {}
+  ~C();
+};
+
+class D : public virtual B {
+public:
+  D() : B() {}
+  ~D();
+  virtual int g();
+  virtual int h(...);
+};
+
+class E {
+public:
+  virtual int f();
+  virtual int g();
+  virtual int h(...);
+  virtual ~E(){};
+};
+
+class F : public C, public D, public virtual E {
+  ~F();
+};
+
+class G : public virtual E, public C, public D {
+  ~G();
+};
+
+C::~C() {}
+D::~D() {}
+F::~F() {}
+G::~G() {}
+int E::f() { return 1; }
+int A::f() { return 0; }
+int E::g() { return 1; }
+int A::g() { return 0; }
+int D::g() { return 0; }
+
+int E::h(...) { return 1; }
+int A::h(...) { return 0; }
+int D::h(...) { return 0; }
+
+int main() {
+  A *ans = new C();
+  delete ans;
+
+  B *b = new D();
+  b->f();
+  b->g();
+  b->h(1,2,3);
+  b = new C();
+  b->f();
+  b->h(1,2,3);
+  b = new C();
+  b->f();
+  b->h(1,2,3);
+  b = new F();
+  b->f();
+  b->g();
+  b->h(1,2,3);
+
+  ans = new B();
+  delete ans;
+
+  ans = new F();
+  ans->f();
+  ans->g();
+  ans->h(1,2,3);
+  delete ans;
+
+  E *e = new F();
+  e->f();
+  e->g();
+  e->h(1,2,3);
+  delete e;
+  e = new G();
+  e->f();
+  e->g();
+  e->h(1,2,3);
+  delete e;
+}
+
+// And check the thunks
+// CHECK: ptr @_ZTv0_n48_N1CD1Ev(ptr noundef %this)
+// CHECK: [[TEMP:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[TEMP:%.*]], i32 2, i64 62866)
+
+// CHECK: void @_ZTv0_n48_N1CD0Ev(ptr noundef %this)
+// CHECK: [[TEMP:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[TEMP:%.*]], i32 2, i64 62866)
+
+// CHECK: ptr @_ZTv0_n48_N1DD1Ev(ptr noundef %this)
+// CHECK: [[TEMP:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[TEMP:%.*]], i32 2, i64 62866)
+
+// CHECK: void @_ZTv0_n48_N1DD0Ev(ptr noundef %this)
+// CHECK: [[TEMP:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[TEMP:%.*]], i32 2, i64 62866)
+
+// CHECK: void @_ZTv0_n48_N1FD0EvU11__vtptrauthILj0Lb0Lj62866E(ptr noundef %this)
+// CHECK: [[TEMP:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[TEMP:%.*]], i32 2, i64 62866)
+
+// CHECK: void @_ZTv0_n48_N1FD0Ev(ptr noundef %this)
+// CHECK: [[TEMP:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[TEMP:%.*]], i32 2, i64 12810)
+
+// CHECK: void @_ZTv0_n48_N1GD0Ev(ptr noundef %this)
+// CHECK: [[TEMP:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[TEMP:%.*]], i32 2, i64 12810)
+
+// CHECK: void @_ZTv0_n48_N1GD0EvU11__vtptrauthILj0Lb0Lj62866E(ptr noundef %this)
+// CHECK: [[TEMP:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[TEMP:%.*]], i32 2, i64 62866)

diff  --git a/clang/test/CodeGenCXX/ubsan-vtable-checks.cpp b/clang/test/CodeGenCXX/ubsan-vtable-checks.cpp
index c70316214cf66..72c59cb41c34b 100644
--- a/clang/test/CodeGenCXX/ubsan-vtable-checks.cpp
+++ b/clang/test/CodeGenCXX/ubsan-vtable-checks.cpp
@@ -2,6 +2,7 @@
 // RUN: %clang_cc1 -std=c++11 -triple x86_64-windows -emit-llvm -fsanitize=null %s -o - | FileCheck %s --check-prefix=CHECK-NULL --check-prefix=MSABI
 // RUN: %clang_cc1 -std=c++11 -triple x86_64-unknown-linux -emit-llvm -fsanitize=null,vptr %s -o - | FileCheck %s --check-prefix=CHECK-VPTR --check-prefix=ITANIUM
 // RUN: %clang_cc1 -std=c++11 -triple x86_64-windows -emit-llvm -fsanitize=null,vptr %s -o - | FileCheck %s --check-prefix=CHECK-VPTR --check-prefix=MSABI  --check-prefix=CHECK-VPTR-MS
+// RUN: %clang_cc1 -std=c++11 -triple arm64e-ios-13 -emit-llvm -fptrauth-intrinsics -fptrauth-calls -fptrauth-vtable-pointer-type-discrimination -fptrauth-vtable-pointer-address-discrimination -fsanitize=null,vptr %s -o - | FileCheck %s --check-prefix=CHECK-VPTR --check-prefix=ITANIUM --check-prefix=CHECK-PTRAUTH
 struct T {
   virtual ~T() {}
   virtual int v() { return 1; }
@@ -26,18 +27,49 @@ int get_v(T* t) {
   // CHECK-NULL: call void @__ubsan_handle_type_mismatch_v1_abort
   // Second, we check that vtable is actually loaded once the type check is done.
   // CHECK-NULL: load ptr, ptr {{.*}}
+
+  // CHECK-PTRAUTH: [[CAST_VTABLE:%.*]] = ptrtoint ptr %vtable to i64
+  // CHECK-PTRAUTH: [[STRIPPED_VTABLE:%.*]] = call i64 @llvm.ptrauth.strip(i64 [[CAST_VTABLE]], i32 0), !nosanitize !2
+  // CHECK-PTRAUTH: [[STRIPPED_PTR:%.*]] = inttoptr i64 [[STRIPPED_VTABLE]] to ptr
+  // CHECK-PTRAUTH: [[STRIPPED_INT:%.*]] = ptrtoint ptr [[STRIPPED_PTR]] to i64
+  // Make sure authed vtable pointer feeds into hashing
+  // CHECK-PTRAUTH: {{%.*}} = mul i64 [[STRIPPED_INT]], {{.*}}
+
+  // Verify that we authenticate for the actual vcall
+  // CHECK-PTRAUTH: [[BLENDED:%.*]] = call i64 @llvm.ptrauth.blend(i64 {{%.*}}, i64 17113)
+  // CHECK-PTRAUTH: [[CAST_VTABLE:%.*]] = ptrtoint ptr %vtable2 to i64
+  // CHECK-PTRAUTH: [[AUTHED_INT:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[CAST_VTABLE]], i32 2, i64 [[BLENDED]])
+  // CHECK-PTRAUTH: [[AUTHED_PTR:%.*]] = inttoptr i64 [[AUTHED_INT]] to ptr
+  // CHECK-PTRAUTH: {{%.*}} = getelementptr inbounds ptr, ptr [[AUTHED_PTR]], i64 2
   return t->v();
 }
 
 // ITANIUM: define{{.*}} void @_Z9delete_itP1T
 // MSABI: define dso_local void @"?delete_it
 void delete_it(T *t) {
-  // First, we check that vtable is not loaded before a type check.
   // CHECK-VPTR-NOT: load {{.*}} (ptr{{.*}})**, {{.*}} (ptr{{.*}})***
   // CHECK-VPTR: br i1 {{.*}} label %{{.*}}
-  // CHECK-VPTR: call void @__ubsan_handle_dynamic_type_cache_miss_abort
+  // CHECK-VPTR: call void @__ubsan_handle_type_mismatch_v1_abort
   // Second, we check that vtable is actually loaded once the type check is done.
   // CHECK-VPTR: load ptr, ptr {{.*}}
+
+  // First, we check that vtable is not loaded before a type check.
+  // CHECK-PTRAUTH: ptrtoint ptr {{%.*}} to i64
+  // CHECK-PTRAUTH: [[CAST_VTABLE:%.*]] = ptrtoint ptr [[VTABLE:%.*]] to i64
+  // CHECK-PTRAUTH: [[STRIPPED_VTABLE:%.*]] = call i64 @llvm.ptrauth.strip(i64 [[CAST_VTABLE]], i32 0)
+  // CHECK-PTRAUTH: [[STRIPPED_PTR:%.*]] = inttoptr i64 [[STRIPPED_VTABLE]] to ptr
+  // CHECK-PTRAUTH: [[STRIPPED_INT:%.*]] = ptrtoint ptr [[STRIPPED_PTR]] to i64
+  // CHECK-PTRAUTH: {{%.*}} = mul i64 [[STRIPPED_INT]], {{.*}}
+  // CHECK-PTRAUTH: call void @__ubsan_handle_dynamic_type_cache_miss_abort(
+  // Second, we check that vtable is actually loaded once the type check is done.
+  // ptrauth for the virtual function load
+  // CHECK-PTRAUTH: [[VTABLE2:%.*]] = load ptr, ptr {{.*}}
+  // CHECK-PTRAUTH: [[BLENDED:%.*]] = call i64 @llvm.ptrauth.blend(i64 %{{.*}}, i64 17113)
+  // CHECK-PTRAUTH: [[CAST_VTABLE:%.*]] = ptrtoint ptr [[VTABLE2]] to i64
+  // CHECK-PTRAUTH: [[AUTHED_INT:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[CAST_VTABLE]], i32 2, i64 [[BLENDED]])
+  // CHECK-PTRAUTH: [[AUTHED_PTR:%.*]] = inttoptr i64 [[AUTHED_INT]] to ptr
+  // CHECK-PTRAUTH: getelementptr inbounds ptr, ptr
+  // CHECK-PTRAUTH {{%.*}} = getelementptr inbounds ptr, ptr [[AUTHED_PTR]], i64 1
   delete t;
 }
 
@@ -47,7 +79,19 @@ U* dyncast(T *t) {
   // First, we check that dynamic_cast is not called before a type check.
   // CHECK-VPTR-NOT: call ptr @__{{dynamic_cast|RTDynamicCast}}
   // CHECK-VPTR: br i1 {{.*}} label %{{.*}}
+  // CHECK-PTRAUTH: [[V0:%.*]] = ptrtoint ptr {{%.*}} to i64
+  // CHECK-PTRAUTH: [[BLENDED:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[V0]], i64 17113)
+  // CHECK-PTRAUTH: [[CAST_VTABLE:%.*]] = ptrtoint ptr {{%.*}} to i64
+  // CHECK-PTRAUTH: [[STRIPPED_VTABLE:%.*]] = call i64 @llvm.ptrauth.strip(i64 [[CAST_VTABLE]], i32 0)
+  // CHECK-PTRAUTH: [[STRIPPED_PTR:%.*]] = inttoptr i64 [[STRIPPED_VTABLE]] to ptr
+  // CHECK-PTRAUTH: [[STRIPPED_INT:%.*]] = ptrtoint ptr [[STRIPPED_PTR]] to i64
+  // CHECK-PTRAUTH: {{%.*}} = mul i64 [[STRIPPED_INT]], {{.*}}
   // CHECK-VPTR: call void @__ubsan_handle_dynamic_type_cache_miss_abort
+  // CHECK-PTRAUTH: [[BLENDED:%.*]] = call i64 @llvm.ptrauth.blend(i64 {{%.*}}, i64 17113)
+  // CHECK-PTRAUTH: [[CAST_VTABLE:%.*]] = ptrtoint ptr %vtable1 to i64
+  // CHECK-PTRAUTH: [[AUTHED_INT:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[CAST_VTABLE]], i32 2, i64 [[BLENDED]])
+  // CHECK-PTRAUTH: [[AUTHED_PTR:%.*]] = inttoptr i64 [[AUTHED_INT]] to ptr
+  // CHECK-PTRAUTH: {{%.*}} = load volatile i8, ptr [[AUTHED_PTR]], align 8
   // Second, we check that dynamic_cast is actually called once the type check is done.
   // CHECK-VPTR: call ptr @__{{dynamic_cast|RTDynamicCast}}
   return dynamic_cast<U*>(t);

diff  --git a/clang/test/Misc/pragma-attribute-supported-attributes-list.test b/clang/test/Misc/pragma-attribute-supported-attributes-list.test
index 99732694f72a5..28df04c5e33ef 100644
--- a/clang/test/Misc/pragma-attribute-supported-attributes-list.test
+++ b/clang/test/Misc/pragma-attribute-supported-attributes-list.test
@@ -201,6 +201,7 @@
 // CHECK-NEXT: Uninitialized (SubjectMatchRule_variable_is_local)
 // CHECK-NEXT: UnsafeBufferUsage (SubjectMatchRule_function)
 // CHECK-NEXT: UseHandle (SubjectMatchRule_variable_is_parameter)
+// CHECK-NEXT: VTablePointerAuthentication (SubjectMatchRule_record)
 // CHECK-NEXT: VecReturn (SubjectMatchRule_record)
 // CHECK-NEXT: VecTypeHint (SubjectMatchRule_function)
 // CHECK-NEXT: WarnUnused (SubjectMatchRule_record)

diff  --git a/clang/test/SemaCXX/ptrauth-incomplete-virtual-member-function-return-arg-type.cpp b/clang/test/SemaCXX/ptrauth-incomplete-virtual-member-function-return-arg-type.cpp
new file mode 100644
index 0000000000000..41bbba0e832fd
--- /dev/null
+++ b/clang/test/SemaCXX/ptrauth-incomplete-virtual-member-function-return-arg-type.cpp
@@ -0,0 +1,50 @@
+// RUN: %clang_cc1 -triple arm64-apple-ios -std=c++17 -Wno-vla -fsyntax-only -verify -fptrauth-intrinsics -fptrauth-calls %s
+
+struct Incomplete0; // expected-note 3 {{forward declaration of 'Incomplete0'}}
+
+template <class T>
+struct Incomplete1; // expected-note {{template is declared here}}
+
+struct Complete0 {
+};
+
+template <class T>
+struct Complete1 {
+};
+
+struct S {
+  virtual int foo();
+  virtual Incomplete0 virtual0(); // expected-note 2 {{'Incomplete0' is incomplete}}
+  virtual void virtual1(Incomplete1<int>); // expected-note {{'Incomplete1<int>' is incomplete}}
+  virtual Complete0 virtual2();
+  virtual Complete1<int> virtual3();
+  Incomplete0 nonvirtual0();
+  template <class T>
+  void m0() {
+    (void)&S::virtual0; // expected-error {{incomplete type 'Incomplete0'}} expected-note {{cannot take an address of a virtual}}
+  }
+};
+
+template <bool T>
+struct S2 {
+  virtual Incomplete0 virtual0() noexcept(T); // expected-note {{'Incomplete0' is incomplete}}
+
+  void m0() {
+    (void)&S2<T>::virtual0;
+  }
+
+  void m1() {
+    (void)&S2<T>::virtual0; // expected-error {{incomplete type 'Incomplete0'}} expected-note {{cannot take an address of a virtual}}
+  }
+};
+
+void test_incomplete_virtual_member_function_return_arg_type() {
+  (void)&S::virtual0; // expected-error {{incomplete type 'Incomplete0}} expected-note {{cannot take an address of a virtual member function}}
+  (void)&S::virtual1; // expected-error {{implicit instantiation of undefined template 'Incomplete1<int>'}} expected-note {{cannot take an address of a virtual member function}}
+  (void)&S::virtual2;
+  (void)&S::virtual3;
+  (void)&S::nonvirtual0;
+  int s = sizeof(&S::virtual0);
+  S2<true>().m1(); // expected-note {{in instantiation of}}
+}
+

diff  --git a/clang/test/SemaCXX/vtable_pointer_authentication_attribute.cpp b/clang/test/SemaCXX/vtable_pointer_authentication_attribute.cpp
new file mode 100644
index 0000000000000..3a3386196fbfa
--- /dev/null
+++ b/clang/test/SemaCXX/vtable_pointer_authentication_attribute.cpp
@@ -0,0 +1,225 @@
+// RUN: %clang_cc1 -fsyntax-only -triple arm64-apple-ios -verify -fptrauth-calls -std=c++2a %s
+
+namespace basic {
+
+#define authenticated(a, b, c...) [[clang::ptrauth_vtable_pointer(a, b, c)]]
+
+// Basic sanity tests
+#define TEST_AUTH(name, auth...)                        \
+  struct [[clang::ptrauth_vtable_pointer(auth)]] name { \
+    virtual ~name() {}                                  \
+  }
+
+TEST_AUTH(NoParams);
+// expected-error at -1{{'ptrauth_vtable_pointer' attribute takes at least 3 arguments}}
+TEST_AUTH(NoAuth, no_authentication, default_address_discrimination, default_extra_discrimination);
+TEST_AUTH(InvalidKey, wat, default_address_discrimination, default_extra_discrimination);
+// expected-error at -1{{invalid authentication key 'wat'}}
+TEST_AUTH(InvalidAddressDiscrimination, no_authentication, wat, default_extra_discrimination);
+// expected-error at -1{{invalid address discrimination mode 'wat'}}
+TEST_AUTH(InvalidExtraDiscrimination, no_authentication, default_address_discrimination, wat);
+// expected-error at -1{{invalid extra discrimination selection 'wat'}}
+TEST_AUTH(InvalidNoCustomDiscrimination, no_authentication, default_address_discrimination, custom_discrimination);
+// expected-error at -1{{missing custom discrimination}}
+TEST_AUTH(InvalidCustomDiscrimination, no_authentication, default_address_discrimination, custom_discrimination, wat);
+// expected-error at -1{{invalid custom discrimination}}
+TEST_AUTH(Default, default_key, default_address_discrimination, default_extra_discrimination);
+TEST_AUTH(InvalidDefaultExtra, default_key, default_address_discrimination, default_extra_discrimination, 1);
+// expected-error at -1{{'ptrauth_vtable_pointer' attribute takes no more than 3 arguments}}
+TEST_AUTH(ProcessDependentKey, process_dependent, default_address_discrimination, default_extra_discrimination);
+TEST_AUTH(ProcessIndependentKey, process_independent, default_address_discrimination, default_extra_discrimination);
+TEST_AUTH(DefaultAddressDiscrimination, process_independent, default_address_discrimination, default_extra_discrimination);
+TEST_AUTH(NoAddressDiscrimination, process_independent, no_address_discrimination, default_extra_discrimination);
+TEST_AUTH(AddressDiscrimination, process_independent, address_discrimination, default_extra_discrimination);
+TEST_AUTH(DefaultExtraDiscrimination, process_independent, default_address_discrimination, default_extra_discrimination);
+TEST_AUTH(NoExtraDiscrimination, process_independent, default_address_discrimination, no_extra_discrimination);
+TEST_AUTH(TypeExtraDiscrimination, process_independent, default_address_discrimination, type_discrimination);
+TEST_AUTH(InvalidCustomExtraDiscrimination, process_independent, default_address_discrimination, custom_discrimination);
+// expected-error at -1{{missing custom discrimination}}
+TEST_AUTH(ValidCustomExtraDiscrimination, process_independent, default_address_discrimination, custom_discrimination, 1);
+
+// Basic valid authentication configuration
+#define generic_authenticated \
+  authenticated(process_independent, address_discrimination, type_discrimination)
+
+struct generic_authenticated ForwardDecl;
+
+struct generic_authenticated generic_authenticated InvalidDuplicateAttribute {
+  // expected-error at -1{{multiple vtable pointer authentication policies on 'InvalidDuplicateAttribute'}}
+  virtual ~InvalidDuplicateAttribute(){};
+};
+struct generic_authenticated ValidPolymorphic {
+  virtual ~ValidPolymorphic(){};
+};
+struct generic_authenticated InvalidMonomorphic { // expected-error{{cannot set vtable pointer authentication on monomorphic type 'InvalidMonomorphic'}}
+};
+struct ValidMonomorphic {
+};
+
+struct ValidSubclass : ValidPolymorphic {};
+struct generic_authenticated InvalidSubclass : ValidPolymorphic {}; // expected-error{{cannot set vtable pointer authentication on 'InvalidSubclass' which is a subclass of polymorphic type 'ValidPolymorphic'}}
+
+// Awful template time
+template <typename T>
+struct generic_authenticated ExplicitlyAuthedMonomorphicTemplateClass : T {};
+// expected-error at -1{{cannot set vtable pointer authentication on 'ExplicitlyAuthedMonomorphicTemplateClass<basic::ValidPolymorphic>' which is a subclass of polymorphic type 'ValidPolymorphic'}}
+// expected-error at -2{{cannot set vtable pointer authentication on monomorphic type 'ExplicitlyAuthedMonomorphicTemplateClass<basic::ValidMonomorphic>'}}
+template <typename T>
+struct generic_authenticated ExplicitlyAuthedPolymorphicTemplateClass : T { // expected-error{{cannot set vtable pointer authentication on 'ExplicitlyAuthedPolymorphicTemplateClass<basic::ValidPolymorphic>' which is a subclass of polymorphic type 'ValidPolymorphic'}}
+  virtual ~ExplicitlyAuthedPolymorphicTemplateClass(){};
+};
+template <typename T>
+struct UnauthedMonomorphicTemplateClass : T {};
+template <typename T>
+struct UnauthedPolymorphicTemplateClass : T {
+  virtual ~UnauthedPolymorphicTemplateClass(){};
+};
+
+ExplicitlyAuthedMonomorphicTemplateClass<ValidPolymorphic> test1;
+// expected-note at -1{{in instantiation of template class 'basic::ExplicitlyAuthedMonomorphicTemplateClass<basic::ValidPolymorphic>' requested here}}
+ExplicitlyAuthedMonomorphicTemplateClass<ValidMonomorphic> test2;
+// expected-note at -1{{in instantiation of template class 'basic::ExplicitlyAuthedMonomorphicTemplateClass<basic::ValidMonomorphic>' requested here}}
+ExplicitlyAuthedPolymorphicTemplateClass<ValidPolymorphic> test3;
+// expected-note at -1{{in instantiation of template class 'basic::ExplicitlyAuthedPolymorphicTemplateClass<basic::ValidPolymorphic>' requested here}}
+ExplicitlyAuthedPolymorphicTemplateClass<ValidMonomorphic> test4;
+
+UnauthedMonomorphicTemplateClass<ValidPolymorphic> test5;
+UnauthedMonomorphicTemplateClass<ValidMonomorphic> test6;
+UnauthedPolymorphicTemplateClass<ValidPolymorphic> test7;
+UnauthedPolymorphicTemplateClass<ValidMonomorphic> test8;
+
+// Just use a 
diff erent policy from the generic macro to verify we won't complain
+// about the insanity
+struct authenticated(process_independent, no_address_discrimination, type_discrimination) SecondAuthenticatedPolymorphic {
+  virtual ~SecondAuthenticatedPolymorphic(){};
+};
+struct UnauthenticatedPolymorphic {
+  virtual ~UnauthenticatedPolymorphic(){};
+};
+
+struct MultipleParents1 : ValidPolymorphic, SecondAuthenticatedPolymorphic, UnauthenticatedPolymorphic {};
+struct MultipleParents2 : UnauthenticatedPolymorphic, ValidPolymorphic, SecondAuthenticatedPolymorphic {};
+struct generic_authenticated InvalidMultipleParents : UnauthenticatedPolymorphic, ValidPolymorphic, SecondAuthenticatedPolymorphic {};
+// expected-error at -1{{cannot set vtable pointer authentication on 'InvalidMultipleParents' which is a subclass of polymorphic type 'UnauthenticatedPolymorphic'}}
+
+template <typename T>
+struct generic_authenticated ExplicitlyAuthedPolymorphicTemplateClassNoBase {
+  virtual ~ExplicitlyAuthedPolymorphicTemplateClassNoBase();
+};
+
+ExplicitlyAuthedPolymorphicTemplateClassNoBase<int> v;
+
+struct ValidSubclassOfTemplate : ExplicitlyAuthedPolymorphicTemplateClassNoBase<int> {
+};
+
+struct generic_authenticated InvalidSubclassOfTemplate : ExplicitlyAuthedPolymorphicTemplateClassNoBase<int> {
+  // expected-error at -1{{cannot set vtable pointer authentication on 'InvalidSubclassOfTemplate' which is a subclass of polymorphic type 'ExplicitlyAuthedPolymorphicTemplateClassNoBase<int>'}}
+};
+
+template <typename T>
+struct generic_authenticated ExplicitlyAuthedMonomorphicTemplateClassNoBase {
+  // expected-error at -1{{cannot set vtable pointer authentication on monomorphic type 'ExplicitlyAuthedMonomorphicTemplateClassNoBase'}}
+  // expected-error at -2{{cannot set vtable pointer authentication on monomorphic type 'ExplicitlyAuthedMonomorphicTemplateClassNoBase<int>'}}
+};
+
+ExplicitlyAuthedMonomorphicTemplateClassNoBase<int> X;
+// expected-note at -1{{in instantiation of template class 'basic::ExplicitlyAuthedMonomorphicTemplateClassNoBase<int>' requested here}}
+
+template <typename T>
+struct generic_authenticated ExplicitlyAuthedTemplateClassValidBase : ValidMonomorphic {
+  // expected-error at -1{{cannot set vtable pointer authentication on monomorphic type 'ExplicitlyAuthedTemplateClassValidBase'}}
+  // expected-error at -2{{cannot set vtable pointer authentication on monomorphic type 'ExplicitlyAuthedTemplateClassValidBase<int>'}}
+};
+
+ExplicitlyAuthedTemplateClassValidBase<int> Y;
+// expected-note at -1{{in instantiation of template class 'basic::ExplicitlyAuthedTemplateClassValidBase<int>' requested here}}
+
+template <typename T>
+struct generic_authenticated ExplicitlyAuthedTemplateClassInvalidBase : ValidPolymorphic {
+  // expected-error at -1{{cannot set vtable pointer authentication on 'ExplicitlyAuthedTemplateClassInvalidBase' which is a subclass of polymorphic type 'ValidPolymorphic'}}
+  // expected-error at -2{{cannot set vtable pointer authentication on 'ExplicitlyAuthedTemplateClassInvalidBase<int>' which is a subclass of polymorphic type 'ValidPolymorphic'}}
+};
+
+ExplicitlyAuthedTemplateClassInvalidBase<int> Z;
+// expected-note at -1{{in instantiation of template class 'basic::ExplicitlyAuthedTemplateClassInvalidBase<int>' requested here}}
+
+template <class test1, class test2>
+class generic_authenticated TestPolymorphicTemplateSpecialization;
+
+template <>
+class TestPolymorphicTemplateSpecialization<double, float> {
+  MissingDecl *zl;
+  // expected-error at -1 {{unknown type name 'MissingDecl'}}
+public:
+  virtual ~TestPolymorphicTemplateSpecialization();
+};
+template <class test1>
+class generic_authenticated TestPolymorphicTemplateSpecialization<test1, double>
+// expected-error at -1 {{cannot set vtable pointer authentication on monomorphic type 'TestPolymorphicTemplateSpecialization<test1, double>'}}
+// expected-error at -2 {{cannot set vtable pointer authentication on monomorphic type 'TestPolymorphicTemplateSpecialization<double, double>'}}
+{
+};
+
+TestPolymorphicTemplateSpecialization<double, float> b;
+TestPolymorphicTemplateSpecialization<double, double> b2;
+// expected-note at -1 {{in instantiation of template class 'basic::TestPolymorphicTemplateSpecialization<double, double>' requested here}}
+
+template <typename A> class generic_authenticated TestMonomorphic {};
+// expected-error at -1 {{cannot set vtable pointer authentication on monomorphic type 'TestMonomorphic'}}
+// expected-error at -2 {{cannot set vtable pointer authentication on monomorphic type 'TestMonomorphic<double>'}}
+
+template <> class generic_authenticated TestMonomorphic<int> {
+public:
+  virtual ~TestMonomorphic();
+};
+
+struct TestMonomorphicSubclass : TestMonomorphic<int> {
+};
+template <typename T> struct generic_authenticated TestMonomorphicSubclass2 : TestMonomorphic<T> {
+  // expected-error at -1 {{cannot set vtable pointer authentication on 'TestMonomorphicSubclass2<int>' which is a subclass of polymorphic type 'TestMonomorphic<int>'}}
+  // expected-error at -2 {{cannot set vtable pointer authentication on monomorphic type 'TestMonomorphicSubclass2<double>'}}
+  // expected-note at -3 {{in instantiation of template class 'basic::TestMonomorphic<double>' requested here}}
+};
+
+TestMonomorphicSubclass tms_1;
+TestMonomorphicSubclass2<int> tms2_1;
+// expected-note at -1 {{in instantiation of template class 'basic::TestMonomorphicSubclass2<int>' requested here}}
+TestMonomorphicSubclass2<double> tms2_2;
+// expected-note at -1 {{in instantiation of template class 'basic::TestMonomorphicSubclass2<double>' requested here}}
+// expected-note at -2 {{in instantiation of template class 'basic::TestMonomorphicSubclass2<double>' requested here}}
+
+template <typename T>
+class generic_authenticated dependent_type {
+  // expected-error at -1 {{cannot set vtable pointer authentication on monomorphic type 'dependent_type'}}
+  static constexpr unsigned small_object_size = 1;
+  char _model[small_object_size];
+};
+
+template <typename... T>
+class generic_authenticated dependent_type2 : public T... {
+  // expected-error at -1 {{cannot set vtable pointer authentication on 'dependent_type2<basic::Foo>' which is a subclass of polymorphic type 'Foo'}}
+  static constexpr unsigned small_object_size = 1;
+  char _model[small_object_size];
+};
+
+struct Foo {
+  virtual ~Foo();
+};
+
+dependent_type2<Foo> thing;
+// expected-note at -1 {{in instantiation of template class 'basic::dependent_type2<basic::Foo>' requested here}}
+
+template <class>
+class task;
+template <unsigned align> struct alignedthing {
+  char buffer[align];
+};
+
+template <class R, class... Args>
+class generic_authenticated task<R(Args...)> {
+  // expected-error at -1 {{cannot set vtable pointer authentication on monomorphic type 'task<R (Args...)>'}}
+  static constexpr __SIZE_TYPE__ small_object_size = 256;
+  alignedthing<small_object_size> _model;
+};
+
+} // namespace basic

diff  --git a/clang/utils/TableGen/ClangAttrEmitter.cpp b/clang/utils/TableGen/ClangAttrEmitter.cpp
index 307334a65b1fe..7bf6091e63d18 100644
--- a/clang/utils/TableGen/ClangAttrEmitter.cpp
+++ b/clang/utils/TableGen/ClangAttrEmitter.cpp
@@ -2578,6 +2578,32 @@ static void emitClangAttrIdentifierArgList(RecordKeeper &Records, raw_ostream &O
   OS << "#endif // CLANG_ATTR_IDENTIFIER_ARG_LIST\n\n";
 }
 
+// Emits the indexed-argument-is-identifier property for attributes.
+static void emitClangAttrStrictIdentifierArgAtIndexList(RecordKeeper &Records,
+                                                        raw_ostream &OS) {
+  OS << "#if defined(CLANG_ATTR_STRICT_IDENTIFIER_ARG_AT_INDEX_LIST)\n";
+  std::vector<Record *> Attrs = Records.getAllDerivedDefinitions("Attr");
+
+  for (const auto *Attr : Attrs) {
+    if (!Attr->getValueAsBit("StrictEnumParameters"))
+      continue;
+    // Determine whether the first argument is an identifier.
+    std::vector<Record *> Args = Attr->getValueAsListOfDefs("Args");
+    uint64_t enumAtIndex = 0;
+    for (size_t i = 0; i < Args.size(); i++) {
+      enumAtIndex |= ((uint64_t)isIdentifierArgument(Args[0])) << i;
+    }
+    if (!enumAtIndex)
+      continue;
+
+    // All these spellings take an identifier argument.
+    forEachUniqueSpelling(*Attr, [&](const FlattenedSpelling &S) {
+      OS << ".Case(\"" << S.name() << "\", " << enumAtIndex << "ull)\n";
+    });
+  }
+  OS << "#endif // CLANG_ATTR_STRICT_IDENTIFIER_ARG_AT_INDEX_LIST\n\n";
+}
+
 static bool keywordThisIsaIdentifierInArgument(const Record *Arg) {
   return !Arg->getSuperClasses().empty() &&
          llvm::StringSwitch<bool>(
@@ -4959,6 +4985,7 @@ void EmitClangAttrParserStringSwitches(RecordKeeper &Records, raw_ostream &OS) {
   emitClangAttrTypeArgList(Records, OS);
   emitClangAttrLateParsedList(Records, OS);
   emitClangAttrLateParsedExperimentalList(Records, OS);
+  emitClangAttrStrictIdentifierArgAtIndexList(Records, OS);
 }
 
 void EmitClangAttrSubjectMatchRulesParserStringSwitches(RecordKeeper &Records,


        


More information about the cfe-commits mailing list