Skip to content

[mlir,python] Fix case when FuncOp.arg_attrs is not set #117188

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Dec 2, 2024

Conversation

Wheest
Copy link
Contributor

@Wheest Wheest commented Nov 21, 2024

FuncOps can have arg_attrs, an array of dictionary attributes associated with their arguments.

E.g.,

func.func @main(%arg0: tensor<8xf32> {test.attr_name = "value"}, %arg1: tensor<8x16xf32>)

These are exposed via the MLIR Python bindings with my_funcop.arg_attrs.

In this case, it would return [{test.attr_name = "value"}, {}], i.e., %arg1 has an empty DictAttr.

However, if I try and access this property from a FuncOp with an empty arg_attrs, e.g.,

func.func @main(%arg0: tensor<8xf32>, %arg1: tensor<8x16xf32>)

This raises the error:

    return ArrayAttr(self.attributes[ARGUMENT_ATTRIBUTE_NAME])
                     ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^
KeyError: 'attempt to access a non-existent attribute'

This PR fixes this by returning the expected [{}, {}].

@Wheest Wheest marked this pull request as ready for review November 22, 2024 14:51
@llvmbot llvmbot added mlir:python MLIR Python bindings mlir labels Nov 22, 2024
@llvmbot
Copy link
Member

llvmbot commented Nov 22, 2024

@llvm/pr-subscribers-mlir

Author: Perry Gibson (Wheest)

Changes

FuncOps can have arg_attrs, an array of dictionary attributes associated with their arguments.

E.g.,

func.func @<!-- -->main(%arg0: tensor&lt;8xf32&gt; {test.attr_name = "value"}, %arg1: tensor&lt;8x16xf32&gt;)

These are exposed via the MLIR Python bindings with my_funcop.arg_attrs.

In this case, it would return [{test.attr_name = "value"}, {}], i.e., %arg1 has an empty DictAttr.

However, if I try and access this property from a FuncOp with an empty arg_attrs, e.g.,

func.func @<!-- -->main(%arg0: tensor&lt;8xf32&gt;, %arg1: tensor&lt;8x16xf32&gt;)

This raises the error:

    return ArrayAttr(self.attributes[ARGUMENT_ATTRIBUTE_NAME])
                     ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^
KeyError: 'attempt to access a non-existent attribute'

This PR fixes this by returning the expected [{}, {}].


Full diff: https://github.com/llvm/llvm-project/pull/117188.diff

2 Files Affected:

  • (modified) mlir/python/mlir/dialects/func.py (+4)
  • (modified) mlir/test/python/dialects/func.py (+29)
diff --git a/mlir/python/mlir/dialects/func.py b/mlir/python/mlir/dialects/func.py
index 24fdcbcd85b29f..211027d88051a7 100644
--- a/mlir/python/mlir/dialects/func.py
+++ b/mlir/python/mlir/dialects/func.py
@@ -105,6 +105,10 @@ def add_entry_block(self, arg_locs: Optional[Sequence[Location]] = None):
 
     @property
     def arg_attrs(self):
+        if ARGUMENT_ATTRIBUTE_NAME not in self.attributes:
+            self.attributes[ARGUMENT_ATTRIBUTE_NAME] = ArrayAttr.get(
+                [DictAttr.get({}) for _ in self.type.inputs]
+            )
         return ArrayAttr(self.attributes[ARGUMENT_ATTRIBUTE_NAME])
 
     @arg_attrs.setter
diff --git a/mlir/test/python/dialects/func.py b/mlir/test/python/dialects/func.py
index a2014c64d2fa53..6b3932ce64f137 100644
--- a/mlir/test/python/dialects/func.py
+++ b/mlir/test/python/dialects/func.py
@@ -104,3 +104,32 @@ def testFunctionCalls():
 # CHECK:   %1 = call @qux() : () -> f32
 # CHECK:   return
 # CHECK: }
