Author: Jonas Paulsson Date: 2024-09-19T16:59:31+02:00 New Revision: 14120227a34365e829d05c1413033d235d7d272c
URL: https://github.com/llvm/llvm-project/commit/14120227a34365e829d05c1413033d235d7d272c DIFF: https://github.com/llvm/llvm-project/commit/14120227a34365e829d05c1413033d235d7d272c.diff LOG: Target ABI: improve call parameters extensions handling (#100757) For the purpose of verifying proper arguments extensions per the target's ABI, introduce the NoExt attribute that may be used by a target when neither sign- or zeroextension is required (e.g. with a struct in register). The purpose of doing so is to be able to verify that there is always one of these attributes present and by this detecting cases where sign/zero extension is actually missing. As a first step, this patch has the verification step done for the SystemZ backend only, but left off by default until all known issues have been addressed. Other targets/front-ends can now also add NoExt attribute where needed and do this check in the backend. Added: llvm/test/CodeGen/SystemZ/args-14.ll llvm/test/CodeGen/SystemZ/args-15.ll llvm/test/CodeGen/SystemZ/args-16.ll llvm/test/CodeGen/SystemZ/args-17.ll llvm/test/CodeGen/SystemZ/args-18.ll llvm/test/CodeGen/SystemZ/args-19.ll llvm/test/CodeGen/SystemZ/args-20.ll llvm/test/CodeGen/SystemZ/args-21.ll Modified: clang/include/clang/CodeGen/CGFunctionInfo.h clang/lib/CodeGen/CGCall.cpp clang/lib/CodeGen/Targets/SystemZ.cpp clang/test/CodeGen/SystemZ/systemz-abi-vector.c clang/test/CodeGen/SystemZ/systemz-abi.c clang/test/CodeGen/SystemZ/systemz-abi.cpp llvm/docs/LangRef.rst llvm/include/llvm/Bitcode/LLVMBitCodes.h llvm/include/llvm/CodeGen/TargetCallingConv.h llvm/include/llvm/CodeGen/TargetLowering.h llvm/include/llvm/IR/Attributes.td llvm/include/llvm/Target/TargetOptions.h llvm/lib/Bitcode/Reader/BitcodeReader.cpp llvm/lib/Bitcode/Writer/BitcodeWriter.cpp llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp llvm/lib/CodeGen/SelectionDAG/TargetLowering.cpp llvm/lib/Target/SystemZ/SystemZISelLowering.cpp llvm/lib/Target/SystemZ/SystemZISelLowering.h llvm/lib/Transforms/Utils/CodeExtractor.cpp llvm/tools/llc/llc.cpp Removed: ################################################################################ diff --git a/clang/include/clang/CodeGen/CGFunctionInfo.h b/clang/include/clang/CodeGen/CGFunctionInfo.h index 811f33407368c6..d19f84d198876f 100644 --- a/clang/include/clang/CodeGen/CGFunctionInfo.h +++ b/clang/include/clang/CodeGen/CGFunctionInfo.h @@ -116,6 +116,7 @@ class ABIArgInfo { bool InReg : 1; // isDirect() || isExtend() || isIndirect() bool CanBeFlattened: 1; // isDirect() bool SignExt : 1; // isExtend() + bool ZeroExt : 1; // isExtend() bool canHavePaddingType() const { return isDirect() || isExtend() || isIndirect() || isIndirectAliased() || @@ -137,7 +138,7 @@ class ABIArgInfo { PaddingInReg(false), InAllocaSRet(false), InAllocaIndirect(false), IndirectByVal(false), IndirectRealign(false), SRetAfterThis(false), InReg(false), CanBeFlattened(false), - SignExt(false) {} + SignExt(false), ZeroExt(false) {} static ABIArgInfo getDirect(llvm::Type *T = nullptr, unsigned Offset = 0, llvm::Type *Padding = nullptr, @@ -174,12 +175,12 @@ class ABIArgInfo { AI.setPaddingType(nullptr); AI.setDirectOffset(0); AI.setDirectAlign(0); - AI.setSignExt(false); + AI.setZeroExt(true); return AI; } // ABIArgInfo will record the argument as being extended based on the sign - // of its type. + // of its type. Produces a sign or zero extension. static ABIArgInfo getExtend(QualType Ty, llvm::Type *T = nullptr) { assert(Ty->isIntegralOrEnumerationType() && "Unexpected QualType"); if (Ty->hasSignedIntegerRepresentation()) @@ -187,6 +188,16 @@ class ABIArgInfo { return getZeroExtend(Ty, T); } + // Struct in register marked explicitly as not needing extension. + static ABIArgInfo getNoExtend(llvm::IntegerType *T) { + auto AI = ABIArgInfo(Extend); + AI.setCoerceToType(T); + AI.setPaddingType(nullptr); + AI.setDirectOffset(0); + AI.setDirectAlign(0); + return AI; + } + static ABIArgInfo getExtendInReg(QualType Ty, llvm::Type *T = nullptr) { auto AI = getExtend(Ty, T); AI.setInReg(true); @@ -326,7 +337,7 @@ class ABIArgInfo { } bool isSignExt() const { - assert(isExtend() && "Invalid kind!"); + assert(isExtend() && (SignExt + ZeroExt <= 1) && "Invalid kind / flags!"); return SignExt; } void setSignExt(bool SExt) { @@ -334,6 +345,20 @@ class ABIArgInfo { SignExt = SExt; } + bool isZeroExt() const { + assert(isExtend() && (SignExt + ZeroExt <= 1) && "Invalid kind / flags!"); + return ZeroExt; + } + void setZeroExt(bool ZExt) { + assert(isExtend() && "Invalid kind!"); + ZeroExt = ZExt; + } + + bool isNoExt() const { + assert(isExtend() && (SignExt + ZeroExt <= 1) && "Invalid kind / flags!"); + return !SignExt && !ZeroExt; + } + llvm::Type *getPaddingType() const { return (canHavePaddingType() ? PaddingType : nullptr); } diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp index 997510d71c3efb..4ae981e4013e9c 100644 --- a/clang/lib/CodeGen/CGCall.cpp +++ b/clang/lib/CodeGen/CGCall.cpp @@ -2182,7 +2182,7 @@ static bool DetermineNoUndef(QualType QTy, CodeGenTypes &Types, if (AI.getKind() == ABIArgInfo::Indirect || AI.getKind() == ABIArgInfo::IndirectAliased) return true; - if (AI.getKind() == ABIArgInfo::Extend) + if (AI.getKind() == ABIArgInfo::Extend && !AI.isNoExt()) return true; if (!DL.typeSizeEqualsStoreSize(Ty)) // TODO: This will result in a modest amount of values not marked noundef @@ -2567,8 +2567,10 @@ void CodeGenModule::ConstructAttributeList(StringRef Name, case ABIArgInfo::Extend: if (RetAI.isSignExt()) RetAttrs.addAttribute(llvm::Attribute::SExt); - else + else if (RetAI.isZeroExt()) RetAttrs.addAttribute(llvm::Attribute::ZExt); + else + RetAttrs.addAttribute(llvm::Attribute::NoExt); [[fallthrough]]; case ABIArgInfo::Direct: if (RetAI.getInReg()) @@ -2708,8 +2710,10 @@ void CodeGenModule::ConstructAttributeList(StringRef Name, case ABIArgInfo::Extend: if (AI.isSignExt()) Attrs.addAttribute(llvm::Attribute::SExt); - else + else if (AI.isZeroExt()) Attrs.addAttribute(llvm::Attribute::ZExt); + else + Attrs.addAttribute(llvm::Attribute::NoExt); [[fallthrough]]; case ABIArgInfo::Direct: if (ArgNo == 0 && FI.isChainCall()) diff --git a/clang/lib/CodeGen/Targets/SystemZ.cpp b/clang/lib/CodeGen/Targets/SystemZ.cpp index 4d61f513793463..56129622f48dbd 100644 --- a/clang/lib/CodeGen/Targets/SystemZ.cpp +++ b/clang/lib/CodeGen/Targets/SystemZ.cpp @@ -445,16 +445,16 @@ ABIArgInfo SystemZABIInfo::classifyArgumentType(QualType Ty) const { return getNaturalAlignIndirect(Ty, /*ByVal=*/false); // The structure is passed as an unextended integer, a float, or a double. - llvm::Type *PassTy; if (isFPArgumentType(SingleElementTy)) { assert(Size == 32 || Size == 64); - if (Size == 32) - PassTy = llvm::Type::getFloatTy(getVMContext()); - else - PassTy = llvm::Type::getDoubleTy(getVMContext()); - } else - PassTy = llvm::IntegerType::get(getVMContext(), Size); - return ABIArgInfo::getDirect(PassTy); + return ABIArgInfo::getDirect( + Size == 32 ? llvm::Type::getFloatTy(getVMContext()) + : llvm::Type::getDoubleTy(getVMContext())); + } else { + llvm::IntegerType *PassTy = llvm::IntegerType::get(getVMContext(), Size); + return Size <= 32 ? ABIArgInfo::getNoExtend(PassTy) + : ABIArgInfo::getDirect(PassTy); + } } // Non-structure compounds are passed indirectly. diff --git a/clang/test/CodeGen/SystemZ/systemz-abi-vector.c b/clang/test/CodeGen/SystemZ/systemz-abi-vector.c index 23f4996723f826..8361ccef21022d 100644 --- a/clang/test/CodeGen/SystemZ/systemz-abi-vector.c +++ b/clang/test/CodeGen/SystemZ/systemz-abi-vector.c @@ -146,17 +146,17 @@ v1f128 pass_v1f128(v1f128 arg) { return arg; } struct agg_v1i8 { v1i8 a; }; struct agg_v1i8 pass_agg_v1i8(struct agg_v1i8 arg) { return arg; } -// CHECK-LABEL: define{{.*}} void @pass_agg_v1i8(ptr dead_on_unwind noalias writable sret(%struct.agg_v1i8) align 1 %{{.*}}, i8 %{{.*}}) +// CHECK-LABEL: define{{.*}} void @pass_agg_v1i8(ptr dead_on_unwind noalias writable sret(%struct.agg_v1i8) align 1 %{{.*}}, i8 noext %{{.*}}) // CHECK-VECTOR-LABEL: define{{.*}} void @pass_agg_v1i8(ptr dead_on_unwind noalias writable sret(%struct.agg_v1i8) align 1 %{{.*}}, <1 x i8> %{{.*}}) struct agg_v2i8 { v2i8 a; }; struct agg_v2i8 pass_agg_v2i8(struct agg_v2i8 arg) { return arg; } -// CHECK-LABEL: define{{.*}} void @pass_agg_v2i8(ptr dead_on_unwind noalias writable sret(%struct.agg_v2i8) align 2 %{{.*}}, i16 %{{.*}}) +// CHECK-LABEL: define{{.*}} void @pass_agg_v2i8(ptr dead_on_unwind noalias writable sret(%struct.agg_v2i8) align 2 %{{.*}}, i16 noext %{{.*}}) // CHECK-VECTOR-LABEL: define{{.*}} void @pass_agg_v2i8(ptr dead_on_unwind noalias writable sret(%struct.agg_v2i8) align 2 %{{.*}}, <2 x i8> %{{.*}}) struct agg_v4i8 { v4i8 a; }; struct agg_v4i8 pass_agg_v4i8(struct agg_v4i8 arg) { return arg; } -// CHECK-LABEL: define{{.*}} void @pass_agg_v4i8(ptr dead_on_unwind noalias writable sret(%struct.agg_v4i8) align 4 %{{.*}}, i32 %{{.*}}) +// CHECK-LABEL: define{{.*}} void @pass_agg_v4i8(ptr dead_on_unwind noalias writable sret(%struct.agg_v4i8) align 4 %{{.*}}, i32 noext %{{.*}}) // CHECK-VECTOR-LABEL: define{{.*}} void @pass_agg_v4i8(ptr dead_on_unwind noalias writable sret(%struct.agg_v4i8) align 4 %{{.*}}, <4 x i8> %{{.*}}) struct agg_v8i8 { v8i8 a; }; @@ -189,8 +189,8 @@ struct agg_novector2 pass_agg_novector2(struct agg_novector2 arg) { return arg; struct agg_novector3 { v4i8 a; int : 0; }; struct agg_novector3 pass_agg_novector3(struct agg_novector3 arg) { return arg; } -// CHECK-LABEL: define{{.*}} void @pass_agg_novector3(ptr dead_on_unwind noalias writable sret(%struct.agg_novector3) align 4 %{{.*}}, i32 %{{.*}}) -// CHECK-VECTOR-LABEL: define{{.*}} void @pass_agg_novector3(ptr dead_on_unwind noalias writable sret(%struct.agg_novector3) align 4 %{{.*}}, i32 %{{.*}}) +// CHECK-LABEL: define{{.*}} void @pass_agg_novector3(ptr dead_on_unwind noalias writable sret(%struct.agg_novector3) align 4 %{{.*}}, i32 noext %{{.*}}) +// CHECK-VECTOR-LABEL: define{{.*}} void @pass_agg_novector3(ptr dead_on_unwind noalias writable sret(%struct.agg_novector3) align 4 %{{.*}}, i32 noext %{{.*}}) struct agg_novector4 { v4i8 a __attribute__((aligned (8))); }; struct agg_novector4 pass_agg_novector4(struct agg_novector4 arg) { return arg; } diff --git a/clang/test/CodeGen/SystemZ/systemz-abi.c b/clang/test/CodeGen/SystemZ/systemz-abi.c index 3526772008d382..fd2b5d450cc643 100644 --- a/clang/test/CodeGen/SystemZ/systemz-abi.c +++ b/clang/test/CodeGen/SystemZ/systemz-abi.c @@ -86,11 +86,11 @@ _Complex long double pass_complex_longdouble(_Complex long double arg) { return struct agg_1byte { char a[1]; }; struct agg_1byte pass_agg_1byte(struct agg_1byte arg) { return arg; } -// CHECK-LABEL: define{{.*}} void @pass_agg_1byte(ptr dead_on_unwind noalias writable sret(%struct.agg_1byte) align 1 %{{.*}}, i8 %{{.*}}) +// CHECK-LABEL: define{{.*}} void @pass_agg_1byte(ptr dead_on_unwind noalias writable sret(%struct.agg_1byte) align 1 %{{.*}}, i8 noext %{{.*}}) struct agg_2byte { char a[2]; }; struct agg_2byte pass_agg_2byte(struct agg_2byte arg) { return arg; } -// CHECK-LABEL: define{{.*}} void @pass_agg_2byte(ptr dead_on_unwind noalias writable sret(%struct.agg_2byte) align 1 %{{.*}}, i16 %{{.*}}) +// CHECK-LABEL: define{{.*}} void @pass_agg_2byte(ptr dead_on_unwind noalias writable sret(%struct.agg_2byte) align 1 %{{.*}}, i16 noext %{{.*}}) struct agg_3byte { char a[3]; }; struct agg_3byte pass_agg_3byte(struct agg_3byte arg) { return arg; } @@ -98,7 +98,7 @@ struct agg_3byte pass_agg_3byte(struct agg_3byte arg) { return arg; } struct agg_4byte { char a[4]; }; struct agg_4byte pass_agg_4byte(struct agg_4byte arg) { return arg; } -// CHECK-LABEL: define{{.*}} void @pass_agg_4byte(ptr dead_on_unwind noalias writable sret(%struct.agg_4byte) align 1 %{{.*}}, i32 %{{.*}}) +// CHECK-LABEL: define{{.*}} void @pass_agg_4byte(ptr dead_on_unwind noalias writable sret(%struct.agg_4byte) align 1 %{{.*}}, i32 noext %{{.*}}) struct agg_5byte { char a[5]; }; struct agg_5byte pass_agg_5byte(struct agg_5byte arg) { return arg; } @@ -126,7 +126,7 @@ struct agg_16byte pass_agg_16byte(struct agg_16byte arg) { return arg; } struct agg_float { float a; }; struct agg_float pass_agg_float(struct agg_float arg) { return arg; } // HARD-FLOAT-LABEL: define{{.*}} void @pass_agg_float(ptr dead_on_unwind noalias writable sret(%struct.agg_float) align 4 %{{.*}}, float %{{.*}}) -// SOFT-FLOAT-LABEL: define{{.*}} void @pass_agg_float(ptr dead_on_unwind noalias writable sret(%struct.agg_float) align 4 %{{.*}}, i32 %{{.*}}) +// SOFT-FLOAT-LABEL: define{{.*}} void @pass_agg_float(ptr dead_on_unwind noalias writable sret(%struct.agg_float) align 4 %{{.*}}, i32 noext %{{.*}}) struct agg_double { double a; }; struct agg_double pass_agg_double(struct agg_double arg) { return arg; } @@ -159,14 +159,14 @@ struct agg_nofloat2 pass_agg_nofloat2(struct agg_nofloat2 arg) { return arg; } struct agg_nofloat3 { float a; int : 0; }; struct agg_nofloat3 pass_agg_nofloat3(struct agg_nofloat3 arg) { return arg; } -// CHECK-LABEL: define{{.*}} void @pass_agg_nofloat3(ptr dead_on_unwind noalias writable sret(%struct.agg_nofloat3) align 4 %{{.*}}, i32 %{{.*}}) +// CHECK-LABEL: define{{.*}} void @pass_agg_nofloat3(ptr dead_on_unwind noalias writable sret(%struct.agg_nofloat3) align 4 %{{.*}}, i32 noext %{{.*}}) // Union types likewise are *not* float-like aggregate types union union_float { float a; }; union union_float pass_union_float(union union_float arg) { return arg; } -// CHECK-LABEL: define{{.*}} void @pass_union_float(ptr dead_on_unwind noalias writable sret(%union.union_float) align 4 %{{.*}}, i32 %{{.*}}) +// CHECK-LABEL: define{{.*}} void @pass_union_float(ptr dead_on_unwind noalias writable sret(%union.union_float) align 4 %{{.*}}, i32 noext %{{.*}}) union union_double { double a; }; union union_double pass_union_double(union union_double arg) { return arg; } diff --git a/clang/test/CodeGen/SystemZ/systemz-abi.cpp b/clang/test/CodeGen/SystemZ/systemz-abi.cpp index 06be85421ba17a..b13aedfca464b7 100644 --- a/clang/test/CodeGen/SystemZ/systemz-abi.cpp +++ b/clang/test/CodeGen/SystemZ/systemz-abi.cpp @@ -7,7 +7,7 @@ class agg_float_class { float a; }; class agg_float_class pass_agg_float_class(class agg_float_class arg) { return arg; } // CHECK-LABEL: define{{.*}} void @_Z20pass_agg_float_class15agg_float_class(ptr dead_on_unwind noalias writable sret(%class.agg_float_class) align 4 %{{.*}}, float %{{.*}}) -// SOFT-FLOAT-LABEL: define{{.*}} void @_Z20pass_agg_float_class15agg_float_class(ptr dead_on_unwind noalias writable sret(%class.agg_float_class) align 4 %{{.*}}, i32 %{{.*}}) +// SOFT-FLOAT-LABEL: define{{.*}} void @_Z20pass_agg_float_class15agg_float_class(ptr dead_on_unwind noalias writable sret(%class.agg_float_class) align 4 %{{.*}}, i32 noext %{{.*}}) class agg_double_class { double a; }; class agg_double_class pass_agg_double_class(class agg_double_class arg) { return arg; } @@ -18,8 +18,8 @@ class agg_double_class pass_agg_double_class(class agg_double_class arg) { retur // This structure is passed in a GPR in C++ (and C, checked in systemz-abi.c). struct agg_float_cpp { float a; int : 0; }; struct agg_float_cpp pass_agg_float_cpp(struct agg_float_cpp arg) { return arg; } -// CHECK-LABEL: define{{.*}} void @_Z18pass_agg_float_cpp13agg_float_cpp(ptr dead_on_unwind noalias writable sret(%struct.agg_float_cpp) align 4 %{{.*}}, i32 %{{.*}}) -// SOFT-FLOAT-LABEL: define{{.*}} void @_Z18pass_agg_float_cpp13agg_float_cpp(ptr dead_on_unwind noalias writable sret(%struct.agg_float_cpp) align 4 %{{.*}}, i32 %{{.*}}) +// CHECK-LABEL: define{{.*}} void @_Z18pass_agg_float_cpp13agg_float_cpp(ptr dead_on_unwind noalias writable sret(%struct.agg_float_cpp) align 4 %{{.*}}, i32 noext %{{.*}}) +// SOFT-FLOAT-LABEL: define{{.*}} void @_Z18pass_agg_float_cpp13agg_float_cpp(ptr dead_on_unwind noalias writable sret(%struct.agg_float_cpp) align 4 %{{.*}}, i32 noext %{{.*}}) // A field member of empty class type in C++ makes the record nonhomogeneous, @@ -32,7 +32,7 @@ struct agg_nofloat_empty pass_agg_nofloat_empty(struct agg_nofloat_empty arg) { struct agg_float_empty { float a; [[no_unique_address]] empty dummy; }; struct agg_float_empty pass_agg_float_empty(struct agg_float_empty arg) { return arg; } // CHECK-LABEL: define{{.*}} void @_Z20pass_agg_float_empty15agg_float_empty(ptr dead_on_unwind noalias writable sret(%struct.agg_float_empty) align 4 %{{.*}}, float %{{.*}}) -// SOFT-FLOAT-LABEL: define{{.*}} void @_Z20pass_agg_float_empty15agg_float_empty(ptr dead_on_unwind noalias writable sret(%struct.agg_float_empty) align 4 %{{.*}}, i32 %{{.*}}) +// SOFT-FLOAT-LABEL: define{{.*}} void @_Z20pass_agg_float_empty15agg_float_empty(ptr dead_on_unwind noalias writable sret(%struct.agg_float_empty) align 4 %{{.*}}, i32 noext %{{.*}}) struct agg_nofloat_emptyarray { float a; [[no_unique_address]] empty dummy[3]; }; struct agg_nofloat_emptyarray pass_agg_nofloat_emptyarray(struct agg_nofloat_emptyarray arg) { return arg; } // CHECK-LABEL: define{{.*}} void @_Z27pass_agg_nofloat_emptyarray22agg_nofloat_emptyarray(ptr dead_on_unwind noalias writable sret(%struct.agg_nofloat_emptyarray) align 4 %{{.*}}, i64 %{{.*}}) @@ -48,7 +48,7 @@ struct emptybase { [[no_unique_address]] empty dummy; }; struct agg_float_emptybase : emptybase { float a; }; struct agg_float_emptybase pass_agg_float_emptybase(struct agg_float_emptybase arg) { return arg; } // CHECK-LABEL: define{{.*}} void @_Z24pass_agg_float_emptybase19agg_float_emptybase(ptr dead_on_unwind noalias writable sret(%struct.agg_float_emptybase) align 4 %{{.*}}, float %{{.*}}) -// SOFT-FLOAT-LABEL: define{{.*}} void @_Z24pass_agg_float_emptybase19agg_float_emptybase(ptr dead_on_unwind noalias writable sret(%struct.agg_float_emptybase) align 4 %{{.*}}, i32 %{{.*}}) +// SOFT-FLOAT-LABEL: define{{.*}} void @_Z24pass_agg_float_emptybase19agg_float_emptybase(ptr dead_on_unwind noalias writable sret(%struct.agg_float_emptybase) align 4 %{{.*}}, i32 noext %{{.*}}) struct noemptybasearray { [[no_unique_address]] empty dummy[3]; }; struct agg_nofloat_emptybasearray : noemptybasearray { float a; }; struct agg_nofloat_emptybasearray pass_agg_nofloat_emptybasearray(struct agg_nofloat_emptybasearray arg) { return arg; } diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst index abeafb7616201a..2e6030af7ba935 100644 --- a/llvm/docs/LangRef.rst +++ b/llvm/docs/LangRef.rst @@ -1174,6 +1174,10 @@ For example: Note that any attributes for the function result (``nonnull``, ``signext``) come before the result type. +If an integer argument to a function is not marked signext/zeroext/noext, the +kind of extension used is target-specific. Some targets depend for +correctness on the kind of extension to be explicitly specified. + Currently, only the following parameter attributes are defined: ``zeroext`` @@ -1185,6 +1189,12 @@ Currently, only the following parameter attributes are defined: value should be sign-extended to the extent required by the target's ABI (which is usually 32-bits) by the caller (for a parameter) or the callee (for a return value). +``noext`` + This indicates to the code generator that the parameter or return + value has the high bits undefined, as for a struct in register, and + therefore does not need to be sign or zero extended. This is the same + as default behavior and is only actually used (by some targets) to + validate that one of the attributes is always present. ``inreg`` This indicates that this parameter or return value should be treated in a special target-dependent fashion while emitting code for @@ -9113,8 +9123,8 @@ This instruction requires several arguments: convention <callingconv>` the call should use. If none is specified, the call defaults to using C calling conventions. #. The optional :ref:`Parameter Attributes <paramattrs>` list for return - values. Only '``zeroext``', '``signext``', and '``inreg``' attributes - are valid here. + values. Only '``zeroext``', '``signext``', '``noext``', and '``inreg``' + attributes are valid here. #. The optional addrspace attribute can be used to indicate the address space of the called function. If it is not specified, the program address space from the :ref:`datalayout string<langref_datalayout>` will be used. @@ -9209,8 +9219,8 @@ This instruction requires several arguments: convention <callingconv>` the call should use. If none is specified, the call defaults to using C calling conventions. #. The optional :ref:`Parameter Attributes <paramattrs>` list for return - values. Only '``zeroext``', '``signext``', and '``inreg``' attributes - are valid here. + values. Only '``zeroext``', '``signext``', '``noext``', and '``inreg``' + attributes are valid here. #. The optional addrspace attribute can be used to indicate the address space of the called function. If it is not specified, the program address space from the :ref:`datalayout string<langref_datalayout>` will be used. @@ -12699,8 +12709,8 @@ This instruction requires several arguments: calling convention of the call must match the calling convention of the target function, or else the behavior is undefined. #. The optional :ref:`Parameter Attributes <paramattrs>` list for return - values. Only '``zeroext``', '``signext``', and '``inreg``' attributes - are valid here. + values. Only '``zeroext``', '``signext``', '``noext``', and '``inreg``' + attributes are valid here. #. The optional addrspace attribute can be used to indicate the address space of the called function. If it is not specified, the program address space from the :ref:`datalayout string<langref_datalayout>` will be used. diff --git a/llvm/include/llvm/Bitcode/LLVMBitCodes.h b/llvm/include/llvm/Bitcode/LLVMBitCodes.h index 05ed148148d7cb..96668435e8d7bc 100644 --- a/llvm/include/llvm/Bitcode/LLVMBitCodes.h +++ b/llvm/include/llvm/Bitcode/LLVMBitCodes.h @@ -763,6 +763,7 @@ enum AttributeKindCodes { ATTR_KIND_SANITIZE_REALTIME = 96, ATTR_KIND_NO_SANITIZE_REALTIME = 97, ATTR_KIND_CORO_ELIDE_SAFE = 98, + ATTR_KIND_NO_EXT = 99, }; enum ComdatSelectionKindCodes { diff --git a/llvm/include/llvm/CodeGen/TargetCallingConv.h b/llvm/include/llvm/CodeGen/TargetCallingConv.h index cb0055633f4f33..a28c7a99fb3b5a 100644 --- a/llvm/include/llvm/CodeGen/TargetCallingConv.h +++ b/llvm/include/llvm/CodeGen/TargetCallingConv.h @@ -28,6 +28,7 @@ namespace ISD { private: unsigned IsZExt : 1; ///< Zero extended unsigned IsSExt : 1; ///< Sign extended + unsigned IsNoExt : 1; ///< No extension unsigned IsInReg : 1; ///< Passed in register unsigned IsSRet : 1; ///< Hidden struct-ret ptr unsigned IsByVal : 1; ///< Struct passed by value @@ -60,8 +61,8 @@ namespace ISD { public: ArgFlagsTy() - : IsZExt(0), IsSExt(0), IsInReg(0), IsSRet(0), IsByVal(0), IsByRef(0), - IsNest(0), IsReturned(0), IsSplit(0), IsInAlloca(0), + : IsZExt(0), IsSExt(0), IsNoExt(0), IsInReg(0), IsSRet(0), IsByVal(0), + IsByRef(0), IsNest(0), IsReturned(0), IsSplit(0), IsInAlloca(0), IsPreallocated(0), IsSplitEnd(0), IsSwiftSelf(0), IsSwiftAsync(0), IsSwiftError(0), IsCFGuardTarget(0), IsHva(0), IsHvaStart(0), IsSecArgPass(0), MemAlign(0), OrigAlign(0), @@ -76,6 +77,9 @@ namespace ISD { bool isSExt() const { return IsSExt; } void setSExt() { IsSExt = 1; } + bool isNoExt() const { return IsNoExt; } + void setNoExt() { IsNoExt = 1; } + bool isInReg() const { return IsInReg; } void setInReg() { IsInReg = 1; } diff --git a/llvm/include/llvm/CodeGen/TargetLowering.h b/llvm/include/llvm/CodeGen/TargetLowering.h index 5888eaa6fbdb52..3842af56e6b3d7 100644 --- a/llvm/include/llvm/CodeGen/TargetLowering.h +++ b/llvm/include/llvm/CodeGen/TargetLowering.h @@ -301,6 +301,7 @@ class TargetLoweringBase { Type *Ty = nullptr; bool IsSExt : 1; bool IsZExt : 1; + bool IsNoExt : 1; bool IsInReg : 1; bool IsSRet : 1; bool IsNest : 1; @@ -317,10 +318,11 @@ class TargetLoweringBase { Type *IndirectType = nullptr; ArgListEntry() - : IsSExt(false), IsZExt(false), IsInReg(false), IsSRet(false), - IsNest(false), IsByVal(false), IsByRef(false), IsInAlloca(false), - IsPreallocated(false), IsReturned(false), IsSwiftSelf(false), - IsSwiftAsync(false), IsSwiftError(false), IsCFGuardTarget(false) {} + : IsSExt(false), IsZExt(false), IsNoExt(false), IsInReg(false), + IsSRet(false), IsNest(false), IsByVal(false), IsByRef(false), + IsInAlloca(false), IsPreallocated(false), IsReturned(false), + IsSwiftSelf(false), IsSwiftAsync(false), IsSwiftError(false), + IsCFGuardTarget(false) {} void setAttributes(const CallBase *Call, unsigned ArgIdx); }; diff --git a/llvm/include/llvm/IR/Attributes.td b/llvm/include/llvm/IR/Attributes.td index f3ef1e707675eb..24070d646e184e 100644 --- a/llvm/include/llvm/IR/Attributes.td +++ b/llvm/include/llvm/IR/Attributes.td @@ -160,6 +160,9 @@ def NoCapture : EnumAttr<"nocapture", [ParamAttr]>; /// Call cannot be duplicated. def NoDuplicate : EnumAttr<"noduplicate", [FnAttr]>; +/// No extension needed before/after call (high bits are undefined). +def NoExt : EnumAttr<"noext", [ParamAttr, RetAttr]>; + /// Function does not deallocate memory. def NoFree : EnumAttr<"nofree", [FnAttr, ParamAttr]>; diff --git a/llvm/include/llvm/Target/TargetOptions.h b/llvm/include/llvm/Target/TargetOptions.h index d3464b5202ff32..94e0fa2404d6fc 100644 --- a/llvm/include/llvm/Target/TargetOptions.h +++ b/llvm/include/llvm/Target/TargetOptions.h @@ -155,6 +155,7 @@ namespace llvm { XRayFunctionIndex(true), DebugStrictDwarf(false), Hotpatch(false), PPCGenScalarMASSEntries(false), JMCInstrument(false), EnableCFIFixup(false), MisExpect(false), XCOFFReadOnlyPointers(false), + VerifyArgABICompliance(true), FPDenormalMode(DenormalMode::IEEE, DenormalMode::IEEE) {} /// DisableFramePointerElim - This returns true if frame pointer elimination @@ -381,6 +382,12 @@ namespace llvm { /// into the RO data section. unsigned XCOFFReadOnlyPointers : 1; + /// When set to true, call/return argument extensions of narrow integers + /// are verified in the target backend if it cares about them. This is + /// not done with internal tools like llc that run many tests that ignore + /// (lack) these extensions. + unsigned VerifyArgABICompliance : 1; + /// Name of the stack usage file (i.e., .su file) if user passes /// -fstack-usage. If empty, it can be implied that -fstack-usage is not /// passed on the command line. diff --git a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp index 027a29764148f0..b7db631d7d432f 100644 --- a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp +++ b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp @@ -2190,6 +2190,8 @@ static Attribute::AttrKind getAttrFromCode(uint64_t Code) { return Attribute::Initializes; case bitc::ATTR_KIND_CORO_ELIDE_SAFE: return Attribute::CoroElideSafe; + case bitc::ATTR_KIND_NO_EXT: + return Attribute::NoExt; } } diff --git a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp index a5942153dc2d64..5ead94218c3f87 100644 --- a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp +++ b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp @@ -893,6 +893,8 @@ static uint64_t getAttrKindEncoding(Attribute::AttrKind Kind) { return bitc::ATTR_KIND_RANGE; case Attribute::Initializes: return bitc::ATTR_KIND_INITIALIZES; + case Attribute::NoExt: + return bitc::ATTR_KIND_NO_EXT; case Attribute::EndAttrKinds: llvm_unreachable("Can not encode end-attribute kinds marker."); case Attribute::None: diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp index eec89f04c6356d..492e23231065d5 100644 --- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp +++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp @@ -2284,6 +2284,8 @@ void SelectionDAGBuilder::visitRet(const ReturnInst &I) { Flags.setSExt(); else if (ExtendKind == ISD::ZERO_EXTEND) Flags.setZExt(); + else if (F->getAttributes().hasRetAttr(Attribute::NoExt)) + Flags.setNoExt(); for (unsigned i = 0; i < NumParts; ++i) { Outs.push_back(ISD::OutputArg(Flags, @@ -11004,6 +11006,8 @@ TargetLowering::LowerCallTo(TargetLowering::CallLoweringInfo &CLI) const { Flags.setZExt(); if (Args[i].IsSExt) Flags.setSExt(); + if (Args[i].IsNoExt) + Flags.setNoExt(); if (Args[i].IsInReg) { // If we are using vectorcall calling convention, a structure that is // passed InReg - is surely an HVA diff --git a/llvm/lib/CodeGen/SelectionDAG/TargetLowering.cpp b/llvm/lib/CodeGen/SelectionDAG/TargetLowering.cpp index a293c2391c3283..a2a232ed93b72f 100644 --- a/llvm/lib/CodeGen/SelectionDAG/TargetLowering.cpp +++ b/llvm/lib/CodeGen/SelectionDAG/TargetLowering.cpp @@ -113,6 +113,7 @@ void TargetLoweringBase::ArgListEntry::setAttributes(const CallBase *Call, unsigned ArgIdx) { IsSExt = Call->paramHasAttr(ArgIdx, Attribute::SExt); IsZExt = Call->paramHasAttr(ArgIdx, Attribute::ZExt); + IsNoExt = Call->paramHasAttr(ArgIdx, Attribute::NoExt); IsInReg = Call->paramHasAttr(ArgIdx, Attribute::InReg); IsSRet = Call->paramHasAttr(ArgIdx, Attribute::StructRet); IsNest = Call->paramHasAttr(ArgIdx, Attribute::Nest); diff --git a/llvm/lib/Target/SystemZ/SystemZISelLowering.cpp b/llvm/lib/Target/SystemZ/SystemZISelLowering.cpp index 582a8c139b2937..a24ed89e2af455 100644 --- a/llvm/lib/Target/SystemZ/SystemZISelLowering.cpp +++ b/llvm/lib/Target/SystemZ/SystemZISelLowering.cpp @@ -34,6 +34,13 @@ using namespace llvm; #define DEBUG_TYPE "systemz-lower" +// Temporarily let this be disabled by default until all known problems +// related to argument extensions are fixed. +static cl::opt<bool> EnableIntArgExtCheck( + "argext-abi-check", cl::init(false), + cl::desc("Verify that narrow int args are properly extended per the " + "SystemZ ABI.")); + namespace { // Represents information about a comparison. struct Comparison { @@ -1917,6 +1924,13 @@ SystemZTargetLowering::LowerCall(CallLoweringInfo &CLI, if (Subtarget.isTargetXPLINK64()) IsTailCall = false; + // Integer args <=32 bits should have an extension attribute. + bool IsInternal = false; + if (auto *G = dyn_cast<GlobalAddressSDNode>(Callee)) + if (const Function *Fn = dyn_cast<Function>(G->getGlobal())) + IsInternal = isFullyInternal(Fn); + verifyNarrowIntegerArgs(Outs, IsInternal); + // Analyze the operands of the call, assigning locations to each operand. SmallVector<CCValAssign, 16> ArgLocs; SystemZCCState ArgCCInfo(CallConv, IsVarArg, MF, ArgLocs, Ctx); @@ -2177,6 +2191,9 @@ SystemZTargetLowering::LowerReturn(SDValue Chain, CallingConv::ID CallConv, const SDLoc &DL, SelectionDAG &DAG) const { MachineFunction &MF = DAG.getMachineFunction(); + // Integer args <=32 bits should have an extension attribute. + verifyNarrowIntegerArgs(Outs, isFullyInternal(&MF.getFunction())); + // Assign locations to each returned value. SmallVector<CCValAssign, 16> RetLocs; CCState RetCCInfo(CallConv, IsVarArg, MF, RetLocs, *DAG.getContext()); @@ -9800,3 +9817,50 @@ SDValue SystemZTargetLowering::lowerVECREDUCE_ADD(SDValue Op, ISD::EXTRACT_VECTOR_ELT, DL, VT, DAG.getBitcast(OpVT, Op), DAG.getConstant(OpVT.getVectorNumElements() - 1, DL, MVT::i32)); } + +// Only consider a function fully internal as long as it has local linkage +// and is not used in any other way than acting as the called function at +// call sites. +bool SystemZTargetLowering::isFullyInternal(const Function *Fn) const { + if (!Fn->hasLocalLinkage()) + return false; + for (const User *U : Fn->users()) { + if (auto *CB = dyn_cast<CallBase>(U)) { + if (CB->getCalledFunction() != Fn) + return false; + } else + return false; + } + return true; +} + +// Verify that narrow integer arguments are extended as required by the ABI. +void SystemZTargetLowering:: +verifyNarrowIntegerArgs(const SmallVectorImpl<ISD::OutputArg> &Outs, + bool IsInternal) const { + if (IsInternal || !Subtarget.isTargetELF()) + return; + + // Temporarily only do the check when explicitly requested, until it can be + // enabled by default. + if (!EnableIntArgExtCheck) + return; + + if (EnableIntArgExtCheck.getNumOccurrences()) { + if (!EnableIntArgExtCheck) + return; + } else if (!getTargetMachine().Options.VerifyArgABICompliance) + return; + + for (unsigned i = 0; i < Outs.size(); ++i) { + MVT VT = Outs[i].VT; + ISD::ArgFlagsTy Flags = Outs[i].Flags; + if (VT.isInteger()) { + assert((VT == MVT::i32 || VT.getSizeInBits() >= 64) && + "Unexpected integer argument VT."); + assert((VT != MVT::i32 || + (Flags.isSExt() || Flags.isZExt() || Flags.isNoExt())) && + "Narrow integer argument must have a valid extension type."); + } + } +} diff --git a/llvm/lib/Target/SystemZ/SystemZISelLowering.h b/llvm/lib/Target/SystemZ/SystemZISelLowering.h index 4a18bde00a0b98..8c528897182d17 100644 --- a/llvm/lib/Target/SystemZ/SystemZISelLowering.h +++ b/llvm/lib/Target/SystemZ/SystemZISelLowering.h @@ -804,6 +804,10 @@ class SystemZTargetLowering : public TargetLowering { MachineMemOperand::Flags getTargetMMOFlags(const Instruction &I) const override; const TargetRegisterClass *getRepRegClassFor(MVT VT) const override; + + bool isFullyInternal(const Function *Fn) const; + void verifyNarrowIntegerArgs(const SmallVectorImpl<ISD::OutputArg> &Outs, + bool IsInternal) const; }; struct SystemZVectorConstantInfo { diff --git a/llvm/lib/Transforms/Utils/CodeExtractor.cpp b/llvm/lib/Transforms/Utils/CodeExtractor.cpp index 895b588a9e5ac3..894e02d314125e 100644 --- a/llvm/lib/Transforms/Utils/CodeExtractor.cpp +++ b/llvm/lib/Transforms/Utils/CodeExtractor.cpp @@ -996,6 +996,7 @@ Function *CodeExtractor::constructFunction(const ValueSet &inputs, case Attribute::DeadOnUnwind: case Attribute::Range: case Attribute::Initializes: + case Attribute::NoExt: // These are not really attributes. case Attribute::None: case Attribute::EndAttrKinds: diff --git a/llvm/test/CodeGen/SystemZ/args-14.ll b/llvm/test/CodeGen/SystemZ/args-14.ll new file mode 100644 index 00000000000000..84e7523e788de4 --- /dev/null +++ b/llvm/test/CodeGen/SystemZ/args-14.ll @@ -0,0 +1,39 @@ +; RUN: llc < %s -mtriple=s390x-linux-gnu -argext-abi-check + +; Test that it works to pass structs as outgoing call arguments when the +; NoExt attribute is given, either in the call instruction or in the +; prototype of the called function. +define void @caller() { + call void @bar_Struct_32(i32 noext 123) + call void @bar_Struct_16(i16 123) + call void @bar_Struct_8(i8 noext 123) + ret void +} + +declare void @bar_Struct_32(i32 %Arg) +declare void @bar_Struct_16(i16 noext %Arg) +declare void @bar_Struct_8(i8 %Arg) + +; Test that it works to return values with the NoExt attribute. +define noext i8 @callee_NoExtRet_i8() { + ret i8 -1 +} + +define noext i16 @callee_NoExtRet_i16() { + ret i16 -1 +} + +define noext i32 @callee_NoExtRet_i32() { + ret i32 -1 +} + +; An internal function is not checked for an extension attribute. +define internal i32 @callee_NoExtRet_internal(i32 %Arg) { + ret i32 %Arg +} + +; A call to an internal function is ok without argument extension. +define void @caller_internal() { + call i32 @callee_NoExtRet_internal(i32 0) + ret void +} diff --git a/llvm/test/CodeGen/SystemZ/args-15.ll b/llvm/test/CodeGen/SystemZ/args-15.ll new file mode 100644 index 00000000000000..c810aeb8c46c5d --- /dev/null +++ b/llvm/test/CodeGen/SystemZ/args-15.ll @@ -0,0 +1,11 @@ +; RUN: not --crash llc < %s -mtriple=s390x-linux-gnu -argext-abi-check 2>&1 \ +; RUN: | FileCheck %s +; REQUIRES: asserts +; +; Test detection of missing extension of an i32 return value. + +define i32 @callee_MissingRetAttr() { + ret i32 -1 +} + +; CHECK: Narrow integer argument must have a valid extension type. diff --git a/llvm/test/CodeGen/SystemZ/args-16.ll b/llvm/test/CodeGen/SystemZ/args-16.ll new file mode 100644 index 00000000000000..b76a2afea50775 --- /dev/null +++ b/llvm/test/CodeGen/SystemZ/args-16.ll @@ -0,0 +1,12 @@ +; RUN: not --crash llc < %s -mtriple=s390x-linux-gnu -argext-abi-check 2>&1 \ +; RUN: | FileCheck %s +; REQUIRES: asserts +; +; Test detection of missing extension of an i16 return value. + +define i16 @callee_MissingRetAttr() { + ret i16 -1 +} + +; CHECK: Narrow integer argument must have a valid extension type. + diff --git a/llvm/test/CodeGen/SystemZ/args-17.ll b/llvm/test/CodeGen/SystemZ/args-17.ll new file mode 100644 index 00000000000000..bce54b3d2aa1fe --- /dev/null +++ b/llvm/test/CodeGen/SystemZ/args-17.ll @@ -0,0 +1,11 @@ +; RUN: not --crash llc < %s -mtriple=s390x-linux-gnu -argext-abi-check 2>&1 \ +; RUN: | FileCheck %s +; REQUIRES: asserts +; +; Test detection of missing extension of an i8 return value. + +define i8 @callee_MissingRetAttr() { + ret i8 -1 +} + +; CHECK: Narrow integer argument must have a valid extension type. diff --git a/llvm/test/CodeGen/SystemZ/args-18.ll b/llvm/test/CodeGen/SystemZ/args-18.ll new file mode 100644 index 00000000000000..82e9729d3a2dfd --- /dev/null +++ b/llvm/test/CodeGen/SystemZ/args-18.ll @@ -0,0 +1,14 @@ +; RUN: not --crash llc < %s -mtriple=s390x-linux-gnu -argext-abi-check 2>&1 \ +; RUN: | FileCheck %s +; REQUIRES: asserts +; +; Test detection of missing extension of an outgoing i32 call argument. + +define void @caller() { + call void @bar_Struct(i32 123) + ret void +} + +declare void @bar_Struct(i32 %Arg) + +; CHECK: Narrow integer argument must have a valid extension type diff --git a/llvm/test/CodeGen/SystemZ/args-19.ll b/llvm/test/CodeGen/SystemZ/args-19.ll new file mode 100644 index 00000000000000..40a794417b4c6f --- /dev/null +++ b/llvm/test/CodeGen/SystemZ/args-19.ll @@ -0,0 +1,14 @@ +; RUN: not --crash llc < %s -mtriple=s390x-linux-gnu -argext-abi-check 2>&1 \ +; RUN: | FileCheck %s +; REQUIRES: asserts +; +; Test detection of missing extension of an outgoing i16 call argument. + +define void @caller() { + call void @bar_Struct(i16 123) + ret void +} + +declare void @bar_Struct(i16 %Arg) + +; CHECK: Narrow integer argument must have a valid extension type diff --git a/llvm/test/CodeGen/SystemZ/args-20.ll b/llvm/test/CodeGen/SystemZ/args-20.ll new file mode 100644 index 00000000000000..ce8b828a2d539a --- /dev/null +++ b/llvm/test/CodeGen/SystemZ/args-20.ll @@ -0,0 +1,14 @@ +; RUN: not --crash llc < %s -mtriple=s390x-linux-gnu -argext-abi-check 2>&1 \ +; RUN: | FileCheck %s +; REQUIRES: asserts +; +; Test detection of missing extension of an outgoing i8 call argument. + +define void @caller() { + call void @bar_Struct(i8 123) + ret void +} + +declare void @bar_Struct(i8 %Arg) + +; CHECK: Narrow integer argument must have a valid extension type diff --git a/llvm/test/CodeGen/SystemZ/args-21.ll b/llvm/test/CodeGen/SystemZ/args-21.ll new file mode 100644 index 00000000000000..c64233094c7df9 --- /dev/null +++ b/llvm/test/CodeGen/SystemZ/args-21.ll @@ -0,0 +1,19 @@ +; RUN: not --crash llc < %s -mtriple=s390x-linux-gnu -argext-abi-check 2>&1 \ +; RUN: | FileCheck %s +; REQUIRES: asserts +; +; Test detection of missing extension involving an internal function which is +; passed as a function pointer to an external function. + +define internal i32 @bar(i32 %Arg) { + ret i32 %Arg +} + +declare void @ExtFun(ptr %FunPtr); + +define void @foo() { + call void @ExtFun(ptr @bar) + ret void +} + +; CHECK: Narrow integer argument must have a valid extension type diff --git a/llvm/tools/llc/llc.cpp b/llvm/tools/llc/llc.cpp index 3f27a5fd1a0eb9..2c1901cdd49d8b 100644 --- a/llvm/tools/llc/llc.cpp +++ b/llvm/tools/llc/llc.cpp @@ -620,6 +620,10 @@ static int compileModule(char **argv, LLVMContext &Context) { // Ensure the filename is passed down to CodeViewDebug. Target->Options.ObjectFilenameForDebug = Out->outputFilename(); + // Tell target that this tool is not necessarily used with argument ABI + // compliance (i.e. narrow integer argument extensions). + Target->Options.VerifyArgABICompliance = 0; + std::unique_ptr<ToolOutputFile> DwoOut; if (!SplitDwarfOutputFile.empty()) { std::error_code EC; _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits