[llvm] [SystemZ][z/OS] Restore the frame header on stack restore (PR #228376)

MMS IT GmbH via llvm-commits llvm-commits at lists.llvm.org
Fri Oct 2 03:06:23 PDT 2026


https://github.com/mms-it-ch created https://github.com/llvm/llvm-project/pull/228376

`@@ALCAXP`, which `lowerDYNAMIC_STACKALLOC_XPLINK` calls for a dynamic alloca, moves the frame header (register save area at SP + 2048) down with the stack pointer. Calls made while the stack pointer is lowered place their arguments over the old header. `lowerSTACKRESTORE` only reset the stack pointer, so the epilogue and the LE stack walk then used the clobbered old header; the program ended with abend U4083 RSN 0F.

This patch copies the current header (`getCallFrameSize()` bytes at `getStackPointerBias()`) to the restored stack pointer before R4 is set. The restored stack pointer is never below the current one, so the copy goes from the highest word down and is safe if the areas overlap. Only XPLINK64 is affected; ELF code is unchanged.

Found with flang: character temporaries of run-time length are allocated between `llvm.stacksave` and `llvm.stackrestore`. With the patch, the flang test program and a second frontend that keeps temporaries of run-time length on the stack run correctly on z/OS (previously U4083 RSN 0F or a return to a wrong address).

Test: new `llvm/test/CodeGen/SystemZ/zos-stackrestore.ll`. `llvm-lit llvm/test/CodeGen/SystemZ llvm/test/MC/SystemZ llvm/test/MC/GOFF` on this branch (main 128be64f5b): 1309 passed, 19 unsupported, 0 failed.

Fixes #228375.

Assisted-by: Claude Code (Anthropic)

🤖 Generated with [Claude Code](https://claude.com/claude-code)


>From 578f05d3ed62a488b3def8fa58905b800925db7c Mon Sep 17 00:00:00 2001
From: mms-it-ch <info at mms-it.ch>
Date: Fri, 2 Oct 2026 08:59:55 +0200
Subject: [PATCH] [SystemZ][z/OS] Restore the frame header on stack restore

@@ALCAXP, which lowerDYNAMIC_STACKALLOC_XPLINK calls for a dynamic alloca,
moves the frame header (register save area at SP + 2048) down with the
stack pointer. Calls made while the stack pointer is lowered place their
arguments over the old header. lowerSTACKRESTORE only reset the stack
pointer, so the epilogue and the LE stack walk then used the clobbered old
header; the program ended with abend U4083 RSN 0F.

Copy the current header to the restored stack pointer, from the highest
word down (the restored stack pointer is never below the current one).

Found with flang: character temporaries of run-time length are allocated
between llvm.stacksave and llvm.stackrestore.

Assisted-by: Claude Code (Anthropic)
---
 .../Target/SystemZ/SystemZISelLowering.cpp    | 26 +++++++++++++++
 llvm/test/CodeGen/SystemZ/zos-stackrestore.ll | 32 +++++++++++++++++++
 2 files changed, 58 insertions(+)
 create mode 100644 llvm/test/CodeGen/SystemZ/zos-stackrestore.ll

diff --git a/llvm/lib/Target/SystemZ/SystemZISelLowering.cpp b/llvm/lib/Target/SystemZ/SystemZISelLowering.cpp
index a6cc3e94b7e7d..f7721d831f2ff 100644
--- a/llvm/lib/Target/SystemZ/SystemZISelLowering.cpp
+++ b/llvm/lib/Target/SystemZ/SystemZISelLowering.cpp
@@ -5424,6 +5424,32 @@ SDValue SystemZTargetLowering::lowerSTACKRESTORE(SDValue Op,
                             MachinePointerInfo());
   }
 
+  // XPLINK: @@ALCAXP (lowerDYNAMIC_STACKALLOC_XPLINK) moves the frame header
+  // (register save area and reserved words at SP + bias) down with the stack
+  // pointer, and calls made while the stack pointer is lowered place their
+  // arguments over the old header. Copy the current header back to the
+  // restored stack pointer, otherwise the epilogue and the LE stack walk use
+  // a clobbered header (abend U4083). The restored stack pointer is never
+  // below the current one, so copying from the highest word down is safe even
+  // if the areas overlap.
+  if (Subtarget.isTargetXPLINK64()) {
+    auto &XRegs = Subtarget.getSpecialRegisters<SystemZXPLINK64Registers>();
+    uint64_t Bias = XRegs.getStackPointerBias();
+    SDValue OldSP = DAG.getCopyFromReg(
+        Chain, DL, Regs->getStackPointerRegister(), MVT::i64);
+    Chain = OldSP.getValue(1);
+    for (int I = XRegs.getCallFrameSize() / 8 - 1; I >= 0; --I) {
+      SDValue Offset = DAG.getConstant(Bias + 8 * I, DL, MVT::i64);
+      SDValue Word =
+          DAG.getLoad(MVT::i64, DL, Chain,
+                      DAG.getNode(ISD::ADD, DL, MVT::i64, OldSP, Offset),
+                      MachinePointerInfo());
+      Chain = DAG.getStore(Word.getValue(1), DL, Word,
+                           DAG.getNode(ISD::ADD, DL, MVT::i64, NewSP, Offset),
+                           MachinePointerInfo());
+    }
+  }
+
   Chain = DAG.getCopyToReg(Chain, DL, Regs->getStackPointerRegister(), NewSP);
 
   if (StoreBackchain)
diff --git a/llvm/test/CodeGen/SystemZ/zos-stackrestore.ll b/llvm/test/CodeGen/SystemZ/zos-stackrestore.ll
new file mode 100644
index 0000000000000..6603aa8c1368e
--- /dev/null
+++ b/llvm/test/CodeGen/SystemZ/zos-stackrestore.ll
@@ -0,0 +1,32 @@
+; RUN: llc < %s -mtriple=s390x-ibm-zos | FileCheck %s
+;
+; @@ALCAXP moves the frame header (save area at SP + 2048) down with the stack
+; pointer. A stack restore must copy the current header back to the restored
+; stack pointer before the frame is used again, from the highest word down.
+
+declare void @use(ptr)
+
+; CHECK-LABEL: f DS 0H
+; CHECK:      lgr 11,4
+; CHECK:      basr 7,6
+; CHECK:      la 1,2240(4)
+; CHECK:      basr 7,6
+; CHECK:      lg 0,2168(4)
+; CHECK-NEXT: stg 0,2168(11)
+; CHECK-NEXT: lg 0,2160(4)
+; CHECK-NEXT: stg 0,2160(11)
+; CHECK:      lg 0,2056(4)
+; CHECK-NEXT: stg 0,2056(11)
+; CHECK-NEXT: lg 0,2048(4)
+; CHECK-NEXT: stg 0,2048(11)
+; CHECK-NEXT: lgr 4,11
+; CHECK:      basr 7,6
+; CHECK:      lmg 4,11,2048(4)
+define void @f(i64 %n) {
+  %sp = call ptr @llvm.stacksave.p0()
+  %p = alloca i8, i64 %n, align 8
+  call void @use(ptr %p)
+  call void @llvm.stackrestore.p0(ptr %sp)
+  call void @use(ptr null)
+  ret void
+}



More information about the llvm-commits mailing list