[llvm] [SFrames] Emit and relax FREs (PR #158154)

via llvm-commits llvm-commits at lists.llvm.org
Fri Sep 12 10:12:45 PDT 2025


https://github.com/Sterling-Augustine updated https://github.com/llvm/llvm-project/pull/158154

>From 3bb6f9c4eae3fe85fe338dfdf2ebce19203f5788 Mon Sep 17 00:00:00 2001
From: Sterling Augustine <saugustine at google.com>
Date: Tue, 9 Sep 2025 16:02:51 -0700
Subject: [PATCH 1/3] [SFrames] Emit and relax FREs

After this change, llvm emits usable sframe sections that can be
compared to the gnu-gas implementation and linked with the gnu linker.

There are a few remaining cfi directives to handle before we have
complete compatibility, however.

The output isn't identical with gnu in every case (this code produces
fewer identical FREs in a row than gas), but I'm reasonably sure that
they are correct. There are even more size optimizations that can be
done later.

This is a fairly big commit, but I'm not sure how to make it smaller.

Also, while working on the tests, I found a few bugs in older portions
and cleaned those up.
---
 llvm/include/llvm/BinaryFormat/SFrame.h |   2 +
 llvm/include/llvm/MC/MCAsmBackend.h     |   1 +
 llvm/include/llvm/MC/MCAssembler.h      |   2 +
 llvm/include/llvm/MC/MCObjectStreamer.h |   3 +
 llvm/include/llvm/MC/MCSFrame.h         |  14 ++
 llvm/include/llvm/MC/MCSection.h        |  25 ++++
 llvm/lib/MC/MCAssembler.cpp             |  24 ++++
 llvm/lib/MC/MCFragment.cpp              |   5 +-
 llvm/lib/MC/MCObjectStreamer.cpp        |  17 ++-
 llvm/lib/MC/MCSFrame.cpp                | 184 +++++++++++++++++++++---
 llvm/test/MC/ELF/cfi-sframe-encoding.s  |  87 +++++++++++
 llvm/test/MC/ELF/cfi-sframe-fre-cases.s | 114 +++++++++++++++
 llvm/test/MC/ELF/cfi-sframe.s           |  51 ++++---
 13 files changed, 487 insertions(+), 42 deletions(-)
 create mode 100644 llvm/test/MC/ELF/cfi-sframe-encoding.s
 create mode 100644 llvm/test/MC/ELF/cfi-sframe-fre-cases.s

diff --git a/llvm/include/llvm/BinaryFormat/SFrame.h b/llvm/include/llvm/BinaryFormat/SFrame.h
index 095db18b9c254..7b58043c60363 100644
--- a/llvm/include/llvm/BinaryFormat/SFrame.h
+++ b/llvm/include/llvm/BinaryFormat/SFrame.h
@@ -117,6 +117,7 @@ template <endianness E> struct FDEInfo {
     Info = ((PAuthKey & 1) << 5) | ((static_cast<uint8_t>(FDE) & 1) << 4) |
            (static_cast<uint8_t>(FRE) & 0xf);
   }
