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

Reply via email to