This allows printing the users of an operation as proposed in the git issue #53286.
To be able to refer to operations with no result, these operations are assigned an
ID in SSANameState.
Details
Diff Detail
- Repository
- rG LLVM Github Monorepo
Event Timeline
Can you add tests to cover all this? I'd to see how it looks in practice!
Also please include a test with a user operation that customize its result names? An op like %q, %q_1, %q_2, %r = test.string_attr_pretty_name attributes {names = ["q", "q", "q", "r"]} (but that also accepts operands, you need to create one or modify this one to add an optional argument).
mlir/lib/IR/AsmPrinter.cpp | ||
---|---|---|
2711 | Why wouldn't you print for an operation with operands and regions? | |
2716 | Do you think explicit is better? | |
2719 | What about grouping per results? If an op is like %a, %b = my.op, I could see useful to track it this way: %a, %b = my.op // users: (%42, %56), (%73, %45) | |
2720 | Nit: Operation * | |
2723 | You can check the return of the insert and save a lookup instead of calling contains separately | |
2737 | (same as above) |
Actually I patched your file to try it:
module { func @pretty_names(%arg0: i64) { // %arg0 // user: %q %c0_i64 = arith.constant 0 : i64 // user: %q %q, %q_0, %q_1, %r = "test.custom_result_name"(%c0_i64, %arg0) {names = ["q", "q", "q", "r"]} : (i64, i64) -> (i32, i32, i32, i32) return } }
I think the block argument handling isn't ideal right now?
Slightly more elaborated:
$ ./bin/mlir-opt /tmp/test.mlir -mlir-print-operation-users module { func @pretty_names(%arg0: i32, %arg1: i32) { // %arg0 // users: %q_2, %q // %arg1 // users: %q_6, %q_2 %c0_i32 = arith.constant 0 : i32 // users: %q %q, %q_0, %q_1, %r = "test.custom_result_name"(%c0_i32, %c0_i32, %c0_i32, %arg0) {names = ["q", "q", "q", "r"]} : (i32, i32, i32, i32) -> (i32, i32, i32, i32) // users: %q_6, %q_2 %q_2, %q_3, %q_4, %r_5 = "test.custom_result_name"(%q_0, %q, %arg1, %arg0) {names = ["q", "q", "q", "r"]} : (i32, i32, i32, i32) -> (i32, i32, i32, i32) // users: %q_6 %q_6, %q_7, %q_8, %r_9 = "test.custom_result_name"(%q, %r_5, %q_2, %arg1) {names = ["q", "q", "q", "r"]} : (i32, i32, i32, i32) -> (i32, i32, i32, i32) return } }
Address part of comments
Print id for operation with regions and indicate operation with unused results.
Add suggested code improvements.
mlir/lib/IR/AsmPrinter.cpp | ||
---|---|---|
2711 | I thought of the operation too much in terms of a function. So I wanted to not print the id for a it as it would already have a name and the extra id comment would be redundant. But that doesn't make sense. | |
2716 | Definitely makes sense whenever there is a result but no user. I added that. | |
2719 | In this example it looks good. From my understanding the printer would always join the named values together and then we could get a result like this %a:2 = my.op // users: (%1), (%1) %1 = my.other(%a#0, %a#1) ... I thought it is not great to have %1 in there twice. |
mlir/lib/IR/AsmPrinter.cpp | ||
---|---|---|
2719 |
It does not always do it the op can control that. %q, %q_0, %q_1, %r = "test.custom_result_name"(%c0_i32, %c0_i32, %c0_i32, %arg0) {names = ["q", "q", "q", "r"]} : (i32, i32, i32, i32) -> (i32, i32, i32, i32) // users: %q_6, %q_2 But even if it collapsed, that is just cosmetic. I would instead wonder what is the best information to provide to the user when there are multiple results? That is: when there are multiple results would the important information be about the user of the operation or the users of an indvidual result? Expanding your example it could look like: %a:2 = my.op // users: (%1 %2), (%1, %3) %1 = my.other(%a#0, %a#1) ... %2 = my.other(%a#0) ... %3 = my.other(%a#1) ... There aren't many ops with multiple results, scf.if may be one: %if#2 = scf.if %cond -> (index, index) { // Users: (%2, %3, %4, %5) or // Users: (%2, %4), (%3, %4, %5) ... scf.yield %a, %b : index, index } else { ... scf.yield %c, %d : index, index } %2 = foo(%if#0) %3 = bar(%if#1) %4 = foo(%if#0, %if#1) %5 = bar(%if#1) |
Thanks for the comments @mehdi_amini. How would you test the behaviour with the custom result names? For me, mlir-opt does not behave like you show in your comment and always gives anonymous names to the results.
I patched MLIR for this:
diff --git a/mlir/test/lib/Dialect/Test/TestDialect.cpp b/mlir/test/lib/Dialect/Test/TestDialect.cpp index 5cfefbb55971..699a365a1e2f 100644 --- a/mlir/test/lib/Dialect/Test/TestDialect.cpp +++ b/mlir/test/lib/Dialect/Test/TestDialect.cpp @@ -1168,6 +1168,15 @@ void StringAttrPrettyNameOp::getAsmResultNames( setNameFn(getResult(i), str.getValue()); } +void CustomResultsNameOp::getAsmResultNames( + function_ref<void(Value, StringRef)> setNameFn) { + auto value = getNames(); + for (size_t i = 0, e = value.size(); i != e; ++i) + if (auto str = value[i].dyn_cast<StringAttr>()) + if (!str.getValue().empty()) + setNameFn(getResult(i), str.getValue()); +} + //===----------------------------------------------------------------------===// // ResultTypeWithTraitOp //===----------------------------------------------------------------------===// diff --git a/mlir/test/lib/Dialect/Test/TestOps.td b/mlir/test/lib/Dialect/Test/TestOps.td index f42fb8edcc18..c2aa2cd49fa0 100644 --- a/mlir/test/lib/Dialect/Test/TestOps.td +++ b/mlir/test/lib/Dialect/Test/TestOps.td @@ -727,11 +727,27 @@ def OIListAllowedLiteral : TEST_Op<"oilist_allowed_literal"> { def StringAttrPrettyNameOp : TEST_Op<"string_attr_pretty_name", [DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>]> { - let arguments = (ins StrArrayAttr:$names); + let arguments = (ins + Optional<I64>:$optional, + StrArrayAttr:$names + ); let results = (outs Variadic<I32>:$r); let hasCustomAssemblyFormat = 1; } + +// This is used to test encoding of a string attribute into an SSA name of a +// pretty printed value name. +def CustomResultsNameOp + : TEST_Op<"custom_result_name", + [DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>]> { + let arguments = (ins + Variadic<AnyInteger>:$optional, + StrArrayAttr:$names + ); + let results = (outs Variadic<AnyInteger>:$r); +} + // This is used to test the OpAsmOpInterface::getDefaultDialect() feature: // operations nested in a region under this op will drop the "test." dialect // prefix.
mlir/lib/IR/AsmPrinter.cpp | ||
---|---|---|
2719 | You are right, thanks for the example. I think the user would prefer to check where a result is used instead of where any result from an operation is used. |
Address comments
- Add tests
- Add grouping of users based on results
- Improve printing of BlockArgument users
Thanks a lot for your help. This made it much clearer for me. I added your operation to create the tests.
mlir/include/mlir/IR/OperationSupport.h | ||
---|---|---|
777 | Add a function so that the flag can be set programmatically? | |
798 | It's not just the users of operations but block arguments too. The comment and the names of everything should reflect that. printValueUsers? | |
mlir/lib/IR/AsmPrinter.cpp | ||
1013 | In what scenarios would this case be triggered? | |
2593 | ||
2596 | ||
2718 | Fit this comment to 80 characters long lines. | |
2723 | Drop the explicit static size specifier unless you have a good reason to have it | |
2724 | spell out the auto | |
2766 | ||
2767 | ||
2783 | ||
2845 | spell out the auto | |
mlir/test/IR/print-op-users.mlir | ||
62 ↗ | (On Diff #424252) | nit |
mlir/test/lib/Dialect/Test/TestDialect.cpp | ||
1173 ↗ | (On Diff #424252) | spell out the auto |
1174 ↗ | (On Diff #424252) | prefer unsigned |
LGTM overall!
mlir/include/mlir/IR/OperationSupport.h | ||
---|---|---|
777 | +1: looks at the setters style line 736-753 | |
mlir/lib/IR/AsmPrinter.cpp | ||
1013 | Invalid IR? | |
2723 | That work only with SmallVector AFAIK | |
mlir/test/lib/Dialect/Test/TestDialect.cpp | ||
1174 ↗ | (On Diff #424252) | prefer int! :) for (int i : llvm::seq<int>(0, value.size()) Or: for (auto nameAndResult : llvm::zip(getNames(), getResults())) if (auto str = std::get<0>(nameAndResult).dyn_cast<StringAttr>()) if (!str.getValue().empty()) setNameFn(std::get<1>(nameAndResult), str.getValue()); |
mlir/lib/IR/AsmPrinter.cpp | ||
---|---|---|
1151 | Can you reflow this comment? | |
2709 | Please drop comments from function definitions, prefer having them only on the declaration. | |
2711 | Please spell out auto here. | |
2715 | Prefer op->use_empty() over op->getUsers().empty() | |
2718 | Can you please fix the flow of the comments? clang-format can auto format the comments so that they wrap properly. | |
2718 | Should print feels a bit weird to read, can you tweak this comment? | |
2720 | Please use punctuation for documentation. | |
2724 | Please wrap the for in braces as well. | |
2730 | Please add punctuation to the end of comments. | |
mlir/test/lib/Dialect/Test/TestDialect.cpp | ||
1174 ↗ | (On Diff #424252) | (I would say size_t is fine and consistent with the codebase) |
mlir/lib/IR/AsmPrinter.cpp | ||
---|---|---|
1013 | This case should never be reached right now imo. An operation id is only printed if the operation has no result, but in that case an id should have been assigned. I added the output in this case to be safe and so that this method always has reasonable output. | |
mlir/test/lib/Dialect/Test/TestDialect.cpp | ||
1174 ↗ | (On Diff #424252) | This method is the same as for StringAttrPrettyNameOp::getAsmResultNames. Maybe it makes sense to change both at once in a follow up? |
Great, thanks again for the help and the feedback to all of you!
I don't have commit access, so it would be great if you could land the patch for me. Please use "Christoph Pillmayer cpillmayer@gmail.com" for the commit.
And also credit yourself for the new test op you provided me with, if that's possible?
Sorry, I applied the patch with arc and forgot to check that the name was right, it appears as Author: cpillmayer <cpillmayer@gmail.com>, hopefully this is good enough?
Add a function so that the flag can be set programmatically?