Skip to content

[LLD][ELF][AArch64] Add support for SHF_AARCH64_PURECODE ELF section flag (3/3) #125689

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

Merged
merged 13 commits into from
Feb 21, 2025

Conversation

Il-Capitano
Copy link
Contributor

@Il-Capitano Il-Capitano commented Feb 4, 2025

Add support for the new SHF_AARCH64_PURECODE ELF section flag: ARM-software/abi-aa#304

The general implementation follows the existing one for ARM targets. The output section only has the SHF_AARCH64_PURECODE flag set if all input sections have it set.

Related PRs:

@llvmbot
Copy link
Member

llvmbot commented Feb 4, 2025

@llvm/pr-subscribers-llvm-binary-utilities
@llvm/pr-subscribers-backend-aarch64
@llvm/pr-subscribers-lld-elf
@llvm/pr-subscribers-objectyaml
@llvm/pr-subscribers-mc

@llvm/pr-subscribers-lld

Author: Csanád Hajdú (Il-Capitano)

Changes

Add support for the new SHF_AARCH64_PURECODE ELF section flag: ARM-software/abi-aa#304

The general implementation follows the existing one for ARM targets. The output section only has the SHF_AARCH64_PURECODE flag set if all input sections have it set.

Depends on #125687. The changes in that PR are included here as well for now, but will be removed once that gets merged.


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

23 Files Affected:

  • (modified) lld/ELF/Config.h (+1)
  • (modified) lld/ELF/Driver.cpp (+44-4)
  • (modified) lld/ELF/OutputSections.cpp (+7-3)
  • (modified) lld/ELF/ScriptParser.cpp (+1)
  • (added) lld/test/ELF/aarch64-execute-only.s (+33)
  • (modified) llvm/include/llvm/BinaryFormat/ELF.h (+4-1)
  • (modified) llvm/lib/CodeGen/TargetLoweringObjectFileImpl.cpp (+8-5)
  • (modified) llvm/lib/MC/MCParser/ELFAsmParser.cpp (+5-2)
  • (modified) llvm/lib/MC/MCSectionELF.cpp (+3)
  • (modified) llvm/lib/ObjectYAML/ELFYAML.cpp (+3)
  • (modified) llvm/lib/Target/AArch64/AArch64Features.td (+5)
  • (modified) llvm/lib/Target/AArch64/AArch64TargetObjectFile.cpp (+26)
  • (modified) llvm/lib/Target/AArch64/AArch64TargetObjectFile.h (+6)
  • (modified) llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp (+16-1)
  • (modified) llvm/lib/Target/AArch64/AArch64TargetTransformInfo.h (+2)
  • (modified) llvm/lib/Target/AArch64/MCTargetDesc/AArch64ELFStreamer.cpp (+18)
  • (added) llvm/test/CodeGen/AArch64/execute-only-section.ll (+21)
  • (added) llvm/test/MC/ELF/AArch64/execute-only-populated-text-section.s (+27)
  • (added) llvm/test/MC/ELF/AArch64/execute-only-section.s (+43)
  • (added) llvm/test/MC/ELF/AArch64/execute-only-text-section-data.s (+27)
  • (modified) llvm/test/MC/ELF/section-flags-unknown.s (+3)
  • (modified) llvm/test/Transforms/Inline/AArch64/inline-target-attr.ll (+25)
  • (modified) llvm/tools/llvm-readobj/ELFDumper.cpp (+8)
