[RISCV] Emit .note.gnu.property section when Zicfiss-based shadow stack is enabled (#127036)

RISC-V Zicfiss-based shadow stack needs to let the linker/loader know if
the binary is built with the mechanism enabled to support proper
link-time/load-time management of this feature. The information is
encoded as a bit in the `.note.gnu.property` section. This patch
implements emitting the section for RISC-V targets when Zicfiss-based
shadow stack is enabled.

When Clang receives the `-fcf-protection=return` flag, it adds the
`hw-shadow-stack` attribute to LLVM functions, and adds a non-zero
valued attribute named `cf-protection-return` to the LLVM module it
generates. The backend depends on the `hw-shadow-stack` attributes to
generate Zicfiss-based shadow stack instructions for each function, but
at the module scope, the `cf-protection-return` attribute is a better
indication of whether the translation unit is built with Zicfiss-based
shadow stack enabled, so this patch emits the `.note.gnu.property`
section with the "Zicfiss-based shadow stack" bit toggled on when it
sees the `cf-protection-return` attribute.
diff --git a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVTargetStreamer.cpp b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVTargetStreamer.cpp
index 5785f7d..6df8b18 100644
--- a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVTargetStreamer.cpp
+++ b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVTargetStreamer.cpp
@@ -13,8 +13,15 @@
 #include "RISCVTargetStreamer.h"
 #include "RISCVBaseInfo.h"
 #include "RISCVMCTargetDesc.h"
+#include "llvm/BinaryFormat/ELF.h"
+#include "llvm/MC/MCContext.h"
+#include "llvm/MC/MCExpr.h"
+#include "llvm/MC/MCSectionELF.h"
+#include "llvm/MC/MCStreamer.h"
 #include "llvm/MC/MCSymbol.h"
+#include "llvm/Support/Alignment.h"
 #include "llvm/Support/CommandLine.h"
+#include "llvm/Support/ErrorHandling.h"
 #include "llvm/Support/FormattedStream.h"
 #include "llvm/Support/RISCVAttributes.h"
 #include "llvm/TargetParser/RISCVISAInfo.h"
@@ -53,6 +60,55 @@
 void RISCVTargetStreamer::emitIntTextAttribute(unsigned Attribute,
                                                unsigned IntValue,
                                                StringRef StringValue) {}
+
+void RISCVTargetStreamer::emitNoteGnuPropertySection(
+    const uint32_t Feature1And) {
+  MCStreamer &OutStreamer = getStreamer();
+  MCContext &Ctx = OutStreamer.getContext();
+
+  const Triple &Triple = Ctx.getTargetTriple();
+  Align NoteAlign;
+  if (Triple.isArch64Bit()) {
+    NoteAlign = Align(8);
+  } else {
+    assert(Triple.isArch32Bit());
+    NoteAlign = Align(4);
+  }
+
+  assert(Ctx.getObjectFileType() == MCContext::Environment::IsELF);
+  MCSection *const NoteSection =
+      Ctx.getELFSection(".note.gnu.property", ELF::SHT_NOTE, ELF::SHF_ALLOC);
+  NoteSection->setAlignment(NoteAlign);
+  OutStreamer.pushSection();
+  OutStreamer.switchSection(NoteSection);
+
+  // Emit the note header
+  OutStreamer.emitIntValue(4, 4); // n_namsz
+
+  MCSymbol *const NDescBeginSym = Ctx.createTempSymbol();
+  MCSymbol *const NDescEndSym = Ctx.createTempSymbol();
+  const MCExpr *const NDescSzExpr =
+      MCBinaryExpr::createSub(MCSymbolRefExpr::create(NDescEndSym, Ctx),
+                              MCSymbolRefExpr::create(NDescBeginSym, Ctx), Ctx);
+
+  OutStreamer.emitValue(NDescSzExpr, 4);                    // n_descsz
+  OutStreamer.emitIntValue(ELF::NT_GNU_PROPERTY_TYPE_0, 4); // n_type
+  OutStreamer.emitBytes(StringRef("GNU", 4));               // n_name
+
+  // Emit n_desc field
+  OutStreamer.emitLabel(NDescBeginSym);
+  OutStreamer.emitValueToAlignment(NoteAlign);
+
+  // Emit the feature_1_and property
+  OutStreamer.emitIntValue(ELF::GNU_PROPERTY_RISCV_FEATURE_1_AND, 4); // pr_type
+  OutStreamer.emitIntValue(4, 4);              // pr_datasz
+  OutStreamer.emitIntValue(Feature1And, 4);    // pr_data
+  OutStreamer.emitValueToAlignment(NoteAlign); // pr_padding
+
+  OutStreamer.emitLabel(NDescEndSym);
+  OutStreamer.popSection();
+}
+
 void RISCVTargetStreamer::setTargetABI(RISCVABI::ABI ABI) {
   assert(ABI != RISCVABI::ABI_Unknown && "Improperly initialized target ABI");
   TargetABI = ABI;
diff --git a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVTargetStreamer.h b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVTargetStreamer.h
index 169cb0f..d7b3b1e 100644
--- a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVTargetStreamer.h
+++ b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVTargetStreamer.h
@@ -58,6 +58,7 @@
   virtual void emitTextAttribute(unsigned Attribute, StringRef String);
   virtual void emitIntTextAttribute(unsigned Attribute, unsigned IntValue,
                                     StringRef StringValue);
+  void emitNoteGnuPropertySection(const uint32_t Feature1And);
 
   void emitTargetAttributes(const MCSubtargetInfo &STI, bool EmitStackAlign);
   void setTargetABI(RISCVABI::ABI ABI);
diff --git a/llvm/lib/Target/RISCV/RISCVAsmPrinter.cpp b/llvm/lib/Target/RISCV/RISCVAsmPrinter.cpp
index e40bd18..5aa323a 100644
--- a/llvm/lib/Target/RISCV/RISCVAsmPrinter.cpp
+++ b/llvm/lib/Target/RISCV/RISCVAsmPrinter.cpp
@@ -112,6 +112,8 @@
   void emitFunctionEntryLabel() override;
   bool emitDirectiveOptionArch();
 
+  void emitNoteGnuProperty(const Module &M);
+
 private:
   void emitAttributes(const MCSubtargetInfo &SubtargetInfo);
 
@@ -581,8 +583,10 @@
   RISCVTargetStreamer &RTS =
       static_cast<RISCVTargetStreamer &>(*OutStreamer->getTargetStreamer());
 
-  if (TM.getTargetTriple().isOSBinFormatELF())
+  if (TM.getTargetTriple().isOSBinFormatELF()) {
     RTS.finishAttributeSection();
+    emitNoteGnuProperty(M);
+  }
   EmitHwasanMemaccessSymbols(M);
 }
 
@@ -941,6 +945,15 @@
   }
 }
 
