Skip to content

[clang][PAC] Support trivially_relocating polymorphic objects #144420

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 2 commits into
base: main
Choose a base branch
from

Conversation

ojhunt
Copy link
Contributor

@ojhunt ojhunt commented Jun 16, 2025

Adds support for trivial relocation of polymorphic objects with address discriminated vtable pointers.

This is implemented as a post-memmove fixup pass over the impacted objects. We do this by traversing the object graph and finding all the vtable slots in the type being relocated, or any of the subobjects.

Adds support for trivial relocation of polymorphic objects with
address discriminated vtable pointers.

This is implemented as a post-memmove fixup pass over the impacted
objects. We do this by traversing the object graph and finding all
the vtable slots in the type being relocated, or any of the
subobjects.
@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 Jun 16, 2025
@llvmbot
Copy link
Member

llvmbot commented Jun 16, 2025

@llvm/pr-subscribers-clang-codegen

@llvm/pr-subscribers-clang

Author: Oliver Hunt (ojhunt)

Changes

Adds support for trivial relocation of polymorphic objects with address discriminated vtable pointers.

This is implemented as a post-memmove fixup pass over the impacted objects. We do this by traversing the object graph and finding all the vtable slots in the type being relocated, or any of the subobjects.


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

6 Files Affected:

  • (modified) clang/lib/AST/ASTContext.cpp (+5)
  • (modified) clang/lib/CodeGen/CGBuiltin.cpp (+12-4)
  • (modified) clang/lib/CodeGen/CGPointerAuth.cpp (+216)
  • (modified) clang/lib/CodeGen/CodeGenFunction.h (+44)
  • (added) clang/test/CodeGenCXX/cxx2c-trivially-relocatable-ptrauth.cpp (+362)
  • (modified) clang/test/SemaCXX/trivially-relocatable-ptrauth.cpp (+6)
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 189e67e4eed0d..5760f382b59ed 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -1727,6 +1727,11 @@ ASTContext::PointerAuthContent ASTContext::findPointerAuthContent(QualType T) {
   T = T.getCanonicalType();
   if (T.hasAddressDiscriminatedPointerAuth())
     return PointerAuthContent::AddressDiscriminatedData;
+
+  T = getBaseElementType(T).getCanonicalType();
+  if (T.hasAddressDiscriminatedPointerAuth())
+    return PointerAuthContent::AddressDiscriminatedData;
+
   const RecordDecl *RD = T->getAsRecordDecl();
   if (!RD)
     return PointerAuthContent::None;
diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp
index 1f69274351676..dea0f214f6806 100644
--- a/clang/lib/CodeGen/CGBuiltin.cpp
+++ b/clang/lib/CodeGen/CGBuiltin.cpp
@@ -4467,17 +4467,25 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
   case Builtin::BI__builtin_trivially_relocate:
   case Builtin::BImemmove:
   case Builtin::BI__builtin_memmove: {
+    QualType CopiedType = E->getArg(0)->getType()->getPointeeType();
     Address Dest = EmitPointerWithAlignment(E->getArg(0));
     Address Src = EmitPointerWithAlignment(E->getArg(1));
     Value *SizeVal = EmitScalarExpr(E->getArg(2));
-    if (BuiltinIDIfNoAsmLabel == Builtin::BI__builtin_trivially_relocate)
+    if (BuiltinIDIfNoAsmLabel == Builtin::BI__builtin_trivially_relocate) {
+      QualType BaseElementType = getContext().getBaseElementType(CopiedType);
+      if (getContext().containsAddressDiscriminatedPointerAuth(
+              BaseElementType)) {
+        llvm::Instruction *LastFixupInstruction =
+            EmitPointerAuthRelocationFixup(CopiedType, Dest, Src, SizeVal);
+        addInstToNewSourceAtom(LastFixupInstruction, nullptr);
+        return RValue::get(Dest, *this);
+      }
       SizeVal = Builder.CreateMul(
           SizeVal,
           ConstantInt::get(
               SizeVal->getType(),
-              getContext()
-                  .getTypeSizeInChars(E->getArg(0)->getType()->getPointeeType())
-                  .getQuantity()));
+              getContext().getTypeSizeInChars(CopiedType).getQuantity()));
+    }
     EmitArgCheck(TCK_Store, Dest, E->getArg(0), 0);
     EmitArgCheck(TCK_Load, Src, E->getArg(1), 1);
     auto *I = Builder.CreateMemMove(Dest, Src, SizeVal, false);
