Skip to content

Conversation

Sterling-Augustine
Copy link
Contributor

[Previously reverted due to msan failures on two uninitialized padding bits.]

This PR emits and relaxes the FREs generated in the previous PR.

After this change llvm emits usable sframe sections that can be linked with the gnu linker. There are a few remaining cfi directives to handle before they are generally usable, however.

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

Also, while working on the tests, I found a few bugs in older portions and cleaned those up.

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

This PR emits and relaxes the FREs generated in the previous PR.

After this change llvm emits usable sframe sections that can be
linked with the gnu linker. There are a few remaining cfi directives to
handle before they are generally usable, however.

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

Also, while working on the tests, I found a few bugs in older portions
and cleaned those up.

This is a fairly big commit, but I'm not sure how to make it smaller.
@Sterling-Augustine Sterling-Augustine added the skip-precommit-approval PR for CI feedback, not intended for review label Sep 18, 2025
@llvmbot llvmbot added llvm:mc Machine (object) code llvm:binary-utilities labels Sep 18, 2025
@llvmbot
Copy link
Member

llvmbot commented Sep 18, 2025

@llvm/pr-subscribers-llvm-binary-utilities

@llvm/pr-subscribers-llvm-mc

Author: None (Sterling-Augustine)

Changes

[Previously reverted due to msan failures on two uninitialized padding bits.]

This PR emits and relaxes the FREs generated in the previous PR.

After this change llvm emits usable sframe sections that can be linked with the gnu linker. There are a few remaining cfi directives to handle before they are generally usable, however.

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

Also, while working on the tests, I found a few bugs in older portions and cleaned those up.

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


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

13 Files Affected:

  • (modified) llvm/include/llvm/BinaryFormat/SFrame.h (+2)
  • (modified) llvm/include/llvm/MC/MCAsmBackend.h (+1)
  • (modified) llvm/include/llvm/MC/MCAssembler.h (+1)
  • (modified) llvm/include/llvm/MC/MCObjectStreamer.h (+3)
  • (modified) llvm/include/llvm/MC/MCSFrame.h (+14)
  • (modified) llvm/include/llvm/MC/MCSection.h (+25)
  • (modified) llvm/lib/MC/MCAssembler.cpp (+24)
  • (modified) llvm/lib/MC/MCFragment.cpp (+4-1)
  • (modified) llvm/lib/MC/MCObjectStreamer.cpp (+13)
  • (modified) llvm/lib/MC/MCSFrame.cpp (+157-17)
  • (added) llvm/test/MC/ELF/cfi-sframe-encoding.s (+85)
  • (added) llvm/test/MC/ELF/cfi-sframe-fre-cases.s (+113)
  • (modified) llvm/test/MC/ELF/cfi-sframe.s (+70-67)
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..6e1d6421b8d33 100644
--- a/llvm/include/llvm/MC/MCAssembler.h
+++ b/llvm/include/llvm/MC/MCAssembler.h
@@ -117,6 +117,7 @@ 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..85d1c5888f1da 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..701a0836d2c70 100644
--- a/llvm/lib/MC/MCObjectStreamer.cpp
+++ b/llvm/lib/MC/MCObjectStreamer.cpp
@@ -583,6 +583,19 @@ 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..066d1a34e1548 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,70 @@ 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) {}
+  SFrameFRE(const MCSymbol *Start) : Label(Start) { Info.Info = 0; }
+
+  void emitOffset(MCObjectStreamer &S, FREOffset OffsetSize, size_t Offset) {
+    switch (OffsetSize) {
+    case (FREOffset::B1):
+      S.emitInt8(Offset);
+      return;
+    case (FREOffset::B2):
+      S.emitInt16(Offset);
+      return;
+    case (FREOffset::B4):
+      S.emitInt32(Offset);
+      return;
+    }
+  }
+
+  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;
+    emitOffset(S, Info.getOffsetSize(), CFAOffset);
+    if (FPOffset) {
+      ++OffsetsEmitted;
+      emitOffset(S, Info.getOffsetSize(), FPOffset);
+    }
+    if (RAOffset) {
+      ++OffsetsEmitted;
+      emitOffset(S, Info.getOffsetSize(), RAOffset);
+    }
+    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 +107,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 +137,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 +167,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 +184,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 +256,8 @@ class SFrameEmitterImpl {
   }
 
 public:
-  SFrameEmitterImpl(MCObjectStreamer &Streamer) : Streamer(Streamer) {
+  SFrameEmitterImpl(MCObjectStreamer &Streamer)
+      : Streamer(Streamer), TotalFREs(0) {
     assert(Streamer.getContext()
                .getObjectFileInfo()
                ->getSFrameABIArch()
@@ -195,6 +270,7 @@ class SFrameEmitterImpl {
       SPReg = 31;
       RAReg = 29;
       FPReg = 30;
+      FixedRAOffset = 0;
       break;
     case ABI::AMD64EndianLittle:
       SPReg = 7;
@@ -202,6 +278,7 @@ class SFrameEmitterImpl {
       // MCDwarfFrameInfo constructor.
       RAReg = static_cast<unsigned>(INT_MAX);
       FPReg = 6;
+      FixedRAOffset = -8;
       break;
     }
 
@@ -219,10 +296,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 +360,11 @@ class SFrameEmitterImpl {
         LastLabel = L;
       }
     }
-    if (Valid)
+
+    if (Valid) {
       FDEs.push_back(FDE);
+      TotalFREs += FDE.FREs.size();
+    }
   }
 
   void emitPreamble() {
@@ -294,13 +380,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 +407,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 +447,55 @@ 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_func_info.
+  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;
+    I.Info = 0;
+    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);
+    // 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;
+  }
+
+  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..1f293030c7dde
--- /dev/null
+++ b/llvm/test/MC/ELF/cfi-sframe-encoding.s
@@ -0,0 +1,85 @@
+# RUN: llvm-mc --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 ...
[truncated]

@Sterling-Augustine Sterling-Augustine merged commit 714f032 into llvm:main Sep 18, 2025
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

llvm:binary-utilities llvm:mc Machine (object) code skip-precommit-approval PR for CI feedback, not intended for review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants