[clang] [llvm] [SystemZ] Add i128 support for z/OS XPLINK64 ABI (PR #223026)
Zibi Sarbinowski via llvm-commits
llvm-commits at lists.llvm.org
Thu Sep 17 05:40:29 PDT 2026
https://github.com/zibi2 updated https://github.com/llvm/llvm-project/pull/223026
>From de8f5889e0bada955aaa3f37c67cf1b8d273f646 Mon Sep 17 00:00:00 2001
From: Zibi Sarbinowski <zibi at ca.ibm.com>
Date: Tue, 15 Sep 2026 11:01:11 -0400
Subject: [PATCH 1/2] [SystemZ][z/OS] Add -mvx/-msoft-float diagnostics and fix
arch guard
Port of Woz commits 3ba754d, 5e94ae9, 8702193:
- Add err_drv_incompatible_arch diagnostic: '-mvx' requires at least
'-march=arch11' on z/OS; the guard is T.isOSzOS() so Linux targets
are unaffected
- Add err_drv_unsupported_opt_for_target diagnostic for '-msoft-float'
on z/OS targets
- Pass Triple into getSystemZTargetFeatures() to enable the OS check
- Move FloatABI handling after the -mvx block (no functional change for
the feature vector ordering)
- Extend systemz-features.cpp with s390x-ibm-zos RUN lines; -mvx z/OS
lines require -march=arch11 since the default z/OS CPU is zEC12
---
.../clang/Basic/DiagnosticDriverKinds.td | 3 +
clang/lib/Basic/Targets/SystemZ.h | 5 +
clang/lib/Driver/ToolChains/Arch/SystemZ.cpp | 22 +-
clang/lib/Driver/ToolChains/Arch/SystemZ.h | 3 +-
clang/lib/Driver/ToolChains/CommonArgs.cpp | 2 +-
clang/test/CodeGen/SystemZ/zos-alignment.c | 10 +-
clang/test/Driver/systemz-features.cpp | 9 +
clang/test/Sema/zvector.c | 4 +
llvm/lib/Target/SystemZ/SystemZCallingConv.h | 8 +
llvm/lib/Target/SystemZ/SystemZCallingConv.td | 7 +
.../Target/SystemZ/SystemZISelLowering.cpp | 29 +-
llvm/lib/Target/SystemZ/SystemZSubtarget.h | 3 +
llvm/test/CodeGen/SystemZ/call-zos-i128.ll | 14 +-
llvm/test/CodeGen/SystemZ/int-div-09.ll | 13 +
.../test/CodeGen/SystemZ/zos-abi-int128-64.ll | 253 ++++++++++++++++++
15 files changed, 356 insertions(+), 29 deletions(-)
create mode 100644 llvm/test/CodeGen/SystemZ/int-div-09.ll
create mode 100644 llvm/test/CodeGen/SystemZ/zos-abi-int128-64.ll
diff --git a/clang/include/clang/Basic/DiagnosticDriverKinds.td b/clang/include/clang/Basic/DiagnosticDriverKinds.td
index df6ff0c2cf399..7e872c5da674d 100644
--- a/clang/include/clang/Basic/DiagnosticDriverKinds.td
+++ b/clang/include/clang/Basic/DiagnosticDriverKinds.td
@@ -203,6 +203,9 @@ def err_drv_unsupported_unwind_for_platform : Error<
"unsupported unwind library '%0' for platform '%1'">;
def err_drv_incompatible_unwindlib : Error<
"--rtlib=libgcc requires --unwindlib=libgcc">;
+def err_drv_incompatible_arch : Error<
+ "'%0' option requires at least '%1', current '%2' is too low">;
+
def err_drv_incompatible_options : Error<
"the combination of '%0' and '%1' is incompatible">;
def err_drv_invalid_cstdlib_name : Error<
diff --git a/clang/lib/Basic/Targets/SystemZ.h b/clang/lib/Basic/Targets/SystemZ.h
index cf7d940c1b2e5..6aaf47fb44f5b 100644
--- a/clang/lib/Basic/Targets/SystemZ.h
+++ b/clang/lib/Basic/Targets/SystemZ.h
@@ -261,6 +261,11 @@ class LLVM_LIBRARY_VISIBILITY SystemZTargetInfo : public TargetInfo {
uint64_t getPointerAlignV(LangAS AddrSpace) const override {
return getPointerWidthV(AddrSpace);
}
+
+ bool hasInt128Type() const override {
+ return (getTriple().isOSzOS() ? hasFeature("vx")
+ : TargetInfo::hasInt128Type());
+ }
};
} // namespace targets
} // namespace clang
diff --git a/clang/lib/Driver/ToolChains/Arch/SystemZ.cpp b/clang/lib/Driver/ToolChains/Arch/SystemZ.cpp
index 1ef6a725483e8..6ac0f8ffba47d 100644
--- a/clang/lib/Driver/ToolChains/Arch/SystemZ.cpp
+++ b/clang/lib/Driver/ToolChains/Arch/SystemZ.cpp
@@ -53,7 +53,8 @@ std::string systemz::getSystemZTargetCPU(const ArgList &Args,
return CLANG_SYSTEMZ_DEFAULT_ARCH;
}
-void systemz::getSystemZTargetFeatures(const Driver &D, const ArgList &Args,
+void systemz::getSystemZTargetFeatures(const Driver &D, const llvm::Triple &T,
+ const ArgList &Args,
std::vector<llvm::StringRef> &Features) {
// -m(no-)htm overrides use of the transactional-execution facility.
if (Arg *A = Args.getLastArg(options::OPT_mhtm, options::OPT_mno_htm)) {
@@ -62,11 +63,26 @@ void systemz::getSystemZTargetFeatures(const Driver &D, const ArgList &Args,
else
Features.push_back("-transactional-execution");
}
+
// -m(no-)vx overrides use of the vector facility.
if (Arg *A = Args.getLastArg(options::OPT_mvx, options::OPT_mno_vx)) {
- if (A->getOption().matches(options::OPT_mvx))
+
+ // The -mvx requires at least -march=arch11/z13 on z/OS.
+ if (A->getOption().matches(options::OPT_mvx)) {
+ auto Arch = getSystemZTargetCPU(Args, T);
+ if (T.isOSzOS() && llvm::StringSwitch<bool>(Arch)
+ .Case("arch8", true)
+ .Case("z10", true)
+ .Case("arch9", true)
+ .Case("z196", true)
+ .Case("arch10", true)
+ .Case("zEC12", true)
+ .Default(false)) {
+ D.Diag(diag::err_drv_incompatible_arch)
+ << "-mvx" << "-march=arch11" << Arch;
+ }
Features.push_back("+vector");
- else
+ } else
Features.push_back("-vector");
}
diff --git a/clang/lib/Driver/ToolChains/Arch/SystemZ.h b/clang/lib/Driver/ToolChains/Arch/SystemZ.h
index f2d30d24ba63c..b5d1da71f58e1 100644
--- a/clang/lib/Driver/ToolChains/Arch/SystemZ.h
+++ b/clang/lib/Driver/ToolChains/Arch/SystemZ.h
@@ -30,7 +30,8 @@ FloatABI getSystemZFloatABI(const Driver &D, const llvm::opt::ArgList &Args);
std::string getSystemZTargetCPU(const llvm::opt::ArgList &Args,
const llvm::Triple &T);
-void getSystemZTargetFeatures(const Driver &D, const llvm::opt::ArgList &Args,
+void getSystemZTargetFeatures(const Driver &D, const llvm::Triple &T,
+ const llvm::opt::ArgList &Args,
std::vector<llvm::StringRef> &Features);
} // end namespace systemz
diff --git a/clang/lib/Driver/ToolChains/CommonArgs.cpp b/clang/lib/Driver/ToolChains/CommonArgs.cpp
index 64859a318485b..dba514adbff8f 100644
--- a/clang/lib/Driver/ToolChains/CommonArgs.cpp
+++ b/clang/lib/Driver/ToolChains/CommonArgs.cpp
@@ -922,7 +922,7 @@ void tools::getTargetFeatures(const Driver &D, const llvm::Triple &Triple,
riscv::getRISCVTargetFeatures(D, Triple, Args, Features);
break;
case llvm::Triple::systemz:
- systemz::getSystemZTargetFeatures(D, Args, Features);
+ systemz::getSystemZTargetFeatures(D, Triple, Args, Features);
break;
case llvm::Triple::aarch64:
case llvm::Triple::aarch64_32:
diff --git a/clang/test/CodeGen/SystemZ/zos-alignment.c b/clang/test/CodeGen/SystemZ/zos-alignment.c
index 65c6843e0d9ac..a9fe32e248157 100644
--- a/clang/test/CodeGen/SystemZ/zos-alignment.c
+++ b/clang/test/CodeGen/SystemZ/zos-alignment.c
@@ -1,4 +1,6 @@
// RUN: %clang_cc1 -emit-llvm-only -triple s390x-none-zos -fdump-record-layouts %s | FileCheck %s --check-prefix=CHECK
+// RUN: %clang_cc1 -emit-llvm-only -triple s390x-none-zos -fdump-record-layouts -DINT128_A \
+// RUN: -target-cpu z13 %s | FileCheck %s --check-prefix=INT128
// RUN: %clang_cc1 -emit-llvm -triple s390x-none-zos %s -o - | FileCheck %s --check-prefix=DECL
static int __attribute__((aligned(32))) v0;
@@ -160,12 +162,14 @@ struct s11 {
// CHECK-NEXT: 8 | char b
// CHECK-NEXT: | [sizeof=16, align=8]
+#ifdef INT128_A
struct s12 {
__int128_t a;
} S12;
-// CHECK: 0 | struct s12
-// CHECK-NEXT: 0 | __int128_t a
-// CHECK-NEXT: | [sizeof=16, align=8]
+// INT128: 0 | struct s12
+// INT128-NEXT: 0 | __int128_t a
+// INT128-NEXT: | [sizeof=16, align=8]
+#endif
union u0 {
unsigned short d1 __attribute__((packed));
diff --git a/clang/test/Driver/systemz-features.cpp b/clang/test/Driver/systemz-features.cpp
index be1818a032355..4c1e0f3ae6a95 100644
--- a/clang/test/Driver/systemz-features.cpp
+++ b/clang/test/Driver/systemz-features.cpp
@@ -1,5 +1,6 @@
// RUN: %clang -target s390x-unknown-linux-gnu %s -### -o %t.o 2>&1 | FileCheck -check-prefix=CHECK-DEFAULT %s
+// RUN: %clang -target s390x-ibm-zos %s -### -o %t.o 2>&1 | FileCheck -check-prefix=CHECK-DEFAULT %s
// CHECK-DEFAULT-NOT: "-target-feature" "+transactional-execution"
// CHECK-DEFAULT-NOT: "-target-feature" "-transactional-execution"
// CHECK-DEFAULT-NOT: "-target-feature" "+vector"
@@ -7,20 +8,28 @@
// RUN: %clang -target s390x-unknown-linux-gnu %s -mhtm -### -o %t.o 2>&1 | FileCheck -check-prefix=CHECK-HTM %s
// RUN: %clang -target s390x-unknown-linux-gnu %s -mno-htm -mhtm -### -o %t.o 2>&1 | FileCheck -check-prefix=CHECK-HTM %s
+// RUN: %clang -target s390x-ibm-zos %s -mhtm -### -o %t.o 2>&1 | FileCheck -check-prefix=CHECK-HTM %s
+// RUN: %clang -target s390x-ibm-zos %s -mno-htm -mhtm -### -o %t.o 2>&1 | FileCheck -check-prefix=CHECK-HTM %s
// CHECK-HTM: "-target-feature" "+transactional-execution"
// CHECK-HTM-NOT: "-target-feature" "-transactional-execution"
// RUN: %clang -target s390x-unknown-linux-gnu %s -mno-htm -### -o %t.o 2>&1 | FileCheck -check-prefix=CHECK-NOHTM %s
// RUN: %clang -target s390x-unknown-linux-gnu %s -mhtm -mno-htm -### -o %t.o 2>&1 | FileCheck -check-prefix=CHECK-NOHTM %s
+// RUN: %clang -target s390x-ibm-zos %s -mno-htm -### -o %t.o 2>&1 | FileCheck -check-prefix=CHECK-NOHTM %s
+// RUN: %clang -target s390x-ibm-zos %s -mhtm -mno-htm -### -o %t.o 2>&1 | FileCheck -check-prefix=CHECK-NOHTM %s
// CHECK-NOHTM: "-target-feature" "-transactional-execution"
// CHECK-NOHTM-NOT: "-target-feature" "+transactional-execution"
// RUN: %clang -target s390x-unknown-linux-gnu %s -mvx -### -o %t.o 2>&1 | FileCheck -check-prefix=CHECK-VX %s
// RUN: %clang -target s390x-unknown-linux-gnu %s -mno-vx -mvx -### -o %t.o 2>&1 | FileCheck -check-prefix=CHECK-VX %s
+// RUN: %clang -target s390x-ibm-zos %s -mvx -march=arch11 -### -o %t.o 2>&1 | FileCheck -check-prefix=CHECK-VX %s
+// RUN: %clang -target s390x-ibm-zos %s -mno-vx -mvx -march=arch11 -### -o %t.o 2>&1 | FileCheck -check-prefix=CHECK-VX %s
// CHECK-VX: "-target-feature" "+vector"
// CHECK-VX-NOT: "-target-feature" "-vector"
//
// RUN: %clang -target s390x-unknown-linux-gnu %s -mno-vx -### -o %t.o 2>&1 | FileCheck -check-prefix=CHECK-NOVX %s
// RUN: %clang -target s390x-unknown-linux-gnu %s -mvx -mno-vx -### -o %t.o 2>&1 | FileCheck -check-prefix=CHECK-NOVX %s
+// RUN: %clang -target s390x-ibm-zos %s -mno-vx -### -o %t.o 2>&1 | FileCheck -check-prefix=CHECK-NOVX %s
+// RUN: %clang -target s390x-ibm-zos %s -mvx -mno-vx -### -o %t.o 2>&1 | FileCheck -check-prefix=CHECK-NOVX %s
// CHECK-NOVX: "-target-feature" "-vector"
// CHECK-NOVX-NOT: "-target-feature" "+vector"
diff --git a/clang/test/Sema/zvector.c b/clang/test/Sema/zvector.c
index e1e4ab532426d..e3ceac48faf0b 100644
--- a/clang/test/Sema/zvector.c
+++ b/clang/test/Sema/zvector.c
@@ -2,6 +2,10 @@
// RUN: -flax-vector-conversions=none -W -Wall -Wconversion \
// RUN: -Werror -fsyntax-only -verify %s
+// RUN: %clang_cc1 -triple s390x-ibm-zos -fzvector -target-cpu z13 \
+// RUN: -flax-vector-conversions=none -W -Wall -Wconversion \
+// RUN: -Werror -fsyntax-only -verify %s
+
vector signed char sc, sc2;
vector unsigned char uc, uc2;
vector bool char bc, bc2;
diff --git a/llvm/lib/Target/SystemZ/SystemZCallingConv.h b/llvm/lib/Target/SystemZ/SystemZCallingConv.h
index 9fcd5b8152c5a..91a1fe8f12d96 100644
--- a/llvm/lib/Target/SystemZ/SystemZCallingConv.h
+++ b/llvm/lib/Target/SystemZ/SystemZCallingConv.h
@@ -96,6 +96,14 @@ inline bool CC_XPLINK64_Pointer(unsigned &ValNo, MVT &ValVT, MVT &LocVT,
return false;
}
+inline bool CC_XPLINK_Int128(unsigned &ValNo, MVT &ValVT, MVT &LocVT,
+ CCValAssign::LocInfo &LocInfo,
+ ISD::ArgFlagsTy &ArgFlags, CCState &State) {
+ LocVT = MVT::v16i8;
+ LocInfo = CCValAssign::BCvt;
+ return false;
+}
+
inline bool CC_XPLINK64_Shadow_Reg(unsigned &ValNo, MVT &ValVT, MVT &LocVT,
CCValAssign::LocInfo &LocInfo,
ISD::ArgFlagsTy &ArgFlags, CCState &State) {
diff --git a/llvm/lib/Target/SystemZ/SystemZCallingConv.td b/llvm/lib/Target/SystemZ/SystemZCallingConv.td
index 4f483cf2479dd..7263587d54369 100644
--- a/llvm/lib/Target/SystemZ/SystemZCallingConv.td
+++ b/llvm/lib/Target/SystemZ/SystemZCallingConv.td
@@ -167,6 +167,9 @@ def CSR_SystemZ_XPLINK64_Vector : CalleeSavedRegs<(add CSR_SystemZ_XPLINK64,
// z/OS XPLINK64 return value calling convention
//===----------------------------------------------------------------------===//
def RetCC_SystemZ_XPLINK64 : CallingConv<[
+ // Convert an i128 value to vector
+ CCIfType<[i128], CCCustom<"CC_XPLINK_Int128">>,
+
// XPLINK64 ABI compliant code widens integral types smaller than i64
// to i64.
CCIfType<[i32], CCPromoteToType<i64>>,
@@ -225,6 +228,10 @@ def CC_SystemZ_XPLINK64 : CallingConv<[
CCIfPtr<CCCustom<"CC_XPLINK64_Pointer">>,
// long double, can only be passed in GPR2 and GPR3, if available,
// hence R2Q
+
+ // Convert an i128 value to vector
+ CCIfType<[i128], CCCustom<"CC_XPLINK_Int128">>,
+
CCIfType<[f128], CCIfArgVarArg<CCCustom<"CC_XPLINK64_Allocate128BitVararg">>>,
// Non fixed vector arguments are treated in the same way as long
// doubles.
diff --git a/llvm/lib/Target/SystemZ/SystemZISelLowering.cpp b/llvm/lib/Target/SystemZ/SystemZISelLowering.cpp
index 8e621aafe27d2..f93cbbfb557ca 100644
--- a/llvm/lib/Target/SystemZ/SystemZISelLowering.cpp
+++ b/llvm/lib/Target/SystemZ/SystemZISelLowering.cpp
@@ -1900,11 +1900,15 @@ static SDValue convertLocVTToValVT(SelectionDAG &DAG, const SDLoc &DL,
if (VA.isExtInLoc())
Value = DAG.getNode(ISD::TRUNCATE, DL, VA.getValVT(), Value);
else if (VA.getLocInfo() == CCValAssign::BCvt) {
- // If this is a short vector argument loaded from the stack,
- // extend from i64 to full vector size and then bitcast.
- assert(VA.getLocVT() == MVT::i64);
- assert(VA.getValVT().isVector());
- Value = DAG.getBuildVector(MVT::v2i64, DL, {Value, DAG.getUNDEF(MVT::i64)});
+ // If the argument is a short vector loaded from the stack,
+ // extend it from i64 to the full vector size and then perform a bitcast.
+ // Alternatively, if the argument is an int128,
+ // directly bitcast it into a vector of v16i8.
+ assert(VA.getLocVT() == MVT::i64 || VA.getLocVT() == MVT::v16i8);
+ assert(VA.getValVT().isVector() || VA.getValVT() == MVT::i128);
+ if (VA.getLocVT() == MVT::i64)
+ Value =
+ DAG.getBuildVector(MVT::v2i64, DL, {Value, DAG.getUNDEF(MVT::i64)});
Value = DAG.getNode(ISD::BITCAST, DL, VA.getValVT(), Value);
} else
assert(VA.getLocInfo() == CCValAssign::Full && "Unsupported getLocInfo");
@@ -1924,9 +1928,11 @@ static SDValue convertValVTToLocVT(SelectionDAG &DAG, const SDLoc &DL,
case CCValAssign::AExt:
return DAG.getNode(ISD::ANY_EXTEND, DL, VA.getLocVT(), Value);
case CCValAssign::BCvt: {
- assert(VA.getLocVT() == MVT::i64 || VA.getLocVT() == MVT::i128);
+ assert(VA.getLocVT() == MVT::i64 || VA.getLocVT() == MVT::i128 ||
+ VA.getLocVT() == MVT::v16i8);
assert(VA.getValVT().isVector() || VA.getValVT() == MVT::f32 ||
- VA.getValVT() == MVT::f64 || VA.getValVT() == MVT::f128);
+ VA.getValVT() == MVT::f64 || VA.getValVT() == MVT::f128 ||
+ VA.getValVT() == MVT::i128);
// For an f32 vararg we need to first promote it to an f64 and then
// bitcast it to an i64.
if (VA.getValVT() == MVT::f32 && VA.getLocVT() == MVT::i64)
@@ -2608,9 +2614,12 @@ bool SystemZTargetLowering::CanLowerReturn(
const Type *RetTy) const {
// Special case that we cannot easily detect in RetCC_SystemZ since
// i128 may not be a legal type.
- for (auto &Out : Outs)
- if (Out.ArgVT.isScalarInteger() && Out.ArgVT.getSizeInBits() > 64)
- return false;
+ // On z/OS we need to skip the convention of passing the return value on
+ // the stack used on zLinux.
+ if (Subtarget.isTargetLinux())
+ for (auto &Out : Outs)
+ if (Out.ArgVT.isScalarInteger() && Out.ArgVT.getSizeInBits() > 64)
+ return false;
SmallVector<CCValAssign, 16> RetLocs;
CCState RetCCInfo(CallConv, IsVarArg, MF, RetLocs, Context);
diff --git a/llvm/lib/Target/SystemZ/SystemZSubtarget.h b/llvm/lib/Target/SystemZ/SystemZSubtarget.h
index 761bc525b59d3..eda9f095fb237 100644
--- a/llvm/lib/Target/SystemZ/SystemZSubtarget.h
+++ b/llvm/lib/Target/SystemZ/SystemZSubtarget.h
@@ -124,6 +124,9 @@ class SystemZSubtarget : public SystemZGenSubtargetInfo {
// Returns TRUE if we are generating code for a s390x machine running zOS
bool isTargetzOS() const { return TargetTriple.isOSzOS(); }
+
+ // Returns TRUE if we are generating code for a s390x machine running Linux
+ bool isTargetLinux() const { return TargetTriple.isOSLinux(); }
};
} // end namespace llvm
diff --git a/llvm/test/CodeGen/SystemZ/call-zos-i128.ll b/llvm/test/CodeGen/SystemZ/call-zos-i128.ll
index c12e26184f068..e5285377c939e 100644
--- a/llvm/test/CodeGen/SystemZ/call-zos-i128.ll
+++ b/llvm/test/CodeGen/SystemZ/call-zos-i128.ll
@@ -4,14 +4,9 @@
; CHECK-LABEL: call_i128 DS 0H
; CHECK-DAG: larl 1,L#CPI0_0
-; CHECK-DAG: vl 0,0(1),3
-; CHECK-DAG: vst 0,2256(4),3
+; CHECK-DAG: vl 24,0(1),3
; CHECK-DAG: larl 1,L#CPI0_1
-; CHECK-DAG: vl 0,0(1),3
-; CHECK-DAG: vst 0,2272(4),3
-; CHECK-DAG: la 1,2288(4)
-; CHECK-DAG: la 2,2272(4)
-; CHECK-DAG: la 3,2256(4)
+; CHECK-DAG: vl 25,0(1),3
define i128 @call_i128() {
entry:
@@ -20,10 +15,7 @@ entry:
}
; CHECK-LABEL: pass_i128 DS 0H
-; CHECK: vl 0,0(3),3
-; CHECK: vl 1,0(2),3
-; CHECK: vaq 0,1,0
-; CHECK: vst 0,0(1),3
+; CHECK: vaq 24,24,25
define i128 @pass_i128(i128 %arg0, i128 %arg1) {
entry:
%N = add i128 %arg0, %arg1
diff --git a/llvm/test/CodeGen/SystemZ/int-div-09.ll b/llvm/test/CodeGen/SystemZ/int-div-09.ll
new file mode 100644
index 0000000000000..bd77d62bf23ca
--- /dev/null
+++ b/llvm/test/CodeGen/SystemZ/int-div-09.ll
@@ -0,0 +1,13 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 5
+; The objective of this test is to check conversion from i128 argument to vector v16i8.
+; This test was derived from builtins/floatuntitf.c residing in compiler.rt.
+
+; RUN: env LLVM_EMIT_GNU_AS_ZOS=0 llc -mtriple s390x-ibm-zos < %s | FileCheck %s
+$test_i128_arg = comdat any
+define void @test_i128_arg(ptr %this, i128 %__arg) "target-features"="+vector" {
+; CHECK-LABEL: test_i128_arg DS 0H
+; CHECK: * %entry
+; CHECK-NEXT: * %bb.0:
+entry:
+ ret void
+}
diff --git a/llvm/test/CodeGen/SystemZ/zos-abi-int128-64.ll b/llvm/test/CodeGen/SystemZ/zos-abi-int128-64.ll
new file mode 100644
index 0000000000000..afbd5f0b0d5e9
--- /dev/null
+++ b/llvm/test/CodeGen/SystemZ/zos-abi-int128-64.ll
@@ -0,0 +1,253 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 5
+; RUN: env LLVM_EMIT_GNU_AS_ZOS=0 llc < %s| FileCheck %s
+
+source_filename = "zos-abi-int128-input.c"
+target datalayout = "E-m:l-p1:32:32-i1:8:16-i8:8:16-i64:64-f128:64-v128:64-a:8:16-n32:64"
+target triple = "s390x-ibm-zos"
+
+attributes #0 = { "target-cpu"="z13" "target-features"="+vector" }
+
+; Check if GPR1 is used for the first integer argument and
+; VR24 for the second int128 argument and return value.
+;
+define i128 @pi_1i128(i32 signext %a1, i128 %a2) #0 {
+; CHECK-LABEL: pi_1i128 DS 0H
+; CHECK: stmg 6,7,1904(4)
+; CHECK-NEXT: L#stack_update0 DS 0H
+; CHECK-NEXT: aghi 4,-160
+; CHECK: L#end_of_prologue0 DS 0H
+; CHECK-NEXT: vlvgp 0,1,1
+; CHECK-NEXT: vrepf 0,0,3
+; CHECK-NEXT: vrepib 1,96
+; CHECK-NEXT: st 1,2204(4)
+; CHECK-NEXT: vst 24,2184(4),3
+; CHECK-NEXT: vsrab 0,0,1
+; CHECK-NEXT: lg 7,2072(4)
+; CHECK-NEXT: vaq 24,0,24
+; CHECK-NEXT: aghi 4,160
+; CHECK-NEXT: b 2(7)
+entry:
+ %a1.addr = alloca i32, align 4
+ %a2.addr = alloca i128, align 8
+ store i32 %a1, ptr %a1.addr, align 4
+ store i128 %a2, ptr %a2.addr, align 8
+ %0 = load i32, ptr %a1.addr, align 4
+ %conv = sext i32 %0 to i128
+ %1 = load i128, ptr %a2.addr, align 8
+ %add = add nsw i128 %conv, %1
+ ret i128 %add
+}
+
+; Check if VR24-31 are used for the first 8 int128 arguments and
+; the 9th int128 argument is loaded from the stack into VR0.
+; In addition, check VR24 is used for return value.
+;
+define i128 @p9i128(i128 %a1, i128 %a2, i128 %a3, i128 %a4, i128 %a5, i128 %a6, i128 %a7, i128 %a8, i128 %a9) #0 {
+; CHECK-LABEL: p9i128 DS 0H
+; CHECK: stmg 6,7,1776(4)
+; CHECK-NEXT: L#stack_update1 DS 0H
+; CHECK-NEXT: aghi 4,-288
+; CHECK: L#end_of_prologue1 DS 0H
+; CHECK-NEXT: vaq 1,24,25
+; CHECK-NEXT: vaq 1,1,26
+; CHECK-NEXT: vaq 1,1,27
+; CHECK-NEXT: vl 0,2592(4),4
+; CHECK-NEXT: vaq 1,1,28
+; CHECK-NEXT: vaq 1,1,29
+; CHECK-NEXT: vst 24,2320(4),3
+; CHECK-NEXT: vst 25,2304(4),3
+; CHECK-NEXT: vst 26,2288(4),3
+; CHECK-NEXT: vst 27,2272(4),3
+; CHECK-NEXT: vst 28,2256(4),3
+; CHECK-NEXT: vst 29,2240(4),3
+; CHECK-NEXT: vst 30,2224(4),3
+; CHECK-NEXT: vst 31,2208(4),3
+; CHECK-NEXT: vst 0,2192(4),3
+; CHECK-NEXT: vaq 1,1,30
+; CHECK-NEXT: lg 7,2072(4)
+; CHECK-NEXT: vaq 1,1,31
+; CHECK-NEXT: vaq 24,1,0
+; CHECK-NEXT: aghi 4,288
+; CHECK-NEXT: b 2(7)
+entry:
+ %a1.addr = alloca i128, align 8
+ %a2.addr = alloca i128, align 8
+ %a3.addr = alloca i128, align 8
+ %a4.addr = alloca i128, align 8
+ %a5.addr = alloca i128, align 8
+ %a6.addr = alloca i128, align 8
+ %a7.addr = alloca i128, align 8
+ %a8.addr = alloca i128, align 8
+ %a9.addr = alloca i128, align 8
+ store i128 %a1, ptr %a1.addr, align 8
+ store i128 %a2, ptr %a2.addr, align 8
+ store i128 %a3, ptr %a3.addr, align 8
+ store i128 %a4, ptr %a4.addr, align 8
+ store i128 %a5, ptr %a5.addr, align 8
+ store i128 %a6, ptr %a6.addr, align 8
+ store i128 %a7, ptr %a7.addr, align 8
+ store i128 %a8, ptr %a8.addr, align 8
+ store i128 %a9, ptr %a9.addr, align 8
+ %0 = load i128, ptr %a1.addr, align 8
+ %1 = load i128, ptr %a2.addr, align 8
+ %add = add nsw i128 %0, %1
+ %2 = load i128, ptr %a3.addr, align 8
+ %add1 = add nsw i128 %add, %2
+ %3 = load i128, ptr %a4.addr, align 8
+ %add2 = add nsw i128 %add1, %3
+ %4 = load i128, ptr %a5.addr, align 8
+ %add3 = add nsw i128 %add2, %4
+ %5 = load i128, ptr %a6.addr, align 8
+ %add4 = add nsw i128 %add3, %5
+ %6 = load i128, ptr %a7.addr, align 8
+ %add5 = add nsw i128 %add4, %6
+ %7 = load i128, ptr %a8.addr, align 8
+ %add6 = add nsw i128 %add5, %7
+ %8 = load i128, ptr %a9.addr, align 8
+ %add7 = add nsw i128 %add6, %8
+ ret i128 %add7
+}
+
+; Check if three int128 arguments in addition to variable argument(s) are passed in extended argument list.
+;
+define i128 @p3i128_var(i128 %a1, i128 %a2, i128 %a3, ...) #0 {
+; CHECK-LABEL: p3i128_var DS 0H
+; CHECK: stmg 6,7,1840(4)
+; CHECK-NEXT: L#stack_update2 DS 0H
+; CHECK-NEXT: aghi 4,-224
+; CHECK-NEXT: stg 1,2400(4)
+; CHECK-NEXT: stg 2,2408(4)
+; CHECK-NEXT: stg 3,2416(4)
+; CHECK: L#end_of_prologue2 DS 0H
+; CHECK-NEXT: vl 0,2448(4),4
+; CHECK-NEXT: la 0,2464(4)
+; CHECK-NEXT: vst 24,2256(4),3
+; CHECK-NEXT: vst 25,2240(4),3
+; CHECK-NEXT: vst 26,2224(4),3
+; CHECK-NEXT: stg 0,2200(4)
+; CHECK-NEXT: vst 0,2208(4),3
+; CHECK-NEXT: vaq 1,24,25
+; CHECK-NEXT: lg 7,2072(4)
+; CHECK-NEXT: vaq 1,1,26
+; CHECK-NEXT: vsq 24,1,0
+; CHECK-NEXT: aghi 4,224
+; CHECK-NEXT: b 2(7)
+entry:
+ %a1.addr = alloca i128, align 8
+ %a2.addr = alloca i128, align 8
+ %a3.addr = alloca i128, align 8
+ %a4 = alloca i128, align 8
+ %args = alloca ptr, align 8
+ store i128 %a1, ptr %a1.addr, align 8
+ store i128 %a2, ptr %a2.addr, align 8
+ store i128 %a3, ptr %a3.addr, align 8
+ call void @llvm.va_start.p0(ptr %args)
+ %argp.cur = load ptr, ptr %args, align 8
+ %argp.next = getelementptr inbounds i8, ptr %argp.cur, i64 16
+ store ptr %argp.next, ptr %args, align 8
+ %0 = load i128, ptr %argp.cur, align 8
+ store i128 %0, ptr %a4, align 8
+ call void @llvm.va_end.p0(ptr %args)
+ %1 = load i128, ptr %a1.addr, align 8
+ %2 = load i128, ptr %a2.addr, align 8
+ %add = add nsw i128 %1, %2
+ %3 = load i128, ptr %a3.addr, align 8
+ %add1 = add nsw i128 %add, %3
+ %4 = load i128, ptr %a4, align 8
+ %sub = sub nsw i128 %add1, %4
+ ret i128 %sub
+}
+
+; Check the call side for above functions.
+;
+define signext i32 @main() #0 {
+; CHECK-LABEL: main DS 0H
+; CHECK: stmg 6,8,1584(4)
+; CHECK-NEXT: L#stack_update3 DS 0H
+; CHECK-NEXT: aghi 4,-480
+; CHECK: L#end_of_prologue3 DS 0H
+; CHECK-NEXT: vgbm 0,0
+; CHECK-NEXT: vgbm 24,0
+; CHECK-NEXT: lgr 8,5
+; CHECK-NEXT: lg 6,8(5)
+; CHECK-NEXT: lg 5,0(5)
+; CHECK-NEXT: lghi 1,10
+; CHECK-NEXT: mvhi 2524(4),0
+; CHECK-NEXT: vst 0,2504(4),3
+; CHECK-NEXT: basr 7,6
+; CHECK-NEXT: bcr 0,0
+; CHECK-NEXT: vl 0,2504(4),3
+; CHECK-NEXT: vaq 24,0,24
+; CHECK-NEXT: vl 0,2376(4),3
+; CHECK-NEXT: vl 31,2392(4),3
+; CHECK-NEXT: vl 30,2408(4),3
+; CHECK-NEXT: vl 29,2424(4),3
+; CHECK-NEXT: vl 28,2440(4),3
+; CHECK-NEXT: vl 27,2456(4),3
+; CHECK-NEXT: vl 26,2472(4),3
+; CHECK-NEXT: vl 25,2488(4),3
+; CHECK-NEXT: lg 6,24(8)
+; CHECK-NEXT: lg 5,16(8)
+; CHECK-NEXT: vst 24,2504(4),3
+; CHECK-NEXT: vst 0,2304(4),3
+; CHECK-NEXT: basr 7,6
+; CHECK-NEXT: bcr 0,0
+; CHECK-NEXT: vl 0,2504(4),3
+; CHECK-NEXT: vaq 24,0,24
+; CHECK-NEXT: vl 0,2456(4),3
+; CHECK-NEXT: vl 26,2472(4),3
+; CHECK-NEXT: vl 25,2488(4),3
+; CHECK-NEXT: lg 6,40(8)
+; CHECK-NEXT: lg 5,32(8)
+; CHECK-NEXT: vst 24,2504(4),3
+; CHECK-NEXT: vst 0,2224(4),3
+; CHECK-NEXT: basr 7,6
+; CHECK-NEXT: bcr 0,0
+; CHECK-NEXT: vl 0,2504(4),3
+; CHECK-NEXT: vaq 0,0,24
+; CHECK-NEXT: vst 0,2504(4),3
+; CHECK-NEXT: lmg 7,8,2072(4)
+; CHECK-NEXT: lghi 3,0
+; CHECK-NEXT: aghi 4,480
+; CHECK-NEXT: b 2(7)
+entry:
+ %retval = alloca i32, align 4
+ %a1 = alloca i128, align 8
+ %a2 = alloca i128, align 8
+ %a3 = alloca i128, align 8
+ %s4 = alloca i128, align 8
+ %a5 = alloca i128, align 8
+ %a6 = alloca i128, align 8
+ %a7 = alloca i128, align 8
+ %a8 = alloca i128, align 8
+ %a9 = alloca i128, align 8
+ store i32 0, ptr %retval, align 4
+ store i128 0, ptr %a1, align 8
+ %0 = load i128, ptr %a1, align 8
+ %call = call i128 @pi_1i128(i32 signext 10, i128 %0)
+ %1 = load i128, ptr %a1, align 8
+ %add = add nsw i128 %1, %call
+ store i128 %add, ptr %a1, align 8
+ %2 = load i128, ptr %a1, align 8
+ %3 = load i128, ptr %a2, align 8
+ %4 = load i128, ptr %a3, align 8
+ %5 = load i128, ptr %s4, align 8
+ %6 = load i128, ptr %a5, align 8
+ %7 = load i128, ptr %a6, align 8
+ %8 = load i128, ptr %a7, align 8
+ %9 = load i128, ptr %a8, align 8
+ %10 = load i128, ptr %a9, align 8
+ %call1 = call i128 @p9i128(i128 %2, i128 %3, i128 %4, i128 %5, i128 %6, i128 %7, i128 %8, i128 %9, i128 %10)
+ %11 = load i128, ptr %a1, align 8
+ %add2 = add nsw i128 %11, %call1
+ store i128 %add2, ptr %a1, align 8
+ %12 = load i128, ptr %a1, align 8
+ %13 = load i128, ptr %a2, align 8
+ %14 = load i128, ptr %a3, align 8
+ %15 = load i128, ptr %s4, align 8
+ %call3 = call i128 (i128, i128, i128, ...) @p3i128_var(i128 %12, i128 %13, i128 %14, i128 %15)
+ %16 = load i128, ptr %a1, align 8
+ %add4 = add nsw i128 %16, %call3
+ store i128 %add4, ptr %a1, align 8
+ ret i32 0
+}
>From 5f857be0ee07b1126f4ec8324d313010ed42db61 Mon Sep 17 00:00:00 2001
From: Zibi Sarbinowski <zibi at ca.ibm.com>
Date: Wed, 16 Sep 2026 16:06:29 -0400
Subject: [PATCH 2/2] [SystemZ] Simplify i128 passing in XPLINK64 calling
conventions
Remove the CC_XPLINK_Int128 custom handler that mutated i128 LocVT to
v16i8, and instead add i128 directly to the CCIfType rules alongside
vectors in CC_SystemZ_XPLINK64 and RetCC_SystemZ_XPLINK64.
This removes the CCPassIndirect<i64> / CC_SystemZ_I128Indirect path for
i128 in the XPLINK64 argument calling convention and passes i128 in
vector registers V24-V31 (or stack) directly, consistent with how other
128-bit vector types are handled.
The LowerCall_XPLINK guard on lowerI128ToGR128 is updated to only fire
when the i128 is assigned to a GPR pair (R2Q), not when it is in a
VR128 vector register.
Note: removing CCPassIndirect for i128 changes the frame layout for
callers that also pass f64/f128/vector varargs, because the old
CCPassIndirect stack-temporary allocation was shifting the GPR
allocation state seen by subsequent arguments. Callers mixing i128
fixed args with f64/vector varargs may see different code generation
for the vararg passing compared to code compiled before this change.
---
llvm/lib/Target/SystemZ/SystemZCallingConv.h | 10 +-------
llvm/lib/Target/SystemZ/SystemZCallingConv.td | 23 +++++--------------
.../Target/SystemZ/SystemZISelLowering.cpp | 23 ++++++++-----------
3 files changed, 17 insertions(+), 39 deletions(-)
diff --git a/llvm/lib/Target/SystemZ/SystemZCallingConv.h b/llvm/lib/Target/SystemZ/SystemZCallingConv.h
index 91a1fe8f12d96..b9b8921d24817 100644
--- a/llvm/lib/Target/SystemZ/SystemZCallingConv.h
+++ b/llvm/lib/Target/SystemZ/SystemZCallingConv.h
@@ -96,21 +96,13 @@ inline bool CC_XPLINK64_Pointer(unsigned &ValNo, MVT &ValVT, MVT &LocVT,
return false;
}
-inline bool CC_XPLINK_Int128(unsigned &ValNo, MVT &ValVT, MVT &LocVT,
- CCValAssign::LocInfo &LocInfo,
- ISD::ArgFlagsTy &ArgFlags, CCState &State) {
- LocVT = MVT::v16i8;
- LocInfo = CCValAssign::BCvt;
- return false;
-}
-
inline bool CC_XPLINK64_Shadow_Reg(unsigned &ValNo, MVT &ValVT, MVT &LocVT,
CCValAssign::LocInfo &LocInfo,
ISD::ArgFlagsTy &ArgFlags, CCState &State) {
if (LocVT == MVT::f32 || LocVT == MVT::f64) {
State.AllocateReg(SystemZ::XPLINK64ArgGPRs);
}
- if (LocVT == MVT::f128 || LocVT.is128BitVector()) {
+ if (LocVT == MVT::f128 || LocVT.is128BitVector() || LocVT == MVT::i128) {
// Shadow next two GPRs, if available.
State.AllocateReg(SystemZ::XPLINK64ArgGPRs);
State.AllocateReg(SystemZ::XPLINK64ArgGPRs);
diff --git a/llvm/lib/Target/SystemZ/SystemZCallingConv.td b/llvm/lib/Target/SystemZ/SystemZCallingConv.td
index 7263587d54369..c823051ebc3a1 100644
--- a/llvm/lib/Target/SystemZ/SystemZCallingConv.td
+++ b/llvm/lib/Target/SystemZ/SystemZCallingConv.td
@@ -167,9 +167,6 @@ def CSR_SystemZ_XPLINK64_Vector : CalleeSavedRegs<(add CSR_SystemZ_XPLINK64,
// z/OS XPLINK64 return value calling convention
//===----------------------------------------------------------------------===//
def RetCC_SystemZ_XPLINK64 : CallingConv<[
- // Convert an i128 value to vector
- CCIfType<[i128], CCCustom<"CC_XPLINK_Int128">>,
-
// XPLINK64 ABI compliant code widens integral types smaller than i64
// to i64.
CCIfType<[i32], CCPromoteToType<i64>>,
@@ -190,10 +187,10 @@ def RetCC_SystemZ_XPLINK64 : CallingConv<[
// F4D and F6D, hence F4Q are used for complex long double types.
CCIfType<[f128], CCAssignToReg<[F0Q,F4Q]>>,
- // ABI compliant code returns vectors in VR24 but other registers
+ // ABI compliant code returns i128 and vectors in VR24 but other registers
// are provided for code that does not care about the ABI.
CCIfSubtarget<"hasVector()",
- CCIfType<[v16i8, v8i16, v4i32, v2i64, v4f32, v2f64],
+ CCIfType<[i128, v16i8, v8i16, v4i32, v2i64, v4f32, v2f64],
CCAssignToReg<[V24, V25, V26, V27, V28, V29, V30, V31]>>>
]>;
@@ -229,9 +226,6 @@ def CC_SystemZ_XPLINK64 : CallingConv<[
// long double, can only be passed in GPR2 and GPR3, if available,
// hence R2Q
- // Convert an i128 value to vector
- CCIfType<[i128], CCCustom<"CC_XPLINK_Int128">>,
-
CCIfType<[f128], CCIfArgVarArg<CCCustom<"CC_XPLINK64_Allocate128BitVararg">>>,
// Non fixed vector arguments are treated in the same way as long
// doubles.
@@ -245,11 +239,6 @@ def CC_SystemZ_XPLINK64 : CallingConv<[
// A SwiftError is passed in R0.
CCIfSwiftError<CCIfType<[i64], CCAssignToReg<[R0D]>>>,
- // Force i128 values to the stack and pass i64 pointers to them.
- CCIfType<[i128], CCPassIndirect<i64>>,
- // 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 R1-R3.
// The rest will be passed in the user area.
CCIfType<[i32], CCAssignToRegAndStack<[R1L, R2L, R3L], 8, 8>>,
@@ -259,10 +248,10 @@ def CC_SystemZ_XPLINK64 : CallingConv<[
// are passed in the same way, but they're widened to one of these types
// during type legalization.
CCIfSubtarget<"hasVector()",
- CCIfType<[v16i8, v8i16, v4i32, v2i64, v4f32, v2f64],
+ CCIfType<[i128, v16i8, v8i16, v4i32, v2i64, v4f32, v2f64],
CCIfArgFixed<CCCustom<"CC_XPLINK64_Shadow_Reg">>>>,
CCIfSubtarget<"hasVector()",
- CCIfType<[v16i8, v8i16, v4i32, v2i64, v4f32, v2f64],
+ CCIfType<[i128, v16i8, v8i16, v4i32, v2i64, v4f32, v2f64],
CCIfArgFixed<CCAssignToRegAndStack<[V24, V25, V26, V27,
V28, V29, V30, V31], 16, 8>>>>,
@@ -283,9 +272,9 @@ def CC_SystemZ_XPLINK64 : CallingConv<[
CCIfType<[i32, i64, f32, f64], CCAssignToStack<8, 8>>,
// Other f128 arguments are passed in 8-byte-aligned 16-byte stack slots.
CCIfType<[f128], CCAssignToStack<16, 8>>,
- // Vector arguments are passed in 8-byte-alinged 16-byte stack slots too.
+ // i128 and vector arguments are passed in 8-byte-aligned 16-byte stack slots.
CCIfSubtarget<"hasVector()",
- CCIfType<[v16i8, v8i16, v4i32, v2i64, v4f32, v2f64],
+ CCIfType<[i128, v16i8, v8i16, v4i32, v2i64, v4f32, v2f64],
CCAssignToStack<16, 8>>>
]>;
diff --git a/llvm/lib/Target/SystemZ/SystemZISelLowering.cpp b/llvm/lib/Target/SystemZ/SystemZISelLowering.cpp
index f93cbbfb557ca..04bb41b5b4f6c 100644
--- a/llvm/lib/Target/SystemZ/SystemZISelLowering.cpp
+++ b/llvm/lib/Target/SystemZ/SystemZISelLowering.cpp
@@ -1902,13 +1902,10 @@ static SDValue convertLocVTToValVT(SelectionDAG &DAG, const SDLoc &DL,
else if (VA.getLocInfo() == CCValAssign::BCvt) {
// If the argument is a short vector loaded from the stack,
// extend it from i64 to the full vector size and then perform a bitcast.
- // Alternatively, if the argument is an int128,
- // directly bitcast it into a vector of v16i8.
- assert(VA.getLocVT() == MVT::i64 || VA.getLocVT() == MVT::v16i8);
- assert(VA.getValVT().isVector() || VA.getValVT() == MVT::i128);
- if (VA.getLocVT() == MVT::i64)
- Value =
- DAG.getBuildVector(MVT::v2i64, DL, {Value, DAG.getUNDEF(MVT::i64)});
+ assert(VA.getLocVT() == MVT::i64);
+ assert(VA.getValVT().isVector());
+ Value =
+ DAG.getBuildVector(MVT::v2i64, DL, {Value, DAG.getUNDEF(MVT::i64)});
Value = DAG.getNode(ISD::BITCAST, DL, VA.getValVT(), Value);
} else
assert(VA.getLocInfo() == CCValAssign::Full && "Unsupported getLocInfo");
@@ -1931,8 +1928,7 @@ static SDValue convertValVTToLocVT(SelectionDAG &DAG, const SDLoc &DL,
assert(VA.getLocVT() == MVT::i64 || VA.getLocVT() == MVT::i128 ||
VA.getLocVT() == MVT::v16i8);
assert(VA.getValVT().isVector() || VA.getValVT() == MVT::f32 ||
- VA.getValVT() == MVT::f64 || VA.getValVT() == MVT::f128 ||
- VA.getValVT() == MVT::i128);
+ VA.getValVT() == MVT::f64 || VA.getValVT() == MVT::f128);
// For an f32 vararg we need to first promote it to an f64 and then
// bitcast it to an i64.
if (VA.getValVT() == MVT::f32 && VA.getLocVT() == MVT::i64)
@@ -2437,10 +2433,11 @@ SystemZTargetLowering::LowerCall(CallLoweringInfo &CLI,
ArgValue = convertValVTToLocVT(DAG, DL, VA, ArgValue);
if (VA.isRegLoc()) {
- // In XPLINK64, for the 128-bit vararg case, ArgValue is bitcasted to a
- // MVT::i128 type. We decompose the 128-bit type to a pair of its high
- // and low values.
- if (VA.getLocVT() == MVT::i128)
+ // i128 in a GR128 register pair (e.g. R2Q) must be decomposed into
+ // hi/lo GPR halves. i128 assigned to a VR128 vector register (V24-V31)
+ // is passed directly — do not decompose in that case.
+ if (VA.getLocVT() == MVT::i128 &&
+ !SystemZ::VR128BitRegClass.contains(VA.getLocReg()))
ArgValue = lowerI128ToGR128(DAG, ArgValue);
// Queue up the argument copies and emit them at the end.
RegsToPass.push_back(std::make_pair(VA.getLocReg(), ArgValue));
More information about the llvm-commits
mailing list