Skip to content

[AArch64][PAC] Handle signing of init/fini pointers in AsmPrinter#193087

Draft
atrosinenko wants to merge 1 commit intomainfrom
users/atrosinenko/pauth-init-fini
Draft

[AArch64][PAC] Handle signing of init/fini pointers in AsmPrinter#193087
atrosinenko wants to merge 1 commit intomainfrom
users/atrosinenko/pauth-init-fini

Conversation

@atrosinenko
Copy link
Copy Markdown
Contributor

Move signing of the contents of @llvm.global_(ctors|dtors) from Clang frontend to the end of the backend pipeline, to AsmPrinter.

Signing of the pointers to init/fini functions in the backend fixes registration of the constructors and destructors performed by the optimizer or the backend.

This commit introduces two new module flags, ptrauth-init-fini and ptrauth-init-fini-address-discrimination, mirroring corresponding Clang options. The flags are semantically boolean, and the module is allowed to have either none of these flags, only the first one, or both. The particular constant discriminator to use is not configurable via module flags and is hardcoded to the value 0xD9D4 in the llvm/lib/Target/AArch64/AArch64PointerAuth.h file.

Move signing of the contents of `@llvm.global_(ctors|dtors)` from
Clang frontend to the end of the backend pipeline, to AsmPrinter.

Signing of the pointers to init/fini functions in the backend fixes
registration of the constructors and destructors performed by the
optimizer or the backend.

This commit introduces two new module flags, `ptrauth-init-fini` and
`ptrauth-init-fini-address-discrimination`, mirroring corresponding
Clang options. The flags are semantically boolean, and the module
is allowed to have either none of these flags, only the first one,
or both. The particular constant discriminator to use is not
configurable via module flags and is hardcoded to the value 0xD9D4
in the `llvm/lib/Target/AArch64/AArch64PointerAuth.h` file.
@llvmbot llvmbot added backend:AArch64 clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:codegen IR generation bugs: mangling, exceptions, etc. llvm:ir labels Apr 20, 2026
@llvmbot
Copy link
Copy Markdown
Member

llvmbot commented Apr 20, 2026

@llvm/pr-subscribers-backend-aarch64

@llvm/pr-subscribers-clang-codegen

Author: Anatoly Trosinenko (atrosinenko)

Changes

Move signing of the contents of @<!-- -->llvm.global_(ctors|dtors) from Clang frontend to the end of the backend pipeline, to AsmPrinter.

Signing of the pointers to init/fini functions in the backend fixes registration of the constructors and destructors performed by the optimizer or the backend.

This commit introduces two new module flags, ptrauth-init-fini and ptrauth-init-fini-address-discrimination, mirroring corresponding Clang options. The flags are semantically boolean, and the module is allowed to have either none of these flags, only the first one, or both. The particular constant discriminator to use is not configurable via module flags and is hardcoded to the value 0xD9D4 in the llvm/lib/Target/AArch64/AArch64PointerAuth.h file.


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

10 Files Affected:

  • (modified) clang/include/clang/Basic/PointerAuthOptions.h (-7)
  • (modified) clang/lib/CodeGen/CodeGenModule.cpp (+7-20)
  • (modified) clang/lib/Frontend/CompilerInvocation.cpp (-6)
  • (modified) clang/test/CodeGen/ptrauth-init-fini.c (+19-11)
  • (modified) llvm/lib/IR/AutoUpgrade.cpp (+99-2)
  • (modified) llvm/lib/IR/Verifier.cpp (+47-11)
  • (modified) llvm/lib/Target/AArch64/AArch64AsmPrinter.cpp (+32-12)
  • (modified) llvm/lib/Target/AArch64/AArch64PointerAuth.h (+8-2)
  • (added) llvm/test/CodeGen/AArch64/ptrauth-init-fini-autoupgrade.ll (+143)
  • (modified) llvm/test/CodeGen/AArch64/ptrauth-init-fini.ll (+73-14)
