[clang] [llvm] [SystemZ] Do not extend integer arguments in z/OS XPLINK64 ABI (PR #206833)
Zibi Sarbinowski via cfe-commits
cfe-commits at lists.llvm.org
Thu Jul 9 11:47:24 PDT 2026
https://github.com/zibi2 updated https://github.com/llvm/llvm-project/pull/206833
>From a4a963d8ad3e17f09f5714044d6d15e28492c1e4 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] [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 getNoExtend() for
sub-64-bit promotable integer types. This preserves normal C integer
promotion (8/16-bit widening to 32 bits) but omits signext/zeroext
from LLVM IR parameters, so the backend emits no 32->64-bit extension
instruction for incoming arguments (lgr plain copy). 64-bit types
(long, unsigned long) are already register-width and pass via
getDirect() unchanged.
- 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: sub-64-bit integer parameters
now carry noext; 64-bit and non-integer parameters unchanged; return
types still carry signext/zeroext.
- clang/test/CodeGen/pragma-export.cpp: i32 parameters updated to noext.
---
clang/lib/CodeGen/Targets/SystemZ.cpp | 15 ++++++++++---
clang/test/CodeGen/SystemZ/zos-abi.c | 22 +++++++++----------
clang/test/CodeGen/pragma-export.cpp | 8 +++----
.../include/llvm/Analysis/TargetLibraryInfo.h | 11 +++++++---
llvm/lib/Target/SystemZ/SystemZCallingConv.td | 1 +
5 files changed, 36 insertions(+), 21 deletions(-)
diff --git a/clang/lib/CodeGen/Targets/SystemZ.cpp b/clang/lib/CodeGen/Targets/SystemZ.cpp
index 37f6b3017f776..03e92478fcac5 100644
--- a/clang/lib/CodeGen/Targets/SystemZ.cpp
+++ b/clang/lib/CodeGen/Targets/SystemZ.cpp
@@ -822,9 +822,18 @@ 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.
- if (isPromotableIntegerTypeForABI(Ty))
- return ABIArgInfo::getExtend(Ty, CGT.ConvertType(Ty));
+ // Sub-64-bit integer arguments undergo normal C integer promotion (8/16-bit
+ // types are widened to 32 bits) but are NOT sign- or zero-extended to 64
+ // bits. The XPLINK64 ABI specification mandates 64-bit widening only for
+ // return values; other compilers (e.g. xlc) leave the upper 32 bits of an
+ // argument register unspecified, so emitting signext/zeroext on parameters
+ // would produce incorrect code when interoperating with xlc.
+ // 64-bit types (long, unsigned long) are already register-width and are
+ // passed unmodified via getDirect().
+ if (isPromotableIntegerTypeForABI(Ty) &&
+ getContext().getTypeSize(Ty) < 64)
+ return ABIArgInfo::getNoExtend(
+ cast<llvm::IntegerType>(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 d7843d9bee089..03bbba7dc2d63 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 noext %{{.*}})
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 noext %{{.*}})
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 noext %{{.*}})
short pass_short(short arg) { return arg; }
-// CHECK-LABEL: define signext i16 @pass_short(i16 signext %{{.*}})
+// CHECK-LABEL: define signext i16 @pass_short(i16 noext %{{.*}})
int pass_int(int arg) { return arg; }
-// CHECK-LABEL: define signext i32 @pass_int(i32 signext %{{.*}})
+// CHECK-LABEL: define signext i32 @pass_int(i32 noext %{{.*}})
long pass_long(long arg) { return arg; }
-// CHECK-LABEL: define signext i64 @pass_long(i64 signext %{{.*}})
+// CHECK-LABEL: define signext 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 noext %{{.*}})
#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 noext %{{.*}})
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 noext %{{.*}})
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 noext %{{.*}})
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; }
diff --git a/clang/test/CodeGen/pragma-export.cpp b/clang/test/CodeGen/pragma-export.cpp
index 531afbd659234..d977e4c2e9940 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 noext noundef %0)
+// CHECK: define hidden void @_Z2f2ii(i32 noext noundef %0, i32 noext 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 noext 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 noext 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..df5c02917b16b 100644
--- a/llvm/lib/Target/SystemZ/SystemZCallingConv.td
+++ b/llvm/lib/Target/SystemZ/SystemZCallingConv.td
@@ -216,6 +216,7 @@ 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>>>,
+
// 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().
More information about the cfe-commits
mailing list