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

Reply via email to