-
Notifications
You must be signed in to change notification settings - Fork 59
feat: add cluster visualization for WhileOp, IfOp, ForOp
#2234
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
base: feature/graph-visualization
Are you sure you want to change the base?
Changes from all commits
888d025
d4b34f9
2f27f08
33aa334
d50cfdc
a364869
cddb234
1b5210c
14b28bb
a1e9211
aad7449
8225658
897fd16
8ae8692
3750ef2
f823ede
f397b47
59e7826
55fbb8e
0e1b68b
1749a1e
0b65d42
8f579a0
4105697
028098e
9f37379
a5b8cd2
b51016e
13f39eb
d8a38b7
07a0597
40b1eb4
77dd502
7b807fd
022171e
19af1c7
4c2c0b8
b4ccd61
d354dba
9cf53b5
b36b3ae
d14c15b
4f35dcf
d429566
a2794c5
a1110ad
cbb96ff
9c0220b
0e0ddfd
c41adb9
e803543
42cede2
a9aa9ec
aa36682
085ec57
c08a84c
edc2076
4c71876
f1e5849
0ff2ce9
6f49870
a065593
3c9ca3a
b83e2a4
bc54dbf
ee57f40
c341858
2ded5dc
5943c1a
7d051ac
0460cac
fd44ce6
1a6aa98
39fe0b9
f495934
194433f
76025f3
ccceb2b
0cbe9af
4dbdb13
fddfa75
4596d02
813ec0e
3b1d25e
f25d136
96d3b37
2e2a97c
e752329
5dccfbb
123144f
d0c7c9f
cd9d5f4
6cda3bf
86b5662
9c26efd
81a9aa6
ab06276
3e4102b
194f14a
199ab70
e28a1a9
24d3dd3
685842c
986fb3f
07b7655
11f5864
8c64d81
606f5f5
edfcb93
0370886
9847b39
57ce573
805d21b
c0e0283
e1a6c64
2a960cf
a21e6dd
b06585b
f666a9f
e28b2b7
af5e52c
952fd7f
99f9fc6
492276d
8f2dc98
eac25d5
b06e92e
53497ba
83225b3
7d04249
fbb0c5a
941675f
bb9549d
2bad538
683768e
2adb3b2
f7bbb6b
f7f4b5a
a21f878
ae0ff32
26ae968
1d4f1f1
70ec17a
ed8bae0
25e8051
ffc9726
a243614
e58891a
6d1ab99
710e6a8
cb931d1
049a683
9ea917e
fd41bc5
fd26a37
1ad2ec0
7fdbac0
8fc4a30
bfd28b2
4753071
6200c76
1f90c6d
9aef399
e87dba2
14ed4bc
be1961c
c92736a
ca44e59
f84a6f1
3a8da55
26e95f9
d7f21bd
c9594ee
d76fcca
c0554c5
a80141c
529cace
4a172dd
a54de21
5a731c0
53f80b3
2bb5bde
434c500
cfa5f3b
a62f2b1
d9026ef
faf338f
5d03a81
56e4756
c95c45f
18aa30b
929bd23
709e961
b51bb7b
89f45f3
b45b2f0
2028fa7
4e994e6
e590847
eb6a0c3
7eb31b6
8e787c9
8b6287f
83ebb18
6d84d36
3c42841
e319aae
1c9c857
093a5d5
2ca7bb8
61b70d9
089cb14
2a2d55c
220c6d5
f7261a9
a8cd49c
70b0d93
cb05921
1883fb3
5e2d456
36b5f7d
ee15b67
16e003d
23561f7
d5ccaf7
1dd9f38
6a9b01d
a2ac9ff
0dbe4fe
d92aea4
8afc67a
c0d4d67
9706967
5e62c31
1474a66
82430af
ced6f75
5641e68
22c8ae1
ab7c471
9996684
c262df9
506f839
f67802f
30db67e
d069fef
e29f546
b34d8e2
aa1d90c
27ff971
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,10 +16,10 @@ | |
|
|
||
| from functools import singledispatchmethod | ||
|
|
||
| from xdsl.dialects import builtin, func | ||
| from xdsl.ir import Block, Operation, Region | ||
| from xdsl.dialects import builtin, func, scf | ||
| from xdsl.ir import Block, Operation, Region, SSAValue | ||
|
|
||
| from catalyst.python_interface.dialects import catalyst, quantum | ||
| from catalyst.python_interface.dialects import quantum | ||
| from catalyst.python_interface.visualization.dag_builder import DAGBuilder | ||
|
|
||
|
|
||
|
|
@@ -85,6 +85,97 @@ def _visit_block(self, block: Block) -> None: | |
| for op in block.ops: | ||
| self._visit_operation(op) | ||
|
|
||
| # ============= | ||
| # CONTROL FLOW | ||
| # ============= | ||
|
|
||
| @_visit_operation.register | ||
| def _for_op(self, operation: scf.ForOp) -> None: | ||
| """Handle an xDSL ForOp operation.""" | ||
|
|
||
| uid = f"cluster{self._cluster_uid_counter}" | ||
| self.dag_builder.add_cluster( | ||
| uid, | ||
| node_label="for loop", | ||
| label="", | ||
| cluster_uid=self._cluster_uid_stack[-1], | ||
| ) | ||
| self._cluster_uid_stack.append(uid) | ||
| self._cluster_uid_counter += 1 | ||
|
|
||
| for region in operation.regions: | ||
| self._visit_region(region) | ||
|
|
||
| self._cluster_uid_stack.pop() | ||
|
|
||
| @_visit_operation.register | ||
| def _while_op(self, operation: scf.WhileOp) -> None: | ||
| """Handle an xDSL WhileOp operation.""" | ||
| uid = f"cluster{self._cluster_uid_counter}" | ||
| self.dag_builder.add_cluster( | ||
| uid, | ||
| node_label="while loop", | ||
| label="", | ||
| cluster_uid=self._cluster_uid_stack[-1], | ||
| ) | ||
| self._cluster_uid_stack.append(uid) | ||
| self._cluster_uid_counter += 1 | ||
|
|
||
| for region in operation.regions: | ||
| self._visit_region(region) | ||
|
|
||
| self._cluster_uid_stack.pop() | ||
|
Comment on lines
+115
to
+127
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't there be a cluster for each of the regions of the while loop similar to |
||
|
|
||
| @_visit_operation.register | ||
| def _if_op(self, operation: scf.IfOp): | ||
| """Handles the scf.IfOp operation.""" | ||
| flattened_if_op: list[tuple[SSAValue | None, Region]] = _flatten_if_op(operation) | ||
|
|
||
| uid = f"cluster{self._cluster_uid_counter}" | ||
| self.dag_builder.add_cluster( | ||
| uid, | ||
| node_label="", | ||
| label="conditional", | ||
| labeljust="l", | ||
| cluster_uid=self._cluster_uid_stack[-1], | ||
| ) | ||
| self._cluster_uid_stack.append(uid) | ||
| self._cluster_uid_counter += 1 | ||
|
|
||
| # Loop through each branch and visualize as a cluster | ||
| num_regions = len(flattened_if_op) | ||
| for i, (condition_ssa, region) in enumerate(flattened_if_op): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| def _get_conditional_branch_label(i): | ||
| if i == 0: | ||
| return "if" | ||
| elif i == num_regions - 1: | ||
| return "else" | ||
| else: | ||
| return "elif" | ||
|
Comment on lines
+149
to
+155
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd prefer if this function was defined outside the class's definition.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Definitely no reason for it to be defined for each loop iteration.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It might not even make sense for this to be a function at all (optional) |
||
|
|
||
| uid = f"cluster{self._cluster_uid_counter}" | ||
| self.dag_builder.add_cluster( | ||
| uid, | ||
| node_label=_get_conditional_branch_label(i), | ||
| label="", | ||
| style="dashed", | ||
| penwidth=1, | ||
| cluster_uid=self._cluster_uid_stack[-1], | ||
| ) | ||
| self._cluster_uid_stack.append(uid) | ||
| self._cluster_uid_counter += 1 | ||
|
|
||
| # Go recursively into the branch to process internals | ||
| self._visit_region(region) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For if-elif-else ops, won't this also visit all the inner IfOps that you already flattened? |
||
|
|
||
| # Pop branch cluster after processing to ensure | ||
| # logical branches are treated as 'parallel' | ||
| self._cluster_uid_stack.pop() | ||
|
|
||
| # Pop IfOp cluster before leaving this handler | ||
| self._cluster_uid_stack.pop() | ||
|
|
||
| # ============ | ||
| # DEVICE NODE | ||
| # ============ | ||
|
|
@@ -138,3 +229,28 @@ def _func_return(self, operation: func.ReturnOp) -> None: | |
| # If we hit a func.return operation we know we are leaving | ||
| # the FuncOp's scope and so we can pop the ID off the stack. | ||
| self._cluster_uid_stack.pop() | ||
|
|
||
|
|
||
| def _flatten_if_op(op: scf.IfOp) -> list[tuple[SSAValue | None, Region]]: | ||
| """Recursively flattens a nested IfOp (if/elif/else chains).""" | ||
|
|
||
| condition_ssa: SSAValue = op.operands[0] | ||
| then_region, else_region = op.regions | ||
|
|
||
| # Save condition SSA in case we want to visualize it eventually | ||
| flattened_op: list[tuple[SSAValue | None, Region]] = [(condition_ssa, then_region)] | ||
|
|
||
| # Peak into else region to see if there's another IfOp | ||
| else_block: Block = else_region.block | ||
| # Completely relies on the structure that the second last operation | ||
| # will be an IfOp (seems to hold true) | ||
| if isinstance(else_block.ops.last.prev_op, scf.IfOp): | ||
| # Recursively flatten any IfOps found in said block | ||
| nested_flattened_op = _flatten_if_op(else_block.ops.last.prev_op) | ||
| flattened_op.extend(nested_flattened_op) | ||
| return flattened_op | ||
|
Comment on lines
+247
to
+251
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't you be checking if the first op in the |
||
|
|
||
| # No more nested IfOps, therefore append final region | ||
| # with no SSAValue | ||
| flattened_op.extend([(None, else_region)]) | ||
| return flattened_op | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.