Author: adams381 Date: 2026-05-29T13:46:53-05:00 New Revision: 43511b564751fa0231d012b8bda59b88915d5855
URL: https://github.com/llvm/llvm-project/commit/43511b564751fa0231d012b8bda59b88915d5855 DIFF: https://github.com/llvm/llvm-project/commit/43511b564751fa0231d012b8bda59b88915d5855.diff LOG: [CIR] Add Extend (signext/zeroext) handling to CallConvLowering (#195745) Third PR in the series splitting [#192119](https://github.com/llvm/llvm-project/pull/192119) / [#192124](https://github.com/llvm/llvm-project/pull/192124). [#195725](https://github.com/llvm/llvm-project/pull/195725) and [#195737](https://github.com/llvm/llvm-project/pull/195737) have merged; this PR is now a standalone diff on main. Adds Extend (signext / zeroext) to `cir-call-conv-lowering`. The CIR signature keeps the original narrow integer type; the rewriter attaches `llvm.signext` / `llvm.zeroext` to `arg_attrs` and `res_attrs`. That matches classic Clang's LLVM IR convention — `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 informational only; the rewriter doesn't use it to change the CIR signature. Three `.cir` tests cover narrow-signed-arg, narrow-unsigned-arg, and narrow-signed-return. Since the test target's narrow-int Extend rule fires only on MLIR `IntegerType` and CIR functions use `cir::IntType`, these tests drive the rewriter through the classification-injection path added in [#195725](https://github.com/llvm/llvm-project/pull/195725). Added: clang/test/CIR/Transforms/abi-lowering/extend-after-ignore.cir clang/test/CIR/Transforms/abi-lowering/extend-return.cir clang/test/CIR/Transforms/abi-lowering/extend-signed-arg.cir clang/test/CIR/Transforms/abi-lowering/extend-unsigned-arg.cir Modified: clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp Removed: ################################################################################ diff --git a/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp b/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp index d33346cc0dcb3..2c29c83b999ba 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) @@ -35,14 +39,6 @@ bool needsRewrite(const FunctionClassification &fc) { return false; } -SmallVector<unsigned> ignoredArgIndices(const FunctionClassification &fc) { - SmallVector<unsigned> v; - for (auto [idx, ac] : llvm::enumerate(fc.argInfos)) - if (ac.kind == ArgKind::Ignore) - v.push_back(idx); - return v; -} - /// Build the new argument-type list for a function whose ABI classification /// is \p fc. This currently handles only Direct (no coercion) and Ignore; /// other kinds emit an error. Classifications that add arguments (e.g. @@ -72,9 +68,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 +106,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,17 +127,65 @@ 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, ArrayAttr existingArgAttrs, + const FunctionClassification &fc) { + SmallVector<Attribute> newArgAttrs; + newArgAttrs.reserve(fc.argInfos.size()); + for (auto [oldIdx, ac] : llvm::enumerate(fc.argInfos)) { + if (ac.kind == ArgKind::Ignore) + continue; + DictionaryAttr existing = DictionaryAttr::get(ctx); + if (existingArgAttrs && oldIdx < existingArgAttrs.size()) + existing = cast<DictionaryAttr>(existingArgAttrs[oldIdx]); + if (ac.kind == ArgKind::Extend) { + StringRef attrName = ac.signExtend ? "llvm.signext" : "llvm.zeroext"; + NamedAttribute extAttr(StringAttr::get(ctx, attrName), + UnitAttr::get(ctx)); + if (existing.empty()) { + newArgAttrs.push_back(DictionaryAttr::get(ctx, {extAttr})); + } else { + SmallVector<NamedAttribute> attrs(existing.begin(), existing.end()); + attrs.push_back(extAttr); + newArgAttrs.push_back(DictionaryAttr::get(ctx, attrs)); + } + } else { + newArgAttrs.push_back(existing); + } + } + 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.empty()) + 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( FunctionOpInterface funcOpInterface, const FunctionClassification &fc, OpBuilder &builder) { - // The pass driver (CallConvLoweringPass) only ever hands us cir.func ops, - // and the body of this routine is end-to-end CIR (it creates cir.constant, - // cir.return, etc.). Cast once at the top so the rest of the function - // reads in CIR's own vocabulary, and so we can dispatch to the - // CIRGlobalValueInterface for isDefinition() (FunctionOpInterface alone - // does not inherit from CIRGlobalValueInterface). + // The pass driver (CallConvLoweringPass) only ever hands us cir.func ops. + // Cast once at the top so the rest of the function reads in CIR's own + // vocabulary, and so we can dispatch to the CIRGlobalValueInterface for + // isDefinition() (FunctionOpInterface alone does not inherit from + // CIRGlobalValueInterface). cir::FuncOp funcOp = cast<cir::FuncOp>(funcOpInterface); if (!needsRewrite(fc)) @@ -172,10 +223,13 @@ LogicalResult CIRABIRewriteContext::rewriteFunctionDefinition( // body still references it, replace those uses with a poison // constant. Ignore classifications mean the value is empty / not // passed at the ABI level, so any remaining uses are vacuous; - // poison says exactly that. - SmallVector<unsigned> ignored = ignoredArgIndices(fc); - for (unsigned blockIdx : llvm::reverse(ignored)) { - if (blockIdx >= entry.getNumArguments()) + // poison says exactly that. Iterate in reverse so that earlier + // indices stay stable as later ones are erased. + for (int blockIdx = static_cast<int>(fc.argInfos.size()) - 1; + blockIdx >= 0; --blockIdx) { + if (fc.argInfos[blockIdx].kind != ArgKind::Ignore) + continue; + if (static_cast<unsigned>(blockIdx) >= entry.getNumArguments()) continue; BlockArgument arg = entry.getArgument(blockIdx); if (!arg.use_empty()) { @@ -212,22 +266,22 @@ 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 when any arg is Ignore (dropped from the output array) + // or Extend (needs llvm.signext / llvm.zeroext layered on). + bool needsArgAttrUpdate = + llvm::any_of(fc.argInfos, [](const ArgClassification &ac) { + return ac.kind == ArgKind::Ignore || ac.kind == ArgKind::Extend; + }); + if (needsArgAttrUpdate) { + auto existing = funcOp->getAttrOfType<ArrayAttr>("arg_attrs"); + funcOp->setAttr("arg_attrs", updateArgAttrs(ctx, 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 +301,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 +317,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 +341,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 +358,22 @@ 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. Ignore args also require a rebuild + // because their slots are dropped from the output array. + bool needsArgAttrUpdate = + llvm::any_of(fc.argInfos, [](const ArgClassification &ac) { + return ac.kind == ArgKind::Ignore || ac.kind == ArgKind::Extend; + }); + if (needsArgAttrUpdate) { + auto existing = call->getAttrOfType<ArrayAttr>("arg_attrs"); + newCall->setAttr("arg_attrs", updateArgAttrs(ctx, 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-after-ignore.cir b/clang/test/CIR/Transforms/abi-lowering/extend-after-ignore.cir new file mode 100644 index 0000000000000..d19c511206b62 --- /dev/null +++ b/clang/test/CIR/Transforms/abi-lowering/extend-after-ignore.cir @@ -0,0 +1,44 @@ +// RUN: cir-opt %s -cir-call-conv-lowering="classification-attr=test_classify" \ +// RUN: | FileCheck %s + +!s8i = !cir.int<s, 8> +!s32i = !cir.int<s, 32> + +// Direct(0), Ignore(1), Extend-signed(2): the Ignore slot drops from the +// signature and the Extend slot shifts to position 1. llvm.signext must +// land on the shifted position, not the original one. +#direct_ignore_extend = { + return = { kind = "direct" }, + args = [ { kind = "direct" }, + { kind = "ignore" }, + { kind = "extend", + coerced_type = !s32i, + 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 @callee(%arg0: !s32i, %arg1: !s32i, %arg2: !s8i) -> !s32i + attributes { test_classify = #direct_ignore_extend } { + cir.return %arg0 : !s32i + } + + // Ignore slot drops; Extend shifts to position 1 and gets llvm.signext. + // CHECK: cir.func{{.*}} @callee(%arg0: !s32i, %arg1: !s8i {llvm.signext}) -> !s32i + // CHECK-NEXT: cir.return %arg0 : !s32i + + cir.func @caller(%arg0: !s32i, %arg1: !s32i, %arg2: !s8i) -> !s32i + attributes { test_classify = #direct_ignore_extend } { + %0 = cir.call @callee(%arg0, %arg1, %arg2) : (!s32i, !s32i, !s8i) -> !s32i + cir.return %0 : !s32i + } + + // CHECK: cir.func{{.*}} @caller(%arg0: !s32i, %arg1: !s8i {llvm.signext}) -> !s32i + // CHECK-NEXT: %[[R:.*]] = cir.call @callee(%arg0, %arg1) : (!s32i, !s8i {llvm.signext}) -> !s32i + // CHECK-NEXT: cir.return %[[R]] : !s32i + +} 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..1d07ec1468809 --- /dev/null +++ b/clang/test/CIR/Transforms/abi-lowering/extend-return.cir @@ -0,0 +1,38 @@ +// 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..499db952ccf42 --- /dev/null +++ b/clang/test/CIR/Transforms/abi-lowering/extend-signed-arg.cir @@ -0,0 +1,41 @@ +// 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 + +!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}) -> () + +} + +// LLVM: define void @takes_s8(i8 signext %{{.+}}) +// LLVM: define void @caller(i8 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..8c3e01c6328df --- /dev/null +++ b/clang/test/CIR/Transforms/abi-lowering/extend-unsigned-arg.cir @@ -0,0 +1,34 @@ +// 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
