[BinaryFormat] Add "SFrame" structures and constants (#147264)

This patch defines the structures and constants used by the SFrame
unwind info format supported by GNU binutils. For more information about
the format, see https://sourceware.org/binutils/wiki/sframe and
https://discourse.llvm.org/t/rfc-adding-sframe-support-to-llvm/86900

The patch uses the GNU names for all relevant entities, but I've converted
them to the llvm naming convention, and I've replaced macros with enums.
diff --git a/llvm/include/llvm/BinaryFormat/SFrame.h b/llvm/include/llvm/BinaryFormat/SFrame.h
new file mode 100644
index 0000000..16d3b16
--- /dev/null
+++ b/llvm/include/llvm/BinaryFormat/SFrame.h
@@ -0,0 +1,165 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+/// \file
+/// This file contains data-structure definitions and constants to support
+/// unwinding based on .sframe sections.  This only supports SFRAME_VERSION_2
+/// as described at https://sourceware.org/binutils/docs/sframe-spec.html
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_BINARYFORMAT_SFRAME_H
+#define LLVM_BINARYFORMAT_SFRAME_H
+
+#include "llvm/ADT/BitmaskEnum.h"
+#include "llvm/Support/DataTypes.h"
+#include "llvm/Support/Endian.h"
+
+namespace llvm::sframe {
+
+LLVM_ENABLE_BITMASK_ENUMS_IN_NAMESPACE();
+
+constexpr uint16_t Magic = 0xdee2;
+
+enum class Version : uint8_t {
+  V1 = 1,
+  V2 = 2,
+};
+
+enum class Flags : uint8_t {
+  FDESorted = 0x01,
+  FramePointer = 0x02,
+  FDEFuncStartPCRel = 0x04,
+  V2AllFlags = FDESorted | FramePointer | FDEFuncStartPCRel,
+  LLVM_MARK_AS_BITMASK_ENUM(/*LargestValue=*/0xff),
+};
+
+enum class ABI : uint8_t {
+  AArch64EndianBig = 1,
+  AArch64EndianLittle = 2,
+  AMD64EndianLittle = 3,
+};
+
+/// SFrame FRE Types. Bits 0-3 of FuncDescEntry.Info.
+enum class FREType : uint8_t {
+  Addr1 = 0,
+  Addr2 = 1,
+  Addr4 = 2,
+};
+
+/// SFrame FDE Types. Bit 4 of FuncDescEntry.Info.
+enum class FDEType : uint8_t {
+  PCInc = 0,
+  PCMask = 1,
+};
+
+/// Speficies key used for signing return addresses. Bit 5 of
+/// FuncDescEntry.Info.
+enum class AArch64PAuthKey : uint8_t {
+  A = 0,
+  B = 1,
+};
+
+/// Size of stack offsets. Bits 5-6 of FREInfo.Info.
+enum class FREOffset : uint8_t {
+  B1 = 0,
+  B2 = 1,
+  B4 = 2,
+};
+
+/// Stack frame base register. Bit 0 of FREInfo.Info.
+enum class BaseReg : uint8_t {
+  FP = 0,
+  SP = 1,
+};
+
+namespace detail {
+template <typename T, endianness E>
+using packed =
+    support::detail::packed_endian_specific_integral<T, E, support::unaligned>;
+}
+
+template <endianness E> struct Preamble {
+  detail::packed<uint16_t, E> Magic;
+  detail::packed<enum Version, E> Version;
+  detail::packed<enum Flags, E> Flags;
+};
+
+template <endianness E> struct Header {
+  struct Preamble<E> Preamble;
+  detail::packed<ABI, E> ABIArch;
+  detail::packed<int8_t, E> CFAFixedFPOffset;
+  detail::packed<int8_t, E> CFAFixedRAOffset;
+  detail::packed<uint8_t, E> AuxHdrLen;
+  detail::packed<uint32_t, E> NumFDEs;
+  detail::packed<uint32_t, E> NumFREs;
+  detail::packed<uint32_t, E> FRELen;
+  detail::packed<uint32_t, E> FDEOff;
+  detail::packed<uint32_t, E> FREOff;
+};
+
+template <endianness E> struct FuncDescEntry {
+  detail::packed<int32_t, E> StartAddress;
+  detail::packed<uint32_t, E> Size;
+  detail::packed<uint32_t, E> StartFREOff;
+  detail::packed<uint32_t, E> NumFREs;
+  detail::packed<uint8_t, E> Info;
+  detail::packed<uint8_t, E> RepSize;
+  detail::packed<uint16_t, E> Padding2;
+
+  uint8_t getPAuthKey() const { return (Info >> 5) & 1; }
+  FDEType getFDEType() const { return static_cast<FDEType>((Info >> 4) & 1); }
+  FREType getFREType() const { return static_cast<FREType>(Info & 0xf); }
+  void setPAuthKey(uint8_t P) { setFuncInfo(P, getFDEType(), getFREType()); }
+  void setFDEType(FDEType D) { setFuncInfo(getPAuthKey(), D, getFREType()); }
+  void setFREType(FREType R) { setFuncInfo(getPAuthKey(), getFDEType(), R); }
+  void setFuncInfo(uint8_t PAuthKey, FDEType FDE, FREType FRE) {
+    Info = ((PAuthKey & 1) << 5) | ((static_cast<uint8_t>(FDE) & 1) << 4) |
+           (static_cast<uint8_t>(FRE) & 0xf);
+  }
+};
+
+template <endianness E> struct FREInfo {
+  detail::packed<uint8_t, E> Info;
+
+  bool isReturnAddressSigned() const { return Info >> 7; }
+  FREOffset getOffsetSize() const {
+    return static_cast<FREOffset>((Info >> 5) & 3);
+  }
+  uint8_t getOffsetCount() const { return (Info >> 1) & 0xf; }
+  BaseReg getBaseRegister() const { return static_cast<BaseReg>(Info & 1); }
+  void setReturnAddressSigned(bool RA) {
+    setFREInfo(RA, getOffsetSize(), getOffsetCount(), getBaseRegister());
+  }
+  void setOffsetSize(FREOffset Sz) {
+    setFREInfo(isReturnAddressSigned(), Sz, getOffsetCount(),
+               getBaseRegister());
+  }
+  void setOffsetCount(uint8_t N) {
+    setFREInfo(isReturnAddressSigned(), getOffsetSize(), N, getBaseRegister());
+  }
+  void setBaseRegister(BaseReg Reg) {
+    setFREInfo(isReturnAddressSigned(), getOffsetSize(), getOffsetCount(), Reg);
+  }
+  void setFREInfo(bool RA, FREOffset Sz, uint8_t N, BaseReg Reg) {
+    Info = ((RA & 1) << 7) | ((static_cast<uint8_t>(Sz) & 3) << 5) |
+           ((N & 0xf) << 1) | (static_cast<uint8_t>(Reg) & 1);
+  }
+};
+
+template <typename T, endianness E> struct FrameRowEntry {
+  detail::packed<T, E> StartAddress;
+  FREInfo<E> Info;
+};
+
+template <endianness E> using FrameRowEntryAddr1 = FrameRowEntry<uint8_t, E>;
+template <endianness E> using FrameRowEntryAddr2 = FrameRowEntry<uint16_t, E>;
+template <endianness E> using FrameRowEntryAddr4 = FrameRowEntry<uint32_t, E>;
+
+} // namespace llvm::sframe
+
+#endif // LLVM_BINARYFORMAT_SFRAME_H
diff --git a/llvm/unittests/BinaryFormat/CMakeLists.txt b/llvm/unittests/BinaryFormat/CMakeLists.txt
index 40d3bc4..eac5977a2 100644
--- a/llvm/unittests/BinaryFormat/CMakeLists.txt
+++ b/llvm/unittests/BinaryFormat/CMakeLists.txt
@@ -10,6 +10,7 @@
   MsgPackDocumentTest.cpp
   MsgPackReaderTest.cpp
   MsgPackWriterTest.cpp
+  SFrameTest.cpp
   TestFileMagic.cpp
   )
 
