[clang] [clang] Add builtin_get_vtable_pointer and virtual_member_address (PR #135469)

Oliver Hunt via cfe-commits cfe-commits at lists.llvm.org
Sun Apr 13 00:44:58 PDT 2025


https://github.com/ojhunt updated https://github.com/llvm/llvm-project/pull/135469

>From 3c20ae33d51c4e8e7a80e7f1477de02c02dec513 Mon Sep 17 00:00:00 2001
From: Ahmed Bougacha <ahmed at bougacha.org>
Date: Mon, 27 Sep 2021 08:00:00 -0700
Subject: [PATCH] [clang] Add builtin_get_vtable_pointer and
 virtual_member_address

These are a pair of builtins to support particularly weird edge
case operations while correctly handling the non-trivial implicit
pointer authentication schemas applied to polymorphic members.
---
 clang/docs/LanguageExtensions.rst             |  66 ++++
 clang/docs/ReleaseNotes.rst                   |   3 +
 clang/include/clang/Basic/Builtins.td         |  12 +
 .../clang/Basic/DiagnosticSemaKinds.td        |  19 +
 clang/lib/CodeGen/CGBuiltin.cpp               |  35 ++
 clang/lib/Sema/SemaChecking.cpp               |  92 +++++
 .../CodeGenCXX/builtin-get-vtable-pointer.cpp | 350 ++++++++++++++++++
 .../builtin_virtual_member_address.cpp        |  68 ++++
 .../SemaCXX/builtin-get-vtable-pointer.cpp    | 124 +++++++
 .../builtin_virtual_member_address.cpp        |  60 +++
 10 files changed, 829 insertions(+)
 create mode 100644 clang/test/CodeGenCXX/builtin-get-vtable-pointer.cpp
 create mode 100644 clang/test/CodeGenCXX/builtin_virtual_member_address.cpp
 create mode 100644 clang/test/SemaCXX/builtin-get-vtable-pointer.cpp
 create mode 100644 clang/test/SemaCXX/builtin_virtual_member_address.cpp

diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst
index 3b8a9cac6587a..62e3002407631 100644
--- a/clang/docs/LanguageExtensions.rst
+++ b/clang/docs/LanguageExtensions.rst
@@ -3031,6 +3031,72 @@ following way:
 
 Query for this feature with ``__has_builtin(__builtin_offsetof)``.
 
+``__builtin_get_vtable_pointer``
+--------------------------------
+
+``__builtin_get_vtable_pointer`` loads and authenticates the primary vtable
+pointer from an instance of a polymorphic C++ class.
+
+**Syntax**:
+
+.. code-block:: c++
+
+  __builtin_get_vtable_pointer(PolymorphicClass*)
+
+**Example of Use**:
+
+.. code-block:: c++
+
+  struct PolymorphicClass {
+    virtual ~PolymorphicClass();
+  };
+
+  PolymorphicClass anInstance;
+  const void* vtablePointer = __builtin_get_vtable_pointer(&anInstance);
+
+**Description**:
+
+The ``__builtin_get_vtable_pointer`` builtin loads the primary vtable
+pointer from a polymorphic C++ type. If the target platform authenticates
+vtable pointers, this builtin will perform the authentication and produce
+the underlying raw pointer. The object being queried must be polymorphic,
+and so must also be a complete type.
+
+Query for this feature with ``__has_builtin(__builtin_get_vtable_pointer)``.
+
+``__builtin_virtual_member_address``
+------------------------------------
+
+``__builtin_virtual_member_address`` loads the function pointer that would
+be called by a virtual method.
+
+**Syntax**:
+
+.. code-block:: c++
+
+  __builtin_virtual_member_address(PolymorphicClass&, Member function pointer)
+
+**Exampe of Use**
+
+.. code-block:: c++
+
+  struct PolymorphicClass {
+    virtual ~PolymorphicClass();
+    virtual void SomeMethod();
+  };
+
+  PolymorphicClass anInstance;
+  const void* MethodAddress =
+    __builtin_virtual_member_address(anInstance, &PolymorphicClass::SomeMethod);
+
+**Description**
+
+This builtin returns the dynamic target for virtual dispatch of the requested virtual
+method. If the target platform supports pointer authentication, it emits the code to
+authenticates the vtable pointer and the virtual function pointer being loaded. The returned
+value is an untyped pointer as it cannot reasonably be proved that any given use of the returned
+function pointer is correct, so we want to discourage any attempt to do such.
+
 ``__builtin_call_with_static_chain``
 ------------------------------------
 
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 9c45965dc4d82..fde3a865e6128 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -191,6 +191,9 @@ Non-comprehensive list of changes in this release
 - Support parsing the `cc` operand modifier and alias it to the `c` modifier (#GH127719).
 - Added `__builtin_elementwise_exp10`.
 - For AMDPGU targets, added `__builtin_v_cvt_off_f32_i4` that maps to the `v_cvt_off_f32_i4` instruction.
+- Added `__builtin_get_vtable_pointer` to directly load the primary vtable pointer from a
+  polymorphic object and `__builtin_virtual_member_address` to load the real function pointer of a
+  virtual method.
 
 New Compiler Flags
 ------------------
diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td
index 868e5b92acdc9..a55f411343c2d 100644
--- a/clang/include/clang/Basic/Builtins.td
+++ b/clang/include/clang/Basic/Builtins.td
@@ -970,6 +970,18 @@ def IsWithinLifetime : LangBuiltin<"CXX_LANG"> {
   let Prototype = "bool(void*)";
 }
 
+def GetVtablePointer : LangBuiltin<"CXX_LANG"> {
+  let Spellings = ["__builtin_get_vtable_pointer"];
+  let Attributes = [CustomTypeChecking, NoThrow, Const];
+  let Prototype = "void*(void*)";
+}
+
+def VirtualMemberAddress : Builtin {
+  let Spellings = ["__builtin_virtual_member_address"];
+  let Attributes = [CustomTypeChecking, NoThrow, Const];
+  let Prototype = "void*(void*,void*)";
+}
+
 // GCC exception builtins
 def EHReturn : Builtin {
   let Spellings = ["__builtin_eh_return"];
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 180ca39bc07e9..e6b0584df35b4 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -1013,6 +1013,17 @@ def err_ptrauth_indirect_goto_addrlabel_arithmetic : Error<
   "%select{subtraction|addition}0 of address-of-label expressions is not "
   "supported with ptrauth indirect gotos">;
 
+def err_virtual_member_lhs_cxxrec
+    : Error<"first argument to __builtin_virtual_member_address must have C++ "
+            "class type">;
+def err_virtual_member_addrof
+    : Error<
+          "second argument to __builtin_virtual_member_address must be the "
+          "address of a virtual C++ member function: for example '&Foo::func'">;
+def err_virtual_member_inherit
+    : Error<"first argument to __builtin_virtual_member_address must have a "
+            "type deriving from class where second argument was defined">;
+
 /// main()
 // static main() is not an error in C, just in C++.
 def warn_static_main : Warning<"'main' should not be declared static">,
@@ -12547,6 +12558,14 @@ def err_bit_cast_non_trivially_copyable : Error<
 def err_bit_cast_type_size_mismatch : Error<
   "size of '__builtin_bit_cast' source type %0 does not match destination type %1 (%2 vs %3 bytes)">;
 
+def err_get_vtable_pointer_incorrect_type
+    : Error<"__builtin_get_vtable_pointer requires an argument of%select{| "
+            "polymorphic}0 class pointer type"
+            ", but %1 %select{was provided|has no virtual methods}0">;
+def err_get_vtable_pointer_requires_complete_type
+    : Error<"__builtin_get_vtable_pointer requires an argument with a complete "
+            "type, but %0 is incomplete">;
+
 // SYCL-specific diagnostics
 def warn_sycl_kernel_num_of_template_params : Warning<
   "'sycl_kernel' attribute only applies to a function template with at least"
diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp
index fe55dfffc1cbe..9402dfa40b1ba 100644
--- a/clang/lib/CodeGen/CGBuiltin.cpp
+++ b/clang/lib/CodeGen/CGBuiltin.cpp
@@ -17,6 +17,7 @@
 #include "CGDebugInfo.h"
 #include "CGObjCRuntime.h"
 #include "CGOpenCLRuntime.h"
+#include "CGPointerAuthInfo.h"
 #include "CGRecordLayout.h"
 #include "CGValue.h"
 #include "CodeGenFunction.h"
@@ -5349,6 +5350,40 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
     return RValue::get(Result);
   }
 
+  case Builtin::BI__builtin_virtual_member_address: {
+    Address This = EmitLValue(E->getArg(0)).getAddress();
+    APValue ConstMemFun;
+    E->getArg(1)->isCXX11ConstantExpr(getContext(), &ConstMemFun, nullptr);
+    const CXXMethodDecl *CXXMethod =
+        cast<CXXMethodDecl>(ConstMemFun.getMemberPointerDecl());
+    const CGFunctionInfo &FInfo =
+        CGM.getTypes().arrangeCXXMethodDeclaration(CXXMethod);
+    llvm::FunctionType *Ty = CGM.getTypes().GetFunctionType(FInfo);
+    CGCallee VCallee = CGM.getCXXABI().getVirtualFunctionPointer(
+        *this, CXXMethod, This, Ty, E->getBeginLoc());
+    llvm::Value *Callee = VCallee.getFunctionPointer();
+    if (const CGPointerAuthInfo &Schema = VCallee.getPointerAuthInfo())
+      Callee = EmitPointerAuthAuth(Schema, Callee);
+    return RValue::get(Callee);
+  }
+
+  case Builtin::BI__builtin_get_vtable_pointer: {
+    const Expr *Target = E->getArg(0);
+    QualType TargetType = Target->getType();
+    QualType RecordType = TargetType;
+    if (RecordType->isPointerOrReferenceType())
+      RecordType = RecordType->getPointeeType();
+    const CXXRecordDecl *Decl = RecordType->getAsCXXRecordDecl();
+    assert(Decl);
+    auto ThisAddress = TargetType->isPointerType()
+                           ? EmitPointerWithAlignment(Target)
+                           : EmitLValue(Target).getAddress();
+    assert(ThisAddress.isValid());
+    llvm::Value *VTablePointer =
+        GetVTablePtr(ThisAddress, Int8PtrTy, Decl, VTableAuthMode::MustTrap);
+    return RValue::get(VTablePointer);
+  }
+
   case Builtin::BI__exception_code:
   case Builtin::BI_exception_code:
     return RValue::get(EmitSEHExceptionCode());
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index bffd0dd461d3d..dffc569b3cf49 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -1782,6 +1782,92 @@ static ExprResult PointerAuthStringDiscriminator(Sema &S, CallExpr *Call) {
   return Call;
 }
 
+static ExprResult VirtualMemberAddress(Sema &S, CallExpr *Call) {
+  if (S.checkArgCount(Call, 2))
+    return ExprError();
+
+  for (int i = 0; i < 2; ++i) {
+    ExprResult ArgRValue =
+        S.DefaultFunctionArrayLvalueConversion(Call->getArg(1));
+    if (ArgRValue.isInvalid())
+      return ExprError();
+    Call->setArg(1, ArgRValue.get());
+  }
+
+  if (Call->getArg(0)->isTypeDependent() || Call->getArg(1)->isValueDependent())
+    return Call;
+
+  const Expr *ThisArg = Call->getArg(0);
+  QualType ThisTy = ThisArg->getType();
+  if (ThisTy->isPointerOrReferenceType())
+    ThisTy = ThisTy->getPointeeType();
+  if (!ThisTy->getAsCXXRecordDecl()) {
+    S.Diag(ThisArg->getExprLoc(), diag::err_virtual_member_lhs_cxxrec);
+    return ExprError();
+  }
+
+  const Expr *MemFunArg = Call->getArg(1);
+  APValue Result;
+  if (!MemFunArg->isCXX11ConstantExpr(S.getASTContext(), &Result, nullptr)) {
+    S.Diag(MemFunArg->getExprLoc(), diag::err_virtual_member_addrof);
+    return ExprError();
+  }
+
+  if (!Result.isMemberPointer() ||
+      !isa<CXXMethodDecl>(Result.getMemberPointerDecl())) {
+    S.Diag(MemFunArg->getExprLoc(), diag::err_virtual_member_addrof);
+    return ExprError();
+  }
+
+  const CXXMethodDecl *CXXMethod =
+      cast<CXXMethodDecl>(Result.getMemberPointerDecl());
+  if (!CXXMethod->isVirtual()) {
+    S.Diag(MemFunArg->getExprLoc(), diag::err_virtual_member_addrof);
+    return ExprError();
+  }
+
+  if (ThisTy->getAsCXXRecordDecl() != CXXMethod->getParent() &&
+      !S.IsDerivedFrom(Call->getBeginLoc(), ThisTy,
+                       CXXMethod->getFunctionObjectParameterType())) {
+    S.Diag(ThisArg->getExprLoc(), diag::err_virtual_member_inherit);
+    return ExprError();
+  }
+  return Call;
+}
+
+static ExprResult GetVTablePointer(Sema &S, CallExpr *Call) {
+  if (S.checkArgCount(Call, 1))
+    return ExprError();
+  ExprResult ThisArg = S.DefaultFunctionArrayLvalueConversion(Call->getArg(0));
+  if (ThisArg.isInvalid())
+    return ExprError();
+  Call->setArg(0, ThisArg.get());
+  const Expr *Subject = Call->getArg(0);
+  QualType SubjectType = Subject->getType();
+  if (SubjectType->isPointerOrReferenceType())
+    SubjectType = SubjectType->getPointeeType();
+  const CXXRecordDecl *SubjectRecord = SubjectType->getAsCXXRecordDecl();
+  if (!SubjectRecord) {
+    S.Diag(Subject->getBeginLoc(), diag::err_get_vtable_pointer_incorrect_type)
+        << 0 << Subject->getType();
+    return ExprError();
+  }
+  if (S.RequireCompleteType(
+          Subject->getBeginLoc(), SubjectType,
+          diag::err_get_vtable_pointer_requires_complete_type)) {
+    return ExprError();
+  }
+
+  if (!SubjectRecord->isPolymorphic()) {
+    S.Diag(Subject->getBeginLoc(), diag::err_get_vtable_pointer_incorrect_type)
+        << 1 << SubjectType;
+    return ExprError();
+  }
+  QualType ReturnType = S.Context.getPointerType(S.Context.VoidTy.withConst());
+  Call->setType(ReturnType);
+  return Call;
+}
+
 static ExprResult BuiltinLaunder(Sema &S, CallExpr *TheCall) {
   if (S.checkArgCount(TheCall, 1))
     return ExprError();
@@ -2625,6 +2711,12 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID,
     return PointerAuthAuthAndResign(*this, TheCall);
   case Builtin::BI__builtin_ptrauth_string_discriminator:
     return PointerAuthStringDiscriminator(*this, TheCall);
+
+  case Builtin::BI__builtin_get_vtable_pointer:
+    return GetVTablePointer(*this, TheCall);
+  case Builtin::BI__builtin_virtual_member_address:
+    return VirtualMemberAddress(*this, TheCall);
+
   // OpenCL v2.0, s6.13.16 - Pipe functions
   case Builtin::BIread_pipe:
   case Builtin::BIwrite_pipe:
diff --git a/clang/test/CodeGenCXX/builtin-get-vtable-pointer.cpp b/clang/test/CodeGenCXX/builtin-get-vtable-pointer.cpp
new file mode 100644
index 0000000000000..f0ee13152ddf7
--- /dev/null
+++ b/clang/test/CodeGenCXX/builtin-get-vtable-pointer.cpp
@@ -0,0 +1,350 @@
+// RUN: %clang_cc1 %s -x c++ -std=c++11  -triple x86_64-apple-darwin10 -emit-llvm -O1 -disable-llvm-passes -no-enable-noundef-analysis  -o - | FileCheck --check-prefix=CHECK-NOAUTH %s
+// RUN: %clang_cc1 %s -x c++ -std=c++11  -triple arm64-apple-ios -fptrauth-calls -fptrauth-vtable-pointer-type-discrimination -emit-llvm -O1 -disable-llvm-passes -no-enable-noundef-analysis   -o - | FileCheck --check-prefix=CHECK-TYPEAUTH %s
+// RUN: %clang_cc1 %s -x c++ -std=c++11  -triple arm64-apple-ios -fptrauth-calls -fptrauth-vtable-pointer-address-discrimination -emit-llvm -O1 -disable-llvm-passes -no-enable-noundef-analysis  -o - | FileCheck --check-prefix=CHECK-ADDRESSAUTH %s
+// RUN: %clang_cc1 %s -x c++ -std=c++11  -triple arm64-apple-ios -fptrauth-calls -fptrauth-vtable-pointer-type-discrimination -fptrauth-vtable-pointer-address-discrimination -emit-llvm -O1 -disable-llvm-passes -no-enable-noundef-analysis  -o - | FileCheck --check-prefix=CHECK-BOTHAUTH %s
+// FIXME: Assume load should not require -fstrict-vtable-pointers
+
+namespace test1 {
+struct A {
+  A();
+  virtual void bar();
+};
+
+struct B : A {
+  B();
+  virtual void foo();
+};
+
+struct Z : A {};
+struct C : Z, B {
+  C();
+  virtual void wibble();
+};
+
+struct D : virtual A {
+};
+
+struct E : D, B {
+};
+
+const void *a(A *o) {
+  // CHECK-NOAUTH: define ptr @_ZN5test11aEPNS_1AE(ptr %o) #0 {
+  // CHECK-TYPEAUTH: define ptr @_ZN5test11aEPNS_1AE(ptr %o) #0 {
+  return __builtin_get_vtable_pointer(o);
+  // CHECK-NOAUTH: %vtable = load ptr, ptr %0, align 8
+  // CHECK-TYPEAUTH: %0 = load ptr, ptr %o.addr, align 8
+  // CHECK-TYPEAUTH: %vtable = load ptr, ptr %0, align 8
+  // CHECK-TYPEAUTH: %1 = ptrtoint ptr %vtable to i64
+  // CHECK-TYPEAUTH: %2 = call i64 @llvm.ptrauth.auth(i64 %1, i32 2, i64 48388)
+  // CHECK-TYPEAUTH: %3 = inttoptr i64 %2 to ptr
+  // CHECK-TYPEAUTH: %4 = load volatile i8, ptr %3, align 8
+  // CHECK-ADDRESSAUTH: %2 = ptrtoint ptr %vtable to i64
+  // CHECK-ADDRESSAUTH: %3 = call i64 @llvm.ptrauth.auth(i64 %2, i32 2, i64 %1)
+  // CHECK-ADDRESSAUTH: %4 = inttoptr i64 %3 to ptr
+  // CHECK-ADDRESSAUTH: %5 = load volatile i8, ptr %4, align 8
+  // CHECK-BOTHAUTH: [[T1:%.*]] = ptrtoint ptr %0 to i64
+  // CHECK-BOTHAUTH: [[T2:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[T1]], i64 48388)
+  // CHECK-BOTHAUTH: [[T3:%.*]] = ptrtoint ptr %vtable to i64
+  // CHECK-BOTHAUTH: [[T4:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[T3]], i32 2, i64 [[T2]])
+  // CHECK-BOTHAUTH: [[T5:%.*]] = inttoptr i64 [[T4]] to ptr
+  // CHECK-BOTHAUTH: [[T6:%.*]] = load volatile i8, ptr [[T5]], align 8
+}
+
+const void *b(B *o) {
+  // CHECK-TYPEAUTH: define ptr @_ZN5test11bEPNS_1BE(ptr %o) #0 {
+  // CHECK-NOAUTH: define ptr @_ZN5test11bEPNS_1BE(ptr %o) #0 {
+  return __builtin_get_vtable_pointer(o);
+  // CHECK-NOAUTH: %vtable = load ptr, ptr %0, align 8
+  // CHECK-TYPEAUTH: %vtable = load ptr, ptr %0, align 8
+  // CHECK-TYPEAUTH: %1 = ptrtoint ptr %vtable to i64
+  // CHECK-TYPEAUTH: %2 = call i64 @llvm.ptrauth.auth(i64 %1, i32 2, i64 48388)
+  // CHECK-TYPEAUTH: %3 = inttoptr i64 %2 to ptr
+  // CHECK-TYPEAUTH: %4 = load volatile i8, ptr %3, align 8
+  // CHECK-ADDRESSAUTH: %2 = ptrtoint ptr %vtable to i64
+  // CHECK-ADDRESSAUTH: %3 = call i64 @llvm.ptrauth.auth(i64 %2, i32 2, i64 %1)
+  // CHECK-ADDRESSAUTH: %4 = inttoptr i64 %3 to ptr
+  // CHECK-ADDRESSAUTH: %5 = load volatile i8, ptr %4, align 8
+  // CHECK-BOTHAUTH: [[T2:%.*]] = call i64 @llvm.ptrauth.blend(i64 %1, i64 48388)
+  // CHECK-BOTHAUTH: [[T3:%.*]] = ptrtoint ptr %vtable to i64
+  // CHECK-BOTHAUTH: [[T4:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[T3]], i32 2, i64 [[T2]])
+  // CHECK-BOTHAUTH: [[T5:%.*]] = inttoptr i64 [[T4]] to ptr
+  // CHECK-BOTHAUTH: [[T6:%.*]] = load volatile i8, ptr [[T5]], align 8
+}
+
+const void *b_as_A(B *o) {
+  // CHECK-NOAUTH: define ptr @_ZN5test16b_as_AEPNS_1BE(ptr %o) #0 {
+  return __builtin_get_vtable_pointer((A *)o);
+  // CHECK-NOAUTH: %vtable = load ptr, ptr %0, align 8
+  // CHECK-TYPEAUTH: %vtable = load ptr, ptr %0, align 8
+  // CHECK-TYPEAUTH: %1 = ptrtoint ptr %vtable to i64
+  // CHECK-TYPEAUTH: %2 = call i64 @llvm.ptrauth.auth(i64 %1, i32 2, i64 48388)
+  // CHECK-TYPEAUTH: %3 = inttoptr i64 %2 to ptr
+  // CHECK-TYPEAUTH: %4 = load volatile i8, ptr %3, align 8
+  // CHECK-ADDRESSAUTH: %2 = ptrtoint ptr %vtable to i64
+  // CHECK-ADDRESSAUTH: %3 = call i64 @llvm.ptrauth.auth(i64 %2, i32 2, i64 %1)
+  // CHECK-ADDRESSAUTH: %4 = inttoptr i64 %3 to ptr
+  // CHECK-ADDRESSAUTH: %5 = load volatile i8, ptr %4, align 8
+  // CHECK-BOTHAUTH: [[T2:%.*]] = call i64 @llvm.ptrauth.blend(i64 %1, i64 48388)
+  // CHECK-BOTHAUTH: [[T3:%.*]] = ptrtoint ptr %vtable to i64
+  // CHECK-BOTHAUTH: [[T4:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[T3]], i32 2, i64 [[T2]])
+  // CHECK-BOTHAUTH: [[T5:%.*]] = inttoptr i64 [[T4]] to ptr
+  // CHECK-BOTHAUTH: [[T6:%.*]] = load volatile i8, ptr [[T5]], align 8
+}
+
+const void *c(C *o) {
+  // CHECK-NOAUTH: define ptr @_ZN5test11cEPNS_1CE(ptr %o) #0 {
+  return __builtin_get_vtable_pointer(o);
+  // CHECK-NOAUTH: %vtable = load ptr, ptr %0, align 8
+  // CHECK-TYPEAUTH: %vtable = load ptr, ptr %0, align 8
+  // CHECK-TYPEAUTH: %1 = ptrtoint ptr %vtable to i64
+  // CHECK-TYPEAUTH: %2 = call i64 @llvm.ptrauth.auth(i64 %1, i32 2, i64 48388)
+  // CHECK-TYPEAUTH: %3 = inttoptr i64 %2 to ptr
+  // CHECK-TYPEAUTH: %4 = load volatile i8, ptr %3, align 8
+  // CHECK-ADDRESSAUTH: %2 = ptrtoint ptr %vtable to i64
+  // CHECK-ADDRESSAUTH: %3 = call i64 @llvm.ptrauth.auth(i64 %2, i32 2, i64 %1)
+  // CHECK-ADDRESSAUTH: %4 = inttoptr i64 %3 to ptr
+  // CHECK-ADDRESSAUTH: %5 = load volatile i8, ptr %4, align 8
+  // CHECK-BOTHAUTH: [[T2:%.*]] = call i64 @llvm.ptrauth.blend(i64 %1, i64 48388)
+  // CHECK-BOTHAUTH: [[T3:%.*]] = ptrtoint ptr %vtable to i64
+  // CHECK-BOTHAUTH: [[T4:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[T3]], i32 2, i64 [[T2]])
+  // CHECK-BOTHAUTH: [[T5:%.*]] = inttoptr i64 [[T4]] to ptr
+  // CHECK-BOTHAUTH: [[T6:%.*]] = load volatile i8, ptr [[T5]], align 8
+}
+
+const void *c_as_Z(C *o) {
+  // CHECK-NOAUTH: define ptr @_ZN5test16c_as_ZEPNS_1CE(ptr %o) #0 {
+  return __builtin_get_vtable_pointer((Z *)o);
+  // CHECK-NOAUTH: %0 = load ptr, ptr %o.addr, align 8
+  // CHECK-NOAUTH: %vtable = load ptr, ptr %0, align 8
+  // CHECK-TYPEAUTH: %vtable = load ptr, ptr %0, align 8
+  // CHECK-TYPEAUTH: %1 = ptrtoint ptr %vtable to i64
+  // CHECK-TYPEAUTH: %2 = call i64 @llvm.ptrauth.auth(i64 %1, i32 2, i64 48388)
+  // CHECK-TYPEAUTH: %3 = inttoptr i64 %2 to ptr
+  // CHECK-TYPEAUTH: %4 = load volatile i8, ptr %3, align 8
+  // CHECK-ADDRESSAUTH: %2 = ptrtoint ptr %vtable to i64
+  // CHECK-ADDRESSAUTH: %3 = call i64 @llvm.ptrauth.auth(i64 %2, i32 2, i64 %1)
+  // CHECK-ADDRESSAUTH: %4 = inttoptr i64 %3 to ptr
+  // CHECK-ADDRESSAUTH: %5 = load volatile i8, ptr %4, align 8
+  // CHECK-BOTHAUTH: [[T2:%.*]] = call i64 @llvm.ptrauth.blend(i64 %1, i64 48388)
+  // CHECK-BOTHAUTH: [[T3:%.*]] = ptrtoint ptr %vtable to i64
+  // CHECK-BOTHAUTH: [[T4:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[T3]], i32 2, i64 [[T2]])
+  // CHECK-BOTHAUTH: [[T5:%.*]] = inttoptr i64 [[T4]] to ptr
+  // CHECK-BOTHAUTH: [[T6:%.*]] = load volatile i8, ptr [[T5]], align 8
+}
+
+const void *c_as_B(C *o) {
+  // CHECK-NOAUTH: define ptr @_ZN5test16c_as_BEPNS_1CE(ptr %o) #0 {
+  return __builtin_get_vtable_pointer((B *)o);
+  // CHECK-NOAUTH: %add.ptr = getelementptr inbounds i8, ptr %0, i64 8
+  // CHECK-NOAUTH: br label %cast.end
+  // CHECK-NOAUTH: %cast.result = phi ptr [ %add.ptr, %cast.notnull ], [ null, %entry ]
+  // CHECK-NOAUTH: %vtable = load ptr, ptr %cast.result, align 8
+  // CHECK-TYPEAUTH: %cast.result = phi ptr [ %add.ptr, %cast.notnull ], [ null, %entry ]
+  // CHECK-TYPEAUTH: %vtable = load ptr, ptr %cast.result, align 8
+  // CHECK-TYPEAUTH: %2 = ptrtoint ptr %vtable to i64
+  // CHECK-TYPEAUTH: %3 = call i64 @llvm.ptrauth.auth(i64 %2, i32 2, i64 48388)
+  // CHECK-TYPEAUTH: %4 = inttoptr i64 %3 to ptr
+  // CHECK-TYPEAUTH: %5 = load volatile i8, ptr %4, align 8
+  // CHECK-ADDRESSAUTH: %2 = ptrtoint ptr %cast.result to i64
+  // CHECK-ADDRESSAUTH: %3 = ptrtoint ptr %vtable to i64
+  // CHECK-ADDRESSAUTH: %4 = call i64 @llvm.ptrauth.auth(i64 %3, i32 2, i64 %2)
+  // CHECK-ADDRESSAUTH: %5 = inttoptr i64 %4 to ptr
+  // CHECK-ADDRESSAUTH: %6 = load volatile i8, ptr %5, align 8
+  // CHECK-BOTHAUTH: [[T2:%.*]] = call i64 @llvm.ptrauth.blend(i64 %2, i64 48388)
+  // CHECK-BOTHAUTH: [[T3:%.*]] = ptrtoint ptr %vtable to i64
+  // CHECK-BOTHAUTH: [[T4:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[T3]], i32 2, i64 [[T2]])
+  // CHECK-BOTHAUTH: [[T5:%.*]] = inttoptr i64 [[T4]] to ptr
+  // CHECK-BOTHAUTH: [[T6:%.*]] = load volatile i8, ptr [[T5]], align 8
+}
+
+const void *d(D *o) {
+  // CHECK-NOAUTH: define ptr @_ZN5test11dEPNS_1DE(ptr %o) #0 {
+  return __builtin_get_vtable_pointer(o);
+  // CHECK-NOAUTH: %vtable = load ptr, ptr %0, align 8
+  // CHECK-TYPEAUTH: %vtable = load ptr, ptr %0, align 8
+  // CHECK-TYPEAUTH: %1 = ptrtoint ptr %vtable to i64
+  // CHECK-TYPEAUTH: %2 = call i64 @llvm.ptrauth.auth(i64 %1, i32 2, i64 48388)
+  // CHECK-TYPEAUTH: %3 = inttoptr i64 %2 to ptr
+  // CHECK-TYPEAUTH: %4 = load volatile i8, ptr %3, align 8
+  // CHECK-ADDRESSAUTH: %1 = ptrtoint ptr %0 to i64
+  // CHECK-ADDRESSAUTH: %2 = ptrtoint ptr %vtable to i64
+  // CHECK-ADDRESSAUTH: %3 = call i64 @llvm.ptrauth.auth(i64 %2, i32 2, i64 %1)
+  // CHECK-ADDRESSAUTH: %4 = inttoptr i64 %3 to ptr
+  // CHECK-ADDRESSAUTH: %5 = load volatile i8, ptr %4, align 8
+  // CHECK-BOTHAUTH: [[T1:%.*]] = ptrtoint ptr %0 to i64
+  // CHECK-BOTHAUTH: [[T2:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[T1]], i64 48388)
+  // CHECK-BOTHAUTH: [[T3:%.*]] = ptrtoint ptr %vtable to i64
+  // CHECK-BOTHAUTH: [[T4:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[T3]], i32 2, i64 [[T2]])
+  // CHECK-BOTHAUTH: [[T5:%.*]] = inttoptr i64 [[T4]] to ptr
+  // CHECK-BOTHAUTH: [[T6:%.*]] = load volatile i8, ptr [[T5]], align 8
+}
+
+const void *d_as_A(D *o) {
+  // CHECK-NOAUTH: define ptr @_ZN5test16d_as_AEPNS_1DE(ptr %o) #0 {
+  return __builtin_get_vtable_pointer((A *)o);
+  // CHECK-NOAUTH: %vtable = load ptr, ptr %0, align 8
+  // CHECK-NOAUTH: %vbase.offset.ptr = getelementptr i8, ptr %vtable, i64 -32
+  // CHECK-NOAUTH: %vbase.offset = load i64, ptr %vbase.offset.ptr, align 8
+  // CHECK-NOAUTH: %add.ptr = getelementptr inbounds i8, ptr %0, i64 %vbase.offset
+  // CHECK-NOAUTH: %cast.result = phi ptr [ %add.ptr, %cast.notnull ], [ null, %entry ]
+  // CHECK-NOAUTH: %vtable1 = load ptr, ptr %cast.result, align 8
+  // CHECK-TYPEAUTH: %vtable1 = load ptr, ptr %cast.result, align 8
+  // CHECK-TYPEAUTH: %5 = ptrtoint ptr %vtable1 to i64
+  // CHECK-TYPEAUTH: %6 = call i64 @llvm.ptrauth.auth(i64 %5, i32 2, i64 48388)
+  // CHECK-TYPEAUTH: %7 = inttoptr i64 %6 to ptr
+  // CHECK-TYPEAUTH: %8 = load volatile i8, ptr %7, align 8
+  // CHECK-ADDRESSAUTH: %6 = ptrtoint ptr %cast.result to i64
+  // CHECK-ADDRESSAUTH: %7 = ptrtoint ptr %vtable1 to i64
+  // CHECK-ADDRESSAUTH: %8 = call i64 @llvm.ptrauth.auth(i64 %7, i32 2, i64 %6)
+  // CHECK-ADDRESSAUTH: %9 = inttoptr i64 %8 to ptr
+  // CHECK-ADDRESSAUTH: %10 = load volatile i8, ptr %9, align 8
+  // CHECK-BOTHAUTH: [[T1:%.*]] = ptrtoint ptr %cast.result to i64
+  // CHECK-BOTHAUTH: [[T2:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[T1]], i64 48388)
+  // CHECK-BOTHAUTH: [[T3:%.*]] = ptrtoint ptr %vtable1 to i64
+  // CHECK-BOTHAUTH: [[T4:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[T3]], i32 2, i64 [[T2]])
+  // CHECK-BOTHAUTH: [[T5:%.*]] = inttoptr i64 [[T4]] to ptr
+  // CHECK-BOTHAUTH: [[T6:%.*]] = load volatile i8, ptr [[T5]], align 8
+}
+
+const void *e(E *o) {
+  // CHECK-NOAUTH: define ptr @_ZN5test11eEPNS_1EE(ptr %o) #0 {
+  return __builtin_get_vtable_pointer(o);
+  // CHECK-NOAUTH: %vtable = load ptr, ptr %0, align 8
+  // CHECK-TYPEAUTH: %vtable = load ptr, ptr %0, align 8
+  // CHECK-TYPEAUTH: %1 = ptrtoint ptr %vtable to i64
+  // CHECK-TYPEAUTH: %2 = call i64 @llvm.ptrauth.auth(i64 %1, i32 2, i64 48388)
+  // CHECK-TYPEAUTH: %3 = inttoptr i64 %2 to ptr
+  // CHECK-TYPEAUTH: %4 = load volatile i8, ptr %3, align 8
+  // CHECK-ADDRESSAUTH: [[T1:%.*]] = ptrtoint ptr %0 to i64
+  // CHECK-ADDRESSAUTH: [[T2:%.*]] = ptrtoint ptr %vtable to i64
+  // CHECK-ADDRESSAUTH: [[T3:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[T2]], i32 2, i64 [[T1]])
+  // CHECK-ADDRESSAUTH: [[T4:%.*]] = inttoptr i64 [[T3]] to ptr
+  // CHECK-ADDRESSAUTH: [[T5:%.*]] = load volatile i8, ptr [[T4]], align 8
+  // CHECK-BOTHAUTH: [[T1:%.*]] = ptrtoint ptr %0 to i64
+  // CHECK-BOTHAUTH: [[T2:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[T1]], i64 48388)
+  // CHECK-BOTHAUTH: [[T3:%.*]] = ptrtoint ptr %vtable to i64
+  // CHECK-BOTHAUTH: [[T4:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[T3]], i32 2, i64 [[T2]])
+  // CHECK-BOTHAUTH: [[T5:%.*]] = inttoptr i64 [[T4]] to ptr
+  // CHECK-BOTHAUTH: [[T6:%.*]] = load volatile i8, ptr [[T5]], align 8
+}
+
+const void *e_as_B(E *o) {
+  // CHECK-NOAUTH: define ptr @_ZN5test16e_as_BEPNS_1EE(ptr %o) #0 {
+  return __builtin_get_vtable_pointer((B *)o);
+  // CHECK-NOAUTH: %add.ptr = getelementptr inbounds i8, ptr %0, i64 8
+  // CHECK-NOAUTH: %cast.result = phi ptr [ %add.ptr, %cast.notnull ], [ null, %entry ]
+  // CHECK-NOAUTH: %vtable = load ptr, ptr %cast.result, align 8
+  // CHECK-TYPEAUTH: %vtable = load ptr, ptr %cast.result, align 8
+  // CHECK-TYPEAUTH: %2 = ptrtoint ptr %vtable to i64
+  // CHECK-TYPEAUTH: %3 = call i64 @llvm.ptrauth.auth(i64 %2, i32 2, i64 48388)
+  // CHECK-TYPEAUTH: %4 = inttoptr i64 %3 to ptr
+  // CHECK-TYPEAUTH: %5 = load volatile i8, ptr %4, align 8
+  // CHECK-ADDRESSAUTH: [[T1:%.*]] = ptrtoint ptr %cast.result to i64
+  // CHECK-ADDRESSAUTH: [[T2:%.*]] = ptrtoint ptr %vtable to i64
+  // CHECK-ADDRESSAUTH: [[T3:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[T2]], i32 2, i64 [[T1]])
+  // CHECK-ADDRESSAUTH: [[T4:%.*]] = inttoptr i64 [[T3]] to ptr
+  // CHECK-ADDRESSAUTH: [[T5:%.*]] = load volatile i8, ptr [[T4]], align 8
+  // CHECK-BOTHAUTH: [[T1:%.*]] = ptrtoint ptr %cast.result to i64
+  // CHECK-BOTHAUTH: [[T2:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[T1]], i64 48388)
+  // CHECK-BOTHAUTH: [[T3:%.*]] = ptrtoint ptr %vtable to i64
+  // CHECK-BOTHAUTH: [[T4:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[T3]], i32 2, i64 [[T2]])
+  // CHECK-BOTHAUTH: [[T5:%.*]] = inttoptr i64 [[T4]] to ptr
+  // CHECK-BOTHAUTH: [[T6:%.*]] = load volatile i8, ptr [[T5]], align 8
+}
+
+const void *e_as_B_ref(E &o) {
+  // CHECK-NOAUTH: define ptr @_ZN5test110e_as_B_refERNS_1EE
+  return __builtin_get_vtable_pointer((B &)o);
+  // CHECK-NOAUTH: [[THIS_ADDR:%.*]] = getelementptr inbounds i8, ptr %0
+  // CHECK-NOAUTH: %vtable = load ptr, ptr [[THIS_ADDR]]
+  // CHECK-TYPEAUTH: [[THIS_ADDR:%.*]] = getelementptr inbounds i8, ptr %0
+  // CHECK-TYPEAUTH: [[VTABLE:%.*]] = load ptr, ptr [[THIS_ADDR]]
+  // CHECK-TYPEAUTH: [[VTABLE_I64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+  // CHECK-TYPEAUTH: [[VTABLE_I64_AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLE_I64]], i32 2, i64 48388)
+  // CHECK-ADDRESSAUTH: [[THIS_ADDR:%.*]] = getelementptr inbounds i8, ptr %0
+  // CHECK-ADDRESSAUTH: [[VTABLE:%.*]] = load ptr, ptr [[THIS_ADDR]]
+  // CHECK-ADDRESSAUTH: [[THIS_ADDR_I64:%.*]] = ptrtoint ptr [[THIS_ADDR]] to i64
+  // CHECK-ADDRESSAUTH: [[VTABLE_I64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+  // CHECK-ADDRESSAUTH: [[VTABLE_I64_AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLE_I64]], i32 2, i64 [[THIS_ADDR_I64]])
+  // CHECK-BOTHAUTH: [[THIS_ADDR:%.*]] = getelementptr inbounds i8, ptr %0
+  // CHECK-BOTHAUTH: [[VTABLE:%.*]] = load ptr, ptr [[THIS_ADDR]]
+  // CHECK-BOTHAUTH: [[THIS_ADDR_I64:%.*]] = ptrtoint ptr [[THIS_ADDR]] to i64
+  // CHECK-BOTHAUTH: [[DISC:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[THIS_ADDR_I64]], i64 48388)
+  // CHECK-BOTHAUTH: [[VTABLE_I64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+  // CHECK-BOTHAUTH: [[VTABLE_I64_AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLE_I64]], i32 2, i64 [[DISC]])
+  // CHECK-BOTHAUTH: [[VTABLE_AUTHED:%.*]] = inttoptr i64 [[VTABLE_I64_AUTHED]] to ptr
+}
+
+const void *e_as_D(E *o) {
+  // CHECK-NOAUTH: define ptr @_ZN5test16e_as_DEPNS_1EE(ptr %o) #0 {
+  return __builtin_get_vtable_pointer((D *)o);
+  // CHECK-NOAUTH: %vtable = load ptr, ptr %0, align 8
+  // CHECK-TYPEAUTH: %vtable = load ptr, ptr %0, align 8
+  // CHECK-TYPEAUTH: %1 = ptrtoint ptr %vtable to i64
+  // CHECK-TYPEAUTH: %2 = call i64 @llvm.ptrauth.auth(i64 %1, i32 2, i64 48388)
+  // CHECK-TYPEAUTH: %3 = inttoptr i64 %2 to ptr
+  // CHECK-TYPEAUTH: %4 = load volatile i8, ptr %3, align 8
+  // CHECK-ADDRESSAUTH: [[T1:%.*]] = ptrtoint ptr %0 to i64
+  // CHECK-ADDRESSAUTH: [[T2:%.*]] = ptrtoint ptr %vtable to i64
+  // CHECK-ADDRESSAUTH: [[T3:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[T2]], i32 2, i64 [[T1]])
+  // CHECK-ADDRESSAUTH: [[T4:%.*]] = inttoptr i64 [[T3]] to ptr
+  // CHECK-ADDRESSAUTH: [[T5:%.*]] = load volatile i8, ptr [[T4]], align 8
+  // CHECK-BOTHAUTH: [[T1:%.*]] = ptrtoint ptr %0 to i64
+  // CHECK-BOTHAUTH: [[T2:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[T1]], i64 48388)
+  // CHECK-BOTHAUTH: [[T3:%.*]] = ptrtoint ptr %vtable to i64
+  // CHECK-BOTHAUTH: [[T4:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[T3]], i32 2, i64 [[T2]])
+  // CHECK-BOTHAUTH: [[T5:%.*]] = inttoptr i64 [[T4]] to ptr
+  // CHECK-BOTHAUTH: [[T6:%.*]] = load volatile i8, ptr [[T5]], align 8
+}
+const void *e_as_D_ref(E &o) {
+  // CHECK-NOAUTH: define ptr @_ZN5test110e_as_D_refERNS_1EE(
+  return __builtin_get_vtable_pointer((D &)o);
+  // CHECK-NOAUTH: %vtable = load ptr, ptr %0, align 8
+  // CHECK-TYPEAUTH: %vtable = load ptr, ptr %0, align 8
+  // CHECK-TYPEAUTH: %1 = ptrtoint ptr %vtable to i64
+  // CHECK-TYPEAUTH: %2 = call i64 @llvm.ptrauth.auth(i64 %1, i32 2, i64 48388)
+  // CHECK-TYPEAUTH: %3 = inttoptr i64 %2 to ptr
+  // CHECK-TYPEAUTH: %4 = load volatile i8, ptr %3, align 8
+  // CHECK-ADDRESSAUTH: [[T1:%.*]] = ptrtoint ptr %0 to i64
+  // CHECK-ADDRESSAUTH: [[T2:%.*]] = ptrtoint ptr %vtable to i64
+  // CHECK-ADDRESSAUTH: [[T3:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[T2]], i32 2, i64 [[T1]])
+  // CHECK-ADDRESSAUTH: [[T4:%.*]] = inttoptr i64 [[T3]] to ptr
+  // CHECK-ADDRESSAUTH: [[T5:%.*]] = load volatile i8, ptr [[T4]], align 8
+  // CHECK-BOTHAUTH: [[T1:%.*]] = ptrtoint ptr %0 to i64
+  // CHECK-BOTHAUTH: [[T2:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[T1]], i64 48388)
+  // CHECK-BOTHAUTH: [[T3:%.*]] = ptrtoint ptr %vtable to i64
+  // CHECK-BOTHAUTH: [[T4:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[T3]], i32 2, i64 [[T2]])
+  // CHECK-BOTHAUTH: [[T5:%.*]] = inttoptr i64 [[T4]] to ptr
+  // CHECK-BOTHAUTH: [[T6:%.*]] = load volatile i8, ptr [[T5]], align 8
+}
+
+void test() {
+  A aInstance;
+  B bInstance;
+  C cInstance;
+  D dInstance;
+  E eInstance;
+  a(&aInstance);
+  a(&bInstance);
+  a((B *)&cInstance);
+  a(&dInstance);
+  a((D *)&eInstance);
+  a((B *)&eInstance);
+  b(&bInstance);
+  b(&cInstance);
+  b(&eInstance);
+  b_as_A(&bInstance);
+  c(&cInstance);
+  c_as_Z(&cInstance);
+  c_as_B(&cInstance);
+  d(&dInstance);
+  d(&eInstance);
+  d_as_A(&dInstance);
+  d_as_A(&eInstance);
+  e(&eInstance);
+  e_as_B(&eInstance);
+  e_as_B_ref(eInstance);
+  e_as_D(&eInstance);
+  e_as_D_ref(eInstance);
+}
+} // namespace test1
diff --git a/clang/test/CodeGenCXX/builtin_virtual_member_address.cpp b/clang/test/CodeGenCXX/builtin_virtual_member_address.cpp
new file mode 100644
index 0000000000000..ba3b65a330106
--- /dev/null
+++ b/clang/test/CodeGenCXX/builtin_virtual_member_address.cpp
@@ -0,0 +1,68 @@
+// RUN: %clang_cc1 -triple arm64e-apple-ios -fptrauth-calls -emit-llvm -no-enable-noundef-analysis -o - %s | FileCheck %s --check-prefixes CHECK,CHECK-AUTH
+// RUN: %clang_cc1 -triple arm64-apple-ios -emit-llvm -no-enable-noundef-analysis -o - %s | FileCheck %s --check-prefixes CHECK,CHECK-NOAUTH
+
+struct Base {
+  virtual void func1();
+  virtual void func2();
+  void nonvirt();
+};
+
+struct Derived : Base {
+  virtual void func1();
+};
+
+// CHECK-LABEL: define ptr @_Z7simple1R4Base(ptr{{.*}}%b)
+
+// CHECK-AUTH: [[BLOC:%.*]] = alloca ptr
+// CHECK-AUTH: store ptr %b, ptr [[BLOC]]
+// CHECK-AUTH: [[B:%.*]] = load ptr, ptr [[BLOC]]
+// CHECK-AUTH: [[VTABLE:%.*]] = load ptr, ptr [[B]]
+// CHECK-AUTH: [[VTABLE_AUTH_IN:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// CHECK-AUTH: [[VTABLE_AUTH_OUT:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLE_AUTH_IN]], i32 2, i64 0)
+// CHECK-AUTH: [[VTABLE:%.*]] = inttoptr i64 [[VTABLE_AUTH_OUT]] to ptr
+// CHECK-AUTH: [[FUNC_ADDR:%.*]] = getelementptr inbounds ptr, ptr [[VTABLE]], i64 1
+// CHECK-AUTH: [[FUNC:%.*]] = load ptr, ptr [[FUNC_ADDR]]
+// CHECK-AUTH: [[FUNC_ADDR_I64:%.*]] = ptrtoint ptr [[FUNC_ADDR]] to i64
+// CHECK-AUTH: [[DISC:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[FUNC_ADDR_I64]], i64 25637)
+// CHECK-AUTH: [[FUNC_I64:%.*]] = ptrtoint ptr [[FUNC]] to i64
+// CHECK-AUTH: [[FUNC_AUTHED:%.*]] =  call i64 @llvm.ptrauth.auth(i64 [[FUNC_I64]], i32 0, i64 [[DISC]])
+// CHECK-AUTH: [[FUNC:%.*]] = inttoptr i64 [[FUNC_AUTHED]] to ptr
+// CHECK-AUTH: ret ptr [[FUNC]]
+
+// CHECK-NOAUTH: [[BLOC:%.*]] = alloca ptr
+// CHECK-NOAUTH: store ptr %b, ptr [[BLOC]]
+// CHECK-NOAUTH: [[B:%.*]] = load ptr, ptr [[BLOC]]
+// CHECK-NOAUTH: [[VTABLE:%.*]] = load ptr, ptr [[B]]
+// CHECK-NOAUTH: [[FUNC_ADDR:%.*]] = getelementptr inbounds ptr, ptr [[VTABLE]], i64 1
+// CHECK-NOAUTH: [[FUNC:%.*]] = load ptr, ptr [[FUNC_ADDR]]
+// CHECK-NOAUTH: ret ptr [[FUNC]]
+void *simple1(Base &b) {
+  return __builtin_virtual_member_address(b, &Base::func2);
+}
+
+// CHECK-LABEL: define ptr @_Z7simple2P4Base(ptr{{.*}}%b)
+
+// CHECK-AUTH:   [[B_ADDR:%.*]] = alloca ptr, align 8
+// CHECK-AUTH:   store ptr %b, ptr [[B_ADDR]]
+// CHECK-AUTH:   [[VTABLE:%.*]] = load ptr, ptr [[B_ADDR]]
+// CHECK-AUTH:   [[VTABLE_I64:%.*]] = ptrtoint ptr [[VTABLE]] to i64
+// CHECK-AUTH:   [[VTABLE_I64_AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLE_I64]], i32 2, i64 0)
+// CHECK-AUTH:   [[VTABLE_AUTHED:%.*]] = inttoptr i64 [[VTABLE_I64_AUTHED]] to ptr
+// CHECK-AUTH:   [[VFUNCTION_ADDR:%.*]] = getelementptr inbounds ptr, ptr [[VTABLE_AUTHED]], i64 1
+// CHECK-AUTH:   [[VFUNCTION_PTR:%.*]] = load ptr, ptr [[VFUNCTION_ADDR]]
+// CHECK-AUTH:   [[VFUNCTION_ADDR_I64:%.*]] = ptrtoint ptr [[VFUNCTION_ADDR]] to i64
+// CHECK-AUTH:   [[DISC:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[VFUNCTION_ADDR_I64]], i64 25637)
+// CHECK-AUTH:   [[VFUNCTION_I64:%.*]] = ptrtoint ptr [[VFUNCTION_PTR]] to i64
+// CHECK-AUTH:   [[AUTHED_FPTR_I64:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VFUNCTION_I64]], i32 0, i64 %5)
+// CHECK-AUTH:   [[AUTHED_FPTR:%.*]] = inttoptr i64 [[AUTHED_FPTR_I64]] to ptr
+// CHECK-AUTH:   ret ptr [[AUTHED_FPTR]]
+
+// CHECK-NOAUTH: [[B_ADDR:%.*]] = alloca ptr
+// CHECK-NOAUTH: store ptr %b, ptr [[B_ADDR]]
+// CHECK-NOAUTH: [[VTABLE:%.*]] = load ptr, ptr [[B_ADDR]]
+// CHECK-NOAUTH: [[FUNC_ADDR:%.*]] = getelementptr inbounds ptr, ptr [[VTABLE]], i64 1
+// CHECK-NOAUTH: [[FUNC:%.*]] = load ptr, ptr [[FUNC_ADDR]]
+// CHECK-NOAUTH: ret ptr [[FUNC]]
+void *simple2(Base *b) {
+  return __builtin_virtual_member_address(b, &Base::func2);
+}
diff --git a/clang/test/SemaCXX/builtin-get-vtable-pointer.cpp b/clang/test/SemaCXX/builtin-get-vtable-pointer.cpp
new file mode 100644
index 0000000000000..9818ab5b00f5f
--- /dev/null
+++ b/clang/test/SemaCXX/builtin-get-vtable-pointer.cpp
@@ -0,0 +1,124 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -std=c++2a %s
+
+namespace basic {
+struct ForwardDeclaration; // expected-note{{forward declaration of 'basic::ForwardDeclaration'}}
+                           // expected-note at -1{{forward declaration of 'basic::ForwardDeclaration'}}
+struct NonPolymorphic {};
+struct Polymorphic {
+  virtual ~Polymorphic();
+};
+
+template <typename T>
+struct Foo {
+  virtual ~Foo();
+};
+
+template <>
+struct Foo<int> {
+};
+
+template <typename T>
+struct Bar {
+  using SubType = typename T::SubType;
+  SubType *ty() const;
+};
+
+struct Thing1 {
+  using SubType = Thing1;
+};
+
+struct Thing2 {
+  using SubType = Thing2;
+  virtual ~Thing2();
+};
+
+struct Thing3 {
+  using SubType = int;
+};
+
+struct Thing4 {
+  using SubType = Polymorphic;
+};
+
+struct Thing5 {
+  using SubType = NonPolymorphic;
+};
+
+struct Thing6 {
+  using SubType = ForwardDeclaration;
+};
+
+template <typename T>
+const void *getThing(const Bar<T> *b = nullptr) {
+  return __builtin_get_vtable_pointer(b->ty()); // expected-error{{__builtin_get_vtable_pointer requires an argument of class pointer type, but 'SubType *' (aka 'int *') was provided}}
+                                                // expected-error at -1{{__builtin_get_vtable_pointer requires an argument of polymorphic class pointer type, but 'SubType' (aka 'basic::Thing1') has no virtual methods}}
+                                                // expected-error at -2{{__builtin_get_vtable_pointer requires an argument of polymorphic class pointer type, but 'SubType' (aka 'basic::NonPolymorphic') has no virtual methods}}
+                                                // expected-error at -3{{__builtin_get_vtable_pointer requires an argument with a complete type, but 'SubType' (aka 'basic::ForwardDeclaration') is incomplete}}
+}
+template <typename>
+struct IncompleteTemplate; // expected-note{{template is declared here}}
+template <typename>
+struct MonomorphicTemplate {
+};
+template <typename>
+struct PolymorphicTemplate {
+  virtual ~PolymorphicTemplate();
+};
+
+void test_function(int);    // expected-note{{possible target for call}}
+                            // expected-note at -1{{possible target for call}}
+void test_function(double); // expected-note{{possible target for call}}
+                            // expected-note at -1{{possible target for call}}
+
+void getVTablePointer() {
+  ForwardDeclaration *fd = nullptr;
+  NonPolymorphic np;
+  Polymorphic p;
+  NonPolymorphic np_array[1];
+  Polymorphic p_array[1];
+  __builtin_get_vtable_pointer(0);             // expected-error{{__builtin_get_vtable_pointer requires an argument of class pointer type, but 'int' was provided}}
+  __builtin_get_vtable_pointer(nullptr);       // expected-error{{__builtin_get_vtable_pointer requires an argument of class pointer type, but 'std::nullptr_t' was provided}}
+  __builtin_get_vtable_pointer(0.5);           // expected-error{{__builtin_get_vtable_pointer requires an argument of class pointer type, but 'double' was provided}}
+  __builtin_get_vtable_pointer(fd);            // expected-error{{__builtin_get_vtable_pointer requires an argument with a complete type, but 'ForwardDeclaration' is incomplete}}
+  __builtin_get_vtable_pointer(np);            // expected-error{{__builtin_get_vtable_pointer requires an argument of polymorphic class pointer type, but 'NonPolymorphic' has no virtual methods}}
+  __builtin_get_vtable_pointer(&np);           // expected-error{{__builtin_get_vtable_pointer requires an argument of polymorphic class pointer type, but 'NonPolymorphic' has no virtual methods}}
+  (void)__builtin_get_vtable_pointer(p);
+  __builtin_get_vtable_pointer(&p);            // expected-warning{{ignoring return value of function declared with const attribute}}
+  __builtin_get_vtable_pointer(p_array);       // expected-warning{{ignoring return value of function declared with const attribute}}
+  __builtin_get_vtable_pointer(&p_array);      // expected-error{{__builtin_get_vtable_pointer requires an argument of class pointer type, but 'Polymorphic (*)[1]' was provided}}
+  __builtin_get_vtable_pointer(np_array);      // expected-error{{__builtin_get_vtable_pointer requires an argument of polymorphic class pointer type, but 'NonPolymorphic' has no virtual methods}}
+  __builtin_get_vtable_pointer(&np_array);     // expected-error{{__builtin_get_vtable_pointer requires an argument of class pointer type, but 'NonPolymorphic (*)[1]' was provided}}
+  __builtin_get_vtable_pointer(test_function); // expected-error{{reference to overloaded function could not be resolved; did you mean to call it?}}
+                                               // expected-error at -1{{reference to overloaded function could not be resolved; did you mean to call it?}}
+  Foo<double> Food;
+  Foo<int> Fooi;
+  (void)__builtin_get_vtable_pointer(Food);
+  (void)__builtin_get_vtable_pointer(&Food);
+  __builtin_get_vtable_pointer(Fooi);  // expected-error{{__builtin_get_vtable_pointer requires an argument of polymorphic class pointer type, but 'Foo<int>' has no virtual methods}}
+  __builtin_get_vtable_pointer(&Fooi); // expected-error{{__builtin_get_vtable_pointer requires an argument of polymorphic class pointer type, but 'Foo<int>' has no virtual methods}}
+
+  IncompleteTemplate<bool> *incomplete = nullptr;
+  (void)__builtin_get_vtable_pointer(incomplete); // expected-error{{implicit instantiation of undefined template 'basic::IncompleteTemplate<bool>'}}
+  PolymorphicTemplate<bool> *ptb = nullptr;
+  MonomorphicTemplate<bool> *mtb = nullptr;
+  PolymorphicTemplate<int> pti;
+  MonomorphicTemplate<int> mti;
+  PolymorphicTemplate<float> ptf;
+  MonomorphicTemplate<float> mtf;
+  (void)__builtin_get_vtable_pointer(ptb);
+  (void)__builtin_get_vtable_pointer(*ptb);
+  __builtin_get_vtable_pointer(mtb); // expected-error{{__builtin_get_vtable_pointer requires an argument of polymorphic class pointer type, but 'MonomorphicTemplate<bool>' has no virtual methods}}
+  (void)__builtin_get_vtable_pointer(pti);
+  __builtin_get_vtable_pointer(mti); // expected-error{{__builtin_get_vtable_pointer requires an argument of polymorphic class pointer type, but 'MonomorphicTemplate<int>' has no virtual methods}}
+  (void)__builtin_get_vtable_pointer(&ptf);
+  __builtin_get_vtable_pointer(&mtf); // expected-error{{__builtin_get_vtable_pointer requires an argument of polymorphic class pointer type, but 'MonomorphicTemplate<float>' has no virtual methods}}
+
+  getThing<Thing1>(); // expected-note{{in instantiation of function template specialization 'basic::getThing<basic::Thing1>' requested here}}
+  getThing<Thing2>();
+  getThing<Thing3>(); // expected-note{{in instantiation of function template specialization 'basic::getThing<basic::Thing3>' requested here}}
+  getThing<Thing4>();
+  getThing<Thing5>(); // expected-note{{in instantiation of function template specialization 'basic::getThing<basic::Thing5>' requested here}}
+  getThing<Thing6>(); // expected-note{{in instantiation of function template specialization 'basic::getThing<basic::Thing6>' requested here}}
+}
+
+} // namespace basic
diff --git a/clang/test/SemaCXX/builtin_virtual_member_address.cpp b/clang/test/SemaCXX/builtin_virtual_member_address.cpp
new file mode 100644
index 0000000000000..5ffe0f053ca4d
--- /dev/null
+++ b/clang/test/SemaCXX/builtin_virtual_member_address.cpp
@@ -0,0 +1,60 @@
+// RUN: %clang_cc1 -fsyntax-only -verify %s
+
+struct Base {
+  virtual void func1();
+  virtual void func2();
+
+  virtual void overloaded();
+  virtual void overloaded() const;
+
+  void nonvirt();
+};
+
+struct Derived : Base {
+  virtual void func1();
+};
+
+struct Unrelated {
+  virtual void func1();
+};
+
+void *simple(Base &b) {
+  return __builtin_virtual_member_address(b, &Base::func1);
+}
+
+void test(Base &b, Derived &d, Unrelated &u) {
+  __builtin_virtual_member_address(42, &Base::func1); // expected-error {{first argument to __builtin_virtual_member_address must have C++ class type}}
+  __builtin_virtual_member_address(u, &Base::func1); // expected-error {{first argument to __builtin_virtual_member_address must have a type deriving from class where second argument was defined}}
+
+  __builtin_virtual_member_address(b, &Derived::func1); // expected-error {{first argument to __builtin_virtual_member_address must have a type deriving from class where second argument was defined}}
+  __builtin_virtual_member_address(b, &Unrelated::func1); // expected-error {{first argument to __builtin_virtual_member_address must have a type deriving from class where second argument was defined}}
+
+  __builtin_virtual_member_address(b, 42); // expected-error {{second argument to __builtin_virtual_member_address must be the address of a virtual C++ member function: for example '&Foo::func'}}
+  __builtin_virtual_member_address(b, &Base::nonvirt); // expected-error {{second argument to __builtin_virtual_member_address must be the address of a virtual C++ member function: for example '&Foo::func'}}
+
+  (void)__builtin_virtual_member_address(d, &Base::overloaded); // expected-error {{reference to overloaded function could not be resolved; did you mean to call it?}} expected-error {{reference to overloaded function could not be resolved; did you mean to call it?}}
+  (void)__builtin_virtual_member_address(d,(void (Base::*)() const) &Base::overloaded);
+
+  (void)__builtin_virtual_member_address(d, &Base::func1);
+  (void)__builtin_virtual_member_address(Base(), &Base::func1);
+}
+
+template<typename T> void test(T &t, Base &b, Derived &d) {
+  (void)__builtin_virtual_member_address(&b, &Base::func1);
+  (void)__builtin_virtual_member_address(t, &Base::func1);
+  (void)__builtin_virtual_member_address(&t, &Base::func1);
+  (void)__builtin_virtual_member_address(t, &T::func1);
+  (void)__builtin_virtual_member_address(&t, &T::func1);
+  (void)__builtin_virtual_member_address(d, &T::func1);
+  (void)__builtin_virtual_member_address(&d, &T::func1);
+  (void)__builtin_virtual_member_address(t, &Unrelated::func1); // expected-error {{first argument to __builtin_virtual_member_address must have a type deriving from class where second argument was defined}} expected-error {{first argument to __builtin_virtual_member_address must have a type deriving from class where second argument was defined}}
+
+}
+
+void foo() {
+  Base b;
+  Derived d;
+
+  test<Base>(b, b, d); // expected-note {{in instantiation of function template specialization 'test<Base>' requested here}}
+  test<Derived>(d, b, d); // expected-note {{in instantiation of function template specialization 'test<Derived>' requested here}}
+}



More information about the cfe-commits mailing list