+void RISCVAsmPrinter::emitNoteGnuProperty(const Module &M) {
+  if (const Metadata *const Flag = M.getModuleFlag("cf-protection-return");
+      Flag && !mdconst::extract<ConstantInt>(Flag)->isZero()) {
+    RISCVTargetStreamer &RTS =
+        static_cast<RISCVTargetStreamer &>(*OutStreamer->getTargetStreamer());
+    RTS.emitNoteGnuPropertySection(ELF::GNU_PROPERTY_RISCV_FEATURE_1_CFI_SS);
+  }
+}
+
 static MCOperand lowerSymbolOperand(const MachineOperand &MO, MCSymbol *Sym,
                                     const AsmPrinter &AP) {
   MCContext &Ctx = AP.OutContext;
diff --git a/llvm/test/CodeGen/RISCV/note-gnu-property-zicfiss.ll b/llvm/test/CodeGen/RISCV/note-gnu-property-zicfiss.ll
new file mode 100644
index 0000000..24d63cb
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/note-gnu-property-zicfiss.ll
@@ -0,0 +1,31 @@
+; RUN: llc --mtriple=riscv32 --filetype=obj -o - %s | llvm-readelf -n - | FileCheck --check-prefixes=READELF %s
+; RUN: llc --mtriple=riscv64 --filetype=obj -o - %s | llvm-readelf -n - | FileCheck --check-prefixes=READELF %s
+; RUN: llc --mtriple=riscv32 -o - %s | FileCheck --check-prefixes=ASM,ASM32 %s
+; RUN: llc --mtriple=riscv64 -o - %s | FileCheck --check-prefixes=ASM,ASM64 %s
+
+; READELF: Properties: RISC-V feature: ZICFISS
+
+; ASM:                .section        ".note.GNU-stack","",@progbits
+; ASM-NEXT:           .section        .note.gnu.property,"a",@note
+; ASM-NEXT:           .word   4
+; ASM-NEXT:           .word   .Ltmp1-.Ltmp0
+; ASM-NEXT:           .word   5
+; ASM-NEXT:           .asciz  "GNU"
+; ASM-NEXT:   .Ltmp0:
+; ASM32-NEXT:         .p2align        2, 0x0
+; ASM64-NEXT:         .p2align        3, 0x0
+; ASM-NEXT:           .word   3221225472
+; ASM-NEXT:           .word   4
+; ASM-NEXT:           .word   2
+; ASM32-NEXT:         .p2align        2, 0x0
+; ASM64-NEXT:         .p2align        3, 0x0
+; ASM-NEXT:   .Ltmp1:
+
+define i32 @f() "hw-shadow-stack" {
+entry:
+  ret i32 0
+}
+
+!llvm.module.flags = !{!0}
+
+!0 = !{i32 8, !"cf-protection-return", i32 1}