Skip to content
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

test rendering and fix non-determinism #118

Merged
merged 1 commit into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions numba_rvsdg/core/datastructures/scfg.py
Original file line number Diff line number Diff line change
Expand Up @@ -1046,10 +1046,12 @@ def make_scfg(

scfg_graph = {}
seen = set()
queue = curr_heads
# The queue must be a sorted FIFO to maintain reproducible insertion
# order for the SCFG.
queue = deque(sorted(curr_heads))

while queue:
current_name = queue.pop()
current_name = queue.popleft()
if current_name in seen:
continue
seen.add(current_name)
Expand Down Expand Up @@ -1083,7 +1085,7 @@ def make_scfg(

scfg_graph[current_name] = block
if current_name != exiting:
queue.update(edges[current_name])
queue.extend(edges[current_name])

scfg = SCFG(scfg_graph, name_gen=name_gen)
return scfg
Expand Down
10 changes: 7 additions & 3 deletions numba_rvsdg/core/transformations.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,10 @@ def reverse_lookup(d: Mapping[int, str], value: str) -> int:
# All new blocks are recorded for later insertion into the loop set
new_blocks = set()
doms = _doms(scfg)
# For every block in the loop:
for name in loop:
# The loop is a set of blocks, but order matters for reproducibility when
# assigning synth blocks and so we iterate over the sorted blocks since
# sets in Python are unorderd.
for name in sorted(loop):
# If the block is an exiting block or a backedge block
if name in exiting_blocks or name in backedge_blocks:
# Copy the jump targets, these will be modified
Expand Down Expand Up @@ -377,8 +379,10 @@ def extract_region(

# Generate a new region name
region_name = scfg.name_gen.new_region_name(region_kind)
# Create the subregion, make sure that blocks are inserted in a predictable
# order by sorting the set of region blocks.
head_subgraph = SCFG(
{name: scfg.graph[name] for name in region_blocks},
{name: scfg.graph[name] for name in sorted(region_blocks)},
name_gen=scfg.name_gen,
)

Expand Down
9 changes: 3 additions & 6 deletions numba_rvsdg/rendering/rendering.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,9 @@ def render_block(
"""
if type(block) == BasicBlock: # noqa: E721
self.render_basic_block(digraph, name, block)
if type(block) == PythonBytecodeBlock: # noqa: E721
elif type(block) == PythonBytecodeBlock: # noqa: E721
self.render_basic_block(digraph, name, block)
if type(block) == PythonASTBlock: # noqa: E721
elif type(block) == PythonASTBlock: # noqa: E721
self.render_python_ast_block(digraph, name, block) # type: ignore
elif type(block) == SyntheticAssignment: # noqa: E721
self.render_control_variable_block(digraph, name, block)
Expand Down Expand Up @@ -337,10 +337,7 @@ def render_branching_block(
digraph.node(str(name), shape="rect", label=body)

def render_scfg(self) -> "Digraph":
"""Renders the provided SCFG object."""
for name, block in self.scfg.graph.items(): # type: ignore
self.render_block(self.g, name, block)
self.render_edges(self.scfg) # type: ignore
"""Return the graphviz Digraph that contains the rendered SCFG."""
return self.g

def view(self, name: Optional[str] = None) -> None:
Expand Down
198 changes: 198 additions & 0 deletions numba_rvsdg/tests/test_rendering.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
# This file conatins expected dot output, which uses tabs for indentation.
# Flake8 will fail, so we just ignore the whole file.
# flake8: noqa
# Also ignore types, since we don't type annotate tests.
# mypy: ignore-errors

from numba_rvsdg.core.datastructures.scfg import SCFG
from numba_rvsdg.rendering.rendering import SCFGRenderer

expected_original = r"""digraph {
0 [label="0\l
jump targets: ('1', '2')
back edges: ()" shape=rect]
1 [label="1\l
jump targets: ('3',)
back edges: ()" shape=rect]
2 [label="2\l
jump targets: ('4',)
back edges: ()" shape=rect]
3 [label="3\l
jump targets: ('2', '5')
back edges: ()" shape=rect]
4 [label="4\l
jump targets: ('1',)
back edges: ()" shape=rect]
5 [label="5\l
jump targets: ()
back edges: ()" shape=rect]
0 -> 1
0 -> 2
1 -> 3
2 -> 4
3 -> 2
3 -> 5
4 -> 1
}"""

expected_restructured = r"""digraph {
subgraph cluster_head_region_0 {
color=red label="head_region_0
jump targets: ('branch_region_0', 'branch_region_1')
back edges: ()"
0 [label="0\l
jump targets: ('branch_region_0', 'branch_region_1')
back edges: ()" shape=rect]
}
subgraph cluster_branch_region_0 {
color=green label="branch_region_0
jump targets: ('tail_region_0',)
back edges: ()"
synth_asign_block_0 [label="synth_asign_block_0\lcontrol_var_0 = 0
jump targets: ('tail_region_0',)
back edges: ()" shape=rect]
}
subgraph cluster_branch_region_1 {
color=green label="branch_region_1
jump targets: ('tail_region_0',)
back edges: ()"
synth_asign_block_1 [label="synth_asign_block_1\lcontrol_var_0 = 1
jump targets: ('tail_region_0',)
back edges: ()" shape=rect]
}
subgraph cluster_tail_region_0 {
color=purple label="tail_region_0
jump targets: ()
back edges: ()"
5 [label="5\l
jump targets: ()
back edges: ()" shape=rect]
subgraph cluster_loop_region_0 {
color=blue label="loop_region_0
jump targets: ('5',)
back edges: ()"
subgraph cluster_head_region_1 {
color=red label="head_region_1
jump targets: ('branch_region_2', 'branch_region_3')
back edges: ()"
synth_head_block_0 [label="synth_head_block_0\lvariable: control_var_0\l0=>branch_region_2\l1=>branch_region_3
jump targets: ('branch_region_2', 'branch_region_3')
back edges: ()" shape=rect]
}
subgraph cluster_branch_region_2 {
color=green label="branch_region_2
jump targets: ('tail_region_1',)
back edges: ()"
subgraph cluster_head_region_2 {
color=red label="head_region_2
jump targets: ('branch_region_4', 'branch_region_5')
back edges: ()"
1 [label="1\l
jump targets: ('3',)
back edges: ()" shape=rect]
3 [label="3\l
jump targets: ('branch_region_4', 'branch_region_5')
back edges: ()" shape=rect]
}
subgraph cluster_branch_region_4 {
color=green label="branch_region_4
jump targets: ('tail_region_2',)
back edges: ()"
synth_asign_block_2 [label="synth_asign_block_2\lbackedge_var_0 = 0\lcontrol_var_0 = 1
jump targets: ('tail_region_2',)
back edges: ()" shape=rect]
}
subgraph cluster_branch_region_5 {
color=green label="branch_region_5
jump targets: ('tail_region_2',)
back edges: ()"
synth_asign_block_3 [label="synth_asign_block_3\lbackedge_var_0 = 1
jump targets: ('tail_region_2',)
back edges: ()" shape=rect]
}
subgraph cluster_tail_region_2 {
color=purple label="tail_region_2
jump targets: ('tail_region_1',)
back edges: ()"
synth_tail_block_0 [label="synth_tail_block_0\l
jump targets: ('tail_region_1',)
back edges: ()" shape=rect]
}
}
subgraph cluster_branch_region_3 {
color=green label="branch_region_3
jump targets: ('tail_region_1',)
back edges: ()"
2 [label="2\l
jump targets: ('4',)
back edges: ()" shape=rect]
4 [label="4\l
jump targets: ('synth_asign_block_4',)
back edges: ()" shape=rect]
synth_asign_block_4 [label="synth_asign_block_4\lbackedge_var_0 = 0\lcontrol_var_0 = 0
jump targets: ('tail_region_1',)
back edges: ()" shape=rect]
}
subgraph cluster_tail_region_1 {
color=purple label="tail_region_1
jump targets: ('5',)
back edges: ()"
synth_exit_latch_block_0 [label="synth_exit_latch_block_0\lvariable: backedge_var_0\l1=>5\l0=>head_region_1
jump targets: ('5',)
back edges: ('head_region_1',)" shape=rect]
}
}
}
0 -> synth_asign_block_0
0 -> synth_asign_block_1
synth_asign_block_0 -> synth_head_block_0
synth_asign_block_1 -> synth_head_block_0
synth_head_block_0 -> 1
synth_head_block_0 -> 2
1 -> 3
3 -> synth_asign_block_2
3 -> synth_asign_block_3
synth_asign_block_2 -> synth_tail_block_0
synth_asign_block_3 -> synth_tail_block_0
synth_tail_block_0 -> synth_exit_latch_block_0
2 -> 4
4 -> synth_asign_block_4
synth_asign_block_4 -> synth_exit_latch_block_0
synth_exit_latch_block_0 -> 5
synth_exit_latch_block_0 -> synth_head_block_0 [color=grey constraint=0 style=dashed]
}"""


def test_simple():
original = """
blocks:
'0':
type: basic
'1':
type: basic
'2':
type: basic
'3':
type: basic
'4':
type: basic
'5':
type: basic
edges:
'0': ['1', '2']
'1': ['3']
'2': ['4']
'3': ['2', '5']
'4': ['1']
'5': []
backedges:
"""
scfg, _ = SCFG.from_yaml(original)

dot_original = str(SCFGRenderer(scfg).render_scfg()).strip()
assert expected_original == dot_original

scfg.restructure()

dot_restructured = str(SCFGRenderer(scfg).render_scfg()).strip()
assert expected_restructured == dot_restructured
Loading