https://github.com/adams381 updated https://github.com/llvm/llvm-project/pull/223069
>From 20744da19e9fd484d5c8cc267b384bf228948637 Mon Sep 17 00:00:00 2001 From: Adam Smith <[email protected]> Date: Wed, 9 Sep 2026 15:01:21 -0700 Subject: [PATCH 1/5] [CIR] Drop res_attrs when a rewritten return has no result An Indirect return moves the value to an sret pointer argument the pass inserts, and an Ignore return drops it, so the rewritten func or call has no result for the per-result `res_attrs` array to describe. The function rewrite and both call rewrites now remove it. Assisted-by: Cursor / claude-opus-5 --- .../TargetLowering/CIRABIRewriteContext.cpp | 19 +++- .../abi-lowering/dropped-return-res-attrs.cir | 96 +++++++++++++++++++ 2 files changed, 112 insertions(+), 3 deletions(-) create mode 100644 clang/test/CIR/Transforms/abi-lowering/dropped-return-res-attrs.cir diff --git a/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp b/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp index b850c4c0bce03..f7dfe0bb11996 100644 --- a/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp +++ b/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp @@ -1022,6 +1022,8 @@ void rewriteIndirectReturnCall(cir::CallOp call, for (mlir::NamedAttribute attr : call->getAttrs()) if (!newCall->hasAttr(attr.getName())) newCall->setAttr(attr.getName(), attr.getValue()); + // res_attrs arrived in the copy above, from the call that had a result. + newCall->removeAttr("res_attrs"); // Shape the per-argument attrs exactly as the non-sret path does // (signext / zeroext for Extend, drop Ignore slots, byval / align for @@ -1224,9 +1226,16 @@ mlir::LogicalResult CIRABIRewriteContext::rewriteFunctionDefinition( } } - // Rebuild res_attrs: layer llvm.signext / llvm.zeroext onto an Extend - // return. - if (fc.returnInfo.kind == ArgKind::Extend) { + // Whatever emptied the result list, an sret pointer taking the value or an + // Ignore return dropping it, res_attrs no longer describes anything. Keyed + // on the result count rather than the kind so a future kind that voids the + // return cannot slip past. + bool returnDropped = + !oldResultTypes.empty() && mlir::isa<cir::VoidType>(newRetTy); + if (returnDropped) { + funcOp->removeAttr("res_attrs"); + } else if (fc.returnInfo.kind == ArgKind::Extend) { + // Layer llvm.signext / llvm.zeroext onto an Extend return. auto existing = funcOp->getAttrOfType<mlir::ArrayAttr>("res_attrs"); funcOp->setAttr("res_attrs", updateResAttrs(ctx, existing, fc.returnInfo)); } @@ -1424,6 +1433,10 @@ CIRABIRewriteContext::rewriteCallSite(mlir::Operation *callOp, if (fc.returnInfo.kind == ArgKind::Extend) { auto existing = call->getAttrOfType<mlir::ArrayAttr>("res_attrs"); newCall->setAttr("res_attrs", updateResAttrs(ctx, existing, fc.returnInfo)); + } else if (hasResult && mlir::isa<cir::VoidType>(callRetTy)) { + // The copy above brought res_attrs over from a call that had a result, + // and this one no longer does. + newCall->removeAttr("res_attrs"); } if (hasResult && fc.returnInfo.kind == ArgKind::Ignore) { diff --git a/clang/test/CIR/Transforms/abi-lowering/dropped-return-res-attrs.cir b/clang/test/CIR/Transforms/abi-lowering/dropped-return-res-attrs.cir new file mode 100644 index 0000000000000..3c9438db7d8b3 --- /dev/null +++ b/clang/test/CIR/Transforms/abi-lowering/dropped-return-res-attrs.cir @@ -0,0 +1,96 @@ +// RUN: cir-opt %s -cir-call-conv-lowering="classification-attr=test_classify" \ +// RUN: | FileCheck %s +// RUN: cir-opt %s -cir-call-conv-lowering="classification-attr=test_classify" \ +// RUN: --mlir-print-op-generic | FileCheck %s --check-prefix=GENERIC \ +// RUN: --implicit-check-not=res_attrs + +!s32i = !cir.int<s, 32> +!s64i = !cir.int<s, 64> +!rec_Big = !cir.struct<"Big" {data !s64i, data !s64i, data !s64i, data !s64i}> + +#indirect_return = { + return = { kind = "indirect", indirect_align = 8 }, + args = [ ] +} + +#ignore_return = { + return = { kind = "ignore" }, + args = [ ] +} + +#caller_cls = { + return = { kind = "direct" }, + args = [ ] +} + +module attributes { + dlti.dl_spec = #dlti.dl_spec< + #dlti.dl_entry<i32, dense<32>: vector<2xi64>>, + #dlti.dl_entry<i64, dense<64>: vector<2xi64>>> +} { + + // The llvm.noundef on this return has nowhere to go once the result count + // drops to zero, and a stale res_attrs fails the func verifier. + cir.func @returns_big_res_attr() -> (!rec_Big {llvm.noundef}) + attributes { test_classify = #indirect_return } { + %0 = cir.alloca "__retval" align(8) : !cir.ptr<!rec_Big> + %z = cir.const #cir.zero : !rec_Big + cir.store %z, %0 : !rec_Big, !cir.ptr<!rec_Big> + %1 = cir.load %0 : !cir.ptr<!rec_Big>, !rec_Big + cir.return %1 : !rec_Big + } + + // CHECK: cir.func{{.*}} @returns_big_res_attr(%[[SRET:.*]]: !cir.ptr<!rec_Big> { + // CHECK-SAME: llvm.sret = !rec_Big + // CHECK-NOT: -> !rec_Big + // CHECK: cir.store %{{.*}}, %[[SRET]] : !rec_Big, !cir.ptr<!rec_Big> + // CHECK: cir.return + // GENERIC: sym_name = "returns_big_res_attr" + + cir.func private @returns_big() -> !rec_Big + attributes { test_classify = #indirect_return } + + // cir.call prints res_attrs through its result list, which is empty once + // the call returns void, so only the generic form catches a stale one. + cir.func @caller_res_attr() -> !s32i + attributes { test_classify = #caller_cls } { + %a = cir.alloca "a" align(8) : !cir.ptr<!rec_Big> + %b = cir.alloca "b" align(8) : !cir.ptr<!rec_Big> + %0 = cir.call @returns_big() : () -> (!rec_Big {llvm.noundef}) + cir.store %0, %a : !rec_Big, !cir.ptr<!rec_Big> + cir.store %0, %b : !rec_Big, !cir.ptr<!rec_Big> + %z = cir.const #cir.int<0> : !s32i + cir.return %z : !s32i + } + + // CHECK: cir.call @returns_big(%{{.*}}) : (!cir.ptr<!rec_Big> + // CHECK-SAME: llvm.sret = !rec_Big + // CHECK-SAME: -> () + // GENERIC: callee = @returns_big + + // An Ignore return empties the result list without an sret pointer, so it + // reaches the same state by a different route. + cir.func @ignored_res_attr() -> (!s32i {llvm.noundef}) + attributes { test_classify = #ignore_return } { + %0 = cir.const #cir.int<0> : !s32i + cir.return %0 : !s32i + } + + // CHECK: cir.func{{.*}} @ignored_res_attr() + // CHECK-NOT: -> !s32i + // CHECK: cir.return + // GENERIC: sym_name = "ignored_res_attr" + + cir.func private @ignored_ret() -> !s32i + attributes { test_classify = #ignore_return } + + cir.func @caller_ignored_res_attr() -> !s32i + attributes { test_classify = #caller_cls } { + %0 = cir.call @ignored_ret() : () -> (!s32i {llvm.noundef}) + %z = cir.const #cir.int<0> : !s32i + cir.return %z : !s32i + } + + // CHECK: cir.call @ignored_ret() : () -> () + // GENERIC: callee = @ignored_ret +} >From e6942b484ec55398066fdc48068fa9392e591929 Mon Sep 17 00:00:00 2001 From: Adam Smith <[email protected]> Date: Wed, 9 Sep 2026 17:41:49 -0700 Subject: [PATCH 2/5] [CIR] Add a C test for the sret res_attrs drop Assisted-by: Cursor / claude-opus-5 --- ...call-conv-lowering-x86_64-sret-res-attrs.c | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 clang/test/CIR/CodeGen/call-conv-lowering-x86_64-sret-res-attrs.c diff --git a/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-sret-res-attrs.c b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-sret-res-attrs.c new file mode 100644 index 0000000000000..b8dd060606bff --- /dev/null +++ b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-sret-res-attrs.c @@ -0,0 +1,41 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir \ +// RUN: -fno-clangir-call-conv-lowering -menable-no-infs -menable-no-nans \ +// RUN: -emit-cir %s -o %t-before.cir +// RUN: FileCheck --check-prefix=BEFORE --input-file=%t-before.cir %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -menable-no-infs \ +// RUN: -menable-no-nans -emit-cir %s -o %t.cir +// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s \ +// RUN: --implicit-check-not=llvm.nofpclass +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -menable-no-infs \ +// RUN: -menable-no-nans -emit-llvm %s -o %t-cir.ll +// RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -menable-no-infs \ +// RUN: -menable-no-nans -emit-llvm %s -o %t.ll +// RUN: FileCheck --check-prefix=LLVM --input-file=%t.ll %s + +// A return with a floating representation gets llvm.nofpclass under +// -menable-no-infs or -menable-no-nans, and a _Complex __float128 is returned +// indirectly, so the attribute describes a result the sret rewrite removes. +// The BEFORE lines pin that the attribute is there to remove, so the CIR +// checks cannot pass by it never having been attached. +_Complex __float128 ret_cf128(void) { return 1.0Q; } + +// BEFORE: cir.func{{.*}} @ret_cf128() -> (!cir.complex<!cir.f128> {llvm.nofpclass = 519 : i64}) + +// CIR: cir.func{{.*}} @ret_cf128(%arg0: !cir.ptr<!cir.complex<!cir.f128>> +// CIR-SAME: llvm.sret = !cir.complex<!cir.f128> + +// LLVM: define dso_local void @ret_cf128(ptr dead_on_unwind noalias writable sret({ fp128, fp128 }) align 16 %{{.+}}) + +_Complex __float128 ret_cf128_decl(void); +void call_cf128(void) { _Complex __float128 x = ret_cf128_decl(); (void)x; } + +// BEFORE: cir.call @ret_cf128_decl() : () -> (!cir.complex<!cir.f128> {llvm.nofpclass = 519 : i64}) +// BEFORE: cir.func private @ret_cf128_decl() -> (!cir.complex<!cir.f128> {llvm.nofpclass = 519 : i64}) + +// CIR: cir.call @ret_cf128_decl(%{{.+}}) : (!cir.ptr<!cir.complex<!cir.f128>> +// CIR-SAME: llvm.sret = !cir.complex<!cir.f128> +// CIR-SAME: -> () + +// LLVM: call void @ret_cf128_decl(ptr dead_on_unwind writable sret({ fp128, fp128 }) align 16 %{{.+}}) +// LLVM: declare void @ret_cf128_decl(ptr dead_on_unwind writable sret({ fp128, fp128 }) align 16) >From c7a7a3b980144b6b9ee036d2749d4ef864e162a0 Mon Sep 17 00:00:00 2001 From: Adam Smith <[email protected]> Date: Thu, 10 Sep 2026 10:41:35 -0700 Subject: [PATCH 3/5] [CIR] Inline the res_attrs void-return condition A cir.func with no results can only have res_attrs absent or empty, so the emptiness guard this drops was protecting an invariant the result attribute count check already enforces. Assisted-by: Cursor / claude-opus-5 --- .../TargetLowering/CIRABIRewriteContext.cpp | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp b/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp index f7dfe0bb11996..997f9b0d59f27 100644 --- a/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp +++ b/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp @@ -1022,7 +1022,6 @@ void rewriteIndirectReturnCall(cir::CallOp call, for (mlir::NamedAttribute attr : call->getAttrs()) if (!newCall->hasAttr(attr.getName())) newCall->setAttr(attr.getName(), attr.getValue()); - // res_attrs arrived in the copy above, from the call that had a result. newCall->removeAttr("res_attrs"); // Shape the per-argument attrs exactly as the non-sret path does @@ -1226,13 +1225,7 @@ mlir::LogicalResult CIRABIRewriteContext::rewriteFunctionDefinition( } } - // Whatever emptied the result list, an sret pointer taking the value or an - // Ignore return dropping it, res_attrs no longer describes anything. Keyed - // on the result count rather than the kind so a future kind that voids the - // return cannot slip past. - bool returnDropped = - !oldResultTypes.empty() && mlir::isa<cir::VoidType>(newRetTy); - if (returnDropped) { + if (mlir::isa<cir::VoidType>(newRetTy)) { funcOp->removeAttr("res_attrs"); } else if (fc.returnInfo.kind == ArgKind::Extend) { // Layer llvm.signext / llvm.zeroext onto an Extend return. @@ -1434,8 +1427,6 @@ CIRABIRewriteContext::rewriteCallSite(mlir::Operation *callOp, auto existing = call->getAttrOfType<mlir::ArrayAttr>("res_attrs"); newCall->setAttr("res_attrs", updateResAttrs(ctx, existing, fc.returnInfo)); } else if (hasResult && mlir::isa<cir::VoidType>(callRetTy)) { - // The copy above brought res_attrs over from a call that had a result, - // and this one no longer does. newCall->removeAttr("res_attrs"); } >From 46fc5bdf707e8addec5d50c3eb48dec001c1adb9 Mon Sep 17 00:00:00 2001 From: Adam Smith <[email protected]> Date: Fri, 11 Sep 2026 14:17:45 -0700 Subject: [PATCH 4/5] [CIR] Accept `_BitInt` wider than 128 bits in x86_64 callconv lowering With byval in place, a `_BitInt` wider than 128 bits can classify Indirect and flow through the same byval/sret path as an oversized aggregate. That turned up a latent bug, fixed here since it was not reachable or testable before: the byval/sret pointee type was using the raw declared width instead of the padded storage width (e.g. `i200` instead of `i256`), understating the copy size. Widths whose padded storage integer is not a whole number of 16 bytes, such as `_BitInt(129)` at `i192`, still have no in-memory layout in CIR and now reach that gap through the byval and sret pointee instead of through the classifier. They stay NYI. Drop the opt-out from the three CIR lit tests gated on this. Assisted-by: Cursor / claude-opus-5 --- clang/include/clang/CIR/Dialect/IR/CIROps.td | 2 +- .../Transforms/CallConvLoweringPass.cpp | 46 ++-- .../CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp | 96 ++++++--- .../CIR/CodeGen/bitint-split-storage-nyi.c | 34 ++- clang/test/CIR/CodeGen/bitint.c | 11 +- .../call-conv-lowering-x86_64-bitint.c | 34 +++ .../test/CIR/CodeGenBuiltins/builtin-bit.cpp | 6 +- .../Transforms/abi-lowering/x86_64-bitint.cir | 201 ++++++++++++++++++ .../abi-lowering/x86_64-int-nyi.cir | 24 --- 9 files changed, 350 insertions(+), 104 deletions(-) create mode 100644 clang/test/CIR/CodeGen/call-conv-lowering-x86_64-bitint.c diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td index f03c591fe9597..b366e781084a4 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -4377,7 +4377,7 @@ def CIR_FuncOp : CIR_Op<"func", [ let extraLLVMLoweringPatternDecl = [{ static mlir::StringRef getLinkageAttrNameString() { return "linkage"; } - void lowerFuncAttributes( + mlir::LogicalResult lowerFuncAttributes( cir::FuncOp func, bool includeFunctionOnlyAttrs, mlir::SmallVectorImpl<mlir::NamedAttribute> &result) const; diff --git a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp index 601dc54f754a6..e517af7030035 100644 --- a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp +++ b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp @@ -74,13 +74,13 @@ namespace { // 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 / vtable pointer / -// bool / floating-point scalars are handled, as are struct / union / array -// aggregates, `_Complex`, and a fixed-width vector whose width is a power -// of two. Other vectors, a padded record reached through a named bit-field -// access unit, a record holding an empty-for-ABI member that occupies bytes -// or a zero-sized one off its own alignment, a union no member of which spans -// its declared size, and a union with a bit-field access unit no spanning +// Integer (including `_BitInt` of any width and `__int128`) / pointer / vtable +// pointer / bool / floating-point scalars are handled, as are struct / union / +// array aggregates, `_Complex`, and a fixed-width vector whose width is a +// power of two. Other vectors, a padded record reached through a named +// bit-field access unit, a record holding an empty-for-ABI member that occupies +// bytes or a zero-sized one off its own alignment, a union no member of which +// spans its declared size, and a union with a bit-field access unit no spanning // member of which supplies data are reported NYI by classifyX86_64Function // so an unsupported signature fails the pass instead of being misclassified. //===----------------------------------------------------------------------===// @@ -132,12 +132,12 @@ static bool hasIncompleteRecordByValue(mlir::Type ty) { } /// The CIR types the x86_64 bridge handles. Scalars: an integer up to 128 -/// bits (including `_BitInt` and `__int128`), pointer, vtable 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`, or a fixed-width vector, of a supported -/// element type. Everything else is reported NYI at the reject() choke point -/// in classifyX86_64Function. +/// bits (including `__int128`), a `_BitInt` of any width, pointer, vtable +/// 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`, or a fixed-width vector, +/// 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 @@ -159,18 +159,15 @@ static bool isSupportedType(mlir::Type ty, const DataLayout &dl) { 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 + // Integers up to 64 bits, __int128, and _BitInt of any width are // handled: the classifier extends a width below 32, widens 33 through 63 // to i64, coerces 65 through 127 to a {i64, i64} pair, and passes 32, 64, - // and 128 in the natural type. A wider _BitInt classifies Indirect, - // where at a multiple of 8 the byval attributes the rewriter appends - // duplicate the llvm.noundef CIRGen already emitted and trip the - // uniqueness assertion on the merged dictionary. The bound is a blanket - // 128 because the widths that do not collide reach that same untested - // Indirect path. Non-_BitInt intermediate widths (65..127) do not arise - // from C. Both stay rejected. + // and 128 in the natural type. A _BitInt wider than 128 bits classifies + // Indirect, the same path an oversized aggregate already takes. + // Non-_BitInt intermediate widths (65..127) do not arise from C and stay + // rejected. if (intTy.getIsBitInt()) - return intTy.getWidth() <= 128; + return true; return intTy.getWidth() <= 64 || intTy.getWidth() == 128; } if (auto complexTy = dyn_cast<cir::ComplexType>(ty)) @@ -527,8 +524,9 @@ static const llvm::abi::Type *mapCIRType(mlir::Type type, /// for an integer or bool operand, so any other origTy is asserted rather /// than silently handled. /// -/// Indirect: an aggregate that does not fit in registers is passed via a -/// pointer (sret for returns, byval for arguments). +/// Indirect: an aggregate that does not fit in registers, or a scalar too +/// wide to fit (a `_BitInt` over 128 bits), is passed via a pointer (sret +/// for returns, byval for arguments). /// /// Ignore: a void return, or an empty record dropped from the signature. static std::optional<ArgClassification> diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp index 5b8cab548f241..2bd8aa3e17362 100644 --- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp +++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp @@ -2102,13 +2102,16 @@ mlir::LogicalResult CIRToLLVMRotateOpLowering::matchAndRewrite( /// The `llvm.byval`, `llvm.sret`, and `llvm.byref` argument attributes carry /// the pointee type as a TypeAttr. After the CallConvLowering pass that type -/// is still a CIR record; remap it to the lowered LLVM type so translation to -/// LLVM IR does not encounter a CIR type in an attribute. Returns the input -/// unchanged when there is nothing to convert. -static mlir::ArrayAttr -convertTypedArgAttrs(mlir::ArrayAttr argAttrs, - const mlir::TypeConverter &converter, - mlir::MLIRContext *ctx) { +/// is still a CIR type; remap it to the lowered LLVM type so translation to +/// LLVM IR does not encounter a CIR type in an attribute. The pointee must +/// name the in-memory storage type, not the value type, since it sizes the +/// byval copy and the sret slot: a _BitInt narrower than its padded storage +/// integer would otherwise understate both. Returns std::nullopt when a +/// pointee has no memory representation, leaving the caller to emit the NYI +/// error, and the input unchanged when there is nothing to convert. +static std::optional<mlir::ArrayAttr> convertTypedArgAttrs( + mlir::ArrayAttr argAttrs, const mlir::TypeConverter &converter, + const mlir::DataLayout &dataLayout, mlir::MLIRContext *ctx) { if (!argAttrs) return argAttrs; bool changed = false; @@ -2126,8 +2129,11 @@ convertTypedArgAttrs(mlir::ArrayAttr argAttrs, auto typeAttr = dyn_cast<mlir::TypeAttr>(entry.getValue()); if (!typeAttr) continue; - mlir::Type lowered = converter.convertType(typeAttr.getValue()); - if (lowered && lowered != typeAttr.getValue()) { + mlir::Type lowered = + convertTypeForMemory(converter, dataLayout, typeAttr.getValue()); + if (!lowered) + return std::nullopt; + if (lowered != typeAttr.getValue()) { entry.setValue(mlir::TypeAttr::get(lowered)); changed = true; } @@ -2137,9 +2143,11 @@ convertTypedArgAttrs(mlir::ArrayAttr argAttrs, return changed ? mlir::ArrayAttr::get(ctx, loweredArgAttrs) : argAttrs; } -static void lowerCallAttributes(cir::CIRCallOpInterface op, - const mlir::TypeConverter &converter, - SmallVectorImpl<mlir::NamedAttribute> &result) { +static mlir::LogicalResult +lowerCallAttributes(cir::CIRCallOpInterface op, + const mlir::TypeConverter &converter, + const mlir::DataLayout &dataLayout, + SmallVectorImpl<mlir::NamedAttribute> &result) { for (mlir::NamedAttribute attr : op->getAttrs()) { if (attr.getName() == CIRDialect::getCalleeAttrName() || attr.getName() == CIRDialect::getSideEffectAttrName() || @@ -2153,23 +2161,27 @@ static void lowerCallAttributes(cir::CIRCallOpInterface op, assert(!cir::MissingFeatures::opFuncExtraAttrs()); if (attr.getName() == CIRDialect::getArgAttrsAttrName()) { auto argAttrs = cast<mlir::ArrayAttr>(attr.getValue()); - result.emplace_back( - attr.getName(), - convertTypedArgAttrs(argAttrs, converter, op->getContext())); + std::optional<mlir::ArrayAttr> lowered = convertTypedArgAttrs( + argAttrs, converter, dataLayout, op->getContext()); + if (!lowered) + return op->emitError() << "NYI: lowering a byval/sret/byref call " + "argument whose pointee type has no " + "memory representation"; + result.emplace_back(attr.getName(), *lowered); continue; } result.push_back(attr); } + return mlir::success(); } -static mlir::LogicalResult -rewriteCallOrInvoke(mlir::Operation *op, mlir::ValueRange callOperands, - mlir::ConversionPatternRewriter &rewriter, - const mlir::TypeConverter *converter, - mlir::SymbolTableCollection &symbolTables, - mlir::FlatSymbolRefAttr calleeAttr, - mlir::Block *continueBlock = nullptr, - mlir::Block *landingPadBlock = nullptr) { +static mlir::LogicalResult rewriteCallOrInvoke( + mlir::Operation *op, mlir::ValueRange callOperands, + mlir::ConversionPatternRewriter &rewriter, + const mlir::TypeConverter *converter, const mlir::DataLayout &dataLayout, + mlir::SymbolTableCollection &symbolTables, + mlir::FlatSymbolRefAttr calleeAttr, mlir::Block *continueBlock = nullptr, + mlir::Block *landingPadBlock = nullptr) { llvm::SmallVector<mlir::Type, 8> llvmResults; mlir::ValueTypeRange<mlir::ResultRange> cirResults = op->getResultTypes(); auto call = cast<cir::CIRCallOpInterface>(op); @@ -2187,7 +2199,9 @@ rewriteCallOrInvoke(mlir::Operation *op, mlir::ValueRange callOperands, memoryEffects, noUnwind, willReturn, noReturn); SmallVector<mlir::NamedAttribute, 4> attributes; - lowerCallAttributes(call, *converter, attributes); + if (mlir::failed( + lowerCallAttributes(call, *converter, dataLayout, attributes))) + return mlir::failure(); mlir::LLVM::LLVMFunctionType llvmFnTy; @@ -2273,7 +2287,7 @@ mlir::LogicalResult CIRToLLVMCallOpLowering::matchAndRewrite( cir::CallOp op, OpAdaptor adaptor, mlir::ConversionPatternRewriter &rewriter) const { return rewriteCallOrInvoke(op.getOperation(), adaptor.getOperands(), rewriter, - getTypeConverter(), symbolTables, + getTypeConverter(), dataLayout, symbolTables, op.getCalleeAttr()); } @@ -2281,9 +2295,10 @@ mlir::LogicalResult CIRToLLVMTryCallOpLowering::matchAndRewrite( cir::TryCallOp op, OpAdaptor adaptor, mlir::ConversionPatternRewriter &rewriter) const { assert(!cir::MissingFeatures::opCallCallConv()); - return rewriteCallOrInvoke( - op.getOperation(), adaptor.getOperands(), rewriter, getTypeConverter(), - symbolTables, op.getCalleeAttr(), op.getNormalDest(), op.getUnwindDest()); + return rewriteCallOrInvoke(op.getOperation(), adaptor.getOperands(), rewriter, + getTypeConverter(), dataLayout, symbolTables, + op.getCalleeAttr(), op.getNormalDest(), + op.getUnwindDest()); } mlir::LogicalResult CIRToLLVMReturnAddrOpLowering::matchAndRewrite( @@ -2678,8 +2693,9 @@ static bool shouldDropFuncAttribute(cir::FuncOp func, mlir::NamedAttribute attr, /// Lower `cir.func` attributes for an `LLVMFuncOp` or `LLVM::AliasOp`. /// Drop attributes populated by the destination op builder. If /// `includeFunctionOnlyAttrs` is false, also omit attributes that are only -/// valid on functions. -void CIRToLLVMFuncOpLowering::lowerFuncAttributes( +/// valid on functions. Fails when an argument attribute's pointee type has +/// no memory representation. +mlir::LogicalResult CIRToLLVMFuncOpLowering::lowerFuncAttributes( cir::FuncOp func, bool includeFunctionOnlyAttrs, SmallVectorImpl<mlir::NamedAttribute> &result) const { OpenCLFunctionMetadataLowering openCLMetadataLowering(func.getContext()); @@ -2692,9 +2708,13 @@ void CIRToLLVMFuncOpLowering::lowerFuncAttributes( assert(!cir::MissingFeatures::opFuncExtraAttrs()); if (attr.getName() == func.getArgAttrsAttrName()) { auto argAttrs = cast<mlir::ArrayAttr>(attr.getValue()); - result.emplace_back( - attr.getName(), - convertTypedArgAttrs(argAttrs, *getTypeConverter(), getContext())); + std::optional<mlir::ArrayAttr> lowered = convertTypedArgAttrs( + argAttrs, *getTypeConverter(), dataLayout, getContext()); + if (!lowered) + return func.emitError() << "NYI: lowering a byval/sret/byref " + "argument whose pointee type has no " + "memory representation"; + result.emplace_back(attr.getName(), *lowered); continue; } result.push_back(attr); @@ -2702,13 +2722,17 @@ void CIRToLLVMFuncOpLowering::lowerFuncAttributes( if (includeFunctionOnlyAttrs) openCLMetadataLowering.appendAttrs(result); + + return mlir::success(); } mlir::LogicalResult CIRToLLVMFuncOpLowering::matchAndRewriteAlias( cir::FuncOp op, llvm::StringRef aliasee, mlir::Type ty, OpAdaptor adaptor, mlir::ConversionPatternRewriter &rewriter) const { SmallVector<mlir::NamedAttribute, 4> attributes; - lowerFuncAttributes(op, /*includeFunctionOnlyAttrs=*/false, attributes); + if (mlir::failed(lowerFuncAttributes(op, /*includeFunctionOnlyAttrs=*/false, + attributes))) + return mlir::failure(); mlir::Location loc = op.getLoc(); auto aliasOp = rewriter.replaceOpWithNewOp<mlir::LLVM::AliasOp>( @@ -2769,7 +2793,9 @@ mlir::LogicalResult CIRToLLVMFuncOpLowering::matchAndRewrite( mlir::LLVM::Linkage linkage = convertLinkage(op.getLinkage()); mlir::LLVM::CConv cconv = convertCallingConv(op.getCallingConv()); SmallVector<mlir::NamedAttribute, 4> attributes; - lowerFuncAttributes(op, /*includeFunctionOnlyAttrs=*/true, attributes); + if (mlir::failed(lowerFuncAttributes(op, /*includeFunctionOnlyAttrs=*/true, + attributes))) + return mlir::failure(); mlir::LLVM::LLVMFuncOp fn = mlir::LLVM::LLVMFuncOp::create( rewriter, loc, op.getName(), llvmFnTy, linkage, isDsoLocal, cconv, diff --git a/clang/test/CIR/CodeGen/bitint-split-storage-nyi.c b/clang/test/CIR/CodeGen/bitint-split-storage-nyi.c index a228506d9ad41..f8e3d3de5da07 100644 --- a/clang/test/CIR/CodeGen/bitint-split-storage-nyi.c +++ b/clang/test/CIR/CodeGen/bitint-split-storage-nyi.c @@ -1,11 +1,11 @@ -// TODO(cir): drop -fno-clangir-call-conv-lowering once CallConvLowering -// supports _BitInt wider than 128 bits. -// RUN: not %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -fno-clangir-call-conv-lowering -emit-llvm -DGLOBAL %s -o - 2>&1 | FileCheck %s --check-prefix=GLOBAL -// RUN: not %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -fno-clangir-call-conv-lowering -emit-llvm -DALLOCA %s -o - 2>&1 | FileCheck %s --check-prefix=ALLOCA -// RUN: not %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -fno-clangir-call-conv-lowering -emit-llvm -DSTORE %s -o - 2>&1 | FileCheck %s --check-prefix=STORE -// RUN: not %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -fno-clangir-call-conv-lowering -emit-llvm -DLOAD %s -o - 2>&1 | FileCheck %s --check-prefix=LOAD -// RUN: not %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -fno-clangir-call-conv-lowering -emit-llvm -DSTRUCT %s -o - 2>&1 | FileCheck %s --check-prefix=STRUCT -// RUN: not %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -fno-clangir-call-conv-lowering -emit-llvm -DARRAY %s -o - 2>&1 | FileCheck %s --check-prefix=ARRAY +// RUN: not %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm -DGLOBAL %s -o - 2>&1 | FileCheck %s --check-prefix=GLOBAL +// RUN: not %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm -DALLOCA %s -o - 2>&1 | FileCheck %s --check-prefix=ALLOCA +// RUN: not %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm -DSTORE %s -o - 2>&1 | FileCheck %s --check-prefix=STORE +// RUN: not %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm -DLOAD %s -o - 2>&1 | FileCheck %s --check-prefix=LOAD +// RUN: not %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm -DSTRUCT %s -o - 2>&1 | FileCheck %s --check-prefix=STRUCT +// RUN: not %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm -DARRAY %s -o - 2>&1 | FileCheck %s --check-prefix=ARRAY +// RUN: not %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm -DPARAM %s -o - 2>&1 | FileCheck %s --check-prefix=PARAM +// RUN: not %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm -DRETURN %s -o - 2>&1 | FileCheck %s --check-prefix=RETURN #ifdef GLOBAL signed _BitInt(129) g129 = 1; @@ -13,9 +13,9 @@ signed _BitInt(129) g129 = 1; #endif #ifdef ALLOCA -signed _BitInt(129) use_local(signed _BitInt(129) a) { +int use_local(int a) { signed _BitInt(129) x = a; - return x; + return (int)x; } // ALLOCA: NYI: lowering alloca of a type with no memory representation #endif @@ -49,3 +49,17 @@ struct HasWide129Array { struct HasWide129Array g_array; // ARRAY: NYI: lowering global of a type with no memory representation #endif + +#ifdef PARAM +// A split-storage width passed by value classifies Indirect, so the width +// appears as the byval pointee. +void take_param(signed _BitInt(129) x) {} +// PARAM: NYI: lowering a byval/sret/byref argument whose pointee type has no memory representation +#endif + +#ifdef RETURN +// Returned by value it classifies Indirect too, so the width appears as the +// sret pointee. +signed _BitInt(129) ret_wide(void) { return 1; } +// RETURN: NYI: lowering a byval/sret/byref argument whose pointee type has no memory representation +#endif diff --git a/clang/test/CIR/CodeGen/bitint.c b/clang/test/CIR/CodeGen/bitint.c index c6b4d607b8bc8..edd7b91cae49a 100644 --- a/clang/test/CIR/CodeGen/bitint.c +++ b/clang/test/CIR/CodeGen/bitint.c @@ -1,8 +1,6 @@ -// TODO(cir): drop -fno-clangir-call-conv-lowering once CallConvLowering -// supports _BitInt wider than 128 bits. -// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -fno-clangir-call-conv-lowering -emit-cir %s -o %t.cir +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -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 -fclangir -fno-clangir-call-conv-lowering -emit-llvm %s -o %t-cir.ll +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t-cir.ll // RUN: FileCheck --check-prefix=LLVM --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=OGCG --input-file=%t.ll %s @@ -55,8 +53,9 @@ void take_unsigned_bitint(unsigned _BitInt(64) x) {} // OGCG: define {{.*}} void @take_unsigned_bitint(i64 {{.*}}) void take_bitint_254(signed _BitInt(254) x) {} -// CIR: cir.func {{.*}} @take_bitint_254(%arg0: !cir.int<s, 254, bitint> -// LLVM: define {{.*}} void @take_bitint_254(i254 {{.*}}) +// CIR: cir.func {{.*}} @take_bitint_254(%arg0: !cir.ptr<!cir.int<s, 254, bitint>> +// CIR-SAME: {llvm.align = 8 : i64, llvm.byval = !cir.int<s, 254, bitint>, llvm.noundef} +// LLVM: define {{.*}} void @take_bitint_254(ptr noundef byval(i256) align 8 {{.*}}) // OGCG: define {{.*}} void @take_bitint_254(ptr noundef byval(i256) align 8 {{.*}}) // Regular __int128 should NOT have the bitint flag. diff --git a/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-bitint.c b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-bitint.c new file mode 100644 index 0000000000000..a9a3616a0c765 --- /dev/null +++ b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-bitint.c @@ -0,0 +1,34 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -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 -fclangir -emit-llvm %s -o %t-cir.ll +// RUN: FileCheck --check-prefix=LLVM,LLVMCIR --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=LLVM,OGCG --input-file=%t.ll %s + +void take_wide(_BitInt(200) x) {} + +// CIR: cir.func{{.*}} @take_wide( +// CIR-SAME: %arg0: !cir.ptr<!cir.int<s, 200, bitint>> {llvm.align = 8 : i64 +// CIR-SAME: , llvm.byval = !cir.int<s, 200, bitint>, llvm.noundef} loc{{.*}}) + +// The byval copy is sized from the padded storage type (i256), matching +// classic. One divergence survives: the position of the local's alloca, +// which CIR places at the point of the declaration while classic hoists +// every alloca to the function's entry block ahead of the parameter +// materialization. +// LLVM-LABEL: define dso_local void @take_wide( +// LLVM-SAME: ptr noundef byval(i256) align 8 %[[ARG:[0-9]+]]) + +// LLVMCIR-NEXT: %[[WIDE:.+]] = load i256, ptr %[[ARG]], align 8 +// LLVMCIR-NEXT: %[[NARROW:.+]] = trunc i256 %[[WIDE]] to i200 +// LLVMCIR-NEXT: %[[SLOT:.+]] = alloca i256, align 8 +// LLVMCIR-NEXT: %[[EXT:.+]] = sext i200 %[[NARROW]] to i256 +// LLVMCIR-NEXT: store i256 %[[EXT]], ptr %[[SLOT]], align 8 +// LLVMCIR-NEXT: ret void + +// OGCG: %[[SLOT:.+]] = alloca i256, align 8 +// OGCG-NEXT: %[[WIDE:.+]] = load i256, ptr %[[ARG]], align 8 +// OGCG-NEXT: %[[NARROW:.+]] = trunc i256 %[[WIDE]] to i200 +// OGCG-NEXT: %[[EXT:.+]] = sext i200 %[[NARROW]] to i256 +// OGCG-NEXT: store i256 %[[EXT]], ptr %[[SLOT]], align 8 +// OGCG-NEXT: ret void diff --git a/clang/test/CIR/CodeGenBuiltins/builtin-bit.cpp b/clang/test/CIR/CodeGenBuiltins/builtin-bit.cpp index bb5bb414eb8de..99765b22f0dba 100644 --- a/clang/test/CIR/CodeGenBuiltins/builtin-bit.cpp +++ b/clang/test/CIR/CodeGenBuiltins/builtin-bit.cpp @@ -1,8 +1,6 @@ -// TODO(cir): drop -fno-clangir-call-conv-lowering once CallConvLowering -// supports _BitInt wider than 128 bits. -// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -fno-clangir-call-conv-lowering -emit-cir %s -o %t.cir +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir // RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR -// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -fno-clangir-call-conv-lowering -emit-llvm %s -o %t-cir.ll +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t-cir.ll // RUN: FileCheck --input-file=%t-cir.ll %s -check-prefix=LLVM // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll // RUN: FileCheck --input-file=%t.ll %s -check-prefix=OGCG diff --git a/clang/test/CIR/Transforms/abi-lowering/x86_64-bitint.cir b/clang/test/CIR/Transforms/abi-lowering/x86_64-bitint.cir index 883e52f408b9e..03f93ce4bc49c 100644 --- a/clang/test/CIR/Transforms/abi-lowering/x86_64-bitint.cir +++ b/clang/test/CIR/Transforms/abi-lowering/x86_64-bitint.cir @@ -14,12 +14,18 @@ !ub65i = !cir.int<u, 65, bitint> !b127i = !cir.int<s, 127, bitint> !b128i = !cir.int<s, 128, bitint> +!b200i = !cir.int<s, 200, bitint> +!ub200i = !cir.int<u, 200, bitint> +!b201i = !cir.int<s, 201, bitint> !s8i = !cir.int<s, 8> !rec_S = !cir.struct<"S" {data !b17i}> !rec_P = !cir.struct<"P" {data !b65i}> !rec_W = !cir.struct<"W" {data !b128i}> !rec_O = !cir.struct<"O" {data !b128i, data !s8i}> !rec_Arr = !cir.struct<"Arr" {data !cir.array<!b65i x 2>}> +!rec_D = !cir.struct<"D" {data !b200i}> +!rec_UD = !cir.union<"UD" {data !b200i}> +!rec_AD = !cir.struct<"AD" {data !cir.array<!b200i x 2>}> module attributes { dlti.dl_spec = #dlti.dl_spec< @@ -812,4 +818,199 @@ module attributes { // LLVM-SAME: ptr noundef byval(%struct.Arr) align 8 %[[ARG:[0-9]+]]) // LLVM-NEXT: %{{[0-9]+}} = load %struct.Arr, ptr %[[ARG]] // LLVM-NEXT: ret void + + // A _BitInt wider than 128 bits classifies Indirect: byval on the argument + // side. The rewriter attaches llvm.noundef to a byval argument, so where + // CIRGen already attached one the merged dictionary keeps a single entry. + cir.func @take_b200(%arg0: !b200i {llvm.noundef}) { + cir.return + } + + // CHECK-LABEL: cir.func{{.*}} @take_b200( + // CHECK-SAME: %arg0: !cir.ptr<!cir.int<s, 200, bitint>> {llvm.align = 8 : i64 + // CHECK-SAME: , llvm.byval = !cir.int<s, 200, bitint>, llvm.noundef}) + // CHECK-NEXT: %{{[0-9]+}} = cir.load %arg0 + // CHECK-SAME: : !cir.ptr<!cir.int<s, 200, bitint>>, !cir.int<s, 200, bitint> + // CHECK-NEXT: cir.return + + // The byval pointee names the padded storage type (i256), not the literal + // i200, since that is what the copy is sized from and what the load below + // reads: a byval(i200) would tell the backend to reserve a 25-byte copy + // while the body reads 32 bytes through it. + // LLVM-LABEL: define void @take_b200( + // LLVM-SAME: ptr noundef byval(i256) align 8 %[[ARG:[0-9]+]]) + // LLVM-NEXT: %[[WIDE:[0-9]+]] = load i256, ptr %[[ARG]] + // LLVM-NEXT: %{{[0-9]+}} = trunc i256 %[[WIDE]] to i200 + // LLVM-NEXT: ret void + + // A call site builds the byval copy itself, and the call operand's own + // byval attribute is widened the same way as the callee's. + cir.func @call_b200(%arg0: !b200i) { + cir.call @take_b200(%arg0) : (!b200i) -> () + cir.return + } + + // CHECK-LABEL: cir.func{{.*}} @call_b200( + // CHECK-SAME: %arg0: !cir.ptr<!cir.int<s, 200, bitint>> {llvm.align = 8 : i64 + // CHECK-SAME: , llvm.byval = !cir.int<s, 200, bitint>, llvm.noundef}) + // CHECK: %[[SLOT:[0-9]+]] = cir.alloca "byval" align(8) + // CHECK-SAME: : !cir.ptr<!cir.int<s, 200, bitint>> + // CHECK-NEXT: cir.store %{{[0-9]+}}, %[[SLOT]] + // CHECK-SAME: : !cir.int<s, 200, bitint>, !cir.ptr<!cir.int<s, 200, bitint>> + // CHECK-NEXT: cir.call @take_b200(%[[SLOT]]) + // CHECK-SAME: : (!cir.ptr<!cir.int<s, 200, bitint>> {llvm.align = 8 : i64 + // CHECK-SAME: , llvm.byval = !cir.int<s, 200, bitint>, llvm.noundef}) -> () + + // LLVM-LABEL: define void @call_b200( + // LLVM-SAME: ptr noundef byval(i256) align 8 %[[ARG:[0-9]+]]) + // LLVM: %[[SLOT:[0-9]+]] = alloca i256, align 8 + // LLVM: store i256 %{{[0-9]+}}, ptr %[[SLOT]], align 8 + // LLVM-NEXT: call void @take_b200(ptr noundef byval(i256) align 8 %[[SLOT]]) + // LLVM-NEXT: ret void + + // A non-multiple-of-8 width past 128 still classifies Indirect at the same + // 8-byte alignment: the byval alignment is the target's clamped _BitInt + // maximum, not derived from the width's own byte count. + cir.func @take_b201(%arg0: !b201i) { + cir.return + } + + // CHECK-LABEL: cir.func{{.*}} @take_b201( + // CHECK-SAME: %arg0: !cir.ptr<!cir.int<s, 201, bitint>> {llvm.align = 8 : i64 + // CHECK-SAME: , llvm.byval = !cir.int<s, 201, bitint>, llvm.noundef}) + // CHECK-NEXT: %{{[0-9]+}} = cir.load %arg0 + // CHECK-SAME: : !cir.ptr<!cir.int<s, 201, bitint>>, !cir.int<s, 201, bitint> + // CHECK-NEXT: cir.return + + // LLVM-LABEL: define void @take_b201( + // LLVM-SAME: ptr noundef byval(i256) align 8 %[[ARG:[0-9]+]]) + // LLVM-NEXT: %[[WIDE:[0-9]+]] = load i256, ptr %[[ARG]] + // LLVM-NEXT: %{{[0-9]+}} = trunc i256 %[[WIDE]] to i201 + // LLVM-NEXT: ret void + + // Signedness only affects the extend direction on the memory round trip, + // not the classification. + cir.func @take_ub200(%arg0: !ub200i) { + cir.return + } + + // CHECK-LABEL: cir.func{{.*}} @take_ub200( + // CHECK-SAME: %arg0: !cir.ptr<!cir.int<u, 200, bitint>> {llvm.align = 8 : i64 + // CHECK-SAME: , llvm.byval = !cir.int<u, 200, bitint>, llvm.noundef}) + // CHECK-NEXT: %{{[0-9]+}} = cir.load %arg0 + // CHECK-SAME: : !cir.ptr<!cir.int<u, 200, bitint>>, !cir.int<u, 200, bitint> + // CHECK-NEXT: cir.return + + // LLVM-LABEL: define void @take_ub200( + // LLVM-SAME: ptr noundef byval(i256) align 8 %[[ARG:[0-9]+]]) + // LLVM-NEXT: %[[WIDE:[0-9]+]] = load i256, ptr %[[ARG]] + // LLVM-NEXT: %{{[0-9]+}} = trunc i256 %[[WIDE]] to i200 + // LLVM-NEXT: ret void + + // Returning a _BitInt wider than 128 bits takes the sret path, the same as + // an oversized aggregate. The pre-existing llvm.noundef (as CIRGen would + // attach to any scalar return) must be dropped along with the result + // itself rather than surviving as a stale one-element res_attrs on a + // function that now returns void. + cir.func @ret_b200() -> (!b200i {llvm.noundef}) { + %0 = cir.alloca "__retval" align(8) : !cir.ptr<!b200i> + %1 = cir.const #cir.int<5> : !b200i + cir.store %1, %0 : !b200i, !cir.ptr<!b200i> + %2 = cir.load %0 : !cir.ptr<!b200i>, !b200i + cir.return %2 : !b200i + } + + // CHECK-LABEL: cir.func{{.*}} @ret_b200( + // CHECK-SAME: %arg0: !cir.ptr<!cir.int<s, 200, bitint>> {llvm.align = 8 : i64 + // CHECK-SAME: , llvm.dead_on_unwind, llvm.noalias, llvm.sret = !cir.int<s, 200, bitint> + // CHECK-SAME: , llvm.writable}) + // CHECK-NEXT: %[[C:[0-9]+]] = cir.const #cir.int<5> : !cir.int<s, 200, bitint> + // CHECK-NEXT: cir.store %[[C]], %arg0 + // CHECK-SAME: : !cir.int<s, 200, bitint>, !cir.ptr<!cir.int<s, 200, bitint>> + // CHECK-NEXT: cir.return + + // Likewise the sret pointee is the padded storage type. + // LLVM-LABEL: define void @ret_b200( + // LLVM-SAME: ptr dead_on_unwind noalias writable sret(i256) align 8 + // LLVM-SAME: %[[ARG:[0-9]+]]) + // LLVM-NEXT: store i256 5, ptr %[[ARG]] + // LLVM-NEXT: ret void + + // A call site rewriting to the sret shape drops the pre-existing + // llvm.noundef the same way the callee's own definition does. + cir.func @call_ret_b200() -> (!b200i {llvm.noundef}) { + %0 = cir.alloca "__retval" align(8) : !cir.ptr<!b200i> + %1 = cir.call @ret_b200() : () -> !b200i + cir.store %1, %0 : !b200i, !cir.ptr<!b200i> + %2 = cir.load %0 : !cir.ptr<!b200i>, !b200i + cir.return %2 : !b200i + } + + // CHECK-LABEL: cir.func{{.*}} @call_ret_b200( + // CHECK-SAME: %arg0: !cir.ptr<!cir.int<s, 200, bitint>> {llvm.align = 8 : i64 + // CHECK-SAME: , llvm.dead_on_unwind, llvm.noalias, llvm.sret = !cir.int<s, 200, bitint> + // CHECK-SAME: , llvm.writable}) + // CHECK-NEXT: cir.call @ret_b200(%arg0) + // CHECK-SAME: : (!cir.ptr<!cir.int<s, 200, bitint>> {llvm.align = 8 : i64 + // CHECK-SAME: , llvm.dead_on_unwind, llvm.sret = !cir.int<s, 200, bitint>, llvm.writable}) -> () + // CHECK-NEXT: cir.return + + // LLVM-LABEL: define void @call_ret_b200( + // LLVM-SAME: ptr dead_on_unwind noalias writable sret(i256) align 8 + // LLVM-SAME: %[[ARG:[0-9]+]]) + // LLVM-NEXT: call void @ret_b200(ptr dead_on_unwind writable sret(i256) align 8 %[[ARG]]) + // LLVM-NEXT: ret void + + // A struct member wider than 128 bits recurses through the same rule as a + // scalar of that width; the struct as a whole still passes byval since it + // spans more than two eightbytes. + cir.func @take_d(%arg0: !rec_D) { + cir.return + } + + // CHECK-LABEL: cir.func{{.*}} @take_d( + // CHECK-SAME: %arg0: !cir.ptr<!rec_D> {llvm.align = 8 : i64 + // CHECK-SAME: , llvm.byval = !rec_D, llvm.noundef}) + // CHECK-NEXT: %{{[0-9]+}} = cir.load %arg0 : !cir.ptr<!rec_D>, !rec_D + // CHECK-NEXT: cir.return + + // LLVM-LABEL: define void @take_d( + // LLVM-SAME: ptr noundef byval(%struct.D) align 8 %[[ARG:[0-9]+]]) + // LLVM-NEXT: %{{[0-9]+}} = load %struct.D, ptr %[[ARG]] + // LLVM-NEXT: ret void + + // A union member wider than 128 bits recurses through the same rule via + // the union's own member-recursion check, a separate code path from a + // struct's. + cir.func @take_ud(%arg0: !rec_UD) { + cir.return + } + + // CHECK-LABEL: cir.func{{.*}} @take_ud( + // CHECK-SAME: %arg0: !cir.ptr<!rec_UD> {llvm.align = 8 : i64 + // CHECK-SAME: , llvm.byval = !rec_UD, llvm.noundef}) + // CHECK-NEXT: %{{[0-9]+}} = cir.load %arg0 : !cir.ptr<!rec_UD>, !rec_UD + // CHECK-NEXT: cir.return + + // LLVM-LABEL: define void @take_ud( + // LLVM-SAME: ptr noundef byval(%union.UD) align 8 %[[ARG:[0-9]+]]) + // LLVM-NEXT: %{{[0-9]+}} = load %union.UD, ptr %[[ARG]] + // LLVM-NEXT: ret void + + // An array of a width wider than 128 bits recurses through the element type, + // a third member-recursion path distinct from a struct's and a union's. + cir.func @take_ad(%arg0: !rec_AD) { + cir.return + } + + // CHECK-LABEL: cir.func{{.*}} @take_ad( + // CHECK-SAME: %arg0: !cir.ptr<!rec_AD> {llvm.align = 8 : i64 + // CHECK-SAME: , llvm.byval = !rec_AD, llvm.noundef}) + // CHECK-NEXT: %{{[0-9]+}} = cir.load %arg0 : !cir.ptr<!rec_AD>, !rec_AD + // CHECK-NEXT: cir.return + + // LLVM-LABEL: define void @take_ad( + // LLVM-SAME: ptr noundef byval(%struct.AD) align 8 %[[ARG:[0-9]+]]) + // LLVM-NEXT: %{{[0-9]+}} = load %struct.AD, ptr %[[ARG]] + // LLVM-NEXT: ret void } diff --git a/clang/test/CIR/Transforms/abi-lowering/x86_64-int-nyi.cir b/clang/test/CIR/Transforms/abi-lowering/x86_64-int-nyi.cir index 683551cafcb36..aeeedeba4db21 100644 --- a/clang/test/CIR/Transforms/abi-lowering/x86_64-int-nyi.cir +++ b/clang/test/CIR/Transforms/abi-lowering/x86_64-int-nyi.cir @@ -1,9 +1,6 @@ // RUN: not cir-opt %s -cir-call-conv-lowering=target=x86_64 2>&1 | FileCheck %s -!b129i = !cir.int<s, 129, bitint> -!b200i = !cir.int<s, 200, bitint> !i96 = !cir.int<s, 96> -!rec_D = !cir.struct<"D" {data !b200i}> module attributes { dlti.dl_spec = #dlti.dl_spec< @@ -12,27 +9,6 @@ module attributes { #dlti.dl_entry<i128, dense<128>: vector<2xi64>>> } { - // 129 bits is the first rejected width. - cir.func @take_b129(%arg0: !b129i) { - cir.return - } - - // CHECK: not yet implemented for type '!cir.int<s, 129, bitint> - - cir.func @take_b200(%arg0: !b200i) { - cir.return - } - - // CHECK: not yet implemented for type '!cir.int<s, 200, bitint> - - // A struct carrying an over-wide _BitInt is rejected through the same - // recursive field check. - cir.func @take_d(%arg0: !rec_D) { - cir.return - } - - // CHECK: not yet implemented for type '!cir.struct<"D" - // A non-bitint integer of intermediate width (65..127) is rejected: only // widths up to 64 and exactly 128 are accepted. cir.func @take_i96(%arg0: !i96) { >From 4436c314fdc10beff2ea78fbab0a04ff25d4f8ed Mon Sep 17 00:00:00 2001 From: Adam Smith <[email protected]> Date: Mon, 14 Sep 2026 08:20:45 -0700 Subject: [PATCH 5/5] [CIR] Combine the LLVM and OGCG checks in the wide `_BitInt` test Assisted-by: Cursor / claude-opus-5 --- .../call-conv-lowering-x86_64-bitint.c | 25 ++++++------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-bitint.c b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-bitint.c index a9a3616a0c765..9334724773f89 100644 --- a/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-bitint.c +++ b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-bitint.c @@ -12,23 +12,14 @@ void take_wide(_BitInt(200) x) {} // CIR-SAME: , llvm.byval = !cir.int<s, 200, bitint>, llvm.noundef} loc{{.*}}) // The byval copy is sized from the padded storage type (i256), matching -// classic. One divergence survives: the position of the local's alloca, -// which CIR places at the point of the declaration while classic hoists -// every alloca to the function's entry block ahead of the parameter -// materialization. +// classic. Only the local's alloca moves: CIR emits it where the +// declaration is, classic hoists it to the top of the entry block. // LLVM-LABEL: define dso_local void @take_wide( // LLVM-SAME: ptr noundef byval(i256) align 8 %[[ARG:[0-9]+]]) - -// LLVMCIR-NEXT: %[[WIDE:.+]] = load i256, ptr %[[ARG]], align 8 -// LLVMCIR-NEXT: %[[NARROW:.+]] = trunc i256 %[[WIDE]] to i200 -// LLVMCIR-NEXT: %[[SLOT:.+]] = alloca i256, align 8 -// LLVMCIR-NEXT: %[[EXT:.+]] = sext i200 %[[NARROW]] to i256 -// LLVMCIR-NEXT: store i256 %[[EXT]], ptr %[[SLOT]], align 8 -// LLVMCIR-NEXT: ret void - // OGCG: %[[SLOT:.+]] = alloca i256, align 8 -// OGCG-NEXT: %[[WIDE:.+]] = load i256, ptr %[[ARG]], align 8 -// OGCG-NEXT: %[[NARROW:.+]] = trunc i256 %[[WIDE]] to i200 -// OGCG-NEXT: %[[EXT:.+]] = sext i200 %[[NARROW]] to i256 -// OGCG-NEXT: store i256 %[[EXT]], ptr %[[SLOT]], align 8 -// OGCG-NEXT: ret void +// LLVM: %[[WIDE:.+]] = load i256, ptr %[[ARG]], align 8 +// LLVM-NEXT: %[[NARROW:.+]] = trunc i256 %[[WIDE]] to i200 +// LLVMCIR-NEXT: %[[SLOT:.+]] = alloca i256, align 8 +// LLVM-NEXT: %[[EXT:.+]] = sext i200 %[[NARROW]] to i256 +// LLVM-NEXT: store i256 %[[EXT]], ptr %[[SLOT]], align 8 +// LLVM-NEXT: ret void _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
