forked from OSchip/llvm-project
[Clang][LoongArch] Implement ABI lowering
Reuse most of RISCV's implementation with several exceptions:
1. Assign signext/zeroext attribute to args passed in stack.
On RISCV, integer scalars passed in registers have signext/zeroext
when promoted, but are anyext if passed on the stack. This is defined
in early RISCV ABI specification. But after this change [1], integers
should also be signext/zeroext if passed on the stack. So I think
RISCV's ABI lowering should be updated [2].
While in LoongArch ABI spec, we can see that integer scalars narrower
than GRLEN bits are zero/sign-extended no matter passed in registers
or on the stack.
2. Zero-width bit fields are ignored.
This matches GCC's behavior but it hasn't been documented in ABI sepc.
See https://gcc.gnu.org/r12-8294.
3. `char` is signed by default.
There is another difference worth mentioning is that `char` is signed
by default on LoongArch while it is unsigned on RISCV.
This patch also adds `_BitInt` type support to LoongArch and handle it
in LoongArchABIInfo::classifyArgumentType.
[1] cec39a064e
[2] https://github.com/llvm/llvm-project/issues/57261
Differential Revision: https://reviews.llvm.org/D132285
This commit is contained in:
parent
b76da14b3b
commit
7d88a05cc0
|
@ -55,6 +55,8 @@ public:
|
|||
|
||||
bool validateAsmConstraint(const char *&Name,
|
||||
TargetInfo::ConstraintInfo &Info) const override;
|
||||
|
||||
bool hasBitIntType() const override { return true; }
|
||||
};
|
||||
|
||||
class LLVM_LIBRARY_VISIBILITY LoongArch32TargetInfo
|
||||
|
|
|
@ -11598,6 +11598,438 @@ public:
|
|||
|
||||
}
|
||||
|
||||
// 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<ComplexType>()->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<ComplexType>()) {
|
||||
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<RecordType>()) {
|
||||
// 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<CXXRecordDecl>(RD)) {
|
||||
for (const CXXBaseSpecifier &B : CXXRD->bases()) {
|
||||
const auto *BDecl =
|
||||
cast<CXXRecordDecl>(B.getType()->castAs<RecordType>()->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<llvm::Type *, 3> CoerceElts;
|
||||
SmallVector<llvm::Type *, 2> 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<ComplexType>()->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<EnumType>())
|
||||
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<BitIntType>()) {
|
||||
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<LoongArchABIInfo>(CGT, GRLen, FRLen)) {}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Driver code
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
@ -11829,6 +12261,17 @@ const TargetCodeGenInfo &CodeGenModule::getTargetCodeGenInfo() {
|
|||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/// 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;
|
||||
}
|
||||
|
|
@ -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 @@ void ParamPassing(_BitInt(128) b, _BitInt(64) c) {}
|
|||
// 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 @@ void ParamPassing2(_BitInt(127) b, _BitInt(63) c) {}
|
|||
// 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 @@ void ParamPassing3(_BitInt(15) a, _BitInt(31) b) {}
|
|||
// 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 @@ void ParamPassing4(_BitInt(129) a) {}
|
|||
// 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 @@ _BitInt(63) ReturnPassing(void){}
|
|||
// 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 @@ _BitInt(64) ReturnPassing2(void){}
|
|||
// 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 @@ _BitInt(127) ReturnPassing3(void){}
|
|||
// 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 @@ _BitInt(128) ReturnPassing4(void){}
|
|||
// 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 @@ _BitInt(129) ReturnPassing5(void){}
|
|||
// 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.
|
||||
|
|
|
@ -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 <stdint.h>
|
||||
|
||||
/// 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;
|
||||
}
|
Loading…
Reference in New Issue