================
@@ -226,11 +257,560 @@ GetOptionalAbsDifference(const MCAssembler &Assembler, 
const MCSymbol *LHS,
   return value;
 }
 
+//===----------------------------------------------------------------------===//
+// V3 UNWIND_INFO Emission
+// See https://learn.microsoft.com/en-us/cpp/build/x64-unwind-information-v3
+//===----------------------------------------------------------------------===//
+
+/// Encode a single WinEH::Instruction as V3 WOD bytes.
+/// Appends encoded bytes to Out.
+void Win64EH::EncodeWOD(const WinEH::Instruction &Inst,
+                        SmallVectorImpl<uint8_t> &Out) {
+  switch (static_cast<Win64EH::UnwindOpcodes>(Inst.Operation)) {
+  case Win64EH::UOP_PushNonVol: {
+    // WOD_PUSH: 1 byte, bits [2:0] = 4, bits [7:3] = register (5-bit)
+    uint8_t Reg = Inst.Register & 0x1F;
+    Out.push_back((Reg << 3) | Win64EH::WOD_PUSH);
+    break;
+  }
+  case Win64EH::UOP_AllocSmall: {
+    // WOD_ALLOC_SMALL: 1 byte, bits [3:0] = 8, bits [7:4] = (size/8 - 1)
+    // V1/V2 stores (size-8)/8 in OpInfo; actual size = Offset.
+    // Inst.Offset is the raw allocation size.
+    assert(Inst.Offset >= 8 && Inst.Offset <= 128 && Inst.Offset % 8 == 0 &&
+           "UOP_AllocSmall outside expected range or alignment");
+    uint8_t Encoded = ((Inst.Offset / 8 - 1) & 0x0F);
+    Out.push_back((Encoded << 4) | Win64EH::WOD_ALLOC_SMALL);
+    break;
+  }
+  case Win64EH::UOP_AllocLarge: {
+    if (Inst.Offset > 512 * 1024 - 8) {
+      // WOD_ALLOC_HUGE: 5 bytes, byte[0] = 1, bytes[1:4] = LE32(size)
+      Out.push_back(Win64EH::WOD_ALLOC_HUGE);
+      uint32_t Size = Inst.Offset;
+      Out.push_back(Size & 0xFF);
+      Out.push_back((Size >> 8) & 0xFF);
+      Out.push_back((Size >> 16) & 0xFF);
+      Out.push_back((Size >> 24) & 0xFF);
+    } else {
+      // WOD_ALLOC_LARGE: 3 bytes, byte[0] = 2, bytes[1:2] = LE16(size/8)
+      Out.push_back(Win64EH::WOD_ALLOC_LARGE);
+      uint16_t Scaled = Inst.Offset / 8;
+      Out.push_back(Scaled & 0xFF);
+      Out.push_back((Scaled >> 8) & 0xFF);
+    }
+    break;
+  }
+  case Win64EH::UOP_SetFPReg: {
+    // WOD_SET_FPREG: 2 bytes, byte[0] = 0, byte[1] = reg(4) | (offset/16)(4)
+    Out.push_back(Win64EH::WOD_SET_FPREG);
+    uint8_t Reg = Inst.Register & 0x0F;
+    uint8_t Off = (Inst.Offset / 16) & 0x0F;
+    Out.push_back(Reg | (Off << 4));
+    break;
+  }
+  case Win64EH::UOP_SaveNonVol: {
+    // WOD_SAVE_NONVOL: 3 bytes, bits [2:0] = 6, bits [7:3] = reg,
+    // bytes[1:2] = LE16(displacement/8)
+    uint8_t Reg = Inst.Register & 0x1F;
+    Out.push_back((Reg << 3) | Win64EH::WOD_SAVE_NONVOL);
+    uint16_t Disp = Inst.Offset / 8;
+    Out.push_back(Disp & 0xFF);
+    Out.push_back((Disp >> 8) & 0xFF);
+    break;
+  }
+  case Win64EH::UOP_SaveNonVolBig: {
+    // WOD_SAVE_NONVOL_FAR: 5 bytes, bits [2:0] = 5, bits [7:3] = reg,
+    // bytes[1:4] = LE32(displacement)
+    uint8_t Reg = Inst.Register & 0x1F;
+    Out.push_back((Reg << 3) | Win64EH::WOD_SAVE_NONVOL_FAR);
+    uint32_t Disp = Inst.Offset;
+    Out.push_back(Disp & 0xFF);
+    Out.push_back((Disp >> 8) & 0xFF);
+    Out.push_back((Disp >> 16) & 0xFF);
+    Out.push_back((Disp >> 24) & 0xFF);
+    break;
+  }
+  case Win64EH::UOP_SaveXMM128: {
+    // WOD_SAVE_XMM128: 3 bytes, bits [3:0] = 10, bits [7:4] = reg,
+    // bytes[1:2] = LE16(displacement/16)
+    uint8_t Reg = Inst.Register & 0x0F;
+    Out.push_back((Reg << 4) | Win64EH::WOD_SAVE_XMM128);
+    uint16_t Disp = Inst.Offset / 16;
+    Out.push_back(Disp & 0xFF);
+    Out.push_back((Disp >> 8) & 0xFF);
+    break;
+  }
+  case Win64EH::UOP_SaveXMM128Big: {
+    // WOD_SAVE_XMM128_FAR: 5 bytes, bits [3:0] = 9, bits [7:4] = reg,
+    // bytes[1:4] = LE32(displacement)
+    uint8_t Reg = Inst.Register & 0x0F;
+    Out.push_back((Reg << 4) | Win64EH::WOD_SAVE_XMM128_FAR);
+    uint32_t Disp = Inst.Offset;
+    Out.push_back(Disp & 0xFF);
+    Out.push_back((Disp >> 8) & 0xFF);
+    Out.push_back((Disp >> 16) & 0xFF);
+    Out.push_back((Disp >> 24) & 0xFF);
+    break;
+  }
+  case Win64EH::UOP_PushMachFrame: {
+    // WOD_PUSH_CANONICAL_FRAME: 2 bytes, byte[0] = 3, byte[1] = type
+    Out.push_back(Win64EH::WOD_PUSH_CANONICAL_FRAME);
+    Out.push_back(Inst.Offset == 1 ? 1 : 0);
+    break;
+  }
+  case Win64EH::UOP_Push2: {
+    uint8_t Reg1 = Inst.Register & 0x1F;
+    uint8_t Reg2 = Inst.Register2 & 0x1F;
+    // Optimization: if registers are consecutive, use WOD_PUSH_CONSECUTIVE_2
+    // (opcode 7, 1 byte) instead of WOD_PUSH2 (opcode 32, 2 bytes).
+    if (Reg2 == Reg1 + 1) {
+      // WOD_PUSH_CONSECUTIVE_2: bits [2:0] = 111b, bits [7:3] = Register
+      Out.push_back((Reg1 << 3) | Win64EH::WOD_PUSH_CONSECUTIVE_2);
+    } else {
+      // WOD_PUSH2: 2 bytes
+      // Byte 0: [5:0] = 100000b (opcode 32), [7:6] = Register1[1:0]
+      // Byte 1: [2:0] = Register1[4:2], [7:3] = Register2
+      Out.push_back(((Reg1 & 0x03) << 6) | Win64EH::WOD_PUSH2);
+      Out.push_back(((Reg2 & 0x1F) << 3) | ((Reg1 >> 2) & 0x07));
+    }
+    break;
+  }
+  default:
+    llvm_unreachable("Unsupported unwind operation for V3 encoding");
+  }
+}
+
+/// Try to find Needle as a contiguous subsequence within Haystack.
+/// Returns the byte offset if found, or std::nullopt.
+static std::optional<uint16_t> FindInPool(ArrayRef<uint8_t> Haystack,
+                                          ArrayRef<uint8_t> Needle) {
+  assert(!Needle.empty() && "FindInPool called with empty Needle");
+  auto It = std::search(Haystack.begin(), Haystack.end(), Needle.begin(),
+                        Needle.end());
+  if (It == Haystack.end())
+    return std::nullopt;
+  return static_cast<uint16_t>(std::distance(Haystack.begin(), It));
+}
+
+/// Compare the relative IP offset arrays of two epilogs.
+static bool EpilogIpOffsetsMatch(const WinEH::FrameInfo::Epilog &A,
+                                 const WinEH::FrameInfo::Epilog &B,
+                                 const MCAssembler &Asm) {
+  if (A.Instructions.size() != B.Instructions.size())
+    return false;
+  for (unsigned I = 0; I < A.Instructions.size(); ++I) {
+    auto OffA = GetOptionalAbsDifference(Asm, A.Instructions[I].Label, 
A.Start);
+    auto OffB = GetOptionalAbsDifference(Asm, B.Instructions[I].Label, 
B.Start);
+    if (!OffA || !OffB || *OffA != *OffB)
+      return false;
+  }
+  return true;
+}
+
+/// Emit V3 UNWIND_INFO for a single frame.
+static void EmitUnwindInfoV3(MCStreamer &Streamer, WinEH::FrameInfo *Info) {
+  // Should have been checked by our caller.
+  assert(!Info->Symbol && "UNWIND_INFO already has a symbol");
+
+  MCContext &Context = Streamer.getContext();
+  MCObjectStreamer *OS = static_cast<MCObjectStreamer *>(&Streamer);
+  const MCAssembler &Asm = OS->getAssembler();
+
+  MCSymbol *Label = Context.createTempSymbol();
+  Streamer.emitValueToAlignment(Align(4));
+  Streamer.emitLabel(Label);
+  Info->Symbol = Label;
+
+  // ===================================================================
+  // Phase 1: Data preparation — compute all metadata before emitting.
+  // ===================================================================
+
+  // --- Build prolog WOD pool (body-to-entry order) ---
+  SmallVector<uint8_t, 64> WODPool;
+  for (auto It = Info->Instructions.rbegin(); It != Info->Instructions.rend();
+       ++It)
+    Win64EH::EncodeWOD(*It, WODPool);
+
+  // --- Build prolog IP offset label pairs (body-to-entry order) ---
+  SmallVector<std::pair<const MCSymbol *, const MCSymbol *>, 16> 
PrologIpLabels;
+  unsigned PrologOpCount = Info->Instructions.size();
+  if (PrologOpCount > 31) {
+    reportFatalUsageError(
+        "Too many prolog unwind codes for V3 encoding. Maximum "
+        "is 31. This function has " +
+        Twine(PrologOpCount));
+  }
+  for (auto It = Info->Instructions.rbegin(); It != Info->Instructions.rend();
+       ++It)
+    PrologIpLabels.push_back({It->Label, Info->Begin});
+
+  // --- Determine if UNW_FLAG_LARGE is needed for the prolog ---
+  // Conservative: if we KNOW a value exceeds 255 or can't measure, use LARGE.
+  // Reject evaluatable values that are negative or exceed the 16-bit unsigned
+  // range supported by LARGE, since those would be silently truncated.
+  bool NeedsLargeProlog = false;
+  if (Info->PrologEnd) {
+    auto MaybePrologSize =
+        GetOptionalAbsDifference(Asm, Info->PrologEnd, Info->Begin);
+    if (MaybePrologSize) {
+      if (*MaybePrologSize < 0)
+        reportFatalUsageError("Negative SizeOfProlog in V3 unwind info");
+      if (*MaybePrologSize > UINT16_MAX)
+        reportFatalUsageError(
+            "SizeOfProlog exceeds 16-bit range for V3 unwind info");
+      NeedsLargeProlog = (*MaybePrologSize > 255);
+    } else {
+      NeedsLargeProlog = true; // Can't measure -> be conservative
+    }
+  }
+  for (auto &[InstLabel, BeginLabel] : PrologIpLabels) {
+    if (NeedsLargeProlog)
+      break;
+    auto MaybeOffset = GetOptionalAbsDifference(Asm, InstLabel, BeginLabel);
+    if (MaybeOffset) {
+      if (*MaybeOffset < 0)
+        reportFatalUsageError("Negative prolog IP offset in V3 unwind info");
+      if (*MaybeOffset > UINT16_MAX)
+        reportFatalUsageError(
+            "Prolog IP offset exceeds 16-bit range for V3 unwind info");
+      NeedsLargeProlog = (*MaybeOffset > 255);
+    } else {
+      NeedsLargeProlog = true; // Can't measure -> be conservative
+    }
+  }
+
+  // --- Per-epilog data preparation ---
+  struct EpilogEmitInfo {
+    const WinEH::FrameInfo::Epilog *Epilog;
+    SmallVector<uint8_t, 32> WODBytes;
+    uint16_t FirstOp;
+    uint8_t NumberOfOps;
+    bool Inherited;
+    bool NeedsLarge; // EPILOG_INFO_LARGE needed for this epilog
+  };
+
+  SmallVector<EpilogEmitInfo, 8> EpilogInfos;
+  for (const auto &[EpilogSym, Epilog] : Info->EpilogMap) {
+    if (Epilog.Instructions.empty())
+      continue;
+
+    EpilogEmitInfo EI;
+    EI.Epilog = &Epilog;
+    EI.NumberOfOps = Epilog.Instructions.size();
+    if (EI.NumberOfOps > 31)
+      reportFatalUsageError(
+          "Too many epilog unwind codes for V3 encoding. Maximum "
+          "is 31. This epilog has " +
+          Twine(EI.NumberOfOps));
+    EI.Inherited = false;
+    EI.NeedsLarge = false;
+
+    // Determine if EPILOG_INFO_LARGE is needed.
+    // Check IpOffsetOfLastInstruction (EpilogEnd - EpilogStart).
+    // Reject negative or out-of-range evaluatable values.
+    auto MaybeLastInstOfs =
+        GetOptionalAbsDifference(Asm, Epilog.End, Epilog.Start);
+    if (MaybeLastInstOfs) {
+      if (*MaybeLastInstOfs < 0)
+        reportFatalUsageError(
+            "Negative IpOffsetOfLastInstruction in V3 unwind info");
+      if (*MaybeLastInstOfs > UINT16_MAX)
+        reportFatalUsageError(
+            "IpOffsetOfLastInstruction exceeds 16-bit range for "
+            "V3 unwind info");
+      EI.NeedsLarge = (*MaybeLastInstOfs > 255);
+    } else {
+      EI.NeedsLarge = true; // Can't measure -> be conservative
+    }
+
+    // Check each epilog IP offset.
+    for (const auto &EpiInst : Epilog.Instructions) {
+      if (EI.NeedsLarge)
+        break;
+      auto MaybeOffset =
+          GetOptionalAbsDifference(Asm, EpiInst.Label, Epilog.Start);
+      if (MaybeOffset) {
+        if (*MaybeOffset < 0)
+          reportFatalUsageError("Negative epilog IP offset in V3 unwind info");
+        if (*MaybeOffset > UINT16_MAX)
+          reportFatalUsageError(
+              "Epilog IP offset exceeds 16-bit range for V3 unwind info");
+        EI.NeedsLarge = (*MaybeOffset > 255);
+      } else {
+        EI.NeedsLarge = true; // Can't measure -> be conservative
+      }
+    }
+
+    // Encode this epilog's WODs (forward order: body-to-terminator).
+    for (const auto &Inst : Epilog.Instructions)
+      Win64EH::EncodeWOD(Inst, EI.WODBytes);
+
+    // Pool sharing: try to re-use existing bytes in the pool rather than
+    // appending.
+    if (auto Offset = FindInPool(WODPool, EI.WODBytes)) {
+      EI.FirstOp = *Offset;
+    } else {
+      EI.FirstOp = WODPool.size();
+      WODPool.append(EI.WODBytes.begin(), EI.WODBytes.end());
+    }
+
+    EpilogInfos.push_back(std::move(EI));
+  }
+  if (EpilogInfos.size() > 7)
+    reportFatalUsageError("Too many epilogs for V3 encoding. Maximum is 7."
+                          " This function has " +
+                          Twine(EpilogInfos.size()));
+
+  // --- Inheritance decisions ---
+  // An epilog can use the inherited (3-byte) descriptor when FirstOp,
+  // NumberOfOps, NeedsLarge, and relative IP offsets all match the previous
+  // epilog.
+  for (unsigned I = 1; I < EpilogInfos.size(); ++I) {
+    auto &Prev = EpilogInfos[I - 1];
+    auto &Curr = EpilogInfos[I];
+    if (Curr.FirstOp == Prev.FirstOp && Curr.NumberOfOps == Prev.NumberOfOps &&
+        Curr.NeedsLarge == Prev.NeedsLarge &&
+        EpilogIpOffsetsMatch(*Curr.Epilog, *Prev.Epilog, OS->getAssembler()))
+      Curr.Inherited = true;
+  }
+
+  // --- Compute payload sizes ---
+  unsigned PrologIpEntrySize = NeedsLargeProlog ? 2 : 1;
+  unsigned EpilogDescBytes = 0;
+  for (const auto &EI : EpilogInfos) {
+    if (EI.Inherited) {
+      EpilogDescBytes += 3;
+    } else if (EI.NeedsLarge) {
+      // EPILOG_INFO_V3 (3) + EPILOG_INFO_LARGE_EX_V3 (4) + IP offsets (N*2)
+      EpilogDescBytes += 7 + EI.NumberOfOps * 2;
+    } else {
+      // EPILOG_INFO_V3 (3) + EPILOG_INFO_EX_V3 (3) + IP offsets (N*1)
+      EpilogDescBytes += 6 + EI.NumberOfOps;
+    }
+  }
+
+  unsigned PrologIpBytes = PrologOpCount * PrologIpEntrySize;
+  unsigned WODPoolBytes = WODPool.size();
+  // When UNW_FLAG_LARGE is set, the SizeOfPrologHighByte sits at the start
+  // of the payload (immediately after the 4-byte fixed header) and is
+  // counted in PayloadWords. See decodeUnwindInfoV3 / the V3 spec:
+  //   handler_offset = ALIGN_UP(sizeof(UNWIND_INFO_V3) + PayloadWords * 2, 4)
+  unsigned LargeHeaderBytes = NeedsLargeProlog ? 1 : 0;
+  unsigned TotalPayloadBytes =
----------------
mikolaj-pirog wrote:

Shouldn't the large prolog byte be included in size calculations per spec?

https://github.com/llvm/llvm-project/pull/200249
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to