[clang] [CIR] Accept _Complex and all float formats in x86_64 callconv lowering (PR #215117)
via cfe-commits
cfe-commits at lists.llvm.org
Sun Aug 9 10:19:27 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clangir
Author: Adam Smith (adams381)
<details>
<summary>Changes</summary>
The CallConvLowering bridge accepted only `float` and `double`, so a function taking a `_Complex`, or a float in any other format, failed the pass instead of being classified. An all-float aggregate failed for a different reason: its SSE eightbyte coerces to a vector, and the bridge had no way to represent one, so it reported the coercion NYI rather than emitting a wrong signature.
Mapping every CIR floating-point type through `FPTypeInterface` covers all of them at once. A `_Complex` maps to the library's complex type and a vector coercion now converts back to a CIR vector.
Accepting a `long double` also makes a union holding one classifiable. That exposes the ABI-compatibility flags, which the pass left at the library defaults. They now come from the triple and the compatibility version, which is what lets a `long double` union reach registers on Darwin instead of memory.
`updateArgAttrs` appended argument attributes instead of setting them, so a name already present landed in the dictionary twice. CIRGen marks a `_Complex long double` parameter `llvm.noundef`, and the ABI then passes it byval, which wants `llvm.noundef` too.
An integer coercion lost its bit-precise flag coming back from the classifier. A struct holding a `_BitInt(128)` then took `__int128`'s 16-byte alignment for its coerce slot instead of 8.
Assisted-by: Cursor / claude-opus-5
---
Patch is 49.68 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/215117.diff
13 Files Affected:
- (modified) clang/include/clang/CIR/Dialect/Passes.h (+2-1)
- (modified) clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp (+65-34)
- (modified) clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp (+15-13)
- (modified) clang/lib/CIR/Lowering/CIRPasses.cpp (+31-1)
- (added) clang/test/CIR/CodeGen/call-conv-lowering-x86_64-abi-compat.c (+20)
- (modified) clang/test/CIR/CodeGen/call-conv-lowering-x86_64.c (+187)
- (modified) clang/test/CIR/Transforms/abi-lowering/x86_64-aggregate-nyi.cir (-51)
- (modified) clang/test/CIR/Transforms/abi-lowering/x86_64-bitint.cir (+6-6)
- (added) clang/test/CIR/Transforms/abi-lowering/x86_64-complex.cir (+75)
- (modified) clang/test/CIR/Transforms/abi-lowering/x86_64-variadic-call.cir (+15)
- (modified) clang/test/CIR/Transforms/abi-lowering/x86_64-variadic-nyi.cir (-29)
- (added) clang/test/CIR/Transforms/abi-lowering/x86_64-vector.cir (+62)
- (added) clang/test/CIR/Transforms/abi-lowering/x86_64-wide-floats.cir (+75)
``````````diff
diff --git a/clang/include/clang/CIR/Dialect/Passes.h b/clang/include/clang/CIR/Dialect/Passes.h
index 0b8142fc394bd..888e7b833b1cf 100644
--- a/clang/include/clang/CIR/Dialect/Passes.h
+++ b/clang/include/clang/CIR/Dialect/Passes.h
@@ -38,7 +38,8 @@ std::unique_ptr<Pass> createTargetLoweringPass();
std::unique_ptr<Pass> createCallConvLoweringPass();
std::unique_ptr<Pass>
createCallConvLoweringPass(cir::CallConvTarget target,
- llvm::abi::X86AVXABILevel x86AvxAbiLevel);
+ llvm::abi::X86AVXABILevel x86AvxAbiLevel,
+ const llvm::abi::ABICompatInfo &x86AbiCompat);
std::unique_ptr<Pass> createHoistAllocasPass();
std::unique_ptr<Pass> createLoweringPreparePass();
std::unique_ptr<Pass> createLoweringPreparePass(clang::ASTContext *astCtx);
diff --git a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
index 193c2b6f4a9dc..10ed3d81f8d56 100644
--- a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
@@ -62,17 +62,16 @@ namespace mlir {
namespace {
//===----------------------------------------------------------------------===//
-// x86_64 System V classifier bridge (scalar and struct/array types)
+// x86_64 System V classifier bridge
//
// Maps CIR types to llvm::abi::Type, runs the LLVM ABI Lowering Library's
// SysV x86_64 classifier, and converts the result back into the
// dialect-agnostic mlir::abi::FunctionClassification that CIRABIRewriteContext
// consumes. Integer (including `_BitInt` up to 128 bits) / pointer / bool /
-// f32 / f64 scalars and struct / union / array aggregates are handled.
-// `_Complex`, vectors, wider floats, packed or padded records, and a union no
-// member of which spans its declared size are reported NYI by
-// classifyX86_64Function so an unsupported signature fails the pass instead of
-// being misclassified.
+// floating-point scalars are handled, as are struct / union / array aggregates
+// and `_Complex`. Vectors, packed or padded records, and a union no member of
+// which spans its declared size are reported NYI by classifyX86_64Function so
+// an unsupported signature fails the pass instead of being misclassified.
//===----------------------------------------------------------------------===//
/// Whether a struct's declared argument-passing kind (from the module's
@@ -101,10 +100,11 @@ static llvm::Align recordDeclaredAlign(ModuleOp modOp, cir::RecordType recTy,
}
/// The CIR types the x86_64 bridge handles. Scalars: an integer up to 128
-/// bits (including `_BitInt` and `__int128`), pointer, bool, void, f32, or f64.
-/// Aggregates: a complete struct or union whose members are all themselves
-/// supported, or an array of a supported element type. Everything else is
-/// reported NYI at the reject() choke point in classifyX86_64Function.
+/// bits (including `_BitInt` and `__int128`), pointer, bool, void, or any
+/// floating-point type. Aggregates: a complete struct or union whose members
+/// are all themselves supported, or an array of a supported element type.
+/// Also a `_Complex` of a supported element type. Everything else is reported
+/// NYI at the reject() choke point in classifyX86_64Function.
static bool isSupportedType(mlir::Type ty, const DataLayout &dl) {
// A pointer is only handled in the default address space (null) or an
// already-lowered target address space. A LangAddressSpaceAttr must be
@@ -112,7 +112,11 @@ static bool isSupportedType(mlir::Type ty, const DataLayout &dl) {
if (auto ptrTy = dyn_cast<cir::PointerType>(ty))
return !ptrTy.getAddrSpace() ||
mlir::isa<cir::TargetAddressSpaceAttr>(ptrTy.getAddrSpace());
- if (isa<cir::VoidType, cir::BoolType, cir::SingleType, cir::DoubleType>(ty))
+ if (isa<cir::VoidType, cir::BoolType>(ty))
+ return true;
+ // Every CIR floating-point type carries the semantics the classifier
+ // switches on, so all of them are handled.
+ if (isa<cir::FPTypeInterface>(ty))
return true;
if (auto intTy = dyn_cast<cir::IntType>(ty)) {
// Integers up to 64 bits, __int128, and _BitInt up to 128 bits are
@@ -129,6 +133,8 @@ static bool isSupportedType(mlir::Type ty, const DataLayout &dl) {
return intTy.getWidth() <= 128;
return intTy.getWidth() <= 64 || intTy.getWidth() == 128;
}
+ if (auto complexTy = dyn_cast<cir::ComplexType>(ty))
+ return isSupportedType(complexTy.getElementType(), dl);
if (auto arrTy = dyn_cast<cir::ArrayType>(ty))
return isSupportedType(arrTy.getElementType(), dl);
if (auto recTy = dyn_cast<cir::RecordType>(ty)) {
@@ -177,7 +183,7 @@ static mlir::Type abiTypeToCIR(const llvm::abi::Type *ty, MLIRContext *ctx) {
[&](const llvm::abi::VoidType *) { return cir::VoidType::get(ctx); })
.Case([&](const llvm::abi::IntegerType *intTy) {
return cir::IntType::get(ctx, intTy->getSizeInBits().getFixedValue(),
- intTy->isSigned());
+ intTy->isSigned(), intTy->isBitInt());
})
.Case([&](const llvm::abi::FloatType *fltTy) {
return cir::getFloatingPointType(*fltTy->getSemantics(), ctx);
@@ -185,6 +191,13 @@ static mlir::Type abiTypeToCIR(const llvm::abi::Type *ty, MLIRContext *ctx) {
.Case([&](const llvm::abi::PointerType *) {
return cir::PointerType::get(cir::VoidType::get(ctx));
})
+ .Case([&](const llvm::abi::VectorType *vecTy) -> mlir::Type {
+ mlir::Type elemCIR = abiTypeToCIR(vecTy->getElementType(), ctx);
+ if (!elemCIR)
+ return nullptr;
+ return cir::VectorType::get(elemCIR,
+ vecTy->getNumElements().getFixedValue());
+ })
.Case([&](const llvm::abi::RecordType *recTy) -> mlir::Type {
SmallVector<mlir::Type> fieldTypes;
fieldTypes.reserve(recTy->getFields().size());
@@ -230,13 +243,16 @@ static const llvm::abi::Type *mapCIRType(mlir::Type type,
/*Signed=*/false);
})
.Case([&](cir::VoidType) { return tb.getVoidType(); })
- .Case([&](cir::SingleType) {
- return tb.getFloatType(llvm::APFloat::IEEEsingle(),
+ .Case([&](cir::FPTypeInterface fpTy) {
+ // LongDoubleType reports its underlying format's semantics, so the
+ // classifier sees x87 or IEEE quad rather than the wrapper.
+ return tb.getFloatType(fpTy.getFloatSemantics(),
llvm::Align(dl.getTypeABIAlignment(type)));
})
- .Case([&](cir::DoubleType) {
- return tb.getFloatType(llvm::APFloat::IEEEdouble(),
- llvm::Align(dl.getTypeABIAlignment(type)));
+ .Case([&](cir::ComplexType complexTy) {
+ return tb.getComplexType(
+ mapCIRType(complexTy.getElementType(), typeMapper, dl, modOp),
+ llvm::Align(dl.getTypeABIAlignment(type)));
})
.Case([&](cir::ArrayType arrTy) {
const llvm::abi::Type *elemAbi =
@@ -296,8 +312,8 @@ static const llvm::abi::Type *mapCIRType(mlir::Type type,
/// eightbyte. getDirect keeps canFlatten set so the rewriter can split a
/// multi-field coerced struct into individual wire arguments. Any other scalar
/// passes in its natural CIR type, which a null coercion denotes. A coercion
-/// this bridge cannot represent (an SSE <2 x float>, say) yields std::nullopt
-/// so the caller reports NYI rather than silently passing the value unchanged.
+/// this bridge cannot represent yields std::nullopt so the caller reports NYI
+/// rather than silently passing the value unchanged.
///
/// Extend: bool or a sub-register integer needs a signext/zeroext attribute.
/// The x86_64 classifier (llvm/lib/ABI/Targets/X86.cpp) only returns Extend
@@ -314,12 +330,12 @@ convertABIArgInfo(const llvm::abi::ArgInfo &info, MLIRContext *ctx,
if (info.isDirect()) {
// The classifier names a coerce type even where it matches the natural
// type, so a non-null coerce does not by itself mean a rewrite is needed.
- // Leaving a scalar alone also preserves its ABI alignment: abiTypeToCIR
- // drops the bit-precise flag, so a _BitInt(128) routed through it would
- // come back as !cir.int<s, 128> with __int128's 16-byte alignment instead
- // of 8.
const llvm::abi::Type *coerceAbi = info.getCoerceToType();
bool isAggregate = isa_and_present<cir::RecordType, cir::ArrayType>(origTy);
+ // For a _Complex the classifier's coerce is only sometimes the natural
+ // type, so it has to be read rather than assumed.
+ bool comparesAgainstCoerce =
+ coerceAbi && isa_and_present<cir::ComplexType>(origTy);
bool coerceIsRegisterTuple =
isa_and_present<llvm::abi::RecordType>(coerceAbi);
// Compare widths rather than identity: a coerce no wider than the natural
@@ -330,15 +346,19 @@ convertABIArgInfo(const llvm::abi::ArgInfo &info, MLIRContext *ctx,
bool coerceWidensScalar =
origInt && coerceInt &&
coerceInt->getSizeInBits().getFixedValue() > origInt.getWidth();
- if (!isAggregate && !coerceIsRegisterTuple && !coerceWidensScalar)
+ // Leaving the rest alone also avoids a lossy round trip: abiTypeToCIR
+ // drops the LongDoubleType wrapper and a pointer's pointee, so comparing a
+ // scalar against its own coerce would report a difference that is not one.
+ if (!isAggregate && !comparesAgainstCoerce && !coerceIsRegisterTuple &&
+ !coerceWidensScalar)
return ArgClassification::getDirect(nullptr);
- // The coerce must be a type this bridge can represent. One it cannot map
- // (an SSE vector, or a nested type it does not handle) yields a null type.
- // Report that as NYI instead of leaving the value as an unchanged by-value
- // record.
mlir::Type coerced = abiTypeToCIR(coerceAbi, ctx);
if (!coerced)
return std::nullopt;
+ // Coercing a value to the type it already has would add a memory round
+ // trip for nothing.
+ if (comparesAgainstCoerce && coerced == origTy)
+ return ArgClassification::getDirect(nullptr);
return ArgClassification::getDirect(coerced);
}
if (info.isExtend()) {
@@ -412,9 +432,8 @@ static std::optional<FunctionClassification> classifyX86_64Signature(
llvm::CallingConv::C, retAbi, argAbi, required);
targetInfo.computeInfo(*fi);
- // convertABIArgInfo returns nullopt when the classifier picks a coercion
- // this bridge cannot represent (e.g. an SSE vector coerce for an all-float
- // aggregate). Report it as NYI rather than emitting a wrong signature.
+ // convertABIArgInfo returns nullopt when the classifier picks a coercion this
+ // bridge cannot represent.
auto nyiCoercion = [&](mlir::Type t) {
emitError() << "x86_64 calling-convention lowering not yet "
"implemented for the ABI coercion of type "
@@ -499,7 +518,18 @@ static bool classifiesSamePrefix(const FunctionClassification &calleeFc,
struct CallConvLoweringPass
: public impl::CallConvLoweringBase<CallConvLoweringPass> {
using CallConvLoweringBase::CallConvLoweringBase;
+
+ CallConvLoweringPass(const CallConvLoweringOptions &options,
+ const llvm::abi::ABICompatInfo &x86AbiCompat)
+ : CallConvLoweringBase(options), x86AbiCompat(x86AbiCompat) {}
+
void runOnOperation() override;
+
+ /// The x86_64 flags whose value depends on the target and the requested ABI
+ /// compatibility version. Carried outside the pass options because the
+ /// struct has no command-line parser, so a cir-opt run gets the library
+ /// defaults rather than a target's values.
+ llvm::abi::ABICompatInfo x86AbiCompat;
};
/// Record on \p fc whether \p returnType is CIR's void. The x86_64 classifier
@@ -610,7 +640,7 @@ void CallConvLoweringPass::runOnOperation() {
x86TypeMapper.emplace(dl);
x86Target = llvm::abi::createX86_64TargetInfo(
x86TypeMapper->getTypeBuilder(), x86AvxAbiLevel.getValue(),
- /*Has64BitPointers=*/true, llvm::abi::ABICompatInfo());
+ /*Has64BitPointers=*/true, x86AbiCompat);
}
// Classify every cir.func up front. No IR mutation happens here, so
@@ -827,9 +857,10 @@ std::unique_ptr<Pass> mlir::createCallConvLoweringPass() {
std::unique_ptr<Pass>
mlir::createCallConvLoweringPass(cir::CallConvTarget target,
- llvm::abi::X86AVXABILevel x86AvxAbiLevel) {
+ llvm::abi::X86AVXABILevel x86AvxAbiLevel,
+ const llvm::abi::ABICompatInfo &x86AbiCompat) {
CallConvLoweringOptions options;
options.target = target;
options.x86AvxAbiLevel = x86AvxAbiLevel;
- return std::make_unique<CallConvLoweringPass>(options);
+ return std::make_unique<CallConvLoweringPass>(options, x86AbiCompat);
}
diff --git a/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp b/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp
index a8b7f60b6a014..7c80aa300d642 100644
--- a/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp
@@ -179,6 +179,11 @@ mlir::Value createIgnoredValue(mlir::OpBuilder &builder, mlir::Location loc,
/// llvm.align on Indirect args. Preserves any existing arg attributes on
/// retained arg slots. \p origArgTypes provides the pre-rewrite type for
/// each arg slot (needed to compute the llvm.byval pointee type).
+///
+/// An attribute this function sets can already be present on the arg slot:
+/// CIRGen marks a scalar parameter llvm.noundef, and the ABI can then pass that
+/// parameter byval, which wants llvm.noundef too. So each name has to be set
+/// rather than appended, or the dictionary carries it twice.
mlir::ArrayAttr updateArgAttrs(mlir::MLIRContext *ctx,
ArrayRef<mlir::Type> origArgTypes,
mlir::ArrayAttr existingArgAttrs,
@@ -204,9 +209,9 @@ mlir::ArrayAttr updateArgAttrs(mlir::MLIRContext *ctx,
newArgAttrs.append(recTy.getNumElements(), builder.getDictionaryAttr({}));
} else if (ac.kind == ArgKind::Extend) {
StringRef attrName = ac.signExtend ? "llvm.signext" : "llvm.zeroext";
- SmallVector<mlir::NamedAttribute> attrs(existing.begin(), existing.end());
- attrs.push_back(builder.getNamedAttr(attrName, builder.getUnitAttr()));
- newArgAttrs.push_back(builder.getDictionaryAttr(attrs));
+ mlir::NamedAttrList attrs(existing);
+ attrs.set(attrName, builder.getUnitAttr());
+ newArgAttrs.push_back(attrs.getDictionary(ctx));
} else if (ac.kind == ArgKind::Indirect) {
// byval: caller-allocated copy; callee receives pointer to copy.
// byref: callee receives pointer to the caller's original storage.
@@ -226,18 +231,15 @@ mlir::ArrayAttr updateArgAttrs(mlir::MLIRContext *ctx,
// produces a fresh alloca+store.
mlir::Type pointeeTy = origArgTypes[oldIdx];
StringRef ownershipAttr = ac.byVal ? "llvm.byval" : "llvm.byref";
- SmallVector<mlir::NamedAttribute> attrs(existing.begin(), existing.end());
- attrs.push_back(builder.getNamedAttr(
- "llvm.align", builder.getI64IntegerAttr(ac.indirectAlign.value())));
- attrs.push_back(
- builder.getNamedAttr(ownershipAttr, mlir::TypeAttr::get(pointeeTy)));
+ mlir::NamedAttrList attrs(existing);
+ attrs.set("llvm.align",
+ builder.getI64IntegerAttr(ac.indirectAlign.value()));
+ attrs.set(ownershipAttr, mlir::TypeAttr::get(pointeeTy));
if (ac.byVal) {
- attrs.push_back(
- builder.getNamedAttr("llvm.noalias", builder.getUnitAttr()));
- attrs.push_back(
- builder.getNamedAttr("llvm.noundef", builder.getUnitAttr()));
+ attrs.set("llvm.noalias", builder.getUnitAttr());
+ attrs.set("llvm.noundef", builder.getUnitAttr());
}
- newArgAttrs.push_back(builder.getDictionaryAttr(attrs));
+ newArgAttrs.push_back(attrs.getDictionary(ctx));
} else {
newArgAttrs.push_back(existing);
}
diff --git a/clang/lib/CIR/Lowering/CIRPasses.cpp b/clang/lib/CIR/Lowering/CIRPasses.cpp
index 0a683b8c8a498..dad17f2ef8659 100644
--- a/clang/lib/CIR/Lowering/CIRPasses.cpp
+++ b/clang/lib/CIR/Lowering/CIRPasses.cpp
@@ -13,6 +13,7 @@
#include "mlir/IR/BuiltinOps.h"
#include "mlir/Pass/PassManager.h"
#include "clang/AST/ASTContext.h"
+#include "clang/Basic/LangOptions.h"
#include "clang/Basic/TargetInfo.h"
#include "clang/CIR/Dialect/Passes.h"
#include "llvm/Support/TimeProfiler.h"
@@ -28,6 +29,34 @@ static CallConvTarget getCallConvTarget(const llvm::Triple &triple) {
return CallConvTarget::None;
}
+/// The x86_64 ABI-compatibility flags, derived from the target and the
+/// requested compatibility version. Every flag defaults to true in the ABI
+/// library, which is not what any target computes: Clang11Compat is false for a
+/// modern Linux target, so leaving it at the default classifies a union larger
+/// than an eightbyte as though every member spanned its size. Mirrors the
+/// predicates in clang/lib/CodeGen/Targets/X86.cpp and the derivation in
+/// CodeGenModule::getLLVMABITargetInfo, which computes the same five flags for
+/// the classic path.
+static llvm::abi::ABICompatInfo
+getX86ABICompatInfo(const clang::ASTContext &astContext) {
+ const llvm::Triple &triple = astContext.getTargetInfo().getTriple();
+ const clang::LangOptions &langOpts = astContext.getLangOpts();
+ clang::LangOptions::ClangABI compat = langOpts.getClangABICompat();
+ llvm::abi::ABICompatInfo abiCompat;
+ abiCompat.HonorsRevision98 = !triple.isOSDarwin();
+ abiCompat.ClassifyIntegerMMXAsSSE =
+ compat > clang::LangOptions::ClangABI::Ver3_8 && !triple.isOSDarwin() &&
+ !triple.isPS() && !triple.isOSFreeBSD();
+ abiCompat.PassInt128VectorsInMem =
+ compat > clang::LangOptions::ClangABI::Ver9 &&
+ (triple.isOSLinux() || triple.isOSNetBSD());
+ abiCompat.ReturnCXXRecordGreaterThan128InMem =
+ compat > clang::LangOptions::ClangABI::Ver20 && !triple.isPS();
+ abiCompat.Clang11Compat =
+ compat <= clang::LangOptions::ClangABI::Ver11 || triple.isPS();
+ return abiCompat;
+}
+
mlir::LogicalResult
runCIRToCIRPasses(mlir::ModuleOp theModule, mlir::MLIRContext &mlirContext,
clang::ASTContext &astContext, bool enableVerifier,
@@ -70,7 +99,8 @@ runCIRToCIRPasses(mlir::ModuleOp theModule, mlir::MLIRContext &mlirContext,
getCallConvTarget(astContext.getTargetInfo().getTriple());
if (target != CallConvTarget::None)
pm.addPass(mlir::createCallConvLoweringPass(
- target, llvm::abi::X86AVXABILevel::None));
+ target, llvm::abi::X86AVXABILevel::None,
+ getX86ABICompatInfo(astContext)));
}
pm.addPass(mlir::createLoweringPreparePass(&astContext));
diff --git a/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-abi-compat.c b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-abi-compat.c
new file mode 100644
index 0000000000000..6a1d9a3c4d3d2
--- /dev/null
+++ b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-abi-compat.c
@@ -0,0 +1,20 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -clangir-enable-call-conv-lowering -emit-llvm %s -o %t-cir.ll
+// RUN: FileCheck --check-prefix=LINUX-CIR --input-file=%t-cir.ll %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll
+// RUN: FileCheck --check-prefix=LINUX-OGCG --input-file=%t.ll %s
+
+// RUN: %clang_cc1 -triple x86_64-apple-darwin -fclangir -clangir-enable-call-conv-lowering -emit-llvm %s -o %t-darwin-cir.ll
+// RUN: FileCheck --check-prefix=DARWIN --input-file=%t-darwin-cir.ll %s
+// RUN: %clang_cc1 -triple x86_64-apple-darwin -emit-llvm %s -o %t-darwin.ll
+// RUN: FileCheck --check-prefix=DARWIN --input-file=%t-darwin.ll %s
+
+// The 0.98 ABI revision sends an eightbyte pair to memory when the high half is
+// X87UP and the low half is not X87. Darwin exempts itself for binary
+// compatibility with older GCC, so the same union passes in registers there.
+// The int member is what makes the low half INTEGER rather than X87.
+typedef union { long double l; int i; } ULongDouble;
+void rev98(ULongDouble ...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/215117
More information about the cfe-commits
mailing list