diff --git a/llvm/unittests/BinaryFormat/SFrameTest.cpp b/llvm/unittests/BinaryFormat/SFrameTest.cpp
new file mode 100644
index 0000000..394e382
--- /dev/null
+++ b/llvm/unittests/BinaryFormat/SFrameTest.cpp
@@ -0,0 +1,118 @@
+//===- SFrameTest.cpp -----------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/BinaryFormat/SFrame.h"
+#include "gtest/gtest.h"
+#include <type_traits>
+
+using namespace llvm;
+using namespace llvm::sframe;
+
+namespace {
+
+template <typename EndianT> class SFrameTest : public testing::Test {
+protected:
+  static constexpr endianness Endian = EndianT::value;
+
+  // Test structure sizes and triviality.
+  static_assert(std::is_trivial_v<Preamble<Endian>>);
+  static_assert(sizeof(Preamble<Endian>) == 4);
+
+  static_assert(std::is_trivial_v<Header<Endian>>);
+  static_assert(sizeof(Header<Endian>) == 28);
+
+  static_assert(std::is_trivial_v<FuncDescEntry<Endian>>);
+  static_assert(sizeof(FuncDescEntry<Endian>) == 20);
+
+  static_assert(std::is_trivial_v<FrameRowEntryAddr1<Endian>>);
+  static_assert(sizeof(FrameRowEntryAddr1<Endian>) == 2);
+
+  static_assert(std::is_trivial_v<FrameRowEntryAddr2<Endian>>);
+  static_assert(sizeof(FrameRowEntryAddr2<Endian>) == 3);
+
+  static_assert(std::is_trivial_v<FrameRowEntryAddr4<Endian>>);
+  static_assert(sizeof(FrameRowEntryAddr4<Endian>) == 5);
+};
+
+struct NameGenerator {
+  template <typename T> static constexpr const char *GetName(int) {
+    if constexpr (T::value == endianness::little)
+      return "little";
+    if constexpr (T::value == endianness::big)
+      return "big";
+  }
+};
+using Types =
+    testing::Types<std::integral_constant<endianness, endianness::little>,
+                   std::integral_constant<endianness, endianness::big>>;
+TYPED_TEST_SUITE(SFrameTest, Types, NameGenerator);
+
+TYPED_TEST(SFrameTest, FDEFlags) {
+  FuncDescEntry<TestFixture::Endian> FDE = {};
+  EXPECT_EQ(FDE.Info, 0u);
+  EXPECT_EQ(FDE.getPAuthKey(), 0);
+  EXPECT_EQ(FDE.getFDEType(), FDEType::PCInc);
+  EXPECT_EQ(FDE.getFREType(), FREType::Addr1);
+
+  FDE.setPAuthKey(1);
+  EXPECT_EQ(FDE.Info, 0x20u);
+  EXPECT_EQ(FDE.getPAuthKey(), 1);
+  EXPECT_EQ(FDE.getFDEType(), FDEType::PCInc);
+  EXPECT_EQ(FDE.getFREType(), FREType::Addr1);
+
+  FDE.setFDEType(FDEType::PCMask);
+  EXPECT_EQ(FDE.Info, 0x30u);
+  EXPECT_EQ(FDE.getPAuthKey(), 1);
+  EXPECT_EQ(FDE.getFDEType(), FDEType::PCMask);
+  EXPECT_EQ(FDE.getFREType(), FREType::Addr1);
+
+  FDE.setFREType(FREType::Addr4);
+  EXPECT_EQ(FDE.Info, 0x32u);
+  EXPECT_EQ(FDE.getPAuthKey(), 1);
+  EXPECT_EQ(FDE.getFDEType(), FDEType::PCMask);
+  EXPECT_EQ(FDE.getFREType(), FREType::Addr4);
+}
+
+TYPED_TEST(SFrameTest, FREFlags) {
+  FREInfo<TestFixture::Endian> Info = {};
+  EXPECT_EQ(Info.Info, 0u);
+  EXPECT_FALSE(Info.isReturnAddressSigned());
+  EXPECT_EQ(Info.getOffsetSize(), FREOffset::B1);
+  EXPECT_EQ(Info.getOffsetCount(), 0u);
+  EXPECT_EQ(Info.getBaseRegister(), BaseReg::FP);
+
+  Info.setReturnAddressSigned(true);
+  EXPECT_EQ(Info.Info, 0x80u);
+  EXPECT_TRUE(Info.isReturnAddressSigned());
+  EXPECT_EQ(Info.getOffsetSize(), FREOffset::B1);
+  EXPECT_EQ(Info.getOffsetCount(), 0u);
+  EXPECT_EQ(Info.getBaseRegister(), BaseReg::FP);
+
+  Info.setOffsetSize(FREOffset::B4);
+  EXPECT_EQ(Info.Info, 0xc0u);
+  EXPECT_TRUE(Info.isReturnAddressSigned());
+  EXPECT_EQ(Info.getOffsetSize(), FREOffset::B4);
+  EXPECT_EQ(Info.getOffsetCount(), 0u);
+  EXPECT_EQ(Info.getBaseRegister(), BaseReg::FP);
+
+  Info.setOffsetCount(3);
+  EXPECT_EQ(Info.Info, 0xc6u);
+  EXPECT_TRUE(Info.isReturnAddressSigned());
+  EXPECT_EQ(Info.getOffsetSize(), FREOffset::B4);
+  EXPECT_EQ(Info.getOffsetCount(), 3u);
+  EXPECT_EQ(Info.getBaseRegister(), BaseReg::FP);
+
+  Info.setBaseRegister(BaseReg::SP);
+  EXPECT_EQ(Info.Info, 0xc7u);
+  EXPECT_TRUE(Info.isReturnAddressSigned());
+  EXPECT_EQ(Info.getOffsetSize(), FREOffset::B4);
+  EXPECT_EQ(Info.getOffsetCount(), 3u);
+  EXPECT_EQ(Info.getBaseRegister(), BaseReg::SP);
+}
+
+} // namespace