llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-clang Author: Zibi Sarbinowski (zibi2) <details> <summary>Changes</summary> This patch introduces full support for the XPLINK calling convention on z/OS within LLVM. XPLINK is documented in the Language Environment Vendor Interface ([Chapter 22](https://www.ibm.com/docs/en/zos/3.2.0?topic=applications-call-linkage-convention-amode-64)) and in the IBM Redbook [XPLink: OS/390 Extra Performance Linkage](http://www.redbooks.ibm.com/abstracts/sg245991.html?Open ). It defines a high‑performance linkage model used by the z/OS Language Environment (LE) and by the XL C/C++ compiler. This work succeeds and replaces the earlier effort in [PR 101024](https://github.com/llvm/llvm-project/pull/101024). The present implementation restructures the original approach, significantly expands test coverage, and aligns more closely with both the architectural requirements of XPLINK and the de‑facto ABI behavior of XL C/C++. --- Patch is 39.71 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/188501.diff 5 Files Affected: - (modified) clang/lib/CodeGen/CodeGenModule.cpp (+2) - (modified) clang/lib/CodeGen/TargetInfo.h (+4) - (modified) clang/lib/CodeGen/Targets/SystemZ.cpp (+410) - (added) clang/test/CodeGen/SystemZ/zos-abi.c (+414) - (added) clang/test/CodeGen/SystemZ/zos-abi.cpp (+96) ``````````diff diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp index daaa846bf42bc..bbc96b0b3916b 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -242,6 +242,8 @@ createTargetCodeGenInfo(CodeGenModule &CGM) { case llvm::Triple::systemz: { bool SoftFloat = CodeGenOpts.FloatABI == "soft"; bool HasVector = !SoftFloat && Target.getABI() == "vector"; + if (Triple.getOS() == llvm::Triple::ZOS) + return createSystemZ_ZOS_TargetCodeGenInfo(CGM, HasVector, SoftFloat); return createSystemZTargetCodeGenInfo(CGM, HasVector, SoftFloat); } diff --git a/clang/lib/CodeGen/TargetInfo.h b/clang/lib/CodeGen/TargetInfo.h index 98ee894fe557f..0be1cb225858c 100644 --- a/clang/lib/CodeGen/TargetInfo.h +++ b/clang/lib/CodeGen/TargetInfo.h @@ -572,6 +572,10 @@ std::unique_ptr<TargetCodeGenInfo> createSystemZTargetCodeGenInfo(CodeGenModule &CGM, bool HasVector, bool SoftFloatABI); +std::unique_ptr<TargetCodeGenInfo> +createSystemZ_ZOS_TargetCodeGenInfo(CodeGenModule &CGM, bool HasVector, + bool SoftFloatABI); + std::unique_ptr<TargetCodeGenInfo> createTCETargetCodeGenInfo(CodeGenModule &CGM); diff --git a/clang/lib/CodeGen/Targets/SystemZ.cpp b/clang/lib/CodeGen/Targets/SystemZ.cpp index e50f06c2f548c..879e11ea4108d 100644 --- a/clang/lib/CodeGen/Targets/SystemZ.cpp +++ b/clang/lib/CodeGen/Targets/SystemZ.cpp @@ -544,9 +544,419 @@ bool SystemZTargetCodeGenInfo::isVectorTypeBased(const Type *Ty, return false; } +//===----------------------------------------------------------------------===// +// z/OS XPLINK ABI Implementation +//===----------------------------------------------------------------------===// + +namespace { + +class ZOSXPLinkABIInfo : public ABIInfo { + const unsigned GPRBits = 64; + bool HasVector; + +public: + ZOSXPLinkABIInfo(CodeGenTypes &CGT, bool HV) + : ABIInfo(CGT), HasVector(HV) {} + + bool isPromotableIntegerType(QualType Ty) const; + bool isCompoundType(QualType Ty) const; + bool isVectorArgumentType(QualType Ty) const; + bool isFPArgumentType(QualType Ty) const; + QualType getSingleElementType(QualType Ty) const; + unsigned getMaxAlignFromTypeDefs(QualType Ty) const; + std::optional<QualType> getFPTypeOfComplexLikeType(QualType Ty) const; + + ABIArgInfo classifyReturnType(QualType RetTy, + unsigned functionCallConv) const; + ABIArgInfo classifyArgumentType(QualType ArgTy, bool IsNamedArg, + unsigned functionCallConv) const; + + void computeInfo(CGFunctionInfo &FI) const override { + if (!getCXXABI().classifyReturnType(FI)) + FI.getReturnInfo() = + classifyReturnType(FI.getReturnType(), FI.getCallingConvention()); + + unsigned NumRequiredArgs = FI.getNumRequiredArgs(); + unsigned ArgNo = 0; + + for (auto &I : FI.arguments()) { + bool IsNamedArg = ArgNo < NumRequiredArgs; + I.info = + classifyArgumentType(I.type, IsNamedArg, FI.getCallingConvention()); + ++ArgNo; + } + } + + RValue EmitVAArg(CodeGenFunction &CGF, Address VAListAddr, QualType Ty, + AggValueSlot Slot) const override; +}; + +class ZOSXPLinkTargetCodeGenInfo : public TargetCodeGenInfo { +public: + ZOSXPLinkTargetCodeGenInfo(CodeGenTypes &CGT, bool HasVector) + : TargetCodeGenInfo(std::make_unique<ZOSXPLinkABIInfo>(CGT, HasVector)) { + SwiftInfo = + std::make_unique<SwiftABIInfo>(CGT, /*SwiftErrorInRegister=*/false); + } +}; + +} // namespace + +// Return true if the ABI requires Ty to be passed sign- or zero- +// extended to 64 bits. +bool ZOSXPLinkABIInfo::isPromotableIntegerType(QualType Ty) const { + // Treat an enum type as its underlying type. + if (const EnumType *EnumTy = Ty->getAs<EnumType>()) + Ty = EnumTy->getDecl()->getIntegerType(); + + // Promotable integer types are required to be promoted by the ABI. + if (getContext().isPromotableIntegerType(Ty)) + 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. + if (const BuiltinType *BT = Ty->getAs<BuiltinType>()) + switch (BT->getKind()) { + case BuiltinType::Int: + case BuiltinType::UInt: + return true; + default: + break; + } + + return false; +} + +bool ZOSXPLinkABIInfo::isCompoundType(QualType Ty) const { + return (Ty->isAnyComplexType() || Ty->isVectorType() || + isAggregateTypeForABI(Ty)); +} + +bool ZOSXPLinkABIInfo::isVectorArgumentType(QualType Ty) const { + return (HasVector && Ty->isVectorType() && + getContext().getTypeSize(Ty) <= 128); +} + +bool ZOSXPLinkABIInfo::isFPArgumentType(QualType Ty) const { + if (const BuiltinType *BT = Ty->getAs<BuiltinType>()) + switch (BT->getKind()) { + case BuiltinType::Float: + case BuiltinType::Double: + case BuiltinType::LongDouble: + return true; + default: + return false; + } + + return false; +} + +QualType ZOSXPLinkABIInfo::getSingleElementType(QualType Ty) const { + // Unions just containing a floating point type, e.g. union { float f1, f2; }; + // are treated as a single floating point number. Check if the union only + // consists of a single type (handling embedded unions recursively), and + // return that type. + if (const RecordType *RT = Ty->getAsUnionType()) { + QualType Found; + // Check the fields. + const RecordDecl *RD = RT->getDecl(); + for (const auto *FD : RD->fields()) { + if (Found.isNull()) + Found = getSingleElementType(FD->getType()); + else if (Found != getSingleElementType(FD->getType())) + return Ty; + } + return Found.isNull() ? Ty : Found; + } + + if (const RecordType *RT = Ty->getAsStructureType()) { + const RecordDecl *RD = RT->getDecl(); + QualType Found; + + // If this is a C++ class/struct, inspect its base classes first. + if (const CXXRecordDecl *CXXRD = dyn_cast<CXXRecordDecl>(RD)) + for (const auto &I : CXXRD->bases()) { + QualType Base = I.getType(); + + // Skip empty base classes, they cannot contribute a data member. + if (isEmptyRecord(getContext(), Base, true)) + continue; + + // A candidate base type was already found; encountering another non‑empty + // base means the choice is no longer unique. Return the type from the + // first candidate. + if (!Found.isNull()) + return Ty; + Found = getSingleElementType(Base); + } + + // Now inspect the record's own fields. We allow at most one field to + // contribute a single element type. If we've already recorded one + // candidate, encountering another field immediately disqualifies the + // record from being a single element aggregate. + for (const auto *FD : RD->fields()) { + if (!Found.isNull()) + return Ty; // more than one field, not a single-element + Found = getSingleElementType(FD->getType()); + } + return Found.isNull() ? Ty : Found; + } + return Ty; // not record/union, unchanged +} + +unsigned ZOSXPLinkABIInfo::getMaxAlignFromTypeDefs(QualType Ty) const { + unsigned MaxAlign = 0; + clang::QualType Cur = Ty; + + while (true) { + if (auto *TypedefTy = dyn_cast<TypedefType>(Cur.getTypePtr())) { + auto *TyDecl = TypedefTy->getDecl(); + unsigned CurrAlign = TyDecl->getMaxAlignment(); + MaxAlign = std::max(CurrAlign, MaxAlign); + } + // Peel exactly one sugar layer (Typedef, Attributed, Paren, Elaborated, etc.). + clang::QualType Next = Cur.getSingleStepDesugaredType(getContext()); + if (Next == Cur) // no more sugar to peel + break; + Cur = Next; + } + return MaxAlign; +} + +std::optional<QualType> +ZOSXPLinkABIInfo::getFPTypeOfComplexLikeType(QualType Ty) const { + if (const RecordType *RT = Ty->getAsStructureType()) { + const RecordDecl *RD = RT->getDecl(); + + // Check for non-empty base classes. + if (const CXXRecordDecl *CXXRD = dyn_cast<CXXRecordDecl>(RD)) + if (CXXRD->hasDefinition()) + for (const auto &I : CXXRD->bases()) { + QualType Base = I.getType(); + if (!isEmptyRecord(getContext(), Base, true)) + return std::nullopt; + } + + // Check for exactly two elements with exactly the same floating point type. + // A single-element struct containing only a float, double, or long double + // counts as a field of that type. If the struct has one field consisting + // of a complex type, it does not count. This design may be somewhat + // inconsistent but it matches the behavior of the legacy C compiler. + int Count = 0; + clang::BuiltinType::Kind elemKind; + QualType RetTy; + for (const auto *FD : RD->fields()) { + if (Count >= 2) + return std::nullopt; + + unsigned MaxAlignOnDecl = FD->getMaxAlignment(); + QualType FT = FD->getType(); + QualType FTSingleTy = getSingleElementType(FT); + unsigned MaxAlign = + std::max(getMaxAlignFromTypeDefs(FTSingleTy), MaxAlignOnDecl); + + // The first element of a complex type may have an alignment enforced + // that is less strict than twice its size, since that would be naturally + // enforced by any complex type anyways. The second element may have an + // alignment enforced that is less strict than its size. + if (Count == 0) { + if (MaxAlign > 2 * getContext().getTypeSize(FTSingleTy)) + return std::nullopt; + } + else if (Count == 1) { + if (MaxAlign > getContext().getTypeSize(FTSingleTy)) + return std::nullopt; + } + + if (const BuiltinType *BT = FTSingleTy->getAs<BuiltinType>()) { + switch (BT->getKind()) { + case BuiltinType::Float: + case BuiltinType::Double: + case BuiltinType::LongDouble: + if (Count == 0) { + elemKind = BT->getKind(); + RetTy = FTSingleTy; + break; + } else if (elemKind == BT->getKind()) + break; + else + return std::nullopt; + default: + return std::nullopt; + } + } else + return std::nullopt; + + Count++; + fprintf(stderr, "Count increased to %d\n", Count); + } + + if (Count == 2) { + // The last thing that needs to be checked is the alignment of the struct. + // If we have to emit any padding (eg. because of attribute aligned), this + // disqualifies the type from being complex. + unsigned MaxAlign = RT->getDecl()->getMaxAlignment(); + unsigned ElemSize = getContext().getTypeSize(RetTy); + if (MaxAlign > 2 * ElemSize) + return std::nullopt; + return RetTy; + } + } + return std::nullopt; +} + +ABIArgInfo +ZOSXPLinkABIInfo::classifyReturnType(QualType RetTy, + unsigned CallConv) const { + + // Ignore void types. + if (RetTy->isVoidType()) + return ABIArgInfo::getIgnore(); + + // For non-C calling convention, indirect by value for structs and complex. + if ((CallConv != llvm::CallingConv::C) && + (isAggregateTypeForABI(RetTy) || RetTy->isAnyComplexType())) { + return getNaturalAlignIndirect(RetTy, getDataLayout().getAllocaAddrSpace()); + } + + // Vectors are returned directly. + if (isVectorArgumentType(RetTy)) + return ABIArgInfo::getDirect(); + + // Complex types are returned by value as per the XPLINK docs. + // Their members will be placed in FPRs. + if (RetTy->isAnyComplexType()) + return ABIArgInfo::getDirect(); + + // Complex LIKE structures are returned by value as per the XPLINK docs. + // Their members will be placed in FPRs. + if (RetTy->getAs<RecordType>()) { + if (getFPTypeOfComplexLikeType(RetTy)) + return ABIArgInfo::getDirect(); + } + + // Aggregates with a size of less than 3 GPRs are returned in GRPs 1, 2 and 3. + // Other aggregates are passed in memory as an implicit first parameter. + if (isAggregateTypeForABI(RetTy)) { + uint64_t AggregateTypeSize = getContext().getTypeSize(RetTy); + + if (AggregateTypeSize <= 3 * GPRBits) { + uint64_t NumElements = + AggregateTypeSize / GPRBits + (AggregateTypeSize % GPRBits != 0); + + // Types up to 8 bytes are passed as an integer type in GPR1. + // Types between 8 and 16 bytes are passed as integer types in GPR1, 2. + // Types between 16 and 24 bytes are passed as integer types in GPR1, 2 + // and 3. + llvm::Type *CoerceTy = llvm::IntegerType::get(getVMContext(), GPRBits); + CoerceTy = llvm::ArrayType::get(CoerceTy, NumElements); + return ABIArgInfo::getDirectInReg(CoerceTy); + } else + return getNaturalAlignIndirect(RetTy, getDataLayout().getAllocaAddrSpace()); + } + + // Treat an enum type as its underlying type. + if (const EnumType *EnumTy = RetTy->getAs<EnumType>()) + RetTy = EnumTy->getDecl()->getIntegerType(); + + return (isPromotableIntegerType(RetTy) ? ABIArgInfo::getExtend(RetTy) + : ABIArgInfo::getDirect()); +} + +ABIArgInfo ZOSXPLinkABIInfo::classifyArgumentType(QualType Ty, bool IsNamedArg, + unsigned CallConv) const { + + // Handle the generic C++ ABI. + if (CGCXXABI::RecordArgABI RAA = getRecordArgABI(Ty, getCXXABI())) + return getNaturalAlignIndirect(Ty, getDataLayout().getAllocaAddrSpace(), RAA == CGCXXABI::RAA_DirectInMemory); + + // Integers and enums are extended to full register width. + if (isPromotableIntegerType(Ty)) + return ABIArgInfo::getExtend(Ty); + + // For non-C calling conventions, compound types passed by address copy. + if ((CallConv != llvm::CallingConv::C) && isCompoundType(Ty)) + return getNaturalAlignIndirect(Ty, getDataLayout().getAllocaAddrSpace(), + /*ByVal=*/false); + + // Complex types are passed by value as per the XPLINK docs. + // If place available, their members will be placed in FPRs. + auto CompTy = getFPTypeOfComplexLikeType(Ty); + if (IsNamedArg) { + if (Ty->isComplexType()) { + auto AI = ABIArgInfo::getDirectInReg(CGT.ConvertType(Ty)); + AI.setCanBeFlattened(false); + return AI; + } + + if (CompTy.has_value()) { + llvm::Type *FPTy = CGT.ConvertType(*CompTy); + llvm::Type *CoerceTy = llvm::StructType::get(FPTy, FPTy); + auto AI = ABIArgInfo::getDirectInReg(CoerceTy); + AI.setCanBeFlattened(false); + return AI; + } + } + + // Vectors are passed directly. + if (isVectorArgumentType(Ty)) + return ABIArgInfo::getDirect(); + + // Handle structures. They are returned by value. + // If not complex like types, they are passed in GPRs, if possible. + // If place available, complex like types will have their members + // placed in FPRs. + if (Ty->getAs<RecordType>() || Ty->isAnyComplexType() || CompTy.has_value()) { + if (isAggregateTypeForABI(Ty) || Ty->isAnyComplexType() || CompTy.has_value()) { + // Since an aggregate may end up in registers, pass the aggregate as + // array. This is usually beneficial since we avoid forcing the back-end + // to store the argument to memory. + uint64_t Bits = getContext().getTypeSize(Ty); + llvm::Type *CoerceTy; + + // Struct types up to 8 bytes are passed as integer type (which will be + // properly aligned in the argument save area doubleword). + if (Bits <= GPRBits) + CoerceTy = llvm::IntegerType::get(getVMContext(), GPRBits); + // Larger types are passed as arrays, with the base type selected + // according to the required alignment in the save area. + else { + uint64_t NumRegs = llvm::alignTo(Bits, GPRBits) / GPRBits; + llvm::Type *RegTy = llvm::IntegerType::get(getVMContext(), GPRBits); + CoerceTy = llvm::ArrayType::get(RegTy, NumRegs); + } + + return ABIArgInfo::getDirectInReg(CoerceTy); + } + + return ABIArgInfo::getDirectInReg(); + } + + // Non-structure compounds are passed indirectly, i.e. arrays. + if (isCompoundType(Ty)) + return getNaturalAlignIndirect(Ty, getDataLayout().getAllocaAddrSpace(), /*ByVal=*/false); + + return ABIArgInfo::getDirect(); +} + +RValue ZOSXPLinkABIInfo::EmitVAArg(CodeGenFunction &CGF, Address VAListAddr, + QualType Ty, AggValueSlot Slot) const { + return emitVoidPtrVAArg(CGF, VAListAddr, Ty, /*indirect*/ false, + CGF.getContext().getTypeInfoInChars(Ty), + CGF.getPointerSize(), + /*allowHigherAlign*/ false, Slot); +} + std::unique_ptr<TargetCodeGenInfo> CodeGen::createSystemZTargetCodeGenInfo(CodeGenModule &CGM, bool HasVector, bool SoftFloatABI) { return std::make_unique<SystemZTargetCodeGenInfo>(CGM.getTypes(), HasVector, SoftFloatABI); } + +std::unique_ptr<TargetCodeGenInfo> +CodeGen::createSystemZ_ZOS_TargetCodeGenInfo(CodeGenModule &CGM, bool HasVector, + bool SoftFloatABI) { + return std::make_unique<ZOSXPLinkTargetCodeGenInfo>(CGM.getTypes(), + HasVector); +} diff --git a/clang/test/CodeGen/SystemZ/zos-abi.c b/clang/test/CodeGen/SystemZ/zos-abi.c new file mode 100644 index 0000000000000..999b8f07814c1 --- /dev/null +++ b/clang/test/CodeGen/SystemZ/zos-abi.c @@ -0,0 +1,414 @@ +// RUN: %clang_cc1 -triple s390x-ibm-zos \ +// RUN: -emit-llvm -no-enable-noundef-analysis -o - %s | FileCheck %s --check-prefixes=CHECK,CHECKNOVEC +// RUN: %clang_cc1 -triple s390x-ibm-zos -target-feature +vector \ +// RUN: -emit-llvm -no-enable-noundef-analysis -o - %s | FileCheck %s --check-prefixes=CHECK,CHECKVEC +// RUN: %clang_cc1 -triple s390x-ibm-zos -target-cpu z13 \ +// RUN: -emit-llvm -no-enable-noundef-analysis -o - %s | FileCheck %s --check-prefixes=CHECK,CHECKVEC +// RUN: %clang_cc1 -triple s390x-ibm-zos -target-cpu arch11 \ +// RUN: -emit-llvm -no-enable-noundef-analysis -o - %s | FileCheck %s --check-prefixes=CHECK,CHECKVEC +// RUN: %clang_cc1 -triple s390x-ibm-zos -target-cpu z14 \ +// RUN: -emit-llvm -no-enable-noundef-analysis -o - %s | FileCheck %s --check-prefixes=CHECK,CHECKVEC +// RUN: %clang_cc1 -triple s390x-ibm-zos -target-cpu arch12 \ +// RUN: -emit-llvm -no-enable-noundef-analysis -o - %s | FileCheck %s --check-prefixes=CHECK,CHECKVEC +// RUN: %clang_cc1 -triple s390x-ibm-zos -target-cpu z15 \ +// RUN: -emit-llvm -no-enable-noundef-analysis -o - %s | FileCheck %s --check-prefixes=CHECK,CHECKVEC +// RUN: %clang_cc1 -triple s390x-ibm-zos -target-cpu arch13 \ +// RUN: -emit-llvm -no-enable-noundef-analysis -o - %s | FileCheck %s --check-prefixes=CHECK,CHECKVEC + +// RUN: %clang_cc1 -triple s390x-ibm-zos -target-cpu arch11 \ +// RUN: -DTEST_VEC -fzvector -emit-llvm -no-enable-noundef-analysis \ +// RUN: -o - %s | FileCheck %s --check-prefixes=CHECK,CHECKVEC,CHECK-ZVEC + +// Scalar types + +signed char pass_schar(signed char arg) { return arg; } +// CHECK-LABEL: define signext i8 @pass_schar(i8 signext %{{.*}}) + +unsigned char pass_uchar(unsigned char arg) { return arg; } +// CHECK-LABEL: define zeroext i8 @pass_uchar(i8 zeroext %{{.*}}) + +short pass_short(short arg) { return arg; } +// CHECK-LABEL: define signext i16 @pass_short(i16 signext %{{.*}}) + +int pass_int(int arg) { return arg; } +// CHECK-LABEL: define signext i32 @pass_int(i32 signext %{{.*}}) + +long pass_long(long arg) { return arg; } +// CHECK-LABEL: define i64 @pass_long(i64 %{{.*}}) + +long long pass_longlong(long long arg) { return arg; } +// CHECK-LABEL: define i64 @pass_longlong(i64 %{{.*}}) + +float pass_float(float arg) { return arg; } +// CHECK-LABEL: define float @pass_float(float %{{.*}}) + +double pass_double(double arg) { return arg; } +// CHECK-LABEL: define double @pass_double(double %{{.*}}) + +long double pass_longdouble(long double arg) { return arg; } +// CHECK-LABEL: define fp128 @pass_longdouble(fp128 %{{.*}}) + +enum Color { Red, Blue }; +enum Color pass_enum(enum Color arg) { return arg; } +// CHECK-LABEL: define zeroext i32 @pass_enum(i32 zeroext %{{.*}}) + +#ifdef TEST_VEC +vector unsigned int pass_vector(vector unsigned int arg) { return arg; }; +// C... [truncated] `````````` </details> https://github.com/llvm/llvm-project/pull/188501 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
