- Visualize blocks and regions as subgraphs.
- Generate DOT file directly instead of using GraphTraits. GraphTraits does not support subgraphs, colors, etc.
Details
Diff Detail
- Repository
- rG LLVM Github Monorepo
Event Timeline
mlir/include/mlir/Transforms/Passes.td | ||
---|---|---|
702–711 | Can you split these features into separate commits? Some of them have less obvious value than others. It also makes the review easier. | |
mlir/lib/Transforms/ViewOpGraph.cpp | ||
22 | Namespaces should only really contain classes. Please move everything else out. | |
85–86 | Please use proper C++ constructor field initialization. | |
421 | The llvm:: here shouldn't be necessary. |
For reference:
- Previous output of PrintOpPass: https://dreampuf.github.io/GraphvizOnline/#digraph%20%22merge_blocks%22%20%7B%0A%20%20%20%20%20%20%20%20label%3D%22merge_blocks%22%3B%0A%0A%20%20%20%20%20%20%20%20Node0x4518ffdc1860%20%5Bshape%3Drecord%2Clabel%3D%22%7Bstd.constant%5Cnloc(%5C%22%2Fllvm-project%2Fmlir%2Ftest%2FTransforms%2Fprint-op-graph.mlir%5C%22%3A26%3A8)%5Cntensor%5C%3C2x2xi32%5C%3E%5Cn%5Cnvalue%3A%20%5B%5B...%5D%5D%20%3A%20tensor%5C%3C2x2xi32%5C%3E%7D%22%5D%3B%0A%20%20%20%20%20%20%20%20Node0x4518ffdc1980%20%5Bshape%3Drecord%2Clabel%3D%22%7Bstd.constant%5Cnloc(%5C%22%2Fllvm-project%2Fmlir%2Ftest%2FTransforms%2Fprint-op-graph.mlir%5C%22%3A27%3A8)%5Cntensor%5C%3C5xi32%5C%3E%5Cn%5Cnvalue%3A%20dense%5C%3C1%5C%3E%20%3A%20tensor%5C%3C5xi32%5C%3E%7D%22%5D%3B%0A%20%20%20%20%20%20%20%20Node0x4518ffdc1aa0%20%5Bshape%3Drecord%2Clabel%3D%22%7Bstd.constant%5Cnloc(%5C%22%2Fllvm-project%2Fmlir%2Ftest%2FTransforms%2Fprint-op-graph.mlir%5C%22%3A28%3A8)%5Cntensor%5C%3C1x2xi32%5C%3E%5Cn%5Cnvalue%3A%20dense%5C%3C%5B%5B0%2C%201%5D%5D%5C%3E%20%3A%20tensor%5C%3C1x2xi32%5C%3E%7D%22%5D%3B%0A%20%20%20%20%20%20%20%20Node0x4518ffdc1bc0%20%5Bshape%3Drecord%2Clabel%3D%22%7Bstd.constant%5Cnloc(%5C%22%2Fllvm-project%2Fmlir%2Ftest%2FTransforms%2Fprint-op-graph.mlir%5C%22%3A29%3A8)%5Cni32%5Cn%5Cnvalue%3A%2010%20%3A%20i32%7D%22%5D%3B%0A%20%20%20%20%20%20%20%20Node0x4518ffdc1bc0%20-%3E%20Node0x4518ffd35120%3B%0A%20%20%20%20%20%20%20%20Node0x4518ffdcf570%20%5Bshape%3Drecord%2Clabel%3D%22%7Btest.func%5Cnloc(%5C%22%2Fllvm-project%2Fmlir%2Ftest%2FTransforms%2Fprint-op-graph.mlir%5C%22%3A30%3A8)%5Cni32%5Cn%7D%22%5D%3B%0A%20%20%20%20%20%20%20%20Node0x4518ffdcf570%20-%3E%20Node0x4518ffd35120%3B%0A%20%20%20%20%20%20%20%20Node0x4518ffc9cac0%20%5Bshape%3Drecord%2Clabel%3D%22%7Btest.merge_blocks%5Cnloc(%5C%22%2Fllvm-project%2Fmlir%2Ftest%2FTransforms%2Fprint-op-graph.mlir%5C%22%3A31%3A10)%5Cni32%2C%20i32%5Cn%7D%22%5D%3B%0A%20%20%20%20%20%20%20%20Node0x4518ffc9cac0%20-%3E%20Node0x4518ffdca1c0%3B%0A%20%20%20%20%20%20%20%20Node0x4518ffc9cac0%20-%3E%20Node0x4518ffdca1c0%3B%0A%20%20%20%20%20%20%20%20Node0x4518ffdca1c0%20%5Bshape%3Drecord%2Clabel%3D%22%7Btest.return%5Cnloc(%5C%22%2Fllvm-project%2Fmlir%2Ftest%2FTransforms%2Fprint-op-graph.mlir%5C%22%3A37%3A3)%5Cn%5Cn%7D%22%5D%3B%0A%7D%0A%0A
- New output: https://dreampuf.github.io/GraphvizOnline/#digraph%20G%20%7B%0A%20%20compound%20%3D%20true%3B%0A%20%20subgraph%20cluster_1%20%7B%0A%20%20%20%20v2%20%5Blabel%20%3D%20%22%20%22%2C%20shape%20%3D%20plain%5D%3B%0A%20%20%20%20label%20%3D%20%22%22%3B%0A%20%20%20%20subgraph%20cluster_3%20%7B%0A%20%20%20%20%20%20v4%20%5Blabel%20%3D%20%22%20%22%2C%20shape%20%3D%20plain%5D%3B%0A%20%20%20%20%20%20label%20%3D%20%22func%20%3A%20()%5Cn%5Cnsym_name%3A%20%5C%22merge_blocks%5C%22%5Cntype%3A%20(i32%2C%20i32)%20-%3E%20()%22%3B%0A%20%20%20%20%20%20subgraph%20cluster_5%20%7B%0A%20%20%20%20%20%20%20%20v6%20%5Blabel%20%3D%20%22%20%22%2C%20shape%20%3D%20plain%5D%3B%0A%20%20%20%20%20%20%20%20label%20%3D%20%22%22%3B%0A%20%20%20%20%20%20%20%20v7%20%5Blabel%20%3D%20%22arg0%22%2C%20shape%20%3D%20ellipse%5D%3B%0A%20%20%20%20%20%20%20%20v8%20%5Blabel%20%3D%20%22arg1%22%2C%20shape%20%3D%20ellipse%5D%3B%0A%20%20%20%20%20%20%20%20v9%20%5Blabel%20%3D%20%22std.constant%20%3A%20(tensor%3C2x2xi32%3E)%5Cn%5Cnvalue%3A%20%5B%5B...%5D%5D%20%3A%20tensor%3C2x2xi32%3E%22%2C%20shape%20%3D%20ellipse%5D%3B%0A%20%20%20%20%20%20%20%20v10%20%5Blabel%20%3D%20%22std.constant%20%3A%20(tensor%3C5xi32%3E)%5Cn%5Cnvalue%3A%20dense%3C1%3E%20%3A%20tensor%3C5xi32%3E%22%2C%20shape%20%3D%20ellipse%5D%3B%0A%20%20%20%20%20%20%20%20v11%20%5Blabel%20%3D%20%22std.constant%20%3A%20(tensor%3C1x2xi32%3E)%5Cn%5Cnvalue%3A%20dense%3C%5B%5B0%2C%201%5D%5D%3E%20%3A%20tensor%3C1x2xi32%3E%22%2C%20shape%20%3D%20ellipse%5D%3B%0A%20%20%20%20%20%20%20%20v12%20%5Blabel%20%3D%20%22std.constant%20%3A%20(i32)%5Cn%5Cnvalue%3A%2010%20%3A%20i32%22%2C%20shape%20%3D%20ellipse%5D%3B%0A%20%20%20%20%20%20%20%20v13%20%5Blabel%20%3D%20%22test.func%20%3A%20(i32)%5Cn%22%2C%20shape%20%3D%20ellipse%5D%3B%0A%20%20%20%20%20%20%20%20subgraph%20cluster_14%20%7B%0A%20%20%20%20%20%20%20%20%20%20v15%20%5Blabel%20%3D%20%22%20%22%2C%20shape%20%3D%20plain%5D%3B%0A%20%20%20%20%20%20%20%20%20%20label%20%3D%20%22test.merge_blocks%20%3A%20(i32%2C%20i32)%5Cn%22%3B%0A%20%20%20%20%20%20%20%20%20%20subgraph%20cluster_16%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20v17%20%5Blabel%20%3D%20%22%20%22%2C%20shape%20%3D%20plain%5D%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20label%20%3D%20%22%22%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20v18%20%5Blabel%20%3D%20%22test.br%20%3A%20()%5Cn%22%2C%20shape%20%3D%20ellipse%5D%3B%0A%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20subgraph%20cluster_19%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20v20%20%5Blabel%20%3D%20%22%20%22%2C%20shape%20%3D%20plain%5D%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20label%20%3D%20%22%22%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20v21%20%5Blabel%20%3D%20%22arg0%22%2C%20shape%20%3D%20ellipse%5D%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20v22%20%5Blabel%20%3D%20%22arg1%22%2C%20shape%20%3D%20ellipse%5D%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20v23%20%5Blabel%20%3D%20%22arg2%22%2C%20shape%20%3D%20ellipse%5D%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20v24%20%5Blabel%20%3D%20%22test.return%20%3A%20()%5Cn%22%2C%20shape%20%3D%20ellipse%5D%3B%0A%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20v25%20%5Blabel%20%3D%20%22test.return%20%3A%20()%5Cn%22%2C%20shape%20%3D%20ellipse%5D%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A%20%20%7D%0A%20%20v7%20-%3E%20v18%20%5Blabel%20%3D%20%220%22%2C%20style%20%3D%20solid%5D%3B%0A%20%20v13%20-%3E%20v18%20%5Blabel%20%3D%20%221%22%2C%20style%20%3D%20solid%5D%3B%0A%20%20v12%20-%3E%20v18%20%5Blabel%20%3D%20%222%22%2C%20style%20%3D%20solid%5D%3B%0A%20%20v21%20-%3E%20v24%20%5Blabel%20%3D%20%220%22%2C%20style%20%3D%20solid%5D%3B%0A%20%20v22%20-%3E%20v24%20%5Blabel%20%3D%20%221%22%2C%20style%20%3D%20solid%5D%3B%0A%20%20v15%20-%3E%20v25%20%5Blabel%20%3D%20%220%22%2C%20style%20%3D%20solid%2C%20ltail%20%3D%20cluster_14%5D%3B%0A%20%20v15%20-%3E%20v25%20%5Blabel%20%3D%20%221%22%2C%20style%20%3D%20solid%2C%20ltail%20%3D%20cluster_14%5D%3B%0A%7D
mlir/lib/Transforms/ViewOpGraph.cpp | ||
---|---|---|
23 | I'd rather have this be kShapeNode which documents it's use rather than have kX == lowercase(X), else seems like a magical constant with extra step. | |
75 | There can be multiple visualizations of an MLIR module (dominated by, will execute after, CDG, ...) it would be good to spell this out what this particular one is visualizing (which could refer to the description of pass for more info if appropriate) | |
78 | We have an indented ostream, why not use that? | |
262 | We have operations with very large number of results, always adding them to label will make this unusable there. | |
276 | When I did one of these recently I made the arguments a table, that way arguments were just named numerically and associated with block entry. It kept all the arguments together and avoided it appearing the same as ops. |
mlir/lib/Transforms/ViewOpGraph.cpp | ||
---|---|---|
75 | Done. This is a dataflow visualization. Btw, I'm planning to add a CFG visualization (to be enabled via a pass option) in a subsequent commit. | |
78 | Yes, raw_indented_ostream is what we need here. But I couldn't find a way to use it in a pass. The problem is that:
| |
262 | I'd add this in a separate commit if that's OK. (To keep this one small.) To be enabled via a pass option. | |
276 | Not sure what you mean by that. Are you referring to Graphviz HTML tables? (https://graphviz.org/doc/info/shapes.html#html) With Graphviz tables I could generate a single node for all block arguments of a block. Then draw arrows from each cell to their respective uses. Or do you recommend not generating nodes for block arguments at all? Btw, in some of my experiments I found it useful to duplicate block argument nodes and constant nodes (e.g. %c0). Such nodes are often used many times throughout the IR, which renders quite unreadable due to the many edges. Also, those edges are quite long. What helps is creating a new block arg/constant node for each use, which the Graphviz engine will render close to the user, resulting in shorter edges (and less lines on the screen). I'll add this functionality in a subsequent commit. |
The floating 0 below test.br is weird, could that be avoided?
mlir/lib/Transforms/ViewOpGraph.cpp | ||
---|---|---|
78 | Yes, raw_ostream's copy constructor is deleted, which results in raw_indented_ostream's being deleted. If you just added a copy constructor for this pass where you create a new one, that should work for this case (what would happen I believe is a it would be created with underlying raw_ostream, so you'd lose the indendation of the passed in indented ostream [which is fine as there is no expectation that the cloned pass and the original share indentation]). | |
102 | Why not combine these? Use interleaveComma on the map & in functor create the string rather than creating all the strings and then outputting. | |
276 | Yes, an HTML table. But indeed I ran into the same issue with long lines on larger graphs. Duplication is good idea for keeping it scoped - it makes it more difficult to notice when 2 ops have the same input (so if one has patterns that match equal operands then that is more desirable). |
This is a rendering issue. Edges have labels to indicate that a value is used as the 0th, 1st, 2nd, etc. operand. Those edges are clipped at cluster (subgraph) boundaries, but for some reason the labels are not. I think this is a bug in Graphviz. As a workaround, I stopped printing labels for edges that start/end at a cluster boundary.
Are there any other changes that I should make to this revision?
In case you are worried about the std::string, which are considered a code smell as you said: There is a way to avoid a few of the std::string. By writing writing Graphviz attributes directly onto the stream instead of storing them in an AttributeMap. But that would reduce code readability and some level of abstraction.
Basically, I could no longer use helper functions such as emitAttrList. These would have to be duplicated wherever attributes are printed. Also more lambdas are likely needed for printing labels. The main reason why I added emitAttrList and AttributeMap is that it gives the code some more structure. Note that parts of the function names are derived from the DOT language specification. E.g., the language spec defines things such as attr_list. I tried to mirror that structure in the code as good as possible.
For reference: DOT language spec is here: https://graphviz.org/doc/info/lang.html
Sorry, heavily in and out of OOO recently.
Took a glance, looks good to land after resolving comments. Thanks!
mlir/include/mlir/Transforms/Passes.td | ||
---|---|---|
691 | Unrelated, but I don't think this should be a ModuleOp pass. This feels like it should be a general "any op" pass. | |
mlir/lib/Transforms/ViewOpGraph.cpp | ||
70–71 | Can you move this to the description field of the pass? | |
77–79 | ||
80 | ||
82 | Optional is exported in the mlir namespace, so the llvm:: here can be dropped. | |
289 | ||
352 | ||
411 | ||
422 | Where is plainOs used? It looks like it can be dropped. |
Thanks for the review!
mlir/lib/Transforms/ViewOpGraph.cpp | ||
---|---|---|
422 | This is necessary to implement the copy constructor (required by clonePass) correctly. There's no way to copy the indented output stream, so I have to construct a new one. That's where I pass in plainOs. See my response to jpienaar's comment regarding the indented output stream for more details. |
mlir/lib/Transforms/ViewOpGraph.cpp | ||
---|---|---|
422 | Can we just add a raw_ostream &getOStream() { return os; } method to raw_indented_ostream then? The use of plainOs feels a bit hacky. |
Unrelated, but I don't think this should be a ModuleOp pass. This feels like it should be a general "any op" pass.