Inspecting mlir dataflow analysis

I have been trying to use the DenseDataflowAnalysis infrastructure offered by mlir.
Unfortunately it is a fairly complex tool that relies other analysis, such as dead code analysis, and is generalized to the point where it does not necessarily relies on the underlying graph being fixed.

This implies that it is very hard to debug, the only mechanism i have found is the --debug flag which logs entries such as:

Creating dependency between mlir::dataflow::Executable of mlir-asm-printer: Verifying operation: builtin.module
^bb0:
  %4 = dialect.assign %2 : !dialect.int<64>, %3#0 : !dialect.int<64> -> !dialect.int<64>
  dialect.yield

and mlir::dataflow::Executable on mlir-asm-printer: Verifying operation: builtin.module
^bb0:
  %4 = dialect.assign %2 : !dialect.int<64>, %3#0 : !dialect.int<64> -> !dialect.int<64>
  dialect.yield

which seems to indicate that there is a edge in the graph between the region contained of some operation and some other unknown node in the same operation.

From manually inspecting the logs it seems that somehow it fails to understand that the control flow should flow from a terminator of a instruction back to the instruction itself, but when i run LivenessAnalysis it seems to work correctly.

Is there any way to visualize the underlying graph?

I don’t know of a visualization tool. When using data flow analysis I have put debug prints in the implementation of visitOperation in my subclass of DenseForwardDataFlowAnalysis.

Why do you think that after a terminator the analysis should go back to the instruction itself? Please could you give an example? You might be able to achieve this by overriding processOperation

Jeff explained more about the design of the data flow analysis framework here https://www.youtube.com/watch?v=5BijBv2TDnU

By default, the framework will use the information specified by RegionBranchOpInterface and RegionBranchTerminatorOpInterfaces to reason about the control flow between the operation. Make sure those are set up to reflect the control flow you are expecting.

There is a hook in dense analyses to override the default behavior for region-level control flow, add here [mlir] allow dense dataflow to customize call and region operations · llvm/llvm-project@5d8813d · GitHub.

i found the issue by stepping through the whole execution and writing down the graph by hand.

the yield instructions in my dialect have a optional region that is used to insert code to be executed when the scope ends. I assumed that if the BranchOpInterface returned a empty set of successors when queried for the top level operation it meant that the control flow ignored all regions and resumed at the next operation. It instead seem to represent a “no return” operation, which I did not considered possible. That broke all yields operations that did not had code to run on scope exit.

A visualization here may not be too difficult/be fun: use the graphviz dumper at many stages with different highlighting of where it currently is and where it’ll go next. Now, ViewOpGraph.cpp is not that configurable today … But with a couple of functors doable :slight_smile:

i noticed that this line is wrong.

the check to see if it is a external call should be done after the one to see if it is interprocedural. Forward analyses do it correctly, while backward ones are broken.

I tried moving it before and it works correctly in my tests. Would a mr adressing this issue be accepted?

The best way to see if a change will be accepted is to send a pull request with said change, and a test.

i created this pr to fix the issue [mlir][dataflow]Fix dense backward dataflow intraprocedural hook by drblallo · Pull Request #76865 · llvm/llvm-project · GitHub

Thanks, merged. Don’t hesitate to send more patches and add me as a reviewer.