Author: adams381 Date: 2026-07-22T10:52:33-05:00 New Revision: 0590a36b312b950cb16dadfe349d07e73be1809a
URL: https://github.com/llvm/llvm-project/commit/0590a36b312b950cb16dadfe349d07e73be1809a DIFF: https://github.com/llvm/llvm-project/commit/0590a36b312b950cb16dadfe349d07e73be1809a.diff LOG: [CIR] Convert byval/sret/byref arg-attr types when lowering to LLVM (#210764) The CallConvLowering pass lowers an indirectly-passed aggregate to a pointer argument and records the pointee record type on the `llvm.byval`, `llvm.sret`, or `llvm.byref` argument attribute. That type payload is still a CIR record after the pass runs, so once the module reaches the LLVM dialect and is translated to LLVM IR, the translation hits a CIR type inside the attribute and fails. LowerToLLVM already routes every operand and result type through the type converter. It now does the same for the type carried by those three argument attributes, in the attribute lowering shared by the function definition and by the call and invoke sites. With the conversion in place, a byval or sret parameter translates to `byval(%struct.X)` / `sret(%struct.X)` carrying the lowered LLVM struct type, and byref does the same. CallConvLowering does not classify `cir.try_call` yet, so no invoke carries these attributes today, but the shared path already covers the invoke once it is classified. The test injects the three attributes directly, lowers to the LLVM dialect, and translates to LLVM IR, checking the emitted attributes carry the LLVM struct type. It uses no aggregate classifier, so it stands alone from the x86_64 classifier stack. Added: clang/test/CIR/Transforms/abi-lowering/byval-sret-arg-attr-lowering.cir Modified: clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp Removed: ################################################################################ diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp index f3f77a9151c35..88706bace854d 100644 --- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp +++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp @@ -1868,7 +1868,45 @@ mlir::LogicalResult CIRToLLVMRotateOpLowering::matchAndRewrite( return mlir::LogicalResult::success(); } +/// 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) { + if (!argAttrs) + return argAttrs; + bool changed = false; + SmallVector<mlir::Attribute> loweredArgAttrs; + loweredArgAttrs.reserve(argAttrs.size()); + for (mlir::Attribute a : argAttrs) { + auto dict = cast<mlir::DictionaryAttr>(a); + SmallVector<mlir::NamedAttribute> entries(dict.begin(), dict.end()); + for (mlir::NamedAttribute &entry : entries) { + StringRef name = entry.getName().strref(); + if (name != mlir::LLVM::LLVMDialect::getByValAttrName() && + name != mlir::LLVM::LLVMDialect::getStructRetAttrName() && + name != mlir::LLVM::LLVMDialect::getByRefAttrName()) + continue; + auto typeAttr = dyn_cast<mlir::TypeAttr>(entry.getValue()); + if (!typeAttr) + continue; + mlir::Type lowered = converter.convertType(typeAttr.getValue()); + if (lowered && lowered != typeAttr.getValue()) { + entry.setValue(mlir::TypeAttr::get(lowered)); + changed = true; + } + } + loweredArgAttrs.push_back(mlir::DictionaryAttr::get(ctx, entries)); + } + return changed ? mlir::ArrayAttr::get(ctx, loweredArgAttrs) : argAttrs; +} + static void lowerCallAttributes(cir::CIRCallOpInterface op, + const mlir::TypeConverter &converter, SmallVectorImpl<mlir::NamedAttribute> &result) { for (mlir::NamedAttribute attr : op->getAttrs()) { if (attr.getName() == CIRDialect::getCalleeAttrName() || @@ -1880,6 +1918,13 @@ static void lowerCallAttributes(cir::CIRCallOpInterface op, continue; 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())); + continue; + } result.push_back(attr); } } @@ -1909,7 +1954,7 @@ rewriteCallOrInvoke(mlir::Operation *op, mlir::ValueRange callOperands, memoryEffects, noUnwind, willReturn, noReturn); SmallVector<mlir::NamedAttribute, 4> attributes; - lowerCallAttributes(call, attributes); + lowerCallAttributes(call, *converter, attributes); mlir::LLVM::LLVMFunctionType llvmFnTy; @@ -2398,6 +2443,13 @@ void CIRToLLVMFuncOpLowering::lowerFuncAttributes( continue; 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())); + continue; + } result.push_back(attr); } } diff --git a/clang/test/CIR/Transforms/abi-lowering/byval-sret-arg-attr-lowering.cir b/clang/test/CIR/Transforms/abi-lowering/byval-sret-arg-attr-lowering.cir new file mode 100644 index 0000000000000..8f121e5ed78dd --- /dev/null +++ b/clang/test/CIR/Transforms/abi-lowering/byval-sret-arg-attr-lowering.cir @@ -0,0 +1,85 @@ +// 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: -cir-to-llvm -o - 2>/dev/null \ +// RUN: | mlir-translate -mlir-to-llvmir --allow-unregistered-dialect \ +// RUN: | FileCheck %s --check-prefix=LLVM + +!s64i = !cir.int<s, 64> +!rec_Big = !cir.struct<"Big" {!s64i, !s64i, !s64i}> + +#byval_arg = { + return = { kind = "direct" }, + args = [ { kind = "indirect", indirect_align = 8 } ] +} + +#sret_ret = { + return = { kind = "indirect", indirect_align = 8 }, + args = [ ] +} + +#byref_arg = { + return = { kind = "direct" }, + args = [ { kind = "indirect", indirect_align = 8, byval = false } ] +} + +#passthrough = { + return = { kind = "direct" }, + args = [ ] +} + +module attributes { + cir.triple = "x86_64-unknown-linux-gnu", + dlti.dl_spec = #dlti.dl_spec< + #dlti.dl_entry<i32, dense<32>: vector<2xi64>>, + #dlti.dl_entry<i64, dense<64>: vector<2xi64>>> +} { + + // The byval argument attribute carries the record type. cir-to-llvm must + // convert that type to the LLVM struct so translation emits byval(%struct). + cir.func @takes_big(%arg0: !rec_Big) attributes { test_classify = #byval_arg } { + cir.return + } + + // CHECK: cir.func{{.*}} @takes_big(%{{.*}}: !cir.ptr<!rec_Big> {{{.*}}llvm.byval = !rec_Big{{.*}}}) + // LLVM: define void @takes_big(ptr noalias noundef byval(%struct.Big) align 8 %{{.+}}) + + // The sret return attribute is converted the same way. + cir.func @ret_big() -> !rec_Big attributes { test_classify = #sret_ret } { + %0 = cir.alloca "r" 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{{.*}} @ret_big(%{{.*}}: !cir.ptr<!rec_Big> {{{.*}}llvm.sret = !rec_Big{{.*}}}) + // LLVM: define void @ret_big(ptr dead_on_unwind noalias writable sret(%struct.Big) align 8 %{{.+}}) + + // byref carries the type the same way and is converted identically. + cir.func @takes_byref(%arg0: !rec_Big) attributes { test_classify = #byref_arg } { + cir.return + } + + // CHECK: cir.func{{.*}} @takes_byref(%{{.*}}: !cir.ptr<!rec_Big> {{{.*}}llvm.byref = !rec_Big{{.*}}}) + // LLVM: define void @takes_byref(ptr byref(%struct.Big) align 8 %{{.+}}) + + // The call site's byval operand attribute is converted too. + cir.func @caller(%s: !rec_Big) attributes { test_classify = #passthrough } { + cir.call @takes_big(%s) : (!rec_Big) -> () + cir.return + } + + // LLVM: define void @caller(%struct.Big %{{.+}}) + // LLVM: call void @takes_big(ptr noalias noundef byval(%struct.Big) align 8 %{{.+}}) + + // A caller of an sret function carries the sret attribute on the call + // operand; its type payload is converted as well. + cir.func @caller_sret() attributes { test_classify = #passthrough } { + %r = cir.call @ret_big() : () -> !rec_Big + cir.return + } + + // LLVM: define void @caller_sret() + // LLVM: call void @ret_big(ptr {{.*}}sret(%struct.Big) align 8 %{{.+}}) +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
