https://github.com/adams381 updated https://github.com/llvm/llvm-project/pull/230302
>From e3d27550ff6effab245f719b6ddfa45af250154d Mon Sep 17 00:00:00 2001 From: Adam Smith <[email protected]> Date: Thu, 8 Oct 2026 15:12:00 -0700 Subject: [PATCH 1/3] [CIR] Pass x87 long double vectors on x86_64 Vectors of x87 long double now go through x86_64 calling-convention lowering instead of hitting NYI. The ABI library sizes their elements at 128 bits like clang does, so the signatures match classic codegen. Unions are still moved as a value of their storage type, and a long double stores only 10 of its 16 bytes. So a union holding an x87 value next to another member stays NYI unless it's a plain long double and the other members fit in those 10 bytes. That also stops a silent miscompile of unions like `union { long double ld; char c[16]; }`. Assisted-by: Cursor / Claude Opus 5.5 --- .../CIR/Dialect/Builder/CIRBaseBuilder.h | 25 +- clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp | 16 +- .../Transforms/CallConvLoweringPass.cpp | 107 ++++++- .../call-conv-lowering-x86_64-x87-vector.c | 291 ++++++++++++++++++ .../CodeGen/vector-logical-long-double.cpp | 66 ++++ .../abi-lowering/x86_64-aggregate-nyi.cir | 114 ++++++- .../Transforms/abi-lowering/x86_64-vector.cir | 18 ++ 7 files changed, 602 insertions(+), 35 deletions(-) create mode 100644 clang/test/CIR/CodeGen/call-conv-lowering-x86_64-x87-vector.c create mode 100644 clang/test/CIR/CodeGen/vector-logical-long-double.cpp diff --git a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h index 36d583cfe9fbed..36140601caab21 100644 --- a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h +++ b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h @@ -906,18 +906,35 @@ class CIRBaseBuilderTy : public mlir::OpBuilder { return cir::CmpOp::create(*this, loc, kind, lhs, rhs, fenv); } + /// Compares \p lhs and \p rhs element-wise. Each result element is a + /// signed integer of the operand element's bit width, 80 for x87 long + /// double. cir::VecCmpOp createVecCompare(mlir::Location loc, cir::CmpOpKind kind, mlir::Value lhs, mlir::Value rhs) { VectorType vecCast = mlir::cast<VectorType>(lhs.getType()); IntType integralTy = getSIntNTy(getCIRIntOrFloatBitWidth(vecCast.getElementType())); - VectorType integralVecTy = - cir::VectorType::get(integralTy, vecCast.getSize()); + return createVecCompare(loc, + cir::VectorType::get(integralTy, vecCast.getSize()), + kind, lhs, rhs); + } + + /// Compares \p lhs and \p rhs element-wise, producing \p resultTy, which + /// must be a vector of integers or bools with as many elements as \p lhs. + /// The result element width can then differ from the operand element's + /// bit width, as for x87 long double, whose result elements are 128 bits. + cir::VecCmpOp createVecCompare(mlir::Location loc, VectorType resultTy, + cir::CmpOpKind kind, mlir::Value lhs, + mlir::Value rhs) { + assert(mlir::cast<VectorType>(lhs.getType()).getSize() == + resultTy.getSize() && + cir::isIntOrBoolType(resultTy.getElementType()) && + "vector compare result must be an int or bool vector of the " + "operand length"); cir::FenvAttr fenv; if (cir::isFPOrVectorOfFPType(lhs.getType())) fenv = getConstrainedFPAttr(); - return cir::VecCmpOp::create(*this, loc, integralVecTy, kind, lhs, rhs, - fenv); + return cir::VecCmpOp::create(*this, loc, resultTy, kind, lhs, rhs, fenv); } mlir::Value createIsNaN(mlir::Location loc, mlir::Value operand) { diff --git a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp index 836f27cf5345ef..59d14d724dbfc5 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp @@ -1428,8 +1428,10 @@ class ScalarExprEmitter : public StmtVisitor<ScalarExprEmitter, mlir::Value> { mlir::Value rhs = Visit(e->getRHS()); auto cmpOpKind = cir::CmpOpKind::ne; - lhs = builder.createVecCompare(loc, cmpOpKind, lhs, zeroVec); - rhs = builder.createVecCompare(loc, cmpOpKind, rhs, zeroVec); + auto resultTy = + mlir::cast<cir::VectorType>(cgf.convertType(e->getType())); + lhs = builder.createVecCompare(loc, resultTy, cmpOpKind, lhs, zeroVec); + rhs = builder.createVecCompare(loc, resultTy, cmpOpKind, rhs, zeroVec); return builder.createAnd(loc, lhs, rhs); } @@ -1474,8 +1476,10 @@ class ScalarExprEmitter : public StmtVisitor<ScalarExprEmitter, mlir::Value> { mlir::Value rhs = Visit(e->getRHS()); auto cmpOpKind = cir::CmpOpKind::ne; - lhs = builder.createVecCompare(loc, cmpOpKind, lhs, zeroVec); - rhs = builder.createVecCompare(loc, cmpOpKind, rhs, zeroVec); + auto resultTy = + mlir::cast<cir::VectorType>(cgf.convertType(e->getType())); + lhs = builder.createVecCompare(loc, resultTy, cmpOpKind, lhs, zeroVec); + rhs = builder.createVecCompare(loc, resultTy, cmpOpKind, rhs, zeroVec); return builder.createOr(loc, lhs, rhs); } @@ -3005,7 +3009,9 @@ mlir::Value ScalarExprEmitter::VisitUnaryLNot(const UnaryOperator *e) { mlir::Location loc = cgf.getLoc(e->getExprLoc()); auto operVecTy = mlir::cast<cir::VectorType>(oper.getType()); mlir::Value zeroVec = builder.getNullValue(operVecTy, loc); - return builder.createVecCompare(loc, cir::CmpOpKind::eq, oper, zeroVec); + auto resultTy = mlir::cast<cir::VectorType>(cgf.convertType(e->getType())); + return builder.createVecCompare(loc, resultTy, cir::CmpOpKind::eq, oper, + zeroVec); } // Compare operand to zero. diff --git a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp index 402cd09afd6370..beeab4bf4d5e3d 100644 --- a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp +++ b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp @@ -159,6 +159,55 @@ static bool hasIncompleteRecordByValue(mlir::Type ty) { return false; } +/// Whether \p ty has the x87 80-bit format, wrapped in LongDoubleType or not. +static bool isX87LongDouble(mlir::Type ty) { + auto fpTy = dyn_cast<cir::FPTypeInterface>(ty); + return fpTy && + &fpTy.getFloatSemantics() == &llvm::APFloat::x87DoubleExtended(); +} + +/// Whether \p ty is, or holds by value, an x87 long double, alone or as the +/// element of a _Complex or a vector. +static bool holdsX87LongDouble(mlir::Type ty) { + if (isa<cir::FPTypeInterface>(ty)) + return isX87LongDouble(ty); + if (auto complexTy = dyn_cast<cir::ComplexType>(ty)) + return holdsX87LongDouble(complexTy.getElementType()); + if (auto vecTy = dyn_cast<cir::VectorType>(ty)) + return holdsX87LongDouble(vecTy.getElementType()); + if (auto arrTy = dyn_cast<cir::ArrayType>(ty)) + return arrTy.getSize() && holdsX87LongDouble(arrTy.getElementType()); + if (auto recTy = dyn_cast<cir::RecordType>(ty)) + return llvm::any_of(recTy.getMembers(), holdsX87LongDouble); + return false; +} + +/// Returns how many bytes from the start of \p ty a value of it reaches. A +/// scalar reaches the end of its storage, a record the furthest end of its +/// non-padding members, and an array the end of its last element, so padding +/// at the end of a record does not count. +static uint64_t getDataExtentInBytes(mlir::Type ty, const DataLayout &dl) { + if (auto recTy = dyn_cast<cir::RecordType>(ty)) { + uint64_t extent = 0; + for (auto [idx, memberTy, kind] : + llvm::enumerate(recTy.getMembers(), recTy.getMemberKinds())) { + if (kind == cir::RecordMemberKind::Pad) + continue; + extent = std::max(extent, recTy.getElementOffset(dl, idx) + + getDataExtentInBytes(memberTy, dl)); + } + return extent; + } + if (auto arrTy = dyn_cast<cir::ArrayType>(ty)) { + if (!arrTy.getSize()) + return 0; + mlir::Type elemTy = arrTy.getElementType(); + return (arrTy.getSize() - 1) * dl.getTypeSize(elemTy).getFixedValue() + + getDataExtentInBytes(elemTy, dl); + } + return dl.getTypeSize(ty).getFixedValue(); +} + /// The CIR types the x86_64 bridge handles. Scalars: an integer up to 128 /// bits (including `__int128`), a `_BitInt` of any width, pointer, vtable /// pointer, bool, void, or any floating-point type. Aggregates: a complete @@ -205,24 +254,20 @@ static bool isSupportedType(mlir::Type ty, const DataLayout &dl) { // x86_64 has no calling convention for one. if (vecTy.getIsScalable()) return false; - // The classifier sizes a vector as element count times element width, so - // an element is only usable where that width is the one clang gives it. - // It is not for bool (a bit to clang, a byte here), for a _BitInt narrower - // than a byte (clang rounds to the storage container), or for x87 long - // double (80 bits here against clang's 128). + // The classifier and clang have to agree on the vector's size, which they + // do not for a bool element (a bit to clang, a byte to the classifier). + // A _BitInt element narrower than a byte has no memory representation in + // CIR's lowering to LLVM. mlir::Type elemTy = vecTy.getElementType(); if (auto elemInt = dyn_cast<cir::IntType>(elemTy)) { if (elemInt.getWidth() % 8) return false; - } else if (auto elemFp = dyn_cast<cir::FPTypeInterface>(elemTy)) { - if (&elemFp.getFloatSemantics() == &llvm::APFloat::x87DoubleExtended()) - return false; - } else { + } else if (!isa<cir::FPTypeInterface>(elemTy)) { return false; } - // Clang also rounds the vector's own width up to a power of two, and the - // classifier branches on the exact width, so a three-char vector would be - // classified at 24 bits where clang uses 32. + // The classifier rounds the vector's own width up to a power of two, as + // clang does, and the CIR data layout does not, so a three-char vector + // would get a 32-bit coercion for a value CIR gives 24 bits. if (!llvm::isPowerOf2_64(dl.getTypeSizeInBits(ty).getFixedValue())) return false; return isSupportedType(elemTy, dl); @@ -261,6 +306,26 @@ static bool isSupportedType(mlir::Type ty, const DataLayout &dl) { // there is no member here to build the Indirect coercion from. if (members.empty() && recordBits > 128) return false; + // A union is moved as a value of its storage type, and an x87 value + // stores fewer bytes than it occupies, so the bytes another member + // holds past them would be lost. A long double stores its first 10 + // bytes, so it can share a union with other long double members and + // with members whose data ends within those bytes, provided it is the + // storage type. An x87 value held any other way, in a _Complex, a + // vector, a record or an array, is measured at its full storage, which + // reaches past them. + if (members.size() > 1 && llvm::any_of(members, holdsX87LongDouble)) { + uint64_t x87StoreBytes = llvm::APFloat::semanticsSizeInBits( + llvm::APFloat::x87DoubleExtended()) / + 8; + if (!isX87LongDouble( + mlir::cast<cir::UnionType>(recTy).getUnionStorageType(dl)) || + !llvm::all_of(members, [&](mlir::Type m) { + return isX87LongDouble(m) || + getDataExtentInBytes(m, dl) <= x87StoreBytes; + })) + return false; + } // A `_BitInt` access unit past an eightbyte has byte-array storage in // CIR and integer storage in classic CodeGen, so the narrowing walk // finds an i8 in the second eightbyte where classic keeps an i64. @@ -342,6 +407,19 @@ static mlir::Type abiTypeToCIR(const llvm::abi::Type *ty, MLIRContext *ctx) { .Default([](const llvm::abi::Type *) -> mlir::Type { return nullptr; }); } +/// Returns \p ty with a LongDoubleType vector element replaced by its +/// underlying format, which is the element abiTypeToCIR builds for the same +/// float. +static mlir::Type unwrapLongDoubleVectorElement(mlir::Type ty) { + if (auto vecTy = dyn_cast<cir::VectorType>(ty)) { + if (auto longDoubleTy = + dyn_cast<cir::LongDoubleType>(vecTy.getElementType())) + return cir::VectorType::get(longDoubleTy.getUnderlying(), vecTy.getSize(), + vecTy.getIsScalable()); + } + return ty; +} + /// Map a CIR type to an llvm::abi::Type. classifyX86_64Function pre-filters /// the signature, so only the scalar and struct/array types handled here can /// reach this function. @@ -605,13 +683,14 @@ convertABIArgInfo(const llvm::abi::ArgInfo &info, MLIRContext *ctx, mlir::Type coerced = abiTypeToCIR(coerceAbi, ctx); if (!coerced) return std::nullopt; + bool coerceIsNatural = coerced == unwrapLongDoubleVectorElement(origTy); // An offset would be lost here, since a coercion to the value's own type // is indistinguishable from no coercion at all. - if (offset && coerced == origTy) + if (offset && coerceIsNatural) return std::nullopt; // Coercing a value to the type it already has would add a memory round // trip for nothing. - if (comparesAgainstCoerce && coerced == origTy) + if (comparesAgainstCoerce && coerceIsNatural) return ArgClassification::getDirect(); ArgClassification classified = ArgClassification::getDirect(coerced, offset); diff --git a/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-x87-vector.c b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-x87-vector.c new file mode 100644 index 00000000000000..dc1623f2df4d72 --- /dev/null +++ b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-x87-vector.c @@ -0,0 +1,291 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-psabi -fclangir -emit-cir %s -o %t.cir +// RUN: FileCheck --check-prefixes=CIR,CIR-SSE --input-file=%t.cir %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-psabi -fclangir -emit-llvm %s -o %t-cir.ll +// RUN: FileCheck --check-prefixes=LLVM,LLVM-SSE --input-file=%t-cir.ll %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-psabi -emit-llvm %s -o %t.ll +// RUN: FileCheck --check-prefixes=LLVM,LLVM-SSE --input-file=%t.ll %s + +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-psabi -target-feature +avx -fclangir -emit-cir %s -o %t-avx.cir +// RUN: FileCheck --check-prefixes=CIR,CIR-AVX --input-file=%t-avx.cir %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-psabi -target-feature +avx -fclangir -emit-llvm %s -o %t-avx-cir.ll +// RUN: FileCheck --check-prefixes=LLVM,LLVM-AVX --input-file=%t-avx-cir.ll %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-psabi -target-feature +avx -emit-llvm %s -o %t-avx.ll +// RUN: FileCheck --check-prefixes=LLVM,LLVM-AVX --input-file=%t-avx.ll %s + +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-psabi -target-feature +avx512f -fclangir -emit-cir %s -o %t-avx512.cir +// RUN: FileCheck --check-prefixes=CIR,CIR-AVX512 --input-file=%t-avx512.cir %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-psabi -target-feature +avx512f -fclangir -emit-llvm %s -o %t-avx512-cir.ll +// RUN: FileCheck --check-prefixes=LLVM,LLVM-AVX512 --input-file=%t-avx512-cir.ll %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-psabi -target-feature +avx512f -emit-llvm %s -o %t-avx512.ll +// RUN: FileCheck --check-prefixes=LLVM,LLVM-AVX512 --input-file=%t-avx512.ll %s + +typedef long double ld1 __attribute__((vector_size(16))); +typedef long double ld2 __attribute__((vector_size(32))); +typedef long double ld4 __attribute__((vector_size(64))); + +// A one-element x87 vector occupies 16 bytes and passes in a register as +// itself, so the value keeps its long double element and is not coerced. +ld1 take_ld1(ld1 v) { return v; } + +// CIR: cir.func {{.*}}@take_ld1(%arg0: !cir.vector<1 x !cir.long_double<!cir.f80>> {llvm.noundef} loc({{[^)]+}})) -> !cir.vector<1 x !cir.long_double<!cir.f80>> +// CIR: cir.store %arg0, %{{.+}} : !cir.vector<1 x !cir.long_double<!cir.f80>>, !cir.ptr<!cir.vector<1 x !cir.long_double<!cir.f80>>> +// LLVM: define dso_local <1 x x86_fp80> @take_ld1(<1 x x86_fp80> noundef %{{[^,)]+}}) + +ld1 call_ld1(ld1 v) { return take_ld1(v); } + +// CIR: cir.call @take_ld1(%{{.+}}) : (!cir.vector<1 x !cir.long_double<!cir.f80>> {llvm.noundef}) -> !cir.vector<1 x !cir.long_double<!cir.f80>> +// LLVM: call <1 x x86_fp80> @take_ld1(<1 x x86_fp80> noundef %{{[^,)]+}}) + +// A record holding only that vector, directly, in a one-element array, as a +// union member, or inside another record, is coerced to a vector of the +// underlying x87 format. +struct S1 { ld1 v; }; +struct S1 take_s1(struct S1 s) { return s; } + +// CIR: cir.func {{.*}}@take_s1(%arg0: !cir.vector<1 x !cir.f80> loc({{[^)]+}})) -> !cir.vector<1 x !cir.f80> +// LLVM: define dso_local <1 x x86_fp80> @take_s1(<1 x x86_fp80> %{{[^,)]+}}) + +struct S1 call_s1(struct S1 s) { return take_s1(s); } + +// CIR: cir.call @take_s1(%{{.+}}) : (!cir.vector<1 x !cir.f80>) -> !cir.vector<1 x !cir.f80> +// LLVM: call <1 x x86_fp80> @take_s1(<1 x x86_fp80> %{{[^,)]+}}) + +struct A1 { ld1 a[1]; }; +struct A1 take_a1(struct A1 a) { return a; } + +// CIR: cir.func {{.*}}@take_a1(%arg0: !cir.vector<1 x !cir.f80> loc({{[^)]+}})) -> !cir.vector<1 x !cir.f80> +// LLVM: define dso_local <1 x x86_fp80> @take_a1(<1 x x86_fp80> %{{[^,)]+}}) + +union U1 { ld1 v; }; +union U1 take_u1(union U1 u) { return u; } + +// CIR: cir.func {{.*}}@take_u1(%arg0: !cir.vector<1 x !cir.f80> loc({{[^)]+}})) -> !cir.vector<1 x !cir.f80> +// LLVM: define dso_local <1 x x86_fp80> @take_u1(<1 x x86_fp80> %{{[^,)]+}}) + +struct N1 { struct S1 s; }; +struct N1 take_n1(struct N1 n) { return n; } + +// CIR: cir.func {{.*}}@take_n1(%arg0: !cir.vector<1 x !cir.f80> loc({{[^)]+}})) -> !cir.vector<1 x !cir.f80> +// LLVM: define dso_local <1 x x86_fp80> @take_n1(<1 x x86_fp80> %{{[^,)]+}}) + +// A struct holding anything past the one vector is larger than 16 bytes, so +// it passes in memory. +struct P2 { ld1 a; ld1 b; }; +struct P2 take_p2(struct P2 p) { return p; } + +// CIR: cir.func {{.*}}@take_p2(%arg0: !cir.ptr<!rec_P2> {llvm.align = 16 : i64, llvm.dead_on_unwind, llvm.noalias, llvm.sret = !rec_P2, llvm.writable} loc({{[^)]+}}), %arg1: !cir.ptr<!rec_P2> {llvm.align = 16 : i64, llvm.byval = !rec_P2, llvm.noundef} loc({{[^)]+}})) +// LLVM: define dso_local void @take_p2(ptr dead_on_unwind noalias writable sret(%struct.P2) align 16 %{{[^,)]+}}, ptr noundef byval(%struct.P2) align 16 %{{[^,)]+}}) + +struct M1 { int i; ld1 v; }; +struct M1 take_m1(struct M1 m) { return m; } + +// CIR: cir.func {{.*}}@take_m1(%arg0: !cir.ptr<!rec_M1> {llvm.align = 16 : i64, llvm.dead_on_unwind, llvm.noalias, llvm.sret = !rec_M1, llvm.writable} loc({{[^)]+}}), %arg1: !cir.ptr<!rec_M1> {llvm.align = 16 : i64, llvm.byval = !rec_M1, llvm.noundef} loc({{[^)]+}})) +// LLVM: define dso_local void @take_m1(ptr dead_on_unwind noalias writable sret(%struct.M1) align 16 %{{[^,)]+}}, ptr noundef byval(%struct.M1) align 16 %{{[^,)]+}}) + +// An unnamed argument up to 16 bytes is passed the same way as a named one. +void var(int n, ...); +void call_var(struct S1 s) { var(1, s); } + +// CIR: cir.call @var(%{{.+}}, %{{.+}}) : (!s32i {llvm.noundef}, !cir.vector<1 x !cir.f80>) -> () +// LLVM: call void (i32, ...) @var(i32 noundef 1, <1 x x86_fp80> %{{[^,)]+}}) + +void var_ld1(ld1 v) { var(1, v); } + +// CIR: cir.call @var(%{{.+}}, %{{.+}}) : (!s32i {llvm.noundef}, !cir.vector<1 x !cir.long_double<!cir.f80>> {llvm.noundef}) -> () +// LLVM: call void (i32, ...) @var(i32 noundef 1, <1 x x86_fp80> noundef %{{[^,)]+}}) + +// A wider vector takes the same IR form unnamed as named: it classifies as +// memory when unnamed, but a vector is still passed directly. +void var_ld2(ld2 v) { var(1, v); } + +// CIR-SSE: cir.call @var(%{{.+}}, %{{.+}}) : (!s32i {llvm.noundef}, !cir.ptr<!cir.vector<2 x !cir.long_double<!cir.f80>>> {llvm.align = 32 : i64, llvm.byval = !cir.vector<2 x !cir.long_double<!cir.f80>>, llvm.noundef}) -> () +// CIR-AVX: cir.call @var(%{{.+}}, %{{.+}}) : (!s32i {llvm.noundef}, !cir.vector<2 x !cir.long_double<!cir.f80>> {llvm.noundef}) -> () +// CIR-AVX512: cir.call @var(%{{.+}}, %{{.+}}) : (!s32i {llvm.noundef}, !cir.vector<2 x !cir.long_double<!cir.f80>> {llvm.noundef}) -> () +// LLVM-SSE: call void (i32, ...) @var(i32 noundef 1, ptr noundef byval(<2 x x86_fp80>) align 32 %{{[^,)]+}}) +// LLVM-AVX: call void (i32, ...) @var(i32 noundef 1, <2 x x86_fp80> noundef %{{[^,)]+}}) +// LLVM-AVX512: call void (i32, ...) @var(i32 noundef 1, <2 x x86_fp80> noundef %{{[^,)]+}}) + +// va_arg takes a one-element vector from an SSE register slot, 16 bytes at a +// time, or from the overflow area aligned to 16. +ld1 arg_ld1(int n, ...) { + __builtin_va_list ap; + __builtin_va_start(ap, n); + ld1 v = __builtin_va_arg(ap, ld1); + __builtin_va_end(ap); + return v; +} + +// CIR-LABEL: cir.func {{.*}}@arg_ld1( +// CIR: %[[FP_OFFSET_P:.+]] = cir.get_member %{{.+}}[1] {name = "fp_offset"} +// CIR: %[[FP_OFFSET:.+]] = cir.load %[[FP_OFFSET_P]] +// CIR: %[[FP_LIMIT:.+]] = cir.const #cir.int<160> : !u32i +// CIR: cir.cmp le %[[FP_OFFSET]], %[[FP_LIMIT]] : !u32i +// CIR: %[[FP_STEP:.+]] = cir.const #cir.int<16> : !u32i +// CIR: cir.add %[[FP_OFFSET]], %[[FP_STEP]] : !u32i +// CIR: %[[ALIGN_MASK:.+]] = cir.const #cir.int<-16> : !s64i +// CIR: %[[ALIGNED:.+]] = cir.ptr_mask %{{.+}}, %[[ALIGN_MASK]] +// CIR: %[[MEM_STEP:.+]] = cir.const #cir.int<16> : !s32i +// CIR: cir.ptr_stride %[[ALIGNED]], %[[MEM_STEP]] +// CIR: %[[VPTR:.+]] = cir.cast bitcast %{{.+}} : !cir.ptr<!u8i> -> !cir.ptr<!cir.vector<1 x !cir.long_double<!cir.f80>>> +// CIR: cir.load align(16) %[[VPTR]] : !cir.ptr<!cir.vector<1 x !cir.long_double<!cir.f80>>>, !cir.vector<1 x !cir.long_double<!cir.f80>> + +// LLVM-LABEL: define dso_local <1 x x86_fp80> @arg_ld1(i32 noundef %{{[^,)]+}}, ...) +// LLVM: icmp ule i32 %{{.+}}, 160 +// LLVM: add i32 %{{.+}}, 16 +// LLVM: call ptr @llvm.ptrmask.p0.i64(ptr %{{.+}}, i64 -16) +// LLVM: %[[ADDR:.+]] = phi ptr +// LLVM: load <1 x x86_fp80>, ptr %[[ADDR]], align 16 + +// A wider vector is always taken from the overflow area, aligned to its size. +ld2 arg_ld2(int n, ...) { + __builtin_va_list ap; + __builtin_va_start(ap, n); + ld2 v = __builtin_va_arg(ap, ld2); + __builtin_va_end(ap); + return v; +} + +// CIR-LABEL: cir.func {{.*}}@arg_ld2( +// CIR-NOT: fp_offset +// CIR: %[[ALIGN_MASK:.+]] = cir.const #cir.int<-32> : !s64i +// CIR: %[[ALIGNED:.+]] = cir.ptr_mask %{{.+}}, %[[ALIGN_MASK]] +// CIR: %[[MEM_STEP:.+]] = cir.const #cir.int<32> : !s32i +// CIR: cir.ptr_stride %[[ALIGNED]], %[[MEM_STEP]] +// CIR: %[[VPTR:.+]] = cir.cast bitcast %[[ALIGNED]] : !cir.ptr<!u8i> -> !cir.ptr<!cir.vector<2 x !cir.long_double<!cir.f80>>> +// CIR: cir.load align(32) %[[VPTR]] : !cir.ptr<!cir.vector<2 x !cir.long_double<!cir.f80>>>, !cir.vector<2 x !cir.long_double<!cir.f80>> + +// LLVM-LABEL: define dso_local <2 x x86_fp80> @arg_ld2(i32 noundef %{{[^,)]+}}, ...) +// LLVM-NOT: icmp +// LLVM: %[[ALIGNED:.+]] = call ptr @llvm.ptrmask.p0.i64(ptr %{{.+}}, i64 -32) +// LLVM: load <2 x x86_fp80>, ptr %[[ALIGNED]], align 32 + +// Two elements take 32 bytes, which pass in memory below AVX. The vector on +// its own is still returned directly, but a record holding it is returned +// through sret. +void take_ld2(ld2 v) {} + +// CIR-SSE: cir.func {{.*}}@take_ld2(%arg0: !cir.ptr<!cir.vector<2 x !cir.long_double<!cir.f80>>> {llvm.align = 32 : i64, llvm.byval = !cir.vector<2 x !cir.long_double<!cir.f80>>, llvm.noundef} loc({{[^)]+}})) +// CIR-AVX: cir.func {{.*}}@take_ld2(%arg0: !cir.vector<2 x !cir.long_double<!cir.f80>> {llvm.noundef} loc({{[^)]+}})) +// CIR-AVX512: cir.func {{.*}}@take_ld2(%arg0: !cir.vector<2 x !cir.long_double<!cir.f80>> {llvm.noundef} loc({{[^)]+}})) +// LLVM-SSE: define dso_local void @take_ld2(ptr noundef byval(<2 x x86_fp80>) align 32 %{{[^,)]+}}) +// LLVM-AVX: define dso_local void @take_ld2(<2 x x86_fp80> noundef %{{[^,)]+}}) +// LLVM-AVX512: define dso_local void @take_ld2(<2 x x86_fp80> noundef %{{[^,)]+}}) + +void call_ld2(ld2 v) { take_ld2(v); } + +// CIR-SSE: cir.call @take_ld2(%{{.+}}) : (!cir.ptr<!cir.vector<2 x !cir.long_double<!cir.f80>>> {llvm.align = 32 : i64, llvm.byval = !cir.vector<2 x !cir.long_double<!cir.f80>>, llvm.noundef}) -> () +// CIR-AVX: cir.call @take_ld2(%{{.+}}) : (!cir.vector<2 x !cir.long_double<!cir.f80>> {llvm.noundef}) -> () +// CIR-AVX512: cir.call @take_ld2(%{{.+}}) : (!cir.vector<2 x !cir.long_double<!cir.f80>> {llvm.noundef}) -> () +// LLVM-SSE: call void @take_ld2(ptr noundef byval(<2 x x86_fp80>) align 32 %{{[^,)]+}}) +// LLVM-AVX: call void @take_ld2(<2 x x86_fp80> noundef %{{[^,)]+}}) +// LLVM-AVX512: call void @take_ld2(<2 x x86_fp80> noundef %{{[^,)]+}}) + +ld2 ret_ld2(ld2 *p) { return *p; } + +// CIR: cir.func {{.*}}@ret_ld2(%arg0: !cir.ptr<!cir.vector<2 x !cir.long_double<!cir.f80>>> {llvm.noundef} loc({{[^)]+}})) -> !cir.vector<2 x !cir.long_double<!cir.f80>> +// LLVM: define dso_local <2 x x86_fp80> @ret_ld2(ptr noundef %{{[^,)]+}}) + +ld2 call_ret_ld2(ld2 *p) { return ret_ld2(p); } + +// CIR: cir.call @ret_ld2(%{{.+}}) : (!cir.ptr<!cir.vector<2 x !cir.long_double<!cir.f80>>> {llvm.noundef}) -> !cir.vector<2 x !cir.long_double<!cir.f80>> +// LLVM: call <2 x x86_fp80> @ret_ld2(ptr noundef %{{[^,)]+}}) + +struct S2 { ld2 v; }; +struct S2 take_s2(struct S2 s) { return s; } + +// CIR-SSE: cir.func {{.*}}@take_s2(%arg0: !cir.ptr<!rec_S2> {llvm.align = 32 : i64, llvm.dead_on_unwind, llvm.noalias, llvm.sret = !rec_S2, llvm.writable} loc({{[^)]+}}), %arg1: !cir.ptr<!rec_S2> {llvm.align = 32 : i64, llvm.byval = !rec_S2, llvm.noundef} loc({{[^)]+}})) +// CIR-AVX: cir.func {{.*}}@take_s2(%arg0: !cir.vector<2 x !cir.f80> loc({{[^)]+}})) -> !cir.vector<2 x !cir.f80> +// CIR-AVX512: cir.func {{.*}}@take_s2(%arg0: !cir.vector<2 x !cir.f80> loc({{[^)]+}})) -> !cir.vector<2 x !cir.f80> +// LLVM-SSE: define dso_local void @take_s2(ptr dead_on_unwind noalias writable sret(%struct.S2) align 32 %{{[^,)]+}}, ptr noundef byval(%struct.S2) align 32 %{{[^,)]+}}) +// LLVM-AVX: define dso_local <2 x x86_fp80> @take_s2(<2 x x86_fp80> %{{[^,)]+}}) +// LLVM-AVX512: define dso_local <2 x x86_fp80> @take_s2(<2 x x86_fp80> %{{[^,)]+}}) + +struct S2 call_s2(struct S2 s) { return take_s2(s); } + +// CIR-SSE: cir.call @take_s2(%arg0, %{{.+}}) : (!cir.ptr<!rec_S2> {llvm.align = 32 : i64, llvm.dead_on_unwind, llvm.sret = !rec_S2, llvm.writable}, !cir.ptr<!rec_S2> {llvm.align = 32 : i64, llvm.byval = !rec_S2, llvm.noundef}) -> () +// CIR-AVX: cir.call @take_s2(%{{.+}}) : (!cir.vector<2 x !cir.f80>) -> !cir.vector<2 x !cir.f80> +// CIR-AVX512: cir.call @take_s2(%{{.+}}) : (!cir.vector<2 x !cir.f80>) -> !cir.vector<2 x !cir.f80> +// LLVM-SSE: call void @take_s2(ptr dead_on_unwind writable sret(%struct.S2) align 32 %{{[^,)]+}}, ptr noundef byval(%struct.S2) align 32 %{{[^,)]+}}) +// LLVM-AVX: call <2 x x86_fp80> @take_s2(<2 x x86_fp80> %{{[^,)]+}}) +// LLVM-AVX512: call <2 x x86_fp80> @take_s2(<2 x x86_fp80> %{{[^,)]+}}) + +// Unnamed, a record wider than 16 bytes goes to memory even where a named one +// is passed in registers. +void var_s2(struct S2 s) { var(1, s); } + +// CIR-SSE: cir.func {{.*}}@var_s2(%arg0: !cir.ptr<!rec_S2> {llvm.align = 32 : i64, llvm.byval = !rec_S2, llvm.noundef} loc({{[^)]+}})) +// CIR-AVX: cir.func {{.*}}@var_s2(%arg0: !cir.vector<2 x !cir.f80> loc({{[^)]+}})) +// CIR-AVX512: cir.func {{.*}}@var_s2(%arg0: !cir.vector<2 x !cir.f80> loc({{[^)]+}})) +// CIR: cir.call @var(%{{.+}}, %{{.+}}) : (!s32i {llvm.noundef}, !cir.ptr<!rec_S2> {llvm.align = 32 : i64, llvm.byval = !rec_S2, llvm.noundef}) -> () +// LLVM-SSE: define dso_local void @var_s2(ptr noundef byval(%struct.S2) align 32 %{{[^,)]+}}) +// LLVM-AVX: define dso_local void @var_s2(<2 x x86_fp80> %{{[^,)]+}}) +// LLVM-AVX512: define dso_local void @var_s2(<2 x x86_fp80> %{{[^,)]+}}) +// LLVM: call void (i32, ...) @var(i32 noundef 1, ptr noundef byval(%struct.S2) align 32 %{{[^,)]+}}) + +// Four elements take 64 bytes, which pass in registers only with AVX-512. +void take_ld4(ld4 v) {} + +// CIR-SSE: cir.func {{.*}}@take_ld4(%arg0: !cir.ptr<!cir.vector<4 x !cir.long_double<!cir.f80>>> {llvm.align = 64 : i64, llvm.byval = !cir.vector<4 x !cir.long_double<!cir.f80>>, llvm.noundef} loc({{[^)]+}})) +// CIR-AVX: cir.func {{.*}}@take_ld4(%arg0: !cir.ptr<!cir.vector<4 x !cir.long_double<!cir.f80>>> {llvm.align = 64 : i64, llvm.byval = !cir.vector<4 x !cir.long_double<!cir.f80>>, llvm.noundef} loc({{[^)]+}})) +// CIR-AVX512: cir.func {{.*}}@take_ld4(%arg0: !cir.vector<4 x !cir.long_double<!cir.f80>> {llvm.noundef} loc({{[^)]+}})) +// LLVM-SSE: define dso_local void @take_ld4(ptr noundef byval(<4 x x86_fp80>) align 64 %{{[^,)]+}}) +// LLVM-AVX: define dso_local void @take_ld4(ptr noundef byval(<4 x x86_fp80>) align 64 %{{[^,)]+}}) +// LLVM-AVX512: define dso_local void @take_ld4(<4 x x86_fp80> noundef %{{[^,)]+}}) + +void call_ld4(ld4 v) { take_ld4(v); } + +// CIR-SSE: cir.call @take_ld4(%{{.+}}) : (!cir.ptr<!cir.vector<4 x !cir.long_double<!cir.f80>>> {llvm.align = 64 : i64, llvm.byval = !cir.vector<4 x !cir.long_double<!cir.f80>>, llvm.noundef}) -> () +// CIR-AVX: cir.call @take_ld4(%{{.+}}) : (!cir.ptr<!cir.vector<4 x !cir.long_double<!cir.f80>>> {llvm.align = 64 : i64, llvm.byval = !cir.vector<4 x !cir.long_double<!cir.f80>>, llvm.noundef}) -> () +// CIR-AVX512: cir.call @take_ld4(%{{.+}}) : (!cir.vector<4 x !cir.long_double<!cir.f80>> {llvm.noundef}) -> () +// LLVM-SSE: call void @take_ld4(ptr noundef byval(<4 x x86_fp80>) align 64 %{{[^,)]+}}) +// LLVM-AVX: call void @take_ld4(ptr noundef byval(<4 x x86_fp80>) align 64 %{{[^,)]+}}) +// LLVM-AVX512: call void @take_ld4(<4 x x86_fp80> noundef %{{[^,)]+}}) + +struct S4 { ld4 v; }; +struct S4 take_s4(struct S4 s) { return s; } + +// CIR-SSE: cir.func {{.*}}@take_s4(%arg0: !cir.ptr<!rec_S4> {llvm.align = 64 : i64, llvm.dead_on_unwind, llvm.noalias, llvm.sret = !rec_S4, llvm.writable} loc({{[^)]+}}), %arg1: !cir.ptr<!rec_S4> {llvm.align = 64 : i64, llvm.byval = !rec_S4, llvm.noundef} loc({{[^)]+}})) +// CIR-AVX: cir.func {{.*}}@take_s4(%arg0: !cir.ptr<!rec_S4> {llvm.align = 64 : i64, llvm.dead_on_unwind, llvm.noalias, llvm.sret = !rec_S4, llvm.writable} loc({{[^)]+}}), %arg1: !cir.ptr<!rec_S4> {llvm.align = 64 : i64, llvm.byval = !rec_S4, llvm.noundef} loc({{[^)]+}})) +// CIR-AVX512: cir.func {{.*}}@take_s4(%arg0: !cir.vector<4 x !cir.f80> loc({{[^)]+}})) -> !cir.vector<4 x !cir.f80> +// LLVM-SSE: define dso_local void @take_s4(ptr dead_on_unwind noalias writable sret(%struct.S4) align 64 %{{[^,)]+}}, ptr noundef byval(%struct.S4) align 64 %{{[^,)]+}}) +// LLVM-AVX: define dso_local void @take_s4(ptr dead_on_unwind noalias writable sret(%struct.S4) align 64 %{{[^,)]+}}, ptr noundef byval(%struct.S4) align 64 %{{[^,)]+}}) +// LLVM-AVX512: define dso_local <4 x x86_fp80> @take_s4(<4 x x86_fp80> %{{[^,)]+}}) + +struct S4 call_s4(struct S4 s) { return take_s4(s); } + +// CIR-SSE: cir.call @take_s4(%arg0, %{{.+}}) : (!cir.ptr<!rec_S4> {llvm.align = 64 : i64, llvm.dead_on_unwind, llvm.sret = !rec_S4, llvm.writable}, !cir.ptr<!rec_S4> {llvm.align = 64 : i64, llvm.byval = !rec_S4, llvm.noundef}) -> () +// CIR-AVX: cir.call @take_s4(%arg0, %{{.+}}) : (!cir.ptr<!rec_S4> {llvm.align = 64 : i64, llvm.dead_on_unwind, llvm.sret = !rec_S4, llvm.writable}, !cir.ptr<!rec_S4> {llvm.align = 64 : i64, llvm.byval = !rec_S4, llvm.noundef}) -> () +// CIR-AVX512: cir.call @take_s4(%{{.+}}) : (!cir.vector<4 x !cir.f80>) -> !cir.vector<4 x !cir.f80> +// LLVM-SSE: call void @take_s4(ptr dead_on_unwind writable sret(%struct.S4) align 64 %{{[^,)]+}}, ptr noundef byval(%struct.S4) align 64 %{{[^,)]+}}) +// LLVM-AVX: call void @take_s4(ptr dead_on_unwind writable sret(%struct.S4) align 64 %{{[^,)]+}}, ptr noundef byval(%struct.S4) align 64 %{{[^,)]+}}) +// LLVM-AVX512: call <4 x x86_fp80> @take_s4(<4 x x86_fp80> %{{[^,)]+}}) + +// A long double stores its first 10 bytes, so it can share a union with +// members whose data ends within them, counting neither a record's tail +// padding nor an empty array. +struct __attribute__((aligned(16))) C16 { char c; }; +union LDPad { long double ld; struct C16 s; }; +union LDPad take_ldpad(union LDPad u) { return u; } + +// CIR: cir.func {{.*}}@take_ldpad(%arg0: !cir.ptr<!rec_LDPad> {llvm.align = 16 : i64, llvm.dead_on_unwind, llvm.noalias, llvm.sret = !rec_LDPad, llvm.writable} loc({{[^)]+}}), %arg1: !cir.ptr<!rec_LDPad> {llvm.align = 16 : i64, llvm.byval = !rec_LDPad, llvm.noundef} loc({{[^)]+}})) +// LLVM: define dso_local void @take_ldpad(ptr dead_on_unwind noalias writable sret(%union.LDPad) align 16 %{{[^,)]+}}, ptr noundef byval(%union.LDPad) align 16 %{{[^,)]+}}) + +union LDZero { long double ld; int z[0]; }; +union LDZero take_ldzero(union LDZero u) { return u; } + +// CIR: cir.func {{.*}}@take_ldzero(%arg0: !cir.ptr<!rec_LDZero> {llvm.align = 16 : i64, llvm.byval = !rec_LDZero, llvm.noundef} loc({{[^)]+}})) -> !cir.f80 +// LLVM: define dso_local x86_fp80 @take_ldzero(ptr noundef byval(%union.LDZero) align 16 %{{[^,)]+}}) + +// An empty array of long double holds no x87 value, so a union holding one is +// classified like any other. +union EmptyX87 { long l; long double z[0]; }; +union EmptyX87 take_empty_x87(union EmptyX87 u) { return u; } + +// CIR: cir.func {{.*}}@take_empty_x87(%arg0: !s64i loc({{[^)]+}})) -> !s64i +// LLVM: define dso_local i64 @take_empty_x87(i64 %{{[^,)]+}}) + +union LDTen { long double ld; char c[10]; }; +union LDTen take_ldten(union LDTen u) { return u; } + +// CIR: cir.func {{.*}}@take_ldten(%arg0: !u64i loc({{[^)]+}}), %arg1: !u64i loc({{[^)]+}})) -> !rec_anon_struct{{[0-9]*}} +// LLVM: define dso_local { i64, i64 } @take_ldten(i64 %{{[^,)]+}}, i64 %{{[^,)]+}}) diff --git a/clang/test/CIR/CodeGen/vector-logical-long-double.cpp b/clang/test/CIR/CodeGen/vector-logical-long-double.cpp new file mode 100644 index 00000000000000..ac580a1fd76402 --- /dev/null +++ b/clang/test/CIR/CodeGen/vector-logical-long-double.cpp @@ -0,0 +1,66 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++17 -fclangir -emit-cir %s -o %t.cir +// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++17 -fclangir -emit-llvm %s -o %t-cir.ll +// RUN: FileCheck --check-prefixes=LLVM,LLVMCIR --implicit-check-not=i80 --input-file=%t-cir.ll %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++17 -emit-llvm %s -o %t.ll +// RUN: FileCheck --check-prefixes=LLVM,OGCG --implicit-check-not=i80 --input-file=%t.ll %s + +typedef long double ld1 __attribute__((vector_size(16))); +typedef __int128 m1 __attribute__((vector_size(16))); +typedef long double ld2 __attribute__((vector_size(32))); +typedef __int128 m2 __attribute__((vector_size(32))); + +// !, && and || on an x87 vector yield 128-bit integer elements. +void lnot(m1 *r, ld1 *v) { *r = !*v; } + +// CIR-LABEL: cir.func{{.*}} @_Z4lnotPDv1_nPDv1_e( +// CIR: cir.vec.cmp(eq, %{{.+}}, %{{.+}}) : !cir.vector<1 x !cir.long_double<!cir.f80>>, !cir.vector<1 x !s128i> + +// LLVM-LABEL: define dso_local void @_Z4lnotPDv1_nPDv1_e(ptr noundef %{{[^,)]+}}, ptr noundef %{{[^,)]+}}) +// LLVM: %[[CMP:.+]] = fcmp oeq <1 x x86_fp80> %{{.+}}, zeroinitializer +// LLVM: %[[EXT:.+]] = sext <1 x i1> %[[CMP]] to <1 x i128> +// LLVM: store <1 x i128> %[[EXT]], ptr %{{.+}}, align 16 + +void lnot2(m2 *r, ld2 *v) { *r = !*v; } + +// CIR-LABEL: cir.func{{.*}} @_Z5lnot2PDv2_nPDv2_e( +// CIR: cir.vec.cmp(eq, %{{.+}}, %{{.+}}) : !cir.vector<2 x !cir.long_double<!cir.f80>>, !cir.vector<2 x !s128i> + +// LLVM-LABEL: define dso_local void @_Z5lnot2PDv2_nPDv2_e(ptr noundef %{{[^,)]+}}, ptr noundef %{{[^,)]+}}) +// LLVM: %[[CMP:.+]] = fcmp oeq <2 x x86_fp80> %{{.+}}, zeroinitializer +// LLVM: %[[EXT:.+]] = sext <2 x i1> %[[CMP]] to <2 x i128> +// LLVM: store <2 x i128> %[[EXT]], ptr %{{.+}}, align 32 + +void land(m1 *r, ld1 *a, ld1 *b) { *r = *a && *b; } + +// CIR-LABEL: cir.func{{.*}} @_Z4landPDv1_nPDv1_eS2_( +// CIR: %[[A:.+]] = cir.vec.cmp(ne, %{{.+}}, %{{.+}}) : !cir.vector<1 x !cir.long_double<!cir.f80>>, !cir.vector<1 x !s128i> +// CIR: %[[B:.+]] = cir.vec.cmp(ne, %{{.+}}, %{{.+}}) : !cir.vector<1 x !cir.long_double<!cir.f80>>, !cir.vector<1 x !s128i> +// CIR: cir.and %[[A]], %[[B]] : !cir.vector<1 x !s128i> + +// LLVM-LABEL: define dso_local void @_Z4landPDv1_nPDv1_eS2_(ptr noundef %{{[^,)]+}}, ptr noundef %{{[^,)]+}}, ptr noundef %{{[^,)]+}}) +// LLVM: %[[A:.+]] = fcmp une <1 x x86_fp80> %{{.+}}, zeroinitializer +// LLVMCIR: %[[A_EXT:.+]] = sext <1 x i1> %[[A]] to <1 x i128> +// LLVM: %[[B:.+]] = fcmp une <1 x x86_fp80> %{{.+}}, zeroinitializer +// LLVMCIR: %[[B_EXT:.+]] = sext <1 x i1> %[[B]] to <1 x i128> +// LLVMCIR: %[[RES:.+]] = and <1 x i128> %[[A_EXT]], %[[B_EXT]] +// OGCG: %[[AND:.+]] = and <1 x i1> %[[A]], %[[B]] +// OGCG: %[[RES:.+]] = sext <1 x i1> %[[AND]] to <1 x i128> +// LLVM: store <1 x i128> %[[RES]], ptr %{{.+}}, align 16 + +void lor(m1 *r, ld1 *a, ld1 *b) { *r = *a || *b; } + +// CIR-LABEL: cir.func{{.*}} @_Z3lorPDv1_nPDv1_eS2_( +// CIR: %[[A:.+]] = cir.vec.cmp(ne, %{{.+}}, %{{.+}}) : !cir.vector<1 x !cir.long_double<!cir.f80>>, !cir.vector<1 x !s128i> +// CIR: %[[B:.+]] = cir.vec.cmp(ne, %{{.+}}, %{{.+}}) : !cir.vector<1 x !cir.long_double<!cir.f80>>, !cir.vector<1 x !s128i> +// CIR: cir.or %[[A]], %[[B]] : !cir.vector<1 x !s128i> + +// LLVM-LABEL: define dso_local void @_Z3lorPDv1_nPDv1_eS2_(ptr noundef %{{[^,)]+}}, ptr noundef %{{[^,)]+}}, ptr noundef %{{[^,)]+}}) +// LLVM: %[[A:.+]] = fcmp une <1 x x86_fp80> %{{.+}}, zeroinitializer +// LLVMCIR: %[[A_EXT:.+]] = sext <1 x i1> %[[A]] to <1 x i128> +// LLVM: %[[B:.+]] = fcmp une <1 x x86_fp80> %{{.+}}, zeroinitializer +// LLVMCIR: %[[B_EXT:.+]] = sext <1 x i1> %[[B]] to <1 x i128> +// LLVMCIR: %[[RES:.+]] = or <1 x i128> %[[A_EXT]], %[[B_EXT]] +// OGCG: %[[OR:.+]] = or <1 x i1> %[[A]], %[[B]] +// OGCG: %[[RES:.+]] = sext <1 x i1> %[[OR]] to <1 x i128> +// LLVM: store <1 x i128> %[[RES]], ptr %{{.+}}, align 16 diff --git a/clang/test/CIR/Transforms/abi-lowering/x86_64-aggregate-nyi.cir b/clang/test/CIR/Transforms/abi-lowering/x86_64-aggregate-nyi.cir index 59a2583b797e74..1003172ebd9c61 100644 --- a/clang/test/CIR/Transforms/abi-lowering/x86_64-aggregate-nyi.cir +++ b/clang/test/CIR/Transforms/abi-lowering/x86_64-aggregate-nyi.cir @@ -12,6 +12,35 @@ [#cir.bitfield_decl<!cir.int<s, 72, bitint>, 70>]>}, padding = {!cir.array<!u8i x 7>}> !rec_SVec = !cir.struct<"SVec" {data !cir.vector<4 x !cir.float>}> +!rec_SX87 = !cir.struct<"SX87" {data !cir.vector<1 x !cir.long_double<!cir.f80>>}> +!rec_UX87Bytes = + !cir.union<"UX87Bytes" {data !cir.vector<1 x !cir.long_double<!cir.f80>>, + data !cir.array<!u8i x 16>}> +!rec_UX87InRec = + !cir.union<"UX87InRec" {data !rec_SX87, data !cir.array<!u8i x 16>}> +!rec_UX87InArr = + !cir.union<"UX87InArr" {data !cir.array<!cir.vector<1 x !cir.long_double<!cir.f80>> x 1>, + data !cir.array<!u8i x 16>}> +!rec_SUX87 = !cir.struct<"SUX87" {data !rec_UX87Bytes}> +!rec_UX87Twice = + !cir.union<"UX87Twice" {data !cir.vector<1 x !cir.long_double<!cir.f80>>, + data !cir.vector<1 x !cir.long_double<!cir.f80>>}> +!rec_UX87Scalar = + !cir.union<"UX87Scalar" {data !cir.long_double<!cir.f80>, + data !cir.array<!u8i x 11>}> +!rec_SLongDouble = !cir.struct<"SLongDouble" {data !cir.long_double<!cir.f80>}> +!rec_UX87Nested = + !cir.union<"UX87Nested" {data !rec_SLongDouble, data !cir.array<!u8i x 4>}> +!rec_UEmptyX87 = + !cir.union<"UEmptyX87" {data !s32i, + empty !cir.array<!cir.long_double<!cir.f80> x 0>}, + padding = {!cir.array<!u8i x 16>}> +!rec_UX87Displaced = + !cir.union<"UX87Displaced" {data !rec_UEmptyX87, + data !cir.long_double<!cir.f80>}> +!rec_UX87Complex = + !cir.union<"UX87Complex" {data !cir.complex<!cir.long_double<!cir.f80>>, + data !cir.array<!u8i x 32>}> !rec_UVec384 = !cir.union<"UVec384" {data !cir.vector<4 x !cir.float>, data !cir.array<!s8i x 33>}, padding = {!cir.array<!u8i x 32>}> @@ -148,33 +177,94 @@ module attributes { // CHECK: not yet implemented for type '!cir.vector<[4] x !cir.float> - // Clang pads a vector's type width up to a power of two and the classifier - // branches on the exact width, so a three-byte vector would be classified at - // 24 bits where clang uses 32. The ABI library's vector type cannot carry - // the padded width, so this is rejected rather than misclassified. + // The classifier rounds a vector's width up to a power of two, as clang + // does, and the CIR data layout does not, so a three-char vector would be + // coerced at 32 bits from a 24-bit value. cir.func @take_odd_width(%arg0: !cir.vector<3 x !s8i>) { cir.return } // CHECK: not yet implemented for type '!cir.vector<3 x !cir.int<s, 8>> - // An element narrower than a byte is rounded up to its storage container by - // clang but not by the classifier, so the two would size the vector - // differently. Classic passes this one in an xmm register from its 128-bit - // width, where the classifier would read 32 bits and coerce to an integer. + // A _BitInt element narrower than a byte has no memory representation in + // CIR's lowering to LLVM, so this is rejected here rather than failing + // there. cir.func @take_sub_byte_element(%arg0: !cir.vector<16 x !cir.int<s, 2, bitint>>) { cir.return } // CHECK: not yet implemented for type '!cir.vector<16 x !cir.int<s, 2, bitint>> - // An x87 long double is 80 bits to the classifier and 128 to clang, so the - // two would size this vector differently for the same reason. - cir.func @take_x87_element(%arg0: !cir.vector<2 x !cir.long_double<!cir.f80>>) { + // Three x87 elements are 384 bits, which the classifier rounds up to 512 + // and CIR does not, so this is rejected for the same reason as the + // three-char vector. + cir.func @take_x87_three(%arg0: !cir.vector<3 x !cir.long_double<!cir.f80>>) { + cir.return + } + + // CHECK: not yet implemented for type '!cir.vector<3 x !cir.long_double<!cir.f80>> + + // A union is moved as a value of its storage type, so a member holding data + // past the 10 bytes a long double stores would lose it. A member reaching + // byte 11 is refused next to a long double, and so is an x87 value held any + // other way, in a record, a _Complex, a vector or an array, even next to one + // of the same type. A record holding such a union is refused with it. + cir.func @take_union_x87_scalar(%arg0: !rec_UX87Scalar) { + cir.return + } + + // CHECK: not yet implemented for type '!cir.union<"UX87Scalar" + + cir.func @take_union_x87_nested(%arg0: !rec_UX87Nested) { + cir.return + } + + // CHECK: not yet implemented for type '!cir.union<"UX87Nested" + + // A member that ties the long double on alignment and size, listed first, + // becomes the storage type in its place, so the union is refused even + // though that member's data ends within the 10 bytes. + cir.func @take_union_x87_displaced(%arg0: !rec_UX87Displaced) { + cir.return + } + + // CHECK: not yet implemented for type '!cir.union<"UX87Displaced" + + cir.func @take_union_x87_complex(%arg0: !rec_UX87Complex) { + cir.return + } + + // CHECK: not yet implemented for type '!cir.union<"UX87Complex" + + cir.func @take_union_x87_twice(%arg0: !rec_UX87Twice) { + cir.return + } + + // CHECK: not yet implemented for type '!cir.union<"UX87Twice" + + cir.func @take_union_x87_bytes(%arg0: !rec_UX87Bytes) { + cir.return + } + + // CHECK: not yet implemented for type '!cir.union<"UX87Bytes" + + cir.func @take_union_x87_in_rec(%arg0: !rec_UX87InRec) { + cir.return + } + + // CHECK: not yet implemented for type '!cir.union<"UX87InRec" + + cir.func @take_union_x87_in_arr(%arg0: !rec_UX87InArr) { + cir.return + } + + // CHECK: not yet implemented for type '!cir.union<"UX87InArr" + + cir.func @take_struct_union_x87(%arg0: !rec_SUX87) { cir.return } - // CHECK: not yet implemented for type '!cir.vector<2 x !cir.long_double<!cir.f80>> + // CHECK: not yet implemented for type '!cir.struct<"SUX87" // A definition (unlike a declaration) is refused: no incomplete-by-value // definition can arise from valid C++, so this stays a hard reject rather diff --git a/clang/test/CIR/Transforms/abi-lowering/x86_64-vector.cir b/clang/test/CIR/Transforms/abi-lowering/x86_64-vector.cir index d5d47a8491b44d..019ae499a7418c 100644 --- a/clang/test/CIR/Transforms/abi-lowering/x86_64-vector.cir +++ b/clang/test/CIR/Transforms/abi-lowering/x86_64-vector.cir @@ -29,6 +29,22 @@ module attributes { // CHECK: cir.func{{.*}} @v128(%arg0: !cir.vector<4 x !cir.float>) -> !cir.vector<4 x !cir.float> // CHECK-NEXT: cir.return %arg0 + // A one-element vector of long double fills one xmm register the same way, + // in either underlying format, and keeps its long double element. + cir.func @v128_x87(%arg0: !cir.vector<1 x !cir.long_double<!cir.f80>>) -> !cir.vector<1 x !cir.long_double<!cir.f80>> { + cir.return %arg0 : !cir.vector<1 x !cir.long_double<!cir.f80>> + } + + // CHECK: cir.func{{.*}} @v128_x87(%arg0: !cir.vector<1 x !cir.long_double<!cir.f80>>) -> !cir.vector<1 x !cir.long_double<!cir.f80>> + // CHECK-NEXT: cir.return %arg0 + + cir.func @v128_quad(%arg0: !cir.vector<1 x !cir.long_double<!cir.f128>>) -> !cir.vector<1 x !cir.long_double<!cir.f128>> { + cir.return %arg0 : !cir.vector<1 x !cir.long_double<!cir.f128>> + } + + // CHECK: cir.func{{.*}} @v128_quad(%arg0: !cir.vector<1 x !cir.long_double<!cir.f128>>) -> !cir.vector<1 x !cir.long_double<!cir.f128>> + // CHECK-NEXT: cir.return %arg0 + // The classifier does not look inside a vector when picking the type for an // SSE eightbyte, so it falls back to double rather than to the two-element // vector that take_ff below gets for the same eightbyte. @@ -115,6 +131,8 @@ module attributes { } // LLVM: define <4 x float> @v128(<4 x float> %{{[^,)]+}}) +// LLVM: define <1 x x86_fp80> @v128_x87(<1 x x86_fp80> %{{[^,)]+}}) +// LLVM: define <1 x fp128> @v128_quad(<1 x fp128> %{{[^,)]+}}) // LLVM: define double @v64(double %{{[^,)]+}}) // LLVM: define i32 @v32(i32 %{{[^,)]+}}) // LLVM: define void @v256(ptr noundef byval(<8 x float>) align 32 %{{[^,)]+}}) >From 2ec9a3d68bc1cf5049984b33d4f9762968570fae Mon Sep 17 00:00:00 2001 From: Adam Smith <[email protected]> Date: Fri, 9 Oct 2026 09:29:00 -0700 Subject: [PATCH 2/3] Address review feedback Assisted-by: Cursor / Claude Opus 5.5 --- .../CIR/Dialect/Builder/CIRBaseBuilder.h | 15 ++++++--- .../Transforms/CallConvLoweringPass.cpp | 31 ++++++++++--------- .../abi-lowering/x86_64-aggregate-nyi.cir | 15 ++++----- 3 files changed, 34 insertions(+), 27 deletions(-) diff --git a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h index 36140601caab21..6dd927fabc0943 100644 --- a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h +++ b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h @@ -907,13 +907,18 @@ class CIRBaseBuilderTy : public mlir::OpBuilder { } /// Compares \p lhs and \p rhs element-wise. Each result element is a - /// signed integer of the operand element's bit width, 80 for x87 long - /// double. + /// signed integer of the operand element's bit width. That is not the + /// result an FP80 element needs, so an FP80 compare names its result type + /// with the overload below. cir::VecCmpOp createVecCompare(mlir::Location loc, cir::CmpOpKind kind, mlir::Value lhs, mlir::Value rhs) { VectorType vecCast = mlir::cast<VectorType>(lhs.getType()); - IntType integralTy = - getSIntNTy(getCIRIntOrFloatBitWidth(vecCast.getElementType())); + mlir::Type elemTy = vecCast.getElementType(); + assert(!(mlir::isa<cir::FPTypeInterface>(elemTy) && + &mlir::cast<cir::FPTypeInterface>(elemTy).getFloatSemantics() == + &llvm::APFloat::x87DoubleExtended()) && + "an FP80 vector compare must name its result type"); + IntType integralTy = getSIntNTy(getCIRIntOrFloatBitWidth(elemTy)); return createVecCompare(loc, cir::VectorType::get(integralTy, vecCast.getSize()), kind, lhs, rhs); @@ -922,7 +927,7 @@ class CIRBaseBuilderTy : public mlir::OpBuilder { /// Compares \p lhs and \p rhs element-wise, producing \p resultTy, which /// must be a vector of integers or bools with as many elements as \p lhs. /// The result element width can then differ from the operand element's - /// bit width, as for x87 long double, whose result elements are 128 bits. + /// bit width, as for FP80, whose result elements are 128 bits. cir::VecCmpOp createVecCompare(mlir::Location loc, VectorType resultTy, cir::CmpOpKind kind, mlir::Value lhs, mlir::Value rhs) { diff --git a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp index beeab4bf4d5e3d..15edae487df28c 100644 --- a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp +++ b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp @@ -159,26 +159,27 @@ static bool hasIncompleteRecordByValue(mlir::Type ty) { return false; } -/// Whether \p ty has the x87 80-bit format, wrapped in LongDoubleType or not. -static bool isX87LongDouble(mlir::Type ty) { +/// Whether \p ty has the x87 double extended format, wrapped in LongDoubleType +/// or not. +static bool isX87DoubleExtended(mlir::Type ty) { auto fpTy = dyn_cast<cir::FPTypeInterface>(ty); return fpTy && &fpTy.getFloatSemantics() == &llvm::APFloat::x87DoubleExtended(); } -/// Whether \p ty is, or holds by value, an x87 long double, alone or as the -/// element of a _Complex or a vector. -static bool holdsX87LongDouble(mlir::Type ty) { +/// Whether \p ty is, or holds by value, an x87 double extended value, alone or +/// as the element of a _Complex or a vector. +static bool holdsX87DoubleExtended(mlir::Type ty) { if (isa<cir::FPTypeInterface>(ty)) - return isX87LongDouble(ty); + return isX87DoubleExtended(ty); if (auto complexTy = dyn_cast<cir::ComplexType>(ty)) - return holdsX87LongDouble(complexTy.getElementType()); + return isX87DoubleExtended(complexTy.getElementType()); if (auto vecTy = dyn_cast<cir::VectorType>(ty)) - return holdsX87LongDouble(vecTy.getElementType()); + return isX87DoubleExtended(vecTy.getElementType()); if (auto arrTy = dyn_cast<cir::ArrayType>(ty)) - return arrTy.getSize() && holdsX87LongDouble(arrTy.getElementType()); + return arrTy.getSize() && holdsX87DoubleExtended(arrTy.getElementType()); if (auto recTy = dyn_cast<cir::RecordType>(ty)) - return llvm::any_of(recTy.getMembers(), holdsX87LongDouble); + return llvm::any_of(recTy.getMembers(), holdsX87DoubleExtended); return false; } @@ -308,20 +309,20 @@ static bool isSupportedType(mlir::Type ty, const DataLayout &dl) { return false; // A union is moved as a value of its storage type, and an x87 value // stores fewer bytes than it occupies, so the bytes another member - // holds past them would be lost. A long double stores its first 10 - // bytes, so it can share a union with other long double members and + // holds past them would be lost. A double extended member stores its + // first 10 bytes, so it can share a union with other such members and // with members whose data ends within those bytes, provided it is the // storage type. An x87 value held any other way, in a _Complex, a // vector, a record or an array, is measured at its full storage, which // reaches past them. - if (members.size() > 1 && llvm::any_of(members, holdsX87LongDouble)) { + if (members.size() > 1 && llvm::any_of(members, holdsX87DoubleExtended)) { uint64_t x87StoreBytes = llvm::APFloat::semanticsSizeInBits( llvm::APFloat::x87DoubleExtended()) / 8; - if (!isX87LongDouble( + if (!isX87DoubleExtended( mlir::cast<cir::UnionType>(recTy).getUnionStorageType(dl)) || !llvm::all_of(members, [&](mlir::Type m) { - return isX87LongDouble(m) || + return isX87DoubleExtended(m) || getDataExtentInBytes(m, dl) <= x87StoreBytes; })) return false; diff --git a/clang/test/CIR/Transforms/abi-lowering/x86_64-aggregate-nyi.cir b/clang/test/CIR/Transforms/abi-lowering/x86_64-aggregate-nyi.cir index 1003172ebd9c61..098c8ec8e2e333 100644 --- a/clang/test/CIR/Transforms/abi-lowering/x86_64-aggregate-nyi.cir +++ b/clang/test/CIR/Transforms/abi-lowering/x86_64-aggregate-nyi.cir @@ -205,10 +205,11 @@ module attributes { // CHECK: not yet implemented for type '!cir.vector<3 x !cir.long_double<!cir.f80>> // A union is moved as a value of its storage type, so a member holding data - // past the 10 bytes a long double stores would lose it. A member reaching - // byte 11 is refused next to a long double, and so is an x87 value held any - // other way, in a record, a _Complex, a vector or an array, even next to one - // of the same type. A record holding such a union is refused with it. + // past the 10 bytes an x87 double extended value stores would lose it. A + // member reaching byte 11 is refused next to one, and so is a double + // extended value held any other way, in a record, a _Complex, a vector or an + // array, even next to one of the same type. A record holding such a union + // is refused with it. cir.func @take_union_x87_scalar(%arg0: !rec_UX87Scalar) { cir.return } @@ -221,9 +222,9 @@ module attributes { // CHECK: not yet implemented for type '!cir.union<"UX87Nested" - // A member that ties the long double on alignment and size, listed first, - // becomes the storage type in its place, so the union is refused even - // though that member's data ends within the 10 bytes. + // A member that ties the double extended member on alignment and size, + // listed first, becomes the storage type in its place, so the union is + // refused even though that member's data ends within the 10 bytes. cir.func @take_union_x87_displaced(%arg0: !rec_UX87Displaced) { cir.return } >From 4de3071bf06e80698ef6182572a3e819c3309b5a Mon Sep 17 00:00:00 2001 From: Adam Smith <[email protected]> Date: Fri, 9 Oct 2026 16:05:14 -0700 Subject: [PATCH 3/3] Test x87 vectors whose element count is not a power of two Assisted-by: Cursor / Claude Opus 5.5 --- .../call-conv-lowering-x86_64-x87-vector.c | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-x87-vector.c b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-x87-vector.c index dc1623f2df4d72..128ce8c2014079 100644 --- a/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-x87-vector.c +++ b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-x87-vector.c @@ -260,6 +260,61 @@ struct S4 call_s4(struct S4 s) { return take_s4(s); } // LLVM-AVX: call void @take_s4(ptr dead_on_unwind writable sret(%struct.S4) align 64 %{{[^,)]+}}, ptr noundef byval(%struct.S4) align 64 %{{[^,)]+}}) // LLVM-AVX512: call <4 x x86_fp80> @take_s4(<4 x x86_fp80> %{{[^,)]+}}) +// Three elements take 48 bytes, which clang rounds up to 64, so they pass in +// registers only with AVX-512. CIRGen cannot load or store a three-element +// vector yet, so these values only flow from a call into a return or another +// call. +typedef long double ld3 __attribute__((ext_vector_type(3))); +ld3 src_ld3(void); +void sink_ld3(ld3); + +ld3 fwd_ld3(void) { return src_ld3(); } + +// CIR: cir.func {{.*}}@fwd_ld3() -> !cir.vector<3 x !cir.long_double<!cir.f80>> +// CIR: cir.call @src_ld3() : () -> !cir.vector<3 x !cir.long_double<!cir.f80>> +// LLVM: define dso_local <3 x x86_fp80> @fwd_ld3() +// LLVM: call <3 x x86_fp80> @src_ld3() + +void pass_ld3(void) { sink_ld3(src_ld3()); } + +// CIR-SSE: cir.call @sink_ld3(%{{.+}}) : (!cir.ptr<!cir.vector<3 x !cir.long_double<!cir.f80>>> {llvm.align = 64 : i64, llvm.byval = !cir.vector<3 x !cir.long_double<!cir.f80>>, llvm.noundef}) -> () +// CIR-AVX: cir.call @sink_ld3(%{{.+}}) : (!cir.ptr<!cir.vector<3 x !cir.long_double<!cir.f80>>> {llvm.align = 64 : i64, llvm.byval = !cir.vector<3 x !cir.long_double<!cir.f80>>, llvm.noundef}) -> () +// CIR-AVX512: cir.call @sink_ld3(%{{.+}}) : (!cir.vector<3 x !cir.long_double<!cir.f80>> {llvm.noundef}) -> () +// LLVM-SSE: call void @sink_ld3(ptr noundef byval(<3 x x86_fp80>) align 64 %{{[^,)]+}}) +// LLVM-AVX: call void @sink_ld3(ptr noundef byval(<3 x x86_fp80>) align 64 %{{[^,)]+}}) +// LLVM-AVX512: call void @sink_ld3(<3 x x86_fp80> noundef %{{[^,)]+}}) + +struct S3 { ld3 v; }; +struct S3 src_s3(void); +void sink_s3(struct S3); + +struct S3 fwd_s3(void) { return src_s3(); } + +// CIR-SSE: cir.func {{.*}}@fwd_s3(%arg0: !cir.ptr<!rec_S3> {llvm.align = 64 : i64, llvm.dead_on_unwind, llvm.noalias, llvm.sret = !rec_S3, llvm.writable} loc({{[^)]+}})) +// CIR-AVX: cir.func {{.*}}@fwd_s3(%arg0: !cir.ptr<!rec_S3> {llvm.align = 64 : i64, llvm.dead_on_unwind, llvm.noalias, llvm.sret = !rec_S3, llvm.writable} loc({{[^)]+}})) +// CIR-AVX512: cir.func {{.*}}@fwd_s3() -> !cir.vector<3 x !cir.f80> +// LLVM-SSE: define dso_local void @fwd_s3(ptr dead_on_unwind noalias writable sret(%struct.S3) align 64 %{{[^,)]+}}) +// LLVM-AVX: define dso_local void @fwd_s3(ptr dead_on_unwind noalias writable sret(%struct.S3) align 64 %{{[^,)]+}}) +// LLVM-AVX512: define dso_local <3 x x86_fp80> @fwd_s3() + +void pass_s3(void) { sink_s3(src_s3()); } + +// CIR-SSE: cir.call @sink_s3(%{{.+}}) : (!cir.ptr<!rec_S3> {llvm.align = 64 : i64, llvm.byval = !rec_S3, llvm.noundef}) -> () +// CIR-AVX: cir.call @sink_s3(%{{.+}}) : (!cir.ptr<!rec_S3> {llvm.align = 64 : i64, llvm.byval = !rec_S3, llvm.noundef}) -> () +// CIR-AVX512: cir.call @sink_s3(%{{.+}}) : (!cir.vector<3 x !cir.f80>) -> () +// LLVM-SSE: call void @sink_s3(ptr noundef byval(%struct.S3) align 64 %{{[^,)]+}}) +// LLVM-AVX: call void @sink_s3(ptr noundef byval(%struct.S3) align 64 %{{[^,)]+}}) +// LLVM-AVX512: call void @sink_s3(<3 x x86_fp80> %{{[^,)]+}}) + +void var_ld3(void) { var(1, src_ld3()); } + +// CIR-SSE: cir.call @var(%{{.+}}, %{{.+}}) : (!s32i {llvm.noundef}, !cir.ptr<!cir.vector<3 x !cir.long_double<!cir.f80>>> {llvm.align = 64 : i64, llvm.byval = !cir.vector<3 x !cir.long_double<!cir.f80>>, llvm.noundef}) -> () +// CIR-AVX: cir.call @var(%{{.+}}, %{{.+}}) : (!s32i {llvm.noundef}, !cir.ptr<!cir.vector<3 x !cir.long_double<!cir.f80>>> {llvm.align = 64 : i64, llvm.byval = !cir.vector<3 x !cir.long_double<!cir.f80>>, llvm.noundef}) -> () +// CIR-AVX512: cir.call @var(%{{.+}}, %{{.+}}) : (!s32i {llvm.noundef}, !cir.vector<3 x !cir.long_double<!cir.f80>> {llvm.noundef}) -> () +// LLVM-SSE: call void (i32, ...) @var(i32 noundef 1, ptr noundef byval(<3 x x86_fp80>) align 64 %{{[^,)]+}}) +// LLVM-AVX: call void (i32, ...) @var(i32 noundef 1, ptr noundef byval(<3 x x86_fp80>) align 64 %{{[^,)]+}}) +// LLVM-AVX512: call void (i32, ...) @var(i32 noundef 1, <3 x x86_fp80> noundef %{{[^,)]+}}) + // A long double stores its first 10 bytes, so it can share a union with // members whose data ends within them, counting neither a record's tail // padding nor an empty array. _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