+
+
+# CHECK-LABEL: TEST: testFunctionArgAttrs
+@constructAndPrintInModule
+def testFunctionArgAttrs():
+    foo = func.FuncOp("foo", ([F32Type.get()], []))
+    foo.sym_visibility = StringAttr.get("private")
+    foo2 = func.FuncOp("foo2", ([F32Type.get(), F32Type.get()], []))
+    foo2.sym_visibility = StringAttr.get("private")
+
+    empty_attr = DictAttr.get({})
+    test_attr = DictAttr.get({"test.foo": StringAttr.get("bar")})
+    test_attr2 = DictAttr.get({"test.baz": StringAttr.get("qux")})
+
+    assert len(foo.arg_attrs) == 1
+    assert foo.arg_attrs[0] == empty_attr
+
+    foo.arg_attrs = [test_attr]
+    assert foo.arg_attrs[0]["test.foo"] == StringAttr.get("bar")
+
+    assert len(foo2.arg_attrs) == 2
+    assert foo2.arg_attrs == ArrayAttr.get([empty_attr, empty_attr])
+
+    foo2.arg_attrs = [empty_attr, test_attr2]
+    assert foo2.arg_attrs == ArrayAttr.get([empty_attr, test_attr2])
+
+
+# CHECK: func private @foo(f32 {test.foo = "bar"})
+# CHECK: func private @foo2(f32, f32  {test.baz = "qux"})

@@ -105,6 +105,10 @@ def add_entry_block(self, arg_locs: Optional[Sequence[Location]] = None):

@property
def arg_attrs(self):
if ARGUMENT_ATTRIBUTE_NAME not in self.attributes:
self.attributes[ARGUMENT_ATTRIBUTE_NAME] = ArrayAttr.get(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes more sense to just return None here? I know it doesn't match the correct semantic but it seems odd to make empty dictionaries just to return them as empty?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can see your point, however in the code I was working on, I have something like:

for arg, arg_attr in zip(block.arguments, func_op.arg_attrs):
    if "my.value" in arg_attr:
        lst1.append(arg)
    else:
        lst2.append(arg)

In your proposal, I would need to add a None check such as:

if func_op.arg_attrs is None:
    lst2.extend(block.arguments)
else:
    for arg, arg_attr in zip(block.arguments, func_op.arg_attrs):
        if "my.value" in arg_attr:
            lst1.append(arg)
        else:
            lst2.append(arg)

This would improve performance, but this is Python and perhaps not the chief concern, rather simplicity.

Happy to make the change to return None if you prefer, but from my perspective the overhead of generating empty dicts and then walking over an empty list makes more sense.

ftynse
ftynse previously requested changes Nov 26, 2024
Copy link
Member

@ftynse ftynse left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't necessarily mind returning a list of empty dictionary attributes when the attribute is missing (though it doesn't correspond to the underlying IR structure by mixing up two different cases in the IR), but having a property accessor mutate the operation by forcibly setting an attribute on it is wrong.

@Wheest
Copy link
Contributor Author

Wheest commented Nov 26, 2024

I don't necessarily mind returning a list of empty dictionary attributes when the attribute is missing (though it doesn't correspond to the underlying IR structure by mixing up two different cases in the IR), but having a property accessor mutate the operation by forcibly setting an attribute on it is wrong.

I agree, the underlying IR shouldn't be changed by this. I've updated the return so that it gives the empty dict list, but not stored in the IR.

- self.attributes[ARGUMENT_ATTRIBUTE_NAME] = ArrayAttr.get(
-    [DictAttr.get({}) for _ in self.type.inputs]
- )
+ return ArrayAttr.get([DictAttr.get({}) for _ in self.type.inputs])

@ftynse ftynse dismissed their stale review November 26, 2024 14:26

No further objections, but I'll let @maksleventhal comment

@Wheest
Copy link
Contributor Author

Wheest commented Dec 2, 2024

I don't yet have committer permissions, would one of you be willing to do this please @makslevental @ftynse

@makslevental makslevental merged commit d898ff6 into llvm:main Dec 2, 2024
8 checks passed
@makslevental
Copy link
Contributor

@Wheest if you want to keep contributing (and we want you to!), I highly encourage you to get commit access.

@Wheest Wheest deleted the Wheest/fix_pyfunc_arg_attrs branch December 3, 2024 17:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
mlir:python MLIR Python bindings mlir
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants