https://github.com/adams381 updated https://github.com/llvm/llvm-project/pull/195745
>From bb0d6cca069ecaf5042b5bc528343ff6a497b632 Mon Sep 17 00:00:00 2001 From: Adam Smith <[email protected]> Date: Mon, 4 May 2026 14:09:42 -0700 Subject: [PATCH] [CIR] Add Extend (signext/zeroext) handling to CallConvLowering Third PR in the series splitting #192119 / #192124. Adds handlers for the Extend ArgKind in both rewriteFunctionDefinition and rewriteCallSite. The CIR signature keeps the original (narrow) integer type; sign- or zero-extension is communicated to LLVM via the llvm.signext / llvm.zeroext arg_attrs and res_attrs entries attached by the rewriter. This matches Classic Clang's LLVM IR convention (e.g. `define void @f(i8 signext %x)`, not `define void @f(i32 signext %x)` with an entry-block truncation). The coercedType field on an Extend ArgClassification is treated as informational only -- it carries the LLVM ABI library's register-width type for downstream consumers, but the rewriter does not use it to change the CIR signature. Three .cir tests covering the three Extend code paths (narrow-signed-arg, narrow-unsigned-arg, narrow-signed-return). The test target's narrow-int Extend rule fires only on MLIR builtin IntegerType -- since CIR functions take cir::IntType, these tests exercise the rewriter via the classification- injection driver added in PR A1. check-clang-cir-codegen / check-clang-cir both pass with no regressions. Co-authored-by: Cursor <[email protected]> --- .../TargetLowering/CIRABIRewriteContext.cpp | 152 ++++++++++++++---- .../Transforms/abi-lowering/extend-return.cir | 41 +++++ .../abi-lowering/extend-signed-arg.cir | 40 +++++ .../abi-lowering/extend-unsigned-arg.cir | 36 +++++ 4 files changed, 239 insertions(+), 30 deletions(-) create mode 100644 clang/test/CIR/Transforms/abi-lowering/extend-return.cir create mode 100644 clang/test/CIR/Transforms/abi-lowering/extend-signed-arg.cir create mode 100644 clang/test/CIR/Transforms/abi-lowering/extend-unsigned-arg.cir diff --git a/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp b/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp index d33346cc0dcb3..895b477eb9054 100644 --- a/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp +++ b/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp @@ -27,6 +27,10 @@ using namespace mlir::abi; namespace { bool needsRewrite(const FunctionClassification &fc) { + // Direct without coercion is a true pass-through; any other kind (or a + // coerced Direct) means the rewriter must touch the IR. Extend is + // technically attribute-only at the IR level but still counts because the + // attribute attachment changes observable behavior. if ((fc.returnInfo.kind != ArgKind::Direct) || fc.returnInfo.coercedType) return true; for (const ArgClassification &ac : fc.argInfos) @@ -72,9 +76,14 @@ LogicalResult buildNewArgTypes(ArrayRef<Type> oldArgTypes, << " not yet implemented in CallConvLowering"; return failure(); case ArgKind::Extend: - emitError() << "Extend at arg " << idx - << " not yet implemented in CallConvLowering"; - return failure(); + // Extend keeps the original (narrow) type in the signature; the + // sign/zero extension is communicated to LLVM via the llvm.signext / + // llvm.zeroext arg attribute, attached separately below. Any + // coercedType the classifier set on the Extend ArgClassification is + // informational (typically the register-width type the value gets + // extended to in registers) but does not change the CIR signature. + newArgTypes.push_back(origTy); + break; case ArgKind::Indirect: emitError() << "Indirect at arg " << idx << " not yet implemented in CallConvLowering"; @@ -105,8 +114,10 @@ Type computeNewReturnType(Type origRetTy, const ArgClassification &retInfo, << "it in EmitFunctionEpilog)"; return nullptr; case ArgKind::Extend: - emitError() << "Extend return not yet implemented in CallConvLowering"; - return nullptr; + // Same convention as Extend args: keep the original return type in the + // signature; the sign/zero extension is communicated via the + // llvm.signext / llvm.zeroext res attribute attached separately below. + return origRetTy; case ArgKind::Indirect: emitError() << "Indirect return (sret) not yet implemented in " << "CallConvLowering"; @@ -124,6 +135,67 @@ Value createIgnoredValue(OpBuilder &builder, Location loc, Type ty) { return cir::ConstantOp::create(builder, loc, ty, cir::PoisonAttr::get(ty)); } +/// Build an updated arg_attrs ArrayAttr that drops Ignore'd args and adds +/// llvm.signext / llvm.zeroext on Extend args. Preserves any existing arg +/// attributes on retained arg slots. +ArrayAttr updateArgAttrs(MLIRContext *ctx, unsigned numNewArgs, + ArrayAttr existingArgAttrs, + const FunctionClassification &fc) { + SmallVector<Attribute> newArgAttrs(numNewArgs, DictionaryAttr::get(ctx)); + + // Step 1: copy existing arg attrs over to their new positions, skipping + // Ignore'd args. + if (existingArgAttrs) { + unsigned newIdx = 0; + for (unsigned oldIdx = 0; oldIdx < existingArgAttrs.size(); ++oldIdx) { + if (oldIdx < fc.argInfos.size() && + fc.argInfos[oldIdx].kind == ArgKind::Ignore) + continue; + if (newIdx < numNewArgs) + newArgAttrs[newIdx] = existingArgAttrs[oldIdx]; + ++newIdx; + } + } + + // Step 2: layer llvm.signext / llvm.zeroext onto each Extend arg. The new + // arg index for Extend at original index `oldIdx` is `oldIdx` minus the + // number of Ignore'd args that came before it. + unsigned newIdx = 0; + for (auto [oldIdx, ac] : llvm::enumerate(fc.argInfos)) { + if (ac.kind == ArgKind::Ignore) + continue; + if (ac.kind == ArgKind::Extend && newIdx < numNewArgs) { + auto existing = cast<DictionaryAttr>(newArgAttrs[newIdx]); + SmallVector<NamedAttribute> attrs(existing.begin(), existing.end()); + StringRef attrName = ac.signExtend ? "llvm.signext" : "llvm.zeroext"; + attrs.push_back( + NamedAttribute(StringAttr::get(ctx, attrName), UnitAttr::get(ctx))); + newArgAttrs[newIdx] = DictionaryAttr::get(ctx, attrs); + } + ++newIdx; + } + + return ArrayAttr::get(ctx, newArgAttrs); +} + +/// Build an updated res_attrs ArrayAttr (single entry, since CIR funcs have +/// at most one result) that adds llvm.signext / llvm.zeroext on an Extend +/// return. Preserves any existing res attributes. +ArrayAttr updateResAttrs(MLIRContext *ctx, ArrayAttr existingResAttrs, + const ArgClassification &retInfo) { + if (retInfo.kind != ArgKind::Extend) + return existingResAttrs; + + SmallVector<NamedAttribute> attrs; + if (existingResAttrs && existingResAttrs.size() > 0) + for (NamedAttribute na : cast<DictionaryAttr>(existingResAttrs[0])) + attrs.push_back(na); + StringRef attrName = retInfo.signExtend ? "llvm.signext" : "llvm.zeroext"; + attrs.push_back( + NamedAttribute(StringAttr::get(ctx, attrName), UnitAttr::get(ctx))); + return ArrayAttr::get(ctx, {DictionaryAttr::get(ctx, attrs)}); +} + } // namespace LogicalResult CIRABIRewriteContext::rewriteFunctionDefinition( @@ -212,22 +284,23 @@ LogicalResult CIRABIRewriteContext::rewriteFunctionDefinition( Type newFnTy = funcOp.cloneTypeWith(newArgTypes, newResultTypes); funcOp.setFunctionTypeAttr(TypeAttr::get(newFnTy)); - // Keep the arg_attrs array in sync with the new argument count by - // dropping entries for every Ignored argument. Without this the - // attribute array would have stale entries that no longer match any - // block argument. - SmallVector<unsigned> ignored = ignoredArgIndices(fc); - if (!ignored.empty()) { - if (auto existing = funcOp->getAttrOfType<ArrayAttr>("arg_attrs")) { - SmallVector<Attribute> kept; - kept.reserve(newArgTypes.size()); - for (auto [oldIdx, attr] : llvm::enumerate(existing.getValue())) { - if (oldIdx >= fc.argInfos.size() || - fc.argInfos[oldIdx].kind != ArgKind::Ignore) - kept.push_back(attr); - } - funcOp->setAttr("arg_attrs", ArrayAttr::get(ctx, kept)); - } + // Rebuild arg_attrs: drop entries for Ignore'd args, layer + // llvm.signext / llvm.zeroext onto Extend args, preserve everything else. + bool needsArgAttrUpdate = !ignoredArgIndices(fc).empty(); + for (const ArgClassification &ac : fc.argInfos) + if (ac.kind == ArgKind::Extend) + needsArgAttrUpdate = true; + if (needsArgAttrUpdate) { + auto existing = funcOp->getAttrOfType<ArrayAttr>("arg_attrs"); + funcOp->setAttr("arg_attrs", + updateArgAttrs(ctx, newArgTypes.size(), existing, fc)); + } + + // Rebuild res_attrs: layer llvm.signext / llvm.zeroext onto an Extend + // return. + if (fc.returnInfo.kind == ArgKind::Extend) { + auto existing = funcOp->getAttrOfType<ArrayAttr>("res_attrs"); + funcOp->setAttr("res_attrs", updateResAttrs(ctx, existing, fc.returnInfo)); } return success(); @@ -247,6 +320,8 @@ LogicalResult CIRABIRewriteContext::rewriteCallSite( return call.emitOpError() << "indirect call not yet implemented in CallConvLowering"; + MLIRContext *ctx = callOp->getContext(); + for (auto [idx, ac] : llvm::enumerate(fc.argInfos)) { switch (ac.kind) { case ArgKind::Direct: @@ -261,8 +336,9 @@ LogicalResult CIRABIRewriteContext::rewriteCallSite( return call.emitOpError() << "Expand at call-site arg " << idx << " not yet implemented in CallConvLowering"; case ArgKind::Extend: - return call.emitOpError() << "Extend at call-site arg " << idx - << " not yet implemented in CallConvLowering"; + // Extend at the call site is just an attribute change (llvm.signext / + // llvm.zeroext on the call's arg_attrs); no IR-level cast. + break; case ArgKind::Indirect: return call.emitOpError() << "Indirect at call-site arg " << idx << " not yet implemented in CallConvLowering"; @@ -284,15 +360,13 @@ LogicalResult CIRABIRewriteContext::rewriteCallSite( } bool hasResult = call.getNumResults() > 0; - Type origRetTy = hasResult ? call.getResult().getType() - : cir::VoidType::get(callOp->getContext()); + Type origRetTy = + hasResult ? call.getResult().getType() : cir::VoidType::get(ctx); Type callRetTy = origRetTy; if (fc.returnInfo.kind == ArgKind::Ignore && hasResult) - callRetTy = cir::VoidType::get(callOp->getContext()); - if ((fc.returnInfo.kind == ArgKind::Direct || - fc.returnInfo.kind == ArgKind::Extend) && - fc.returnInfo.coercedType) - return call.emitOpError() << "Direct/Extend return with coerced type at " + callRetTy = cir::VoidType::get(ctx); + if (fc.returnInfo.kind == ArgKind::Direct && fc.returnInfo.coercedType) + return call.emitOpError() << "Direct return with coerced type at " << "call-site not yet implemented in " << "CallConvLowering"; @@ -303,6 +377,24 @@ LogicalResult CIRABIRewriteContext::rewriteCallSite( if (!newCall->hasAttr(attr.getName())) newCall->setAttr(attr.getName(), attr.getValue()); + // Layer llvm.signext / llvm.zeroext onto the new call's arg_attrs and + // res_attrs for Extend args/return. + bool needsArgAttrUpdate = false; + for (const ArgClassification &ac : fc.argInfos) + if (ac.kind == ArgKind::Extend || ac.kind == ArgKind::Ignore) { + needsArgAttrUpdate = true; + break; + } + if (needsArgAttrUpdate) { + auto existing = call->getAttrOfType<ArrayAttr>("arg_attrs"); + newCall->setAttr("arg_attrs", + updateArgAttrs(ctx, newArgs.size(), existing, fc)); + } + if (fc.returnInfo.kind == ArgKind::Extend) { + auto existing = call->getAttrOfType<ArrayAttr>("res_attrs"); + newCall->setAttr("res_attrs", updateResAttrs(ctx, existing, fc.returnInfo)); + } + if (hasResult && fc.returnInfo.kind == ArgKind::Ignore) { // The new call returns void, but the original call's result may still // have uses. Substitute a poison constant of the original type so diff --git a/clang/test/CIR/Transforms/abi-lowering/extend-return.cir b/clang/test/CIR/Transforms/abi-lowering/extend-return.cir new file mode 100644 index 0000000000000..dd2c40fc5305e --- /dev/null +++ b/clang/test/CIR/Transforms/abi-lowering/extend-return.cir @@ -0,0 +1,41 @@ +// Extend on a narrow return attaches llvm.signext (or llvm.zeroext) to +// res_attrs while keeping the narrow type in the signature. Body and +// call-site IR are unchanged otherwise. +// RUN: cir-opt %s -cir-call-conv-lowering="classification-attr=test_classify" \ +// RUN: | FileCheck %s + +!s8i = !cir.int<s, 8> + +#extend_signed_return = { + return = { kind = "extend", + coerced_type = !cir.int<s, 32>, + sign_extend = true }, + args = [ ] +} + +module attributes { + dlti.dl_spec = #dlti.dl_spec< + #dlti.dl_entry<i8, dense<8>: vector<2xi64>>, + #dlti.dl_entry<i32, dense<32>: vector<2xi64>>> +} { + + cir.func @returns_s8() -> !s8i + attributes { test_classify = #extend_signed_return } { + %0 = cir.const #cir.int<7> : !s8i + cir.return %0 : !s8i + } + + // CHECK: cir.func{{.*}} @returns_s8() -> (!s8i {llvm.signext}) + // CHECK: cir.return %{{.*}} : !s8i + + cir.func @caller() -> !s8i + attributes { test_classify = #extend_signed_return } { + %0 = cir.call @returns_s8() : () -> !s8i + cir.return %0 : !s8i + } + + // CHECK: cir.func{{.*}} @caller() -> (!s8i {llvm.signext}) + // CHECK: %[[R:.*]] = cir.call @returns_s8() : () -> (!s8i {llvm.signext}) + // CHECK: cir.return %[[R]] : !s8i + +} diff --git a/clang/test/CIR/Transforms/abi-lowering/extend-signed-arg.cir b/clang/test/CIR/Transforms/abi-lowering/extend-signed-arg.cir new file mode 100644 index 0000000000000..600acdc94d6b3 --- /dev/null +++ b/clang/test/CIR/Transforms/abi-lowering/extend-signed-arg.cir @@ -0,0 +1,40 @@ +// Extend with sign_extend = true on a narrow signed integer arg. The pass +// keeps the narrow type in the signature (matching Classic Clang's LLVM IR +// convention) and attaches llvm.signext to the corresponding arg_attrs. +// +// The test target's narrow-int Extend rule fires only on MLIR builtin +// IntegerType, not cir::IntType, so this test uses the injection driver. +// RUN: cir-opt %s -cir-call-conv-lowering="classification-attr=test_classify" \ +// RUN: | FileCheck %s + +!s8i = !cir.int<s, 8> + +#extend_signed_arg = { + return = { kind = "direct" }, + args = [ { kind = "extend", + coerced_type = !cir.int<s, 32>, + sign_extend = true } ] +} + +module attributes { + dlti.dl_spec = #dlti.dl_spec< + #dlti.dl_entry<i8, dense<8>: vector<2xi64>>, + #dlti.dl_entry<i32, dense<32>: vector<2xi64>>> +} { + + cir.func @takes_s8(%arg0: !s8i) + attributes { test_classify = #extend_signed_arg } { + cir.return + } + + // CHECK: cir.func{{.*}} @takes_s8(%arg0: !s8i {llvm.signext}) + + cir.func @caller(%arg0: !s8i) + attributes { test_classify = #extend_signed_arg } { + cir.call @takes_s8(%arg0) : (!s8i) -> () + cir.return + } + + // CHECK: cir.call @takes_s8(%arg0) : (!s8i {llvm.signext}) -> () + +} diff --git a/clang/test/CIR/Transforms/abi-lowering/extend-unsigned-arg.cir b/clang/test/CIR/Transforms/abi-lowering/extend-unsigned-arg.cir new file mode 100644 index 0000000000000..6c5c8207bb93b --- /dev/null +++ b/clang/test/CIR/Transforms/abi-lowering/extend-unsigned-arg.cir @@ -0,0 +1,36 @@ +// Extend with sign_extend = false on a narrow unsigned integer arg attaches +// llvm.zeroext (instead of llvm.signext) to the corresponding arg_attrs. +// RUN: cir-opt %s -cir-call-conv-lowering="classification-attr=test_classify" \ +// RUN: | FileCheck %s + +!u16i = !cir.int<u, 16> + +#extend_unsigned_arg = { + return = { kind = "direct" }, + args = [ { kind = "extend", + coerced_type = !cir.int<u, 32>, + sign_extend = false } ] +} + +module attributes { + dlti.dl_spec = #dlti.dl_spec< + #dlti.dl_entry<i16, dense<16>: vector<2xi64>>, + #dlti.dl_entry<i32, dense<32>: vector<2xi64>>> +} { + + cir.func @takes_u16(%arg0: !u16i) + attributes { test_classify = #extend_unsigned_arg } { + cir.return + } + + // CHECK: cir.func{{.*}} @takes_u16(%arg0: !u16i {llvm.zeroext}) + + cir.func @caller(%arg0: !u16i) + attributes { test_classify = #extend_unsigned_arg } { + cir.call @takes_u16(%arg0) : (!u16i) -> () + cir.return + } + + // CHECK: cir.call @takes_u16(%arg0) : (!u16i {llvm.zeroext}) -> () + +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
