[Mlir-commits] [mlir] [mlir][func] Forward llvm. call attributes and no_inline in -convert-func-to-llvm (PR #227672)

Samarth Narang llvmlistbot at llvm.org
Wed Sep 30 04:52:08 PDT 2026


https://github.com/snarang181 created https://github.com/llvm/llvm-project/pull/227672

The call lowering built the `llvm.call` from the discardable attributes only, so the `arg_attrs`,
 `res_attrs` and `no_inline` properties of `func.call` and `func.call_indirect` were dropped. 

`llvm.signext` takes part in the call ABI and `llvm.noundef` / `no_inline` carry optimizer contracts, so a successful conversion changed the semantics of the call.

This PR forwards the `llvm.`-prefixed entries of `arg_attrs` and `res_attrs` when the conversion is one-to-one, that is when no operand was expanded into several values and the results were not packed into a struct; in those cases there is no positional mapping for the dictionaries and they are left out as before. no_inline is forwarded unconditionally.

Fixes #225258

>From 14f1382a61c61d81c4c286d128973e52c271b840 Mon Sep 17 00:00:00 2001
From: Samarth Narang <sn2910 at nyu.edu>
Date: Wed, 30 Sep 2026 11:48:39 +0000
Subject: [PATCH] [mlir][func] Forward llvm. call attributes and no_inline in
 -convert-func-to-llvm

The call lowering built the llvm.call from the discardable attributes only,
so the arg_attrs, res_attrs and no_inline properties of func.call and
func.call_indirect were dropped. llvm.signext takes part in the call ABI and
llvm.noundef / no_inline carry optimizer contracts, so a successful conversion
changed the semantics of the call.

Forward the `llvm.`-prefixed entries of arg_attrs and res_attrs when the
conversion is one-to-one, that is when no operand was expanded into several
values and the results were not packed into a struct; in those cases there is
no positional mapping for the dictionaries and they are left out as before.
Forward no_inline unconditionally, it is a property of the call.

Fixes #225258
---
 mlir/lib/Conversion/FuncToLLVM/FuncToLLVM.cpp | 29 ++++++++++++
 .../FuncToLLVM/call-attributes.mlir           | 45 +++++++++++++++++++
 2 files changed, 74 insertions(+)

diff --git a/mlir/lib/Conversion/FuncToLLVM/FuncToLLVM.cpp b/mlir/lib/Conversion/FuncToLLVM/FuncToLLVM.cpp
index f5e21d63d20b6..50379899a354a 100644
--- a/mlir/lib/Conversion/FuncToLLVM/FuncToLLVM.cpp
+++ b/mlir/lib/Conversion/FuncToLLVM/FuncToLLVM.cpp
@@ -613,6 +613,35 @@ struct CallOpInterfaceLowering : public ConvertOpToLLVMPattern<CallOpType> {
     if constexpr (std::is_same_v<CallOpType, func::CallOp>)
       newOp.setCalleeAttr(callOp.getCalleeAttr());
 
+    // Forward the `llvm.` argument and result attributes where the conversion
+    // is one-to-one; expanded operands or packed results have no positional
+    // mapping for them.
+    auto filterLLVMAttrs = [&](ArrayAttr attrs) -> ArrayAttr {
+      if (!attrs)
+        return {};
+      bool anyKept = false;
+      SmallVector<Attribute> filtered;
+      for (Attribute dict : attrs) {
+        SmallVector<NamedAttribute> kept;
+        for (NamedAttribute attr : cast<DictionaryAttr>(dict))
+          if (attr.getName().getValue().starts_with("llvm."))
+            kept.push_back(attr);
+        anyKept |= !kept.empty();
+        filtered.push_back(rewriter.getDictionaryAttr(kept));
+      }
+      return anyKept ? rewriter.getArrayAttr(filtered) : ArrayAttr();
+    };
+    if (promoted.size() == callOp->getNumOperands())
+      if (ArrayAttr argAttrs = filterLLVMAttrs(callOp.getArgAttrsAttr());
+          argAttrs && argAttrs.size() == newOp.getArgOperands().size())
+        newOp.setArgAttrsAttr(argAttrs);
+    if (numResults <= 1 && numConvertedTypes <= 1)
+      if (ArrayAttr resAttrs = filterLLVMAttrs(callOp.getResAttrsAttr()))
+        newOp.setResAttrsAttr(resAttrs);
+    if constexpr (std::is_same_v<CallOpType, func::CallOp>)
+      if (callOp.getNoInline())
+        newOp.setNoInline(true);
+
     // Helper function that extracts an individual result from the return value
     // of the new call op. llvm.call ops support only 0 or 1 result. In case of
     // 2 or more results, the results are packed into a structure.
diff --git a/mlir/test/Conversion/FuncToLLVM/call-attributes.mlir b/mlir/test/Conversion/FuncToLLVM/call-attributes.mlir
index 518b0f356aad2..4f4484e1dba8d 100644
--- a/mlir/test/Conversion/FuncToLLVM/call-attributes.mlir
+++ b/mlir/test/Conversion/FuncToLLVM/call-attributes.mlir
@@ -15,3 +15,48 @@ func.func @indirect_call(%fn: (f32) -> f32, %arg: f32) -> f32 {
   %0 = func.call_indirect %fn(%arg) {CConv = #llvm.cconv<fastcc>, fastmathFlags = #llvm.fastmath<nnan>, convergent, test.marker = "keep"} : (f32) -> f32
   return %0 : f32
 }
+
+// The `llvm.` argument and result attributes and `no_inline` are forwarded
+// when operands and results map one-to-one.
+
+func.func private @callee_i16(i16) -> i16
+func.func private @callee_memref(memref<4xf32>)
+func.func private @callee_two() -> (i16, i16)
+
+// CHECK-LABEL: llvm.func @call_arg_res_attrs
+func.func @call_arg_res_attrs(%v: i16) -> i16 {
+  // CHECK: llvm.call @callee_i16(%arg0) {no_inline} : (i16 {llvm.noundef, llvm.signext}) -> (i16 {llvm.signext})
+  %r = "func.call"(%v) <{callee = @callee_i16, arg_attrs = [{llvm.noundef, llvm.signext}], res_attrs = [{llvm.signext}], no_inline}> : (i16) -> i16
+  return %r : i16
+}
+
+// CHECK-LABEL: llvm.func @call_indirect_arg_res_attrs
+func.func @call_indirect_arg_res_attrs(%fn: (i16) -> i16, %v: i16) -> i16 {
+  // CHECK: llvm.call %arg0(%arg1) : !llvm.ptr, (i16 {llvm.signext}) -> (i16 {llvm.zeroext})
+  %r = "func.call_indirect"(%fn, %v) <{arg_attrs = [{llvm.signext}], res_attrs = [{llvm.zeroext}]}> : ((i16) -> i16, i16) -> i16
+  return %r : i16
+}
+
+// Attributes from other dialects are not forwarded.
+// CHECK-LABEL: llvm.func @call_non_llvm_attrs
+func.func @call_non_llvm_attrs(%v: i16) -> i16 {
+  // CHECK: llvm.call @callee_i16(%arg0) : (i16) -> i16
+  %r = "func.call"(%v) <{callee = @callee_i16, arg_attrs = [{test.marker}], res_attrs = [{test.marker}]}> : (i16) -> i16
+  return %r : i16
+}
+
+// A memref operand expands to several values: no positional mapping.
+// CHECK-LABEL: llvm.func @call_expanded_operand
+func.func @call_expanded_operand(%m: memref<4xf32>) {
+  // CHECK: llvm.call @callee_memref({{.*}}) : (!llvm.ptr, !llvm.ptr, i64, i64, i64) -> ()
+  "func.call"(%m) <{callee = @callee_memref, arg_attrs = [{llvm.noundef}]}> : (memref<4xf32>) -> ()
+  return
+}
+
+// Several results are packed into one struct: no positional mapping.
+// CHECK-LABEL: llvm.func @call_packed_results
+func.func @call_packed_results() -> (i16, i16) {
+  // CHECK: llvm.call @callee_two() : () -> !llvm.struct<(i16, i16)>
+  %a, %b = "func.call"() <{callee = @callee_two, res_attrs = [{llvm.signext}, {llvm.zeroext}]}> : () -> (i16, i16)
+  return %a, %b : i16, i16
+}



More information about the Mlir-commits mailing list