[llvm] [MC] [Win64EH] Produce packed unwind for the special case of X19+LR (PR #169697)

Martin Storsjö via llvm-commits llvm-commits at lists.llvm.org
Thu Nov 27 02:07:21 PST 2025


https://github.com/mstorsjo updated https://github.com/llvm/llvm-project/pull/169697

>From de454d027959695f82995650e7631ecf20479a28 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Storsj=C3=B6?= <martin at martin.st>
Date: Wed, 26 Nov 2025 18:08:58 +0200
Subject: [PATCH 1/2] [llvm-readobj] [ARMWinEH] Fix the interpretation of
 packed unwind CR=01 RegI=1

Even though the table for how to expand packed unwind info at [1]
doesn't explicitly say this, this case is mentioned at [2] under
the case "Only x19 saved":

    sub    sp,sp,#16                // reg save area allocation*
    stp    x19,lr,[sp]              // save x19, lr
    sub    sp,sp,#(framesz-16)      // allocate the remaining local area

This was discussed and clarified at [3].

[1] https://learn.microsoft.com/en-us/cpp/build/arm64-exception-handling?view=msvc-170#packed-unwind-data
[2] https://learn.microsoft.com/en-us/cpp/build/arm64-exception-handling?view=msvc-170#arm64-stack-frame-layout
[3] https://github.com/llvm/llvm-project/issues/169588#issuecomment-3581688753
---
 .../tools/llvm-readobj/COFF/arm64-packed-unwind.s    |  3 ++-
 llvm/tools/llvm-readobj/ARMWinEHPrinter.cpp          | 12 ++++++++----
 2 files changed, 10 insertions(+), 5 deletions(-)

diff --git a/llvm/test/tools/llvm-readobj/COFF/arm64-packed-unwind.s b/llvm/test/tools/llvm-readobj/COFF/arm64-packed-unwind.s
index d9953ccc3f3d8..72e79b77a01ad 100644
--- a/llvm/test/tools/llvm-readobj/COFF/arm64-packed-unwind.s
+++ b/llvm/test/tools/llvm-readobj/COFF/arm64-packed-unwind.s
@@ -139,7 +139,8 @@
 // CHECK-NEXT:     FrameSize: 32
 // CHECK-NEXT:     Prologue [
 // CHECK-NEXT:       sub sp, sp, #16
-// CHECK-NEXT:       INVALID!
+// CHECK-NEXT:       stp x19, lr, [sp]
+// CHECK-NEXT:       sub sp, sp, #16
 // CHECK-NEXT:       end
 // CHECK-NEXT:     ]
 // CHECK-NEXT:   }
diff --git a/llvm/tools/llvm-readobj/ARMWinEHPrinter.cpp b/llvm/tools/llvm-readobj/ARMWinEHPrinter.cpp
index c6e409c63ef3a..eedf6732ad3f4 100644
--- a/llvm/tools/llvm-readobj/ARMWinEHPrinter.cpp
+++ b/llvm/tools/llvm-readobj/ARMWinEHPrinter.cpp
@@ -1457,10 +1457,14 @@ bool Decoder::dumpPackedARM64Entry(const object::COFFObjectFile &COFF,
       // The last register, an odd register without a pair
       if (RF.CR() == 1) {
         if (I == 0) { // If this is the only register pair
-          // CR=1 combined with RegI=1 doesn't map to a documented case;
-          // it doesn't map to any regular unwind info opcode, and the
-          // actual unwinder doesn't support it.
-          SW.startLine() << "INVALID!\n";
+          // CR=1 combined with RegI=1 maps to a special case; there's
+          // no unwind info opcode that saves a GPR together with LR
+          // with writeback to sp (no save_lrpair_x).
+          // Instead, this case expands to two instructions; a preceding
+          // (in prologue execution order) "sub sp, sp, #16", followed
+          // by a regular "stp x19, lr, [sp]" (save_lrpair).
+          SW.startLine() << format("stp x%d, lr, [sp]\n", 19);
+          SW.startLine() << format("sub sp, sp, #%d\n", SavSZ);
         } else
           SW.startLine() << format("stp x%d, lr, [sp, #%d]\n", 19 + 2 * I,
                                    16 * I);

>From bd1b7ca9b29e18f9efedc0abd0ad5d0d1ab9f0a3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Storsj=C3=B6?= <martin at martin.st>
Date: Wed, 26 Nov 2025 19:41:44 +0200
Subject: [PATCH 2/2] [MC] [Win64EH] Produce packed unwind for the special case
 of X19+LR

---
 llvm/lib/MC/MCWin64EH.cpp                |  35 +++++++-
 llvm/test/MC/AArch64/seh-packed-unwind.s | 109 +++++++++++++++++++++++
 2 files changed, 143 insertions(+), 1 deletion(-)

diff --git a/llvm/lib/MC/MCWin64EH.cpp b/llvm/lib/MC/MCWin64EH.cpp
index 6d146f6cedd6e..ff5234962eeac 100644
--- a/llvm/lib/MC/MCWin64EH.cpp
+++ b/llvm/lib/MC/MCWin64EH.cpp
@@ -1051,7 +1051,9 @@ static bool tryARM64PackedUnwind(WinEH::FrameInfo *info, uint32_t FuncLength,
   // the order - that would work fine when unwinding from within
   // functions, but not be exactly right if unwinding happens within
   // prologs/epilogs.
-  for (const WinEH::Instruction &Inst : info->Instructions) {
+  for (auto It = info->Instructions.begin(), EndIt = info->Instructions.end();
+       It != EndIt; It++) {
+    const WinEH::Instruction &Inst = *It;
     switch (Inst.Operation) {
     case Win64EH::UOP_End:
       if (Location != Start)
@@ -1169,6 +1171,28 @@ static bool tryARM64PackedUnwind(WinEH::FrameInfo *info, uint32_t FuncLength,
           Location != FloatRegs && Location != InputArgs &&
           Location != StackAdjust)
         return false;
+      // Becuase there's no save_lrpair_x opcode, the case of CR=01,
+      // RegI=1 is handled as a special case with a pair of instructions; an
+      // alloc followed by a regular save_lrpair. So when encountering an
+      // alloc here, check if this is the start of such an instruction pair.
+      if (Location == Start2) { // Can't have this at Start3, after PACSignLR
+        auto NextIt = It + 1;
+        if (NextIt != EndIt) {
+          const WinEH::Instruction &NextInst = *NextIt;
+          if (NextInst.Operation == Win64EH::UOP_SaveLRPair &&
+              NextInst.Offset == 0 && NextInst.Register == 19) {
+            assert(Predecrement == 0);
+            assert(RegI == 0);
+            assert(!StandaloneLR);
+            Predecrement = Inst.Offset;
+            RegI = 1;
+            StandaloneLR = true;
+            Location = FloatRegs;
+            It++; // Consume both the Alloc and the SaveLRPair
+            continue;
+          }
+        }
+      }
       // Can have either a single decrement, or a pair of decrements with
       // 4080 and another decrement.
       if (StackOffset == 0)
@@ -1262,6 +1286,15 @@ static bool tryARM64PackedUnwind(WinEH::FrameInfo *info, uint32_t FuncLength,
   // To play it safe, don't produce packed unwind info with homed parameters.
   if (H)
     return false;
+  // Older versions of Windows (at least in 10.0.22000.2176) incorrectly
+  // unwind packed unwind info with CR=01, RegI=1, RegF>0, see
+  // https://github.com/llvm/llvm-project/issues/169588#issuecomment-3584907886.
+  // This issue only exists in older versions; current versions
+  // (10.0.26100.6899) do handle it correctly. As long as we can't be sure
+  // that we won't run on older versions, avoid producing the packed form
+  // here.
+  if (StandaloneLR && RegI == 1 && RegF > 0)
+    return false;
   int IntSZ = 8 * RegI;
   if (StandaloneLR)
     IntSZ += 8;
diff --git a/llvm/test/MC/AArch64/seh-packed-unwind.s b/llvm/test/MC/AArch64/seh-packed-unwind.s
index 5b86ab4bc0d49..4c0f30b2536cd 100644
--- a/llvm/test/MC/AArch64/seh-packed-unwind.s
+++ b/llvm/test/MC/AArch64/seh-packed-unwind.s
@@ -295,6 +295,26 @@
 // CHECK-NEXT:       end
 // CHECK-NEXT:     ]
 // CHECK-NEXT:   }
+// CHECK-NEXT:   RuntimeFunction {
+// CHECK-NEXT:     Function: func19
+// CHECK-NEXT:     Fragment: No
+// CHECK-NEXT:     FunctionLength: 32
+// CHECK-NEXT:     RegF: 0
+// CHECK-NEXT:     RegI: 1
+// CHECK-NEXT:     HomedParameters: No
+// CHECK-NEXT:     CR: 1
+// CHECK-NEXT:     FrameSize: 80
+// CHECK-NEXT:     Prologue [
+// CHECK-NEXT:       sub sp, sp, #64
+// CHECK-NEXT:       stp x19, lr, [sp]
+// CHECK-NEXT:       sub sp, sp, #16
+// CHECK-NEXT:       end
+// CHECK-NEXT:     ]
+// CHECK-NEXT:   }
+// CHECK-NEXT:   RuntimeFunction {
+// CHECK-NEXT:     Function: notpacked_func20
+// CHECK-NEXT:     ExceptionRecord:
+// CHECK-NEXT:     ExceptionData {
 // CHECK:        RuntimeFunction {
 // CHECK-NEXT:     Function: nonpacked1
 // CHECK-NEXT:     ExceptionRecord:
@@ -374,6 +394,11 @@
 // CHECK-NEXT:     Function: nonpacked16
 // CHECK-NEXT:     ExceptionRecord:
 // CHECK-NEXT:     ExceptionData {
+// CHECK:            EpiloguePacked: Yes
+// CHECK:        RuntimeFunction {
+// CHECK-NEXT:     Function: nonpacked17
+// CHECK-NEXT:     ExceptionRecord:
+// CHECK-NEXT:     ExceptionData {
 // CHECK:            EpiloguePacked: Yes
 
 
@@ -809,6 +834,59 @@ func18:
     ret
     .seh_endproc
 
+func19:
+    .seh_proc func19
+    sub sp, sp, #16
+    .seh_stackalloc 16
+    stp x19, lr, [sp]
+    .seh_save_lrpair x19, 0
+    sub sp,  sp,  #64
+    .seh_stackalloc 64
+    .seh_endprologue
+    nop
+    .seh_startepilogue
+    add sp, sp, #64
+    .seh_stackalloc 64
+    ldp x19, lr, [sp]
+    .seh_save_lrpair x19, 0
+    add sp,  sp,  #16
+    .seh_stackalloc 16
+    .seh_endepilogue
+    ret
+    .seh_endproc
+
+notpacked_func20:
+    // This function is expressible with packed unwind info, but older
+    // versions of Windows unwind cases with CR=01, RegI=1, RegF>0
+    // incorrectly; therefore, we choose not to pack this case.
+    .seh_proc notpacked_func20
+    sub sp, sp, #48
+    .seh_stackalloc 48
+    stp x19, lr, [sp]
+    .seh_save_lrpair x19, 0
+    stp d8,  d9,  [sp, #16]
+    .seh_save_fregp d8, 16
+    str d10,      [sp, #32]
+    .seh_save_freg d10, 32
+    sub sp,  sp,  #64
+    .seh_stackalloc 64
+    .seh_endprologue
+    nop
+    .seh_startepilogue
+    add sp, sp, #64
+    .seh_stackalloc 64
+    ldr d10,      [sp, #32]
+    .seh_save_freg d10, 32
+    ldp d8,  d9,  [sp, #16]
+    .seh_save_fregp d8, 16
+    ldp x19, lr, [sp]
+    .seh_save_lrpair x19, 0
+    add sp,  sp,  #48
+    .seh_stackalloc 48
+    .seh_endepilogue
+    ret
+    .seh_endproc
+
 nonpacked1:
     .seh_proc nonpacked1
     // Can't be packed; can't save integer registers after float registers.
@@ -1157,3 +1235,34 @@ nonpacked16:
     .seh_endepilogue
     br      x9
     .seh_endproc
+
+nonpacked17:
+    .seh_proc nonpacked17
+    // Can't be packed; more predecrement for SavSZ than used for
+    // corresponding RegI/RegF/LR saves
+    sub sp, sp, #64
+    .seh_stackalloc 64
+    stp x19, lr, [sp]
+    .seh_save_lrpair x19, 0
+    stp d8,  d9,  [sp, #16]
+    .seh_save_fregp d8, 16
+    str d10,      [sp, #32]
+    .seh_save_freg d10, 32
+    sub sp,  sp,  #64
+    .seh_stackalloc 64
+    .seh_endprologue
+    nop
+    .seh_startepilogue
+    add sp, sp, #64
+    .seh_stackalloc 64
+    ldr d10,      [sp, #32]
+    .seh_save_freg d10, 32
+    ldp d8,  d9,  [sp, #16]
+    .seh_save_fregp d8, 16
+    ldp x19, lr, [sp]
+    .seh_save_lrpair x19, 0
+    add sp,  sp,  #64
+    .seh_stackalloc 64
+    .seh_endepilogue
+    ret
+    .seh_endproc



More information about the llvm-commits mailing list