diff --git a/clang/lib/CodeGen/CGPointerAuth.cpp b/clang/lib/CodeGen/CGPointerAuth.cpp
index dcef01a5eb6d3..9ccfcb390292e 100644
--- a/clang/lib/CodeGen/CGPointerAuth.cpp
+++ b/clang/lib/CodeGen/CGPointerAuth.cpp
@@ -420,6 +420,222 @@ void CodeGenFunction::EmitPointerAuthCopy(PointerAuthQualifier Qual, QualType T,
   Builder.CreateStore(Value, DestAddress);
 }
 
+static const ConstantArrayType *tryGetTypeAsConstantArrayType(QualType T) {
+  if (!T->isConstantArrayType())
+    return nullptr;
+  return cast<ConstantArrayType>(T->castAsArrayTypeUnsafe());
+}
+
+using FixupErrorTy = std::pair<const CXXRecordDecl *, CXXBaseSpecifier>;
+class CodeGenFunction::FixupFinder {
+public:
+  using FixupVectorTy = CodeGenFunction::FixupVectorTy;
+  static FixupVectorTy findFixups(CodeGenFunction &CGF, QualType T) {
+    FixupFinder Finder(CGF);
+    FixupVectorTy Result;
+    Finder.findFixups(Result, T, CharUnits::Zero());
+    std::sort(Result.begin(), Result.end(),
+              [](const auto &L, const auto &R) { return L.Offset < R.Offset; });
+    return Result;
+  }
+
+private:
+  explicit FixupFinder(CodeGenFunction &CGF)
+      : CGF(CGF), Context(CGF.getContext()) {}
+
+  void findVTablePointerFixups(FixupVectorTy &Output, CXXRecordDecl *RD,
+                               CharUnits Offset) {
+    CodeGenFunction::VPtrsVector VPtrs = CGF.getVTablePointers(RD);
+    for (auto VPtr : VPtrs) {
+      std::optional<PointerAuthQualifier> PointerAuth =
+          CGF.CGM.getVTablePointerAuthentication(VPtr.Base.getBase());
+      if (PointerAuth && PointerAuth->isAddressDiscriminated())
+        Output.push_back(
+            {Offset + VPtr.Base.getBaseOffset(), KnownNonNull, *PointerAuth});
+    }
+  }
+  void findObjectFixups(FixupVectorTy &Output, CXXRecordDecl *RD,
+                        CharUnits Offset) {
+    if (RD->isPolymorphic())
+      findVTablePointerFixups(Output, RD, Offset);
+    findFixups(Output, RD, Offset, /*SubobjectIsBase=*/true);
+  }
+
+  void findFixups(FixupVectorTy &Output, CXXRecordDecl *RD,
+                  CharUnits SubobjectOffset, bool SubobjectIsBase) {
+    // If we've found a union it by definition cannot contain
+    // address discriminated fields.
+    if (RD->isUnion())
+      return;
+    const ASTRecordLayout &Layout = Context.getASTRecordLayout(RD);
+    if (Layout.hasOwnVFPtr() && RD == Layout.getPrimaryBase())
+      findVTablePointerFixups(Output, RD, SubobjectOffset);
+
+    for (auto Base : RD->bases()) {
+      CXXRecordDecl *BaseDecl =
+          Base.getType()->getAsCXXRecordDecl()->getDefinition();
+      assert(!Base.isVirtual());
+      CharUnits BaseOffset = Layout.getBaseClassOffset(BaseDecl);
+      findFixups(Output, BaseDecl, SubobjectOffset + BaseOffset,
+                 /*SubobjectIsBase=*/true);
+    }
+
+    for (const FieldDecl *Field : RD->fields()) {
+      if (Field->isBitField())
+        continue;
+      unsigned FieldBitOffset = Layout.getFieldOffset(Field->getFieldIndex());
+      CharUnits FieldOffset = Context.toCharUnitsFromBits(FieldBitOffset);
+      findFixups(Output, Field->getType(), SubobjectOffset + FieldOffset);
+    }
+  }
+  void findFixups(FixupVectorTy &Output, QualType T, CharUnits Offset) {
+    T = T.getCanonicalType();
+    if (!Context.containsAddressDiscriminatedPointerAuth(T))
+      return;
+
+    if (const ConstantArrayType *CAT = tryGetTypeAsConstantArrayType(T)) {
+      if (CAT->getSize() == 0)
+        return;
+      Output.push_back({Offset, CAT});
+      return;
+    }
+
+    if (PointerAuthQualifier Q = T.getPointerAuth();
+        Q && Q.isAddressDiscriminated()) {
+      // FIXME: Would it be reasonable to consider nullability?
+      Output.push_back({Offset, NotKnownNonNull, Q});
+      return;
+    }
+
+    CXXRecordDecl *RD = T->getAsCXXRecordDecl();
+    if (!RD)
+      return;
+    findObjectFixups(Output, RD, Offset);
+  }
+  CodeGenFunction &CGF;
+  ASTContext &Context;
+};
+
+void CodeGenFunction::EmitSingleObjectPointerAuthRelocationFixup(
+    const FixupVectorTy &Fixups, QualType ElementType, Address Dst,
+    Address Src) {
+  auto GetFixupAddress = [&](Address BaseAddress, CharUnits Offset,
+                             KnownNonNull_t IsKnownNonNull,
+                             const char *Reason) {
+    llvm::Value *BasePtr = BaseAddress.emitRawPointer(*this);
+    llvm::Value *OffsetValue =
+        llvm::ConstantInt::get(PtrDiffTy, Offset.getQuantity());
+    llvm::Value *FixupAddress =
+        Builder.CreateInBoundsGEP(Int8Ty, BasePtr, OffsetValue, Reason);
+    return Address(FixupAddress, VoidPtrPtrTy,
+                   BaseAddress.getAlignment().alignmentAtOffset(Offset),
+                   IsKnownNonNull);
+  };
+  for (auto &Fixup : Fixups) {
+    if (const ConstantArrayType *CAT = Fixup.getAsConstantArrayType()) {
+      llvm::Value *CountValue = llvm::ConstantInt::get(SizeTy, 1);
+      EmitArrayPointerAuthRelocationFixup(QualType(CAT, 0), Dst, Src,
+                                          CountValue);
+      continue;
+    }
+    auto [IsKnownNonNull, Qualifier] = Fixup.getValueFixup();
+
+    // We don't use the existing copy helpers as we'll be resigning a
+    // value in place assuming the old address for the read.
+    Address FixupDst = GetFixupAddress(Dst, Fixup.Offset, IsKnownNonNull,
+                                       "fixup.dst.with.offset");
+    CGPointerAuthInfo DstPtrAuth = EmitPointerAuthInfo(Qualifier, FixupDst);
+
+    Address FixupSrc = GetFixupAddress(Src, Fixup.Offset, IsKnownNonNull,
+                                       "fixup.src.with.offset");
+    CGPointerAuthInfo SrcPtrAuth = EmitPointerAuthInfo(Qualifier, FixupSrc);
+
+    // We're loading from the destination here as we've already performed the
+    // copy from src to dst, and as relocation has memmove semantics, the src
+    // address may have been overwritten.
+    llvm::Value *Value = Builder.CreateLoad(FixupDst);
+    Value = emitPointerAuthResign(Value, QualType(), SrcPtrAuth, DstPtrAuth,
+                                  IsKnownNonNull);
+    Builder.CreateStore(Value, FixupDst);
+  }
+}
+
+llvm::Instruction *CodeGenFunction::EmitArrayPointerAuthRelocationFixup(
+    QualType ElementType, Address Dst, Address Src, llvm::Value *Count) {
+  // Preemptively flatten array types so we don't end up with multiple levels
+  // of loops unnecessarily
+  if (const ConstantArrayType *CAT =
+          tryGetTypeAsConstantArrayType(ElementType)) {
+    uint64_t ElementCount = getContext().getConstantArrayElementCount(CAT);
+    llvm::Value *ElementCountValue =
+        llvm::ConstantInt::get(SizeTy, ElementCount);
+    Count = Builder.CreateMul(Count, ElementCountValue);
+    ElementType = getContext().getBaseElementType(QualType(CAT, 0));
+  }
+
+  FixupVectorTy *Fixups;
+  if (const auto Existing = FixupLists.find(ElementType);
+      Existing != FixupLists.end())
+    Fixups = Existing->second.get();
+  else {
+    auto FoundFixups = FixupFinder::findFixups(*this, ElementType);
+    auto [EntryPoint, Inserted] = FixupLists.try_emplace(
+        ElementType, std::make_unique<FixupVectorTy>(std::move(FoundFixups)));
+    (void)Inserted;
+    Fixups = EntryPoint->second.get();
+  }
+
+  CharUnits ElementSize = getContext().getTypeSizeInChars(ElementType);
+  CharUnits ElementAlign =
+      Src.getAlignment().alignmentOfArrayElement(ElementSize);
+  llvm::Type *LLVMElemType = ConvertTypeForMem(ElementType);
+
+  llvm::BasicBlock *RelocationFixupEntry = Builder.GetInsertBlock();
+  llvm::BasicBlock *RelocationFixupBody =
+      createBasicBlock("relocation_ptrauth_fixup.body");
+  EmitBlock(RelocationFixupBody);
+  llvm::Value *Zero = llvm::ConstantInt::get(SizeTy, 0);
+  llvm::PHINode *Index =
+      Builder.CreatePHI(SizeTy, 2, "relocation_ptrauth_fixup.index");
+  Index->addIncoming(Zero, RelocationFixupEntry);
+  llvm::Value *DstElement =
+      Builder.CreateInBoundsGEP(LLVMElemType, Dst.emitRawPointer(*this), Index,
+                                "relocation_ptrauth_fixup.dstobject");
+  Address DstElementAddress = Address(DstElement, LLVMElemType, ElementAlign);
+  llvm::Value *SrcElement =
+      Builder.CreateInBoundsGEP(LLVMElemType, Src.emitRawPointer(*this), Index,
+                                "relocation_ptrauth_fixup.srcobject");
+  Address SrcElementAddress = Address(SrcElement, LLVMElemType, ElementAlign);
+
+  // Do the fixup
+  EmitSingleObjectPointerAuthRelocationFixup(
+      *Fixups, ElementType, DstElementAddress, SrcElementAddress);
+
+  llvm::Value *NextIndex =
+      Builder.CreateNUWAdd(Index, llvm::ConstantInt::get(Index->getType(), 1),
+                           "relocation_ptrauth_fixup.next_index");
+  Index->addIncoming(NextIndex, Builder.GetInsertBlock());
+  llvm::Value *IsComplete = Builder.CreateICmpEQ(
+      NextIndex, Count, "relocation_ptrauth_fixup.is_complete");
+  llvm::BasicBlock *RelocationFixupFinished =
+      createBasicBlock("relocation_ptrauth_fixup.end");
+  Builder.CreateCondBr(IsComplete, RelocationFixupFinished,
+                       RelocationFixupBody);
+  EmitBlock(RelocationFixupFinished);
+  return RelocationFixupFinished->getTerminator();
+}
+
+llvm::Instruction *CodeGenFunction::EmitPointerAuthRelocationFixup(
+    QualType ElementType, Address Dst, Address Src, llvm::Value *Count) {
+  size_t ElementSize =
+      getContext().getTypeSizeInChars(ElementType).getQuantity();
+  llvm::Value *ElementSizeValue =
+      llvm::ConstantInt::get(Count->getType(), ElementSize);
+  llvm::Value *Size = Builder.CreateMul(Count, ElementSizeValue);
+  Builder.CreateMemMove(Dst, Src, Size, false);
+  return EmitArrayPointerAuthRelocationFixup(ElementType, Dst, Src, Count);
+}
+
 llvm::Constant *
 CodeGenModule::getConstantSignedPointer(llvm::Constant *Pointer, unsigned Key,
                                         llvm::Constant *StorageAddress,
diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index a5ab9df01dba9..16098e83f0cd6 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -4610,6 +4610,10 @@ class CodeGenFunction : public CodeGenTypeCache {
   void EmitPointerAuthCopy(PointerAuthQualifier Qualifier, QualType Type,
                            Address DestField, Address SrcField);
 
+  llvm::Instruction *EmitPointerAuthRelocationFixup(QualType ElementType,
+                                                    Address Dst, Address Src,
+                                                    llvm::Value *SrcEnd);
+
   std::pair<llvm::Value *, CGPointerAuthInfo>
   EmitOrigPointerRValue(const Expr *E);
 
@@ -4618,6 +4622,46 @@ class CodeGenFunction : public CodeGenTypeCache {
   Address authPointerToPointerCast(Address Ptr, QualType SourceType,
                                    QualType DestType);
 
+private:
+  llvm::Instruction *EmitArrayPointerAuthRelocationFixup(QualType ElementType,
+                                                         Address Dst,
+                                                         Address Src,
+                                                         llvm::Value *Count);
+
+  struct RelocatedAddressDiscriminatedPointerAuthFixup {
+    using FieldFixup = std::pair<KnownNonNull_t, PointerAuthQualifier>;
+    CharUnits Offset;
+    RelocatedAddressDiscriminatedPointerAuthFixup(
+        CharUnits Offset, KnownNonNull_t IsKnownNonNull,
+        PointerAuthQualifier Qualifier)
+        : Offset(Offset), Info(FieldFixup{IsKnownNonNull, Qualifier}) {}
+    RelocatedAddressDiscriminatedPointerAuthFixup(
+        CharUnits Offset, const ConstantArrayType *ArrayType)
+        : Offset(Offset), Info(ArrayType) {}
+    const ConstantArrayType *getAsConstantArrayType() const {
+      if (!std::holds_alternative<const ConstantArrayType *>(Info))
+        return nullptr;
+      return std::get<const ConstantArrayType *>(Info);
+    }
+    std::pair<KnownNonNull_t, PointerAuthQualifier> getValueFixup() const {
+      assert(std::holds_alternative<FieldFixup>(Info));
+      return std::get<FieldFixup>(Info);
+    }
+
+  private:
+    std::variant<FieldFixup, const ConstantArrayType *> Info;
+  };
+
+  using FixupVectorTy =
+      llvm::SmallVector<RelocatedAddressDiscriminatedPointerAuthFixup>;
+  class FixupFinder;
+  llvm::DenseMap<QualType, std::unique_ptr<FixupVectorTy>> FixupLists;
+
+  void EmitSingleObjectPointerAuthRelocationFixup(const FixupVectorTy &Fixups,
+                                                  QualType ElementType,
+                                                  Address Dst, Address Src);
+
+public:
   Address getAsNaturalAddressOf(Address Addr, QualType PointeeTy);
 
   llvm::Value *getAsNaturalPointerTo(Address Addr, QualType PointeeType) {
diff --git a/clang/test/CodeGenCXX/cxx2c-trivially-relocatable-ptrauth.cpp b/clang/test/CodeGenCXX/cxx2c-trivially-relocatable-ptrauth.cpp
new file mode 100644
index 0000000000000..e83858187f33d
--- /dev/null
+++ b/clang/test/CodeGenCXX/cxx2c-trivially-relocatable-ptrauth.cpp
@@ -0,0 +1,362 @@
+// RUN: %clang_cc1 -std=c++26 -triple aarch64-linux-gnu -fptrauth-intrinsics -fptrauth-calls -emit-llvm -o - %s | FileCheck %s
+
+typedef __SIZE_TYPE__ size_t;
+
+
+#if 1
+#define vtable_ptrauth(...) [[clang::ptrauth_vtable_pointer(__VA_ARGS__)]]
+
+#else
+#define vtable_ptrauth(...)
+#define __ptrauth(...)
+#endif
+
+#define ADDR_AND_TYPE_DISC  vtable_ptrauth(process_independent, address_discrimination, type_discrimination) 
+#define TYPE_DISC_ONLY  vtable_ptrauth(process_independent, no_address_discrimination, type_discrimination) 
+
+struct TYPE_DISC_ONLY NoAddrDiscPoly trivially_relocatable_if_eligible {
+    NoAddrDiscPoly(const NoAddrDiscPoly&);
+    virtual ~NoAddrDiscPoly();
+    int *__ptrauth(1,0,1) no_addr_disc;
+    int b;
+};
+
+// A simple test to ensure that we don't do anything more than the memmove
+// if there's no actual reason to do so, despite being in a configuration
+// where in principle such work _could_ be required
+// CHECK-LABEL: define internal void @_ZL4testI14NoAddrDiscPolyEvPvS1_m(
+// CHECK: call void @llvm.memmove.p0.p0.i64(ptr align 8 %2, ptr align 8 %3, i64 %5, i1 false)
+// CHECK-NEXT: ret void
+
+struct ADDR_AND_TYPE_DISC AddrDiscPoly trivially_relocatable_if_eligible {
+    AddrDiscPoly(const AddrDiscPoly&);
+    virtual ~AddrDiscPoly();
+    int *__ptrauth(1,0,1) no_addr_disc;
+    int b;
+};
+
+// CHECK-LABEL: define internal void @_ZL4testI12AddrDiscPolyEvPvS1_m(
+// CHECK: [[DST_PTR:%.*]] = load ptr, ptr %dest, align 8
+// CHECK: [[SRC_PTR:%.*]] = load ptr, ptr %source, align 8
+// CHECK: [[COUNT:%.*]] = load i64, ptr %count.addr, align 8
+// CHECK: [[SIZE:%.*]] = mul i64 [[COUNT]], 24
+// CHECK: call void @llvm.memmove.p0.p0.i64(ptr{{.*}}[[DST_PTR]], ptr{{.*}}[[SRC_PTR]], i64 [[SIZE]], i1 false)
+// CHECK: br label %[[COPY_BODY:[a-zA-Z._]+]]
+// CHECK: [[COPY_BODY]]:
+// CHECK-NEXT: [[INDEX:%.*]] = phi i64 [ 0, %entry ], [ [[NEXT_INDEX:%.*]], %[[COPY_BODY]] ]
+// CHECK: [[DST_OBJ:%.*]] = getelementptr inbounds %struct.AddrDiscPoly, ptr [[DST_PTR]], i64 [[INDEX]]
+// CHECK: [[SRC_OBJ:%.*]] = getelementptr inbounds %struct.AddrDiscPoly, ptr [[SRC_PTR]], i64 [[INDEX]]
+// CHECK: [[FIXUP_DST_ADDR:%.*]] = getelementptr inbounds i8, ptr [[DST_OBJ]], i64 0
+// CHECK: [[FIXUP_DST_ADDR_INT:%.*]] = ptrtoint ptr [[FIXUP_DST_ADDR]] to i64
+// CHECK: [[FIXUP_DST_DISC:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[FIXUP_DST_ADDR_INT]], i64 [[TYPE_DISC:49645]])
+// CHECK: [[FIXUP_SRC_ADDR:%.*]] = getelementptr inbounds i8, ptr [[SRC_OBJ]], i64 0
+// CHECK: [[FIXUP_SRC_ADDR_INT:%.*]] = ptrtoint ptr [[FIXUP_SRC_ADDR]] to i64
+// CHECK: [[FIXUP_SRC_DISC:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[FIXUP_SRC_ADDR_INT]], i64 [[TYPE_DISC]])
+// CHECK: [[PREFIXUP_VALUE:%.*]] = load ptr, ptr [[FIXUP_DST_ADDR]]
+// CHECK: [[PREFIXUP_VALUE_INT:%.*]] = ptrtoint ptr [[PREFIXUP_VALUE]] to i64
+// CHECK: [[FIXEDUP_VALUE_INT:%.*]] = call i64 @llvm.ptrauth.resign(i64 [[PREFIXUP_VALUE_INT]], i32 2, i64 [[FIXUP_SRC_DISC]], i32 2, i64 [[FIXUP_DST_DISC]])
+// CHECK: [[FIXEDUP_VALUE:%.*]] = inttoptr i64 [[FIXEDUP_VALUE_INT]] to ptr
+// CHECK: store ptr [[FIXEDUP_VALUE]], ptr [[FIXUP_DST_ADDR]]
+// CHECK: [[NEXT_INDEX:%.*]] = add nuw i64 [[INDEX]], 1
+// CHECK: [[IS_COMPLETE:%.*]] = icmp eq i64 [[NEXT_INDEX]], [[COUNT]]
+// CHECK: br i1 [[IS_COMPLETE]], label %[[FIXUP_END:[A-Za-z._]*]], label %[[COPY_BODY]]
+// CHECK: [[FIXUP_END]]:
+// CHECK-NEXT: ret void
+
+struct ADDR_AND_TYPE_DISC A trivially_relocatable_if_eligible {
+    virtual ~A();
+    int i;
+};
+
+struct ADDR_AND_TYPE_DISC B trivially_relocatable_if_eligible {
+    virtual ~B();
+    int j;
+};
+
+struct ADDR_AND_TYPE_DISC C trivially_relocatable_if_eligible {
+    virtual ~C();
+    int k;
+};
+
+struct ADDR_AND_TYPE_DISC D trivially_relocatable_if_eligible {
+    virtual ~D();
+    int l;
+};
+
+// Though different types, the structure of MultipleBaseClasses1
+// and MultipleBaseClasses1 is actually identical
+struct MultipleBaseClasses1 trivially_relocatable_if_eligible : A, B {
+    C c;
+    D d;
+};
+
+// CHECK-LABEL: define internal void @_ZL4testI20MultipleBaseClasses1EvPvS1_m(
+// CHECK: [[DST_PTR:%.*]] = load ptr, ptr %dest, align 8
+// CHECK: [[SRC_PTR:%.*]] = load ptr, ptr %source, align 8
+// CHECK: [[COUNT:%.*]] = load i64, ptr %count.addr, align 8
+// CHECK: [[SIZE:%.*]] = mul i64 [[COUNT]], 64
+// CHECK: call void @llvm.memmove.p0.p0.i64(ptr{{.*}}[[DST_PTR]], ptr{{.*}}[[SRC_PTR]], i64 [[SIZE]], i1 false)
+// CHECK: br label %[[COPY_BODY:[a-zA-Z._]+]]
+// CHECK: [[COPY_BODY]]:
+// CHECK-NEXT: [[INDEX:%.*]] = phi i64 [ 0, %entry ], [ [[NEXT_INDEX:%.*]], %[[COPY_BODY]] ]
+// CHECK: [[DST_OBJ:%.*]] = getelementptr inbounds %struct.MultipleBaseClasses1, ptr [[DST_PTR]], i64 [[INDEX]]
+// CHECK: [[SRC_OBJ:%.*]] = getelementptr inbounds %struct.MultipleBaseClasses1, ptr [[SRC_PTR]], i64 [[INDEX]]
+
+// Fixup 1: MultipleBaseClasses1::A vtable pointer
+// CHECK: [[FIXUP_DST_ADDR:%.*]] = getelementptr inbounds i8, ptr...
[truncated]

@@ -1727,6 +1727,11 @@ ASTContext::PointerAuthContent ASTContext::findPointerAuthContent(QualType T) {
T = T.getCanonicalType();
if (T.hasAddressDiscriminatedPointerAuth())
return PointerAuthContent::AddressDiscriminatedData;

T = getBaseElementType(T).getCanonicalType();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Adding this simplifies the logic in codegen, and to an extent removes a footgun in the form of not requiring every caller to lower the type to the base element prior to calling.

if (PointerAuthQualifier Q = T.getPointerAuth();
Q && Q.isAddressDiscriminated()) {
// FIXME: Would it be reasonable to consider nullability?
Output.push_back({Offset, NotKnownNonNull, Q});
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 think this is a reasonable question, but it would also turn an incorrectly null value become a runtime error?

@ojhunt ojhunt requested review from rjmccall and efriedma-quic June 17, 2025 12:37
Copy link
Contributor

@rjmccall rjmccall left a comment

Choose a reason for hiding this comment

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

If it's not a bitwise copy, it's by definition not a trivial relocation, so I don't think this works. I think you're arguing for some kind of intermediate "non-failing relocation" feature.

Are we really reporting polymorphic class types as trivially relocatable?

@rjmccall
Copy link
Contributor

rjmccall commented Jun 17, 2025

I've been informed that I've misunderstood the nature of "trivial relocation" — it is not a "trivial" operation in the usual sense of triviality, it's just an operation that (1) can be done primitively by the compiler and (2) cannot fail. That is exactly the non-failing relocation operation I mentioned. That's not what I would have expected to be standardized, but if it's what the committee did, it addresses my concern, because we can certainly implement it for polymorphic classes.

I think we should make a point of implementing this for address-discriminated __ptrauth qualifiers before we release it, because changing the type trait in a future release will be an ABI break.

Comment on lines +4477 to +4480
BaseElementType)) {
llvm::Instruction *LastFixupInstruction =
EmitPointerAuthRelocationFixup(CopiedType, Dest, Src, SizeVal);
addInstToNewSourceAtom(LastFixupInstruction, nullptr);
Copy link
Contributor

Choose a reason for hiding this comment

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

It's not really a fixup if it's doing the reallocation inside the fixup.
So either we need a better name, or we need to do it after the memmove line 4492

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 was so confused by this comment, to the extent I thought I had managed to break this and not catch it, but yet I agree now the name should be changed :D

When I first wrote this the memmove was not part of that function, so it was purely a fixup, but that is obviously no longer the case.

@ojhunt
Copy link
Contributor Author

ojhunt commented Jun 18, 2025

I think we should make a point of implementing this for address-discriminated __ptrauth qualifiers before we release it, because changing the type trait in a future release will be an ABI break.

I had been talking to @cor3ntin and decided we could do it later as this is all intrinsics and semantics that would be tied to a single TU, but I can see that it would result in different symbols being resolved or produced. We felt that it would be sufficiently localized to TU level behavior that it wouldn't leak, but I could see that I might be possible to get it leak out from there.

@rjmccall Do you have an example you're thinking of that you think is plausible - just so I can't mull it/turn it over in my head?

The earlier implementation did fixup address discriminated explicitly qualified data, and as you commented (not on GH) you can see the model I built out for the fixups easily supports that so it's not an implementation difficulty problem (there are some performance questions that come up, but they're just QoI issues that can be addressed without changing behavior or semantics).

@rjmccall
Copy link
Contributor

It is very common for type traits to factor into the ABI of a template specialization, such that my_variant<T> has a different layout depending on is_amazing_v<T>. That is why changing the value of a type trait generally has to be considered an ABI break.

@ojhunt
Copy link
Contributor Author

ojhunt commented Jun 18, 2025

It is very common for type traits to factor into the ABI of a template specialization, such that my_variant<T> has a different layout depending on is_amazing_v<T>. That is why changing the value of a type trait generally has to be considered an ABI break.

ah right, it's not that the instantiated symbol directly references the trait in mangling or similar, it's that the layout differs based on the query even though the symbol name does not reference the trait.

@cor3ntin I think the correct call is to revert the removal of support for explicit qualifiers even though it does mean we'll need to do more work on perf design in future - do you agree with @rjmccall's rationale?

@ojhunt ojhunt marked this pull request as draft June 18, 2025 08:13
@ojhunt
Copy link
Contributor Author

ojhunt commented Jun 18, 2025

Converting to draft so I don't accidentally hit the big green commit button (that is big and green despite no approvals ?!?!)

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.

4 participants