diff --git a/clang/include/clang/Basic/PointerAuthOptions.h b/clang/include/clang/Basic/PointerAuthOptions.h
index 2b920250721fc..2aa8d70088dbc 100644
--- a/clang/include/clang/Basic/PointerAuthOptions.h
+++ b/clang/include/clang/Basic/PointerAuthOptions.h
@@ -27,10 +27,6 @@ namespace clang {
 /// is ptrauth_string_discriminator("block_descriptor")
 constexpr uint16_t BlockDescriptorConstantDiscriminator = 0xC0BB;
 
-/// Constant discriminator to be used with function pointers in .init_array and
-/// .fini_array. The value is ptrauth_string_discriminator("init_fini")
-constexpr uint16_t InitFiniPointerConstantDiscriminator = 0xD9D4;
-
 /// Constant discriminator to be used with method list pointers. The value is
 /// ptrauth_string_discriminator("method_list_t")
 constexpr uint16_t MethodListPointerConstantDiscriminator = 0xC310;
@@ -224,9 +220,6 @@ struct PointerAuthOptions {
   /// The ABI for C++ member function pointers.
   PointerAuthSchema CXXMemberFunctionPointers;
 
-  /// The ABI for function addresses in .init_array and .fini_array
-  PointerAuthSchema InitFiniPointers;
-
   /// The ABI for block invocation function pointers.
   PointerAuthSchema BlockInvocationFunctionPointers;
 
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index c635a6c175b25..581b7ab99fb51 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -1392,6 +1392,12 @@ void CodeGenModule::Release() {
 
     if (LangOpts.PointerAuthELFGOT)
       getModule().addModuleFlag(llvm::Module::Error, "ptrauth-elf-got", 1);
+    if (LangOpts.PointerAuthCalls && LangOpts.PointerAuthInitFini) {
+      getModule().addModuleFlag(llvm::Module::Error, "ptrauth-init-fini", 1);
+      if (LangOpts.PointerAuthInitFiniAddressDiscrimination)
+        getModule().addModuleFlag(
+            llvm::Module::Error, "ptrauth-init-fini-address-discrimination", 1);
+    }
 
     if (getTriple().isOSLinux()) {
       if (LangOpts.PointerAuthCalls)
@@ -2432,9 +2438,6 @@ void CodeGenModule::AddGlobalDtor(llvm::Function *Dtor, int Priority,
 void CodeGenModule::EmitCtorList(CtorList &Fns, const char *GlobalName) {
   if (Fns.empty()) return;
 
-  const PointerAuthSchema &InitFiniAuthSchema =
-      getCodeGenOpts().PointerAuth.InitFiniPointers;
-
   // Ctor function type is ptr.
   llvm::PointerType *PtrTy = llvm::PointerType::get(
       getLLVMContext(), TheModule.getDataLayout().getProgramAddressSpace());
@@ -2448,23 +2451,7 @@ void CodeGenModule::EmitCtorList(CtorList &Fns, const char *GlobalName) {
   for (const auto &I : Fns) {
     auto Ctor = Ctors.beginStruct(CtorStructTy);
     Ctor.addInt(Int32Ty, I.Priority);
-    if (InitFiniAuthSchema) {
-      llvm::Constant *StorageAddress =
-          (InitFiniAuthSchema.isAddressDiscriminated()
-               ? llvm::ConstantExpr::getIntToPtr(
-                     llvm::ConstantInt::get(
-                         IntPtrTy,
-                         llvm::ConstantPtrAuth::AddrDiscriminator_CtorsDtors),
-                     PtrTy)
-               : nullptr);
-      llvm::Constant *SignedCtorPtr = getConstantSignedPointer(
-          I.Initializer, InitFiniAuthSchema.getKey(), StorageAddress,
-          llvm::ConstantInt::get(
-              SizeTy, InitFiniAuthSchema.getConstantDiscrimination()));
-      Ctor.add(SignedCtorPtr);
-    } else {
-      Ctor.add(I.Initializer);
-    }
+    Ctor.add(I.Initializer);
     if (I.AssociatedData)
       Ctor.add(I.AssociatedData);
     else
diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp
index c6e8644905964..1bf6f015134d1 100644
--- a/clang/lib/Frontend/CompilerInvocation.cpp
+++ b/clang/lib/Frontend/CompilerInvocation.cpp
@@ -1475,12 +1475,6 @@ void CompilerInvocation::setDefaultPointerAuthOptions(
     Opts.CXXMemberFunctionPointers =
         PointerAuthSchema(Key::ASIA, false, Discrimination::Type);
 
-    if (LangOpts.PointerAuthInitFini) {
-      Opts.InitFiniPointers = PointerAuthSchema(
-          Key::ASIA, LangOpts.PointerAuthInitFiniAddressDiscrimination,
-          Discrimination::Constant, InitFiniPointerConstantDiscriminator);
-    }
-
     Opts.BlockInvocationFunctionPointers =
         PointerAuthSchema(Key::ASIA, true, Discrimination::None);
     Opts.BlockHelperFunctionPointers =
diff --git a/clang/test/CodeGen/ptrauth-init-fini.c b/clang/test/CodeGen/ptrauth-init-fini.c
index 1e8953961d64e..466d992299226 100644
--- a/clang/test/CodeGen/ptrauth-init-fini.c
+++ b/clang/test/CodeGen/ptrauth-init-fini.c
@@ -1,28 +1,36 @@
 // REQUIRES: aarch64-registered-target
 
 // RUN: %clang_cc1 -triple aarch64-elf -target-feature +pauth -fptrauth-calls -fptrauth-init-fini    \
-// RUN:   -emit-llvm %s -o - | FileCheck --check-prefix=SIGNED %s
+// RUN:   -emit-llvm %s -o - | FileCheck --check-prefix=COMMON,SIGNED %s
 
 // RUN: %clang_cc1 -triple aarch64-elf -target-feature +pauth -fptrauth-calls -fptrauth-init-fini    \
-// RUN:   -fptrauth-init-fini-address-discrimination -emit-llvm %s -o - | FileCheck --check-prefix=ADDRDISC %s
+// RUN:   -fptrauth-init-fini-address-discrimination -emit-llvm %s -o - | FileCheck --check-prefix=COMMON,ADDRDISC %s
 
 // RUN: %clang_cc1 -triple aarch64-elf -target-feature +pauth -fptrauth-calls \
-// RUN:   -emit-llvm %s -o - | FileCheck --check-prefix=UNSIGNED %s
+// RUN:   -emit-llvm %s -o - | FileCheck --check-prefix=COMMON,UNSIGNED %s
 
 // RUN: %clang_cc1 -triple aarch64-elf -target-feature +pauth -fptrauth-calls -fptrauth-init-fini-address-discrimination \
-// RUN:   -emit-llvm %s -o - | FileCheck --check-prefix=UNSIGNED %s
+// RUN:   -emit-llvm %s -o - | FileCheck --check-prefix=COMMON,UNSIGNED %s
 
 // RUN: %clang_cc1 -triple aarch64-elf -target-feature +pauth                 -fptrauth-init-fini    \
-// RUN:   -emit-llvm %s -o - | FileCheck --check-prefix=UNSIGNED %s
+// RUN:   -emit-llvm %s -o - | FileCheck --check-prefix=COMMON,UNSIGNED %s
 
-// SIGNED: @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr ptrauth (ptr @foo, i32 0, i64 55764), ptr null }]
-// SIGNED: @llvm.global_dtors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr ptrauth (ptr @bar, i32 0, i64 55764), ptr null }]
+// COMMON: @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @foo, ptr null }]
+// COMMON: @llvm.global_dtors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @bar, ptr null }]
 
-// ADDRDISC: @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr ptrauth (ptr @foo, i32 0, i64 55764, ptr inttoptr (i64 1 to ptr)), ptr null }]
-// ADDRDISC: @llvm.global_dtors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr ptrauth (ptr @bar, i32 0, i64 55764, ptr inttoptr (i64 1 to ptr)), ptr null }]
+// The below checks assume no other module flags happens to be set.
 
-// UNSIGNED: @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @foo, ptr null }]
-// UNSIGNED: @llvm.global_dtors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @bar, ptr null }]
+// UNSIGNED-NOT: !llvm.module.flags
+// UNSIGNED-NOT: !"ptrauth-init-fini"
+// UNSIGNED-NOT: !"ptrauth-init-fini-address-discrimination"
+
+// SIGNED: !llvm.module.flags = !{!0}
+// SIGNED: !0 = !{i32 1, !"ptrauth-init-fini", i32 1}
+// SIGNED-NOT: !"ptrauth-init-fini-address-discrimination"
+
+// ADDRDISC: !llvm.module.flags = !{!0, !1}
+// ADDRDISC: !0 = !{i32 1, !"ptrauth-init-fini", i32 1}
+// ADDRDISC: !1 = !{i32 1, !"ptrauth-init-fini-address-discrimination", i32 1}
 
 volatile int x = 0;
 
diff --git a/llvm/lib/IR/AutoUpgrade.cpp b/llvm/lib/IR/AutoUpgrade.cpp
index 2728897372009..e303922d4765c 100644
--- a/llvm/lib/IR/AutoUpgrade.cpp
+++ b/llvm/lib/IR/AutoUpgrade.cpp
@@ -6165,12 +6165,109 @@ void llvm::UpgradeARCRuntime(Module &M) {
     UpgradeToIntrinsic(I.first, I.second);
 }
 
+// Upgrade from wrapping each pointer stored in the @llvm.global_(ctors|dtors)
+// with ptrauth constant expression to storing plain pointers in these arrays
+// and requesting the particular signing schema globally via module flags.
+//
+// Only perform the upgrade if all elements of *both* arrays agree on a common
+// signing schema. The processing of the array is *not* stopped on the first
+// null function pointer.
+static bool upgradePtrauthInitFiniArrays(Module &M) {
+  // Either "not decided yet" or whether we should request address diversity
+  // in addition to the basic constant diversity.
+  // There is no value representing "decided not to sign", as this results
+  // in immediate return from upgradePtrauthInitFiniArrays.
+  std::optional<bool> UseAddressDisc;
+
+  // Do not attempt upgrading if the new module flags already exist.
+  if (const NamedMDNode *ModFlags = M.getModuleFlagsMetadata()) {
+    for (const MDNode *Flag : ModFlags->operands()) {
+      if (Flag->getNumOperands() != 3)
+        continue;
+      const MDString *ID = dyn_cast_or_null<MDString>(Flag->getOperand(1));
+      if (ID && (ID->getString() == "ptrauth-init-fini" ||
+                 ID->getString() == "ptrauth-init-fini-address-discriminator"))
+        return false;
+    }
+  }
+
+  auto UpgradeSinglePointer = [&UseAddressDisc](Constant *CV) -> Constant * {
+    const unsigned ExpectedConstDisc = 0xD9D4;
+    const unsigned ExpectedAddressMarker = 1;
+
+    auto *CPA = dyn_cast<ConstantPtrAuth>(CV);
+    if (!CPA || !CPA->getDiscriminator()->equalsInt(ExpectedConstDisc))
+      return nullptr; // Nothing to upgrade or unknown pattern found.
+
+    bool HasAddressDisc;
+    if (!CPA->hasAddressDiscriminator())
+      HasAddressDisc = false;
+    else if (CPA->hasSpecialAddressDiscriminator(ExpectedAddressMarker))
+      HasAddressDisc = true;
+    else
+      return nullptr; // Unknown pattern.
+
+    if (UseAddressDisc && *UseAddressDisc != HasAddressDisc)
+      return nullptr; // Disagreement with the decided mode.
+
+    UseAddressDisc = HasAddressDisc;
+    return CPA->getPointer();
+  };
+
+  SmallVector<std::pair<GlobalVariable *, Constant *>> PendingUpgrades;
+  for (const char *Name : {"llvm.global_ctors", "llvm.global_dtors"}) {
+    auto *GV = dyn_cast_if_present<GlobalVariable>(M.getNamedValue(Name));
+    if (!GV || !GV->hasInitializer())
+      continue; // Skip, but it is okay to upgrade the other variable.
+
+    auto *Init = dyn_cast<ConstantArray>(GV->getInitializer());
+    if (!Init)
+      return false;
+
+    std::vector<Constant *> NewStructors;
+    NewStructors.reserve(Init->getNumOperands());
+    for (Use &U : Init->operands()) {
+      auto *Structor = dyn_cast<ConstantStruct>(U.get());
+      if (!Structor || Structor->getNumOperands() != 3)
+        return false;
+
+      Constant *Prio = Structor->getOperand(0);
+      Constant *Func = UpgradeSinglePointer(Structor->getOperand(1));
+      Constant *Arg = Structor->getOperand(2);
+      if (!Func)
+        return false;
+
+      NewStructors.push_back(
+          ConstantStruct::get(Structor->getType(), {Prio, Func, Arg}));
+    }
+
+    Constant *NewInit = ConstantArray::get(Init->getType(), NewStructors);
+    PendingUpgrades.push_back({GV, NewInit});
+  }
+
+  if (PendingUpgrades.empty())
+    return false;
+  assert(UseAddressDisc.has_value());
+
+  for (auto [GV, NewInit] : PendingUpgrades)
+    GV->setInitializer(NewInit);
+  M.addModuleFlag(Module::Error, "ptrauth-init-fini", 1);
+  if (UseAddressDisc.value())
+    M.addModuleFlag(Module::Error, "ptrauth-init-fini-address-discriminator",
+                    1);
+
+  return true;
+}
+
 bool llvm::UpgradeModuleFlags(Module &M) {
+  bool Changed = false;
+  Changed |= upgradePtrauthInitFiniArrays(M);
+
   NamedMDNode *ModFlags = M.getModuleFlagsMetadata();
   if (!ModFlags)
-    return false;
+    return Changed;
 
-  bool HasObjCFlag = false, HasClassProperties = false, Changed = false;
+  bool HasObjCFlag = false, HasClassProperties = false;
   bool HasSwiftVersionFlag = false;
   uint8_t SwiftMajorVersion, SwiftMinorVersion;
   uint32_t SwiftABIVersion;
diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp
index 9dba574f6e9eb..5665c60224dc6 100644
--- a/llvm/lib/IR/Verifier.cpp
+++ b/llvm/lib/IR/Verifier.cpp
@@ -910,6 +910,19 @@ void Verifier::visitGlobalVariable(const GlobalVariable &GV) {
       Check(ETy->isPointerTy(), "wrong type for intrinsic global variable",
             &GV);
     }
+
+    auto *Init = GV.hasInitializer()
+                     ? dyn_cast<ConstantArray>(GV.getInitializer())
+                     : nullptr;
+    if (Init) {
+      for (const Use &U : Init->operands()) {
+        auto *Structor = dyn_cast<ConstantStruct>(U);
+        if (!Structor || Structor->getNumOperands() != 3)
+          continue;
+        Check(!isa<ConstantPtrAuth>(Structor->getOperand(1)),
+              "signing of ctors/dtors should be requested via module flags");
+      }
+    }
   }
 
   if (GV.hasName() && (GV.getName() == "llvm.used" ||
@@ -1961,26 +1974,49 @@ void Verifier::visitModuleFlags() {
   // Scan each flag, and track the flags and requirements.
   DenseMap<const MDString*, const MDNode*> SeenIDs;
   SmallVector<const MDNode*, 16> Requirements;
-  uint64_t PAuthABIPlatform = -1;
-  uint64_t PAuthABIVersion = -1;
+  std::optional<uint64_t> PAuthABIPlatform;
+  std::optional<uint64_t> PAuthABIVersion;
+  std::optional<uint64_t> HasPtrauthInitFini;
+  std::optional<uint64_t> HasPtrauthInitFiniAddr;
+
   for (const MDNode *MDN : Flags->operands()) {
     visitModuleFlag(MDN, SeenIDs, Requirements);
     if (MDN->getNumOperands() != 3)
       continue;
+
     if (const auto *FlagName = dyn_cast_or_null<MDString>(MDN->getOperand(1))) {
-      if (FlagName->getString() == "aarch64-elf-pauthabi-platform") {
-        if (const auto *PAP =
+      auto GetFlagNamed = [&](StringRef Name) -> std::optional<uint64_t> {
+        if (FlagName->getString() != Name)
+          return std::nullopt;
+        if (const auto *FlagValue =
                 mdconst::dyn_extract_or_null<ConstantInt>(MDN->getOperand(2)))
-          PAuthABIPlatform = PAP->getZExtValue();
-      } else if (FlagName->getString() == "aarch64-elf-pauthabi-version") {
-        if (const auto *PAV =
-                mdconst::dyn_extract_or_null<ConstantInt>(MDN->getOperand(2)))
-          PAuthABIVersion = PAV->getZExtValue();
-      }
+          return FlagValue->getZExtValue();
+
+        CheckFailed(Name + ": module flag expects integer value");
+        return std::nullopt;
+      };
+
+      if (auto Value = GetFlagNamed("aarch64-elf-pauthabi-platform"))
+        PAuthABIPlatform = *Value;
+      else if (auto Value = GetFlagNamed("aarch64-elf-pauthabi-version"))
+        PAuthABIVersion = *Value;
+      else if (auto Value = GetFlagNamed("ptrauth-init-fini"))
+        HasPtrauthInitFini = *Value;
+      else if (auto Value =
+                   GetFlagNamed("ptrauth-init-fini-address-discrimination"))
+        HasPtrauthInitFiniAddr = *Value;
     }
   }
 
-  if ((PAuthABIPlatform == uint64_t(-1)) != (PAuthABIVersion == uint64_t(-1)))
+  Check(!HasPtrauthInitFini || HasPtrauthInitFini.value() == 1,
+        "ptrauth-init-fini must be set to 1 or unset");
+  Check(!HasPtrauthInitFiniAddr || HasPtrauthInitFiniAddr.value() == 1,
+        "ptrauth-init-fini-address-discrimination must be set to 1 or unset");
+  if (HasPtrauthInitFiniAddr)
+    Check(HasPtrauthInitFini, "ptrauth-init-fini-address-discrimination module "
+                              "flag requires ptrauth-init-fini");
+
+  if (PAuthABIPlatform.has_value() != PAuthABIVersion.has_value())
     CheckFailed("either both or no 'aarch64-elf-pauthabi-platform' and "
                 "'aarch64-elf-pauthabi-version' module flags must be present");
 
diff --git a/llvm/lib/Target/AArch64/AArch64AsmPrinter.cpp b/llvm/lib/Target/AArch64/AArch64AsmPrinter.cpp
index 97edd896c5992..557f79999a88a 100644
--- a/llvm/lib/Target/AArch64/AArch64AsmPrinter.cpp
+++ b/llvm/lib/Target/AArch64/AArch64AsmPrinter.cpp
@@ -98,6 +98,8 @@ class AArch64AsmPrinter : public AsmPrinter {
   FaultMaps FM;
   const AArch64Subtarget *STI;
   bool ShouldEmitWeakSwiftAsyncExtendedFramePointerFlags = false;
+  bool PtrauthInitFini = false;
+  bool PtrauthInitFiniAddressDisc = false;
 #ifndef NDEBUG
   unsigned InstsEmitted;
 #endif
@@ -405,6 +407,11 @@ void AArch64AsmPrinter::emitStartOfAsmFile(Module &M) {
       EnableImportCallOptimization = true;
   }
 
+  if (M.getModuleFlag("ptrauth-init-fini"))
+    PtrauthInitFini = true;
+  if (M.getModuleFlag("ptrauth-init-fini-address-discrimination"))
+    PtrauthInitFiniAddressDisc = true;
+
   if (!TT.isOSBinFormatELF())
     return;
 
@@ -1464,18 +1471,31 @@ void AArch64AsmPrinter::emitFunctionEntryLabel() {
 
 void AArch64AsmPrinter::emitXXStructor(const DataLayout &DL,
                                        const Constant *CV) {
-  if (const auto *CPA = dyn_cast<ConstantPtrAuth>(CV))
-    if (CPA->hasAddressDiscriminator() &&
-        !CPA->hasSpecialAddressDiscriminator(
-            ConstantPtrAuth::AddrDiscriminator_CtorsDtors))
-      report_fatal_error(
-          "unexpected address discrimination value for ctors/dtors entry, only "
-          "'ptr inttoptr (i64 1 to ptr)' is allowed");
-  // If we have signed pointers in xxstructors list, they'll be lowered to @AUTH
-  // MCExpr's via AArch64AsmPrinter::lowerConstantPtrAuth. It does not look at
-  // actual address discrimination value and only checks
-  // hasAddressDiscriminator(), so it's OK to leave special address
-  // discrimination value here.
+  LLVMContext &C = CV->getContext();
+  assert(!isa<ConstantPtrAuth>(CV) &&
+         "ctors/dtors are to be signed by asm printer");
+
+  if (PtrauthInitFini) {
+    IntegerType *Int32Ty = IntegerType::get(C, 32);
+    IntegerType *Int64Ty = IntegerType::get(C, 64);
+    PointerType *PtrTy = PointerType::get(C, 0);
+
+    ConstantInt *Key = ConstantInt::get(Int32Ty, AArch64PAuth::InitFiniKey);
+    ConstantInt *IntDisc = ConstantInt::get(
+        Int64Ty, AArch64PAuth::InitFiniPointerConstantDiscriminator);
+    Constant *Null = ConstantPointerNull::get(PtrTy);
+    Constant *AddressDisc = Null;
+    if (PtrauthInitFiniAddressDisc) {
+      uint64_t Marker = ConstantPtrAuth::AddrDiscriminator_CtorsDtors;
+      AddressDisc =
+          ConstantExpr::getIntToPtr(ConstantInt::get(Int64Ty, Marker), PtrTy);
+    }
+
+    CV = ConstantPtrAuth::get(const_cast<Constant *>(CV), Key, IntDisc,
+                              AddressDisc, /*DeactivationSymbol=*/Null);
+  }
+
+  // Signed pointers will be lowered by AArch64AsmPrinter::lowerConstantPtrAuth.
   AsmPrinter::emitXXStructor(DL, CV);
 }
 
diff --git a/llvm/lib/Target/AArch64/AArch64PointerAuth.h b/llvm/lib/Target/AArch64/AArch64PointerAuth.h
index d6947f0d22aac..1e69558e996d1 100644
--- a/llvm/lib/Target/AArch64/AArch64PointerAuth.h
+++ b/llvm/lib/Target/AArch64/AArch64PointerAuth.h
@@ -9,12 +9,18 @@
 #ifndef LLVM_LIB_TARGET_AARCH64_AARCH64POINTERAUTH_H
 #define LLVM_LIB_TARGET_AARCH64_AARCH64POINTERAUTH_H
 
-#include "llvm/CodeGen/MachineBasicBlock.h"
-#include "llvm/CodeGen/Register.h"
+#include "Utils/AArch64BaseInfo.h"
 
 namespace llvm {
 namespace AArch64PAuth {
 
+/// PAuth key to be used with function pointers in .init_array and .fini_array.
+constexpr AArch64PACKey::ID InitFiniKey = AArch64PACKey::IA;
+
+/// Constant discriminator to be used with function pointers in .init_array and
+/// .fini_array. The value is ptrauth_string_discriminator("init_fini")
+constexpr unsigned InitFiniPointerConstantDiscriminator = 0xD9D4;
+
 /// Variants of check performed on an authenticated pointer.
 ///
 /// In cases such as authenticating the LR value when performing a tail call
diff --git a/llvm/test/CodeGen/AArch64/ptrauth-init-fini-autoupgrade.ll b/llvm/test/CodeGen/AArch64/ptrauth-init-fini-autoupgrade.ll
new file mode 100644
index 0000000000000..68efd9c2a8b7e
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/ptrauth-init-fini-autoupgrade.ll
@@ -0,0 +1,143 @@
+; RUN: rm -rf %t && split-file %s %t && cd %t
+
+;--- nodisc.ll
+
+; RUN: opt -S < nodisc.ll | FileCheck %s --check-prefix=NODISC
+
+@llvm.global_ctors = appending global [2 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr ptrauth (ptr @foo1, i32 0, i64 55764), ptr null }, { i32, ptr, ptr } { i32 ...
[truncated]

@llvmbot
Copy link
Copy Markdown
Member

llvmbot commented Apr 20, 2026

@llvm/pr-subscribers-llvm-ir

Author: Anatoly Trosinenko (atrosinenko)

Changes

Move signing of the contents of @<!-- -->llvm.global_(ctors|dtors) from Clang frontend to the end of the backend pipeline, to AsmPrinter.

Signing of the pointers to init/fini functions in the backend fixes registration of the constructors and destructors performed by the optimizer or the backend.

This commit introduces two new module flags, ptrauth-init-fini and ptrauth-init-fini-address-discrimination, mirroring corresponding Clang options. The flags are semantically boolean, and the module is allowed to have either none of these flags, only the first one, or both. The particular constant discriminator to use is not configurable via module flags and is hardcoded to the value 0xD9D4 in the llvm/lib/Target/AArch64/AArch64PointerAuth.h file.


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

10 Files Affected:

  • (modified) clang/include/clang/Basic/PointerAuthOptions.h (-7)
  • (modified) clang/lib/CodeGen/CodeGenModule.cpp (+7-20)
  • (modified) clang/lib/Frontend/CompilerInvocation.cpp (-6)
  • (modified) clang/test/CodeGen/ptrauth-init-fini.c (+19-11)
  • (modified) llvm/lib/IR/AutoUpgrade.cpp (+99-2)
  • (modified) llvm/lib/IR/Verifier.cpp (+47-11)
  • (modified) llvm/lib/Target/AArch64/AArch64AsmPrinter.cpp (+32-12)
  • (modified) llvm/lib/Target/AArch64/AArch64PointerAuth.h (+8-2)
  • (added) llvm/test/CodeGen/AArch64/ptrauth-init-fini-autoupgrade.ll (+143)
  • (modified) llvm/test/CodeGen/AArch64/ptrauth-init-fini.ll (+73-14)
diff --git a/clang/include/clang/Basic/PointerAuthOptions.h b/clang/include/clang/Basic/PointerAuthOptions.h
index 2b920250721fc..2aa8d70088dbc 100644
--- a/clang/include/clang/Basic/PointerAuthOptions.h
+++ b/clang/include/clang/Basic/PointerAuthOptions.h
@@ -27,10 +27,6 @@ namespace clang {
 /// is ptrauth_string_discriminator("block_descriptor")
 constexpr uint16_t BlockDescriptorConstantDiscriminator = 0xC0BB;
 
-/// Constant discriminator to be used with function pointers in .init_array and
-/// .fini_array. The value is ptrauth_string_discriminator("init_fini")
-constexpr uint16_t InitFiniPointerConstantDiscriminator = 0xD9D4;
-
 /// Constant discriminator to be used with method list pointers. The value is
 /// ptrauth_string_discriminator("method_list_t")
 constexpr uint16_t MethodListPointerConstantDiscriminator = 0xC310;
@@ -224,9 +220,6 @@ struct PointerAuthOptions {
   /// The ABI for C++ member function pointers.
   PointerAuthSchema CXXMemberFunctionPointers;
 
-  /// The ABI for function addresses in .init_array and .fini_array
-  PointerAuthSchema InitFiniPointers;
-
   /// The ABI for block invocation function pointers.
   PointerAuthSchema BlockInvocationFunctionPointers;
 
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index c635a6c175b25..581b7ab99fb51 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -1392,6 +1392,12 @@ void CodeGenModule::Release() {
 
     if (LangOpts.PointerAuthELFGOT)
       getModule().addModuleFlag(llvm::Module::Error, "ptrauth-elf-got", 1);
+    if (LangOpts.PointerAuthCalls && LangOpts.PointerAuthInitFini) {
+      getModule().addModuleFlag(llvm::Module::Error, "ptrauth-init-fini", 1);
+      if (LangOpts.PointerAuthInitFiniAddressDiscrimination)
+        getModule().addModuleFlag(
+            llvm::Module::Error, "ptrauth-init-fini-address-discrimination", 1);
+    }
 
     if (getTriple().isOSLinux()) {
       if (LangOpts.PointerAuthCalls)
@@ -2432,9 +2438,6 @@ void CodeGenModule::AddGlobalDtor(llvm::Function *Dtor, int Priority,
 void CodeGenModule::EmitCtorList(CtorList &Fns, const char *GlobalName) {
   if (Fns.empty()) return;
 
-  const PointerAuthSchema &InitFiniAuthSchema =
-      getCodeGenOpts().PointerAuth.InitFiniPointers;
-
   // Ctor function type is ptr.
   llvm::PointerType *PtrTy = llvm::PointerType::get(
       getLLVMContext(), TheModule.getDataLayout().getProgramAddressSpace());
@@ -2448,23 +2451,7 @@ void CodeGenModule::EmitCtorList(CtorList &Fns, const char *GlobalName) {
   for (const auto &I : Fns) {
     auto Ctor = Ctors.beginStruct(CtorStructTy);
     Ctor.addInt(Int32Ty, I.Priority);
-    if (InitFiniAuthSchema) {
-      llvm::Constant *StorageAddress =
-          (InitFiniAuthSchema.isAddressDiscriminated()
-               ? llvm::ConstantExpr::getIntToPtr(
-                     llvm::ConstantInt::get(
-                         IntPtrTy,
-                         llvm::ConstantPtrAuth::AddrDiscriminator_CtorsDtors),
-                     PtrTy)
-               : nullptr);
-      llvm::Constant *SignedCtorPtr = getConstantSignedPointer(
-          I.Initializer, InitFiniAuthSchema.getKey(), StorageAddress,
-          llvm::ConstantInt::get(
-              SizeTy, InitFiniAuthSchema.getConstantDiscrimination()));
-      Ctor.add(SignedCtorPtr);
-    } else {
-      Ctor.add(I.Initializer);
-    }
+    Ctor.add(I.Initializer);
     if (I.AssociatedData)
       Ctor.add(I.AssociatedData);
     else
diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp
index c6e8644905964..1bf6f015134d1 100644
--- a/clang/lib/Frontend/CompilerInvocation.cpp
+++ b/clang/lib/Frontend/CompilerInvocation.cpp
@@ -1475,12 +1475,6 @@ void CompilerInvocation::setDefaultPointerAuthOptions(
     Opts.CXXMemberFunctionPointers =
         PointerAuthSchema(Key::ASIA, false, Discrimination::Type);
 
-    if (LangOpts.PointerAuthInitFini) {
-      Opts.InitFiniPointers = PointerAuthSchema(
-          Key::ASIA, LangOpts.PointerAuthInitFiniAddressDiscrimination,
-          Discrimination::Constant, InitFiniPointerConstantDiscriminator);
-    }
-
     Opts.BlockInvocationFunctionPointers =
         PointerAuthSchema(Key::ASIA, true, Discrimination::None);
     Opts.BlockHelperFunctionPointers =
diff --git a/clang/test/CodeGen/ptrauth-init-fini.c b/clang/test/CodeGen/ptrauth-init-fini.c
index 1e8953961d64e..466d992299226 100644
--- a/clang/test/CodeGen/ptrauth-init-fini.c
+++ b/clang/test/CodeGen/ptrauth-init-fini.c
@@ -1,28 +1,36 @@
 // REQUIRES: aarch64-registered-target
 
 // RUN: %clang_cc1 -triple aarch64-elf -target-feature +pauth -fptrauth-calls -fptrauth-init-fini    \
-// RUN:   -emit-llvm %s -o - | FileCheck --check-prefix=SIGNED %s
+// RUN:   -emit-llvm %s -o - | FileCheck --check-prefix=COMMON,SIGNED %s
 
 // RUN: %clang_cc1 -triple aarch64-elf -target-feature +pauth -fptrauth-calls -fptrauth-init-fini    \
-// RUN:   -fptrauth-init-fini-address-discrimination -emit-llvm %s -o - | FileCheck --check-prefix=ADDRDISC %s
+// RUN:   -fptrauth-init-fini-address-discrimination -emit-llvm %s -o - | FileCheck --check-prefix=COMMON,ADDRDISC %s
 
 // RUN: %clang_cc1 -triple aarch64-elf -target-feature +pauth -fptrauth-calls \
-// RUN:   -emit-llvm %s -o - | FileCheck --check-prefix=UNSIGNED %s
+// RUN:   -emit-llvm %s -o - | FileCheck --check-prefix=COMMON,UNSIGNED %s
 
 // RUN: %clang_cc1 -triple aarch64-elf -target-feature +pauth -fptrauth-calls -fptrauth-init-fini-address-discrimination \
-// RUN:   -emit-llvm %s -o - | FileCheck --check-prefix=UNSIGNED %s
+// RUN:   -emit-llvm %s -o - | FileCheck --check-prefix=COMMON,UNSIGNED %s
 
 // RUN: %clang_cc1 -triple aarch64-elf -target-feature +pauth                 -fptrauth-init-fini    \
-// RUN:   -emit-llvm %s -o - | FileCheck --check-prefix=UNSIGNED %s
+// RUN:   -emit-llvm %s -o - | FileCheck --check-prefix=COMMON,UNSIGNED %s
 
-// SIGNED: @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr ptrauth (ptr @foo, i32 0, i64 55764), ptr null }]
-// SIGNED: @llvm.global_dtors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr ptrauth (ptr @bar, i32 0, i64 55764), ptr null }]
+// COMMON: @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @foo, ptr null }]
+// COMMON: @llvm.global_dtors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @bar, ptr null }]
 
-// ADDRDISC: @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr ptrauth (ptr @foo, i32 0, i64 55764, ptr inttoptr (i64 1 to ptr)), ptr null }]
-// ADDRDISC: @llvm.global_dtors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr ptrauth (ptr @bar, i32 0, i64 55764, ptr inttoptr (i64 1 to ptr)), ptr null }]
+// The below checks assume no other module flags happens to be set.
 
-// UNSIGNED: @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @foo, ptr null }]
-// UNSIGNED: @llvm.global_dtors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @bar, ptr null }]
+// UNSIGNED-NOT: !llvm.module.flags
+// UNSIGNED-NOT: !"ptrauth-init-fini"
+// UNSIGNED-NOT: !"ptrauth-init-fini-address-discrimination"
+
+// SIGNED: !llvm.module.flags = !{!0}
+// SIGNED: !0 = !{i32 1, !"ptrauth-init-fini", i32 1}
+// SIGNED-NOT: !"ptrauth-init-fini-address-discrimination"
+
+// ADDRDISC: !llvm.module.flags = !{!0, !1}
+// ADDRDISC: !0 = !{i32 1, !"ptrauth-init-fini", i32 1}
+// ADDRDISC: !1 = !{i32 1, !"ptrauth-init-fini-address-discrimination", i32 1}
 
 volatile int x = 0;
 
diff --git a/llvm/lib/IR/AutoUpgrade.cpp b/llvm/lib/IR/AutoUpgrade.cpp
index 2728897372009..e303922d4765c 100644
--- a/llvm/lib/IR/AutoUpgrade.cpp
+++ b/llvm/lib/IR/AutoUpgrade.cpp
@@ -6165,12 +6165,109 @@ void llvm::UpgradeARCRuntime(Module &M) {
     UpgradeToIntrinsic(I.first, I.second);
 }
 
+// Upgrade from wrapping each pointer stored in the @llvm.global_(ctors|dtors)
+// with ptrauth constant expression to storing plain pointers in these arrays
+// and requesting the particular signing schema globally via module flags.
+//
+// Only perform the upgrade if all elements of *both* arrays agree on a common
+// signing schema. The processing of the array is *not* stopped on the first
+// null function pointer.
+static bool upgradePtrauthInitFiniArrays(Module &M) {
+  // Either "not decided yet" or whether we should request address diversity
+  // in addition to the basic constant diversity.
+  // There is no value representing "decided not to sign", as this results
+  // in immediate return from upgradePtrauthInitFiniArrays.
+  std::optional<bool> UseAddressDisc;
+
+  // Do not attempt upgrading if the new module flags already exist.
+  if (const NamedMDNode *ModFlags = M.getModuleFlagsMetadata()) {
+    for (const MDNode *Flag : ModFlags->operands()) {
+      if (Flag->getNumOperands() != 3)
+        continue;
+      const MDString *ID = dyn_cast_or_null<MDString>(Flag->getOperand(1));
+      if (ID && (ID->getString() == "ptrauth-init-fini" ||
+                 ID->getString() == "ptrauth-init-fini-address-discriminator"))
+        return false;
+    }
+  }
+
+  auto UpgradeSinglePointer = [&UseAddressDisc](Constant *CV) -> Constant * {
+    const unsigned ExpectedConstDisc = 0xD9D4;
+    const unsigned ExpectedAddressMarker = 1;
+
+    auto *CPA = dyn_cast<ConstantPtrAuth>(CV);
+    if (!CPA || !CPA->getDiscriminator()->equalsInt(ExpectedConstDisc))
+      return nullptr; // Nothing to upgrade or unknown pattern found.
+
+    bool HasAddressDisc;
+    if (!CPA->hasAddressDiscriminator())
+      HasAddressDisc = false;
+    else if (CPA->hasSpecialAddressDiscriminator(ExpectedAddressMarker))
+      HasAddressDisc = true;
+    else
+      return nullptr; // Unknown pattern.
+
+    if (UseAddressDisc && *UseAddressDisc != HasAddressDisc)
+      return nullptr; // Disagreement with the decided mode.
+
+    UseAddressDisc = HasAddressDisc;
+    return CPA->getPointer();
+  };
+
+  SmallVector<std::pair<GlobalVariable *, Constant *>> PendingUpgrades;
+  for (const char *Name : {"llvm.global_ctors", "llvm.global_dtors"}) {
+    auto *GV = dyn_cast_if_present<GlobalVariable>(M.getNamedValue(Name));
+    if (!GV || !GV->hasInitializer())
+      continue; // Skip, but it is okay to upgrade the other variable.
+
+    auto *Init = dyn_cast<ConstantArray>(GV->getInitializer());
+    if (!Init)
+      return false;
+
+    std::vector<Constant *> NewStructors;
+    NewStructors.reserve(Init->getNumOperands());
+    for (Use &U : Init->operands()) {
+      auto *Structor = dyn_cast<ConstantStruct>(U.get());
+      if (!Structor || Structor->getNumOperands() != 3)
+        return false;
+
+      Constant *Prio = Structor->getOperand(0);
+      Constant *Func = UpgradeSinglePointer(Structor->getOperand(1));
+      Constant *Arg = Structor->getOperand(2);
+      if (!Func)
+        return false;
+
+      NewStructors.push_back(
+          ConstantStruct::get(Structor->getType(), {Prio, Func, Arg}));
+    }
+
+    Constant *NewInit = ConstantArray::get(Init->getType(), NewStructors);
+    PendingUpgrades.push_back({GV, NewInit});
+  }
+
+  if (PendingUpgrades.empty())
+    return false;
+  assert(UseAddressDisc.has_value());
+
+  for (auto [GV, NewInit] : PendingUpgrades)
+    GV->setInitializer(NewInit);
+  M.addModuleFlag(Module::Error, "ptrauth-init-fini", 1);
+  if (UseAddressDisc.value())
+    M.addModuleFlag(Module::Error, "ptrauth-init-fini-address-discriminator",
+                    1);
+
+  return true;
+}
+
 bool llvm::UpgradeModuleFlags(Module &M) {
+  bool Changed = false;
+  Changed |= upgradePtrauthInitFiniArrays(M);
+
   NamedMDNode *ModFlags = M.getModuleFlagsMetadata();
   if (!ModFlags)
-    return false;
+    return Changed;
 
-  bool HasObjCFlag = false, HasClassProperties = false, Changed = false;
+  bool HasObjCFlag = false, HasClassProperties = false;
   bool HasSwiftVersionFlag = false;
   uint8_t SwiftMajorVersion, SwiftMinorVersion;
   uint32_t SwiftABIVersion;
diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp
index 9dba574f6e9eb..5665c60224dc6 100644
--- a/llvm/lib/IR/Verifier.cpp
+++ b/llvm/lib/IR/Verifier.cpp
@@ -910,6 +910,19 @@ void Verifier::visitGlobalVariable(const GlobalVariable &GV) {
       Check(ETy->isPointerTy(), "wrong type for intrinsic global variable",
             &GV);
     }
+
+    auto *Init = GV.hasInitializer()
+                     ? dyn_cast<ConstantArray>(GV.getInitializer())
+                     : nullptr;
+    if (Init) {
+      for (const Use &U : Init->operands()) {
+        auto *Structor = dyn_cast<ConstantStruct>(U);
+        if (!Structor || Structor->getNumOperands() != 3)
+          continue;
+        Check(!isa<ConstantPtrAuth>(Structor->getOperand(1)),
+              "signing of ctors/dtors should be requested via module flags");
+      }
+    }
   }
 
   if (GV.hasName() && (GV.getName() == "llvm.used" ||
@@ -1961,26 +1974,49 @@ void Verifier::visitModuleFlags() {
   // Scan each flag, and track the flags and requirements.
   DenseMap<const MDString*, const MDNode*> SeenIDs;
   SmallVector<const MDNode*, 16> Requirements;
-  uint64_t PAuthABIPlatform = -1;
-  uint64_t PAuthABIVersion = -1;
+  std::optional<uint64_t> PAuthABIPlatform;
+  std::optional<uint64_t> PAuthABIVersion;
+  std::optional<uint64_t> HasPtrauthInitFini;
+  std::optional<uint64_t> HasPtrauthInitFiniAddr;
+
   for (const MDNode *MDN : Flags->operands()) {
     visitModuleFlag(MDN, SeenIDs, Requirements);
     if (MDN->getNumOperands() != 3)
       continue;
+
     if (const auto *FlagName = dyn_cast_or_null<MDString>(MDN->getOperand(1))) {
-      if (FlagName->getString() == "aarch64-elf-pauthabi-platform") {
-        if (const auto *PAP =
+      auto GetFlagNamed = [&](StringRef Name) -> std::optional<uint64_t> {
+        if (FlagName->getString() != Name)
+          return std::nullopt;
+        if (const auto *FlagValue =
                 mdconst::dyn_extract_or_null<ConstantInt>(MDN->getOperand(2)))
-          PAuthABIPlatform = PAP->getZExtValue();
-      } else if (FlagName->getString() == "aarch64-elf-pauthabi-version") {
-        if (const auto *PAV =
-                mdconst::dyn_extract_or_null<ConstantInt>(MDN->getOperand(2)))
-          PAuthABIVersion = PAV->getZExtValue();
-      }
+          return FlagValue->getZExtValue();
+
+        CheckFailed(Name + ": module flag expects integer value");
+        return std::nullopt;
+      };
+
+      if (auto Value = GetFlagNamed("aarch64-elf-pauthabi-platform"))
+        PAuthABIPlatform = *Value;
+      else if (auto Value = GetFlagNamed("aarch64-elf-pauthabi-version"))
+        PAuthABIVersion = *Value;
+      else if (auto Value = GetFlagNamed("ptrauth-init-fini"))
+        HasPtrauthInitFini = *Value;
+      else if (auto Value =
+                   GetFlagNamed("ptrauth-init-fini-address-discrimination"))
+        HasPtrauthInitFiniAddr = *Value;
     }
   }
 
-  if ((PAuthABIPlatform == uint64_t(-1)) != (PAuthABIVersion == uint64_t(-1)))
+  Check(!HasPtrauthInitFini || HasPtrauthInitFini.value() == 1,
+        "ptrauth-init-fini must be set to 1 or unset");
+  Check(!HasPtrauthInitFiniAddr || HasPtrauthInitFiniAddr.value() == 1,
+        "ptrauth-init-fini-address-discrimination must be set to 1 or unset");
+  if (HasPtrauthInitFiniAddr)
+    Check(HasPtrauthInitFini, "ptrauth-init-fini-address-discrimination module "
+                              "flag requires ptrauth-init-fini");
+
+  if (PAuthABIPlatform.has_value() != PAuthABIVersion.has_value())
     CheckFailed("either both or no 'aarch64-elf-pauthabi-platform' and "
                 "'aarch64-elf-pauthabi-version' module flags must be present");
 
diff --git a/llvm/lib/Target/AArch64/AArch64AsmPrinter.cpp b/llvm/lib/Target/AArch64/AArch64AsmPrinter.cpp
index 97edd896c5992..557f79999a88a 100644
--- a/llvm/lib/Target/AArch64/AArch64AsmPrinter.cpp
+++ b/llvm/lib/Target/AArch64/AArch64AsmPrinter.cpp
@@ -98,6 +98,8 @@ class AArch64AsmPrinter : public AsmPrinter {
   FaultMaps FM;
   const AArch64Subtarget *STI;
   bool ShouldEmitWeakSwiftAsyncExtendedFramePointerFlags = false;
+  bool PtrauthInitFini = false;
+  bool PtrauthInitFiniAddressDisc = false;
 #ifndef NDEBUG
   unsigned InstsEmitted;
 #endif
@@ -405,6 +407,11 @@ void AArch64AsmPrinter::emitStartOfAsmFile(Module &M) {
       EnableImportCallOptimization = true;
   }
 
+  if (M.getModuleFlag("ptrauth-init-fini"))
+    PtrauthInitFini = true;
+  if (M.getModuleFlag("ptrauth-init-fini-address-discrimination"))
+    PtrauthInitFiniAddressDisc = true;
+
   if (!TT.isOSBinFormatELF())
     return;
 
@@ -1464,18 +1471,31 @@ void AArch64AsmPrinter::emitFunctionEntryLabel() {
 
 void AArch64AsmPrinter::emitXXStructor(const DataLayout &DL,
                                        const Constant *CV) {
-  if (const auto *CPA = dyn_cast<ConstantPtrAuth>(CV))
-    if (CPA->hasAddressDiscriminator() &&
-        !CPA->hasSpecialAddressDiscriminator(
-            ConstantPtrAuth::AddrDiscriminator_CtorsDtors))
-      report_fatal_error(
-          "unexpected address discrimination value for ctors/dtors entry, only "
-          "'ptr inttoptr (i64 1 to ptr)' is allowed");
-  // If we have signed pointers in xxstructors list, they'll be lowered to @AUTH
-  // MCExpr's via AArch64AsmPrinter::lowerConstantPtrAuth. It does not look at
-  // actual address discrimination value and only checks
-  // hasAddressDiscriminator(), so it's OK to leave special address
-  // discrimination value here.
+  LLVMContext &C = CV->getContext();
+  assert(!isa<ConstantPtrAuth>(CV) &&
+         "ctors/dtors are to be signed by asm printer");
+
+  if (PtrauthInitFini) {
+    IntegerType *Int32Ty = IntegerType::get(C, 32);
+    IntegerType *Int64Ty = IntegerType::get(C, 64);
+    PointerType *PtrTy = PointerType::get(C, 0);
+
+    ConstantInt *Key = ConstantInt::get(Int32Ty, AArch64PAuth::InitFiniKey);
+    ConstantInt *IntDisc = ConstantInt::get(
+        Int64Ty, AArch64PAuth::InitFiniPointerConstantDiscriminator);
+    Constant *Null = ConstantPointerNull::get(PtrTy);
+    Constant *AddressDisc = Null;
+    if (PtrauthInitFiniAddressDisc) {
+      uint64_t Marker = ConstantPtrAuth::AddrDiscriminator_CtorsDtors;
+      AddressDisc =
+          ConstantExpr::getIntToPtr(ConstantInt::get(Int64Ty, Marker), PtrTy);
+    }
+
+    CV = ConstantPtrAuth::get(const_cast<Constant *>(CV), Key, IntDisc,
+                              AddressDisc, /*DeactivationSymbol=*/Null);
+  }
+
+  // Signed pointers will be lowered by AArch64AsmPrinter::lowerConstantPtrAuth.
   AsmPrinter::emitXXStructor(DL, CV);
 }
 
diff --git a/llvm/lib/Target/AArch64/AArch64PointerAuth.h b/llvm/lib/Target/AArch64/AArch64PointerAuth.h
index d6947f0d22aac..1e69558e996d1 100644
--- a/llvm/lib/Target/AArch64/AArch64PointerAuth.h
+++ b/llvm/lib/Target/AArch64/AArch64PointerAuth.h
@@ -9,12 +9,18 @@
 #ifndef LLVM_LIB_TARGET_AARCH64_AARCH64POINTERAUTH_H
 #define LLVM_LIB_TARGET_AARCH64_AARCH64POINTERAUTH_H
 
-#include "llvm/CodeGen/MachineBasicBlock.h"
-#include "llvm/CodeGen/Register.h"
+#include "Utils/AArch64BaseInfo.h"
 
 namespace llvm {
 namespace AArch64PAuth {
 
+/// PAuth key to be used with function pointers in .init_array and .fini_array.
+constexpr AArch64PACKey::ID InitFiniKey = AArch64PACKey::IA;
+
+/// Constant discriminator to be used with function pointers in .init_array and
+/// .fini_array. The value is ptrauth_string_discriminator("init_fini")
+constexpr unsigned InitFiniPointerConstantDiscriminator = 0xD9D4;
+
 /// Variants of check performed on an authenticated pointer.
 ///
 /// In cases such as authenticating the LR value when performing a tail call
diff --git a/llvm/test/CodeGen/AArch64/ptrauth-init-fini-autoupgrade.ll b/llvm/test/CodeGen/AArch64/ptrauth-init-fini-autoupgrade.ll
new file mode 100644
index 0000000000000..68efd9c2a8b7e
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/ptrauth-init-fini-autoupgrade.ll
@@ -0,0 +1,143 @@
+; RUN: rm -rf %t && split-file %s %t && cd %t
+
+;--- nodisc.ll
+
+; RUN: opt -S < nodisc.ll | FileCheck %s --check-prefix=NODISC
+
+@llvm.global_ctors = appending global [2 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr ptrauth (ptr @foo1, i32 0, i64 55764), ptr null }, { i32, ptr, ptr } { i32 ...
[truncated]

@atrosinenko atrosinenko marked this pull request as draft April 20, 2026 21:58
if (LangOpts.PointerAuthELFGOT)
getModule().addModuleFlag(llvm::Module::Error, "ptrauth-elf-got", 1);
if (LangOpts.PointerAuthCalls && LangOpts.PointerAuthInitFini) {
getModule().addModuleFlag(llvm::Module::Error, "ptrauth-init-fini", 1);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Error doesn't do anything useful when you use it like this...

Maybe consider:

    if (LangOpts.PointerAuthCalls) {
      getModule().addModuleFlag(llvm::Module::Error, "ptrauth-init-fini",
            LangOpts.PointerAuthInitFini);
      getModule().addModuleFlag(
            llvm::Module::Error, "ptrauth-init-fini-address-discrimination",
            LangOpts.PointerAuthInitFiniAddressDiscrimination);
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend:AArch64 clang:codegen IR generation bugs: mangling, exceptions, etc. clang:frontend Language frontend issues, e.g. anything involving "Sema" llvm:ir

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants