[llvm] [SystemZ][z/OS] Allocate a stack frame when callee-saved registers are saved (PR #226681)

MMS IT GmbH via llvm-commits llvm-commits at lists.llvm.org
Sat Sep 26 04:47:47 PDT 2026


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

On z/OS (XPLINK64), a function without calls and without stack objects that uses callee-saved registers got a stack size of 0. No stack frame was allocated, so `STMG` stored the registers relative to the unchanged stack pointer, i.e. into the register save area of the caller's DSA, overwriting the caller's saved return address. Only skip the frame when there is nothing to save.

Two existing tests (`zos-ret-addr.ll`, `zos-frameaddr.ll`) encoded this store into the caller's frame and are updated; the values returned by `llvm.returnaddress`/`llvm.frameaddress` are unchanged (the old stack pointer is still read from offset 2048 of the current frame). New test `zos-frame-callee-saved.ll`.

Found with GMP 6.3.0 on z/OS 3.1 (`mpn_mul_1`): code after the call ran twice, then S0C4/S0C6. With this change GMP works.

Tests: `llvm-lit test/CodeGen/SystemZ test/MC/SystemZ test/MC/GOFF` passes (1304 passed, 19 unsupported).

Fixes #226657.

Assisted-by: Claude Code (Anthropic)

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


>From d70d3891c350be5bd3f2373dec7c7df910b94cff Mon Sep 17 00:00:00 2001
From: mms-it-ch <info at mms-it.ch>
Date: Sat, 26 Sep 2026 13:38:35 +0200
Subject: [PATCH] [SystemZ][z/OS] Allocate a stack frame when callee-saved
 registers are saved

A function without calls and stack objects which uses callee-saved registers
got a stack size of 0, so no stack frame was allocated and STMG stored the
registers relative to the unchanged stack pointer, i.e. into the register save
area of the caller DSA. This overwrote the saved return address of the caller.

Fixes #226657

Assisted-by: Claude Code (Anthropic)
---
 .../Target/SystemZ/SystemZFrameLowering.cpp   |  6 +++-
 .../CodeGen/SystemZ/zos-frame-callee-saved.ll | 28 +++++++++++++++++++
 llvm/test/CodeGen/SystemZ/zos-frameaddr.ll    | 12 ++++++--
 llvm/test/CodeGen/SystemZ/zos-ret-addr.ll     | 12 ++++++--
 4 files changed, 53 insertions(+), 5 deletions(-)
 create mode 100644 llvm/test/CodeGen/SystemZ/zos-frame-callee-saved.ll

diff --git a/llvm/lib/Target/SystemZ/SystemZFrameLowering.cpp b/llvm/lib/Target/SystemZ/SystemZFrameLowering.cpp
index 145db33a41c743..0762465a61431c 100644
--- a/llvm/lib/Target/SystemZ/SystemZFrameLowering.cpp
+++ b/llvm/lib/Target/SystemZ/SystemZFrameLowering.cpp
@@ -1557,7 +1557,11 @@ void SystemZXPLINKFrameLowering::determineFrameLayout(
       static_cast<SystemZXPLINK64Registers *>(Subtarget.getSpecialRegisters());
 
   uint64_t StackSize = MFFrame.getStackSize();
-  if (StackSize == 0)
+  // A function which saves callee-saved registers needs a register save area of
+  // its own, even if it has no other stack objects. Otherwise the registers are
+  // stored relative to the unchanged stack pointer, i.e. into the save area of
+  // the caller's DSA.
+  if (StackSize == 0 && MFFrame.getCalleeSavedInfo().empty())
     return;
 
   // Add the size of the register save area and the reserved area to the size.
diff --git a/llvm/test/CodeGen/SystemZ/zos-frame-callee-saved.ll b/llvm/test/CodeGen/SystemZ/zos-frame-callee-saved.ll
new file mode 100644
index 00000000000000..90c9dea227e8ca
--- /dev/null
+++ b/llvm/test/CodeGen/SystemZ/zos-frame-callee-saved.ll
@@ -0,0 +1,28 @@
+; A function without calls and without stack objects that uses callee-saved
+; registers must get a stack frame of its own. Otherwise the registers are
+; stored relative to the unchanged stack pointer, i.e. into the register save
+; area of the caller's DSA, overwriting the caller's saved return address.
+;
+; RUN: llc < %s -mtriple=s390x-ibm-zos | FileCheck %s
+
+define void @leaf() {
+; CHECK-LABEL: leaf DS 0H
+; CHECK:         stmg 6,9,1936(4)
+; CHECK-NEXT:    L#stack_update0 DS 0H
+; CHECK-NEXT:    aghi 4,-128
+; CHECK:         lmg 7,9,2072(4)
+; CHECK-NEXT:    aghi 4,128
+; CHECK-NEXT:    b 2(7)
+  call void asm sideeffect "", "~{r8},~{r9}"()
+  ret void
+}
+
+; A true leaf (no callee-saved registers) still gets no stack frame.
+define i64 @trueleaf(i64 %a) {
+; CHECK-LABEL: trueleaf DS 0H
+; CHECK-NOT:     stmg
+; CHECK-NOT:     aghi 4,
+; CHECK:         b 2(7)
+  %r = add i64 %a, 1
+  ret i64 %r
+}
diff --git a/llvm/test/CodeGen/SystemZ/zos-frameaddr.ll b/llvm/test/CodeGen/SystemZ/zos-frameaddr.ll
index 66177d56eb6d52..b25f710657058c 100644
--- a/llvm/test/CodeGen/SystemZ/zos-frameaddr.ll
+++ b/llvm/test/CodeGen/SystemZ/zos-frameaddr.ll
@@ -34,7 +34,11 @@ entry:
 ; Check the caller's frame address.
 define ptr @fpcaller() nounwind "backchain" {
 ; CHECK-LABEL: fpcaller DS 0H
-; CHECK:         stmg 4,7,2048(4)
+; CHECK:         stmg 4,7,1920(4)
+; CHECK-NEXT:    L#stack_update1 DS 0H
+; CHECK-NEXT:    aghi 4,-128
+; CHECK-NEXT:    *FENCE
+; CHECK-NEXT:    L#end_of_prologue1 DS 0H
 ; CHECK-NEXT:    lg 3,2048(4)
 ; CHECK-NEXT:    lmg 4,7,2048(4)
 ; CHECK-NEXT:    b 2(7)
@@ -46,7 +50,11 @@ entry:
 ; Check the caller's frame address.
 define ptr @fpcallercaller() nounwind "backchain" {
 ; CHECK-LABEL: fpcallercaller DS 0H
-; CHECK:         stmg 4,7,2048(4)
+; CHECK:         stmg 4,7,1920(4)
+; CHECK-NEXT:    L#stack_update2 DS 0H
+; CHECK-NEXT:    aghi 4,-128
+; CHECK-NEXT:    *FENCE
+; CHECK-NEXT:    L#end_of_prologue2 DS 0H
 ; CHECK-NEXT:    lg 1,2048(4)
 ; CHECK-NEXT:    lg 3,0(1)
 ; CHECK-NEXT:    lmg 4,7,2048(4)
diff --git a/llvm/test/CodeGen/SystemZ/zos-ret-addr.ll b/llvm/test/CodeGen/SystemZ/zos-ret-addr.ll
index ce59020dda4b2c..aa7e99fcfa57c8 100644
--- a/llvm/test/CodeGen/SystemZ/zos-ret-addr.ll
+++ b/llvm/test/CodeGen/SystemZ/zos-ret-addr.ll
@@ -14,7 +14,11 @@ entry:
 ; Check the caller's return address.
 define ptr @rtcaller() nounwind "backchain" {
 ; CHECK-LABEL: rtcaller DS 0H
-; CHECK:         stmg 4,7,2048(4)
+; CHECK:         stmg 4,7,1920(4)
+; CHECK-NEXT:    L#stack_update0 DS 0H
+; CHECK-NEXT:    aghi 4,-128
+; CHECK-NEXT:    *FENCE
+; CHECK-NEXT:    L#end_of_prologue0 DS 0H
 ; CHECK-NEXT:    lg 1,2048(4)
 ; CHECK-NEXT:    lg 3,24(1)
 ; CHECK-NEXT:    lmg 4,7,2048(4)
@@ -27,7 +31,11 @@ entry:
 ; Check the caller's caller's return address.
 define ptr @rtcallercaller() nounwind "backchain" {
 ; CHECK-LABEL: rtcallercaller DS 0H
-; CHECK:         stmg 4,7,2048(4)
+; CHECK:         stmg 4,7,1920(4)
+; CHECK-NEXT:    L#stack_update1 DS 0H
+; CHECK-NEXT:    aghi 4,-128
+; CHECK-NEXT:    *FENCE
+; CHECK-NEXT:    L#end_of_prologue1 DS 0H
 ; CHECK-NEXT:    lg 1,2048(4)
 ; CHECK-NEXT:    lg 1,0(1)
 ; CHECK-NEXT:    lg 3,24(1)



More information about the llvm-commits mailing list