diff --git a/clang/lib/Basic/Targets/LoongArch.h b/clang/lib/Basic/Targets/LoongArch.h --- a/clang/lib/Basic/Targets/LoongArch.h +++ b/clang/lib/Basic/Targets/LoongArch.h @@ -55,6 +55,8 @@ bool validateAsmConstraint(const char *&Name, TargetInfo::ConstraintInfo &Info) const override; + + bool hasBitIntType() const override { return true; } }; class LLVM_LIBRARY_VISIBILITY LoongArch32TargetInfo diff --git a/clang/lib/CodeGen/TargetInfo.cpp b/clang/lib/CodeGen/TargetInfo.cpp --- a/clang/lib/CodeGen/TargetInfo.cpp +++ b/clang/lib/CodeGen/TargetInfo.cpp @@ -11598,6 +11598,438 @@ } +// LoongArch ABI Implementation. Documented at +// https://loongson.github.io/LoongArch-Documentation/LoongArch-ELF-ABI-EN.html +// +//===----------------------------------------------------------------------===// + +namespace { +class LoongArchABIInfo : public DefaultABIInfo { +private: + // Size of the integer ('r') registers in bits. + unsigned GRLen; + // Size of the floating point ('f') registers in bits. + unsigned FRLen; + // Number of general-purpose argument registers. + static const int NumGARs = 8; + // Number of floating-point argument registers. + static const int NumFARs = 8; + bool detectFARsEligibleStructHelper(QualType Ty, CharUnits CurOff, + llvm::Type *&Field1Ty, + CharUnits &Field1Off, + llvm::Type *&Field2Ty, + CharUnits &Field2Off) const; + +public: + LoongArchABIInfo(CodeGen::CodeGenTypes &CGT, unsigned GRLen, unsigned FRLen) + : DefaultABIInfo(CGT), GRLen(GRLen), FRLen(FRLen) {} + + void computeInfo(CGFunctionInfo &FI) const override; + + ABIArgInfo classifyArgumentType(QualType Ty, bool IsFixed, int &GARsLeft, + int &FARsLeft) const; + ABIArgInfo classifyReturnType(QualType RetTy) const; + + Address EmitVAArg(CodeGenFunction &CGF, Address VAListAddr, + QualType Ty) const override; + + ABIArgInfo extendType(QualType Ty) const; + + bool detectFARsEligibleStruct(QualType Ty, llvm::Type *&Field1Ty, + CharUnits &Field1Off, llvm::Type *&Field2Ty, + CharUnits &Field2Off, int &NeededArgGPRs, + int &NeededArgFPRs) const; + ABIArgInfo coerceAndExpandFARsEligibleStruct(llvm::Type *Field1Ty, + CharUnits Field1Off, + llvm::Type *Field2Ty, + CharUnits Field2Off) const; +}; +} // end anonymous namespace + +void LoongArchABIInfo::computeInfo(CGFunctionInfo &FI) const { + QualType RetTy = FI.getReturnType(); + if (!getCXXABI().classifyReturnType(FI)) + FI.getReturnInfo() = classifyReturnType(RetTy); + + // IsRetIndirect is true if classifyArgumentType indicated the value should + // be passed indirect, or if the type size is a scalar greater than 2*GRLen + // and not a complex type with elements <= FRLen. e.g. fp128 is passed direct + // in LLVM IR, relying on the backend lowering code to rewrite the argument + // list and pass indirectly on LA32. + bool IsRetIndirect = FI.getReturnInfo().getKind() == ABIArgInfo::Indirect; + if (!IsRetIndirect && RetTy->isScalarType() && + getContext().getTypeSize(RetTy) > (2 * GRLen)) { + if (RetTy->isComplexType() && FRLen) { + QualType EltTy = RetTy->castAs()->getElementType(); + IsRetIndirect = getContext().getTypeSize(EltTy) > FRLen; + } else { + // This is a normal scalar > 2*GRLen, such as fp128 on LA32. + IsRetIndirect = true; + } + } + + // We must track the number of GARs and FARs used in order to conform to the + // LoongArch ABI. As GAR usage is different for variadic arguments, we must + // also track whether we are examining a vararg or not. + int GARsLeft = IsRetIndirect ? NumGARs - 1 : NumGARs; + int FARsLeft = FRLen ? NumFARs : 0; + int NumFixedArgs = FI.getNumRequiredArgs(); + + int ArgNum = 0; + for (auto &ArgInfo : FI.arguments()) { + ArgInfo.info = classifyArgumentType( + ArgInfo.type, /*IsFixed=*/ArgNum < NumFixedArgs, GARsLeft, FARsLeft); + ArgNum++; + } +} + +// Returns true if the struct is a potential candidate to be passed in FARs (and +// GARs). If this function returns true, the caller is responsible for checking +// that if there is only a single field then that field is a float. +bool LoongArchABIInfo::detectFARsEligibleStructHelper( + QualType Ty, CharUnits CurOff, llvm::Type *&Field1Ty, CharUnits &Field1Off, + llvm::Type *&Field2Ty, CharUnits &Field2Off) const { + bool IsInt = Ty->isIntegralOrEnumerationType(); + bool IsFloat = Ty->isRealFloatingType(); + + if (IsInt || IsFloat) { + uint64_t Size = getContext().getTypeSize(Ty); + if (IsInt && Size > GRLen) + return false; + // Can't be eligible if larger than the FP registers. Half precision isn't + // currently supported on LoongArch and the ABI hasn't been confirmed, so + // default to the integer ABI in that case. + if (IsFloat && (Size > FRLen || Size < 32)) + return false; + // Can't be eligible if an integer type was already found (int+int pairs + // are not eligible). + if (IsInt && Field1Ty && Field1Ty->isIntegerTy()) + return false; + if (!Field1Ty) { + Field1Ty = CGT.ConvertType(Ty); + Field1Off = CurOff; + return true; + } + if (!Field2Ty) { + Field2Ty = CGT.ConvertType(Ty); + Field2Off = CurOff; + return true; + } + return false; + } + + if (auto CTy = Ty->getAs()) { + if (Field1Ty) + return false; + QualType EltTy = CTy->getElementType(); + if (getContext().getTypeSize(EltTy) > FRLen) + return false; + Field1Ty = CGT.ConvertType(EltTy); + Field1Off = CurOff; + Field2Ty = Field1Ty; + Field2Off = Field1Off + getContext().getTypeSizeInChars(EltTy); + return true; + } + + if (const ConstantArrayType *ATy = getContext().getAsConstantArrayType(Ty)) { + uint64_t ArraySize = ATy->getSize().getZExtValue(); + QualType EltTy = ATy->getElementType(); + CharUnits EltSize = getContext().getTypeSizeInChars(EltTy); + for (uint64_t i = 0; i < ArraySize; ++i) { + if (!detectFARsEligibleStructHelper(EltTy, CurOff, Field1Ty, Field1Off, + Field2Ty, Field2Off)) + return false; + CurOff += EltSize; + } + return true; + } + + if (const auto *RTy = Ty->getAs()) { + // Structures with either a non-trivial destructor or a non-trivial + // copy constructor are not eligible for the FP calling convention. + if (getRecordArgABI(Ty, CGT.getCXXABI())) + return false; + if (isEmptyRecord(getContext(), Ty, true)) + return true; + const RecordDecl *RD = RTy->getDecl(); + // Unions aren't eligible unless they're empty (which is caught above). + if (RD->isUnion()) + return false; + const ASTRecordLayout &Layout = getContext().getASTRecordLayout(RD); + // If this is a C++ record, check the bases first. + if (const CXXRecordDecl *CXXRD = dyn_cast(RD)) { + for (const CXXBaseSpecifier &B : CXXRD->bases()) { + const auto *BDecl = + cast(B.getType()->castAs()->getDecl()); + if (!detectFARsEligibleStructHelper( + B.getType(), CurOff + Layout.getBaseClassOffset(BDecl), + Field1Ty, Field1Off, Field2Ty, Field2Off)) + return false; + } + } + for (const FieldDecl *FD : RD->fields()) { + QualType QTy = FD->getType(); + if (FD->isBitField()) { + unsigned BitWidth = FD->getBitWidthValue(getContext()); + // Zero-width bitfields are ignored. + if (BitWidth == 0) + continue; + // Allow a bitfield with a type greater than GRLen as long as the + // bitwidth is GRLen or less. + if (getContext().getTypeSize(QTy) > GRLen && BitWidth <= GRLen) { + QTy = getContext().getIntTypeForBitwidth(GRLen, false); + } + } + + if (!detectFARsEligibleStructHelper( + QTy, + CurOff + getContext().toCharUnitsFromBits( + Layout.getFieldOffset(FD->getFieldIndex())), + Field1Ty, Field1Off, Field2Ty, Field2Off)) + return false; + } + return Field1Ty != nullptr; + } + + return false; +} + +// Determine if a struct is eligible to be passed in FARs (and GARs) (i.e., when +// flattened it contains a single fp value, fp+fp, or int+fp of appropriate +// size). If so, NeededFARs and NeededGARs are incremented appropriately. +bool LoongArchABIInfo::detectFARsEligibleStruct( + QualType Ty, llvm::Type *&Field1Ty, CharUnits &Field1Off, + llvm::Type *&Field2Ty, CharUnits &Field2Off, int &NeededGARs, + int &NeededFARs) const { + Field1Ty = nullptr; + Field2Ty = nullptr; + NeededGARs = 0; + NeededFARs = 0; + if (!detectFARsEligibleStructHelper(Ty, CharUnits::Zero(), Field1Ty, + Field1Off, Field2Ty, Field2Off)) + return false; + // Not really a candidate if we have a single int but no float. + if (Field1Ty && !Field2Ty && !Field1Ty->isFloatingPointTy()) + return false; + if (Field1Ty && Field1Ty->isFloatingPointTy()) + NeededFARs++; + else if (Field1Ty) + NeededGARs++; + if (Field2Ty && Field2Ty->isFloatingPointTy()) + NeededFARs++; + else if (Field2Ty) + NeededGARs++; + return true; +} + +// Call getCoerceAndExpand for the two-element flattened struct described by +// Field1Ty, Field1Off, Field2Ty, Field2Off. This method will create an +// appropriate coerceToType and unpaddedCoerceToType. +ABIArgInfo LoongArchABIInfo::coerceAndExpandFARsEligibleStruct( + llvm::Type *Field1Ty, CharUnits Field1Off, llvm::Type *Field2Ty, + CharUnits Field2Off) const { + SmallVector CoerceElts; + SmallVector UnpaddedCoerceElts; + if (!Field1Off.isZero()) + CoerceElts.push_back(llvm::ArrayType::get( + llvm::Type::getInt8Ty(getVMContext()), Field1Off.getQuantity())); + + CoerceElts.push_back(Field1Ty); + UnpaddedCoerceElts.push_back(Field1Ty); + + if (!Field2Ty) { + return ABIArgInfo::getCoerceAndExpand( + llvm::StructType::get(getVMContext(), CoerceElts, !Field1Off.isZero()), + UnpaddedCoerceElts[0]); + } + + CharUnits Field2Align = + CharUnits::fromQuantity(getDataLayout().getABITypeAlignment(Field2Ty)); + CharUnits Field1End = + Field1Off + + CharUnits::fromQuantity(getDataLayout().getTypeStoreSize(Field1Ty)); + CharUnits Field2OffNoPadNoPack = Field1End.alignTo(Field2Align); + + CharUnits Padding = CharUnits::Zero(); + if (Field2Off > Field2OffNoPadNoPack) + Padding = Field2Off - Field2OffNoPadNoPack; + else if (Field2Off != Field2Align && Field2Off > Field1End) + Padding = Field2Off - Field1End; + + bool IsPacked = !Field2Off.isMultipleOf(Field2Align); + + if (!Padding.isZero()) + CoerceElts.push_back(llvm::ArrayType::get( + llvm::Type::getInt8Ty(getVMContext()), Padding.getQuantity())); + + CoerceElts.push_back(Field2Ty); + UnpaddedCoerceElts.push_back(Field2Ty); + + return ABIArgInfo::getCoerceAndExpand( + llvm::StructType::get(getVMContext(), CoerceElts, IsPacked), + llvm::StructType::get(getVMContext(), UnpaddedCoerceElts, IsPacked)); +} + +ABIArgInfo LoongArchABIInfo::classifyArgumentType(QualType Ty, bool IsFixed, + int &GARsLeft, + int &FARsLeft) const { + assert(GARsLeft <= NumGARs && "GAR tracking underflow"); + Ty = useFirstFieldIfTransparentUnion(Ty); + + // Structures with either a non-trivial destructor or a non-trivial + // copy constructor are always passed indirectly. + if (CGCXXABI::RecordArgABI RAA = getRecordArgABI(Ty, getCXXABI())) { + if (GARsLeft) + GARsLeft -= 1; + return getNaturalAlignIndirect(Ty, /*ByVal=*/RAA == + CGCXXABI::RAA_DirectInMemory); + } + + // Ignore empty structs/unions. + if (isEmptyRecord(getContext(), Ty, true)) + return ABIArgInfo::getIgnore(); + + uint64_t Size = getContext().getTypeSize(Ty); + + // Pass floating point values via FARs if possible. + if (IsFixed && Ty->isFloatingType() && !Ty->isComplexType() && + FRLen >= Size && FARsLeft) { + FARsLeft--; + return ABIArgInfo::getDirect(); + } + + // Complex types for the *f or *d ABI must be passed directly rather than + // using CoerceAndExpand. + if (IsFixed && Ty->isComplexType() && FRLen && FARsLeft >= 2) { + QualType EltTy = Ty->castAs()->getElementType(); + if (getContext().getTypeSize(EltTy) <= FRLen) { + FARsLeft -= 2; + return ABIArgInfo::getDirect(); + } + } + + if (IsFixed && FRLen && Ty->isStructureOrClassType()) { + llvm::Type *Field1Ty = nullptr; + llvm::Type *Field2Ty = nullptr; + CharUnits Field1Off = CharUnits::Zero(); + CharUnits Field2Off = CharUnits::Zero(); + int NeededGARs = 0; + int NeededFARs = 0; + bool IsCandidate = detectFARsEligibleStruct( + Ty, Field1Ty, Field1Off, Field2Ty, Field2Off, NeededGARs, NeededFARs); + if (IsCandidate && NeededGARs <= GARsLeft && NeededFARs <= FARsLeft) { + GARsLeft -= NeededGARs; + FARsLeft -= NeededFARs; + return coerceAndExpandFARsEligibleStruct(Field1Ty, Field1Off, Field2Ty, + Field2Off); + } + } + + uint64_t NeededAlign = getContext().getTypeAlign(Ty); + // Determine the number of GARs needed to pass the current argument + // according to the ABI. 2*GRLen-aligned varargs are passed in "aligned" + // register pairs, so may consume 3 registers. + int NeededGARs = 1; + if (!IsFixed && NeededAlign == 2 * GRLen) + NeededGARs = 2 + (GARsLeft % 2); + else if (Size > GRLen && Size <= 2 * GRLen) + NeededGARs = 2; + + if (NeededGARs > GARsLeft) + NeededGARs = GARsLeft; + + GARsLeft -= NeededGARs; + + if (!isAggregateTypeForABI(Ty) && !Ty->isVectorType()) { + // Treat an enum type as its underlying type. + if (const EnumType *EnumTy = Ty->getAs()) + Ty = EnumTy->getDecl()->getIntegerType(); + + // All integral types are promoted to GRLen width. + if (Size < GRLen && Ty->isIntegralOrEnumerationType()) + return extendType(Ty); + + if (const auto *EIT = Ty->getAs()) { + if (EIT->getNumBits() < GRLen) + return extendType(Ty); + if (EIT->getNumBits() > 128 || + (!getContext().getTargetInfo().hasInt128Type() && + EIT->getNumBits() > 64)) + return getNaturalAlignIndirect(Ty, /*ByVal=*/false); + } + + return ABIArgInfo::getDirect(); + } + + // Aggregates which are <= 2*GRLen will be passed in registers if possible, + // so coerce to integers. + if (Size <= 2 * GRLen) { + // Use a single GRLen int if possible, 2*GRLen if 2*GRLen alignment is + // required, and a 2-element GRLen array if only GRLen alignment is + // required. + if (Size <= GRLen) { + return ABIArgInfo::getDirect( + llvm::IntegerType::get(getVMContext(), GRLen)); + } + if (getContext().getTypeAlign(Ty) == 2 * GRLen) { + return ABIArgInfo::getDirect( + llvm::IntegerType::get(getVMContext(), 2 * GRLen)); + } + return ABIArgInfo::getDirect( + llvm::ArrayType::get(llvm::IntegerType::get(getVMContext(), GRLen), 2)); + } + return getNaturalAlignIndirect(Ty, /*ByVal=*/false); +} + +ABIArgInfo LoongArchABIInfo::classifyReturnType(QualType RetTy) const { + if (RetTy->isVoidType()) + return ABIArgInfo::getIgnore(); + // The rules for return and argument types are the same, so defer to + // classifyArgumentType. + int GARsLeft = 2; + int FARsLeft = FRLen ? 2 : 0; + return classifyArgumentType(RetTy, /*IsFixed=*/true, GARsLeft, FARsLeft); +} + +Address LoongArchABIInfo::EmitVAArg(CodeGenFunction &CGF, Address VAListAddr, + QualType Ty) const { + CharUnits SlotSize = CharUnits::fromQuantity(GRLen / 8); + + // Empty records are ignored for parameter passing purposes. + if (isEmptyRecord(getContext(), Ty, true)) { + Address Addr = Address(CGF.Builder.CreateLoad(VAListAddr), + getVAListElementType(CGF), SlotSize); + Addr = CGF.Builder.CreateElementBitCast(Addr, CGF.ConvertTypeForMem(Ty)); + return Addr; + } + + auto TInfo = getContext().getTypeInfoInChars(Ty); + + // Arguments bigger than 2*GRLen bytes are passed indirectly. + return emitVoidPtrVAArg(CGF, VAListAddr, Ty, + /*IsIndirect=*/TInfo.Width > 2 * SlotSize, TInfo, + SlotSize, + /*AllowHigherAlign=*/true); +} + +ABIArgInfo LoongArchABIInfo::extendType(QualType Ty) const { + int TySize = getContext().getTypeSize(Ty); + // LA64 ABI requires unsigned 32 bit integers to be sign extended. + if (GRLen == 64 && Ty->isUnsignedIntegerOrEnumerationType() && TySize == 32) + return ABIArgInfo::getSignExtend(Ty); + return ABIArgInfo::getExtend(Ty); +} + +namespace { +class LoongArchTargetCodeGenInfo : public TargetCodeGenInfo { +public: + LoongArchTargetCodeGenInfo(CodeGen::CodeGenTypes &CGT, unsigned GRLen, + unsigned FRLen) + : TargetCodeGenInfo( + std::make_unique(CGT, GRLen, FRLen)) {} +}; +} // namespace + //===----------------------------------------------------------------------===// // Driver code //===----------------------------------------------------------------------===// @@ -11829,6 +12261,17 @@ case llvm::Triple::bpfeb: case llvm::Triple::bpfel: return SetCGInfo(new BPFTargetCodeGenInfo(Types)); + case llvm::Triple::loongarch32: + case llvm::Triple::loongarch64: { + StringRef ABIStr = getTarget().getABI(); + unsigned ABIFRLen = 0; + if (ABIStr.endswith("f")) + ABIFRLen = 32; + else if (ABIStr.endswith("d")) + ABIFRLen = 64; + return SetCGInfo(new LoongArchTargetCodeGenInfo( + Types, getTarget().getPointerWidth(0), ABIFRLen)); + } } } diff --git a/clang/test/CodeGen/LoongArch/abi-lp64d.c b/clang/test/CodeGen/LoongArch/abi-lp64d.c new file mode 100644 --- /dev/null +++ b/clang/test/CodeGen/LoongArch/abi-lp64d.c @@ -0,0 +1,488 @@ +// RUN: %clang_cc1 -triple loongarch64 -target-feature +f -target-feature +d -target-abi lp64d \ +// RUN: -emit-llvm %s -o - | FileCheck %s + +/// This test checks the calling convention of the lp64d ABI. + +#include +#include + +/// Part 0: C Data Types and Alignment. + +/// `char` datatype is signed by default. +/// In most cases, the unsigned integer data types are zero-extended when stored +/// in general-purpose register, and the signed integer data types are +/// sign-extended. However, in the LP64D ABI, unsigned 32-bit types, such as +/// unsigned int, are stored in general-purpose registers as proper sign +/// extensions of their 32-bit values. + +// CHECK-LABEL: define{{.*}} zeroext i1 @check_bool() +_Bool check_bool() { return 0; } + +// CHECK-LABEL: define{{.*}} signext i8 @check_char() +char check_char() { return 0; } + +// CHECK-LABEL: define{{.*}} signext i16 @check_short() +short check_short() { return 0; } + +// CHECK-LABEL: define{{.*}} signext i32 @check_int() +int check_int() { return 0; } + +// CHECK-LABEL: define{{.*}} i64 @check_long() +long check_long() { return 0; } + +// CHECK-LABEL: define{{.*}} i64 @check_longlong() +long long check_longlong() { return 0; } + +// CHECK-LABEL: define{{.*}} zeroext i8 @check_uchar() +unsigned char check_uchar() { return 0; } + +// CHECK-LABEL: define{{.*}} zeroext i16 @check_ushort() +unsigned short check_ushort() { return 0; } + +// CHECK-LABEL: define{{.*}} signext i32 @check_uint() +unsigned int check_uint() { return 0; } + +// CHECK-LABEL: define{{.*}} i64 @check_ulong() +unsigned long check_ulong() { return 0; } + +// CHECK-LABEL: define{{.*}} i64 @check_ulonglong() +unsigned long long check_ulonglong() { return 0; } + +// CHECK-LABEL: define{{.*}} float @check_float() +float check_float() { return 0; } + +// CHECK-LABEL: define{{.*}} double @check_double() +double check_double() { return 0; } + +// CHECK-LABEL: define{{.*}} fp128 @check_longdouble() +long double check_longdouble() { return 0; } + +/// Part 1: Scalar arguments and return value. + +/// 1. 1 < WOA <= GRLEN +/// a. Argument is passed in a single argument register, or on the stack by +/// value if none is available. +/// i. If the argument is floating-point type, the argument is passed in FAR. if +/// no FAR is available, it’s passed in GAR. If no GAR is available, it’s +/// passed on the stack. When passed in registers or on the stack, +/// floating-point types narrower than GRLEN bits are widened to GRLEN bits, +/// with the upper bits undefined. +/// ii. If the argument is integer or pointer type, the argument is passed in +/// GAR. If no GAR is available, it’s passed on the stack. When passed in +/// registers or on the stack, the unsigned integer scalars narrower than GRLEN +/// bits are zero-extended to GRLEN bits, and the signed integer scalars are +/// sign-extended. +/// 2. GRLEN < WOA ≤ 2 × GRLEN +/// a. The argument is passed in a pair of GAR, with the low-order GRLEN bits in +/// the lower-numbered register and the high-order GRLEN bits in the +/// higher-numbered register. If exactly one register is available, the +/// low-order GRLEN bits are passed in the register and the high-order GRLEN +/// bits are passed on the stack. If no GAR is available, it’s passed on the +/// stack. + +/// Note that most of these conventions are handled by the backend, so here we +/// only check the correctness of argument (or return value)'s sign/zero +/// extension attribute. + +// CHECK-LABEL: define{{.*}} signext i32 @f_scalar(i1 noundef zeroext %a, i8 noundef signext %b, i8 noundef zeroext %c, i16 noundef signext %d, i16 noundef zeroext %e, i32 noundef signext %f, i32 noundef signext %g, i64 noundef %h, i1 noundef zeroext %i, i8 noundef signext %j, i8 noundef zeroext %k, i16 noundef signext %l, i16 noundef zeroext %m, i32 noundef signext %n, i32 noundef signext %o, i64 noundef %p) +int f_scalar(_Bool a, int8_t b, uint8_t c, int16_t d, uint16_t e, int32_t f, + uint32_t g, int64_t h, _Bool i, int8_t j, uint8_t k, int16_t l, + uint16_t m, int32_t n, uint32_t o, int64_t p) { + return 0; +} + +/// Part 2: Structure arguments and return value. + +/// Empty structures are ignored by C compilers which support them as a +/// non-standard extension(same as union arguments and return values). Bits +/// unused due to padding, and bits past the end of a structure whose size in +/// bits is not divisible by GRLEN, are undefined. And the layout of the +/// structure on the stack is consistent with that in memory. + +/// Check empty structs are ignored. + +struct empty_s {}; + +// CHECK-LABEL: define{{.*}} void @f_empty_s() +struct empty_s f_empty_s(struct empty_s x) { + return x; +} + +/// 1. 0 < WOA ≤ GRLEN +/// a. The structure has only fixed-point members. If there is an available GAR, +/// the structure is passed through the GAR by value passing; If no GAR is +/// available, it’s passed on the stack. + +struct i16x4_s { + int16_t a, b, c, d; +}; + +// CHECK-LABEL: define{{.*}} i64 @f_i16x4_s(i64 %x.coerce) +struct i16x4_s f_i16x4_s(struct i16x4_s x) { + return x; +} + +/// b. The structure has only floating-point members: +/// i. One floating-point member. The argument is passed in a FAR; If no FAR is +/// available, the value is passed in a GAR; if no GAR is available, the value +/// is passed on the stack. + +struct f32x1_s { + float a; +}; + +struct f64x1_s { + double a; +}; + +// CHECK-LABEL: define{{.*}} float @f_f32x1_s(float %0) +struct f32x1_s f_f32x1_s(struct f32x1_s x) { + return x; +} + +// CHECK-LABEL: define{{.*}} double @f_f64x1_s(double %0) +struct f64x1_s f_f64x1_s(struct f64x1_s x) { + return x; +} + +/// ii. Two floating-point members. The argument is passed in a pair of +/// available FAR, with the low-order float member bits in the lower-numbered +/// FAR and the high-order float member bits in the higher-numbered FAR. If the +/// number of available FAR is less than 2, it’s passed in a GAR, and passed on +/// the stack if no GAR is available. + +struct f32x2_s { + float a, b; +}; + +// CHECK-LABEL: define{{.*}} { float, float } @f_f32x2_s(float %0, float %1) +struct f32x2_s f_f32x2_s(struct f32x2_s x) { + return x; +} + +/// c. The structure has both fixed-point and floating-point members, i.e. the +/// structure has one float member and... +/// i. Multiple fixed-point members. If there are available GAR, the structure +/// is passed in a GAR, and passed on the stack if no GAR is available. + +struct f32x1_i16x2_s { + float a; + int16_t b, c; +}; + +// CHECK-LABEL: define{{.*}} i64 @f_f32x1_i16x2_s(i64 %x.coerce) +struct f32x1_i16x2_s f_f32x1_i16x2_s(struct f32x1_i16x2_s x) { + return x; +} + +/// ii. Only one fixed-point member. If one FAR and one GAR are available, the +/// floating-point member of the structure is passed in the FAR, and the integer +/// member of the structure is passed in the GAR; If no floating-point register +/// but one GAR is available, it’s passed in GAR; If no GAR is available, it’s +/// passed on the stack. + +struct f32x1_i32x1_s { + float a; + int32_t b; +}; + +// CHECK-LABEL: define{{.*}} { float, i32 } @f_f32x1_i32x1_s(float %0, i32 %1) +struct f32x1_i32x1_s f_f32x1_i32x1_s(struct f32x1_i32x1_s x) { + return x; +} + +/// 2. GRLEN < WOA ≤ 2 × GRLEN +/// a. Only fixed-point members. +/// i. The argument is passed in a pair of available GAR, with the low-order +/// bits in the lower-numbered GAR and the high-order bits in the +/// higher-numbered GAR. If only one GAR is available, the low-order bits are in +/// the GAR and the high-order bits are on the stack, and passed on the stack if +/// no GAR is available. + +struct i64x2_s { + int64_t a, b; +}; + +// CHECK-LABEL: define{{.*}} [2 x i64] @f_i64x2_s([2 x i64] %x.coerce) +struct i64x2_s f_i64x2_s(struct i64x2_s x) { + return x; +} + +/// b. Only floating-point members. +/// i. The structure has one long double member or one double member and two +/// adjacent float members or 3-4 float members. The argument is passed in a +/// pair of available GAR, with the low-order bits in the lower-numbered GAR and +/// the high-order bits in the higher-numbered GAR. If only one GAR is +/// available, the low-order bits are in the GAR and the high-order bits are on +/// the stack, and passed on the stack if no GAR is available. + +struct f128x1_s { + long double a; +}; + +// CHECK-LABEL: define{{.*}} i128 @f_f128x1_s(i128 %x.coerce) +struct f128x1_s f_f128x1_s(struct f128x1_s x) { + return x; +} + +struct f64x1_f32x2_s { + double a; + float b, c; +}; + +// CHECK-LABEL: define{{.*}} [2 x i64] @f_f64x1_f32x2_s([2 x i64] %x.coerce) +struct f64x1_f32x2_s f_f64x1_f32x2_s(struct f64x1_f32x2_s x) { + return x; +} + +struct f32x3_s { + float a, b, c; +}; + +// CHECK-LABEL: define{{.*}} [2 x i64] @f_f32x3_s([2 x i64] %x.coerce) +struct f32x3_s f_f32x3_s(struct f32x3_s x) { + return x; +} + +struct f32x4_s { + float a, b, c, d; +}; + +// CHECK-LABEL: define{{.*}} [2 x i64] @f_f32x4_s([2 x i64] %x.coerce) +struct f32x4_s f_f32x4_s(struct f32x4_s x) { + return x; +} + +/// ii. The structure with two double members is passed in a pair of available +/// FARs. If no a pair of available FARs, it’s passed in GARs. A structure with +/// one double member and one float member is same. + +struct f64x2_s { + double a, b; +}; + +// CHECK-LABEL: define{{.*}} { double, double } @f_f64x2_s(double %0, double %1) +struct f64x2_s f_f64x2_s(struct f64x2_s x) { + return x; +} + +/// c. Both fixed-point and floating-point members. +/// i. The structure has one double member and only one fixed-point member. +/// A. If one FAR and one GAR are available, the floating-point member of the +/// structure is passed in the FAR, and the integer member of the structure is +/// passed in the GAR; If no floating-point registers but two GARs are +/// available, it’s passed in the two GARs; If only one GAR is available, the +/// low-order bits are in the GAR and the high-order bits are on the stack; And +/// it’s passed on the stack if no GAR is available. + +struct f64x1_i64x1_s { + double a; + int64_t b; +}; + +// CHECK-LABEL: define{{.*}} { double, i64 } @f_f64x1_i64x1_s(double %0, i64 %1) +struct f64x1_i64x1_s f_f64x1_i64x1_s(struct f64x1_i64x1_s x) { + return x; +} + +/// ii. Others +/// A. The argument is passed in a pair of available GAR, with the low-order +/// bits in the lower-numbered GAR and the high-order bits in the +/// higher-numbered GAR. If only one GAR is available, the low-order bits are in +/// the GAR and the high-order bits are on the stack, and passed on the stack if +/// no GAR is available. + +struct f64x1_i32x2_s { + double a; + int32_t b, c; +}; + +// CHECK-LABEL: define{{.*}} [2 x i64] @f_f64x1_i32x2_s([2 x i64] %x.coerce) +struct f64x1_i32x2_s f_f64x1_i32x2_s(struct f64x1_i32x2_s x) { + return x; +} + +struct f32x2_i32x2_s { + float a, b; + int32_t c, d; +}; + +// CHECK-LABEL: define{{.*}} [2 x i64] @f_f32x2_i32x2_s([2 x i64] %x.coerce) +struct f32x2_i32x2_s f_f32x2_i32x2_s(struct f32x2_i32x2_s x) { + return x; +} + +/// 3. WOA > 2 × GRLEN +/// a. It’s passed by reference and are replaced in the argument list with the +/// address. If there is an available GAR, the reference is passed in the GAR, +/// and passed on the stack if no GAR is available. + +struct i64x4_s { + int64_t a, b, c, d; +}; + +// CHECK-LABEL: define{{.*}} void @f_i64x4_s(ptr{{.*}} sret(%struct.i64x4_s) align 8 %agg.result, ptr{{.*}} %x) +struct i64x4_s f_i64x4_s(struct i64x4_s x) { + return x; +} + +struct f64x4_s { + double a, b, c, d; +}; + +// CHECK-LABEL: define{{.*}} void @f_f64x4_s(ptr{{.*}} sret(%struct.f64x4_s) align 8 %agg.result, ptr{{.*}} %x) +struct f64x4_s f_f64x4_s(struct f64x4_s x) { + return x; +} + +/// Part 3: Union arguments and return value. + +/// Check empty unions are ignored. + +union empty_u {}; + +// CHECK-LABEL: define{{.*}} void @f_empty_u() +union empty_u f_empty_u(union empty_u x) { + return x; +} + +/// Union is passed in GAR or stack. +/// 1. 0 < WOA ≤ GRLEN +/// a. The argument is passed in a GAR, or on the stack by value if no GAR is +/// available. + +union i32_f32_u { + int32_t a; + float b; +}; + +// CHECK-LABEL: define{{.*}} i64 @f_i32_f32_u(i64 %x.coerce) +union i32_f32_u f_i32_f32_u(union i32_f32_u x) { + return x; +} + +union i64_f64_u { + int64_t a; + double b; +}; + +// CHECK-LABEL: define{{.*}} i64 @f_i64_f64_u(i64 %x.coerce) +union i64_f64_u f_i64_f64_u(union i64_f64_u x) { + return x; +} + +/// 2. GRLEN < WOA ≤ 2 × GRLEN +/// a. The argument is passed in a pair of available GAR, with the low-order +/// bits in the lower-numbered GAR and the high-order bits in the +/// higher-numbered GAR. If only one GAR is available, the low-order bits are in +/// the GAR and the high-order bits are on the stack. The arguments are passed +/// on the stack when no GAR is available. + +union i128_f128_u { + __int128_t a; + long double b; +}; + +// CHECK-LABEL: define{{.*}} i128 @f_i128_f128_u(i128 %x.coerce) +union i128_f128_u f_i128_f128_u(union i128_f128_u x) { + return x; +} + +/// 3. WOA > 2 × GRLEN +/// a. It’s passed by reference and are replaced in the argument list with the +/// address. If there is an available GAR, the reference is passed in the GAR, +/// and passed on the stack if no GAR is available. + +union i64_arr3_u { + int64_t a[3]; +}; + +// CHECK-LABEL: define{{.*}} void @f_i64_arr3_u(ptr{{.*}} sret(%union.i64_arr3_u) align 8 %agg.result, ptr{{.*}} %x) +union i64_arr3_u f_i64_arr3_u(union i64_arr3_u x) { + return x; +} + +/// Part 4: Complex number arguments and return value. + +/// A complex floating-point number, or a structure containing just one complex +/// floating-point number, is passed as though it were a structure containing +/// two floating-point reals. + +// CHECK-LABEL: define{{.*}} { float, float } @f_floatcomplex(float noundef %x.coerce0, float noundef %x.coerce1) +float __complex__ f_floatcomplex(float __complex__ x) { return x; } + +// CHECK-LABEL: define{{.*}} { double, double } @f_doublecomplex(double noundef %x.coerce0, double noundef %x.coerce1) +double __complex__ f_doublecomplex(double __complex__ x) { return x; } + +struct floatcomplex_s { + float __complex__ c; +}; +// CHECK-LABEL: define{{.*}} { float, float } @f_floatcomplex_s(float %0, float %1) +struct floatcomplex_s f_floatcomplex_s(struct floatcomplex_s x) { + return x; +} + +struct doublecomplex_s { + double __complex__ c; +}; +// CHECK-LABEL: define{{.*}} { double, double } @f_doublecomplex_s(double %0, double %1) +struct doublecomplex_s f_doublecomplex_s(struct doublecomplex_s x) { + return x; +} + +/// Part 5: Variadic arguments. + +/// Variadic arguments are passed in GARs in the same manner as named arguments. + +int f_va_callee(int, ...); + +// CHECK-LABEL: define{{.*}} void @f_va_caller() +// CHECK: call signext i32 (i32, ...) @f_va_callee(i32 noundef signext 1, i32 noundef signext 2, i64 noundef 3, double noundef 4.000000e+00, double noundef 5.000000e+00, i64 {{.*}}, [2 x i64] {{.*}}) +void f_va_caller(void) { + f_va_callee(1, 2, 3LL, 4.0f, 5.0, (struct i16x4_s){6, 7, 8, 9}, + (struct i64x2_s){10, 11}); +} + +// CHECK-LABEL: @f_va_int( +// CHECK-NEXT: entry: +// CHECK-NEXT: [[FMT_ADDR:%.*]] = alloca ptr, align 8 +// CHECK-NEXT: [[VA:%.*]] = alloca ptr, align 8 +// CHECK-NEXT: [[V:%.*]] = alloca i32, align 4 +// CHECK-NEXT: store ptr [[FMT:%.*]], ptr [[FMT_ADDR]], align 8 +// CHECK-NEXT: call void @llvm.va_start(ptr [[VA]]) +// CHECK-NEXT: [[ARGP_CUR:%.*]] = load ptr, ptr [[VA]], align 8 +// CHECK-NEXT: [[ARGP_NEXT:%.*]] = getelementptr inbounds i8, ptr [[ARGP_CUR]], i64 8 +// CHECK-NEXT: store ptr [[ARGP_NEXT]], ptr [[VA]], align 8 +// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[ARGP_CUR]], align 8 +// CHECK-NEXT: store i32 [[TMP0]], ptr [[V]], align 4 +// CHECK-NEXT: call void @llvm.va_end(ptr [[VA]]) +// CHECK-NEXT: [[TMP1:%.*]] = load i32, ptr [[V]], align 4 +// CHECK-NEXT: ret i32 [[TMP1]] +int f_va_int(char *fmt, ...) { + __builtin_va_list va; + __builtin_va_start(va, fmt); + int v = __builtin_va_arg(va, int); + __builtin_va_end(va); + return v; +} + +/// Part 6. Structures with zero size fields (bitfields or arrays). + +/// Check that zero size fields in structure are ignored. +/// Note that this rule is not explicitly documented in ABI spec but it matches +/// GCC's behavior. + +struct f64x2_zsfs_s { + double a; + int : 0; + __int128_t : 0; + int b[0]; + __int128_t c[0]; + double d; +}; + +// CHECK-LABEL: define{{.*}} { double, double } @f_f64x2_zsfs_s(double %0, double %1) +struct f64x2_zsfs_s f_f64x2_zsfs_s(struct f64x2_zsfs_s x) { + return x; +} + diff --git a/clang/test/CodeGen/ext-int-cc.c b/clang/test/CodeGen/ext-int-cc.c --- a/clang/test/CodeGen/ext-int-cc.c +++ b/clang/test/CodeGen/ext-int-cc.c @@ -27,6 +27,8 @@ // RUN: %clang_cc1 -no-opaque-pointers -no-enable-noundef-analysis -triple arm64_32-apple-ios -O3 -disable-llvm-passes -emit-llvm -o - %s | FileCheck %s --check-prefixes=AARCH64 // RUN: %clang_cc1 -no-opaque-pointers -no-enable-noundef-analysis -triple arm64_32-apple-ios -target-abi darwinpcs -O3 -disable-llvm-passes -emit-llvm -o - %s | FileCheck %s --check-prefixes=AARCH64DARWIN // RUN: %clang_cc1 -no-opaque-pointers -no-enable-noundef-analysis -triple arm -O3 -disable-llvm-passes -emit-llvm -o - %s | FileCheck %s --check-prefixes=ARM +// RUN: %clang_cc1 -no-opaque-pointers -no-enable-noundef-analysis -triple loongarch64 -O3 -disable-llvm-passes -emit-llvm -o - %s | FileCheck %s --check-prefixes=LA64 +// RUN: %clang_cc1 -no-opaque-pointers -no-enable-noundef-analysis -triple loongarch32 -O3 -disable-llvm-passes -emit-llvm -o - %s | FileCheck %s --check-prefixes=LA32 // Make sure 128 and 64 bit versions are passed like integers. void ParamPassing(_BitInt(128) b, _BitInt(64) c) {} @@ -57,6 +59,8 @@ // AARCH64: define{{.*}} void @ParamPassing(i128 %{{.+}}, i64 %{{.+}}) // AARCH64DARWIN: define{{.*}} void @ParamPassing(i128 %{{.+}}, i64 %{{.+}}) // ARM: define{{.*}} arm_aapcscc void @ParamPassing(i128* byval(i128) align 8 %{{.+}}, i64 %{{.+}}) +// LA64: define{{.*}} void @ParamPassing(i128 %{{.+}}, i64 %{{.+}}) +// LA32: define{{.*}} void @ParamPassing(i128* %{{.+}}, i64 %{{.+}}) void ParamPassing2(_BitInt(127) b, _BitInt(63) c) {} // LIN64: define{{.*}} void @ParamPassing2(i64 %{{.+}}, i64 %{{.+}}, i64 %{{.+}}) @@ -86,6 +90,8 @@ // AARCH64: define{{.*}} void @ParamPassing2(i127 %{{.+}}, i63 %{{.+}}) // AARCH64DARWIN: define{{.*}} void @ParamPassing2(i127 %{{.+}}, i63 %{{.+}}) // ARM: define{{.*}} arm_aapcscc void @ParamPassing2(i127* byval(i127) align 8 %{{.+}}, i63 %{{.+}}) +// LA64: define{{.*}} void @ParamPassing2(i127 %{{.+}}, i63 signext %{{.+}}) +// LA32: define{{.*}} void @ParamPassing2(i127* %{{.+}}, i63 %{{.+}}) // Make sure we follow the signext rules for promotable integer types. void ParamPassing3(_BitInt(15) a, _BitInt(31) b) {} @@ -116,6 +122,8 @@ // AARCH64: define{{.*}} void @ParamPassing3(i15 %{{.+}}, i31 %{{.+}}) // AARCH64DARWIN: define{{.*}} void @ParamPassing3(i15 signext %{{.+}}, i31 signext %{{.+}}) // ARM: define{{.*}} arm_aapcscc void @ParamPassing3(i15 signext %{{.+}}, i31 signext %{{.+}}) +// LA64: define{{.*}} void @ParamPassing3(i15 signext %{{.+}}, i31 signext %{{.+}}) +// LA32: define{{.*}} void @ParamPassing3(i15 signext %{{.+}}, i31 signext %{{.+}}) #if __BITINT_MAXWIDTH__ > 128 // When supported, bit-precise types that are >128 are passed indirectly. Note, @@ -150,6 +158,8 @@ // AARCH64-NOT: define{{.*}} void @ParamPassing4(i129* byval(i129) align 8 %{{.+}}) // AARCH64DARWIN-NOT: define{{.*}} void @ParamPassing4(i129* byval(i129) align 8 %{{.+}}) // ARM-NOT: define{{.*}} arm_aapcscc void @ParamPassing4(i129* byval(i129) align 8 %{{.+}}) +// LA64-NOT: define{{.*}} void @ParamPassing4(i129* %{{.+}}) +// LA32-NOT: define{{.*}} void @ParamPassing4(i129* %{{.+}}) #endif _BitInt(63) ReturnPassing(void){} @@ -180,6 +190,8 @@ // AARCH64: define{{.*}} i63 @ReturnPassing( // AARCH64DARWIN: define{{.*}} i63 @ReturnPassing( // ARM: define{{.*}} arm_aapcscc i63 @ReturnPassing( +// LA64: define{{.*}} signext i63 @ReturnPassing( +// LA32: define{{.*}} i63 @ReturnPassing( _BitInt(64) ReturnPassing2(void){} // LIN64: define{{.*}} i64 @ReturnPassing2( @@ -209,6 +221,8 @@ // AARCH64: define{{.*}} i64 @ReturnPassing2( // AARCH64DARWIN: define{{.*}} i64 @ReturnPassing2( // ARM: define{{.*}} arm_aapcscc i64 @ReturnPassing2( +// LA64: define{{.*}} i64 @ReturnPassing2( +// LA32: define{{.*}} i64 @ReturnPassing2( _BitInt(127) ReturnPassing3(void){} // LIN64: define{{.*}} { i64, i64 } @ReturnPassing3( @@ -240,6 +254,8 @@ // AARCH64: define{{.*}} i127 @ReturnPassing3( // AARCH64DARWIN: define{{.*}} i127 @ReturnPassing3( // ARM: define{{.*}} arm_aapcscc void @ReturnPassing3(i127* noalias sret +// LA64: define{{.*}} i127 @ReturnPassing3( +// LA32: define{{.*}} void @ReturnPassing3(i127* noalias sret _BitInt(128) ReturnPassing4(void){} // LIN64: define{{.*}} { i64, i64 } @ReturnPassing4( @@ -269,6 +285,8 @@ // AARCH64: define{{.*}} i128 @ReturnPassing4( // AARCH64DARWIN: define{{.*}} i128 @ReturnPassing4( // ARM: define{{.*}} arm_aapcscc void @ReturnPassing4(i128* noalias sret +// LA64: define{{.*}} i128 @ReturnPassing4( +// LA32: define{{.*}} void @ReturnPassing4(i128* noalias sret #if __BITINT_MAXWIDTH__ > 128 _BitInt(129) ReturnPassing5(void){} @@ -299,6 +317,8 @@ // AARCH64-NOT: define{{.*}} void @ReturnPassing5(i129* noalias sret // AARCH64DARWIN-NOT: define{{.*}} void @ReturnPassing5(i129* noalias sret // ARM-NOT: define{{.*}} arm_aapcscc void @ReturnPassing5(i129* noalias sret +// LA64-NOT: define{{.*}} void @ReturnPassing5(i129* noalias sret +// LA32-NOT: define{{.*}} void @ReturnPassing5(i129* noalias sret // SparcV9 is odd in that it has a return-size limit of 256, not 128 or 64 // like other platforms, so test to make sure this behavior will still work. diff --git a/clang/test/CodeGenCXX/LoongArch/abi-lp64d-struct-inherit.cpp b/clang/test/CodeGenCXX/LoongArch/abi-lp64d-struct-inherit.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/LoongArch/abi-lp64d-struct-inherit.cpp @@ -0,0 +1,95 @@ +// RUN: %clang_cc1 -triple loongarch64 -target-feature +f -target-feature +d -target-abi lp64d \ +// RUN: -emit-llvm %s -o - | FileCheck %s + +#include + +/// Ensure that fields inherited from a parent struct are treated in the same +/// way as fields directly in the child for the purposes of LoongArch ABI rules. + +struct parent1_int32_s { + int32_t i1; +}; + +struct child1_int32_s : parent1_int32_s { + int32_t i2; +}; + +// CHECK-LABEL: define{{.*}} i64 @_Z30int32_int32_struct_inheritance14child1_int32_s(i64 %a.coerce) +struct child1_int32_s int32_int32_struct_inheritance(struct child1_int32_s a) { + return a; +} + +struct parent2_int32_s { + int32_t i1; +}; + +struct child2_float_s : parent2_int32_s { + float f1; +}; + +// CHECK-LABEL: define{{.*}} { i32, float } @_Z30int32_float_struct_inheritance14child2_float_s(i32 %0, float %1) +struct child2_float_s int32_float_struct_inheritance(struct child2_float_s a) { + return a; +} + +struct parent3_float_s { + float f1; +}; + +struct child3_int64_s : parent3_float_s { + int64_t i1; +}; + +// CHECK-LABEL: define{{.*}} { float, i64 } @_Z30float_int64_struct_inheritance14child3_int64_s(float %0, i64 %1) +struct child3_int64_s float_int64_struct_inheritance(struct child3_int64_s a) { + return a; +} + +struct parent4_double_s { + double d1; +}; + +struct child4_double_s : parent4_double_s { + double d1; +}; + +// CHECK-LABEL: define{{.*}} { double, double } @_Z32double_double_struct_inheritance15child4_double_s(double %0, double %1) +struct child4_double_s double_double_struct_inheritance(struct child4_double_s a) { + return a; +} + +/// When virtual inheritance is used, the resulting struct isn't eligible for +/// passing in registers. + +struct parent5_virtual_s { + int32_t i1; +}; + +struct child5_virtual_s : virtual parent5_virtual_s { + float f1; +}; + +// CHECK-LABEL: define{{.*}} void @_ZN16child5_virtual_sC1EOS_(ptr noundef nonnull align 8 dereferenceable(12) %this, ptr noundef nonnull align 8 dereferenceable(12) %0) +struct child5_virtual_s int32_float_virtual_struct_inheritance(struct child5_virtual_s a) { + return a; +} + +/// Check for correct lowering in the presence of diamond inheritance. + +struct parent6_float_s { + float f1; +}; + +struct child6a_s : parent6_float_s { +}; + +struct child6b_s : parent6_float_s { +}; + +struct grandchild_6_s : child6a_s, child6b_s { +}; + +// CHECK-LABEL: define{{.*}} { float, float } @_Z38float_float_diamond_struct_inheritance14grandchild_6_s(float %0, float %1) +struct grandchild_6_s float_float_diamond_struct_inheritance(struct grandchild_6_s a) { + return a; +}