[clang] [llvm] [SystemZ] Do not extend integer arguments in z/OS XPLINK64 ABI (PR #206833)
Zibi Sarbinowski via llvm-commits
llvm-commits at lists.llvm.org
Tue Aug 4 17:46:38 PDT 2026
https://github.com/zibi2 updated https://github.com/llvm/llvm-project/pull/206833
>From 3939ab730a89a6f47fac90499e00773b69e787e4 Mon Sep 17 00:00:00 2001
From: Zibi Sarbinowski <zibi at ca.ibm.com>
Date: Tue, 30 Jun 2026 16:25:39 -0400
Subject: [PATCH 1/7] [SystemZ] Fix signext/zeroext handling in XPLINK64
calling convention
For z/OS XPLINK64, the ABI specification mandates that scalar return
values are sign- or zero-extended to 64 bits, but makes no such
guarantee for arguments. Other compilers (e.g. xlc) do not extend
argument values, so clang was incorrectly emitting a sign-extension
instruction (lgfr) on the callee side for every incoming promotable
integer argument, causing interoperability failures.
Fix:
- In ZOSXPLinkABIInfo::classifyArgumentType(), use getDirect() for
sub-64-bit promotable integer types. The XPLINK64 ABI does not
mandate any widening of integer arguments (per the z/OS Language
Environment Vendor Interfaces spec); arguments are passed at their
natural width with no sign- or zero-extension guarantee. This omits
signext/zeroext from LLVM IR parameters, so the backend emits no
extension instruction for incoming arguments (lgr plain copy).
64-bit types (long, unsigned long) fall through to getDirect()
unchanged via the size guard.
- classifyReturnType() is unchanged: return values still use getExtend()
because the spec does mandate 64-bit extension there.
- TargetLibraryInfo::initExtensionsForTriple() updated: z/OS is split
from ELF SystemZ so that middle-end optimization passes synthesizing
library calls set ShouldExtI32Param=false and ShouldExtI32Return=true,
matching the XPLINK64 ABI.
Tests updated:
- clang/test/CodeGen/SystemZ/zos-abi.c: integer parameters no longer
carry signext/zeroext or noext; return types still carry signext/zeroext.
- clang/test/CodeGen/pragma-export.cpp: i32 parameters updated to match.
---
clang/lib/CodeGen/Targets/SystemZ.cpp | 14 ++++++----
clang/test/CodeGen/SystemZ/zos-abi.c | 26 +++++++++----------
clang/test/CodeGen/pragma-export.cpp | 8 +++---
.../include/llvm/Analysis/TargetLibraryInfo.h | 11 +++++---
llvm/lib/Target/SystemZ/SystemZCallingConv.td | 10 ++++---
llvm/test/CodeGen/SystemZ/zos-ppa1-argarea.ll | 2 +-
6 files changed, 42 insertions(+), 29 deletions(-)
diff --git a/clang/lib/CodeGen/Targets/SystemZ.cpp b/clang/lib/CodeGen/Targets/SystemZ.cpp
index a27fee633ead2..d2be96131c4db 100644
--- a/clang/lib/CodeGen/Targets/SystemZ.cpp
+++ b/clang/lib/CodeGen/Targets/SystemZ.cpp
@@ -618,13 +618,11 @@ bool ZOSXPLinkABIInfo::isPromotableIntegerTypeForABI(QualType Ty) const {
return true;
// In addition to the usual promotable integer types, we also need to
- // extend all 32-bit types, since the ABI requires promotion to 64 bits.
+ // extend 32-bit types, since the ABI requires promotion to 64 bits.
if (const BuiltinType *BT = Ty->getAs<BuiltinType>())
switch (BT->getKind()) {
case BuiltinType::Int:
case BuiltinType::UInt:
- case BuiltinType::ULong:
- case BuiltinType::Long:
return true;
default:
break;
@@ -825,9 +823,15 @@ ABIArgInfo ZOSXPLinkABIInfo::classifyArgumentType(QualType Ty, bool IsNamedArg,
return getNaturalAlignIndirect(Ty, getDataLayout().getAllocaAddrSpace(),
RAA == CGCXXABI::RAA_DirectInMemory);
- // Integers and enums are extended to full register width.
+ // The XPLINK64 ABI does not mandate any widening of integer arguments;
+ // arguments are passed at their natural width with no sign- or zero-extension
+ // guarantee. Only return values are required to be widened (per the z/OS
+ // Language Environment Vendor Interfaces spec). Other compilers (e.g. xlc)
+ // leave the upper bits of an argument register unspecified, so emitting
+ // signext/zeroext on parameters would produce incorrect code when
+ // interoperating with xlc.
if (isPromotableIntegerTypeForABI(Ty))
- return ABIArgInfo::getExtend(Ty, CGT.ConvertType(Ty));
+ return ABIArgInfo::getDirect(CGT.ConvertType(Ty));
// For non-C calling conventions, compound types passed by address copy.
if ((CallConv != llvm::CallingConv::C) && isCompoundType(Ty))
diff --git a/clang/test/CodeGen/SystemZ/zos-abi.c b/clang/test/CodeGen/SystemZ/zos-abi.c
index 0a31b47d8dc58..b18a2c6a05857 100644
--- a/clang/test/CodeGen/SystemZ/zos-abi.c
+++ b/clang/test/CodeGen/SystemZ/zos-abi.c
@@ -22,22 +22,22 @@
// Scalar types
char pass_char(char arg) { return arg; }
-// CHECK-LABEL: define signext i8 @pass_char(i8 signext %{{.*}})
+// CHECK-LABEL: define signext i8 @pass_char(i8 %{{.*}})
signed char pass_schar(signed char arg) { return arg; }
-// CHECK-LABEL: define signext i8 @pass_schar(i8 signext %{{.*}})
+// CHECK-LABEL: define signext i8 @pass_schar(i8 %{{.*}})
unsigned char pass_uchar(unsigned char arg) { return arg; }
-// CHECK-LABEL: define zeroext i8 @pass_uchar(i8 zeroext %{{.*}})
+// CHECK-LABEL: define zeroext i8 @pass_uchar(i8 %{{.*}})
short pass_short(short arg) { return arg; }
-// CHECK-LABEL: define signext i16 @pass_short(i16 signext %{{.*}})
+// CHECK-LABEL: define signext i16 @pass_short(i16 %{{.*}})
int pass_int(int arg) { return arg; }
-// CHECK-LABEL: define signext i32 @pass_int(i32 signext %{{.*}})
+// CHECK-LABEL: define signext i32 @pass_int(i32 %{{.*}})
long pass_long(long arg) { return arg; }
-// CHECK-LABEL: define signext i64 @pass_long(i64 signext %{{.*}})
+// CHECK-LABEL: define i64 @pass_long(i64 %{{.*}})
long long pass_longlong(long long arg) { return arg; }
// CHECK-LABEL: define i64 @pass_longlong(i64 %{{.*}})
@@ -53,7 +53,7 @@ long double pass_longdouble(long double arg) { return arg; }
enum Color { Red, Blue };
enum Color pass_enum(enum Color arg) { return arg; }
-// CHECK-LABEL: define zeroext i32 @pass_enum(i32 zeroext %{{.*}})
+// CHECK-LABEL: define zeroext i32 @pass_enum(i32 %{{.*}})
#ifdef TEST_VEC
vector unsigned int pass_vector(vector unsigned int arg) { return arg; };
@@ -472,19 +472,19 @@ struct Bad4 pass_Bad4(struct Bad4 arg) { return arg; }
// ==================================================================
union tu_char { char a; } __attribute__((transparent_union));
union tu_char pass_tu_char(union tu_char arg) { return arg; }
-// CHECK-LABEL: define{{.*}} i8 @pass_tu_char(i8 signext %{{.*}})
+// CHECK-LABEL: define{{.*}} i8 @pass_tu_char(i8 %{{.*}})
union tu_short { short a; } __attribute__((transparent_union));
union tu_short pass_tu_short(union tu_short arg) { return arg; }
-// CHECK-LABEL: define{{.*}} i16 @pass_tu_short(i16 signext %{{.*}})
+// CHECK-LABEL: define{{.*}} i16 @pass_tu_short(i16 %{{.*}})
union tu_int { int a; } __attribute__((transparent_union));
union tu_int pass_tu_int(union tu_int arg) { return arg; }
-// CHECK-LABEL: define{{.*}} i32 @pass_tu_int(i32 signext %{{.*}})
+// CHECK-LABEL: define{{.*}} i32 @pass_tu_int(i32 %{{.*}})
union tu_long { long a; } __attribute__((transparent_union));
union tu_long pass_tu_long(union tu_long arg) { return arg; }
-// CHECK-LABEL: define{{.*}} i64 @pass_tu_long(i64 signext %{{.*}})
+// CHECK-LABEL: define{{.*}} i64 @pass_tu_long(i64 %{{.*}})
union tu_ptr { void *a; } __attribute__((transparent_union));
union tu_ptr pass_tu_ptr(union tu_ptr arg) { return arg; }
@@ -596,7 +596,7 @@ int va_int_s(__builtin_zos_va_list l) { return __builtin_va_arg(l, int); }
// CHECK: ret i32 [[VAL]]
long va_long_e(__builtin_va_list l) { return __builtin_va_arg(l, long); }
-// CHECK-LABEL: define signext i64 @va_long_e(ptr %{{.*}})
+// CHECK-LABEL: define i64 @va_long_e(ptr %{{.*}})
// CHECK: [[L_ADDR:%[._a-z0-9]+]] = alloca ptr, align 8
// CHECK: store ptr %{{.*}}, ptr [[L_ADDR]], align 8
// CHECK: [[ARGP_CURR:%[._a-z0-9]+]] = load ptr, ptr [[L_ADDR]], align 8
@@ -606,7 +606,7 @@ long va_long_e(__builtin_va_list l) { return __builtin_va_arg(l, long); }
// CHECK: ret i64 [[VAL]]
long va_long_s(__builtin_zos_va_list l) { return __builtin_va_arg(l, long); }
-// CHECK-LABEL: define signext i64 @va_long_s(ptr %{{.*}})
+// CHECK-LABEL: define i64 @va_long_s(ptr %{{.*}})
// CHECK: [[L_ADDR:%[._a-z0-9]+]] = alloca ptr, align 8
// CHECK: store ptr %{{.*}}, ptr [[L_ADDR]], align 8
// CHECK: [[VALIST:%[._a-z0-9]+]] = load ptr, ptr [[L_ADDR]], align 8
diff --git a/clang/test/CodeGen/pragma-export.cpp b/clang/test/CodeGen/pragma-export.cpp
index 531afbd659234..f70e64ea7a4ca 100644
--- a/clang/test/CodeGen/pragma-export.cpp
+++ b/clang/test/CodeGen/pragma-export.cpp
@@ -53,10 +53,10 @@ void f10(int) {}
// CHECK: define hidden void @f0()
// CHECK: define void @f1()
// CHECK: define hidden void @_Z2f2dd(double noundef %0, double noundef %1)
-// CHECK: define void @f2(i32 noundef signext %0)
-// CHECK: define hidden void @_Z2f2ii(i32 noundef signext %0, i32 noundef signext %1)
+// CHECK: define void @f2(i32 noundef %0)
+// CHECK: define hidden void @_Z2f2ii(i32 noundef %0, i32 noundef %1)
// CHECK: define hidden void @f3(double noundef %0)
-// CHECK: define hidden void @_Z2f3id(i32 noundef signext %0, double noundef %1)
+// CHECK: define hidden void @_Z2f3id(i32 noundef %0, double noundef %1)
// CHECK: define hidden void @_Z2f3dd(double noundef %0, double noundef %1)
// CHECK: define hidden void @f2b()
// CHECK: define hidden void @_Z2t0v()
@@ -65,5 +65,5 @@ void f10(int) {}
// CHECK: define hidden void @_ZN2N02f5Ev()
// CHECK: define hidden void @_ZN2N03f5aEv()
// CHECK: define void @f10(double noundef %0)
-// CHECK: define hidden void @_Z3f10i(i32 noundef signext %0)
+// CHECK: define hidden void @_Z3f10i(i32 noundef %0)
diff --git a/llvm/include/llvm/Analysis/TargetLibraryInfo.h b/llvm/include/llvm/Analysis/TargetLibraryInfo.h
index 629b126db17c2..b97a7cf1a0e7c 100644
--- a/llvm/include/llvm/Analysis/TargetLibraryInfo.h
+++ b/llvm/include/llvm/Analysis/TargetLibraryInfo.h
@@ -453,13 +453,18 @@ class TargetLibraryInfo {
ShouldExtI32Param = ShouldExtI32Return = false;
ShouldSignExtI32Param = ShouldSignExtI32Return = false;
- // PowerPC64, Sparc64, SystemZ need signext/zeroext on i32 parameters and
- // returns corresponding to C-level ints and unsigned ints.
+ // PowerPC64, Sparc64, and SystemZ ELF need signext/zeroext on i32
+ // parameters and returns corresponding to C-level ints and unsigned ints.
if (T.isPPC64() || T.getArch() == Triple::sparcv9 ||
- T.getArch() == Triple::systemz) {
+ (T.getArch() == Triple::systemz && !T.isOSzOS())) {
ShouldExtI32Param = true;
ShouldExtI32Return = true;
}
+ // z/OS XPLINK64 only extends return values; parameters are not extended
+ // per the XPLINK ABI spec (other compilers do not extend arguments).
+ if (T.getArch() == Triple::systemz && T.isOSzOS()) {
+ ShouldExtI32Return = true;
+ }
// LoongArch, Mips, and riscv64, on the other hand, need signext on i32
// parameters corresponding to both signed and unsigned ints.
if (T.isLoongArch() || T.isMIPS() || T.isRISCV64()) {
diff --git a/llvm/lib/Target/SystemZ/SystemZCallingConv.td b/llvm/lib/Target/SystemZ/SystemZCallingConv.td
index 69202e3fcbc57..9ea09d12ea34a 100644
--- a/llvm/lib/Target/SystemZ/SystemZCallingConv.td
+++ b/llvm/lib/Target/SystemZ/SystemZCallingConv.td
@@ -213,9 +213,13 @@ def RetCC_SystemZ_XPLINK64 : CallingConv<[
// examples.
def CC_SystemZ_XPLINK64 : CallingConv<[
- // XPLINK64 ABI compliant code widens integral types smaller than i64
- // to i64 before placing the parameters either on the stack or in registers.
- CCIfType<[i32], CCIfExtend<CCPromoteToType<i64>>>,
+ // Callers must pass i32 arguments in full 64-bit registers. The XPLINK64
+ // ABI does not mandate sign- or zero-extension in the upper 32 bits (that
+ // requirement applies only to return values), but the caller is still
+ // responsible for placing the value in a 64-bit register before the call.
+ // Other compilers (e.g. xlc) behave this way unconditionally, so we promote
+ // i32 to i64 here regardless of any signext/zeroext attribute.
+ CCIfType<[i32], CCPromoteToType<i64>>,
// Promote f32 to f64 and bitcast to i64, if it needs to be passed in GPRs.
// Although we assign the f32 vararg to be bitcast, it will first be promoted
// to an f64 within convertValVTToLocVT().
diff --git a/llvm/test/CodeGen/SystemZ/zos-ppa1-argarea.ll b/llvm/test/CodeGen/SystemZ/zos-ppa1-argarea.ll
index 610c851798614..acbd624fd10ff 100644
--- a/llvm/test/CodeGen/SystemZ/zos-ppa1-argarea.ll
+++ b/llvm/test/CodeGen/SystemZ/zos-ppa1-argarea.ll
@@ -19,7 +19,7 @@ define void @fLargeOutArgArea() {
; CHECK-LABEL: L#EPM_fLargeOutArgArea_0 DS 0H
; CHECK: * Bit 1: 0 = Non-leaf function
; CHECK: * Bit 2: 0 = Does not use alloca
-; CHECK: DC XL4'00000220'
+; CHECK: DC XL4'00000260'
; CHECK: fLargeOutArgArea DS 0H
%1 = load [33 x i32], ptr @GlobLargeS, align 4
call void @fLargeParm([33 x i32] inreg %1)
>From 0b18fe4d40f89de6f099d175c6d76c5e1d8d8dc0 Mon Sep 17 00:00:00 2001
From: Zibi Sarbinowski <zibi at ca.ibm.com>
Date: Wed, 22 Jul 2026 11:17:35 -0400
Subject: [PATCH 2/7] [SystemZ][z/OS] Fix crashes in null and HLASM streamers
Two crash fixes exposed when WYVERN_DEFAULT_TARGET_TRIPLE=s390x-ibm-zos
is set globally (forcing all compilations to target z/OS):
1. createNullTargetStreamer (SystemZMCTargetDesc.cpp)
The null streamer path is taken for -emit-codegen-only (e.g. when
clang diagnoses __attribute__((error("..."))) at the backend).
Commit c4b3d1d51da2 added two PPA1 fields that call
getTargetStreamer()->createWordDiffExpr(), but the base class
SystemZTargetStreamer::createWordDiffExpr() returns nullptr.
Passing nullptr to emitValue() causes a SIGSEGV.
Fix: mirror what createObjectTargetStreamer already does -- return
a SystemZTargetGOFFStreamer for z/OS targets so that
createWordDiffExpr() produces a real MCExpr instead of null.
Fixes: clang/test/Frontend/backend-attribute-error-warning-optimize.c
2. SystemZHLASMAsmStreamer::finishImpl() (SystemZHLASMAsmStreamer.cpp)
When iterating undefined registered symbols to emit EXTRN/WXTRN
directives, the code assumed that any symbol with CodeData ==
ESD_EXE_DATA has a non-null ADA. However, common/BSS globals that
go through AsmPrinter::emitGlobalVariable() receive
MCSA_ELF_TypeObject (setting CodeData = ESD_EXE_DATA) but then take
the emitCommonSymbol() path which bypasses SelectSectionForGlobal,
so their ADA is never set. The unconditional getADA()->getParent()
dereference crashes.
Fix: guard the ESD_EXE_DATA branch with && Sym.getADA() so that
symbols with no ADA fall through to the plain EXTRN output.
Fixes: llvm/test/CodeGen/Generic/2014-02-05-OpaqueConstants.ll
---
.../lib/Target/SystemZ/MCTargetDesc/SystemZHLASMAsmStreamer.cpp | 2 +-
llvm/lib/Target/SystemZ/MCTargetDesc/SystemZMCTargetDesc.cpp | 2 ++
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/llvm/lib/Target/SystemZ/MCTargetDesc/SystemZHLASMAsmStreamer.cpp b/llvm/lib/Target/SystemZ/MCTargetDesc/SystemZHLASMAsmStreamer.cpp
index 5bf04747af94a..998c0da31fb91 100644
--- a/llvm/lib/Target/SystemZ/MCTargetDesc/SystemZHLASMAsmStreamer.cpp
+++ b/llvm/lib/Target/SystemZ/MCTargetDesc/SystemZHLASMAsmStreamer.cpp
@@ -469,7 +469,7 @@ void SystemZHLASMAsmStreamer::finishImpl() {
if (Symbol.isTemporary() || !Symbol.isRegistered() || Symbol.isDefined())
continue;
auto &Sym = static_cast<MCSymbolGOFF &>(const_cast<MCSymbol &>(Symbol));
- if (Sym.getCodeData() == GOFF::ESD_EXE_DATA) {
+ if (Sym.getCodeData() == GOFF::ESD_EXE_DATA && Sym.getADA()) {
OS << Sym.getADA()->getParent()->getExternalName() << " CATTR PART("
<< Sym.getName() << ")";
EmitEOL();
diff --git a/llvm/lib/Target/SystemZ/MCTargetDesc/SystemZMCTargetDesc.cpp b/llvm/lib/Target/SystemZ/MCTargetDesc/SystemZMCTargetDesc.cpp
index a0de6949f343e..205a780c8fbe6 100644
--- a/llvm/lib/Target/SystemZ/MCTargetDesc/SystemZMCTargetDesc.cpp
+++ b/llvm/lib/Target/SystemZ/MCTargetDesc/SystemZMCTargetDesc.cpp
@@ -227,6 +227,8 @@ createObjectTargetStreamer(MCStreamer &S, const MCSubtargetInfo &STI) {
static MCTargetStreamer *
createNullTargetStreamer(MCStreamer &S) {
+ if (S.getContext().getTargetTriple().isOSzOS())
+ return new SystemZTargetGOFFStreamer(S);
return new SystemZTargetStreamer(S);
}
>From e6ecf88e5298fc936c8be6d86f226139f10234bb Mon Sep 17 00:00:00 2001
From: Zibi Sarbinowski <zibi at ca.ibm.com>
Date: Mon, 27 Jul 2026 14:04:34 -0400
Subject: [PATCH 3/7] Revert "[SystemZ][z/OS] Fix crashes in null and HLASM
streamers"
This reverts commit 0b18fe4d40f89de6f099d175c6d76c5e1d8d8dc0.
---
.../MCTargetDesc/SystemZHLASMAsmStreamer.cpp | 2 +-
.../MCTargetDesc/SystemZMCTargetDesc.cpp | 2 --
llvm/lib/Target/SystemZ/SystemZCallingConv.td | 12 ++++------
llvm/test/CodeGen/SystemZ/call-zos-01.ll | 22 +++++++++----------
llvm/test/CodeGen/SystemZ/call-zos-vararg.ll | 4 ++--
llvm/test/CodeGen/SystemZ/mixed-ptr-sizes.ll | 2 +-
.../CodeGen/SystemZ/zos-ada-relocations.ll | 2 +-
llvm/test/CodeGen/SystemZ/zos-ppa1-argarea.ll | 2 +-
8 files changed, 21 insertions(+), 27 deletions(-)
diff --git a/llvm/lib/Target/SystemZ/MCTargetDesc/SystemZHLASMAsmStreamer.cpp b/llvm/lib/Target/SystemZ/MCTargetDesc/SystemZHLASMAsmStreamer.cpp
index 998c0da31fb91..5bf04747af94a 100644
--- a/llvm/lib/Target/SystemZ/MCTargetDesc/SystemZHLASMAsmStreamer.cpp
+++ b/llvm/lib/Target/SystemZ/MCTargetDesc/SystemZHLASMAsmStreamer.cpp
@@ -469,7 +469,7 @@ void SystemZHLASMAsmStreamer::finishImpl() {
if (Symbol.isTemporary() || !Symbol.isRegistered() || Symbol.isDefined())
continue;
auto &Sym = static_cast<MCSymbolGOFF &>(const_cast<MCSymbol &>(Symbol));
- if (Sym.getCodeData() == GOFF::ESD_EXE_DATA && Sym.getADA()) {
+ if (Sym.getCodeData() == GOFF::ESD_EXE_DATA) {
OS << Sym.getADA()->getParent()->getExternalName() << " CATTR PART("
<< Sym.getName() << ")";
EmitEOL();
diff --git a/llvm/lib/Target/SystemZ/MCTargetDesc/SystemZMCTargetDesc.cpp b/llvm/lib/Target/SystemZ/MCTargetDesc/SystemZMCTargetDesc.cpp
index 205a780c8fbe6..a0de6949f343e 100644
--- a/llvm/lib/Target/SystemZ/MCTargetDesc/SystemZMCTargetDesc.cpp
+++ b/llvm/lib/Target/SystemZ/MCTargetDesc/SystemZMCTargetDesc.cpp
@@ -227,8 +227,6 @@ createObjectTargetStreamer(MCStreamer &S, const MCSubtargetInfo &STI) {
static MCTargetStreamer *
createNullTargetStreamer(MCStreamer &S) {
- if (S.getContext().getTargetTriple().isOSzOS())
- return new SystemZTargetGOFFStreamer(S);
return new SystemZTargetStreamer(S);
}
diff --git a/llvm/lib/Target/SystemZ/SystemZCallingConv.td b/llvm/lib/Target/SystemZ/SystemZCallingConv.td
index 9ea09d12ea34a..c886040bf38d1 100644
--- a/llvm/lib/Target/SystemZ/SystemZCallingConv.td
+++ b/llvm/lib/Target/SystemZ/SystemZCallingConv.td
@@ -213,13 +213,6 @@ def RetCC_SystemZ_XPLINK64 : CallingConv<[
// examples.
def CC_SystemZ_XPLINK64 : CallingConv<[
- // Callers must pass i32 arguments in full 64-bit registers. The XPLINK64
- // ABI does not mandate sign- or zero-extension in the upper 32 bits (that
- // requirement applies only to return values), but the caller is still
- // responsible for placing the value in a 64-bit register before the call.
- // Other compilers (e.g. xlc) behave this way unconditionally, so we promote
- // i32 to i64 here regardless of any signext/zeroext attribute.
- CCIfType<[i32], CCPromoteToType<i64>>,
// Promote f32 to f64 and bitcast to i64, if it needs to be passed in GPRs.
// Although we assign the f32 vararg to be bitcast, it will first be promoted
// to an f64 within convertValVTToLocVT().
@@ -246,9 +239,12 @@ def CC_SystemZ_XPLINK64 : CallingConv<[
// If i128 is not legal, such values are already split into two i64 here,
// so we have to use a custom handler.
CCIfType<[i64], CCCustom<"CC_SystemZ_I128Indirect">>,
- // The first 3 integer arguments are passed in registers R1D-R3D.
+ // The first 3 integer arguments are passed in registers R1-R3.
+ // i32 uses the low-word sub-registers but occupies a full 8-byte slot,
+ // matching the XPLINK64 requirement that each parameter area word is 8 bytes.
// The rest will be passed in the user area. The address offset of the user
// area can be found in register R4D.
+ CCIfType<[i32], CCAssignToRegAndStack<[R1L, R2L, R3L], 8, 8>>,
CCIfType<[i64], CCAssignToRegAndStack<[R1D, R2D, R3D], 8, 8>>,
// The first 8 named vector arguments are passed in V24-V31. Sub-128 vectors
diff --git a/llvm/test/CodeGen/SystemZ/call-zos-01.ll b/llvm/test/CodeGen/SystemZ/call-zos-01.ll
index a6006035dcaa1..425cb0b897758 100644
--- a/llvm/test/CodeGen/SystemZ/call-zos-01.ll
+++ b/llvm/test/CodeGen/SystemZ/call-zos-01.ll
@@ -11,7 +11,7 @@ define i8 @call_char(){
; CHECK-NEXT: L#end_of_prologue{{[0-9]+}} DS 0H
; CHECK-NEXT: lg 6,8(5)
; CHECK-NEXT: lg 5,0(5)
-; CHECK-NEXT: lghi 1,8
+; CHECK-NEXT: lhi 1,8
; CHECK-NEXT: basr 7,6
; CHECK-NEXT: bcr 0,0
; CHECK-NEXT: lg 7,2072(4)
@@ -30,7 +30,7 @@ define i16 @call_short() {
; CHECK-NEXT: L#end_of_prologue{{[0-9]+}} DS 0H
; CHECK-NEXT: lg 6,24(5)
; CHECK-NEXT: lg 5,16(5)
-; CHECK-NEXT: lghi 1,16
+; CHECK-NEXT: lhi 1,16
; CHECK-NEXT: basr 7,6
; CHECK-NEXT: bcr 0,0
; CHECK-NEXT: lg 7,2072(4)
@@ -50,8 +50,8 @@ define i32 @call_int() {
; CHECK-NEXT: L#end_of_prologue{{[0-9]+}} DS 0H
; CHECK-NEXT: lg 6,40(5)
; CHECK-NEXT: lg 5,32(5)
-; CHECK-NEXT: lghi 1,32
-; CHECK-NEXT: lghi 2,33
+; CHECK-NEXT: lhi 1,32
+; CHECK-NEXT: lhi 2,33
; CHECK-NEXT: basr 7,6
; CHECK-NEXT: bcr 0,0
; CHECK-NEXT: lg 7,2072(4)
@@ -114,8 +114,8 @@ define i64 @call_integrals() {
; CHECK-NEXT: lg 6,88(5)
; CHECK-NEXT: lg 5,80(5)
; CHECK-NEXT: lghi 1,64
-; CHECK-NEXT: lghi 2,32
-; CHECK-NEXT: lghi 3,16
+; CHECK-NEXT: lhi 2,32
+; CHECK-NEXT: lhi 3,16
; CHECK-NEXT: mvghi 2200(4),128
; CHECK-NEXT: basr 7,6
; CHECK-NEXT: bcr 0,0
@@ -129,7 +129,7 @@ entry:
define signext i8 @pass_char(i8 signext %arg) {
; CHECK-LABEL: pass_char DS 0H
-; CHECK: lgr 3,1
+; CHECK: lgfr 3,1
; CHECK-NEXT: b 2(7)
entry:
ret i8 %arg
@@ -137,7 +137,7 @@ entry:
define signext i16 @pass_short(i16 signext %arg) {
; CHECK-LABEL: pass_short DS 0H
-; CHECK: lgr 3,1
+; CHECK: lgfr 3,1
; CHECK-NEXT: b 2(7)
entry:
ret i16 %arg
@@ -145,7 +145,7 @@ entry:
define signext i32 @pass_int(i32 signext %arg0, i32 signext %arg1) {
; CHECK-LABEL: pass_int DS 0H
-; CHECK: lgr 3,2
+; CHECK: lgfr 3,2
; CHECK-NEXT: b 2(7)
entry:
ret i32 %arg1
@@ -164,8 +164,8 @@ entry:
define signext i64 @pass_integrals0(i64 signext %arg0, i32 signext %arg1, i16 signext %arg2, i64 signext %arg3) {
; CHECK-LABEL: pass_integrals0 DS 0H
-; CHECK: ag 2,2200(4)
-; CHECK-NEXT: lgr 3,2
+; CHECK: lgfr 3,2
+; CHECK-NEXT: ag 3,2200(4)
; CHECK-NEXT: b 2(7)
entry:
%N = sext i32 %arg1 to i64
diff --git a/llvm/test/CodeGen/SystemZ/call-zos-vararg.ll b/llvm/test/CodeGen/SystemZ/call-zos-vararg.ll
index 3bcc583adec7f..a02c41d298a2c 100644
--- a/llvm/test/CodeGen/SystemZ/call-zos-vararg.ll
+++ b/llvm/test/CodeGen/SystemZ/call-zos-vararg.ll
@@ -287,10 +287,10 @@ define void @call_vec_double_vararg_straddle(<2 x double> %v) {
; CHECK-NEXT: aghi 4,-192
; CHECK-NEXT: *FENCE
; CHECK-NEXT: L#end_of_prologue{{[0-9]+}} DS 0H
-; CHECK-NEXT: lg 0,2392(4)
+; CHECK-NEXT: l 0,2396(4)
; CHECK-NEXT: lg 6,40(5)
; CHECK-NEXT: lg 5,32(5)
-; CHECK-NEXT: stg 0,2200(4)
+; CHECK-NEXT: st 0,2204(4)
; CHECK-NEXT: basr 7,6
; CHECK-NEXT: bcr 0,0
; CHECK-NEXT: lg 7,2072(4)
diff --git a/llvm/test/CodeGen/SystemZ/mixed-ptr-sizes.ll b/llvm/test/CodeGen/SystemZ/mixed-ptr-sizes.ll
index cae54638c3191..cec4490c0ac1b 100644
--- a/llvm/test/CodeGen/SystemZ/mixed-ptr-sizes.ll
+++ b/llvm/test/CodeGen/SystemZ/mixed-ptr-sizes.ll
@@ -307,7 +307,7 @@ entry:
; CHECK-NEXT: lg 5,16(5)
; CHECK-NEXT: stg 1,2216(4)
; CHECK-NEXT: stg 1,2208(4)
-; CHECK-NEXT: lghi 1,5
+; CHECK-NEXT: lhi 1,5
; CHECK-NEXT: stg 2,2200(4)
; CHECK-NEXT: lgr 3,2
; CHECK-NEXT: basr 7,6
diff --git a/llvm/test/CodeGen/SystemZ/zos-ada-relocations.ll b/llvm/test/CodeGen/SystemZ/zos-ada-relocations.ll
index a79e3b67ac3d1..c250c2114da29 100644
--- a/llvm/test/CodeGen/SystemZ/zos-ada-relocations.ll
+++ b/llvm/test/CodeGen/SystemZ/zos-ada-relocations.ll
@@ -30,7 +30,7 @@ declare void @Caller(ptr noundef)
; CHECK: aghi 4,-192
; CHECK: lg 1,24(5)
; CHECK: lg 2,32(5)
-; CHECK: lgf 1,0(1)
+; CHECK: l 1,0(1)
; CHECK: lg 6,48(5)
; CHECK: lg 5,40(5)
; CHECK: l 8,0(2)
diff --git a/llvm/test/CodeGen/SystemZ/zos-ppa1-argarea.ll b/llvm/test/CodeGen/SystemZ/zos-ppa1-argarea.ll
index acbd624fd10ff..610c851798614 100644
--- a/llvm/test/CodeGen/SystemZ/zos-ppa1-argarea.ll
+++ b/llvm/test/CodeGen/SystemZ/zos-ppa1-argarea.ll
@@ -19,7 +19,7 @@ define void @fLargeOutArgArea() {
; CHECK-LABEL: L#EPM_fLargeOutArgArea_0 DS 0H
; CHECK: * Bit 1: 0 = Non-leaf function
; CHECK: * Bit 2: 0 = Does not use alloca
-; CHECK: DC XL4'00000260'
+; CHECK: DC XL4'00000220'
; CHECK: fLargeOutArgArea DS 0H
%1 = load [33 x i32], ptr @GlobLargeS, align 4
call void @fLargeParm([33 x i32] inreg %1)
>From b17680d9a079fb5fff0c62f97928a34c1d3a7fe4 Mon Sep 17 00:00:00 2001
From: Zibi Sarbinowski <zibi at ca.ibm.com>
Date: Tue, 28 Jul 2026 16:36:06 -0400
Subject: [PATCH 4/7] [SystemZ] Fix XPLINK64 i32 arg passing: promote to i64 to
fill full GPR
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The rule CCAssignToRegAndStack<[R1L, R2L, R3L], 8, 8> writes only the
32-bit sub-register of each GPR for i32 arguments. Callees compiled by
other compilers (e.g. xlc / @@CLASSB) read the full 64-bit register,
so the upper 32 bits being undefined produces wrong results — concretely,
calling @@CLASSB via isinf() returns 1 instead of 0 for a finite float
because the upper 32 bits of R3 are garbage when only R3L was written.
Fix: replace CCAssignToRegAndStack<[R1L, R2L, R3L], 8, 8> with
CCPromoteToType<i64> for i32 arguments, placed after the i128/pointer/
vararg rules and before the i64 register-assignment rule. This promotes
i32 to i64 (LocInfo=AExt when no signext/zeroext flag is present), so
the caller emits ANY_EXTEND and writes the full 64-bit register R1D/R2D/R3D.
On the callee side, LocInfo=AExt causes a TRUNCATE back to i32, leaving
the callee's view of the value correct at its natural width.
The downstream CCAssignToRegAndStack<[R1D, R2D, R3D], 8, 8> then assigns
the promoted i64 to the full 64-bit registers, matching the 8-byte stack
slot required by XPLINK64.
Update all affected lit tests:
- call-zos-01.ll: lhi->lghi, lgfr->lgr, reorder ag/lgr in pass_integrals0
- call-zos-vararg.ll: l->lg, st->stg
- mixed-ptr-sizes.ll: lhi->lghi
- zos-ada-relocations.ll: l->lgf (sign-extending i32 load)
- zos-ppa1-argarea.ll: DSA size 0x220->0x260 (i32 stack slots now 8 bytes)
---
llvm/lib/Target/SystemZ/SystemZCallingConv.td | 12 +++++++---
llvm/test/CodeGen/SystemZ/call-zos-01.ll | 22 +++++++++----------
llvm/test/CodeGen/SystemZ/call-zos-vararg.ll | 4 ++--
llvm/test/CodeGen/SystemZ/mixed-ptr-sizes.ll | 2 +-
.../CodeGen/SystemZ/zos-ada-relocations.ll | 2 +-
llvm/test/CodeGen/SystemZ/zos-ppa1-argarea.ll | 2 +-
6 files changed, 25 insertions(+), 19 deletions(-)
diff --git a/llvm/lib/Target/SystemZ/SystemZCallingConv.td b/llvm/lib/Target/SystemZ/SystemZCallingConv.td
index c886040bf38d1..6518b098d55bf 100644
--- a/llvm/lib/Target/SystemZ/SystemZCallingConv.td
+++ b/llvm/lib/Target/SystemZ/SystemZCallingConv.td
@@ -240,11 +240,17 @@ def CC_SystemZ_XPLINK64 : CallingConv<[
// so we have to use a custom handler.
CCIfType<[i64], CCCustom<"CC_SystemZ_I128Indirect">>,
// The first 3 integer arguments are passed in registers R1-R3.
- // i32 uses the low-word sub-registers but occupies a full 8-byte slot,
- // matching the XPLINK64 requirement that each parameter area word is 8 bytes.
+ // i32 is promoted to i64 (AExt) unconditionally so that the full 64-bit
+ // register (R1D/R2D/R3D) is written by the caller. Callees compiled by
+ // other compilers (e.g. xlc / @@CLASSB) read the full GPR; writing only
+ // the 32-bit sub-register leaves the upper half undefined and produces
+ // wrong results (e.g. isinf returning 1 instead of 0).
+ // Note: CCPromoteToType sets LocInfo=AExt when no signext/zeroext flag is
+ // present, so formal-arg lowering truncates back to i32 via TRUNCATE,
+ // leaving the callee's view of the value correct at its natural width.
// The rest will be passed in the user area. The address offset of the user
// area can be found in register R4D.
- CCIfType<[i32], CCAssignToRegAndStack<[R1L, R2L, R3L], 8, 8>>,
+ CCIfType<[i32], CCPromoteToType<i64>>,
CCIfType<[i64], CCAssignToRegAndStack<[R1D, R2D, R3D], 8, 8>>,
// The first 8 named vector arguments are passed in V24-V31. Sub-128 vectors
diff --git a/llvm/test/CodeGen/SystemZ/call-zos-01.ll b/llvm/test/CodeGen/SystemZ/call-zos-01.ll
index 425cb0b897758..a6006035dcaa1 100644
--- a/llvm/test/CodeGen/SystemZ/call-zos-01.ll
+++ b/llvm/test/CodeGen/SystemZ/call-zos-01.ll
@@ -11,7 +11,7 @@ define i8 @call_char(){
; CHECK-NEXT: L#end_of_prologue{{[0-9]+}} DS 0H
; CHECK-NEXT: lg 6,8(5)
; CHECK-NEXT: lg 5,0(5)
-; CHECK-NEXT: lhi 1,8
+; CHECK-NEXT: lghi 1,8
; CHECK-NEXT: basr 7,6
; CHECK-NEXT: bcr 0,0
; CHECK-NEXT: lg 7,2072(4)
@@ -30,7 +30,7 @@ define i16 @call_short() {
; CHECK-NEXT: L#end_of_prologue{{[0-9]+}} DS 0H
; CHECK-NEXT: lg 6,24(5)
; CHECK-NEXT: lg 5,16(5)
-; CHECK-NEXT: lhi 1,16
+; CHECK-NEXT: lghi 1,16
; CHECK-NEXT: basr 7,6
; CHECK-NEXT: bcr 0,0
; CHECK-NEXT: lg 7,2072(4)
@@ -50,8 +50,8 @@ define i32 @call_int() {
; CHECK-NEXT: L#end_of_prologue{{[0-9]+}} DS 0H
; CHECK-NEXT: lg 6,40(5)
; CHECK-NEXT: lg 5,32(5)
-; CHECK-NEXT: lhi 1,32
-; CHECK-NEXT: lhi 2,33
+; CHECK-NEXT: lghi 1,32
+; CHECK-NEXT: lghi 2,33
; CHECK-NEXT: basr 7,6
; CHECK-NEXT: bcr 0,0
; CHECK-NEXT: lg 7,2072(4)
@@ -114,8 +114,8 @@ define i64 @call_integrals() {
; CHECK-NEXT: lg 6,88(5)
; CHECK-NEXT: lg 5,80(5)
; CHECK-NEXT: lghi 1,64
-; CHECK-NEXT: lhi 2,32
-; CHECK-NEXT: lhi 3,16
+; CHECK-NEXT: lghi 2,32
+; CHECK-NEXT: lghi 3,16
; CHECK-NEXT: mvghi 2200(4),128
; CHECK-NEXT: basr 7,6
; CHECK-NEXT: bcr 0,0
@@ -129,7 +129,7 @@ entry:
define signext i8 @pass_char(i8 signext %arg) {
; CHECK-LABEL: pass_char DS 0H
-; CHECK: lgfr 3,1
+; CHECK: lgr 3,1
; CHECK-NEXT: b 2(7)
entry:
ret i8 %arg
@@ -137,7 +137,7 @@ entry:
define signext i16 @pass_short(i16 signext %arg) {
; CHECK-LABEL: pass_short DS 0H
-; CHECK: lgfr 3,1
+; CHECK: lgr 3,1
; CHECK-NEXT: b 2(7)
entry:
ret i16 %arg
@@ -145,7 +145,7 @@ entry:
define signext i32 @pass_int(i32 signext %arg0, i32 signext %arg1) {
; CHECK-LABEL: pass_int DS 0H
-; CHECK: lgfr 3,2
+; CHECK: lgr 3,2
; CHECK-NEXT: b 2(7)
entry:
ret i32 %arg1
@@ -164,8 +164,8 @@ entry:
define signext i64 @pass_integrals0(i64 signext %arg0, i32 signext %arg1, i16 signext %arg2, i64 signext %arg3) {
; CHECK-LABEL: pass_integrals0 DS 0H
-; CHECK: lgfr 3,2
-; CHECK-NEXT: ag 3,2200(4)
+; CHECK: ag 2,2200(4)
+; CHECK-NEXT: lgr 3,2
; CHECK-NEXT: b 2(7)
entry:
%N = sext i32 %arg1 to i64
diff --git a/llvm/test/CodeGen/SystemZ/call-zos-vararg.ll b/llvm/test/CodeGen/SystemZ/call-zos-vararg.ll
index a02c41d298a2c..3bcc583adec7f 100644
--- a/llvm/test/CodeGen/SystemZ/call-zos-vararg.ll
+++ b/llvm/test/CodeGen/SystemZ/call-zos-vararg.ll
@@ -287,10 +287,10 @@ define void @call_vec_double_vararg_straddle(<2 x double> %v) {
; CHECK-NEXT: aghi 4,-192
; CHECK-NEXT: *FENCE
; CHECK-NEXT: L#end_of_prologue{{[0-9]+}} DS 0H
-; CHECK-NEXT: l 0,2396(4)
+; CHECK-NEXT: lg 0,2392(4)
; CHECK-NEXT: lg 6,40(5)
; CHECK-NEXT: lg 5,32(5)
-; CHECK-NEXT: st 0,2204(4)
+; CHECK-NEXT: stg 0,2200(4)
; CHECK-NEXT: basr 7,6
; CHECK-NEXT: bcr 0,0
; CHECK-NEXT: lg 7,2072(4)
diff --git a/llvm/test/CodeGen/SystemZ/mixed-ptr-sizes.ll b/llvm/test/CodeGen/SystemZ/mixed-ptr-sizes.ll
index cec4490c0ac1b..cae54638c3191 100644
--- a/llvm/test/CodeGen/SystemZ/mixed-ptr-sizes.ll
+++ b/llvm/test/CodeGen/SystemZ/mixed-ptr-sizes.ll
@@ -307,7 +307,7 @@ entry:
; CHECK-NEXT: lg 5,16(5)
; CHECK-NEXT: stg 1,2216(4)
; CHECK-NEXT: stg 1,2208(4)
-; CHECK-NEXT: lhi 1,5
+; CHECK-NEXT: lghi 1,5
; CHECK-NEXT: stg 2,2200(4)
; CHECK-NEXT: lgr 3,2
; CHECK-NEXT: basr 7,6
diff --git a/llvm/test/CodeGen/SystemZ/zos-ada-relocations.ll b/llvm/test/CodeGen/SystemZ/zos-ada-relocations.ll
index c250c2114da29..a79e3b67ac3d1 100644
--- a/llvm/test/CodeGen/SystemZ/zos-ada-relocations.ll
+++ b/llvm/test/CodeGen/SystemZ/zos-ada-relocations.ll
@@ -30,7 +30,7 @@ declare void @Caller(ptr noundef)
; CHECK: aghi 4,-192
; CHECK: lg 1,24(5)
; CHECK: lg 2,32(5)
-; CHECK: l 1,0(1)
+; CHECK: lgf 1,0(1)
; CHECK: lg 6,48(5)
; CHECK: lg 5,40(5)
; CHECK: l 8,0(2)
diff --git a/llvm/test/CodeGen/SystemZ/zos-ppa1-argarea.ll b/llvm/test/CodeGen/SystemZ/zos-ppa1-argarea.ll
index 610c851798614..acbd624fd10ff 100644
--- a/llvm/test/CodeGen/SystemZ/zos-ppa1-argarea.ll
+++ b/llvm/test/CodeGen/SystemZ/zos-ppa1-argarea.ll
@@ -19,7 +19,7 @@ define void @fLargeOutArgArea() {
; CHECK-LABEL: L#EPM_fLargeOutArgArea_0 DS 0H
; CHECK: * Bit 1: 0 = Non-leaf function
; CHECK: * Bit 2: 0 = Does not use alloca
-; CHECK: DC XL4'00000220'
+; CHECK: DC XL4'00000260'
; CHECK: fLargeOutArgArea DS 0H
%1 = load [33 x i32], ptr @GlobLargeS, align 4
call void @fLargeParm([33 x i32] inreg %1)
>From b012f91c040689c8160e9225b77b4037c7c0ad00 Mon Sep 17 00:00:00 2001
From: Zibi Sarbinowski <zibi at ca.ibm.com>
Date: Wed, 29 Jul 2026 14:03:38 -0400
Subject: [PATCH 5/7] [XPLINK64] Extend sub-64-bit integers at variadic call
sites
Named XPLINK64 arguments intentionally omit signext/zeroext to interoperate
with xlc, which leaves upper GPR bits unspecified. However, variadic arguments
are different: the callee has no prototype and reads the full 64-bit GPR, so
the upper bits must be clean.
Fix classifyArgumentType() to emit getExtend() instead of getDirect() for
promotable integer types when IsNamedArg is false (i.e. variadic arguments).
This fixes a miscompile visible in MultiSource/Benchmarks/Ptrdist/ks where
'int' values passed to printf with '%lu' printed garbage upper bits, e.g.
'Net 343597383682 cut.' instead of 'Net 2 cut.'.
---
clang/lib/CodeGen/Targets/SystemZ.cpp | 15 +++++++++++----
1 file changed, 11 insertions(+), 4 deletions(-)
diff --git a/clang/lib/CodeGen/Targets/SystemZ.cpp b/clang/lib/CodeGen/Targets/SystemZ.cpp
index d2be96131c4db..c43bc3a500e72 100644
--- a/clang/lib/CodeGen/Targets/SystemZ.cpp
+++ b/clang/lib/CodeGen/Targets/SystemZ.cpp
@@ -823,15 +823,22 @@ ABIArgInfo ZOSXPLinkABIInfo::classifyArgumentType(QualType Ty, bool IsNamedArg,
return getNaturalAlignIndirect(Ty, getDataLayout().getAllocaAddrSpace(),
RAA == CGCXXABI::RAA_DirectInMemory);
- // The XPLINK64 ABI does not mandate any widening of integer arguments;
+ // The XPLINK64 ABI does not mandate any widening of named integer arguments;
// arguments are passed at their natural width with no sign- or zero-extension
// guarantee. Only return values are required to be widened (per the z/OS
// Language Environment Vendor Interfaces spec). Other compilers (e.g. xlc)
// leave the upper bits of an argument register unspecified, so emitting
- // signext/zeroext on parameters would produce incorrect code when
+ // signext/zeroext on named parameters would produce incorrect code when
// interoperating with xlc.
- if (isPromotableIntegerTypeForABI(Ty))
- return ABIArgInfo::getDirect(CGT.ConvertType(Ty));
+ //
+ // However, variadic arguments are different: the callee has no prototype for
+ // them and reads a full 64-bit GPR, so the upper bits must be clean.
+ // Extend variadic integer arguments to 64 bits.
+ if (isPromotableIntegerTypeForABI(Ty)) {
+ if (IsNamedArg)
+ return ABIArgInfo::getDirect(CGT.ConvertType(Ty));
+ return ABIArgInfo::getExtend(Ty, CGT.ConvertType(Ty));
+ }
// For non-C calling conventions, compound types passed by address copy.
if ((CallConv != llvm::CallingConv::C) && isCompoundType(Ty))
>From 3762fc467a9192d317a97b5d69a4702cd30753a3 Mon Sep 17 00:00:00 2001
From: Zibi Sarbinowski <zibi at ca.ibm.com>
Date: Thu, 30 Jul 2026 16:14:33 -0400
Subject: [PATCH 6/7] [SystemZ] Fix XPLINK64 i32 call-arg AExt producing LGF at
-O0
When convertValVTToLocVT emits ISD::ANY_EXTEND for an AExt CCValAssign
and the value was spilled to a 32-bit stack slot (which happens at -O0),
the SelectionDAG lowers the reload+extend on big-endian S390 as LGF
(sign-extend load), not LLGF (zero-extend load). This corrupts negative
int arguments: -1 is widened to 0xffffffffffffffff instead of the
correct 0x00000000ffffffff required by XPLINK64.
Fix: add an IsCallArg parameter to convertValVTToLocVT (default false).
When IsCallArg=true and LocInfo==AExt, emit ZERO_EXTEND instead of
ANY_EXTEND. Return-value paths keep ANY_EXTEND to avoid emitting
unnecessary lgfr instructions.
Also update the comment in clang/lib/CodeGen/Targets/SystemZ.cpp to
better explain why named integer arguments use getDirect (no extend
attribute) while variadic arguments use getExtend.
---
clang/lib/CodeGen/Targets/SystemZ.cpp | 21 +++++++++++--------
.../Target/SystemZ/SystemZISelLowering.cpp | 16 ++++++++++++--
2 files changed, 26 insertions(+), 11 deletions(-)
diff --git a/clang/lib/CodeGen/Targets/SystemZ.cpp b/clang/lib/CodeGen/Targets/SystemZ.cpp
index c43bc3a500e72..9ff394290c1e1 100644
--- a/clang/lib/CodeGen/Targets/SystemZ.cpp
+++ b/clang/lib/CodeGen/Targets/SystemZ.cpp
@@ -824,16 +824,19 @@ ABIArgInfo ZOSXPLinkABIInfo::classifyArgumentType(QualType Ty, bool IsNamedArg,
RAA == CGCXXABI::RAA_DirectInMemory);
// The XPLINK64 ABI does not mandate any widening of named integer arguments;
- // arguments are passed at their natural width with no sign- or zero-extension
- // guarantee. Only return values are required to be widened (per the z/OS
- // Language Environment Vendor Interfaces spec). Other compilers (e.g. xlc)
- // leave the upper bits of an argument register unspecified, so emitting
- // signext/zeroext on named parameters would produce incorrect code when
- // interoperating with xlc.
+ // other compilers (e.g. xlc) may leave the upper 32 bits of a GPR undefined
+ // when passing a sub-64-bit value. Emitting signext/zeroext on named
+ // parameters would cause clang-as-callee to add prologue sign/zero-extension
+ // that could conflict with an xlc caller that did not extend.
//
- // However, variadic arguments are different: the callee has no prototype for
- // them and reads a full 64-bit GPR, so the upper bits must be clean.
- // Extend variadic integer arguments to 64 bits.
+ // The caller is still required to fill the full 64-bit register (per
+ // CCPromoteToType<i64> / AExt in the backend). Use getDirect so that
+ // no extend attribute appears in the IR; AExt is applied by the calling
+ // convention tables.
+ //
+ // Variadic arguments are different: the callee has no prototype and reads
+ // the full 64-bit GPR, so the upper bits must be clean. getExtend emits
+ // a zero-extend (for unsigned types) or sign-extend (for signed types).
if (isPromotableIntegerTypeForABI(Ty)) {
if (IsNamedArg)
return ABIArgInfo::getDirect(CGT.ConvertType(Ty));
diff --git a/llvm/lib/Target/SystemZ/SystemZISelLowering.cpp b/llvm/lib/Target/SystemZ/SystemZISelLowering.cpp
index 42d100cd4f574..626d252855026 100644
--- a/llvm/lib/Target/SystemZ/SystemZISelLowering.cpp
+++ b/llvm/lib/Target/SystemZ/SystemZISelLowering.cpp
@@ -1906,14 +1906,26 @@ static SDValue convertLocVTToValVT(SelectionDAG &DAG, const SDLoc &DL,
// Value is a value of type VA.getValVT() that we need to copy into
// the location described by VA. Return a copy of Value converted to
// VA.getValVT(). The caller is responsible for handling indirect values.
+//
+// IsCallArg: true when converting an outgoing call argument. For AExt
+// (any-extend), ZERO_EXTEND is used instead of ANY_EXTEND to prevent the
+// backend from selecting LGF (sign-extend) when reloading a 32-bit value
+// from a spill slot into a 64-bit register at -O0 on big-endian S390.
+// For return values (IsCallArg=false) the original ANY_EXTEND is kept so
+// that no unnecessary sign/zero-extension instruction is emitted.
static SDValue convertValVTToLocVT(SelectionDAG &DAG, const SDLoc &DL,
- CCValAssign &VA, SDValue Value) {
+ CCValAssign &VA, SDValue Value,
+ bool IsCallArg = false) {
switch (VA.getLocInfo()) {
case CCValAssign::SExt:
return DAG.getNode(ISD::SIGN_EXTEND, DL, VA.getLocVT(), Value);
case CCValAssign::ZExt:
return DAG.getNode(ISD::ZERO_EXTEND, DL, VA.getLocVT(), Value);
case CCValAssign::AExt:
+ // For call arguments, use ZERO_EXTEND so that a spilled i32 is reloaded
+ // with LLGF (zero-extend) rather than LGF (sign-extend) at -O0.
+ if (IsCallArg)
+ return DAG.getNode(ISD::ZERO_EXTEND, DL, VA.getLocVT(), Value);
return DAG.getNode(ISD::ANY_EXTEND, DL, VA.getLocVT(), Value);
case CCValAssign::BCvt: {
assert(VA.getLocVT() == MVT::i64 || VA.getLocVT() == MVT::i128);
@@ -2410,7 +2422,7 @@ SystemZTargetLowering::LowerCall(CallLoweringInfo &CLI,
}
ArgValue = SpillSlot;
} else
- ArgValue = convertValVTToLocVT(DAG, DL, VA, ArgValue);
+ ArgValue = convertValVTToLocVT(DAG, DL, VA, ArgValue, /*IsCallArg=*/true);
if (VA.isRegLoc()) {
// In XPLINK64, for the 128-bit vararg case, ArgValue is bitcasted to a
>From cd4e90cfcb082d5a2955e77534e862094a2494b2 Mon Sep 17 00:00:00 2001
From: Zibi Sarbinowski <zibi at ca.ibm.com>
Date: Mon, 3 Aug 2026 22:37:53 -0400
Subject: [PATCH 7/7] Revert "[SystemZ] Fix XPLINK64 i32 call-arg AExt
producing LGF at -O0"
This reverts commit 3762fc467a9192d317a97b5d69a4702cd30753a3.
---
clang/lib/CodeGen/Targets/SystemZ.cpp | 21 ++++++++-----------
.../Target/SystemZ/SystemZISelLowering.cpp | 16 ++------------
2 files changed, 11 insertions(+), 26 deletions(-)
diff --git a/clang/lib/CodeGen/Targets/SystemZ.cpp b/clang/lib/CodeGen/Targets/SystemZ.cpp
index 9ff394290c1e1..c43bc3a500e72 100644
--- a/clang/lib/CodeGen/Targets/SystemZ.cpp
+++ b/clang/lib/CodeGen/Targets/SystemZ.cpp
@@ -824,19 +824,16 @@ ABIArgInfo ZOSXPLinkABIInfo::classifyArgumentType(QualType Ty, bool IsNamedArg,
RAA == CGCXXABI::RAA_DirectInMemory);
// The XPLINK64 ABI does not mandate any widening of named integer arguments;
- // other compilers (e.g. xlc) may leave the upper 32 bits of a GPR undefined
- // when passing a sub-64-bit value. Emitting signext/zeroext on named
- // parameters would cause clang-as-callee to add prologue sign/zero-extension
- // that could conflict with an xlc caller that did not extend.
+ // arguments are passed at their natural width with no sign- or zero-extension
+ // guarantee. Only return values are required to be widened (per the z/OS
+ // Language Environment Vendor Interfaces spec). Other compilers (e.g. xlc)
+ // leave the upper bits of an argument register unspecified, so emitting
+ // signext/zeroext on named parameters would produce incorrect code when
+ // interoperating with xlc.
//
- // The caller is still required to fill the full 64-bit register (per
- // CCPromoteToType<i64> / AExt in the backend). Use getDirect so that
- // no extend attribute appears in the IR; AExt is applied by the calling
- // convention tables.
- //
- // Variadic arguments are different: the callee has no prototype and reads
- // the full 64-bit GPR, so the upper bits must be clean. getExtend emits
- // a zero-extend (for unsigned types) or sign-extend (for signed types).
+ // However, variadic arguments are different: the callee has no prototype for
+ // them and reads a full 64-bit GPR, so the upper bits must be clean.
+ // Extend variadic integer arguments to 64 bits.
if (isPromotableIntegerTypeForABI(Ty)) {
if (IsNamedArg)
return ABIArgInfo::getDirect(CGT.ConvertType(Ty));
diff --git a/llvm/lib/Target/SystemZ/SystemZISelLowering.cpp b/llvm/lib/Target/SystemZ/SystemZISelLowering.cpp
index 626d252855026..42d100cd4f574 100644
--- a/llvm/lib/Target/SystemZ/SystemZISelLowering.cpp
+++ b/llvm/lib/Target/SystemZ/SystemZISelLowering.cpp
@@ -1906,26 +1906,14 @@ static SDValue convertLocVTToValVT(SelectionDAG &DAG, const SDLoc &DL,
// Value is a value of type VA.getValVT() that we need to copy into
// the location described by VA. Return a copy of Value converted to
// VA.getValVT(). The caller is responsible for handling indirect values.
-//
-// IsCallArg: true when converting an outgoing call argument. For AExt
-// (any-extend), ZERO_EXTEND is used instead of ANY_EXTEND to prevent the
-// backend from selecting LGF (sign-extend) when reloading a 32-bit value
-// from a spill slot into a 64-bit register at -O0 on big-endian S390.
-// For return values (IsCallArg=false) the original ANY_EXTEND is kept so
-// that no unnecessary sign/zero-extension instruction is emitted.
static SDValue convertValVTToLocVT(SelectionDAG &DAG, const SDLoc &DL,
- CCValAssign &VA, SDValue Value,
- bool IsCallArg = false) {
+ CCValAssign &VA, SDValue Value) {
switch (VA.getLocInfo()) {
case CCValAssign::SExt:
return DAG.getNode(ISD::SIGN_EXTEND, DL, VA.getLocVT(), Value);
case CCValAssign::ZExt:
return DAG.getNode(ISD::ZERO_EXTEND, DL, VA.getLocVT(), Value);
case CCValAssign::AExt:
- // For call arguments, use ZERO_EXTEND so that a spilled i32 is reloaded
- // with LLGF (zero-extend) rather than LGF (sign-extend) at -O0.
- if (IsCallArg)
- return DAG.getNode(ISD::ZERO_EXTEND, DL, VA.getLocVT(), Value);
return DAG.getNode(ISD::ANY_EXTEND, DL, VA.getLocVT(), Value);
case CCValAssign::BCvt: {
assert(VA.getLocVT() == MVT::i64 || VA.getLocVT() == MVT::i128);
@@ -2422,7 +2410,7 @@ SystemZTargetLowering::LowerCall(CallLoweringInfo &CLI,
}
ArgValue = SpillSlot;
} else
- ArgValue = convertValVTToLocVT(DAG, DL, VA, ArgValue, /*IsCallArg=*/true);
+ ArgValue = convertValVTToLocVT(DAG, DL, VA, ArgValue);
if (VA.isRegLoc()) {
// In XPLINK64, for the 128-bit vararg case, ArgValue is bitcasted to a
More information about the llvm-commits
mailing list