Skip to content

[clang] Add builtin_get_vtable_pointer and virtual_member_address #135469

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

ojhunt
Copy link
Contributor

@ojhunt ojhunt commented Apr 12, 2025

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.

Co-authored-by: Tim Northover

@ojhunt ojhunt requested review from asl and ahmedbougacha April 12, 2025 03:24
@ojhunt ojhunt self-assigned this Apr 12, 2025
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:codegen IR generation bugs: mangling, exceptions, etc. labels Apr 12, 2025
@llvmbot
Copy link
Member

llvmbot commented Apr 12, 2025

@llvm/pr-subscribers-clang

Author: Oliver Hunt (ojhunt)

Changes

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.


Patch is 39.14 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/135469.diff

9 Files Affected:

  • (modified) clang/docs/LanguageExtensions.rst (+33)
  • (modified) clang/include/clang/Basic/Builtins.td (+12)
  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+15)
  • (modified) clang/lib/CodeGen/CGBuiltin.cpp (+30)
  • (modified) clang/lib/Sema/SemaChecking.cpp (+90)
  • (added) clang/test/CodeGenCXX/builtin-get-vtable-pointer.cpp (+304)
  • (added) clang/test/CodeGenCXX/builtin_virtual_member_address.cpp (+41)
  • (added) clang/test/SemaCXX/builtin-get-vtable-pointer.cpp (+123)
  • (added) clang/test/SemaCXX/builtin_virtual_member_address.cpp (+57)
diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst
index 3b8a9cac6587a..643dea3c6cbf7 100644
--- a/clang/docs/LanguageExtensions.rst
+++ b/clang/docs/LanguageExtensions.rst
@@ -3031,6 +3031,39 @@ 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_call_with_static_chain``
 ------------------------------------
 
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..8c534850696d9 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -1013,6 +1013,13 @@ 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 +12554,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..316f5debf89d7 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,35 @@ 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: {
+    auto target = E->getArg(0);
+    auto type = target->getType();
+    auto decl = type->getPointeeCXXRecordDecl();
+    assert(decl);
+    auto thisAddress = EmitPointerWithAlignment(target);
+    assert(thisAddress.isValid());
+    auto 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..77b8b304f7f0a 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -1764,6 +1764,57 @@ static ExprResult PointerAuthAuthAndResign(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) {
+    auto ArgRValue = S.DefaultFunctionArrayLvalueConversion(Call->getArg(1));
+    if (ArgRValue.isInvalid())
+      return ExprError();
+
+    auto Arg = ArgRValue.get();
+    Call->setArg(1, Arg);
+  }
+
+  if (Call->getArg(0)->isTypeDependent() || Call->getArg(1)->isValueDependent())
+    return Call;
+
+  auto ThisArg = Call->getArg(0);
+  auto ThisTy = ThisArg->getType();
+  if (!ThisTy->getAsCXXRecordDecl()) {
+    S.Diag(ThisArg->getExprLoc(), diag::err_virtual_member_lhs_cxxrec);
+    return ExprError();
+  }
+
+  auto 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();
+  }
+
+  auto 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 PointerAuthStringDiscriminator(Sema &S, CallExpr *Call) {
   if (checkPointerAuthEnabled(S, Call))
     return ExprError();
@@ -1782,6 +1833,39 @@ static ExprResult PointerAuthStringDiscriminator(Sema &S, CallExpr *Call) {
   return Call;
 }
 
+static ExprResult GetVTablePointer(Sema &S, CallExpr *call) {
+  if (S.checkArgCount(call, 1))
+    return ExprError();
+  auto rvalue = S.DefaultFunctionArrayLvalueConversion(call->getArg(0));
+  if (rvalue.isInvalid())
+    return ExprError();
+  call->setArg(0, rvalue.get());
+  auto expression = call->getArg(0);
+  QualType expressionType = expression->getType();
+  const CXXRecordDecl *objectType = expressionType->getPointeeCXXRecordDecl();
+  if (!expressionType->isPointerType() || !objectType) {
+    S.Diag(expression->getBeginLoc(),
+           diag::err_get_vtable_pointer_incorrect_type)
+        << 0 << expressionType;
+    return ExprError();
+  }
+  if (S.RequireCompleteType(
+          expression->getBeginLoc(), expressionType->getPointeeType(),
+          diag::err_get_vtable_pointer_requires_complete_type)) {
+    return ExprError();
+  }
+
+  if (!objectType->isPolymorphic()) {
+    S.Diag(expression->getBeginLoc(),
+           diag::err_get_vtable_pointer_incorrect_type)
+        << 1 << objectType;
+    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 +2709,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..5577e01a09f6f
--- /dev/null
+++ b/clang/test/CodeGenCXX/builtin-get-vtable-pointer.cpp
@@ -0,0 +1,304 @@
+// 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...
[truncated]

@llvmbot
Copy link
Member

llvmbot commented Apr 12, 2025

@llvm/pr-subscribers-clang-codegen

Author: Oliver Hunt (ojhunt)

Changes

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.


Patch is 39.14 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/135469.diff

9 Files Affected:

  • (modified) clang/docs/LanguageExtensions.rst (+33)
  • (modified) clang/include/clang/Basic/Builtins.td (+12)
  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+15)
  • (modified) clang/lib/CodeGen/CGBuiltin.cpp (+30)
  • (modified) clang/lib/Sema/SemaChecking.cpp (+90)
  • (added) clang/test/CodeGenCXX/builtin-get-vtable-pointer.cpp (+304)
  • (added) clang/test/CodeGenCXX/builtin_virtual_member_address.cpp (+41)
  • (added) clang/test/SemaCXX/builtin-get-vtable-pointer.cpp (+123)
  • (added) clang/test/SemaCXX/builtin_virtual_member_address.cpp (+57)
diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst
index 3b8a9cac6587a..643dea3c6cbf7 100644
--- a/clang/docs/LanguageExtensions.rst
+++ b/clang/docs/LanguageExtensions.rst
@@ -3031,6 +3031,39 @@ 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_call_with_static_chain``
 ------------------------------------
 
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..8c534850696d9 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -1013,6 +1013,13 @@ 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 +12554,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..316f5debf89d7 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,35 @@ 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: {
+    auto target = E->getArg(0);
+    auto type = target->getType();
+    auto decl = type->getPointeeCXXRecordDecl();
+    assert(decl);
+    auto thisAddress = EmitPointerWithAlignment(target);
+    assert(thisAddress.isValid());
+    auto 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..77b8b304f7f0a 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -1764,6 +1764,57 @@ static ExprResult PointerAuthAuthAndResign(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) {
+    auto ArgRValue = S.DefaultFunctionArrayLvalueConversion(Call->getArg(1));
+    if (ArgRValue.isInvalid())
+      return ExprError();
+
+    auto Arg = ArgRValue.get();
+    Call->setArg(1, Arg);
+  }
+
+  if (Call->getArg(0)->isTypeDependent() || Call->getArg(1)->isValueDependent())
+    return Call;
+
+  auto ThisArg = Call->getArg(0);
+  auto ThisTy = ThisArg->getType();
+  if (!ThisTy->getAsCXXRecordDecl()) {
+    S.Diag(ThisArg->getExprLoc(), diag::err_virtual_member_lhs_cxxrec);
+    return ExprError();
+  }
+
+  auto 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();
+  }
+
+  auto 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 PointerAuthStringDiscriminator(Sema &S, CallExpr *Call) {
   if (checkPointerAuthEnabled(S, Call))
     return ExprError();
@@ -1782,6 +1833,39 @@ static ExprResult PointerAuthStringDiscriminator(Sema &S, CallExpr *Call) {
   return Call;
 }
 
+static ExprResult GetVTablePointer(Sema &S, CallExpr *call) {
+  if (S.checkArgCount(call, 1))
+    return ExprError();
+  auto rvalue = S.DefaultFunctionArrayLvalueConversion(call->getArg(0));
+  if (rvalue.isInvalid())
+    return ExprError();
+  call->setArg(0, rvalue.get());
+  auto expression = call->getArg(0);
+  QualType expressionType = expression->getType();
+  const CXXRecordDecl *objectType = expressionType->getPointeeCXXRecordDecl();
+  if (!expressionType->isPointerType() || !objectType) {
+    S.Diag(expression->getBeginLoc(),
+           diag::err_get_vtable_pointer_incorrect_type)
+        << 0 << expressionType;
+    return ExprError();
+  }
+  if (S.RequireCompleteType(
+          expression->getBeginLoc(), expressionType->getPointeeType(),
+          diag::err_get_vtable_pointer_requires_complete_type)) {
+    return ExprError();
+  }
+
+  if (!objectType->isPolymorphic()) {
+    S.Diag(expression->getBeginLoc(),
+           diag::err_get_vtable_pointer_incorrect_type)
+        << 1 << objectType;
+    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 +2709,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..5577e01a09f6f
--- /dev/null
+++ b/clang/test/CodeGenCXX/builtin-get-vtable-pointer.cpp
@@ -0,0 +1,304 @@
+// 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...
[truncated]

@@ -0,0 +1,41 @@
// 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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will you please also add ELF target triples in tests?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure thing, what is the triple you folk use?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@asl :D

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From discord: aarch64-linux-pauthtest

@ojhunt
Copy link
Contributor Author

ojhunt commented Apr 12, 2025

I just noticed there's no documentation for __builtin_virtual_member_address() so I'm addressing that.

Due to code drift and time I realized that they disagree as to whether they should take C* or C& so I'm going to make them both accept either (existing code means we can't unify on one or the other, but also there's no real reason to not accept either and simply do the correct thing)

@ojhunt ojhunt force-pushed the users/ojhunt/builtin-get-vtable-and-member branch 2 times, most recently from 3c20ae3 to e03f703 Compare April 13, 2025 07:47
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.

Co-authored-by: Tim Northover
@ojhunt ojhunt force-pushed the users/ojhunt/builtin-get-vtable-and-member branch from e03f703 to 1607a76 Compare April 13, 2025 07:56
@ojhunt ojhunt requested a review from AaronBallman April 13, 2025 07:56
@cor3ntin
Copy link
Contributor

I just noticed there's no documentation for __builtin_virtual_member_address() so I'm addressing that.

Due to code drift and time I realized that they disagree as to whether they should take C* or C& so I'm going to make them both accept either (existing code means we can't unify on one or the other, but also there's no real reason to not accept either and simply do the correct thing)

most of our builtins take pointers. Accepting both seems unwise.
people might want to do interesting things in sfinae context and if we accept everything and anything it will end up causing more pain than it alleviates.

@ojhunt
Copy link
Contributor Author

ojhunt commented Apr 13, 2025

I just noticed there's no documentation for __builtin_virtual_member_address() so I'm addressing that.
Due to code drift and time I realized that they disagree as to whether they should take C* or C& so I'm going to make them both accept either (existing code means we can't unify on one or the other, but also there's no real reason to not accept either and simply do the correct thing)

most of our builtins take pointers. Accepting both seems unwise. people might want to do interesting things in sfinae context and if we accept everything and anything it will end up causing more pain than it alleviates.

Hmmm, I'll revert to only supporting pointers in get_vtable_pointer but there's existing code dependent on the non-pointer argument to virtual_member_address which makes it difficult to switch to having a pointer argument.

@ojhunt
Copy link
Contributor Author

ojhunt commented Apr 13, 2025

I just noticed there's no documentation for __builtin_virtual_member_address() so I'm addressing that.
Due to code drift and time I realized that they disagree as to whether they should take C* or C& so I'm going to make them both accept either (existing code means we can't unify on one or the other, but also there's no real reason to not accept either and simply do the correct thing)

most of our builtins take pointers. Accepting both seems unwise. people might want to do interesting things in sfinae context and if we accept everything and anything it will end up causing more pain than it alleviates.

@AaronBallman Given we need to keep the reference argument to __virtual_member_address, would you like to support both, or remain as reference only?

@AaronBallman
Copy link
Collaborator

I just noticed there's no documentation for __builtin_virtual_member_address() so I'm addressing that.
Due to code drift and time I realized that they disagree as to whether they should take C* or C& so I'm going to make them both accept either (existing code means we can't unify on one or the other, but also there's no real reason to not accept either and simply do the correct thing)

most of our builtins take pointers. Accepting both seems unwise. people might want to do interesting things in sfinae context and if we accept everything and anything it will end up causing more pain than it alleviates.

Hmmm, I'll revert to only supporting pointers in get_vtable_pointer but there's existing code dependent on the non-pointer argument to virtual_member_address which makes it difficult to switch to having a pointer argument.

I'm confused -- how is there existing code depending on a builtin which doesn't yet exist in Clang? Are we copying these builtins from somewhere else?

@ojhunt
Copy link
Contributor Author

ojhunt commented Apr 14, 2025

I'm confused -- how is there existing code depending on a builtin which doesn't yet exist in Clang? Are we copying these builtins from somewhere else?

This is part of the pointer authentication support we are upstreaming, all of which has been in use for a long time, just not available in upstream llvm/clang.

@AaronBallman
Copy link
Collaborator

I'm confused -- how is there existing code depending on a builtin which doesn't yet exist in Clang? Are we copying these builtins from somewhere else?

This is part of the pointer authentication support we are upstreaming, all of which has been in use for a long time, just not available in upstream llvm/clang.

Oof. On the one hand, we don't want to break your existing users. On the other hand, we don't want to force the much broader audience of users into a worse interface. Are those uses in your downstream able to use things like fix-its to switch from the reference-based interface to a pointer-based interface? (Basically, is there a transition approach that leads to a better world?)

case Builtin::BI__builtin_virtual_member_address: {
Address This = EmitLValue(E->getArg(0)).getAddress();
APValue ConstMemFun;
E->getArg(1)->isCXX11ConstantExpr(getContext(), &ConstMemFun, nullptr);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we don't care about the return value of this call? I guess b/c we verify in VirtualMemberAddress, so we are using this for a side effect?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the isCXX11ConstantExpr gives us the target through ConstMemFun, but I'll see if there's a cleaner way to do this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expr::EvaluateAsLValue ?

if (RecordType->isPointerOrReferenceType())
RecordType = RecordType->getPointeeType();
const CXXRecordDecl *Decl = RecordType->getAsCXXRecordDecl();
assert(Decl);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I note you are asserting pointers here but not in BI__builtin_virtual_member_address case, not obvious to me why.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Different authors over time, I'll unify on not asserting as null deref is the downstream outcome.

@ojhunt
Copy link
Contributor Author

ojhunt commented Apr 15, 2025

Oof. On the one hand, we don't want to break your existing users. On the other hand, we don't want to force the much broader audience of users into a worse interface. Are those uses in your downstream able to use things like fix-its to switch from the reference-based interface to a pointer-based interface? (Basically, is there a transition approach that leads to a better world?)

I sent this in discord rather than in GH where the reply can be seen by others and not lost to time:

i'm going to revert the addition of reference parameter i added to __builtin_get_vtable_pointer, for the suboptimal behavior of __builtin_virtual_member_address, i could make it pointer argument only by default, with a flag to permit reference parameters and a deprecation warning so that it's only no new users of a reference argument arrive and we can drop it entirely once all existing users have migrated

@ojhunt
Copy link
Contributor Author

ojhunt commented Apr 15, 2025

pending the above changes I'll remove the review flag so people aren't confused

@ojhunt ojhunt marked this pull request as draft April 15, 2025 20:27
Call->setArg(0, ThisArg.get());
const Expr *Subject = Call->getArg(0);
QualType SubjectType = Subject->getType();
if (SubjectType->isPointerOrReferenceType())
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will be reverting this to require a pointer arg


const Expr *ThisArg = Call->getArg(0);
QualType ThisTy = ThisArg->getType();
if (ThisTy->isPointerOrReferenceType())
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll update this to make it warn on a reference parameter

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:codegen IR generation bugs: mangling, exceptions, etc. clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants