Many thanks to authors/developers of Graphviz, and people who offer help here on the forum!
My usecase is rendering a Control Flow Graph (CFG) for program analysis, which is definitely not an unheard of usage for Graphviz. Nodes of CFG are Basic Blocks, i.e. linear sequences of instructions. In other words, nodes do have their own internal structure. And for this usecase, it is important where edges from a node originate, and where incoming edges point. Specifically, I would like outgoing edges to originate from the bottom of the node (i.e. after the last instruction of a Basic Block), and incoming edges to go to the top of node (to the first instruction). Due to this, I have to specify “compass values” for edges. I specifically found that instead of raw “s” (node bottom) and “n” (node top), “se” and “ne” usually work best.
But so far, we talk about control flow edges between nodes. There are other relationships among the nodes of CFG, and I would like to represent a few at the same time. For example, I would like to show Dominator Tree structure of the CFG.
With such a configuration, it’s not uncommon for nodes “l1” and “l2” to have a l2 -> l1 control edge, and at the same time, have l2 -> l1 “immediate dominator” edge. Here’s where problem comes - somehow, Graphviz thinks that if two nodes have multiple (here, 2 edges) between them, then these edges must be somehow related. It seems to go out of its way to route these 2 edges in such a way that there was a point where they are “close enough” to each other. Given that one of the edge is constrained to the sides of the node, and another not, this leads to completely twisted, unnatural edge rendering, as presented below:
[dot verbose=true]
digraph strlen {
node [shape=plain]
"pre_entry" [label=<<table cellspacing='0' border='0' cellborder='1'><tr><td>pre_entry</td><td><font point-size='9'>post#40</font></td></tr><tr><td colspan='2'><font point-size='9'>livein: </font></td></tr><tr><td balign='left' colspan='2'><b>0:</b> nop <br/><b>1:</b> nop <br/><b>2:</b> (000) s_2 = param 0 <font color='grey' point-size='9'><i>uses=1[@202]</i></font></td></tr><tr><td colspan='2'><font point-size='9'>liveout: s_2</font></td></tr></table>>]
"pre_entry" -> "start"
"start" [label=<<table cellspacing='0' border='0' cellborder='1'><tr><td>start</td><td><font point-size='9'>post#30</font></td></tr><tr><td colspan='2'><font point-size='9'>livein: s_2</font></td></tr><tr><td balign='left' colspan='2'><b>3:</b> (000) len_3 = 0 <font color='grey' point-size='9'><i>uses=1[@201]</i></font></td></tr><tr><td colspan='2'><font point-size='9'>liveout: len_3, s_2</font></td></tr></table>>]
"start" -> "l1"
"start" -> "pre_entry" [style=dotted, constraint=false]
"l1" [label=<<table cellspacing='0' border='0' cellborder='1'><tr><td>l1</td><td><font point-size='9'>post#20</font></td></tr><tr><td colspan='2'><font point-size='9'>livein: len_201, s_202</font></td></tr><tr><td balign='left' colspan='2'><b>200:</b> nop <br/><b>201:</b> (000) len_201 = phi len_3(000), len_6(000) <font color='grey' point-size='9'><i>uses=2[@8, @6]</i></font><br/><b>202:</b> (000) s_202 = phi s_2(000), s_7(000) <font color='grey' point-size='9'><i>uses=2[@4, @7]</i></font><br/><b>4:</b> (000) c_4 = $load s_202(000), ?u8 <font color='grey' point-size='9'><i>uses=1[@5]</i></font><br/><b>5:</b> if c_4*(000), ==, 0</td></tr><tr><td colspan='2'><font point-size='9'>liveout: len_201, s_202</font></td></tr></table>>]
"l1" -> "end" [label="t"]
"l1" -> "l2" [label="f"]
"l1" -> "start" [style=dotted, constraint=false]
"l2" [label=<<table cellspacing='0' border='0' cellborder='1'><tr><td>l2</td><td port='fooport'><font point-size='9'>post#10</font></td></tr><tr><td colspan='2'><font point-size='9'>livein: len_201, s_202</font></td></tr><tr><td balign='left' colspan='2'><b>6:</b> (000) len_6 = + len_201*(000), 1 <font color='grey' point-size='9'><i>uses=1[@201]</i></font><br/><b>7:</b> (000) s_7 = + s_202*(000), 1 <font color='grey' point-size='9'><i>uses=1[@202]</i></font></td></tr><tr><td colspan='2'><font point-size='9'>liveout: len_6, s_7</font></td></tr></table>>]
"l2":se -> "l1":ne
"l2" -> "l1" [style=dotted, constraint=false]
"end" [label=<<table cellspacing='0' border='0' cellborder='1'><tr><td>end</td><td><font point-size='9'>post#0</font></td></tr><tr><td colspan='2'><font point-size='9'>livein: len_201</font></td></tr><tr><td balign='left' colspan='2'><b>8:</b> return len_201*(000)</td></tr><tr><td colspan='2'><font point-size='9'>liveout: </font></td></tr></table>>]
"end" -> "l1" [style=dotted, constraint=false]
}
[/dot]
For reference, 2 problematic edges are defined as:
"l2":se -> "l1":ne
"l2":fooport -> "l1" [style=dotted, constraint=false]
The question is thus how to tell Graphviz that multiple edges between 2 nodes are completely unrelated and should not be constrained to each other in any way. Thanks.