+  uint8_t getFuncInfo() const { return Info; }
 };
 
 template <endianness E> struct FuncDescEntry {
@@ -155,6 +156,7 @@ template <endianness E> struct FREInfo {
     Info = ((RA & 1) << 7) | ((static_cast<uint8_t>(Sz) & 3) << 5) |
            ((N & 0xf) << 1) | (static_cast<uint8_t>(Reg) & 1);
   }
+  uint8_t getFREInfo() const { return Info; }
 };
 
 template <typename T, endianness E> struct FrameRowEntry {
diff --git a/llvm/include/llvm/MC/MCAsmBackend.h b/llvm/include/llvm/MC/MCAsmBackend.h
index 1625355323692..58363f0b671e2 100644
--- a/llvm/include/llvm/MC/MCAsmBackend.h
+++ b/llvm/include/llvm/MC/MCAsmBackend.h
@@ -168,6 +168,7 @@ class LLVM_ABI MCAsmBackend {
   virtual bool relaxAlign(MCFragment &F, unsigned &Size) { return false; }
   virtual bool relaxDwarfLineAddr(MCFragment &) const { return false; }
   virtual bool relaxDwarfCFA(MCFragment &) const { return false; }
+  virtual bool relaxSFrameCFA(MCFragment &) const { return false; }
 
   // Defined by linker relaxation targets to possibly emit LEB128 relocations
   // and set Value at the relocated location.
diff --git a/llvm/include/llvm/MC/MCAssembler.h b/llvm/include/llvm/MC/MCAssembler.h
index 1316d8669239d..a9924a90260bd 100644
--- a/llvm/include/llvm/MC/MCAssembler.h
+++ b/llvm/include/llvm/MC/MCAssembler.h
@@ -117,6 +117,8 @@ class MCAssembler {
   void relaxBoundaryAlign(MCBoundaryAlignFragment &BF);
   void relaxDwarfLineAddr(MCFragment &F);
   void relaxDwarfCallFrameFragment(MCFragment &F);
+  void relaxSFrameFragment(MCFragment &DF);
+
 
 public:
   /// Construct a new assembler instance.
diff --git a/llvm/include/llvm/MC/MCObjectStreamer.h b/llvm/include/llvm/MC/MCObjectStreamer.h
index b9e813b9b0d28..1899cb6331c6f 100644
--- a/llvm/include/llvm/MC/MCObjectStreamer.h
+++ b/llvm/include/llvm/MC/MCObjectStreamer.h
@@ -150,6 +150,9 @@ class MCObjectStreamer : public MCStreamer {
                              MCSymbol *EndLabel = nullptr) override;
   void emitDwarfAdvanceFrameAddr(const MCSymbol *LastLabel,
                                  const MCSymbol *Label, SMLoc Loc);
+  void emitSFrameCalculateFuncOffset(const MCSymbol *FunCabsel,
+                                     const MCSymbol *FREBegin,
+                                     MCFragment *FDEFrag, SMLoc Loc);
   void emitCVLocDirective(unsigned FunctionId, unsigned FileNo, unsigned Line,
                           unsigned Column, bool PrologueEnd, bool IsStmt,
                           StringRef FileName, SMLoc Loc) override;
diff --git a/llvm/include/llvm/MC/MCSFrame.h b/llvm/include/llvm/MC/MCSFrame.h
index 8f182a86d1ab1..694aec55aefeb 100644
--- a/llvm/include/llvm/MC/MCSFrame.h
+++ b/llvm/include/llvm/MC/MCSFrame.h
@@ -16,9 +16,14 @@
 #ifndef LLVM_MC_MCSFRAME_H
 #define LLVM_MC_MCSFRAME_H
 
+#include "llvm/ADT/SmallVector.h"
+#include <cstdint>
+
 namespace llvm {
 
+class MCContext;
 class MCObjectStreamer;
+class MCFragment;
 
 class MCSFrameEmitter {
 public:
@@ -26,6 +31,15 @@ class MCSFrameEmitter {
   //
   // \param Streamer - Emit into this stream.
   static void emit(MCObjectStreamer &Streamer);
+
+  // Encode the FRE's function offset.
+  //
+  // \param C - Context.
+  // \param Offset - Offset to encode.
+  // \param Out - Destination of the encoding.
+  // \param FDEFrag - Frag that specifies the encoding format.
+  static void encodeFuncOffset(MCContext &C, uint64_t Offset,
+                               SmallVectorImpl<char> &Out, MCFragment *FDEFrag);
 };
 
 } // end namespace llvm
diff --git a/llvm/include/llvm/MC/MCSection.h b/llvm/include/llvm/MC/MCSection.h
index 12389d623e588..a26e6cfb2158a 100644
--- a/llvm/include/llvm/MC/MCSection.h
+++ b/llvm/include/llvm/MC/MCSection.h
@@ -59,6 +59,7 @@ class MCFragment {
     FT_Org,
     FT_Dwarf,
     FT_DwarfFrame,
+    FT_SFrame,
     FT_BoundaryAlign,
     FT_SymbolId,
     FT_CVInlineLines,
@@ -143,6 +144,12 @@ class MCFragment {
       // .loc dwarf directives.
       int64_t LineDelta;
     } dwarf;
+    struct {
+      // This FRE describes unwind info at AddrDelta from function start.
+      const MCExpr *AddrDelta;
+      // Fragment that records how many bytes of AddrDelta to emit.
+      MCFragment *FDEFragment;
+    } sframe;
   } u{};
 
 public:
@@ -296,6 +303,24 @@ class MCFragment {
     assert(Kind == FT_Dwarf);
     u.dwarf.LineDelta = LineDelta;
   }
+
+  //== FT_SFrame functions
+  const MCExpr &getSFrameAddrDelta() const {
+    assert(Kind == FT_SFrame);
+    return *u.sframe.AddrDelta;
+  }
+  void setSFrameAddrDelta(const MCExpr *E) {
+    assert(Kind == FT_SFrame);
+    u.sframe.AddrDelta = E;
+  }
+  MCFragment *getSFrameFDE() const {
+    assert(Kind == FT_SFrame);
+    return u.sframe.FDEFragment;
+  }
+  void setSFrameFDE(MCFragment *F) {
+    assert(Kind == FT_SFrame);
+    u.sframe.FDEFragment = F;
+  }
 };
 
 // MCFragment subclasses do not use the fixed-size part or variable-size tail of
diff --git a/llvm/lib/MC/MCAssembler.cpp b/llvm/lib/MC/MCAssembler.cpp
index b1031d7822604..cee281597cfed 100644
--- a/llvm/lib/MC/MCAssembler.cpp
+++ b/llvm/lib/MC/MCAssembler.cpp
@@ -22,6 +22,7 @@
 #include "llvm/MC/MCFixup.h"
 #include "llvm/MC/MCInst.h"
 #include "llvm/MC/MCObjectWriter.h"
+#include "llvm/MC/MCSFrame.h"
 #include "llvm/MC/MCSection.h"
 #include "llvm/MC/MCSymbol.h"
 #include "llvm/MC/MCValue.h"
@@ -199,6 +200,7 @@ uint64_t MCAssembler::computeFragmentSize(const MCFragment &F) const {
   case MCFragment::FT_LEB:
   case MCFragment::FT_Dwarf:
   case MCFragment::FT_DwarfFrame:
+  case MCFragment::FT_SFrame:
   case MCFragment::FT_CVInlineLines:
   case MCFragment::FT_CVDefRange:
     return F.getSize();
@@ -399,6 +401,7 @@ static void writeFragment(raw_ostream &OS, const MCAssembler &Asm,
   case MCFragment::FT_LEB:
   case MCFragment::FT_Dwarf:
   case MCFragment::FT_DwarfFrame:
+  case MCFragment::FT_SFrame:
   case MCFragment::FT_CVInlineLines:
   case MCFragment::FT_CVDefRange: {
     if (F.getKind() == MCFragment::FT_Data)
@@ -914,6 +917,24 @@ void MCAssembler::relaxDwarfCallFrameFragment(MCFragment &F) {
   F.clearVarFixups();
 }
 
+void MCAssembler::relaxSFrameFragment(MCFragment &F) {
+  assert(F.getKind() == MCFragment::FT_SFrame);
+  MCContext &C = getContext();
+  int64_t Value;
+  bool Abs = F.getSFrameAddrDelta().evaluateAsAbsolute(Value, *this);
+  if (!Abs) {
+    C.reportError(F.getSFrameAddrDelta().getLoc(),
+                  "invalid CFI advance_loc expression in sframe");
+    F.setSFrameAddrDelta(MCConstantExpr::create(0, C));
+    return;
+  }
+
+  SmallVector<char, 4> Data;
+  MCSFrameEmitter::encodeFuncOffset(Context, Value, Data, F.getSFrameFDE());
+  F.setVarContents(Data);
+  F.clearVarFixups();
+}
+
 bool MCAssembler::relaxFragment(MCFragment &F) {
   auto Size = computeFragmentSize(F);
   switch (F.getKind()) {
@@ -932,6 +953,9 @@ bool MCAssembler::relaxFragment(MCFragment &F) {
   case MCFragment::FT_DwarfFrame:
     relaxDwarfCallFrameFragment(F);
     break;
+  case MCFragment::FT_SFrame:
+    relaxSFrameFragment(F);
+    break;
   case MCFragment::FT_BoundaryAlign:
     relaxBoundaryAlign(static_cast<MCBoundaryAlignFragment &>(F));
     break;
diff --git a/llvm/lib/MC/MCFragment.cpp b/llvm/lib/MC/MCFragment.cpp
index 21da79bb0aa30..2e68de7807399 100644
--- a/llvm/lib/MC/MCFragment.cpp
+++ b/llvm/lib/MC/MCFragment.cpp
@@ -53,6 +53,7 @@ LLVM_DUMP_METHOD void MCFragment::dump() const {
   case MCFragment::FT_Org:           OS << "Org"; break;
   case MCFragment::FT_Dwarf:         OS << "Dwarf"; break;
   case MCFragment::FT_DwarfFrame:    OS << "DwarfCallFrame"; break;
+  case MCFragment::FT_SFrame:        OS << "SFrame"; break;
   case MCFragment::FT_LEB:           OS << "LEB"; break;
   case MCFragment::FT_BoundaryAlign: OS<<"BoundaryAlign"; break;
   case MCFragment::FT_SymbolId:      OS << "SymbolId"; break;
@@ -79,7 +80,8 @@ LLVM_DUMP_METHOD void MCFragment::dump() const {
   case MCFragment::FT_Align:
   case MCFragment::FT_LEB:
   case MCFragment::FT_Dwarf:
-  case MCFragment::FT_DwarfFrame: {
+  case MCFragment::FT_DwarfFrame: 
+  case MCFragment::FT_SFrame: {
     if (isLinkerRelaxable())
       OS << " LinkerRelaxable";
     auto Fixed = getContents();
@@ -129,6 +131,7 @@ LLVM_DUMP_METHOD void MCFragment::dump() const {
       OS << " LineDelta:" << getDwarfLineDelta();
       break;
     case MCFragment::FT_DwarfFrame:
+    case MCFragment::FT_SFrame:
       OS << " AddrDelta:";
       getDwarfAddrDelta().print(OS, nullptr);
       break;
diff --git a/llvm/lib/MC/MCObjectStreamer.cpp b/llvm/lib/MC/MCObjectStreamer.cpp
index 59265bc8595ba..7279a0f8945a0 100644
--- a/llvm/lib/MC/MCObjectStreamer.cpp
+++ b/llvm/lib/MC/MCObjectStreamer.cpp
@@ -28,8 +28,7 @@ MCObjectStreamer::MCObjectStreamer(MCContext &Context,
                                    std::unique_ptr<MCAsmBackend> TAB,
                                    std::unique_ptr<MCObjectWriter> OW,
                                    std::unique_ptr<MCCodeEmitter> Emitter)
-    : MCStreamer(Context),
-      Assembler(std::make_unique<MCAssembler>(
+    : MCStreamer(Context),      Assembler(std::make_unique<MCAssembler>(
           Context, std::move(TAB), std::move(Emitter), std::move(OW))),
       EmitEHFrame(true), EmitDebugFrame(false), EmitSFrame(false) {
   assert(Assembler->getBackendPtr() && Assembler->getEmitterPtr());
@@ -583,6 +582,20 @@ void MCObjectStreamer::emitDwarfAdvanceFrameAddr(const MCSymbol *LastLabel,
   newFragment();
 }
 
+void MCObjectStreamer::emitSFrameCalculateFuncOffset(const MCSymbol *FuncBase,
+                                                     const MCSymbol *FREBegin,
+                                                     MCFragment *FDEFrag,
+                                                     SMLoc Loc) {
+  assert(FuncBase && "No function base address");
+  assert(FREBegin && "FRE doesn't describe a location");
+  auto *F = getCurrentFragment();
+  F->Kind = MCFragment::FT_SFrame;
+  F->setSFrameAddrDelta(buildSymbolDiff(*this, FREBegin, FuncBase, Loc));
+  F->setSFrameFDE(FDEFrag);
+  newFragment();
+}
+
+
 void MCObjectStreamer::emitCVLocDirective(unsigned FunctionId, unsigned FileNo,
                                           unsigned Line, unsigned Column,
                                           bool PrologueEnd, bool IsStmt,
diff --git a/llvm/lib/MC/MCSFrame.cpp b/llvm/lib/MC/MCSFrame.cpp
index a0d6c80ab72ea..60446d1657034 100644
--- a/llvm/lib/MC/MCSFrame.cpp
+++ b/llvm/lib/MC/MCSFrame.cpp
@@ -14,6 +14,7 @@
 #include "llvm/MC/MCObjectStreamer.h"
 #include "llvm/MC/MCSection.h"
 #include "llvm/MC/MCSymbol.h"
+#include "llvm/Support/Endian.h"
 #include "llvm/Support/EndianStream.h"
 
 using namespace llvm;
@@ -33,10 +34,86 @@ struct SFrameFRE {
   size_t CFAOffset = 0;
   size_t FPOffset = 0;
   size_t RAOffset = 0;
-  bool FromFP = false;
+  FREInfo<endianness::native> Info;
   bool CFARegSet = false;
 
   SFrameFRE(const MCSymbol *Start) : Label(Start) {}
+
+  void emit(MCObjectStreamer &S, const MCSymbol *FuncBegin,
+            MCFragment *FDEFrag) {
+    S.emitSFrameCalculateFuncOffset(FuncBegin, Label, FDEFrag, SMLoc());
+
+    // fre_cfa_base_reg_id already set during parsing
+
+    // fre_offset_count
+    unsigned RegsTracked = 1; // always track the cfa.
+    if (FPOffset != 0)
+      RegsTracked++;
+    if (RAOffset != 0)
+      RegsTracked++;
+    Info.setOffsetCount(RegsTracked);
+
+    // fre_offset_size
+    if (isInt<8>(CFAOffset) && isInt<8>(FPOffset) && isInt<8>(RAOffset))
+      Info.setOffsetSize(FREOffset::B1);
+    else if (isInt<16>(CFAOffset) && isInt<16>(FPOffset) && isInt<16>(RAOffset))
+      Info.setOffsetSize(FREOffset::B2);
+    else {
+      assert(isInt<32>(CFAOffset) && isInt<32>(FPOffset) &&
+             isInt<32>(RAOffset) && "Offset too big for sframe");
+      Info.setOffsetSize(FREOffset::B4);
+    }
+
+    // No support for fre_mangled_ra_p yet.
+    Info.setReturnAddressSigned(false);
+
+    // sframe_fre_info_word
+    S.emitInt8(Info.getFREInfo());
+
+    // FRE Offsets
+    [[maybe_unused]] unsigned OffsetsEmitted = 1;
+    switch (Info.getOffsetSize()) {
+    case (FREOffset::B1):
+      S.emitInt8(CFAOffset);
+      break;
+    case (FREOffset::B2):
+      S.emitInt16(CFAOffset);
+      break;
+    case (FREOffset::B4):
+      S.emitInt32(CFAOffset);
+      break;
+    }
+    if (FPOffset) {
+      OffsetsEmitted++;
+      switch (Info.getOffsetSize()) {
+      case (FREOffset::B1):
+        S.emitInt8(FPOffset);
+        break;
+      case (FREOffset::B2):
+        S.emitInt16(FPOffset);
+        break;
+      case (FREOffset::B4):
+        S.emitInt32(FPOffset);
+        break;
+      }
+    }
+    if (RAOffset) {
+      OffsetsEmitted++;
+      switch (Info.getOffsetSize()) {
+      case (FREOffset::B1):
+        S.emitInt8(RAOffset);
+        break;
+      case (FREOffset::B2):
+        S.emitInt16(RAOffset);
+        break;
+      case (FREOffset::B4):
+        S.emitInt32(RAOffset);
+        break;
+      }
+    }
+    assert(OffsetsEmitted == RegsTracked &&
+           "Didn't emit the right number of offsets");
+  }
 };
 
 // High-level structure to track info needed to emit a sframe_func_desc_entry
@@ -46,11 +123,13 @@ struct SFrameFDE {
   const MCDwarfFrameInfo &DFrame;
   // Label where this FDE's FREs start.
   MCSymbol *FREStart;
+  // Frag where this FDE is emitted.
+  MCFragment *Frag;
   // Unwinding fres
   SmallVector<SFrameFRE> FREs;
 
   SFrameFDE(const MCDwarfFrameInfo &DF, MCSymbol *FRES)
-      : DFrame(DF), FREStart(FRES) {}
+      : DFrame(DF), FREStart(FRES), Frag(nullptr) {}
 
   void emit(MCObjectStreamer &S, const MCSymbol *FRESubSectionStart) {
     MCContext &C = S.getContext();
@@ -74,13 +153,21 @@ struct SFrameFDE {
     S.emitInt32(0);
 
     // sfde_func_num_fres
-    // TODO: When we actually emit fres, replace 0 with FREs.size()
-    S.emitInt32(0);
+    S.emitInt32(FREs.size());
 
     // sfde_func_info word
-    FDEInfo<endianness::native> I;
-    I.setFuncInfo(0 /* No pauth key */, FDEType::PCInc, FREType::Addr1);
-    S.emitInt8(I.Info);
+
+    // All FREs within an FDE share the same sframe::FREType::AddrX. The value
+    // of 'X' is determined by the FRE with the largest offset, which is the
+    // last. This offset isn't known until relax time, so emit a frag which can
+    // calculate that now.
+    //
+    // At relax time, this FDE frag calculates the proper AddrX value (as well
+    // as the rest of the FDE FuncInfo word). Subsequent FRE frags will read it
+    // from this frag and emit the proper number of bytes.
+    Frag = S.getCurrentFragment();
+    S.emitSFrameCalculateFuncOffset(DFrame.Begin, FREs.back().Label, nullptr,
+                                    SMLoc());
 
     // sfde_func_rep_size. Not relevant in non-PCMASK fdes.
     S.emitInt8(0);
@@ -96,13 +183,16 @@ struct SFrameFDE {
 class SFrameEmitterImpl {
   MCObjectStreamer &Streamer;
   SmallVector<SFrameFDE> FDEs;
+  uint32_t TotalFREs;
   ABI SFrameABI;
   // Target-specific convenience variables to detect when a CFI instruction
   // references these registers. Unlike in dwarf frame descriptions, they never
-  // escape into the sframe section itself.
+  // escape into the sframe section itself. TODO: These should be retrieved from
+  // the target.
   unsigned SPReg;
   unsigned FPReg;
   unsigned RAReg;
+  int8_t FixedRAOffset;
   MCSymbol *FDESubSectionStart;
   MCSymbol *FRESubSectionStart;
   MCSymbol *FRESubSectionEnd;
@@ -110,12 +200,12 @@ class SFrameEmitterImpl {
   bool setCFARegister(SFrameFRE &FRE, const MCCFIInstruction &I) {
     if (I.getRegister() == SPReg) {
       FRE.CFARegSet = true;
-      FRE.FromFP = false;
+      FRE.Info.setBaseRegister(BaseReg::SP);
       return true;
     }
     if (I.getRegister() == FPReg) {
       FRE.CFARegSet = true;
-      FRE.FromFP = true;
+      FRE.Info.setBaseRegister(BaseReg::FP);
       return true;
     }
     Streamer.getContext().reportWarning(
@@ -182,7 +272,8 @@ class SFrameEmitterImpl {
   }
 
 public:
-  SFrameEmitterImpl(MCObjectStreamer &Streamer) : Streamer(Streamer) {
+  SFrameEmitterImpl(MCObjectStreamer &Streamer)
+      : Streamer(Streamer), TotalFREs(0) {
     assert(Streamer.getContext()
                .getObjectFileInfo()
                ->getSFrameABIArch()
@@ -195,6 +286,7 @@ class SFrameEmitterImpl {
       SPReg = 31;
       RAReg = 29;
       FPReg = 30;
+      FixedRAOffset = 0;
       break;
     case ABI::AMD64EndianLittle:
       SPReg = 7;
@@ -202,6 +294,7 @@ class SFrameEmitterImpl {
       // MCDwarfFrameInfo constructor.
       RAReg = static_cast<unsigned>(INT_MAX);
       FPReg = 6;
+      FixedRAOffset = -8;
       break;
     }
 
@@ -219,10 +312,16 @@ class SFrameEmitterImpl {
   bool equalIgnoringLocation(const SFrameFRE &Left, const SFrameFRE &Right) {
     return Left.CFAOffset == Right.CFAOffset &&
            Left.FPOffset == Right.FPOffset && Left.RAOffset == Right.RAOffset &&
-           Left.FromFP == Right.FromFP && Left.CFARegSet == Right.CFARegSet;
+           Left.Info.getFREInfo() == Right.Info.getFREInfo() &&
+           Left.CFARegSet == Right.CFARegSet;
   }
 
   void buildSFDE(const MCDwarfFrameInfo &DF) {
+    // Functions with zero size can happen with assembler macros and
+    // machine-generated code. They don't need unwind info at all, so
+    // no need to warn.
+    if (atSameLocation(DF.Begin, DF.End))
+      return;
     bool Valid = true;
     SFrameFDE FDE(DF, Streamer.getContext().createTempSymbol());
     // This would have been set via ".cfi_return_column", but
@@ -277,8 +376,11 @@ class SFrameEmitterImpl {
         LastLabel = L;
       }
     }
-    if (Valid)
+
+    if (Valid) {
       FDEs.push_back(FDE);
+      TotalFREs += FDE.FREs.size();
+    }
   }
 
   void emitPreamble() {
@@ -294,13 +396,12 @@ class SFrameEmitterImpl {
     // sfh_cfa_fixed_fp_offset
     Streamer.emitInt8(0);
     // sfh_cfa_fixed_ra_offset
-    Streamer.emitInt8(0);
+    Streamer.emitInt8(FixedRAOffset);
     // sfh_auxhdr_len
     Streamer.emitInt8(0);
     // shf_num_fdes
     Streamer.emitInt32(FDEs.size());
     // shf_num_fres
-    uint32_t TotalFREs = 0;
     Streamer.emitInt32(TotalFREs);
 
     // shf_fre_len
@@ -322,8 +423,11 @@ class SFrameEmitterImpl {
 
   void emitFREs() {
     Streamer.emitLabel(FRESubSectionStart);
-    for (auto &FDE : FDEs)
+    for (auto &FDE : FDEs) {
       Streamer.emitLabel(FDE.FREStart);
+      for (auto &FRE : FDE.FREs)
+        FRE.emit(Streamer, FDE.DFrame.Begin, FDE.Frag);
+    }
     Streamer.emitLabel(FRESubSectionEnd);
   }
 };
@@ -359,3 +463,51 @@ void MCSFrameEmitter::emit(MCObjectStreamer &Streamer) {
   Emitter.emitFDEs();
   Emitter.emitFREs();
 }
+
+void MCSFrameEmitter::encodeFuncOffset(MCContext &C, uint64_t Offset,
+                                       SmallVectorImpl<char> &Out,
+                                       MCFragment *FDEFrag) {
+  // If encoding into the FDE Frag itself, generate the sfde_info_word.
+  if (FDEFrag == nullptr) {
+    // sfde_func_info
+
+    // Offset is the difference between the function start label and the final
+    // FRE's offset, which is the max offset for this FDE.
+    FDEInfo<endianness::native> I;
+    if (isUInt<8>(Offset))
+      I.setFREType(FREType::Addr1);
+    else if (isUInt<16>(Offset))
+      I.setFREType(FREType::Addr2);
+    else {
+      assert(isUInt<32>(Offset));
+      I.setFREType(FREType::Addr4);
+    }
+    I.setFDEType(FDEType::PCInc);
+
+    Out.push_back(I.getFuncInfo());
+    return;
+  }
+
+  const auto &FDEData = FDEFrag->getVarContents();
+  FDEInfo<endianness::native> I;
+  I.Info = FDEData.back();
+  FREType T = I.getFREType();
+  llvm::endianness E = C.getAsmInfo()->isLittleEndian()
+                           ? llvm::endianness::little
+                           : llvm::endianness::big;
+  // sfre_start_address
+  switch (T) {
+  case FREType::Addr1:
+    assert(isUInt<8>(Offset) && "Miscalculated Sframe FREType");
+    support::endian::write<uint8_t>(Out, Offset, E);
+    break;
+  case FREType::Addr2:
+    assert(isUInt<16>(Offset) && "Miscalculated Sframe FREType");
+    support::endian::write<uint16_t>(Out, Offset, E);
+    break;
+  case FREType::Addr4:
+    assert(isUInt<32>(Offset) && "Miscalculated Sframe FREType");
+    support::endian::write<uint32_t>(Out, Offset, E);
+    break;
+  }
+}
diff --git a/llvm/test/MC/ELF/cfi-sframe-encoding.s b/llvm/test/MC/ELF/cfi-sframe-encoding.s
new file mode 100644
index 0000000000000..e13e11c51c05a
--- /dev/null
+++ b/llvm/test/MC/ELF/cfi-sframe-encoding.s
@@ -0,0 +1,87 @@
+// TODO: Add other architectures as they gain sframe support
+// REQUIRES: x86-registered-target
+// RUN: llvm-mc --assemble --filetype=obj --gsframe -triple x86_64 %s -o %t.o
+// RUN: llvm-readelf --sframe %t.o | FileCheck %s
+
+// Tests selection for the proper FDE AddrX encoding at the boundaries
+// between uint8_t, uint16_t, and uint32_t. The first FRE always fits
+// anywhere, because its address-offset is zero. The last FRE
+// determines the smallest AddrX it is possible to use.  Align
+// functions to 1024 to make it easier to interpet offsets.
+
+	.cfi_sections .sframe
+
+        .align 1024
+fde0_uses_addr1:
+// CHECK:        FuncDescEntry [0] {
+// CHECK:          Start FRE Offset: 0x0
+// CHECK-NEXT:          Num FREs: 2
+// CHECK:            FRE Type: Addr1 (0x0)
+	.cfi_startproc
+// CHECK:        Frame Row Entry {
+// CHECK-NEXT:          Start Address: 0x0
+// CHECK-NEXT:          Return Address Signed: No
+// CHECK-NEXT:          Offset Size: B1 (0x0)
+// CHECK-NEXT:          Base Register: SP (0x1)
+// CHECK-NEXT:          CFA Offset: 8
+// CHECK-NEXT:          RA Offset: -8
+// CHECK-NEXT:        }
+        .fill 0xFF
+	.cfi_def_cfa_offset 16
+// CHECK-NEXT:        Frame Row Entry {
+// CHECK-NEXT:          Start Address: 0xFF
+// CHECK-NEXT:          Return Address Signed: No
+// CHECK-NEXT:          Offset Size: B1 (0x0)
+// CHECK-NEXT:          Base Register: SP (0x1)
+// CHECK-NEXT:          CFA Offset: 16
+// CHECK-NEXT:          RA Offset: -8
+  	nop
+        .cfi_endproc
+
+        .align 1024	
+fde1_uses_addr2:
+// CHECK:        FuncDescEntry [1] {
+// CHECK:          Start FRE Offset: 0x6
+// CHECK-NEXT:          Num FREs: 2
+// CHECK:            FRE Type: Addr2 (0x1)
+	.cfi_startproc
+// CHECK:        Frame Row Entry {
+// CHECK-NEXT:          Start Address: 0x400
+        .fill 0xFF + 1
+	.cfi_def_cfa_offset 16
+// CHECK:        Frame Row Entry {
+// CHECK-NEXT:          Start Address: 0x500
+        .cfi_endproc
+
+.align 1024	
+fde2_uses_addr2:
+// CHECK:        FuncDescEntry [2] {
+// CHECK:          Start FRE Offset: 0xE
+// CHECK-NEXT:          Num FREs: 2
+// CHECK:            FRE Type: Addr2 (0x1)
+	.cfi_startproc
+// CHECK:        Frame Row Entry {
+// CHECK-NEXT:          Start Address: 0x800
+        .fill 0xFFFF
+	.cfi_def_cfa_offset 16
+// CHECK:        Frame Row Entry {
+// CHECK-NEXT:          Start Address: 0x107FF
+	nop
+        .cfi_endproc
+
+        .align 1024
+fde3_uses_addr4:
+// CHECK:        FuncDescEntry [3] {
+// CHECK:          Start FRE Offset: 0x16
+// CHECK-NEXT:          Num FREs: 2
+// CHECK:            FRE Type: Addr4 (0x2)
+	.cfi_startproc
+// CHECK:        Frame Row Entry {
+// CHECK-NEXT:          Start Address: 0x10800
+        .fill 0xFFFF + 1
+// CHECK:        Frame Row Entry {
+// CHECK-NEXT:          Start Address: 0x20800
+	.cfi_def_cfa_offset 16
+	nop
+        .cfi_endproc
+
diff --git a/llvm/test/MC/ELF/cfi-sframe-fre-cases.s b/llvm/test/MC/ELF/cfi-sframe-fre-cases.s
new file mode 100644
index 0000000000000..61ed58f8a9d0e
--- /dev/null
+++ b/llvm/test/MC/ELF/cfi-sframe-fre-cases.s
@@ -0,0 +1,114 @@
+// REQUIRES: x86-registered-target
+// RUN: llvm-mc --assemble --filetype=obj --gsframe -triple x86_64 %s -o %t.o
+// RUN: llvm-readelf --sframe %t.o | FileCheck %s
+
+// Tests selection for the proper FRE::BX encoding at the boundaries
+// between int8_t, int16_t, and int32_t.  Ensures the largest offset
+// between CFA, RA, and FP governs. Align functions to 1024 to make it
+// easier to interpet offsets. Some directives require alignment, so
+// it isn't always possible to test exact boundaries.
+
+// Also, check that irrelevant cfi directives don't create new fres,
+// or affect the current ones. Checking the Start Address ensures that
+// the proper FRE gets the proper checks. Using .long makes addresses
+// architecture independent.
+
+        .align 1024
+fde4_fre_offset_sizes:
+// CHECK:        FuncDescEntry [0] {
+// CHECK:          Start FRE Offset: 0
+// CHECK:            FRE Type: Addr1 (0x0)
+	.cfi_startproc
+// CHECK:        Frame Row Entry {
+// CHECK-NEXT:          Start Address: 0x0
+// CHECK-NEXT:          Return Address Signed: No
+// CHECK-NEXT:          Offset Size: B1 (0x0)
+// CHECK-NEXT:          Base Register: SP (0x1)
+// CHECK-NEXT:          CFA Offset: 8
+// CHECK-NEXT:          RA Offset: -8
+        .long 0
+// Uninteresting register no new fre, no effect on cfa
+	.cfi_offset 0, 8
+        .long 0
+	.cfi_def_cfa_offset 0x78
+// CHECK:        Frame Row Entry {
+// CHECK-NEXT:          Start Address: 0x8
+// CHECK-NEXT:          Return Address Signed: No
+// CHECK-NEXT:          Offset Size: B1 (0x0)
+// CHECK-NEXT:          Base Register: SP (0x1)
+// CHECK-NEXT:          CFA Offset: 120
+// CHECK-NEXT:          RA Offset: -8
+	.long 0
+// Uninteresting register no new fre, no effect on cfa
+        .cfi_rel_offset 1, 8
+        .long 0 
+	.cfi_def_cfa_offset 0x80 
+// CHECK:        Frame Row Entry {
+// CHECK-NEXT:          Start Address: 0x10
+// CHECK-NEXT:          Return Address Signed: No
+// CHECK-NEXT:          Offset Size: B2 (0x1)
+// CHECK-NEXT:          Base Register: SP (0x1)
+// CHECK-NEXT:          CFA Offset: 128
+// CHECK-NEXT:          RA Offset: -8
+	.long 0
+// Uninteresting register no new fre, no effect on cfa
+        .cfi_val_offset 1, 8
+        .long 0
+	.cfi_def_cfa_offset 0x7FFF
+// CHECK:        Frame Row Entry {
+// CHECK-NEXT:          Start Address: 0x18
+// CHECK-NEXT:          Return Address Signed: No
+// CHECK-NEXT:          Offset Size: B2 (0x1)
+// CHECK-NEXT:          Base Register: SP (0x1)
+// CHECK-NEXT:          CFA Offset: 32767
+// CHECK-NEXT:          RA Offset: -8
+	.long 0
+	.cfi_def_cfa_offset 0x8000
+// CHECK:        Frame Row Entry {
+// CHECK-NEXT:          Start Address: 0x1C
+// CHECK-NEXT:          Return Address Signed: No
+// CHECK-NEXT:          Offset Size: B4 (0x2)
+// CHECK-NEXT:          Base Register: SP (0x1)
+// CHECK-NEXT:          CFA Offset: 32768
+// CHECK-NEXT:          RA Offset: -8
+	.long 0
+	.cfi_def_cfa_offset 0x8
+// CHECK:        Frame Row Entry {
+// CHECK-NEXT:          Start Address: 0x20
+// CHECK-NEXT:          Return Address Signed: No
+// CHECK-NEXT:          Offset Size: B1 (0x0)
+// CHECK-NEXT:          Base Register: SP (0x1)
+// CHECK-NEXT:          CFA Offset: 8
+// CHECK-NEXT:          RA Offset: -8
+	.long 0
+	.cfi_adjust_cfa_offset 0x8
+// CHECK:        Frame Row Entry {
+// CHECK-NEXT:          Start Address: 0x24
+// CHECK-NEXT:          Return Address Signed: No
+// CHECK-NEXT:          Offset Size: B1 (0x0)
+// CHECK-NEXT:          Base Register: SP (0x1)
+// CHECK-NEXT:          CFA Offset: 16
+// CHECK-NEXT:          RA Offset: -8
+	.long 0
+	.cfi_def_cfa_register  6  # switch to fp
+// CHECK:        Frame Row Entry {
+// CHECK-NEXT:          Start Address: 0x28
+// CHECK-NEXT:          Return Address Signed: No
+// CHECK-NEXT:          Offset Size: B1 (0x0)
+// CHECK-NEXT:          Base Register: FP (0x0)
+// CHECK-NEXT:          CFA Offset: 16
+// CHECK-NEXT:          RA Offset: -8
+	.long 0
+	.cfi_offset 7, 32
+	# sp not the cfa but with large offset still changes encoding.
+	.cfi_offset 6, 0x7FF8
+// CHECK:        Frame Row Entry {
+// CHECK-NEXT:          Start Address: 0x2C
+// CHECK-NEXT:          Return Address Signed: No
+// CHECK-NEXT:          Offset Size: B2 (0x1)
+// CHECK-NEXT:          Base Register: FP (0x0)
+// CHECK-NEXT:          CFA Offset: 16
+// CHECK-NEXT:          RA Offset: -8
+// CHECK-NEXT:          FP Offset: 32760
+	.long 0
+        .cfi_endproc
diff --git a/llvm/test/MC/ELF/cfi-sframe.s b/llvm/test/MC/ELF/cfi-sframe.s
index ecf77bc3ea6b3..28ee3cbf33b16 100644
--- a/llvm/test/MC/ELF/cfi-sframe.s
+++ b/llvm/test/MC/ELF/cfi-sframe.s
@@ -4,27 +4,35 @@
 // RUN: llvm-readelf --sframe %t.o | FileCheck %s
 
 	.cfi_sections .sframe
-f1:
-	.cfi_startproc  // FRE 0
+f0:
+	.cfi_startproc  # FRE 0
 	nop
-	.cfi_def_cfa_offset 16  // FRE 1
-	.cfi_def_cfa_offset 8   // location didn't change. No new FRE, but new offset.
+	.cfi_def_cfa_offset 16  # FRE 1 
+	.cfi_def_cfa_offset 8   # location didn't change. No new FRE, but new offset.
 	nop
-	.cfi_def_cfa_offset 8   // offset didn't change. No new FRE.
+	.cfi_def_cfa_offset 8   # offset didn't change. No new FRE.
 	nop
-	.cfi_def_cfa_offset 16  // FRE 2. new location, new offset.
+	.cfi_def_cfa_offset 16  # FRE 2. new location, new offset.
 	nop
-	.cfi_register 0, 1      // Uninteresting register. No new FRE.
+	.cfi_register 0, 1      # Uninteresting register. No new FRE.
 	nop
 
         .cfi_endproc
 
-f2:
+f1:
 	.cfi_startproc
 	nop
 	nop
         .cfi_endproc
 
+f2:
+	.cfi_startproc
+        .cfi_endproc
+
+f3:
+	.cfi_startproc simple
+        .cfi_endproc
+
 // CHECK: SFrame section '.sframe' {
 // CHECK-NEXT:  Header {
 // CHECK-NEXT:    Magic: 0xDEE2
@@ -32,11 +40,11 @@ f2:
 // CHECK-NEXT:    Flags [ (0x4)
 // CHECK:    ABI: AMD64EndianLittle (0x3)
 // CHECK-NEXT:    CFA fixed FP offset (unused): 0
-// CHECK-NEXT:    CFA fixed RA offset: 0
+// CHECK-NEXT:    CFA fixed RA offset: -8
 // CHECK-NEXT:    Auxiliary header length: 0
 // CHECK-NEXT:    Num FDEs: 2
-// CHECK-NEXT:    Num FREs: 0
-// CHECK-NEXT:    FRE subsection length: 0
+// CHECK-NEXT:    Num FREs: 4
+// CHECK-NEXT:    FRE subsection length: 12
 // CHECK-NEXT:    FDE subsection offset: 0
 // CHECK-NEXT:    FRE subsection offset: 40
 // CHECK:    Function Index [
@@ -48,7 +56,7 @@ f2:
 // CHECK-NEXT:          }
 // CHECK-NEXT:          Size: 0x5
 // CHECK-NEXT:          Start FRE Offset: 0x0
-// CHECK-NEXT:          Num FREs: 0
+// CHECK-NEXT:          Num FREs: 3
 // CHECK-NEXT:          Info {
 // CHECK-NEXT:            FRE Type: Addr1 (0x0)
 // CHECK-NEXT:            FDE Type: PCInc (0x0)
@@ -56,18 +64,18 @@ f2:
 // CHECK-NEXT:          }
 // CHECK-NEXT:          Repetitive block size (unused): 0x0
 // CHECK-NEXT:          Padding2: 0x0
-// CHECK-NEXT:          FREs [
-// CHECK-NEXT:          ]
-// CHECK-NEXT:        }
-// CHECK-NEXT:        FuncDescEntry [1] {
+
+// Contents of FREs are tested elsewhere
+	
+// CHECK:             FuncDescEntry [1] {
 // CHECK-NEXT:          PC {
 // CHECK-NEXT:            Relocation: {{.*}}PC32{{.*}}
 // CHECK-NEXT:            Symbol Name: .text
 // CHECK-NEXT:            Start Address: {{.*}}
 // CHECK-NEXT:          }
 // CHECK-NEXT:          Size: 0x2
-// CHECK-NEXT:          Start FRE Offset: 0x0
-// CHECK-NEXT:          Num FREs: 0
+// CHECK-NEXT:          Start FRE Offset: 0x9
+// CHECK-NEXT:          Num FREs: 1
 // CHECK-NEXT:          Info {
 // CHECK-NEXT:            FRE Type: Addr1 (0x0)
 // CHECK-NEXT:            FDE Type: PCInc (0x0)
@@ -75,8 +83,5 @@ f2:
 // CHECK-NEXT:          }
 // CHECK-NEXT:          Repetitive block size (unused): 0x0
 // CHECK-NEXT:          Padding2: 0x0
-// CHECK-NEXT:          FREs [
-// CHECK-NEXT:          ]
-// CHECK-NEXT:        }
-// CHECK-NEXT:      ]
-// CHECK-NEXT:    }
+
+

>From b5078110270b3e799ca0549d317f4120f1e9a740 Mon Sep 17 00:00:00 2001
From: Sterling Augustine <saugustine at google.com>
Date: Thu, 11 Sep 2025 14:11:35 -0700
Subject: [PATCH 2/3] Fix formatting.

---
 llvm/include/llvm/MC/MCAssembler.h | 1 -
 llvm/lib/MC/MCFragment.cpp         | 2 +-
 llvm/lib/MC/MCObjectStreamer.cpp   | 4 ++--
 3 files changed, 3 insertions(+), 4 deletions(-)

diff --git a/llvm/include/llvm/MC/MCAssembler.h b/llvm/include/llvm/MC/MCAssembler.h
index a9924a90260bd..6e1d6421b8d33 100644
--- a/llvm/include/llvm/MC/MCAssembler.h
+++ b/llvm/include/llvm/MC/MCAssembler.h
@@ -119,7 +119,6 @@ class MCAssembler {
   void relaxDwarfCallFrameFragment(MCFragment &F);
   void relaxSFrameFragment(MCFragment &DF);
 
-
 public:
   /// Construct a new assembler instance.
   //
diff --git a/llvm/lib/MC/MCFragment.cpp b/llvm/lib/MC/MCFragment.cpp
index 2e68de7807399..85d1c5888f1da 100644
--- a/llvm/lib/MC/MCFragment.cpp
+++ b/llvm/lib/MC/MCFragment.cpp
@@ -80,7 +80,7 @@ LLVM_DUMP_METHOD void MCFragment::dump() const {
   case MCFragment::FT_Align:
   case MCFragment::FT_LEB:
   case MCFragment::FT_Dwarf:
-  case MCFragment::FT_DwarfFrame: 
+  case MCFragment::FT_DwarfFrame:
   case MCFragment::FT_SFrame: {
     if (isLinkerRelaxable())
       OS << " LinkerRelaxable";
diff --git a/llvm/lib/MC/MCObjectStreamer.cpp b/llvm/lib/MC/MCObjectStreamer.cpp
index 7279a0f8945a0..701a0836d2c70 100644
--- a/llvm/lib/MC/MCObjectStreamer.cpp
+++ b/llvm/lib/MC/MCObjectStreamer.cpp
@@ -28,7 +28,8 @@ MCObjectStreamer::MCObjectStreamer(MCContext &Context,
                                    std::unique_ptr<MCAsmBackend> TAB,
                                    std::unique_ptr<MCObjectWriter> OW,
                                    std::unique_ptr<MCCodeEmitter> Emitter)
-    : MCStreamer(Context),      Assembler(std::make_unique<MCAssembler>(
+    : MCStreamer(Context),
+      Assembler(std::make_unique<MCAssembler>(
           Context, std::move(TAB), std::move(Emitter), std::move(OW))),
       EmitEHFrame(true), EmitDebugFrame(false), EmitSFrame(false) {
   assert(Assembler->getBackendPtr() && Assembler->getEmitterPtr());
@@ -595,7 +596,6 @@ void MCObjectStreamer::emitSFrameCalculateFuncOffset(const MCSymbol *FuncBase,
   newFragment();
 }
 
-
 void MCObjectStreamer::emitCVLocDirective(unsigned FunctionId, unsigned FileNo,
                                           unsigned Line, unsigned Column,
                                           bool PrologueEnd, bool IsStmt,

>From 6fc06308b021b2ae2591a9a165edebfd1779cefc Mon Sep 17 00:00:00 2001
From: Sterling Augustine <saugustine at google.com>
Date: Fri, 12 Sep 2025 10:12:13 -0700
Subject: [PATCH 3/3] Ensure bitfield-based structure has padding bits
 initialized

---
 llvm/lib/MC/MCSFrame.cpp | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/llvm/lib/MC/MCSFrame.cpp b/llvm/lib/MC/MCSFrame.cpp
index 60446d1657034..3feb14505ea24 100644
--- a/llvm/lib/MC/MCSFrame.cpp
+++ b/llvm/lib/MC/MCSFrame.cpp
@@ -474,6 +474,7 @@ void MCSFrameEmitter::encodeFuncOffset(MCContext &C, uint64_t Offset,
     // Offset is the difference between the function start label and the final
     // FRE's offset, which is the max offset for this FDE.
     FDEInfo<endianness::native> I;
+    I.Info = 0;
     if (isUInt<8>(Offset))
       I.setFREType(FREType::Addr1);
     else if (isUInt<16>(Offset))
@@ -483,6 +484,9 @@ void MCSFrameEmitter::encodeFuncOffset(MCContext &C, uint64_t Offset,
       I.setFREType(FREType::Addr4);
     }
     I.setFDEType(FDEType::PCInc);
+    // TODO: When we support pauth keys, this will need to be retrieved
+    // from the frag itself.
+    I.setPAuthKey(0);
 
     Out.push_back(I.getFuncInfo());
     return;



More information about the llvm-commits mailing list