diff --git a/lld/ELF/Config.h b/lld/ELF/Config.h
index c2aadb2cef5200f..ecfb492522227bc 100644
--- a/lld/ELF/Config.h
+++ b/lld/ELF/Config.h
@@ -229,6 +229,7 @@ struct Config {
   StringRef zCetReport = "none";
   StringRef zPauthReport = "none";
   StringRef zGcsReport = "none";
+  StringRef zExecuteOnlyReport = "none";
   bool ltoBBAddrMap;
   llvm::StringRef ltoBasicBlockSections;
   std::pair<llvm::StringRef, llvm::StringRef> thinLTOObjectSuffixReplace;
diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp
index acbc97b331b0e1c..f9c61584984a8f5 100644
--- a/lld/ELF/Driver.cpp
+++ b/lld/ELF/Driver.cpp
@@ -459,6 +459,8 @@ static void checkOptions(Ctx &ctx) {
       ErrAlways(ctx) << "-z gcs-report only supported on AArch64";
     if (ctx.arg.zGcs != GcsPolicy::Implicit)
       ErrAlways(ctx) << "-z gcs only supported on AArch64";
+    if (ctx.arg.zExecuteOnlyReport != "none")
+      ErrAlways(ctx) << "-z execute-only-report only supported on AArch64";
   }
 
   if (ctx.arg.emachine != EM_386 && ctx.arg.emachine != EM_X86_64 &&
@@ -1557,10 +1559,12 @@ static void readConfigs(Ctx &ctx, opt::InputArgList &args) {
       ErrAlways(ctx) << errPrefix << pat.takeError() << ": " << kv.first;
   }
 
-  auto reports = {std::make_pair("bti-report", &ctx.arg.zBtiReport),
-                  std::make_pair("cet-report", &ctx.arg.zCetReport),
-                  std::make_pair("gcs-report", &ctx.arg.zGcsReport),
-                  std::make_pair("pauth-report", &ctx.arg.zPauthReport)};
+  auto reports = {
+      std::make_pair("bti-report", &ctx.arg.zBtiReport),
+      std::make_pair("cet-report", &ctx.arg.zCetReport),
+      std::make_pair("gcs-report", &ctx.arg.zGcsReport),
+      std::make_pair("pauth-report", &ctx.arg.zPauthReport),
+      std::make_pair("execute-only-report", &ctx.arg.zExecuteOnlyReport)};
   for (opt::Arg *arg : args.filtered(OPT_z)) {
     std::pair<StringRef, StringRef> option =
         StringRef(arg->getValue()).split('=');
@@ -2844,6 +2848,40 @@ static void readSecurityNotes(Ctx &ctx) {
     ctx.arg.andFeatures &= ~GNU_PROPERTY_AARCH64_FEATURE_1_GCS;
 }
 
+static void checkExecuteOnly(Ctx &ctx) {
+  if (ctx.arg.emachine != EM_AARCH64)
+    return;
+
+  auto report = [&](StringRef config) -> ELFSyncStream {
+    if (config == "error")
+      return {ctx, DiagLevel::Err};
+    if (config == "warning")
+      return {ctx, DiagLevel::Warn};
+    return {ctx, DiagLevel::None};
+  };
+  auto reportUnless = [&](StringRef config, bool cond) -> ELFSyncStream {
+    if (cond)
+      return {ctx, DiagLevel::None};
+    return report(config);
+  };
+
+  for (ELFFileBase *file : ctx.objectFiles) {
+    for (InputSectionBase *section : file->getSections()) {
+      // Only check for executable sections.
+      if (!(section && section->flags & SHF_EXECINSTR))
+        continue;
+
+      OutputSection *outputSection = section->getOutputSection();
+      if (outputSection && outputSection->name == ".text") {
+        reportUnless(ctx.arg.zExecuteOnlyReport,
+                     section->flags & SHF_AARCH64_PURECODE)
+            << file << ": -z execute-only-report: section " << section->name
+            << " does not have SHF_AARCH64_PURECODE flag set";
+      }
+    }
+  }
+}
+
 static void initSectionsAndLocalSyms(ELFFileBase *file, bool ignoreComdats) {
   switch (file->ekind) {
   case ELF32LEKind:
@@ -3213,6 +3251,8 @@ template <class ELFT> void LinkerDriver::link(opt::InputArgList &args) {
     ctx.script->addOrphanSections();
   }
 
+  checkExecuteOnly(ctx);
+
   {
     llvm::TimeTraceScope timeScope("Merge/finalize input sections");
 
diff --git a/lld/ELF/OutputSections.cpp b/lld/ELF/OutputSections.cpp
index a2da5543d58670f..c8dc0e5fe033528 100644
--- a/lld/ELF/OutputSections.cpp
+++ b/lld/ELF/OutputSections.cpp
@@ -42,7 +42,8 @@ using namespace lld::elf;
 
 uint32_t OutputSection::getPhdrFlags() const {
   uint32_t ret = 0;
-  if (ctx.arg.emachine != EM_ARM || !(flags & SHF_ARM_PURECODE))
+  if ((ctx.arg.emachine != EM_ARM || !(flags & SHF_ARM_PURECODE)) &&
+      (ctx.arg.emachine != EM_AARCH64 || !(flags & SHF_AARCH64_PURECODE)))
     ret |= PF_R;
   if (flags & SHF_WRITE)
     ret |= PF_W;
@@ -161,8 +162,11 @@ void OutputSection::commitSection(InputSection *isec) {
   }
 
   isec->parent = this;
-  uint64_t andMask =
-      ctx.arg.emachine == EM_ARM ? (uint64_t)SHF_ARM_PURECODE : 0;
+  uint64_t andMask = 0;
+  if (ctx.arg.emachine == EM_ARM)
+    andMask |= (uint64_t)SHF_ARM_PURECODE;
+  if (ctx.arg.emachine == EM_AARCH64)
+    andMask |= (uint64_t)SHF_AARCH64_PURECODE;
   uint64_t orMask = ~andMask;
   uint64_t andFlags = (flags & isec->flags) & andMask;
   uint64_t orFlags = (flags | isec->flags) & orMask;
diff --git a/lld/ELF/ScriptParser.cpp b/lld/ELF/ScriptParser.cpp
index a10af9565a1d631..671a5ec4deccba3 100644
--- a/lld/ELF/ScriptParser.cpp
+++ b/lld/ELF/ScriptParser.cpp
@@ -1411,6 +1411,7 @@ static std::optional<uint64_t> parseFlag(StringRef tok) {
       .Case(CASE_ENT(SHF_COMPRESSED))
       .Case(CASE_ENT(SHF_EXCLUDE))
       .Case(CASE_ENT(SHF_ARM_PURECODE))
+      .Case(CASE_ENT(SHF_AARCH64_PURECODE))
       .Default(std::nullopt);
 #undef CASE_ENT
 }
diff --git a/lld/test/ELF/aarch64-execute-only.s b/lld/test/ELF/aarch64-execute-only.s
new file mode 100644
index 000000000000000..e67bbf05ff2e215
--- /dev/null
+++ b/lld/test/ELF/aarch64-execute-only.s
@@ -0,0 +1,33 @@
+// REQUIRES: aarch64
+
+// RUN: llvm-mc -filetype=obj -triple=aarch64-linux-none %s -o %t.o
+// RUN: ld.lld %t.o -o %t.so -shared
+// RUN: llvm-readelf -l %t.so | FileCheck --implicit-check-not=LOAD %s
+
+// RUN: echo ".section .foo,\"ax\"; \
+// RUN:       ret" > %t.s
+// RUN: llvm-mc -filetype=obj -triple=aarch64-linux-none %t.s -o %t2.o
+// RUN: ld.lld %t.o %t2.o -o %t.so -shared
+// RUN: llvm-readelf -l %t.so | FileCheck --check-prefix=DIFF --implicit-check-not=LOAD %s
+
+// CHECK:      LOAD           0x000000 0x0000000000000000 0x0000000000000000 0x000245 0x000245 R   0x10000
+// CHECK:      LOAD           0x000248 0x0000000000010248 0x0000000000010248 0x{{.*}} 0x{{.*}} R E 0x10000
+// CHECK:      LOAD           0x00024c 0x000000000002024c 0x000000000002024c 0x{{.*}} 0x{{.*}}   E 0x10000
+// CHECK:      LOAD           0x000250 0x0000000000030250 0x0000000000030250 0x000070 0x000db0 RW  0x10000
+
+// CHECK: 01     .dynsym .gnu.hash .hash .dynstr
+// CHECK: 02     .text
+// CHECK: 03     .foo
+// CHECK: 04     .dynamic
+
+// DIFF:      LOAD           0x000000 0x0000000000000000 0x0000000000000000 0x00020d 0x00020d R   0x10000
+// DIFF:      LOAD           0x000210 0x0000000000010210 0x0000000000010210 0x00000c 0x00000c R E 0x10000
+// DIFF:      LOAD           0x000220 0x0000000000020220 0x0000000000020220 0x000070 0x000de0 RW  0x10000
+
+// DIFF: 01     .dynsym .gnu.hash .hash .dynstr
+// DIFF: 02     .text .foo
+// DIFF: 03     .dynamic
+
+        ret
+        .section .foo,"axy"
+        ret
diff --git a/llvm/include/llvm/BinaryFormat/ELF.h b/llvm/include/llvm/BinaryFormat/ELF.h
index 8853c4a88b0b593..a5979d029a9eafd 100644
--- a/llvm/include/llvm/BinaryFormat/ELF.h
+++ b/llvm/include/llvm/BinaryFormat/ELF.h
@@ -1300,7 +1300,10 @@ enum : unsigned {
   SHF_MIPS_STRING = 0x80000000,
 
   // Make code section unreadable when in execute-only mode
-  SHF_ARM_PURECODE = 0x20000000
+  SHF_ARM_PURECODE = 0x20000000,
+
+  // Section contains only program instructions and no program data.
+  SHF_AARCH64_PURECODE = 0x20000000
 };
 
 // Section Group Flags
diff --git a/llvm/lib/CodeGen/TargetLoweringObjectFileImpl.cpp b/llvm/lib/CodeGen/TargetLoweringObjectFileImpl.cpp
index 3c2c7c8c9fed69a..6cfebca9a4d576e 100644
--- a/llvm/lib/CodeGen/TargetLoweringObjectFileImpl.cpp
+++ b/llvm/lib/CodeGen/TargetLoweringObjectFileImpl.cpp
@@ -547,7 +547,7 @@ static unsigned getELFSectionType(StringRef Name, SectionKind K) {
   return ELF::SHT_PROGBITS;
 }
 
-static unsigned getELFSectionFlags(SectionKind K) {
+static unsigned getELFSectionFlags(SectionKind K, const Triple &T) {
   unsigned Flags = 0;
 
   if (!K.isMetadata() && !K.isExclude())
@@ -559,9 +559,12 @@ static unsigned getELFSectionFlags(SectionKind K) {
   if (K.isText())
     Flags |= ELF::SHF_EXECINSTR;
 
-  if (K.isExecuteOnly())
+  if ((T.isARM() || T.isThumb()) && K.isExecuteOnly())
     Flags |= ELF::SHF_ARM_PURECODE;
 
+  if (T.isAArch64() && K.isExecuteOnly())
+    Flags |= ELF::SHF_AARCH64_PURECODE;
+
   if (K.isWriteable())
     Flags |= ELF::SHF_WRITE;
 
@@ -840,7 +843,7 @@ static MCSection *selectExplicitSectionGlobal(const GlobalObject *GO,
   // Infer section flags from the section name if we can.
   Kind = getELFKindForNamedSection(SectionName, Kind);
 
-  unsigned Flags = getELFSectionFlags(Kind);
+  unsigned Flags = getELFSectionFlags(Kind, TM.getTargetTriple());
   auto [Group, IsComdat, ExtraFlags] = getGlobalObjectInfo(GO, TM);
   Flags |= ExtraFlags;
 
@@ -947,7 +950,7 @@ static MCSection *selectELFSectionForGlobal(
 
 MCSection *TargetLoweringObjectFileELF::SelectSectionForGlobal(
     const GlobalObject *GO, SectionKind Kind, const TargetMachine &TM) const {
-  unsigned Flags = getELFSectionFlags(Kind);
+  unsigned Flags = getELFSectionFlags(Kind, TM.getTargetTriple());
 
   // If we have -ffunction-section or -fdata-section then we should emit the
   // global value to a uniqued section specifically for it.
@@ -967,7 +970,7 @@ MCSection *TargetLoweringObjectFileELF::SelectSectionForGlobal(
 MCSection *TargetLoweringObjectFileELF::getUniqueSectionForFunction(
     const Function &F, const TargetMachine &TM) const {
   SectionKind Kind = SectionKind::getText();
-  unsigned Flags = getELFSectionFlags(Kind);
+  unsigned Flags = getELFSectionFlags(Kind, TM.getTargetTriple());
   // If the function's section names is pre-determined via pragma or a
   // section attribute, call selectExplicitSectionGlobal.
   if (F.hasSection())
diff --git a/llvm/lib/MC/MCParser/ELFAsmParser.cpp b/llvm/lib/MC/MCParser/ELFAsmParser.cpp
index b58210b3c268e99..99b13c68a9966c7 100644
--- a/llvm/lib/MC/MCParser/ELFAsmParser.cpp
+++ b/llvm/lib/MC/MCParser/ELFAsmParser.cpp
@@ -328,9 +328,12 @@ static unsigned parseSectionFlags(const Triple &TT, StringRef flagsStr,
       flags |= ELF::XCORE_SHF_DP_SECTION;
       break;
     case 'y':
-      if (!(TT.isARM() || TT.isThumb()))
+      if (TT.isARM() || TT.isThumb())
+        flags |= ELF::SHF_ARM_PURECODE;
+      else if (TT.isAArch64())
+        flags |= ELF::SHF_AARCH64_PURECODE;
+      else
         return -1U;
-      flags |= ELF::SHF_ARM_PURECODE;
       break;
     case 's':
       if (TT.getArch() != Triple::hexagon)
diff --git a/llvm/lib/MC/MCSectionELF.cpp b/llvm/lib/MC/MCSectionELF.cpp
index 25e62b70b5e2a0c..72a959b1c920825 100644
--- a/llvm/lib/MC/MCSectionELF.cpp
+++ b/llvm/lib/MC/MCSectionELF.cpp
@@ -118,6 +118,9 @@ void MCSectionELF::printSwitchToSection(const MCAsmInfo &MAI, const Triple &T,
   } else if (T.isARM() || T.isThumb()) {
     if (Flags & ELF::SHF_ARM_PURECODE)
       OS << 'y';
+  } else if (T.isAArch64()) {
+    if (Flags & ELF::SHF_AARCH64_PURECODE)
+      OS << 'y';
   } else if (Arch == Triple::hexagon) {
     if (Flags & ELF::SHF_HEX_GPREL)
       OS << 's';
diff --git a/llvm/lib/ObjectYAML/ELFYAML.cpp b/llvm/lib/ObjectYAML/ELFYAML.cpp
index 539834fc8d4dbfe..05e4d85b2ea5d24 100644
--- a/llvm/lib/ObjectYAML/ELFYAML.cpp
+++ b/llvm/lib/ObjectYAML/ELFYAML.cpp
@@ -803,6 +803,9 @@ void ScalarBitSetTraits<ELFYAML::ELF_SHF>::bitset(IO &IO,
     break;
   }
   switch (Object->getMachine()) {
+  case ELF::EM_AARCH64:
+    BCase(SHF_AARCH64_PURECODE);
+    break;
   case ELF::EM_ARM:
     BCase(SHF_ARM_PURECODE);
     break;
diff --git a/llvm/lib/Target/AArch64/AArch64Features.td b/llvm/lib/Target/AArch64/AArch64Features.td
index 20db70ee38572f2..7c95ba662938d9f 100644
--- a/llvm/lib/Target/AArch64/AArch64Features.td
+++ b/llvm/lib/Target/AArch64/AArch64Features.td
@@ -635,6 +635,11 @@ def FeatureStrictAlign : SubtargetFeature<"strict-align",
                                           "Disallow all unaligned memory "
                                           "access">;
 
+def FeatureExecuteOnly : SubtargetFeature<"execute-only",
+                                          "GenExecuteOnly", "true",
+                                          "Enable the generation of "
+                                          "execute only code.">;
+
 foreach i = {1-7,9-15,18,20-28} in
     def FeatureReserveX#i : SubtargetFeature<"reserve-x"#i, "ReserveXRegister["#i#"]", "true",
                                              "Reserve X"#i#", making it unavailable "
diff --git a/llvm/lib/Target/AArch64/AArch64TargetObjectFile.cpp b/llvm/lib/Target/AArch64/AArch64TargetObjectFile.cpp
index 8729fd4b802c8eb..434ae32502d484b 100644
--- a/llvm/lib/Target/AArch64/AArch64TargetObjectFile.cpp
+++ b/llvm/lib/Target/AArch64/AArch64TargetObjectFile.cpp
@@ -148,3 +148,29 @@ MCSymbol *AArch64_MachoTargetObjectFile::getAuthPtrSlotSymbol(
   return getAuthPtrSlotSymbolHelper(getContext(), TM, MMI, MachOMMI, RawSym,
                                     Key, Discriminator);
 }
+
+static bool isExecuteOnlyFunction(const GlobalObject *GO, SectionKind Kind,
+                                  const TargetMachine &TM) {
+  if (const Function *F = dyn_cast<Function>(GO))
+    if (TM.getSubtarget<AArch64Subtarget>(*F).genExecuteOnly() && Kind.isText())
+      return true;
+  return false;
+}
+
+MCSection *AArch64_ELFTargetObjectFile::getExplicitSectionGlobal(
+    const GlobalObject *GO, SectionKind Kind, const TargetMachine &TM) const {
+  // Set execute-only access for the explicit section
+  if (isExecuteOnlyFunction(GO, Kind, TM))
+    Kind = SectionKind::getExecuteOnly();
+
+  return TargetLoweringObjectFileELF::getExplicitSectionGlobal(GO, Kind, TM);
+}
+
+MCSection *AArch64_ELFTargetObjectFile::SelectSectionForGlobal(
+    const GlobalObject *GO, SectionKind Kind, const TargetMachine &TM) const {
+  // Set execute-only access for the explicit section
+  if (isExecuteOnlyFunction(GO, Kind, TM))
+    Kind = SectionKind::getExecuteOnly();
+
+  return TargetLoweringObjectFileELF::SelectSectionForGlobal(GO, Kind, TM);
+}
diff --git a/llvm/lib/Target/AArch64/AArch64TargetObjectFile.h b/llvm/lib/Target/AArch64/AArch64TargetObjectFile.h
index 0c822ac84f200c7..3e9cd51b742a29c 100644
--- a/llvm/lib/Target/AArch64/AArch64TargetObjectFile.h
+++ b/llvm/lib/Target/AArch64/AArch64TargetObjectFile.h
@@ -39,6 +39,12 @@ class AArch64_ELFTargetObjectFile : public TargetLoweringObjectFileELF {
   void emitPersonalityValueImpl(MCStreamer &Streamer, const DataLayout &DL,
                                 const MCSymbol *Sym,
                                 const MachineModuleInfo *MMI) const override;
+
+  MCSection *getExplicitSectionGlobal(const GlobalObject *GO, SectionKind Kind,
+                                      const TargetMachine &TM) const override;
+
+  MCSection *SelectSectionForGlobal(const GlobalObject *GO, SectionKind Kind,
+                                    const TargetMachine &TM) const override;
 };
 
 /// AArch64_MachoTargetObjectFile - This TLOF implementation is used for Darwin.
diff --git a/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp b/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp
index a6edcf125782b2c..284278e34f8b4d0 100644
--- a/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp
+++ b/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp
@@ -262,6 +262,10 @@ bool AArch64TTIImpl::isMultiversionedFunction(const Function &F) const {
   return F.hasFnAttribute("fmv-features");
 }
 
+const FeatureBitset AArch64TTIImpl::InlineInverseFeatures = {
+    AArch64::FeatureExecuteOnly,
+};
+
 bool AArch64TTIImpl::areInlineCompatible(const Function *Caller,
                                          const Function *Callee) const {
   SMEAttrs CallerAttrs(*Caller), CalleeAttrs(*Callee);
@@ -284,7 +288,18 @@ bool AArch64TTIImpl::areInlineCompatible(const Function *Caller,
       return false;
   }
 
-  return BaseT::areInlineCompatible(Caller, Callee);
+  const TargetMachine &TM = getTLI()->getTargetMachine();
+  const FeatureBitset &CallerBits =
+      TM.getSubtargetImpl(*Caller)->getFeatureBits();
+  const FeatureBitset &CalleeBits =
+      TM.getSubtargetImpl(*Callee)->getFeatureBits();
+  // Adjust the feature bitsets by inverting some of the bits. This is needed
+  // for target features that represent restrictions rather than capabilities,
+  // for example "+execute-only".
+  FeatureBitset EffectiveCallerBits = CallerBits ^ InlineInverseFeatures;
+  FeatureBitset EffectiveCalleeBits = CalleeBits ^ InlineInverseFeatures;
+
+  return (EffectiveCallerBits & EffectiveCalleeBits) == EffectiveCalleeBits;
 }
 
 bool AArch64TTIImpl::areTypesABICompatible(
diff --git a/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.h b/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.h
index b65e3c7a1ab20e5..481cb5511a331b7 100644
--- a/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.h
+++ b/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.h
@@ -48,6 +48,8 @@ class AArch64TTIImpl : public BasicTTIImplBase<AArch64TTIImpl> {
   const AArch64Subtarget *ST;
   const AArch64TargetLowering *TLI;
 
+  static const FeatureBitset InlineInverseFeatures;
+
   const AArch64Subtarget *getST() const { return ST; }
   const AArch64TargetLowering *getTLI() const { return TLI; }
 
diff --git a/llvm/lib/Target/AArch64/MCTargetDesc/AArch64ELFStreamer.cpp b/llvm/lib/Target/AArch64/MCTargetDesc/AArch64ELFStreamer.cpp
index 6b5c5f36cbd4b27..d29d383bc231277 100644
--- a/llvm/lib/Target/AArch64/MCTargetDesc/AArch64ELFStreamer.cpp
+++ b/llvm/lib/Target/AArch64/MCTargetDesc/AArch64ELFStreamer.cpp
@@ -27,6 +27,7 @@
 #include "llvm/MC/MCELFStreamer.h"
 #include "llvm/MC/MCExpr.h"
 #include "llvm/MC/MCInst.h"
+#include "llvm/MC/MCObjectFileInfo.h"
 #include "llvm/MC/MCSectionELF.h"
 #include "llvm/MC/MCStreamer.h"
 #include "llvm/MC/MCSubtargetInfo.h"
@@ -504,6 +505,23 @@ void AArch64TargetELFStreamer::finish() {
     }
   }
 
+  // The mix of execute-only and non-execute-only at link time is
+  // non-execute-only. To avoid the empty implicitly created .text
+  // section from making the whole .text section non-execute-only, we
+  // mark it execute-only if it is empty and there is at least one
+  // execute-only section in the object.
+  if (any_of(Asm, [](const MCSection &Sec) {
+        return cast<MCSectionELF>(Sec).getFlags() & ELF::SHF_AARCH64_PURECODE;
+      })) {
+    auto *Text =
+        static_cast<MCSectionELF *>(Ctx.getObjectFileInfo()->getTextSection());
+    for (auto &F : *Text)
+      if (auto *DF = dyn_cast<MCDataFragment>(&F))
+        if (!DF->getContents().empty())
+          return;
+    Text->setFlags(Text->getFlags() | ELF::SHF_AARCH64_PURECODE);
+  }
+
   MCSectionELF *MemtagSec = nullptr;
   for (const MCSymbol &Symbol : Asm.symbols()) {
     const auto &Sym = cast<MCSymbolELF>(Symbol);
diff --git a/llvm/test/CodeGen/AArch64/execute-only-section.ll b/llvm/test/CodeGen/AArch64/execute-only-section.ll
new file mode 100644
index 000000000000000..15a270b07975e69
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/execute-only-section.ll
@@ -0,0 +1,21 @@
+; RUN: llc -mtriple=aarch64 -mattr=+execute-only %s -o - | FileCheck %s
+
+; CHECK:     .section .text,"axy",@progbits,unique,0
+; CHECK-NOT: .section
+; CHECK-NOT: .text
+; CHECK:     .globl test_SectionForGlobal
+; CHECK:     .type test_SectionForGlobal,@function
+define void @test_SectionForGlobal() {
+entry:
+  ret void
+}
+
+; CHECK:     .section .test,"axy",@progbits
+; CHECK-NOT: .section
+; CHECK-NOT: .text
+; CHECK:     .globl test_ExplicitSectionForGlobal
+; CHECK:     .type test_ExplicitSectionForGlobal,@function
+define void @test_ExplicitSectionForGlobal() section ".test" {
+entry:
+  ret void
+}
diff --git a/llvm/test/MC/ELF/AArch64/execute-only-populated-text-section.s b/llvm/test/MC/ELF/AArch64/execute-only-populated-text-section.s
new file mode 100644
index 000000000000000..b0ba8a18d52b386
--- /dev/null
+++ b/llvm/test/MC/ELF/AArch64/execute-only-populated-text-section.s
@@ -0,0 +1,27 @@
+// RUN: llvm-mc -filetype=obj -triple aarch64-unknown-linux-gnu %s -o - \
+// RUN: | llvm-readobj -S --symbols - | FileCheck %s
+
+        .text
+        ret
+
+        .section        .text.foo,"axy"
+        ret
+
+// CHECK:      Section {
+// CHECK:        Name: .text
+// CHECK-NEXT:   Type: SHT_PROGBITS (0x1)
+// CHECK-NEXT:   Flags [ (0x6)
+// CHECK-NEXT:     SHF_ALLOC (0x...
[truncated]

Copy link
Collaborator

@smithp35 smithp35 left a comment

Choose a reason for hiding this comment

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

I've concentrated on the lld side. I accidentally left one on the assembler before reading that this change also included #125687 .

Antother possible way of splitting it up is to do:

  • llvm-mc (section flag only), llvm-readelf
  • lld using llvm-mc for tests
  • llvm code-gen from C

However no need to change unless there's further feedback from other reviewers.

@@ -459,6 +459,8 @@ static void checkOptions(Ctx &ctx) {
ErrAlways(ctx) << "-z gcs-report only supported on AArch64";
if (ctx.arg.zGcs != GcsPolicy::Implicit)
ErrAlways(ctx) << "-z gcs only supported on AArch64";
if (ctx.arg.zExecuteOnlyReport != "none")
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is there any reason this cannot be supported on Arm as well. The underlying mechanism is the same?

Copy link
Contributor Author

@Il-Capitano Il-Capitano Feb 4, 2025

Choose a reason for hiding this comment

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

No particular reason other than this patch is concerned with only SHF_AARCH64_PURECODE, and I hadn't thought about implementing this for ARM as well. I could add support for it here if you prefer, or open another PR implementing that once this gets merged. It would definitely be useful to have for ARM as well, not just AArch64.

Copy link
Collaborator

Choose a reason for hiding this comment

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

When I first saw the PR, my immediate thought was to separate -z execute-only-report into a separate patch and implement for Arm/AArch64. This may be worth doing as you can write tests for that in that patch. Will also help speed up the LLD review for this part as there's less code to look at.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That sounds good to me. I've already written the test cases in the mean time, so I'll wait for MaskRay to chime in if I should remove that part from this patch.

uint64_t andMask =
ctx.arg.emachine == EM_ARM ? (uint64_t)SHF_ARM_PURECODE : 0;
uint64_t andMask = 0;
if (ctx.arg.emachine == EM_ARM)
Copy link
Collaborator

Choose a reason for hiding this comment

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

We could rewrite as

if (ctx.arg.emachine == EM_ARM || ctx.arg.emachine == EM_AARCH64)
  andMask |= (uint64_t)SHF_ARM_PURECODE;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Similarly to above, I didn't want to mix flags from different architectures, even though they have the same value.

@smithp35 smithp35 requested a review from MaskRay February 4, 2025 15:45
Copy link
Member

@MaskRay MaskRay left a comment

Choose a reason for hiding this comment

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

The lld part should go to a separate patch

return {ctx, DiagLevel::None};
};

for (ELFFileBase *file : ctx.objectFiles) {
Copy link
Member

Choose a reason for hiding this comment

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

likely faster by iteration over executable OutputSections and check InputSectionDescriptions and then InputSections.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This check will have to be relocated into Writer then, I'll do that.

@jh7370
Copy link
Collaborator

jh7370 commented Feb 5, 2025

The lld part should go to a separate patch

This is the separate patch, but the previous commits from other PRs are included in it because Github is terrible...

@Il-Capitano
Copy link
Contributor Author

The lld part should go to a separate patch

This is the separate patch, but the previous commits from other PRs are included in it because Github is terrible...

Yes, that's right. Unfortunately you can't base PRs on top of other PRs and I couldn't find guidance on how to do stacked PRs with Github, so I went with this approach. I'll mark this as a draft PR to signify the dependency on #125687.

@Il-Capitano Il-Capitano marked this pull request as draft February 5, 2025 09:17
@Il-Capitano
Copy link
Contributor Author

@MaskRay I've addressed your review comments on the LLVM part of the patch in #125687. I won't update the code here in order to not clutter up this PR with unnecessary commits.

Il-Capitano added a commit to Il-Capitano/llvm-project that referenced this pull request Feb 7, 2025
jh7370 pushed a commit that referenced this pull request Feb 14, 2025
…#125687)

Add support for the new SHF_AARCH64_PURECODE ELF section flag:
ARM-software/abi-aa#304

The general implementation follows the existing one for ARM targets.
Generating object files with the `SHF_AARCH64_PURECODE` flag set is
enabled by the `+execute-only` target feature.

Related PRs:
* Clang: #125688
* LLD: #125689
github-actions bot pushed a commit to arm/arm-toolchain that referenced this pull request Feb 14, 2025
… flag (1/3) (#125687)

Add support for the new SHF_AARCH64_PURECODE ELF section flag:
ARM-software/abi-aa#304

The general implementation follows the existing one for ARM targets.
Generating object files with the `SHF_AARCH64_PURECODE` flag set is
enabled by the `+execute-only` target feature.

Related PRs:
* Clang: llvm/llvm-project#125688
* LLD: llvm/llvm-project#125689
@Il-Capitano Il-Capitano force-pushed the aarch64-execute-only-lld branch from d3af7e4 to 5d894c7 Compare February 14, 2025 10:31
@Il-Capitano Il-Capitano marked this pull request as ready for review February 14, 2025 10:37
@Il-Capitano
Copy link
Contributor Author

I've rebased this patch onto main, since #125687 has been merged. Only the LLD part is included now.

@MaskRay MaskRay force-pushed the aarch64-execute-only-lld branch from a6dcd48 to e0358eb Compare February 20, 2025 16:35
Copy link
Collaborator

@smithp35 smithp35 left a comment

Choose a reason for hiding this comment

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

Test case looks good to me. I think the generic parts of the BTI insertion are already tested in aarch64-thunk-bti.s , leaving the execute-only specific parts here.

As you don't have permission to merge, I'll leave that for MaskRay once he's happy with the changes.

@MaskRay MaskRay merged commit 6e457c2 into llvm:main Feb 21, 2025
6 of 10 checks passed
@Il-Capitano Il-Capitano deleted the aarch64-execute-only-lld branch February 21, 2025 17:05
sivan-shani pushed a commit to sivan-shani/llvm-project that referenced this pull request Feb 24, 2025
…llvm#125687)

Add support for the new SHF_AARCH64_PURECODE ELF section flag:
ARM-software/abi-aa#304

The general implementation follows the existing one for ARM targets.
Generating object files with the `SHF_AARCH64_PURECODE` flag set is
enabled by the `+execute-only` target feature.

Related PRs:
* Clang: llvm#125688
* LLD: llvm#125689
davemgreen pushed a commit that referenced this pull request Mar 10, 2025
…g (2/3) (#125688)

Add support for the new SHF_AARCH64_PURECODE ELF section flag:
ARM-software/abi-aa#304

The general implementation follows the existing one for ARM targets.
Simlarly to ARM targets, generating object files with the
`SHF_AARCH64_PURECODE` flag set is enabled by the
`-mexecute-only`/`-mpure-code` driver flag.

Related PRs:
* LLVM: #125687
* LLD: #125689
llvm-sync bot pushed a commit to arm/arm-toolchain that referenced this pull request Mar 10, 2025
…section flag (2/3) (#125688)

Add support for the new SHF_AARCH64_PURECODE ELF section flag:
ARM-software/abi-aa#304

The general implementation follows the existing one for ARM targets.
Simlarly to ARM targets, generating object files with the
`SHF_AARCH64_PURECODE` flag set is enabled by the
`-mexecute-only`/`-mpure-code` driver flag.

Related PRs:
* LLVM: llvm/llvm-project#125687
* LLD: llvm/llvm-project#125689
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants