From 63e1194eaa7a5f9182da93039471716df8b79fcb Mon Sep 17 00:00:00 2001 From: kc611 Date: Thu, 29 Jun 2023 17:09:21 +0530 Subject: [PATCH 01/22] Added from_yaml, to_yaml, from_dict and to_dict conversion for new format --- .../core/datastructures/block_names.py | 13 +++ numba_rvsdg/core/datastructures/scfg.py | 97 +++++++++++++------ 2 files changed, 80 insertions(+), 30 deletions(-) diff --git a/numba_rvsdg/core/datastructures/block_names.py b/numba_rvsdg/core/datastructures/block_names.py index 07ac40dc..a36db2ce 100644 --- a/numba_rvsdg/core/datastructures/block_names.py +++ b/numba_rvsdg/core/datastructures/block_names.py @@ -10,3 +10,16 @@ SYNTH_RETURN = "synth_return" SYNTH_EXIT_LATCH = "synth_exit_latch" SYNTH_FILL = "synth_fill" + +all_block_names = { + BASIC, + PYTHON_BYTECODE, + SYNTH_HEAD, + SYNTH_BRANCH, + SYNTH_TAIL, + SYNTH_EXIT, + SYNTH_ASSIGN, + SYNTH_RETURN, + SYNTH_EXIT_LATCH, + SYNTH_FILL, +} diff --git a/numba_rvsdg/core/datastructures/scfg.py b/numba_rvsdg/core/datastructures/scfg.py index 16f96078..b28ad0b7 100644 --- a/numba_rvsdg/core/datastructures/scfg.py +++ b/numba_rvsdg/core/datastructures/scfg.py @@ -1,6 +1,5 @@ import dis import yaml -from textwrap import dedent from typing import Set, Tuple, Dict, List, Iterator from dataclasses import dataclass, field from collections import deque @@ -783,21 +782,34 @@ def from_dict(graph_dict: dict): """ scfg_graph = {} name_gen = NameGenerator() - block_dict = {} - for index in graph_dict.keys(): - block_dict[index] = name_gen.new_block_name(block_names.BASIC) - for index, attributes in graph_dict.items(): - jump_targets = attributes["jt"] - backedges = attributes.get("be", ()) - name = block_dict[index] + block_ref_dict = {} + + blocks = graph_dict["blocks"] + edges = graph_dict["edges"] + backedges = graph_dict["backedges"] + graph_dict["regions"] + + for key, block in blocks.items(): + assert block["type"] in block_names.all_block_names + block_ref_dict[key] = name_gen.new_block_name(block["type"]) + + for ref, block_name in block_ref_dict.items(): + name = block_name + block_edges = tuple(block_ref_dict[idx] for idx in edges[ref]) + if backedges and backedges.get(ref): + block_backedges = tuple( + block_ref_dict[idx] for idx in backedges[ref] + ) + else: + block_backedges = () block = BasicBlock( name=name, - backedges=tuple(block_dict[idx] for idx in backedges), - _jump_targets=tuple(block_dict[idx] for idx in jump_targets), + backedges=block_backedges, + _jump_targets=block_edges, ) scfg_graph[name] = block scfg = SCFG(scfg_graph, name_gen=name_gen) - return scfg, block_dict + return scfg, block_ref_dict def to_yaml(self): """Converts the SCFG object to a YAML string representation. @@ -816,20 +828,34 @@ def to_yaml(self): scfg_graph = self.graph yaml_string = """""" - for key, value in scfg_graph.items(): - jump_targets = [i for i in value._jump_targets] - jump_targets = str(jump_targets).replace("'", '"') - back_edges = [i for i in value.backedges] - jump_target_str = f""" - "{key}": - jt: {jump_targets}""" - - if back_edges: - back_edges = str(back_edges).replace("'", '"') - jump_target_str += f""" - be: {back_edges}""" - yaml_string += dedent(jump_target_str) + blocks = {} + edges = {} + backedges = {} + for key, value in scfg_graph.items(): + blocks[key] = {"type": "basic"} + edges[key] = [i for i in value._jump_targets] + backedges[key] = [i for i in value.backedges] + + yaml_string += "\nblocks:" + for _block in blocks.keys(): + yaml_string += f""" + {_block}:""" + block_type = blocks[_block]["type"] + yaml_string += f""" + type: {block_type}""" + + yaml_string += "\nedges:" + for _block in blocks.keys(): + yaml_string += f""" + {_block}: {edges[_block]}""" + + yaml_string += "\nbackedges:" + for _block in blocks.keys(): + if backedges[_block]: + yaml_string += f""" + {_block}: {backedges[_block]}""" + yaml_string += "\nregions:" return yaml_string def to_dict(self): @@ -846,13 +872,24 @@ def to_dict(self): A dictionary representing the SCFG. """ scfg_graph = self.graph - graph_dict = {} + + blocks = {} + edges = {} + backedges = {} + regions = {} + for key, value in scfg_graph.items(): - curr_dict = {} - curr_dict["jt"] = [i for i in value._jump_targets] - if value.backedges: - curr_dict["be"] = [i for i in value.backedges] - graph_dict[key] = curr_dict + blocks[key] = {"type": "basic"} + edges[key] = [i for i in value._jump_targets] + backedges[key] = [i for i in value.backedges] + + graph_dict = { + "blocks": blocks, + "edges": edges, + "backedges": backedges, + "regions": regions, + } + return graph_dict def view(self, name: str = None): From 1d9689a13e098554d275f3f181d8f33e48400daf Mon Sep 17 00:00:00 2001 From: kc611 Date: Thu, 29 Jun 2023 17:09:34 +0530 Subject: [PATCH 02/22] Refactored test suite to match the new functions --- numba_rvsdg/tests/test_scfg.py | 175 ++-- numba_rvsdg/tests/test_transforms.py | 1187 +++++++++++++++++--------- numba_rvsdg/tests/test_utils.py | 27 +- 3 files changed, 906 insertions(+), 483 deletions(-) diff --git a/numba_rvsdg/tests/test_scfg.py b/numba_rvsdg/tests/test_scfg.py index 2bb3e3b3..719b837b 100644 --- a/numba_rvsdg/tests/test_scfg.py +++ b/numba_rvsdg/tests/test_scfg.py @@ -17,43 +17,71 @@ def test_yaml_conversion(self): # Case # 1: Acyclic graph, no back-edges cases = [ """ - "0": - jt: ["1", "2"] - "1": - jt: ["3"] - "2": - jt: ["4"] - "3": - jt: ["4"] - "4": - jt: []""", + blocks: + '0': + type: basic + '1': + type: basic + '2': + type: basic + '3': + type: basic + '4': + type: basic + edges: + '0': ['1', '2'] + '1': ['3'] + '2': ['4'] + '3': ['4'] + '4': [] + backedges: + regions:""", # Case # 2: Cyclic graph, no back edges """ - "0": - jt: ["1", "2"] - "1": - jt: ["5"] - "2": - jt: ["1", "5"] - "3": - jt: ["0"] - "4": - jt: [] - "5": - jt: ["3", "4"]""", + blocks: + '0': + type: basic + '1': + type: basic + '2': + type: basic + '3': + type: basic + '4': + type: basic + '5': + type: basic + edges: + '0': ['1', '2'] + '1': ['5'] + '2': ['1', '5'] + '3': ['0'] + '4': [] + '5': ['3', '4'] + backedges: + regions:""", # Case # 3: Graph with backedges """ - "0": - jt: ["1"] - "1": - jt: ["2", "3"] - "2": - jt: ["4"] - "3": - jt: [] - "4": - jt: ["2", "3"] - be: ["2"]""", + blocks: + '0': + type: basic + '1': + type: basic + '2': + type: basic + '3': + type: basic + '4': + type: basic + edges: + '0': ['1'] + '1': ['2', '3'] + '2': ['4'] + '3': [] + '4': ['2', '3'] + backedges: + '4': ['2'] + regions:""", ] for case in cases: @@ -65,28 +93,62 @@ def test_dict_conversion(self): # Case # 1: Acyclic graph, no back-edges cases = [ { - "0": {"jt": ["1", "2"]}, - "1": {"jt": ["3"]}, - "2": {"jt": ["4"]}, - "3": {"jt": ["4"]}, - "4": {"jt": []}, + "blocks": { + "0": {"type": "basic"}, + "1": {"type": "basic"}, + "2": {"type": "basic"}, + "3": {"type": "basic"}, + "4": {"type": "basic"}, + }, + "edges": { + "0": ["1", "2"], + "1": ["3"], + "2": ["4"], + "3": ["4"], + "4": [], + }, + "backedges": {}, + "regions": {}, }, # Case # 2: Cyclic graph, no back edges { - "0": {"jt": ["1", "2"]}, - "1": {"jt": ["5"]}, - "2": {"jt": ["1", "5"]}, - "3": {"jt": ["0"]}, - "4": {"jt": []}, - "5": {"jt": ["3", "4"]}, + "blocks": { + "0": {"type": "basic"}, + "1": {"type": "basic"}, + "2": {"type": "basic"}, + "3": {"type": "basic"}, + "4": {"type": "basic"}, + "5": {"type": "basic"}, + }, + "edges": { + "0": ["1", "2"], + "1": ["5"], + "2": ["1", "5"], + "3": ["0"], + "4": [], + "5": ["3", "4"], + }, + "backedges": {}, + "regions": {}, }, # Case # 3: Graph with backedges { - "0": {"jt": ["1"]}, - "1": {"jt": ["2", "3"]}, - "2": {"jt": ["4"]}, - "3": {"jt": []}, - "4": {"jt": ["2", "3"], "be": ["2"]}, + "blocks": { + "0": {"type": "basic"}, + "1": {"type": "basic"}, + "2": {"type": "basic"}, + "3": {"type": "basic"}, + "4": {"type": "basic"}, + }, + "edges": { + "0": ["1"], + "1": ["2", "3"], + "2": ["4"], + "3": [], + "4": ["2", "3"], + }, + "backedges": {"4": ["2"]}, + "regions": {}, }, ] @@ -106,11 +168,16 @@ def test_scfg_iter(self): ] scfg, _ = SCFG.from_yaml( """ - "0": - jt: ["1"] - "1": - jt: [] - """ + blocks: + '0': + type: basic + '1': + type: basic + edges: + '0': ['1'] + '1': [] + backedges: + regions:""" ) received = list(scfg) self.assertEqual(expected, received) diff --git a/numba_rvsdg/tests/test_transforms.py b/numba_rvsdg/tests/test_transforms.py index 096b6cae..6536ed46 100644 --- a/numba_rvsdg/tests/test_transforms.py +++ b/numba_rvsdg/tests/test_transforms.py @@ -10,20 +10,31 @@ class TestInsertBlock(SCFGComparator): def test_linear(self): original = """ - "0": - jt: ["1"] - "1": - jt: [] - """ + blocks: + '0': + type: basic + '1': + type: basic + edges: + '0': ['1'] + '1': [] + backedges: + regions:""" original_scfg, block_dict = SCFG.from_yaml(original) expected = """ - "0": - jt: ["2"] - "1": - jt: [] - "2": - jt: ["1"] - """ + blocks: + '0': + type: basic + '1': + type: basic + '2': + type: basic + edges: + '0': ['2'] + '1': [] + '2': ['1'] + backedges: + regions:""" expected_scfg, _ = SCFG.from_yaml(expected) new_name = original_scfg.name_gen.new_block_name(block_names.BASIC) original_scfg.insert_block( @@ -33,23 +44,38 @@ def test_linear(self): def test_dual_predecessor(self): original = """ - "0": - jt: ["2"] - "1": - jt: ["2"] - "2": - jt: [] + blocks: + '0': + type: basic + '1': + type: basic + '2': + type: basic + edges: + '0': ['2'] + '1': ['2'] + '2': [] + backedges: + regions: """ original_scfg, block_dict = SCFG.from_yaml(original) expected = """ - "0": - jt: ["3"] - "1": - jt: ["3"] - "2": - jt: [] - "3": - jt: ["2"] + blocks: + '0': + type: basic + '1': + type: basic + '2': + type: basic + '3': + type: basic + edges: + '0': ['3'] + '1': ['3'] + '2': [] + '3': ['2'] + backedges: + regions: """ expected_scfg, expected_block_dict = SCFG.from_yaml(expected) new_name = original_scfg.name_gen.new_block_name(block_names.BASIC) @@ -70,23 +96,38 @@ def test_dual_predecessor(self): def test_dual_successor(self): original = """ - "0": - jt: ["1", "2"] - "1": - jt: [] - "2": - jt: [] + blocks: + '0': + type: basic + '1': + type: basic + '2': + type: basic + edges: + '0': ['1', '2'] + '1': [] + '2': [] + backedges: + regions: """ original_scfg, block_dict = SCFG.from_yaml(original) expected = """ - "0": - jt: ["3"] - "1": - jt: [] - "2": - jt: [] - "3": - jt: ["1", "2"] + blocks: + '0': + type: basic + '1': + type: basic + '2': + type: basic + '3': + type: basic + edges: + '0': ['3'] + '1': [] + '2': [] + '3': ['1', '2'] + backedges: + regions: """ expected_scfg, _ = SCFG.from_yaml(expected) original_scfg.insert_block( @@ -99,31 +140,50 @@ def test_dual_successor(self): def test_dual_predecessor_and_dual_successor(self): original = """ - "0": - jt: ["1", "2"] - "1": - jt: ["3"] - "2": - jt: ["4"] - "3": - jt: [] - "4": - jt: [] + blocks: + '0': + type: basic + '1': + type: basic + '2': + type: basic + '3': + type: basic + '4': + type: basic + edges: + '0': ['1', '2'] + '1': ['3'] + '2': ['4'] + '3': [] + '4': [] + backedges: + regions: """ original_scfg, block_dict = SCFG.from_yaml(original) expected = """ - "0": - jt: ["1", "2"] - "1": - jt: ["5"] - "2": - jt: ["5"] - "3": - jt: [] - "4": - jt: [] - "5": - jt: ["3", "4"] + blocks: + '0': + type: basic + '1': + type: basic + '2': + type: basic + '3': + type: basic + '4': + type: basic + '5': + type: basic + edges: + '0': ['1', '2'] + '1': ['5'] + '2': ['5'] + '3': [] + '4': [] + '5': ['3', '4'] + backedges: + regions: """ expected_scfg, _ = SCFG.from_yaml(expected) original_scfg.insert_block( @@ -136,31 +196,49 @@ def test_dual_predecessor_and_dual_successor(self): def test_dual_predecessor_and_dual_successor_with_additional_arcs(self): original = """ - "0": - jt: ["1", "2"] - "1": - jt: ["3"] - "2": - jt: ["1", "4"] - "3": - jt: ["0"] - "4": - jt: [] - """ + blocks: + '0': + type: basic + '1': + type: basic + '2': + type: basic + '3': + type: basic + '4': + type: basic + edges: + '0': ['1', '2'] + '1': ['3'] + '2': ['1', '4'] + '3': ['0'] + '4': [] + backedges: + regions:""" original_scfg, block_dict = SCFG.from_yaml(original) expected = """ - "0": - jt: ["1", "2"] - "1": - jt: ["5"] - "2": - jt: ["1", "5"] - "3": - jt: ["0"] - "4": - jt: [] - "5": - jt: ["3", "4"] + blocks: + '0': + type: basic + '1': + type: basic + '2': + type: basic + '3': + type: basic + '4': + type: basic + '5': + type: basic + edges: + '0': ['1', '2'] + '1': ['5'] + '2': ['1', '5'] + '3': ['0'] + '4': [] + '5': ['3', '4'] + backedges: + regions: """ expected_scfg, expected_block_dict = SCFG.from_yaml(expected) original_scfg.insert_block( @@ -182,23 +260,38 @@ def test_dual_predecessor_and_dual_successor_with_additional_arcs(self): class TestJoinReturns(SCFGComparator): def test_two_returns(self): original = """ - "0": - jt: ["1", "2"] - "1": - jt: [] - "2": - jt: [] + blocks: + '0': + type: basic + '1': + type: basic + '2': + type: basic + edges: + '0': ['1', '2'] + '1': [] + '2': [] + backedges: + regions: """ original_scfg, block_dict = SCFG.from_yaml(original) expected = """ - "0": - jt: ["1", "2"] - "1": - jt: ["3"] - "2": - jt: ["3"] - "3": - jt: [] + blocks: + '0': + type: basic + '1': + type: basic + '2': + type: basic + '3': + type: basic + edges: + '0': ['1', '2'] + '1': ['3'] + '2': ['3'] + '3': [] + backedges: + regions: """ expected_scfg, _ = SCFG.from_yaml(expected) original_scfg.join_returns() @@ -208,17 +301,29 @@ def test_two_returns(self): class TestJoinTailsAndExits(SCFGComparator): def test_join_tails_and_exits_case_00(self): original = """ - "0": - jt: ["1"] - "1": - jt: [] + blocks: + '0': + type: basic + '1': + type: basic + edges: + '0': ['1'] + '1': [] + backedges: + regions: """ original_scfg, block_dict = SCFG.from_yaml(original) expected = """ - "0": - jt: ["1"] - "1": - jt: [] + blocks: + '0': + type: basic + '1': + type: basic + edges: + '0': ['1'] + '1': [] + backedges: + regions: """ expected_scfg, _ = SCFG.from_yaml(expected) @@ -234,27 +339,44 @@ def test_join_tails_and_exits_case_00(self): def test_join_tails_and_exits_case_01(self): original = """ - "0": - jt: ["1", "2"] - "1": - jt: ["3"] - "2": - jt: ["3"] - "3": - jt: [] + blocks: + '0': + type: basic + '1': + type: basic + '2': + type: basic + '3': + type: basic + edges: + '0': ['1', '2'] + '1': ['3'] + '2': ['3'] + '3': [] + backedges: + regions: """ original_scfg, block_dict = SCFG.from_yaml(original) expected = """ - "0": - jt: ["4"] - "1": - jt: ["3"] - "2": - jt: ["3"] - "3": - jt: [] - "4": - jt: ["1", "2"] + blocks: + '0': + type: basic + '1': + type: basic + '2': + type: basic + '3': + type: basic + '4': + type: basic + edges: + '0': ['4'] + '1': ['3'] + '2': ['3'] + '3': [] + '4': ['1', '2'] + backedges: + regions: """ expected_scfg, _ = SCFG.from_yaml(expected) @@ -273,27 +395,44 @@ def test_join_tails_and_exits_case_01(self): def test_join_tails_and_exits_case_02_01(self): original = """ - "0": - jt: ["1", "2"] - "1": - jt: ["3"] - "2": - jt: ["3"] - "3": - jt: [] + blocks: + '0': + type: basic + '1': + type: basic + '2': + type: basic + '3': + type: basic + edges: + '0': ['1', '2'] + '1': ['3'] + '2': ['3'] + '3': [] + backedges: + regions: """ original_scfg, block_dict = SCFG.from_yaml(original) expected = """ - "0": - jt: ["1", "2"] - "1": - jt: ["4"] - "2": - jt: ["4"] - "3": - jt: [] - "4": - jt: ["3"] + blocks: + '0': + type: basic + '1': + type: basic + '2': + type: basic + '3': + type: basic + '4': + type: basic + edges: + '0': ['1', '2'] + '1': ['4'] + '2': ['4'] + '3': [] + '4': ['3'] + backedges: + regions: """ expected_scfg, _ = SCFG.from_yaml(expected) @@ -312,27 +451,44 @@ def test_join_tails_and_exits_case_02_01(self): def test_join_tails_and_exits_case_02_02(self): original = """ - "0": - jt: ["1", "2"] - "1": - jt: ["3"] - "2": - jt: ["1", "3"] - "3": - jt: [] + blocks: + '0': + type: basic + '1': + type: basic + '2': + type: basic + '3': + type: basic + edges: + '0': ['1', '2'] + '1': ['3'] + '2': ['1', '3'] + '3': [] + backedges: + regions: """ original_scfg, block_dict = SCFG.from_yaml(original) expected = """ - "0": - jt: ["1", "2"] - "1": - jt: ["4"] - "2": - jt: ["1", "4"] - "3": - jt: [] - "4": - jt: ["3"] + blocks: + '0': + type: basic + '1': + type: basic + '2': + type: basic + '3': + type: basic + '4': + type: basic + edges: + '0': ['1', '2'] + '1': ['4'] + '2': ['1', '4'] + '3': [] + '4': ['3'] + backedges: + regions: """ expected_scfg, _ = SCFG.from_yaml(expected) @@ -351,37 +507,59 @@ def test_join_tails_and_exits_case_02_02(self): def test_join_tails_and_exits_case_03_01(self): original = """ - "0": - jt: ["1", "2"] - "1": - jt: ["3"] - "2": - jt: ["4"] - "3": - jt: ["5"] - "4": - jt: ["5"] - "5": - jt: [] + 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': ['5'] + '4': ['5'] + '5': [] + backedges: + regions: """ original_scfg, block_dict = SCFG.from_yaml(original) expected = """ - "0": - jt: ["1", "2"] - "1": - jt: ["6"] - "2": - jt: ["6"] - "3": - jt: ["5"] - "4": - jt: ["5"] - "5": - jt: [] - "6": - jt: ["7"] - "7": - jt: ["3", "4"] + blocks: + '0': + type: basic + '1': + type: basic + '2': + type: basic + '3': + type: basic + '4': + type: basic + '5': + type: basic + '6': + type: basic + '7': + type: basic + edges: + '0': ['1', '2'] + '1': ['6'] + '2': ['6'] + '3': ['5'] + '4': ['5'] + '5': [] + '6': ['7'] + '7': ['3', '4'] + backedges: + regions: """ expected_scfg, _ = SCFG.from_yaml(expected) @@ -402,37 +580,59 @@ def test_join_tails_and_exits_case_03_01(self): def test_join_tails_and_exits_case_03_02(self): original = """ - "0": - jt: ["1", "2"] - "1": - jt: ["3"] - "2": - jt: ["1", "4"] - "3": - jt: ["5"] - "4": - jt: ["5"] - "5": - jt: [] + 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': ['1', '4'] + '3': ['5'] + '4': ['5'] + '5': [] + backedges: + regions: """ original_scfg, block_dict = SCFG.from_yaml(original) expected = """ - "0": - jt: ["1", "2"] - "1": - jt: ["6"] - "2": - jt: ["1", "6"] - "3": - jt: ["5"] - "4": - jt: ["5"] - "5": - jt: [] - "6": - jt: ["7"] - "7": - jt: ["3", "4"] + blocks: + '0': + type: basic + '1': + type: basic + '2': + type: basic + '3': + type: basic + '4': + type: basic + '5': + type: basic + '6': + type: basic + '7': + type: basic + edges: + '0': ['1', '2'] + '1': ['6'] + '2': ['1', '6'] + '3': ['5'] + '4': ['5'] + '5': [] + '6': ['7'] + '7': ['3', '4'] + backedges: + regions: """ expected_scfg, _ = SCFG.from_yaml(expected) tails = (block_dict["1"], block_dict["2"]) @@ -455,22 +655,35 @@ class TestLoopRestructure(SCFGComparator): def test_no_op_mono(self): """Loop consists of a single Block.""" original = """ - "0": - jt: ["1"] - "1": - jt: ["1", "2"] - "2": - jt: [] + blocks: + '0': + type: basic + '1': + type: basic + '2': + type: basic + edges: + '0': ['1'] + '1': ['1', '2'] + '2': [] + backedges: + regions: """ expected = """ - "0": - jt: ["1"] - "1": - jt: ["1", "2"] - be: ["1"] - "2": - jt: [] - """ + blocks: + '0': + type: basic + '1': + type: basic + '2': + type: basic + edges: + '0': ['1'] + '1': ['1', '2'] + '2': [] + backedges: + '1': ['1'] + regions:""" original_scfg, block_dict = SCFG.from_yaml(original) expected_scfg, _ = SCFG.from_yaml(expected) loop_restructure_helper(original_scfg, set({block_dict["1"]})) @@ -479,25 +692,41 @@ def test_no_op_mono(self): def test_no_op(self): """Loop consists of two blocks, but it's in form.""" original = """ - "0": - jt: ["1"] - "1": - jt: ["2"] - "2": - jt: ["1", "3"] - "3": - jt: [] + blocks: + '0': + type: basic + '1': + type: basic + '2': + type: basic + '3': + type: basic + edges: + '0': ['1'] + '1': ['2'] + '2': ['1', '3'] + '3': [] + backedges: + regions: """ expected = """ - "0": - jt: ["1"] - "1": - jt: ["2"] - "2": - jt: ["1", "3"] - be: ["1"] - "3": - jt: [] + blocks: + '0': + type: basic + '1': + type: basic + '2': + type: basic + '3': + type: basic + edges: + '0': ['1'] + '1': ['2'] + '2': ['1', '3'] + '3': [] + backedges: + '2': ['1'] + regions: """ original_scfg, block_dict = SCFG.from_yaml(original) expected_scfg, _ = SCFG.from_yaml(expected) @@ -512,31 +741,50 @@ def test_backedge_not_exiting(self): This is the situation with the standard Python for loop. """ original = """ - "0": - jt: ["1"] - "1": - jt: ["2", "3"] - "2": - jt: ["1"] - "3": - jt: [] + blocks: + '0': + type: basic + '1': + type: basic + '2': + type: basic + '3': + type: basic + edges: + '0': ['1'] + '1': ['2', '3'] + '2': ['1'] + '3': [] + backedges: + regions: """ expected = """ - "0": - jt: ["1"] - "1": - jt: ["2", "5"] - "2": - jt: ["6"] - "3": - jt: [] - "4": - jt: ["1", "3"] - be: ["1"] - "5": - jt: ["4"] - "6": - jt: ["4"] + blocks: + '0': + type: basic + '1': + type: basic + '2': + type: basic + '3': + type: basic + '4': + type: basic + '5': + type: basic + '6': + type: basic + edges: + '0': ['1'] + '1': ['2', '5'] + '2': ['6'] + '3': [] + '4': ['1', '3'] + '5': ['4'] + '6': ['4'] + backedges: + '4': ['1'] + regions: """ original_scfg, block_dict = SCFG.from_yaml(original) expected_scfg, _ = SCFG.from_yaml(expected) @@ -547,33 +795,54 @@ def test_backedge_not_exiting(self): def test_multi_back_edge_with_backedge_from_header(self): original = """ - "0": - jt: ["1"] - "1": - jt: ["1", "2"] - "2": - jt: ["1", "3"] - "3": - jt: [] + blocks: + '0': + type: basic + '1': + type: basic + '2': + type: basic + '3': + type: basic + edges: + '0': ['1'] + '1': ['1', '2'] + '2': ['1', '3'] + '3': [] + backedges: + regions: + """ expected = """ - "0": - jt: ["1"] - "1": - jt: ["5", "2"] - "2": - jt: ["6", "7"] - "3": - jt: [] - "4": - jt: ["1", "3"] - be: ["1"] - "5": - jt: ["4"] - "6": - jt: ["4"] - "7": - jt: ["4"] + blocks: + '0': + type: basic + '1': + type: basic + '2': + type: basic + '3': + type: basic + '4': + type: basic + '5': + type: basic + '6': + type: basic + '7': + type: basic + edges: + '0': ['1'] + '1': ['5', '2'] + '2': ['6', '7'] + '3': [] + '4': ['1', '3'] + '5': ['4'] + '6': ['4'] + '7': ['4'] + backedges: + '4': ['1'] + regions: """ original_scfg, block_dict = SCFG.from_yaml(original) expected_scfg, _ = SCFG.from_yaml(expected) @@ -589,38 +858,58 @@ def test_double_exit(self): """ original = """ - "0": - jt: ["1"] - "1": - jt: ["2"] - "2": - jt: ["3", "4"] - "3": - jt: ["1", "4"] - "4": - jt: [] - """ + blocks: + '0': + type: basic + '1': + type: basic + '2': + type: basic + '3': + type: basic + '4': + type: basic + edges: + '0': ['1'] + '1': ['2'] + '2': ['3', '4'] + '3': ['1', '4'] + '4': [] + backedges: + regions:""" expected = """ - "0": - jt: ["1"] - "1": - jt: ["2"] - "2": - jt: ["3", "6"] - "3": - jt: ["7", "8"] - "4": - jt: [] - "5": - jt: ["1", "4"] - be: ["1"] - "6": - jt: ["5"] - "7": - jt: ["5"] - "8": - jt: ["5"] - """ + blocks: + '0': + type: basic + '1': + type: basic + '2': + type: basic + '3': + type: basic + '4': + type: basic + '5': + type: basic + '6': + type: basic + '7': + type: basic + '8': + type: basic + edges: + '0': ['1'] + '1': ['2'] + '2': ['3', '6'] + '3': ['7', '8'] + '4': [] + '5': ['1', '4'] + '6': ['5'] + '7': ['5'] + '8': ['5'] + backedges: + '5': ['1'] + regions:""" original_scfg, block_dict = SCFG.from_yaml(original) expected_scfg, _ = SCFG.from_yaml(expected) loop_restructure_helper( @@ -633,48 +922,73 @@ def test_double_header(self): """This is like the example from Bahman2015 fig. 3 -- but with one exiting block removed.""" original = """ - "0": - jt: ["1", "2"] - "1": - jt: ["3"] - "2": - jt: ["4"] - "3": - jt: ["2", "5"] - "4": - jt: ["1"] - "5": - jt: [] - """ + 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: + regions:""" expected = """ - "0": - jt: ["7", "8"] - "1": - jt: ["3"] - "2": - jt: ["4"] - "3": - jt: ["10", "11"] - "4": - jt: ["12"] - "5": - jt: [] - "6": - jt: ["1", "2"] - "7": - jt: ["6"] - "8": - jt: ["6"] - "9": - jt: ["5", "6"] - be: ["6"] - "10": - jt: ["9"] - "11": - jt: ["9"] - "12": - jt: ["9"] - """ + blocks: + '0': + type: basic + '1': + type: basic + '2': + type: basic + '3': + type: basic + '4': + type: basic + '5': + type: basic + '6': + type: basic + '7': + type: basic + '8': + type: basic + '9': + type: basic + '10': + type: basic + '11': + type: basic + '12': + type: basic + edges: + '0': ['7', '8'] + '1': ['3'] + '2': ['4'] + '3': ['10', '11'] + '4': ['12'] + '5': [] + '6': ['1', '2'] + '7': ['6'] + '8': ['6'] + '9': ['5', '6'] + '10': ['9'] + '11': ['9'] + '12': ['9'] + backedges: + '9': ['6'] + regions:""" original_scfg, block_dict = SCFG.from_yaml(original) expected_scfg, _ = SCFG.from_yaml(expected) loop_restructure_helper( @@ -698,60 +1012,91 @@ def test_double_header_double_exiting(self): """ original = """ - "0": - jt: ["1", "2"] - "1": - jt: ["3"] - "2": - jt: ["4"] - "3": - jt: ["2", "5"] - "4": - jt: ["1", "6"] - "5": - jt: ["7"] - "6": - jt: ["7"] - "7": - jt: [] - """ + blocks: + '0': + type: basic + '1': + type: basic + '2': + type: basic + '3': + type: basic + '4': + type: basic + '5': + type: basic + '6': + type: basic + '7': + type: basic + edges: + '0': ['1', '2'] + '1': ['3'] + '2': ['4'] + '3': ['2', '5'] + '4': ['1', '6'] + '5': ['7'] + '6': ['7'] + '7': [] + backedges: + regions:""" expected = """ - "0": - jt: ["10", "9"] - "1": - jt: ["3"] - "2": - jt: ["4"] - "3": - jt: ["13", "14"] - "4": - jt: ["15", "16"] - "5": - jt: ["7"] - "6": - jt: ["7"] - "7": - jt: [] - "8": - jt: ["1", "2"] - "9": - jt: ["8"] - "10": - jt: ["8"] - "11": - jt: ["12", "8"] - be: ["8"] - "12": - jt: ["5", "6"] - "13": - jt: ["11"] - "14": - jt: ["11"] - "15": - jt: ["11"] - "16": - jt: ["11"] - """ + blocks: + '0': + type: basic + '1': + type: basic + '2': + type: basic + '3': + type: basic + '4': + type: basic + '5': + type: basic + '6': + type: basic + '7': + type: basic + '8': + type: basic + '9': + type: basic + '10': + type: basic + '11': + type: basic + '12': + type: basic + '13': + type: basic + '14': + type: basic + '15': + type: basic + '16': + type: basic + edges: + '0': ['10', '9'] + '1': ['3'] + '2': ['4'] + '3': ['13', '14'] + '4': ['15', '16'] + '5': ['7'] + '6': ['7'] + '7': [] + '8': ['1', '2'] + '9': ['8'] + '10': ['8'] + '11': ['12', '8'] + '12': ['5', '6'] + '13': ['11'] + '14': ['11'] + '15': ['11'] + '16': ['11'] + backedges: + '11': ['8'] + regions:""" original_scfg, block_dict = SCFG.from_yaml(original) expected_scfg, _ = SCFG.from_yaml(expected) loop_restructure_helper( diff --git a/numba_rvsdg/tests/test_utils.py b/numba_rvsdg/tests/test_utils.py index f1a9c4f0..c1f228d2 100644 --- a/numba_rvsdg/tests/test_utils.py +++ b/numba_rvsdg/tests/test_utils.py @@ -75,21 +75,32 @@ def assertDictEqual( if node_name in seen: continue seen.add(node_name) - node: BasicBlock = first_yaml[node_name] # Assert that there's a corresponding mapping of current node # in second scfg assert node_name in block_mapping.keys() - # Get the corresponding node in second graph - second_node_name = block_mapping[node_name] - second_node: BasicBlock = second_yaml[second_node_name] + co_node_name = block_mapping[node_name] + + node_properties = first_yaml["blocks"][node_name] + co_node_properties = second_yaml["blocks"][co_node_name] + assert node_properties == co_node_properties + # Both nodes should have equal number of jump targets and backedges - assert len(node["jt"]) == len(second_node["jt"]) - if "be" in node.keys(): - assert len(node["be"]) == len(second_node["be"]) + assert len(first_yaml["edges"][node_name]) == len( + second_yaml["edges"][co_node_name] + ) + if first_yaml["backedges"] and first_yaml["backedges"].get( + node_name + ): + assert len(first_yaml["backedges"][node_name]) == len( + second_yaml["backedges"][co_node_name] + ) # Add the jump targets as corresponding nodes in block mapping # dictionary. Since order must be same we can simply add zip # functionality as the correspondence function for nodes - for jt1, jt2 in zip(node["jt"], second_node["jt"]): + for jt1, jt2 in zip( + first_yaml["edges"][node_name], + second_yaml["edges"][co_node_name], + ): block_mapping[jt1] = jt2 stack.append(jt1) From dae773cf06b92a201590f1f6963b1267ca24d1ed Mon Sep 17 00:00:00 2001 From: kc611 Date: Thu, 29 Jun 2023 18:15:33 +0530 Subject: [PATCH 03/22] Added region identification and printing capabilities to to_yaml and to_dict --- .../core/datastructures/basic_block.py | 17 +++++++ .../core/datastructures/block_names.py | 4 ++ numba_rvsdg/core/datastructures/scfg.py | 51 ++++++++++++++----- 3 files changed, 59 insertions(+), 13 deletions(-) diff --git a/numba_rvsdg/core/datastructures/basic_block.py b/numba_rvsdg/core/datastructures/basic_block.py index 263e09cc..393b1057 100644 --- a/numba_rvsdg/core/datastructures/basic_block.py +++ b/numba_rvsdg/core/datastructures/basic_block.py @@ -3,6 +3,7 @@ from dataclasses import dataclass, replace from numba_rvsdg.core.utils import _next_inst_offset +from numba_rvsdg.core.datastructures import block_names @dataclass(frozen=True) @@ -384,3 +385,19 @@ def replace_exiting(self, new_exiting): The new exiting block of the region represented by the RegionBlock. """ object.__setattr__(self, "exiting", new_exiting) + + +block_type_names = { + block_names.BASIC: BasicBlock, + block_names.PYTHON_BYTECODE: PythonBytecodeBlock, + block_names.SYNTH_HEAD: SyntheticHead, + block_names.SYNTH_BRANCH: SyntheticBranch, + block_names.SYNTH_TAIL: SyntheticTail, + block_names.SYNTH_EXIT: SyntheticExit, + block_names.SYNTH_ASSIGN: SyntheticAssignment, + block_names.SYNTH_RETURN: SyntheticReturn, + block_names.SYNTH_EXIT_LATCH: SyntheticExitingLatch, + block_names.SYNTH_EXIT_BRANCH: SyntheticExitBranch, + block_names.SYNTH_FILL: SyntheticFill, + block_names.REGION: RegionBlock, +} diff --git a/numba_rvsdg/core/datastructures/block_names.py b/numba_rvsdg/core/datastructures/block_names.py index a36db2ce..030e924b 100644 --- a/numba_rvsdg/core/datastructures/block_names.py +++ b/numba_rvsdg/core/datastructures/block_names.py @@ -10,6 +10,9 @@ SYNTH_RETURN = "synth_return" SYNTH_EXIT_LATCH = "synth_exit_latch" SYNTH_FILL = "synth_fill" +SYNTH_EXIT_BRANCH = "synth_exit_branch" + +REGION = "region" all_block_names = { BASIC, @@ -22,4 +25,5 @@ SYNTH_RETURN, SYNTH_EXIT_LATCH, SYNTH_FILL, + REGION, } diff --git a/numba_rvsdg/core/datastructures/scfg.py b/numba_rvsdg/core/datastructures/scfg.py index b28ad0b7..af39e65d 100644 --- a/numba_rvsdg/core/datastructures/scfg.py +++ b/numba_rvsdg/core/datastructures/scfg.py @@ -15,6 +15,7 @@ SyntheticReturn, SyntheticFill, RegionBlock, + block_type_names, ) from numba_rvsdg.core.datastructures import block_names @@ -787,7 +788,6 @@ def from_dict(graph_dict: dict): blocks = graph_dict["blocks"] edges = graph_dict["edges"] backedges = graph_dict["backedges"] - graph_dict["regions"] for key, block in blocks.items(): assert block["type"] in block_names.all_block_names @@ -825,15 +825,29 @@ def to_yaml(self): A YAML string representing the SCFG. """ # Convert to yaml - scfg_graph = self.graph yaml_string = """""" blocks = {} edges = {} backedges = {} - for key, value in scfg_graph.items(): - blocks[key] = {"type": "basic"} + # This does a dictionary reverse lookup, to determine the key for a given + # value. + def reverse_lookup(value): + for k, v in block_type_names.items(): + if v == value: + return k + else: + raise TypeError("Block type not found.") + + for key, value in self: + block_type = reverse_lookup(type(value)) + blocks[key] = {"type": block_type} + if block_type == "region": + blocks[key]["kind"] = value.kind + blocks[key]["contains"] = [ + idx.name for idx in value.subregion.graph.values() + ] edges[key] = [i for i in value._jump_targets] backedges[key] = [i for i in value.backedges] @@ -844,6 +858,11 @@ def to_yaml(self): block_type = blocks[_block]["type"] yaml_string += f""" type: {block_type}""" + if block_type == "region": + yaml_string += f""" + kind: {blocks[_block]["kind"]}""" + yaml_string += f""" + contains: {blocks[_block]["contains"]}""" yaml_string += "\nedges:" for _block in blocks.keys(): @@ -855,7 +874,6 @@ def to_yaml(self): if backedges[_block]: yaml_string += f""" {_block}: {backedges[_block]}""" - yaml_string += "\nregions:" return yaml_string def to_dict(self): @@ -876,19 +894,26 @@ def to_dict(self): blocks = {} edges = {} backedges = {} - regions = {} + + def reverse_lookup(value): + for k, v in block_type_names.items(): + if v == value: + return k + else: + raise TypeError("Block type not found.") for key, value in scfg_graph.items(): - blocks[key] = {"type": "basic"} + block_type = reverse_lookup(type(value)) + blocks[key] = {"type": block_type} + if block_type == "region": + blocks[key]["kind"] = value.kind + blocks[key]["contains"] = [ + idx.name for idx in value.subregion.graph.values() + ] edges[key] = [i for i in value._jump_targets] backedges[key] = [i for i in value.backedges] - graph_dict = { - "blocks": blocks, - "edges": edges, - "backedges": backedges, - "regions": regions, - } + graph_dict = {"blocks": blocks, "edges": edges, "backedges": backedges} return graph_dict From 0ea0e6c9368839d0c74d8860333ce423edfa3d8d Mon Sep 17 00:00:00 2001 From: kc611 Date: Thu, 29 Jun 2023 18:15:54 +0530 Subject: [PATCH 04/22] Adapted tests to the changes --- numba_rvsdg/tests/test_fig3.py | 4 ++- numba_rvsdg/tests/test_scfg.py | 11 +++---- numba_rvsdg/tests/test_transforms.py | 49 ++++++---------------------- 3 files changed, 17 insertions(+), 47 deletions(-) diff --git a/numba_rvsdg/tests/test_fig3.py b/numba_rvsdg/tests/test_fig3.py index 75b090ac..5d4eb513 100644 --- a/numba_rvsdg/tests/test_fig3.py +++ b/numba_rvsdg/tests/test_fig3.py @@ -34,8 +34,10 @@ def make_flow(): def test_fig3(): + # Run this function to print YAML for the given function f = make_flow() - f.restructure() + f = f.restructure() + print(f.scfg.to_yaml()) if __name__ == "__main__": diff --git a/numba_rvsdg/tests/test_scfg.py b/numba_rvsdg/tests/test_scfg.py index 719b837b..c766926f 100644 --- a/numba_rvsdg/tests/test_scfg.py +++ b/numba_rvsdg/tests/test_scfg.py @@ -35,7 +35,7 @@ def test_yaml_conversion(self): '3': ['4'] '4': [] backedges: - regions:""", + """, # Case # 2: Cyclic graph, no back edges """ blocks: @@ -59,7 +59,7 @@ def test_yaml_conversion(self): '4': [] '5': ['3', '4'] backedges: - regions:""", + """, # Case # 3: Graph with backedges """ blocks: @@ -81,7 +81,7 @@ def test_yaml_conversion(self): '4': ['2', '3'] backedges: '4': ['2'] - regions:""", + """, ] for case in cases: @@ -108,7 +108,6 @@ def test_dict_conversion(self): "4": [], }, "backedges": {}, - "regions": {}, }, # Case # 2: Cyclic graph, no back edges { @@ -129,7 +128,6 @@ def test_dict_conversion(self): "5": ["3", "4"], }, "backedges": {}, - "regions": {}, }, # Case # 3: Graph with backedges { @@ -148,7 +146,6 @@ def test_dict_conversion(self): "4": ["2", "3"], }, "backedges": {"4": ["2"]}, - "regions": {}, }, ] @@ -177,7 +174,7 @@ def test_scfg_iter(self): '0': ['1'] '1': [] backedges: - regions:""" + """ ) received = list(scfg) self.assertEqual(expected, received) diff --git a/numba_rvsdg/tests/test_transforms.py b/numba_rvsdg/tests/test_transforms.py index 6536ed46..d42c2e0b 100644 --- a/numba_rvsdg/tests/test_transforms.py +++ b/numba_rvsdg/tests/test_transforms.py @@ -19,7 +19,7 @@ def test_linear(self): '0': ['1'] '1': [] backedges: - regions:""" + """ original_scfg, block_dict = SCFG.from_yaml(original) expected = """ blocks: @@ -34,7 +34,7 @@ def test_linear(self): '1': [] '2': ['1'] backedges: - regions:""" + """ expected_scfg, _ = SCFG.from_yaml(expected) new_name = original_scfg.name_gen.new_block_name(block_names.BASIC) original_scfg.insert_block( @@ -56,7 +56,6 @@ def test_dual_predecessor(self): '1': ['2'] '2': [] backedges: - regions: """ original_scfg, block_dict = SCFG.from_yaml(original) expected = """ @@ -75,7 +74,6 @@ def test_dual_predecessor(self): '2': [] '3': ['2'] backedges: - regions: """ expected_scfg, expected_block_dict = SCFG.from_yaml(expected) new_name = original_scfg.name_gen.new_block_name(block_names.BASIC) @@ -108,7 +106,6 @@ def test_dual_successor(self): '1': [] '2': [] backedges: - regions: """ original_scfg, block_dict = SCFG.from_yaml(original) expected = """ @@ -127,7 +124,6 @@ def test_dual_successor(self): '2': [] '3': ['1', '2'] backedges: - regions: """ expected_scfg, _ = SCFG.from_yaml(expected) original_scfg.insert_block( @@ -158,7 +154,6 @@ def test_dual_predecessor_and_dual_successor(self): '3': [] '4': [] backedges: - regions: """ original_scfg, block_dict = SCFG.from_yaml(original) expected = """ @@ -183,7 +178,6 @@ def test_dual_predecessor_and_dual_successor(self): '4': [] '5': ['3', '4'] backedges: - regions: """ expected_scfg, _ = SCFG.from_yaml(expected) original_scfg.insert_block( @@ -214,7 +208,7 @@ def test_dual_predecessor_and_dual_successor_with_additional_arcs(self): '3': ['0'] '4': [] backedges: - regions:""" + """ original_scfg, block_dict = SCFG.from_yaml(original) expected = """ blocks: @@ -238,7 +232,6 @@ def test_dual_predecessor_and_dual_successor_with_additional_arcs(self): '4': [] '5': ['3', '4'] backedges: - regions: """ expected_scfg, expected_block_dict = SCFG.from_yaml(expected) original_scfg.insert_block( @@ -272,7 +265,6 @@ def test_two_returns(self): '1': [] '2': [] backedges: - regions: """ original_scfg, block_dict = SCFG.from_yaml(original) expected = """ @@ -291,7 +283,6 @@ def test_two_returns(self): '2': ['3'] '3': [] backedges: - regions: """ expected_scfg, _ = SCFG.from_yaml(expected) original_scfg.join_returns() @@ -310,7 +301,6 @@ def test_join_tails_and_exits_case_00(self): '0': ['1'] '1': [] backedges: - regions: """ original_scfg, block_dict = SCFG.from_yaml(original) expected = """ @@ -323,7 +313,6 @@ def test_join_tails_and_exits_case_00(self): '0': ['1'] '1': [] backedges: - regions: """ expected_scfg, _ = SCFG.from_yaml(expected) @@ -354,7 +343,6 @@ def test_join_tails_and_exits_case_01(self): '2': ['3'] '3': [] backedges: - regions: """ original_scfg, block_dict = SCFG.from_yaml(original) expected = """ @@ -376,7 +364,6 @@ def test_join_tails_and_exits_case_01(self): '3': [] '4': ['1', '2'] backedges: - regions: """ expected_scfg, _ = SCFG.from_yaml(expected) @@ -410,7 +397,6 @@ def test_join_tails_and_exits_case_02_01(self): '2': ['3'] '3': [] backedges: - regions: """ original_scfg, block_dict = SCFG.from_yaml(original) expected = """ @@ -432,7 +418,6 @@ def test_join_tails_and_exits_case_02_01(self): '3': [] '4': ['3'] backedges: - regions: """ expected_scfg, _ = SCFG.from_yaml(expected) @@ -466,7 +451,6 @@ def test_join_tails_and_exits_case_02_02(self): '2': ['1', '3'] '3': [] backedges: - regions: """ original_scfg, block_dict = SCFG.from_yaml(original) expected = """ @@ -488,7 +472,6 @@ def test_join_tails_and_exits_case_02_02(self): '3': [] '4': ['3'] backedges: - regions: """ expected_scfg, _ = SCFG.from_yaml(expected) @@ -528,7 +511,6 @@ def test_join_tails_and_exits_case_03_01(self): '4': ['5'] '5': [] backedges: - regions: """ original_scfg, block_dict = SCFG.from_yaml(original) expected = """ @@ -559,7 +541,6 @@ def test_join_tails_and_exits_case_03_01(self): '6': ['7'] '7': ['3', '4'] backedges: - regions: """ expected_scfg, _ = SCFG.from_yaml(expected) @@ -601,7 +582,6 @@ def test_join_tails_and_exits_case_03_02(self): '4': ['5'] '5': [] backedges: - regions: """ original_scfg, block_dict = SCFG.from_yaml(original) expected = """ @@ -632,7 +612,6 @@ def test_join_tails_and_exits_case_03_02(self): '6': ['7'] '7': ['3', '4'] backedges: - regions: """ expected_scfg, _ = SCFG.from_yaml(expected) tails = (block_dict["1"], block_dict["2"]) @@ -667,7 +646,6 @@ def test_no_op_mono(self): '1': ['1', '2'] '2': [] backedges: - regions: """ expected = """ blocks: @@ -683,7 +661,7 @@ def test_no_op_mono(self): '2': [] backedges: '1': ['1'] - regions:""" + """ original_scfg, block_dict = SCFG.from_yaml(original) expected_scfg, _ = SCFG.from_yaml(expected) loop_restructure_helper(original_scfg, set({block_dict["1"]})) @@ -707,7 +685,6 @@ def test_no_op(self): '2': ['1', '3'] '3': [] backedges: - regions: """ expected = """ blocks: @@ -726,7 +703,6 @@ def test_no_op(self): '3': [] backedges: '2': ['1'] - regions: """ original_scfg, block_dict = SCFG.from_yaml(original) expected_scfg, _ = SCFG.from_yaml(expected) @@ -756,7 +732,6 @@ def test_backedge_not_exiting(self): '2': ['1'] '3': [] backedges: - regions: """ expected = """ blocks: @@ -784,7 +759,6 @@ def test_backedge_not_exiting(self): '6': ['4'] backedges: '4': ['1'] - regions: """ original_scfg, block_dict = SCFG.from_yaml(original) expected_scfg, _ = SCFG.from_yaml(expected) @@ -810,8 +784,6 @@ def test_multi_back_edge_with_backedge_from_header(self): '2': ['1', '3'] '3': [] backedges: - regions: - """ expected = """ blocks: @@ -842,7 +814,6 @@ def test_multi_back_edge_with_backedge_from_header(self): '7': ['4'] backedges: '4': ['1'] - regions: """ original_scfg, block_dict = SCFG.from_yaml(original) expected_scfg, _ = SCFG.from_yaml(expected) @@ -876,7 +847,7 @@ def test_double_exit(self): '3': ['1', '4'] '4': [] backedges: - regions:""" + """ expected = """ blocks: '0': @@ -909,7 +880,7 @@ def test_double_exit(self): '8': ['5'] backedges: '5': ['1'] - regions:""" + """ original_scfg, block_dict = SCFG.from_yaml(original) expected_scfg, _ = SCFG.from_yaml(expected) loop_restructure_helper( @@ -943,7 +914,7 @@ def test_double_header(self): '4': ['1'] '5': [] backedges: - regions:""" + """ expected = """ blocks: '0': @@ -988,7 +959,7 @@ def test_double_header(self): '12': ['9'] backedges: '9': ['6'] - regions:""" + """ original_scfg, block_dict = SCFG.from_yaml(original) expected_scfg, _ = SCFG.from_yaml(expected) loop_restructure_helper( @@ -1039,7 +1010,7 @@ def test_double_header_double_exiting(self): '6': ['7'] '7': [] backedges: - regions:""" + """ expected = """ blocks: '0': @@ -1096,7 +1067,7 @@ def test_double_header_double_exiting(self): '16': ['11'] backedges: '11': ['8'] - regions:""" + """ original_scfg, block_dict = SCFG.from_yaml(original) expected_scfg, _ = SCFG.from_yaml(expected) loop_restructure_helper( From 4213e58cd3f97b17b05055d067eb23ed91811da5 Mon Sep 17 00:00:00 2001 From: kc611 Date: Thu, 29 Jun 2023 18:25:35 +0530 Subject: [PATCH 05/22] Fixed no head detected test failure --- numba_rvsdg/tests/test_scfg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/numba_rvsdg/tests/test_scfg.py b/numba_rvsdg/tests/test_scfg.py index c766926f..09ba383f 100644 --- a/numba_rvsdg/tests/test_scfg.py +++ b/numba_rvsdg/tests/test_scfg.py @@ -55,7 +55,7 @@ def test_yaml_conversion(self): '0': ['1', '2'] '1': ['5'] '2': ['1', '5'] - '3': ['0'] + '3': ['1'] '4': [] '5': ['3', '4'] backedges: From 1f5d955fb804b693f210fd3e7effbc4251026877 Mon Sep 17 00:00:00 2001 From: kc611 Date: Sat, 1 Jul 2023 11:07:44 +0530 Subject: [PATCH 06/22] Added block type identification capability to from_dict and from_yaml --- .../core/datastructures/block_names.py | 3 +- numba_rvsdg/core/datastructures/scfg.py | 115 +++++++++--------- numba_rvsdg/tests/test_fig3.py | 9 +- 3 files changed, 68 insertions(+), 59 deletions(-) diff --git a/numba_rvsdg/core/datastructures/block_names.py b/numba_rvsdg/core/datastructures/block_names.py index 030e924b..7226b7d5 100644 --- a/numba_rvsdg/core/datastructures/block_names.py +++ b/numba_rvsdg/core/datastructures/block_names.py @@ -14,7 +14,7 @@ REGION = "region" -all_block_names = { +block_types = { BASIC, PYTHON_BYTECODE, SYNTH_HEAD, @@ -24,6 +24,7 @@ SYNTH_ASSIGN, SYNTH_RETURN, SYNTH_EXIT_LATCH, + SYNTH_EXIT_BRANCH, SYNTH_FILL, REGION, } diff --git a/numba_rvsdg/core/datastructures/scfg.py b/numba_rvsdg/core/datastructures/scfg.py index af39e65d..0a15fda6 100644 --- a/numba_rvsdg/core/datastructures/scfg.py +++ b/numba_rvsdg/core/datastructures/scfg.py @@ -17,7 +17,7 @@ RegionBlock, block_type_names, ) -from numba_rvsdg.core.datastructures import block_names +from numba_rvsdg.core.datastructures.block_names import block_types @dataclass(frozen=True) @@ -605,7 +605,7 @@ def insert_block_and_control_blocks( # and the newly created block for s in set(jt).intersection(successors): synth_assign = self.name_gen.new_block_name( - block_names.SYNTH_ASSIGN + block_types.SYNTH_ASSIGN ) variable_assignment = {} variable_assignment[branch_variable] = branch_variable_value @@ -653,7 +653,7 @@ def join_returns(self): # close if more than one is found if len(return_nodes) > 1: return_solo_name = self.name_gen.new_block_name( - block_names.SYNTH_RETURN + block_types.SYNTH_RETURN ) self.insert_SyntheticReturn( return_solo_name, return_nodes, tuple() @@ -686,7 +686,7 @@ def join_tails_and_exits(self, tails: Set[str], exits: Set[str]): # join only exits solo_tail_name = next(iter(tails)) solo_exit_name = self.name_gen.new_block_name( - block_names.SYNTH_EXIT + block_types.SYNTH_EXIT ) self.insert_SyntheticExit(solo_exit_name, tails, exits) return solo_tail_name, solo_exit_name @@ -694,7 +694,7 @@ def join_tails_and_exits(self, tails: Set[str], exits: Set[str]): if len(tails) >= 2 and len(exits) == 1: # join only tails solo_tail_name = self.name_gen.new_block_name( - block_names.SYNTH_TAIL + block_types.SYNTH_TAIL ) solo_exit_name = next(iter(exits)) self.insert_SyntheticTail(solo_tail_name, tails, exits) @@ -703,10 +703,10 @@ def join_tails_and_exits(self, tails: Set[str], exits: Set[str]): if len(tails) >= 2 and len(exits) >= 2: # join both tails and exits solo_tail_name = self.name_gen.new_block_name( - block_names.SYNTH_TAIL + block_types.SYNTH_TAIL ) solo_exit_name = self.name_gen.new_block_name( - block_names.SYNTH_EXIT + block_types.SYNTH_EXIT ) self.insert_SyntheticTail(solo_tail_name, tails, exits) self.insert_SyntheticExit(solo_exit_name, {solo_tail_name}, exits) @@ -731,7 +731,7 @@ def bcmap_from_bytecode(bc: dis.Bytecode): return {inst.offset: inst for inst in bc} @staticmethod - def from_yaml(yaml_string): + def from_yaml(yaml_string: str, absolute_names: bool = False): """Static method that creates an SCFG object from a YAML representation. @@ -754,11 +754,11 @@ def from_yaml(yaml_string): representation/unique name IDs in the SCFG. """ data = yaml.safe_load(yaml_string) - scfg, block_dict = SCFG.from_dict(data) + scfg, block_dict = SCFG.from_dict(data, absolute_names) return scfg, block_dict @staticmethod - def from_dict(graph_dict: dict): + def from_dict(graph_dict: dict, absolute_names: bool = False): """Static method that creates an SCFG object from a dictionary representation. @@ -790,11 +790,21 @@ def from_dict(graph_dict: dict): backedges = graph_dict["backedges"] for key, block in blocks.items(): - assert block["type"] in block_names.all_block_names - block_ref_dict[key] = name_gen.new_block_name(block["type"]) + assert block["type"] in block_types + if absolute_names: + if block["type"] == "region": + block_ref_dict[key] = name_gen.new_region_name( + block["kind"] + ) + else: + block_ref_dict[key] = name_gen.new_block_name( + block["type"] + ) + else: + block_ref_dict[key] = key for ref, block_name in block_ref_dict.items(): - name = block_name + block_info = blocks[ref] block_edges = tuple(block_ref_dict[idx] for idx in edges[ref]) if backedges and backedges.get(ref): block_backedges = tuple( @@ -802,12 +812,13 @@ def from_dict(graph_dict: dict): ) else: block_backedges = () - block = BasicBlock( - name=name, + block_class = block_type_names[block_info.pop("type")] + block = block_class( + name=block_name, backedges=block_backedges, _jump_targets=block_edges, ) - scfg_graph[name] = block + scfg_graph[block_name] = block scfg = SCFG(scfg_graph, name_gen=name_gen) return scfg, block_ref_dict @@ -827,50 +838,28 @@ def to_yaml(self): # Convert to yaml yaml_string = """""" - blocks = {} - edges = {} - backedges = {} + graph_dict = self.to_dict() - # This does a dictionary reverse lookup, to determine the key for a given - # value. - def reverse_lookup(value): - for k, v in block_type_names.items(): - if v == value: - return k - else: - raise TypeError("Block type not found.") - - for key, value in self: - block_type = reverse_lookup(type(value)) - blocks[key] = {"type": block_type} - if block_type == "region": - blocks[key]["kind"] = value.kind - blocks[key]["contains"] = [ - idx.name for idx in value.subregion.graph.values() - ] - edges[key] = [i for i in value._jump_targets] - backedges[key] = [i for i in value.backedges] + blocks = graph_dict["blocks"] + edges = graph_dict["edges"] + backedges = graph_dict["backedges"] yaml_string += "\nblocks:" - for _block in blocks.keys(): + for _block in sorted(blocks.keys()): yaml_string += f""" {_block}:""" - block_type = blocks[_block]["type"] - yaml_string += f""" - type: {block_type}""" - if block_type == "region": - yaml_string += f""" - kind: {blocks[_block]["kind"]}""" + block_dict = blocks[_block] + for key, value in block_dict.items(): yaml_string += f""" - contains: {blocks[_block]["contains"]}""" + {key}: {value}""" yaml_string += "\nedges:" - for _block in blocks.keys(): + for _block in sorted(blocks.keys()): yaml_string += f""" {_block}: {edges[_block]}""" yaml_string += "\nbackedges:" - for _block in blocks.keys(): + for _block in sorted(blocks.keys()): if backedges[_block]: yaml_string += f""" {_block}: {backedges[_block]}""" @@ -889,8 +878,6 @@ def to_dict(self): graph_dict: Dict[Dict[...]] A dictionary representing the SCFG. """ - scfg_graph = self.graph - blocks = {} edges = {} backedges = {} @@ -902,16 +889,32 @@ def reverse_lookup(value): else: raise TypeError("Block type not found.") - for key, value in scfg_graph.items(): + for key, value in self: block_type = reverse_lookup(type(value)) blocks[key] = {"type": block_type} if block_type == "region": blocks[key]["kind"] = value.kind - blocks[key]["contains"] = [ - idx.name for idx in value.subregion.graph.values() - ] - edges[key] = [i for i in value._jump_targets] - backedges[key] = [i for i in value.backedges] + blocks[key]["contains"] = sorted( + [idx.name for idx in value.subregion.graph.values()] + ) + blocks[key]["header"] = value.header + blocks[key]["exiting"] = value.exiting + blocks[key]["parent_region"] = value.parent_region.name + elif block_type in [ + block_types.SYNTH_BRANCH, + block_types.SYNTH_HEAD, + block_types.SYNTH_EXIT_LATCH, + block_types.SYNTH_EXIT_BRANCH, + ]: + blocks[key]["branch_value_table"] = value.branch_value_table + blocks[key]["variable"] = value.variable + elif block_type in [block_types.SYNTH_ASSIGN]: + blocks[key]["variable_assignment"] = value.variable_assignment + elif block_type in [block_types.PYTHON_BYTECODE]: + blocks[key]["begin"] = value.begin + blocks[key]["end"] = value.end + edges[key] = sorted([i for i in value._jump_targets]) + backedges[key] = sorted([i for i in value.backedges]) graph_dict = {"blocks": blocks, "edges": edges, "backedges": backedges} diff --git a/numba_rvsdg/tests/test_fig3.py b/numba_rvsdg/tests/test_fig3.py index 5d4eb513..d7c36547 100644 --- a/numba_rvsdg/tests/test_fig3.py +++ b/numba_rvsdg/tests/test_fig3.py @@ -1,6 +1,7 @@ # Figure 3 of the paper from numba_rvsdg.core.datastructures.byte_flow import ByteFlow from numba_rvsdg.core.datastructures.flow_info import FlowInfo +from numba_rvsdg.core.datastructures.scfg import SCFG from numba_rvsdg.rendering.rendering import render_flow # import logging @@ -37,8 +38,12 @@ def test_fig3(): # Run this function to print YAML for the given function f = make_flow() f = f.restructure() - print(f.scfg.to_yaml()) if __name__ == "__main__": - render_flow(make_flow()) + f = make_flow() + render_flow(f) + f = f.restructure() + graph_yaml = f.scfg.to_yaml() + print(graph_yaml) + x = SCFG.from_yaml(graph_yaml) From ac2802e2a8352838d4edb55e8fdc4e3c6b9331bd Mon Sep 17 00:00:00 2001 From: kc611 Date: Mon, 3 Jul 2023 23:40:47 +0530 Subject: [PATCH 07/22] Added yaml based block building capabilities to SCFG --- numba_rvsdg/core/datastructures/scfg.py | 180 ++++++++++++++++-------- 1 file changed, 122 insertions(+), 58 deletions(-) diff --git a/numba_rvsdg/core/datastructures/scfg.py b/numba_rvsdg/core/datastructures/scfg.py index 0a15fda6..8516aeeb 100644 --- a/numba_rvsdg/core/datastructures/scfg.py +++ b/numba_rvsdg/core/datastructures/scfg.py @@ -14,10 +14,22 @@ SyntheticTail, SyntheticReturn, SyntheticFill, + PythonBytecodeBlock, RegionBlock, block_type_names, ) -from numba_rvsdg.core.datastructures.block_names import block_types +from numba_rvsdg.core.datastructures.block_names import ( + block_types, + PYTHON_BYTECODE, + SYNTH_HEAD, + SYNTH_BRANCH, + SYNTH_TAIL, + SYNTH_EXIT, + SYNTH_ASSIGN, + SYNTH_RETURN, + SYNTH_EXIT_LATCH, + SYNTH_EXIT_BRANCH, +) @dataclass(frozen=True) @@ -604,9 +616,7 @@ def insert_block_and_control_blocks( # predecessors to a successor and insert it between the predecessor # and the newly created block for s in set(jt).intersection(successors): - synth_assign = self.name_gen.new_block_name( - block_types.SYNTH_ASSIGN - ) + synth_assign = self.name_gen.new_block_name(SYNTH_ASSIGN) variable_assignment = {} variable_assignment[branch_variable] = branch_variable_value synth_assign_block = SyntheticAssignment( @@ -652,9 +662,7 @@ def join_returns(self): ] # close if more than one is found if len(return_nodes) > 1: - return_solo_name = self.name_gen.new_block_name( - block_types.SYNTH_RETURN - ) + return_solo_name = self.name_gen.new_block_name(SYNTH_RETURN) self.insert_SyntheticReturn( return_solo_name, return_nodes, tuple() ) @@ -685,29 +693,21 @@ def join_tails_and_exits(self, tails: Set[str], exits: Set[str]): if len(tails) == 1 and len(exits) == 2: # join only exits solo_tail_name = next(iter(tails)) - solo_exit_name = self.name_gen.new_block_name( - block_types.SYNTH_EXIT - ) + solo_exit_name = self.name_gen.new_block_name(SYNTH_EXIT) self.insert_SyntheticExit(solo_exit_name, tails, exits) return solo_tail_name, solo_exit_name if len(tails) >= 2 and len(exits) == 1: # join only tails - solo_tail_name = self.name_gen.new_block_name( - block_types.SYNTH_TAIL - ) + solo_tail_name = self.name_gen.new_block_name(SYNTH_TAIL) solo_exit_name = next(iter(exits)) self.insert_SyntheticTail(solo_tail_name, tails, exits) return solo_tail_name, solo_exit_name if len(tails) >= 2 and len(exits) >= 2: # join both tails and exits - solo_tail_name = self.name_gen.new_block_name( - block_types.SYNTH_TAIL - ) - solo_exit_name = self.name_gen.new_block_name( - block_types.SYNTH_EXIT - ) + solo_tail_name = self.name_gen.new_block_name(SYNTH_TAIL) + solo_exit_name = self.name_gen.new_block_name(SYNTH_EXIT) self.insert_SyntheticTail(solo_tail_name, tails, exits) self.insert_SyntheticExit(solo_exit_name, {solo_tail_name}, exits) return solo_tail_name, solo_exit_name @@ -731,7 +731,7 @@ def bcmap_from_bytecode(bc: dis.Bytecode): return {inst.offset: inst for inst in bc} @staticmethod - def from_yaml(yaml_string: str, absolute_names: bool = False): + def from_yaml(yaml_string: str): """Static method that creates an SCFG object from a YAML representation. @@ -754,11 +754,11 @@ def from_yaml(yaml_string: str, absolute_names: bool = False): representation/unique name IDs in the SCFG. """ data = yaml.safe_load(yaml_string) - scfg, block_dict = SCFG.from_dict(data, absolute_names) + scfg, block_dict = SCFG.from_dict(data) return scfg, block_dict @staticmethod - def from_dict(graph_dict: dict, absolute_names: bool = False): + def from_dict(graph_dict: dict): """Static method that creates an SCFG object from a dictionary representation. @@ -781,45 +781,109 @@ def from_dict(graph_dict: dict, absolute_names: bool = False): Dictionary of block names in YAML string corresponding to their representation/unique name IDs in the SCFG. """ - scfg_graph = {} name_gen = NameGenerator() block_ref_dict = {} blocks = graph_dict["blocks"] edges = graph_dict["edges"] backedges = graph_dict["backedges"] + if backedges is None: + backedges = {} for key, block in blocks.items(): assert block["type"] in block_types - if absolute_names: - if block["type"] == "region": - block_ref_dict[key] = name_gen.new_region_name( - block["kind"] + block_ref_dict[key] = key + + # Find head of the graph, i.e. node which isn't in anyones contains + # and no edges point towards it (backedges are allowed) + heads = set(blocks.keys()) + for block_name, block_data in blocks.items(): + if block_data.get("contains"): + heads.difference_update(block_data["contains"]) + jump_targets = set(edges[block_name]) + if backedges.get(block_name): + jump_targets.difference_update(set(backedges[block_name])) + heads.difference_update(jump_targets) + assert len(heads) > 0 + + seen = set() + + def make_scfg(curr_heads: set, exiting: str = None): + scfg_graph = {} + queue = curr_heads + while queue: + current_name = queue.pop() + if current_name in seen: + continue + seen.add(current_name) + + block_info = blocks[current_name] + block_edges = tuple( + block_ref_dict[idx] for idx in edges[current_name] + ) + if backedges and backedges.get(current_name): + block_backedges = tuple( + block_ref_dict[idx] for idx in backedges[current_name] ) else: - block_ref_dict[key] = name_gen.new_block_name( - block["type"] + block_backedges = () + block_type = block_info.get("type") + block_class = block_type_names[block_type] + if block_type == "region": + scfg = make_scfg( + {block_info["header"]}, block_info["exiting"] ) - else: - block_ref_dict[key] = key - - for ref, block_name in block_ref_dict.items(): - block_info = blocks[ref] - block_edges = tuple(block_ref_dict[idx] for idx in edges[ref]) - if backedges and backedges.get(ref): - block_backedges = tuple( - block_ref_dict[idx] for idx in backedges[ref] - ) - else: - block_backedges = () - block_class = block_type_names[block_info.pop("type")] - block = block_class( - name=block_name, - backedges=block_backedges, - _jump_targets=block_edges, - ) - scfg_graph[block_name] = block - scfg = SCFG(scfg_graph, name_gen=name_gen) + block = RegionBlock( + name=current_name, + _jump_targets=block_edges, + backedges=block_backedges, + kind=block_info["kind"], + header=block_info["header"], + exiting=block_info["exiting"], + subregion=scfg, + ) + elif block_type in [ + SYNTH_BRANCH, + SYNTH_HEAD, + SYNTH_EXIT_LATCH, + SYNTH_EXIT_BRANCH, + ]: + block = block_class( + name=current_name, + backedges=block_backedges, + _jump_targets=block_edges, + branch_value_table=block_info["branch_value_table"], + variable=block_info["variable"], + ) + elif block_type in [SYNTH_ASSIGN]: + block = SyntheticAssignment( + name=current_name, + _jump_targets=block_edges, + backedges=block_backedges, + variable_assignment=block_info["variable_assignment"], + ) + elif block_type in [PYTHON_BYTECODE]: + block = PythonBytecodeBlock( + name=current_name, + _jump_targets=block_edges, + backedges=block_backedges, + begin=block_info["begin"], + end=block_info["end"], + ) + else: + block = block_class( + name=current_name, + backedges=block_backedges, + _jump_targets=block_edges, + ) + scfg_graph[current_name] = block + if current_name != exiting: + queue.update(edges[current_name]) + + scfg = SCFG(scfg_graph, name_gen=name_gen) + return scfg + + scfg = make_scfg(heads) return scfg, block_ref_dict def to_yaml(self): @@ -847,7 +911,7 @@ def to_yaml(self): yaml_string += "\nblocks:" for _block in sorted(blocks.keys()): yaml_string += f""" - {_block}:""" + '{_block}':""" block_dict = blocks[_block] for key, value in block_dict.items(): yaml_string += f""" @@ -856,13 +920,13 @@ def to_yaml(self): yaml_string += "\nedges:" for _block in sorted(blocks.keys()): yaml_string += f""" - {_block}: {edges[_block]}""" + '{_block}': {edges[_block]}""" yaml_string += "\nbackedges:" for _block in sorted(blocks.keys()): if backedges[_block]: yaml_string += f""" - {_block}: {backedges[_block]}""" + '{_block}': {backedges[_block]}""" return yaml_string def to_dict(self): @@ -901,16 +965,16 @@ def reverse_lookup(value): blocks[key]["exiting"] = value.exiting blocks[key]["parent_region"] = value.parent_region.name elif block_type in [ - block_types.SYNTH_BRANCH, - block_types.SYNTH_HEAD, - block_types.SYNTH_EXIT_LATCH, - block_types.SYNTH_EXIT_BRANCH, + SYNTH_BRANCH, + SYNTH_HEAD, + SYNTH_EXIT_LATCH, + SYNTH_EXIT_BRANCH, ]: blocks[key]["branch_value_table"] = value.branch_value_table blocks[key]["variable"] = value.variable - elif block_type in [block_types.SYNTH_ASSIGN]: + elif block_type in [SYNTH_ASSIGN]: blocks[key]["variable_assignment"] = value.variable_assignment - elif block_type in [block_types.PYTHON_BYTECODE]: + elif block_type in [PYTHON_BYTECODE]: blocks[key]["begin"] = value.begin blocks[key]["end"] = value.end edges[key] = sorted([i for i in value._jump_targets]) From 7a3b4c2dc622ac74afc273bdfcdc20b73a8643a1 Mon Sep 17 00:00:00 2001 From: kc611 Date: Mon, 3 Jul 2023 23:41:15 +0530 Subject: [PATCH 08/22] Automated Bahmann paper figure tests --- numba_rvsdg/tests/test_fig3.py | 49 --- numba_rvsdg/tests/test_fig4.py | 41 --- numba_rvsdg/tests/test_figures.py | 479 ++++++++++++++++++++++++++++++ 3 files changed, 479 insertions(+), 90 deletions(-) delete mode 100644 numba_rvsdg/tests/test_fig3.py delete mode 100644 numba_rvsdg/tests/test_fig4.py create mode 100644 numba_rvsdg/tests/test_figures.py diff --git a/numba_rvsdg/tests/test_fig3.py b/numba_rvsdg/tests/test_fig3.py deleted file mode 100644 index d7c36547..00000000 --- a/numba_rvsdg/tests/test_fig3.py +++ /dev/null @@ -1,49 +0,0 @@ -# Figure 3 of the paper -from numba_rvsdg.core.datastructures.byte_flow import ByteFlow -from numba_rvsdg.core.datastructures.flow_info import FlowInfo -from numba_rvsdg.core.datastructures.scfg import SCFG -from numba_rvsdg.rendering.rendering import render_flow - -# import logging -# logging.basicConfig(level=logging.DEBUG) - - -def make_flow(): - # flowinfo = FlowInfo() - import dis - - # fake bytecode just good enough for FlowInfo - bc = [ - dis.Instruction("OP", 1, None, None, "", 0, None, False), - dis.Instruction("POP_JUMP_IF_TRUE", 2, None, 12, "", 2, None, False), - # label 4 - dis.Instruction("OP", 1, None, None, "", 4, None, False), - dis.Instruction("POP_JUMP_IF_TRUE", 2, None, 12, "", 6, None, False), - dis.Instruction("OP", 1, None, None, "", 8, None, False), - dis.Instruction("JUMP_ABSOLUTE", 2, None, 20, "", 10, None, False), - # label 12 - dis.Instruction("OP", 1, None, None, "", 12, None, False), - dis.Instruction("POP_JUMP_IF_TRUE", 2, None, 4, "", 14, None, False), - dis.Instruction("OP", 1, None, None, "", 16, None, False), - dis.Instruction("JUMP_ABSOLUTE", 2, None, 20, "", 18, None, False), - # label 20 - dis.Instruction("RETURN_VALUE", 1, None, None, "", 20, None, False), - ] - flow = FlowInfo.from_bytecode(bc) - scfg = flow.build_basicblocks() - return ByteFlow(bc=bc, scfg=scfg) - - -def test_fig3(): - # Run this function to print YAML for the given function - f = make_flow() - f = f.restructure() - - -if __name__ == "__main__": - f = make_flow() - render_flow(f) - f = f.restructure() - graph_yaml = f.scfg.to_yaml() - print(graph_yaml) - x = SCFG.from_yaml(graph_yaml) diff --git a/numba_rvsdg/tests/test_fig4.py b/numba_rvsdg/tests/test_fig4.py deleted file mode 100644 index 7d9a046e..00000000 --- a/numba_rvsdg/tests/test_fig4.py +++ /dev/null @@ -1,41 +0,0 @@ -# Figure 3 of the paper -from numba_rvsdg.core.datastructures.byte_flow import ByteFlow -from numba_rvsdg.core.datastructures.flow_info import FlowInfo -from numba_rvsdg.rendering.rendering import render_flow - -# import logging -# logging.basicConfig(level=logging.DEBUG) - - -def make_flow(): - # flowinfo = FlowInfo() - import dis - - # fake bytecode just good enough for FlowInfo - bc = [ - dis.Instruction("OP", 1, None, None, "", 0, None, False), - dis.Instruction("POP_JUMP_IF_TRUE", 2, None, 14, "", 2, None, False), - # label 4 - dis.Instruction("OP", 1, None, None, "", 4, None, False), - dis.Instruction("POP_JUMP_IF_TRUE", 2, None, 12, "", 6, None, False), - dis.Instruction("OP", 1, None, None, "", 8, None, False), - dis.Instruction("JUMP_ABSOLUTE", 2, None, 18, "", 10, None, False), - # label 12 - dis.Instruction("OP", 1, None, None, "", 12, None, False), - dis.Instruction("OP", 2, None, 4, "", 14, None, False), - dis.Instruction("JUMP_ABSOLUTE", 2, None, 18, "", 16, None, False), - # label 18 - dis.Instruction("RETURN_VALUE", 1, None, None, "", 18, None, False), - ] - flow = FlowInfo.from_bytecode(bc) - scfg = flow.build_basicblocks() - return ByteFlow(bc=bc, scfg=scfg) - - -def test_fig4(): - f = make_flow() - f.restructure() - - -if __name__ == "__main__": - render_flow(make_flow()) diff --git a/numba_rvsdg/tests/test_figures.py b/numba_rvsdg/tests/test_figures.py new file mode 100644 index 00000000..82419f1d --- /dev/null +++ b/numba_rvsdg/tests/test_figures.py @@ -0,0 +1,479 @@ +from numba_rvsdg.core.datastructures.byte_flow import ByteFlow +from numba_rvsdg.core.datastructures.flow_info import FlowInfo +from numba_rvsdg.core.datastructures.scfg import SCFG +from numba_rvsdg.tests.test_utils import SCFGComparator +import dis + +fig_3_yaml = """ +blocks: + branch_region_0: + type: region + kind: branch + contains: ['synth_asign_block_0'] + header: synth_asign_block_0 + exiting: synth_asign_block_0 + parent_region: meta_region_0 + branch_region_1: + type: region + kind: branch + contains: ['synth_asign_block_1'] + header: synth_asign_block_1 + exiting: synth_asign_block_1 + parent_region: meta_region_0 + branch_region_2: + type: region + kind: branch + contains: ['python_bytecode_block_2'] + header: python_bytecode_block_2 + exiting: python_bytecode_block_2 + parent_region: tail_region_0 + branch_region_3: + type: region + kind: branch + contains: ['python_bytecode_block_4'] + header: python_bytecode_block_4 + exiting: python_bytecode_block_4 + parent_region: tail_region_0 + branch_region_4: + type: region + kind: branch + contains: ['branch_region_6', 'branch_region_7', 'head_region_3', 'tail_region_3'] # noqa + header: head_region_3 + exiting: tail_region_3 + parent_region: loop_region_0 + branch_region_5: + type: region + kind: branch + contains: ['branch_region_8', 'branch_region_9', 'head_region_4', 'tail_region_4'] # noqa + header: head_region_4 + exiting: tail_region_4 + parent_region: loop_region_0 + branch_region_6: + type: region + kind: branch + contains: ['synth_asign_block_2'] + header: synth_asign_block_2 + exiting: synth_asign_block_2 + parent_region: branch_region_4 + branch_region_7: + type: region + kind: branch + contains: ['synth_asign_block_3'] + header: synth_asign_block_3 + exiting: synth_asign_block_3 + parent_region: branch_region_4 + branch_region_8: + type: region + kind: branch + contains: ['synth_asign_block_4'] + header: synth_asign_block_4 + exiting: synth_asign_block_4 + parent_region: branch_region_5 + branch_region_9: + type: region + kind: branch + contains: ['synth_asign_block_5'] + header: synth_asign_block_5 + exiting: synth_asign_block_5 + parent_region: branch_region_5 + head_region_0: + type: region + kind: head + contains: ['python_bytecode_block_0'] + header: python_bytecode_block_0 + exiting: python_bytecode_block_0 + parent_region: meta_region_0 + head_region_1: + type: region + kind: head + contains: ['loop_region_0', 'synth_exit_block_0'] + header: loop_region_0 + exiting: synth_exit_block_0 + parent_region: tail_region_0 + head_region_2: + type: region + kind: head + contains: ['synth_head_block_0'] + header: synth_head_block_0 + exiting: synth_head_block_0 + parent_region: loop_region_0 + head_region_3: + type: region + kind: head + contains: ['python_bytecode_block_1'] + header: python_bytecode_block_1 + exiting: python_bytecode_block_1 + parent_region: branch_region_4 + head_region_4: + type: region + kind: head + contains: ['python_bytecode_block_3'] + header: python_bytecode_block_3 + exiting: python_bytecode_block_3 + parent_region: branch_region_5 + loop_region_0: + type: region + kind: loop + contains: ['branch_region_4', 'branch_region_5', 'head_region_2', 'tail_region_2'] + header: head_region_2 + exiting: tail_region_2 + parent_region: head_region_1 + python_bytecode_block_0: + type: python_bytecode + begin: 0 + end: 4 + python_bytecode_block_1: + type: python_bytecode + begin: 4 + end: 8 + python_bytecode_block_2: + type: python_bytecode + begin: 8 + end: 12 + python_bytecode_block_3: + type: python_bytecode + begin: 12 + end: 16 + python_bytecode_block_4: + type: python_bytecode + begin: 16 + end: 20 + python_bytecode_block_5: + type: python_bytecode + begin: 20 + end: 22 + synth_asign_block_0: + type: synth_asign + variable_assignment: {'control_var_0': 0} + synth_asign_block_1: + type: synth_asign + variable_assignment: {'control_var_0': 1} + synth_asign_block_2: + type: synth_asign + variable_assignment: {'control_var_0': 0, 'backedge_var_0': 1} + synth_asign_block_3: + type: synth_asign + variable_assignment: {'backedge_var_0': 0, 'control_var_0': 1} + synth_asign_block_4: + type: synth_asign + variable_assignment: {'control_var_0': 1, 'backedge_var_0': 1} + synth_asign_block_5: + type: synth_asign + variable_assignment: {'backedge_var_0': 0, 'control_var_0': 0} + synth_exit_block_0: + type: synth_exit_branch + branch_value_table: {0: 'branch_region_2', 1: 'branch_region_3'} + variable: control_var_0 + synth_exit_latch_block_0: + type: synth_exit_latch + branch_value_table: {1: 'synth_exit_block_0', 0: 'head_region_2'} + variable: backedge_var_0 + synth_head_block_0: + type: synth_head + branch_value_table: {0: 'branch_region_4', 1: 'branch_region_5'} + variable: control_var_0 + synth_tail_block_0: + type: synth_tail + synth_tail_block_1: + type: synth_tail + tail_region_0: + type: region + kind: tail + contains: ['branch_region_2', 'branch_region_3', 'head_region_1', 'tail_region_1'] + header: head_region_1 + exiting: tail_region_1 + parent_region: meta_region_0 + tail_region_1: + type: region + kind: tail + contains: ['python_bytecode_block_5'] + header: python_bytecode_block_5 + exiting: python_bytecode_block_5 + parent_region: tail_region_0 + tail_region_2: + type: region + kind: tail + contains: ['synth_exit_latch_block_0'] + header: synth_exit_latch_block_0 + exiting: synth_exit_latch_block_0 + parent_region: loop_region_0 + tail_region_3: + type: region + kind: tail + contains: ['synth_tail_block_0'] + header: synth_tail_block_0 + exiting: synth_tail_block_0 + parent_region: branch_region_4 + tail_region_4: + type: region + kind: tail + contains: ['synth_tail_block_1'] + header: synth_tail_block_1 + exiting: synth_tail_block_1 + parent_region: branch_region_5 +edges: + branch_region_0: ['tail_region_0'] + branch_region_1: ['tail_region_0'] + branch_region_2: ['tail_region_1'] + branch_region_3: ['tail_region_1'] + branch_region_4: ['tail_region_2'] + branch_region_5: ['tail_region_2'] + branch_region_6: ['tail_region_3'] + branch_region_7: ['tail_region_3'] + branch_region_8: ['tail_region_4'] + branch_region_9: ['tail_region_4'] + head_region_0: ['branch_region_0', 'branch_region_1'] + head_region_1: ['branch_region_2', 'branch_region_3'] + head_region_2: ['branch_region_4', 'branch_region_5'] + head_region_3: ['branch_region_6', 'branch_region_7'] + head_region_4: ['branch_region_8', 'branch_region_9'] + loop_region_0: ['synth_exit_block_0'] + python_bytecode_block_0: ['branch_region_0', 'branch_region_1'] + python_bytecode_block_1: ['branch_region_6', 'branch_region_7'] + python_bytecode_block_2: ['tail_region_1'] + python_bytecode_block_3: ['branch_region_8', 'branch_region_9'] + python_bytecode_block_4: ['tail_region_1'] + python_bytecode_block_5: [] + synth_asign_block_0: ['tail_region_0'] + synth_asign_block_1: ['tail_region_0'] + synth_asign_block_2: ['tail_region_3'] + synth_asign_block_3: ['tail_region_3'] + synth_asign_block_4: ['tail_region_4'] + synth_asign_block_5: ['tail_region_4'] + synth_exit_block_0: ['branch_region_2', 'branch_region_3'] + synth_exit_latch_block_0: ['head_region_2', 'synth_exit_block_0'] + synth_head_block_0: ['branch_region_4', 'branch_region_5'] + synth_tail_block_0: ['tail_region_2'] + synth_tail_block_1: ['tail_region_2'] + tail_region_0: [] + tail_region_1: [] + tail_region_2: ['synth_exit_block_0'] + tail_region_3: ['tail_region_2'] + tail_region_4: ['tail_region_2'] +backedges: + synth_exit_latch_block_0: ['head_region_2']""" + +fig_4_yaml = """ +blocks: + branch_region_0: + type: region + kind: branch + contains: ['branch_region_2', 'branch_region_3', 'head_region_1', 'tail_region_1'] # noqa + header: head_region_1 + exiting: tail_region_1 + parent_region: meta_region_0 + branch_region_1: + type: region + kind: branch + contains: ['synth_asign_block_0'] + header: synth_asign_block_0 + exiting: synth_asign_block_0 + parent_region: meta_region_0 + branch_region_2: + type: region + kind: branch + contains: ['python_bytecode_block_2', 'synth_asign_block_1'] + header: python_bytecode_block_2 + exiting: synth_asign_block_1 + parent_region: branch_region_0 + branch_region_3: + type: region + kind: branch + contains: ['python_bytecode_block_3', 'synth_asign_block_2'] + header: python_bytecode_block_3 + exiting: synth_asign_block_2 + parent_region: branch_region_0 + branch_region_4: + type: region + kind: branch + contains: ['python_bytecode_block_4'] + header: python_bytecode_block_4 + exiting: python_bytecode_block_4 + parent_region: tail_region_0 + branch_region_5: + type: region + kind: branch + contains: ['synth_fill_block_0'] + header: synth_fill_block_0 + exiting: synth_fill_block_0 + parent_region: tail_region_0 + head_region_0: + type: region + kind: head + contains: ['python_bytecode_block_0'] + header: python_bytecode_block_0 + exiting: python_bytecode_block_0 + parent_region: meta_region_0 + head_region_1: + type: region + kind: head + contains: ['python_bytecode_block_1'] + header: python_bytecode_block_1 + exiting: python_bytecode_block_1 + parent_region: branch_region_0 + head_region_2: + type: region + kind: head + contains: ['synth_head_block_0'] + header: synth_head_block_0 + exiting: synth_head_block_0 + parent_region: tail_region_0 + python_bytecode_block_0: + type: python_bytecode + begin: 0 + end: 4 + python_bytecode_block_1: + type: python_bytecode + begin: 4 + end: 8 + python_bytecode_block_2: + type: python_bytecode + begin: 8 + end: 12 + python_bytecode_block_3: + type: python_bytecode + begin: 12 + end: 14 + python_bytecode_block_4: + type: python_bytecode + begin: 14 + end: 18 + python_bytecode_block_5: + type: python_bytecode + begin: 18 + end: 20 + synth_asign_block_0: + type: synth_asign + variable_assignment: {'control_var_0': 0} + synth_asign_block_1: + type: synth_asign + variable_assignment: {'control_var_0': 1} + synth_asign_block_2: + type: synth_asign + variable_assignment: {'control_var_0': 2} + synth_fill_block_0: + type: synth_fill + synth_head_block_0: + type: synth_head + branch_value_table: {0: 'branch_region_4', 2: 'branch_region_4', 1: 'branch_region_5'} # noqa + variable: control_var_0 + synth_tail_block_0: + type: synth_tail + tail_region_0: + type: region + kind: tail + contains: ['branch_region_4', 'branch_region_5', 'head_region_2', 'tail_region_2'] # noqa + header: head_region_2 + exiting: tail_region_2 + parent_region: meta_region_0 + tail_region_1: + type: region + kind: tail + contains: ['synth_tail_block_0'] + header: synth_tail_block_0 + exiting: synth_tail_block_0 + parent_region: branch_region_0 + tail_region_2: + type: region + kind: tail + contains: ['python_bytecode_block_5'] + header: python_bytecode_block_5 + exiting: python_bytecode_block_5 + parent_region: tail_region_0 +edges: + branch_region_0: ['tail_region_0'] + branch_region_1: ['tail_region_0'] + branch_region_2: ['tail_region_1'] + branch_region_3: ['tail_region_1'] + branch_region_4: ['tail_region_2'] + branch_region_5: ['tail_region_2'] + head_region_0: ['branch_region_0', 'branch_region_1'] + head_region_1: ['branch_region_2', 'branch_region_3'] + head_region_2: ['branch_region_4', 'branch_region_5'] + python_bytecode_block_0: ['branch_region_0', 'branch_region_1'] + python_bytecode_block_1: ['branch_region_2', 'branch_region_3'] + python_bytecode_block_2: ['synth_asign_block_1'] + python_bytecode_block_3: ['synth_asign_block_2'] + python_bytecode_block_4: ['tail_region_2'] + python_bytecode_block_5: [] + synth_asign_block_0: ['tail_region_0'] + synth_asign_block_1: ['tail_region_1'] + synth_asign_block_2: ['tail_region_1'] + synth_fill_block_0: ['tail_region_2'] + synth_head_block_0: ['branch_region_4', 'branch_region_5'] + synth_tail_block_0: ['tail_region_0'] + tail_region_0: [] + tail_region_1: ['tail_region_0'] + tail_region_2: [] +backedges:""" + + +class TestBahmannFigures(SCFGComparator): + def test_figure_3(self): + # Figure 3 of the paper + + # fake bytecode just good enough for FlowInfo + bc = [ + dis.Instruction("OP", 1, None, None, "", 0, None, False), + dis.Instruction( + "POP_JUMP_IF_TRUE", 2, None, 12, "", 2, None, False + ), + # label 4 + dis.Instruction("OP", 1, None, None, "", 4, None, False), + dis.Instruction( + "POP_JUMP_IF_TRUE", 2, None, 12, "", 6, None, False + ), + dis.Instruction("OP", 1, None, None, "", 8, None, False), + dis.Instruction("JUMP_ABSOLUTE", 2, None, 20, "", 10, None, False), + # label 12 + dis.Instruction("OP", 1, None, None, "", 12, None, False), + dis.Instruction( + "POP_JUMP_IF_TRUE", 2, None, 4, "", 14, None, False + ), + dis.Instruction("OP", 1, None, None, "", 16, None, False), + dis.Instruction("JUMP_ABSOLUTE", 2, None, 20, "", 18, None, False), + # label 20 + dis.Instruction( + "RETURN_VALUE", 1, None, None, "", 20, None, False + ), + ] + flow = FlowInfo.from_bytecode(bc) + scfg = flow.build_basicblocks() + byteflow = ByteFlow(bc=bc, scfg=scfg) + byteflow = byteflow.restructure() + + x, _ = SCFG.from_yaml(fig_3_yaml) + self.assertSCFGEqual(x, byteflow.scfg) + + def test_figure_4(self): + # Figure 4 of the paper + + # fake bytecode just good enough for FlowInfo + bc = [ + dis.Instruction("OP", 1, None, None, "", 0, None, False), + dis.Instruction( + "POP_JUMP_IF_TRUE", 2, None, 14, "", 2, None, False + ), + # label 4 + dis.Instruction("OP", 1, None, None, "", 4, None, False), + dis.Instruction( + "POP_JUMP_IF_TRUE", 2, None, 12, "", 6, None, False + ), + dis.Instruction("OP", 1, None, None, "", 8, None, False), + dis.Instruction("JUMP_ABSOLUTE", 2, None, 18, "", 10, None, False), + # label 12 + dis.Instruction("OP", 1, None, None, "", 12, None, False), + dis.Instruction("OP", 2, None, 4, "", 14, None, False), + dis.Instruction("JUMP_ABSOLUTE", 2, None, 18, "", 16, None, False), + # label 18 + dis.Instruction( + "RETURN_VALUE", 1, None, None, "", 18, None, False + ), + ] + flow = FlowInfo.from_bytecode(bc) + scfg = flow.build_basicblocks() + byteflow = ByteFlow(bc=bc, scfg=scfg) + byteflow = byteflow.restructure() + + x, _ = SCFG.from_yaml(fig_4_yaml) + self.assertSCFGEqual(x, byteflow.scfg) From e1097ad384705117ec5c85f4ef21dccacdd166a5 Mon Sep 17 00:00:00 2001 From: kc611 Date: Mon, 3 Jul 2023 23:41:29 +0530 Subject: [PATCH 09/22] Adapted test suite to changes --- numba_rvsdg/tests/test_scfg.py | 10 +++++----- numba_rvsdg/tests/test_transforms.py | 2 ++ numba_rvsdg/tests/test_utils.py | 15 +++++++++++++-- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/numba_rvsdg/tests/test_scfg.py b/numba_rvsdg/tests/test_scfg.py index 09ba383f..3d1d9e5a 100644 --- a/numba_rvsdg/tests/test_scfg.py +++ b/numba_rvsdg/tests/test_scfg.py @@ -123,7 +123,7 @@ def test_dict_conversion(self): "0": ["1", "2"], "1": ["5"], "2": ["1", "5"], - "3": ["0"], + "3": ["1"], "4": [], "5": ["3", "4"], }, @@ -166,13 +166,13 @@ def test_scfg_iter(self): scfg, _ = SCFG.from_yaml( """ blocks: - '0': + 'basic_block_0': type: basic - '1': + 'basic_block_1': type: basic edges: - '0': ['1'] - '1': [] + 'basic_block_0': ['basic_block_1'] + 'basic_block_1': [] backedges: """ ) diff --git a/numba_rvsdg/tests/test_transforms.py b/numba_rvsdg/tests/test_transforms.py index d42c2e0b..4b4ce585 100644 --- a/numba_rvsdg/tests/test_transforms.py +++ b/numba_rvsdg/tests/test_transforms.py @@ -208,6 +208,7 @@ def test_dual_predecessor_and_dual_successor_with_additional_arcs(self): '3': ['0'] '4': [] backedges: + '3': ['0'] """ original_scfg, block_dict = SCFG.from_yaml(original) expected = """ @@ -232,6 +233,7 @@ def test_dual_predecessor_and_dual_successor_with_additional_arcs(self): '4': [] '5': ['3', '4'] backedges: + '3': ['0'] """ expected_scfg, expected_block_dict = SCFG.from_yaml(expected) original_scfg.insert_block( diff --git a/numba_rvsdg/tests/test_utils.py b/numba_rvsdg/tests/test_utils.py index c1f228d2..524066d4 100644 --- a/numba_rvsdg/tests/test_utils.py +++ b/numba_rvsdg/tests/test_utils.py @@ -2,12 +2,12 @@ import yaml from numba_rvsdg.core.datastructures.scfg import SCFG -from numba_rvsdg.core.datastructures.basic_block import BasicBlock +from numba_rvsdg.core.datastructures.basic_block import BasicBlock, RegionBlock class SCFGComparator(TestCase): def assertSCFGEqual( - self, first_scfg: SCFG, second_scfg: SCFG, head_map=None + self, first_scfg: SCFG, second_scfg: SCFG, head_map=None, exiting=None ): if head_map: # If more than one head the corresponding map needs to be provided @@ -41,14 +41,25 @@ def assertSCFGEqual( assert len(node.jump_targets) == len(second_node.jump_targets) assert len(node.backedges) == len(second_node.backedges) + # If the given block is a egionBlock, then the underlying SCFGs + # for both regions must be equal + if isinstance(node, RegionBlock): + self.assertSCFGEqual( + node.subregion, second_node.subregion, exiting=node.exiting + ) + # Add the jump targets as corresponding nodes in block mapping # dictionary. Since order must be same we can simply add zip # functionality as the correspondence function for nodes for jt1, jt2 in zip(node.jump_targets, second_node.jump_targets): + if node.name == exiting: + continue block_mapping[jt1] = jt2 stack.append(jt1) for be1, be2 in zip(node.backedges, second_node.backedges): + if node.name == exiting: + continue block_mapping[be1] = be2 stack.append(be1) From fcdc9b90b49b8e4138a7c4a1db460ac2c3b8e602 Mon Sep 17 00:00:00 2001 From: esc Date: Wed, 5 Jul 2023 17:39:17 +0200 Subject: [PATCH 10/22] refactor YAML writing to use indent for readability As title --- numba_rvsdg/core/datastructures/scfg.py | 38 +++++++++++-------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/numba_rvsdg/core/datastructures/scfg.py b/numba_rvsdg/core/datastructures/scfg.py index 8516aeeb..15dd7739 100644 --- a/numba_rvsdg/core/datastructures/scfg.py +++ b/numba_rvsdg/core/datastructures/scfg.py @@ -1,5 +1,6 @@ import dis import yaml +from textwrap import indent from typing import Set, Tuple, Dict, List, Iterator from dataclasses import dataclass, field from collections import deque @@ -900,7 +901,7 @@ def to_yaml(self): A YAML string representing the SCFG. """ # Convert to yaml - yaml_string = """""" + yaml_string = ys = "" graph_dict = self.to_dict() @@ -908,26 +909,21 @@ def to_yaml(self): edges = graph_dict["edges"] backedges = graph_dict["backedges"] - yaml_string += "\nblocks:" - for _block in sorted(blocks.keys()): - yaml_string += f""" - '{_block}':""" - block_dict = blocks[_block] - for key, value in block_dict.items(): - yaml_string += f""" - {key}: {value}""" - - yaml_string += "\nedges:" - for _block in sorted(blocks.keys()): - yaml_string += f""" - '{_block}': {edges[_block]}""" - - yaml_string += "\nbackedges:" - for _block in sorted(blocks.keys()): - if backedges[_block]: - yaml_string += f""" - '{_block}': {backedges[_block]}""" - return yaml_string + ys += "\nblocks:\n" + for b in sorted(blocks): + ys += indent(f"'{b}':\n", " " * 8) + for k, v in blocks[b].items(): + ys += indent(f"{k}: {v}\n", " " * 12) + + ys += "\nedges:\n" + for b in sorted(blocks): + ys += indent(f"'{b}': {edges[b]}\n", " " * 8) + + ys += "\nbackedges:\n" + for b in sorted(blocks): + if backedges[b]: + ys += indent(f"'{b}': {backedges[b]}\n", " " * 8) + return ys def to_dict(self): """Converts the SCFG object to a dictionary representation. From 8cd3009b1883994a801aa1ec4364338f5165a1aa Mon Sep 17 00:00:00 2001 From: Kaustubh Date: Thu, 6 Jul 2023 14:47:26 +0530 Subject: [PATCH 11/22] Apply suggestions from code review Co-authored-by: esc --- numba_rvsdg/core/datastructures/scfg.py | 4 +--- numba_rvsdg/tests/test_utils.py | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/numba_rvsdg/core/datastructures/scfg.py b/numba_rvsdg/core/datastructures/scfg.py index 15dd7739..614ee234 100644 --- a/numba_rvsdg/core/datastructures/scfg.py +++ b/numba_rvsdg/core/datastructures/scfg.py @@ -938,9 +938,7 @@ def to_dict(self): graph_dict: Dict[Dict[...]] A dictionary representing the SCFG. """ - blocks = {} - edges = {} - backedges = {} + blocks, edges, backedges = {}, {}, {} def reverse_lookup(value): for k, v in block_type_names.items(): diff --git a/numba_rvsdg/tests/test_utils.py b/numba_rvsdg/tests/test_utils.py index 524066d4..b370d8cc 100644 --- a/numba_rvsdg/tests/test_utils.py +++ b/numba_rvsdg/tests/test_utils.py @@ -41,8 +41,8 @@ def assertSCFGEqual( assert len(node.jump_targets) == len(second_node.jump_targets) assert len(node.backedges) == len(second_node.backedges) - # If the given block is a egionBlock, then the underlying SCFGs - # for both regions must be equal + # If the given block is a RegionBlock, then the underlying SCFGs + # for both regions must be equal. if isinstance(node, RegionBlock): self.assertSCFGEqual( node.subregion, second_node.subregion, exiting=node.exiting From 462c716676d5961ed48dffa0502c7c4f2e8e7878 Mon Sep 17 00:00:00 2001 From: kc611 Date: Thu, 6 Jul 2023 15:13:56 +0530 Subject: [PATCH 12/22] Used isinstance instead of string comparison in to_dict method --- numba_rvsdg/core/datastructures/scfg.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/numba_rvsdg/core/datastructures/scfg.py b/numba_rvsdg/core/datastructures/scfg.py index 614ee234..2d29025b 100644 --- a/numba_rvsdg/core/datastructures/scfg.py +++ b/numba_rvsdg/core/datastructures/scfg.py @@ -17,6 +17,7 @@ SyntheticFill, PythonBytecodeBlock, RegionBlock, + SyntheticBranch, block_type_names, ) from numba_rvsdg.core.datastructures.block_names import ( @@ -901,7 +902,7 @@ def to_yaml(self): A YAML string representing the SCFG. """ # Convert to yaml - yaml_string = ys = "" + ys = "" graph_dict = self.to_dict() @@ -940,7 +941,7 @@ def to_dict(self): """ blocks, edges, backedges = {}, {}, {} - def reverse_lookup(value): + def reverse_lookup(value: type): for k, v in block_type_names.items(): if v == value: return k @@ -950,7 +951,7 @@ def reverse_lookup(value): for key, value in self: block_type = reverse_lookup(type(value)) blocks[key] = {"type": block_type} - if block_type == "region": + if isinstance(value, RegionBlock): blocks[key]["kind"] = value.kind blocks[key]["contains"] = sorted( [idx.name for idx in value.subregion.graph.values()] @@ -958,17 +959,12 @@ def reverse_lookup(value): blocks[key]["header"] = value.header blocks[key]["exiting"] = value.exiting blocks[key]["parent_region"] = value.parent_region.name - elif block_type in [ - SYNTH_BRANCH, - SYNTH_HEAD, - SYNTH_EXIT_LATCH, - SYNTH_EXIT_BRANCH, - ]: + elif isinstance(value, SyntheticBranch): blocks[key]["branch_value_table"] = value.branch_value_table blocks[key]["variable"] = value.variable - elif block_type in [SYNTH_ASSIGN]: + elif isinstance(value, SyntheticAssignment): blocks[key]["variable_assignment"] = value.variable_assignment - elif block_type in [PYTHON_BYTECODE]: + elif isinstance(value, PythonBytecodeBlock): blocks[key]["begin"] = value.begin blocks[key]["end"] = value.end edges[key] = sorted([i for i in value._jump_targets]) From e4dd79c0d74dc2363a5ffbea7410e1f69009e597 Mon Sep 17 00:00:00 2001 From: kc611 Date: Thu, 6 Jul 2023 15:30:51 +0530 Subject: [PATCH 13/22] Added equality assertions for synthetic assignments and synthetic branching properties --- numba_rvsdg/tests/test_utils.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/numba_rvsdg/tests/test_utils.py b/numba_rvsdg/tests/test_utils.py index b370d8cc..488a9713 100644 --- a/numba_rvsdg/tests/test_utils.py +++ b/numba_rvsdg/tests/test_utils.py @@ -2,7 +2,12 @@ import yaml from numba_rvsdg.core.datastructures.scfg import SCFG -from numba_rvsdg.core.datastructures.basic_block import BasicBlock, RegionBlock +from numba_rvsdg.core.datastructures.basic_block import ( + BasicBlock, + RegionBlock, + SyntheticBranch, + SyntheticAssignment, +) class SCFGComparator(TestCase): @@ -47,6 +52,15 @@ def assertSCFGEqual( self.assertSCFGEqual( node.subregion, second_node.subregion, exiting=node.exiting ) + elif isinstance(node, SyntheticAssignment): + assert ( + node.variable_assignment == second_node.variable_assignment + ) + elif isinstance(node, SyntheticBranch): + assert ( + node.branch_value_table == second_node.branch_value_table + ) + assert node.variable == second_node.variable # Add the jump targets as corresponding nodes in block mapping # dictionary. Since order must be same we can simply add zip From d7e907b6de2fd7232a044accd25f12eaf22eed59 Mon Sep 17 00:00:00 2001 From: kc611 Date: Fri, 7 Jul 2023 15:53:40 +0530 Subject: [PATCH 14/22] Changed jump targets and backedges replacement login in basic blocks --- .../core/datastructures/basic_block.py | 85 +++++++++---------- 1 file changed, 40 insertions(+), 45 deletions(-) diff --git a/numba_rvsdg/core/datastructures/basic_block.py b/numba_rvsdg/core/datastructures/basic_block.py index 393b1057..ab45bf93 100644 --- a/numba_rvsdg/core/datastructures/basic_block.py +++ b/numba_rvsdg/core/datastructures/basic_block.py @@ -1,6 +1,6 @@ import dis from typing import Tuple, Dict, List -from dataclasses import dataclass, replace +from dataclasses import dataclass, replace, field from numba_rvsdg.core.utils import _next_inst_offset from numba_rvsdg.core.datastructures import block_names @@ -20,15 +20,20 @@ class BasicBlock: _jump_targets: Tuple[str] Jump targets (branch destinations) for this block. - backedges: Tuple[str] - Backedges for this block. + backedges: Tuple[bool] + Indicates if the Jump target at the particular index + is a backedge or not. """ name: str _jump_targets: Tuple[str] = tuple() - backedges: Tuple[str] = tuple() + backedges: Tuple[bool] = field(init=False) + + def __post_init__(self): + backedges = tuple([False] * len(self._jump_targets)) + object.__setattr__(self, "backedges", backedges) @property def is_exiting(self) -> bool: @@ -70,32 +75,27 @@ def jump_targets(self) -> Tuple[str]: """ acc = [] - for j in self._jump_targets: - if j not in self.backedges: + for idx, j in enumerate(self._jump_targets): + if not self.backedges[idx]: acc.append(j) return tuple(acc) - def declare_backedge(self, target: str) -> "BasicBlock": + def declare_backedge(self, target: str): """Declare one of the jump targets as a backedge of this block. Parameters ---------- target: str The jump target that is to be declared as a backedge. - - Returns - ------- - basic_block: BasicBlock - The resulting block. - """ - if target in self.jump_targets: - assert not self.backedges - return replace(self, backedges=(target,)) - return self + assert target in self._jump_targets + idx = self._jump_targets.index(target) + current_backedges = list(self.backedges) + current_backedges[idx] = True + object.__setattr__(self, "backedges", tuple(current_backedges)) - def replace_jump_targets(self, jump_targets: Tuple) -> "BasicBlock": - """Replaces jump targets of this block by the given tuple. + def change_jump_targets(self, jump_targets: Tuple): + """Changes jump targets of this block by the given tuple. This method replaces the jump targets of the current BasicBlock. The provided jump targets must be in the same order as their @@ -109,34 +109,18 @@ def replace_jump_targets(self, jump_targets: Tuple) -> "BasicBlock": ---------- jump_targets: Tuple The new jump target tuple. Must be ordered. - - Returns - ------- - basic_block: BasicBlock - The resulting BasicBlock. - """ - return replace(self, _jump_targets=jump_targets) - - def replace_backedges(self, backedges: Tuple) -> "BasicBlock": - """Replaces back edges of this block by the given tuple. - - This method replaces the back edges of the current BasicBlock. - The provided back edges must be in the same order as their - intended original replacements. + is_backedge = {} + new_backedges = [] - Parameters - ---------- - backedges: Tuple - The new back edges tuple. Must be ordered. + for idx, i in enumerate(self.backedges): + is_backedge[self._jump_targets[idx]] = i - Returns - ------- - basic_block: BasicBlock - The resulting BasicBlock. + for i in jump_targets: + new_backedges.append(is_backedge.get(i, False)) - """ - return replace(self, backedges=backedges) + object.__setattr__(self, "_jump_targets", jump_targets) + object.__setattr__(self, "backedges", new_backedges) @dataclass(frozen=True) @@ -234,8 +218,8 @@ class SyntheticFill(SyntheticBlock): @dataclass(frozen=True) class SyntheticAssignment(SyntheticBlock): - """The SyntheticAssignment class represents a artificially added assignment block - in a structured control flow graph (SCFG). + """The SyntheticAssignment class represents a artificially added + assignment block in a structured control flow graph (SCFG). This block is responsible for giving variables their values, once the respective block is executed. @@ -386,6 +370,17 @@ def replace_exiting(self, new_exiting): """ object.__setattr__(self, "exiting", new_exiting) + def replace_parent(self, new_parent): + """This method performs a inplace replacement of the parent region + block. + + Parameters + ---------- + new_exiting: str + The new exiting block of the region represented by the RegionBlock. + """ + object.__setattr__(self, "parent", new_parent) + block_type_names = { block_names.BASIC: BasicBlock, From d28a326ab78fb133078c587d0956b155ea5f138b Mon Sep 17 00:00:00 2001 From: kc611 Date: Fri, 7 Jul 2023 15:54:07 +0530 Subject: [PATCH 15/22] Adapted core datastructures to basic block logic changes --- numba_rvsdg/core/datastructures/byte_flow.py | 79 ++++---------------- numba_rvsdg/core/datastructures/flow_info.py | 1 - numba_rvsdg/core/datastructures/scfg.py | 31 ++++---- 3 files changed, 28 insertions(+), 83 deletions(-) diff --git a/numba_rvsdg/core/datastructures/byte_flow.py b/numba_rvsdg/core/datastructures/byte_flow.py index 9b1ebb04..302a2b9a 100644 --- a/numba_rvsdg/core/datastructures/byte_flow.py +++ b/numba_rvsdg/core/datastructures/byte_flow.py @@ -1,5 +1,4 @@ import dis -from copy import deepcopy from dataclasses import dataclass from numba_rvsdg.core.datastructures.scfg import SCFG @@ -61,89 +60,41 @@ def from_bytecode(code) -> "ByteFlow": return ByteFlow(bc=bc, scfg=scfg) def _join_returns(self): - """Joins the return blocks within the corresponding SCFG. - - This method creates a deep copy of the SCFG and performs - operation to join return blocks within the control flow. - It returns a new ByteFlow object with the updated SCFG. - - Returns - ------- - byteflow: ByteFlow - The new ByteFlow object with updated SCFG. - """ - scfg = deepcopy(self.scfg) - scfg.join_returns() - return ByteFlow(bc=self.bc, scfg=scfg) + """Joins the return blocks within the corresponding SCFG.""" + self.scfg.join_returns() def _restructure_loop(self): - """Restructures the loops within the corresponding SCFG. - - Creates a deep copy of the SCFG and performs the operation to - restructure loop constructs within the control flow using + """Restructures the loops within the corresponding SCFG using the algorithm LOOP RESTRUCTURING from section 4.1 of Bahmann2015. It applies the restructuring operation to both the main SCFG - and any subregions within it. It returns a new ByteFlow object - with the updated SCFG. - - Returns - ------- - byteflow: ByteFlow - The new ByteFlow object with updated SCFG. + and any subregions within it. """ - scfg = deepcopy(self.scfg) - restructure_loop(scfg.region) - for region in _iter_subregions(scfg): + restructure_loop(self.scfg.region) + for region in _iter_subregions(self.scfg): restructure_loop(region) - return ByteFlow(bc=self.bc, scfg=scfg) def _restructure_branch(self): - """Restructures the branches within the corresponding SCFG. - - Creates a deep copy of the SCFG and performs the operation to - restructure branch constructs within the control flow. It applies + """Restructures the branches within the corresponding SCFG. It applies the restructuring operation to both the main SCFG and any - subregions within it. It returns a new ByteFlow object with - the updated SCFG. - - Returns - ------- - byteflow: ByteFlow - The new ByteFlow object with updated SCFG. + subregions within it. """ - scfg = deepcopy(self.scfg) - restructure_branch(scfg.region) - for region in _iter_subregions(scfg): + restructure_branch(self.scfg.region) + for region in _iter_subregions(self.scfg): restructure_branch(region) - return ByteFlow(bc=self.bc, scfg=scfg) def restructure(self): """Applies join_returns, restructure_loop and restructure_branch - in the respective order on the SCFG. - - Creates a deep copy of the SCFG and applies a series of + in the respective order on the SCFG. Applies a series of restructuring operations to it. The operations include joining return blocks, restructuring loop constructs, and - restructuring branch constructs. It returns a new ByteFlow - object with the updated SCFG. - - Returns - ------- - byteflow: ByteFlow - The new ByteFlow object with updated SCFG. + restructuring branch constructs. """ - scfg = deepcopy(self.scfg) # close - scfg.join_returns() + self.scfg.join_returns() # handle loop - restructure_loop(scfg.region) - for region in _iter_subregions(scfg): - restructure_loop(region) + self._restructure_loop() # handle branch - restructure_branch(scfg.region) - for region in _iter_subregions(scfg): - restructure_branch(region) - return ByteFlow(bc=self.bc, scfg=scfg) + self._restructure_branch() def _iter_subregions(scfg: "SCFG"): diff --git a/numba_rvsdg/core/datastructures/flow_info.py b/numba_rvsdg/core/datastructures/flow_info.py index 562bf864..c906bd2f 100644 --- a/numba_rvsdg/core/datastructures/flow_info.py +++ b/numba_rvsdg/core/datastructures/flow_info.py @@ -136,7 +136,6 @@ def build_basicblocks(self: "FlowInfo", end_offset=None) -> "SCFG": begin=begin, end=end, _jump_targets=targets, - backedges=(), ) scfg.add_block(block) return scfg diff --git a/numba_rvsdg/core/datastructures/scfg.py b/numba_rvsdg/core/datastructures/scfg.py index 2d29025b..2a32af64 100644 --- a/numba_rvsdg/core/datastructures/scfg.py +++ b/numba_rvsdg/core/datastructures/scfg.py @@ -510,15 +510,13 @@ def insert_block( """ # TODO: needs a diagram and documentaion # initialize new block - new_block = block_type( - name=new_name, _jump_targets=successors, backedges=set() - ) + new_block = block_type(name=new_name, _jump_targets=successors) # add block to self self.add_block(new_block) # Replace any arcs from any of predecessors to any of successors with # an arc through the inserted block instead. for name in predecessors: - block = self.graph.pop(name) + block = self.graph[name] jt = list(block.jump_targets) if successors: for s in successors: @@ -529,7 +527,7 @@ def insert_block( jt.pop(jt.index(s)) else: jt.append(new_name) - self.add_block(block.replace_jump_targets(jump_targets=tuple(jt))) + block.change_jump_targets(jump_targets=tuple(jt)) def insert_SyntheticExit( self, @@ -624,7 +622,6 @@ def insert_block_and_control_blocks( synth_assign_block = SyntheticAssignment( name=synth_assign, _jump_targets=(new_name,), - backedges=(), variable_assignment=variable_assignment, ) # add block @@ -636,16 +633,11 @@ def insert_block_and_control_blocks( # replace previous successor with synth_assign jt[jt.index(s)] = synth_assign # finally, replace the jump_targets - self.add_block( - self.graph.pop(name).replace_jump_targets( - jump_targets=tuple(jt) - ) - ) + self.graph[name].change_jump_targets(jump_targets=tuple(jt)) # initialize new block, which will hold the branching table new_block = SyntheticHead( name=new_name, _jump_targets=tuple(successors), - backedges=set(), variable=branch_variable, branch_value_table=branch_value_table, ) @@ -838,7 +830,6 @@ def make_scfg(curr_heads: set, exiting: str = None): block = RegionBlock( name=current_name, _jump_targets=block_edges, - backedges=block_backedges, kind=block_info["kind"], header=block_info["header"], exiting=block_info["exiting"], @@ -852,7 +843,6 @@ def make_scfg(curr_heads: set, exiting: str = None): ]: block = block_class( name=current_name, - backedges=block_backedges, _jump_targets=block_edges, branch_value_table=block_info["branch_value_table"], variable=block_info["variable"], @@ -861,23 +851,22 @@ def make_scfg(curr_heads: set, exiting: str = None): block = SyntheticAssignment( name=current_name, _jump_targets=block_edges, - backedges=block_backedges, variable_assignment=block_info["variable_assignment"], ) elif block_type in [PYTHON_BYTECODE]: block = PythonBytecodeBlock( name=current_name, _jump_targets=block_edges, - backedges=block_backedges, begin=block_info["begin"], end=block_info["end"], ) else: block = block_class( name=current_name, - backedges=block_backedges, _jump_targets=block_edges, ) + for backedge in block_backedges: + block.declare_backedge(backedge) scfg_graph[current_name] = block if current_name != exiting: queue.update(edges[current_name]) @@ -968,7 +957,13 @@ def reverse_lookup(value: type): blocks[key]["begin"] = value.begin blocks[key]["end"] = value.end edges[key] = sorted([i for i in value._jump_targets]) - backedges[key] = sorted([i for i in value.backedges]) + backedges[key] = sorted( + [ + i + for idx, i in enumerate(value._jump_targets) + if value.backedges[idx] + ] + ) graph_dict = {"blocks": blocks, "edges": edges, "backedges": backedges} From 0b9584422b7fc928440335ed832b9fa7b38bd09c Mon Sep 17 00:00:00 2001 From: kc611 Date: Fri, 7 Jul 2023 15:54:23 +0530 Subject: [PATCH 16/22] Adapted transformations to basic block changes --- numba_rvsdg/core/transformations.py | 53 +++++++---------------------- 1 file changed, 12 insertions(+), 41 deletions(-) diff --git a/numba_rvsdg/core/transformations.py b/numba_rvsdg/core/transformations.py index 812024de..e6c7e0f9 100644 --- a/numba_rvsdg/core/transformations.py +++ b/numba_rvsdg/core/transformations.py @@ -58,9 +58,7 @@ def loop_restructure_helper(scfg: SCFG, loop: Set[str]): and len(exiting_blocks) == 1 and backedge_blocks[0] == next(iter(exiting_blocks)) ): - scfg.add_block( - scfg.graph.pop(backedge_blocks[0]).declare_backedge(loop_head) - ) + scfg.graph[backedge_blocks[0]].declare_backedge(loop_head) return # The synthetic exiting latch and synthetic exit need to be created @@ -147,7 +145,6 @@ def reverse_lookup(d, value): synth_assign_block = SyntheticAssignment( name=synth_assign, _jump_targets=(synth_exiting_latch,), - backedges=(), variable_assignment=variable_assignment, ) # Insert the assignment to the scfg @@ -177,20 +174,17 @@ def reverse_lookup(d, value): # that point to the headers, no need to add a backedge, # since it will be contained in the SyntheticExitingLatch # later on. - block = scfg.graph.pop(name) + block = scfg.graph[name] jts = list(block.jump_targets) for h in headers: if h in jts: jts.remove(h) - scfg.add_block( - block.replace_jump_targets(jump_targets=tuple(jts)) - ) + block.change_jump_targets(jump_targets=tuple(jts)) # Setup the assignment block and initialize it with the # correct jump_targets and variable assignment. synth_assign_block = SyntheticAssignment( name=synth_assign, _jump_targets=(synth_exiting_latch,), - backedges=(), variable_assignment=variable_assignment, ) # Add the new block to the SCFG @@ -199,11 +193,7 @@ def reverse_lookup(d, value): new_jt[new_jt.index(jt)] = synth_assign # finally, replace the jump_targets for this block with the new # ones - scfg.add_block( - scfg.graph.pop(name).replace_jump_targets( - jump_targets=tuple(new_jt) - ) - ) + scfg.graph[name].change_jump_targets(jump_targets=tuple(new_jt)) # Add any new blocks to the loop. loop.update(new_blocks) @@ -214,10 +204,10 @@ def reverse_lookup(d, value): synth_exit if needs_synth_exit else next(iter(exit_blocks)), loop_head, ), - backedges=(loop_head,), variable=backedge_variable, branch_value_table=backedge_value_table, ) + synth_exiting_latch_block.declare_backedge(loop_head) loop.add(synth_exiting_latch) scfg.add_block(synth_exiting_latch_block) # If an exit is to be created, we do so too, but only add it to the scfg, @@ -226,7 +216,6 @@ def reverse_lookup(d, value): synth_exit_block = SyntheticExitBranch( name=synth_exit, _jump_targets=tuple(exit_blocks), - backedges=(), variable=exit_variable, branch_value_table=exit_value_table, ) @@ -329,29 +318,18 @@ def update_exiting( ): # Recursively updates the exiting blocks of a regionblock region_exiting = region_block.exiting - region_exiting_block: BasicBlock = region_block.subregion.graph.pop( + region_exiting_block: BasicBlock = region_block.subregion.graph[ region_exiting - ) + ] jt = list(region_exiting_block._jump_targets) for idx, s in enumerate(jt): if s is new_region_header: jt[idx] = new_region_name - region_exiting_block = region_exiting_block.replace_jump_targets( - jump_targets=tuple(jt) - ) - be = list(region_exiting_block.backedges) - for idx, s in enumerate(be): - if s is new_region_header: - be[idx] = new_region_name - region_exiting_block = region_exiting_block.replace_backedges( - backedges=tuple(be) - ) + region_exiting_block.change_jump_targets(jump_targets=tuple(jt)) if isinstance(region_exiting_block, RegionBlock): region_exiting_block = update_exiting( region_exiting_block, new_region_header, new_region_name ) - region_block.subregion.add_block(region_exiting_block) - return region_block def extract_region( @@ -381,27 +359,20 @@ def extract_region( # the SCFG represents should not be the meta region. assert scfg.region.kind != "meta" continue - entry = scfg.graph.pop(name) + entry = scfg.graph[name] jt = list(entry._jump_targets) for idx, s in enumerate(jt): if s is region_header: jt[idx] = region_name - entry = entry.replace_jump_targets(jump_targets=tuple(jt)) - be = list(entry.backedges) - for idx, s in enumerate(be): - if s is region_header: - be[idx] = region_name - entry = entry.replace_backedges(backedges=tuple(be)) + entry.change_jump_targets(jump_targets=tuple(jt)) # If the entry itself is a region, update it's # exiting blocks too, recursively if isinstance(entry, RegionBlock): - entry = update_exiting(entry, region_header, region_name) - scfg.add_block(entry) + update_exiting(entry, region_header, region_name) region = RegionBlock( name=region_name, _jump_targets=scfg[region_exiting].jump_targets, - backedges=(), kind=region_kind, header=region_header, subregion=head_subgraph, @@ -426,7 +397,7 @@ def extract_region( # update the parent region for k, v in region.subregion.graph.items(): if isinstance(v, RegionBlock): - object.__setattr__(v, "parent_region", region) + v.replace_parent(region) def restructure_branch(parent_region: RegionBlock): From 7ce552eb351d83a93df3b2a389a86ca6b3fc9c64 Mon Sep 17 00:00:00 2001 From: kc611 Date: Fri, 7 Jul 2023 15:54:33 +0530 Subject: [PATCH 17/22] Adapted rendering to basic block changes --- numba_rvsdg/rendering/rendering.py | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/numba_rvsdg/rendering/rendering.py b/numba_rvsdg/rendering/rendering.py index d0671c8b..e3b4ef3f 100644 --- a/numba_rvsdg/rendering/rendering.py +++ b/numba_rvsdg/rendering/rendering.py @@ -74,22 +74,19 @@ def find_base_header(block: BasicBlock): if isinstance(src_block, RegionBlock): continue src_block = find_base_header(src_block) - for dst_name in src_block.jump_targets: + for idx, dst_name in enumerate(src_block._jump_targets): dst_name = find_base_header(blocks[dst_name]).name if dst_name in blocks.keys(): - self.g.edge(str(src_block.name), str(dst_name)) - else: - raise Exception("unreachable " + str(src_block)) - for dst_name in src_block.backedges: - dst_name = find_base_header(blocks[dst_name]).name - if dst_name in blocks.keys(): - self.g.edge( - str(src_block.name), - str(dst_name), - style="dashed", - color="grey", - constraint="0", - ) + if src_block.backedges[idx]: + self.g.edge( + str(src_block.name), + str(dst_name), + style="dashed", + color="grey", + constraint="0", + ) + else: + self.g.edge(str(src_block.name), str(dst_name)) else: raise Exception("unreachable " + str(src_block)) @@ -169,8 +166,7 @@ def render_branching_block( digraph.node(str(name), shape="rect", label=body) def render_byteflow(self, byteflow: ByteFlow): - """Renders the provided `ByteFlow` object. - """ + """Renders the provided `ByteFlow` object.""" self.bcmap_from_bytecode(byteflow.bc) # render nodes for name, block in byteflow.scfg.graph.items(): From f04eeb936cf574bb44dcf94c32f7da17dfa79291 Mon Sep 17 00:00:00 2001 From: kc611 Date: Fri, 7 Jul 2023 15:54:47 +0530 Subject: [PATCH 18/22] Adapted tests to basic block changes --- numba_rvsdg/tests/test_byteflow.py | 5 ----- numba_rvsdg/tests/test_figures.py | 8 ++++++-- numba_rvsdg/tests/test_scfg.py | 7 ++----- numba_rvsdg/tests/test_simulate.py | 22 +++++++++++----------- numba_rvsdg/tests/test_transforms.py | 6 +++--- numba_rvsdg/tests/test_utils.py | 8 +------- 6 files changed, 23 insertions(+), 33 deletions(-) diff --git a/numba_rvsdg/tests/test_byteflow.py b/numba_rvsdg/tests/test_byteflow.py index a92db249..d3b54ea5 100644 --- a/numba_rvsdg/tests/test_byteflow.py +++ b/numba_rvsdg/tests/test_byteflow.py @@ -115,7 +115,6 @@ def test_constructor(self): begin=0, end=8, _jump_targets=(), - backedges=(), ) self.assertEqual(block.name, "python_bytecode_block_0") self.assertEqual(block.begin, 0) @@ -134,7 +133,6 @@ def test_is_jump_target(self): _jump_targets=( name_gen.new_block_name(block_names.PYTHON_BYTECODE), ), - backedges=(), ) self.assertEqual(block.jump_targets, ("python_bytecode_block_1",)) self.assertFalse(block.is_exiting) @@ -146,7 +144,6 @@ def test_get_instructions(self): begin=0, end=8, _jump_targets=(), - backedges=(), ) expected = [ Instruction( @@ -243,7 +240,6 @@ def test_build_basic_blocks(self): begin=0, end=10, _jump_targets=(), - backedges=(), ) } ) @@ -267,7 +263,6 @@ def test_from_bytecode(self): begin=0, end=10, _jump_targets=(), - backedges=(), ) } ) diff --git a/numba_rvsdg/tests/test_figures.py b/numba_rvsdg/tests/test_figures.py index 82419f1d..249e0f03 100644 --- a/numba_rvsdg/tests/test_figures.py +++ b/numba_rvsdg/tests/test_figures.py @@ -440,7 +440,7 @@ def test_figure_3(self): flow = FlowInfo.from_bytecode(bc) scfg = flow.build_basicblocks() byteflow = ByteFlow(bc=bc, scfg=scfg) - byteflow = byteflow.restructure() + byteflow.restructure() x, _ = SCFG.from_yaml(fig_3_yaml) self.assertSCFGEqual(x, byteflow.scfg) @@ -473,7 +473,11 @@ def test_figure_4(self): flow = FlowInfo.from_bytecode(bc) scfg = flow.build_basicblocks() byteflow = ByteFlow(bc=bc, scfg=scfg) - byteflow = byteflow.restructure() + byteflow.restructure() x, _ = SCFG.from_yaml(fig_4_yaml) self.assertSCFGEqual(x, byteflow.scfg) + + +x = TestBahmannFigures() +x.test_figure_3() diff --git a/numba_rvsdg/tests/test_scfg.py b/numba_rvsdg/tests/test_scfg.py index 3d1d9e5a..02149efc 100644 --- a/numba_rvsdg/tests/test_scfg.py +++ b/numba_rvsdg/tests/test_scfg.py @@ -192,17 +192,14 @@ def foo(n): def test_concealed_region_view_iter(self): flow = ByteFlow.from_bytecode(self.foo) - restructured = flow._restructure_loop() + flow._restructure_loop() expected = [ ("python_bytecode_block_0", PythonBytecodeBlock), ("loop_region_0", RegionBlock), ("python_bytecode_block_3", PythonBytecodeBlock), ] received = list( - ( - (k, type(v)) - for k, v in restructured.scfg.concealed_region_view.items() - ) + ((k, type(v)) for k, v in flow.scfg.concealed_region_view.items()) ) self.assertEqual(expected, received) diff --git a/numba_rvsdg/tests/test_simulate.py b/numba_rvsdg/tests/test_simulate.py index 68f8f44b..9333bfd8 100644 --- a/numba_rvsdg/tests/test_simulate.py +++ b/numba_rvsdg/tests/test_simulate.py @@ -4,7 +4,7 @@ # flow = ByteFlow.from_bytecode(foo) # #pprint(flow.scfg) -# flow = flow.restructure() +# flow.restructure() # #pprint(flow.scfg) # # pprint(rtsflow.scfg) # ByteFlowRenderer().render_byteflow(flow).view() @@ -42,7 +42,7 @@ def foo(x): return c flow = ByteFlow.from_bytecode(foo) - flow = flow.restructure() + flow.restructure() # if case self._run(foo, flow, {"x": 1}) @@ -57,7 +57,7 @@ def foo(x): return c flow = ByteFlow.from_bytecode(foo) - flow = flow.restructure() + flow.restructure() # loop bypass case self._run(foo, flow, {"x": 0}) @@ -76,7 +76,7 @@ def foo(x): return c flow = ByteFlow.from_bytecode(foo) - flow = flow.restructure() + flow.restructure() # loop bypass case self._run(foo, flow, {"x": 0}) @@ -95,7 +95,7 @@ def foo(x): return c flow = ByteFlow.from_bytecode(foo) - flow = flow.restructure() + flow.restructure() # loop bypass case self._run(foo, flow, {"x": 0}) @@ -119,7 +119,7 @@ def foo(x): return c flow = ByteFlow.from_bytecode(foo) - flow = flow.restructure() + flow.restructure() # no loop self._run(foo, flow, {"x": 0}) @@ -143,7 +143,7 @@ def foo(x): return c flow = ByteFlow.from_bytecode(foo) - flow = flow.restructure() + flow.restructure() # loop bypass self._run(foo, flow, {"x": 0}) @@ -159,7 +159,7 @@ def foo(x, y): return (x > 0 and x < 10) or (y > 0 and y < 10) flow = ByteFlow.from_bytecode(foo) - flow = flow.restructure() + flow.restructure() self._run(foo, flow, {"x": 5, "y": 5}) @@ -173,7 +173,7 @@ def foo(s, e): return c flow = ByteFlow.from_bytecode(foo) - flow = flow.restructure() + flow.restructure() # no looping self._run(foo, flow, {"s": 0, "e": 0}) @@ -190,5 +190,5 @@ def foo(s, e): self._run(foo, flow, {"s": 23, "e": 28}) -if __name__ == "__main__": - unittest.main() +# if __name__ == "__main__": +# unittest.main() diff --git a/numba_rvsdg/tests/test_transforms.py b/numba_rvsdg/tests/test_transforms.py index 4b4ce585..e8b384e5 100644 --- a/numba_rvsdg/tests/test_transforms.py +++ b/numba_rvsdg/tests/test_transforms.py @@ -756,7 +756,7 @@ def test_backedge_not_exiting(self): '1': ['2', '5'] '2': ['6'] '3': [] - '4': ['1', '3'] + '4': ['3', '1'] '5': ['4'] '6': ['4'] backedges: @@ -810,7 +810,7 @@ def test_multi_back_edge_with_backedge_from_header(self): '1': ['5', '2'] '2': ['6', '7'] '3': [] - '4': ['1', '3'] + '4': ['3', '1'] '5': ['4'] '6': ['4'] '7': ['4'] @@ -876,7 +876,7 @@ def test_double_exit(self): '2': ['3', '6'] '3': ['7', '8'] '4': [] - '5': ['1', '4'] + '5': ['4', '1'] '6': ['5'] '7': ['5'] '8': ['5'] diff --git a/numba_rvsdg/tests/test_utils.py b/numba_rvsdg/tests/test_utils.py index 488a9713..556e28c6 100644 --- a/numba_rvsdg/tests/test_utils.py +++ b/numba_rvsdg/tests/test_utils.py @@ -65,18 +65,12 @@ def assertSCFGEqual( # Add the jump targets as corresponding nodes in block mapping # dictionary. Since order must be same we can simply add zip # functionality as the correspondence function for nodes - for jt1, jt2 in zip(node.jump_targets, second_node.jump_targets): + for jt1, jt2 in zip(node._jump_targets, second_node._jump_targets): if node.name == exiting: continue block_mapping[jt1] = jt2 stack.append(jt1) - for be1, be2 in zip(node.backedges, second_node.backedges): - if node.name == exiting: - continue - block_mapping[be1] = be2 - stack.append(be1) - def assertYAMLEqual( self, first_yaml: SCFG, second_yaml: SCFG, head_map: dict ): From 2c68abf42b702ce746c7f7ea77479cfe857f4edf Mon Sep 17 00:00:00 2001 From: kc611 Date: Fri, 7 Jul 2023 21:20:45 +0530 Subject: [PATCH 19/22] Removed Byteflow and FlowInfo and moved respective methods to SCFG --- .../core/datastructures/basic_block.py | 2 + numba_rvsdg/core/datastructures/byte_flow.py | 104 ------------- numba_rvsdg/core/datastructures/flow_info.py | 141 ------------------ numba_rvsdg/core/datastructures/scfg.py | 128 +++++++++++++++- 4 files changed, 129 insertions(+), 246 deletions(-) delete mode 100644 numba_rvsdg/core/datastructures/byte_flow.py delete mode 100644 numba_rvsdg/core/datastructures/flow_info.py diff --git a/numba_rvsdg/core/datastructures/basic_block.py b/numba_rvsdg/core/datastructures/basic_block.py index ab45bf93..b1489476 100644 --- a/numba_rvsdg/core/datastructures/basic_block.py +++ b/numba_rvsdg/core/datastructures/basic_block.py @@ -141,6 +141,8 @@ class PythonBytecodeBlock(BasicBlock): end: int = None + bcmap: dis.Bytecode = None + def get_instructions( self, bcmap: Dict[int, dis.Instruction] ) -> List[dis.Instruction]: diff --git a/numba_rvsdg/core/datastructures/byte_flow.py b/numba_rvsdg/core/datastructures/byte_flow.py deleted file mode 100644 index 302a2b9a..00000000 --- a/numba_rvsdg/core/datastructures/byte_flow.py +++ /dev/null @@ -1,104 +0,0 @@ -import dis -from dataclasses import dataclass - -from numba_rvsdg.core.datastructures.scfg import SCFG -from numba_rvsdg.core.datastructures.basic_block import RegionBlock -from numba_rvsdg.core.datastructures.flow_info import FlowInfo -from numba_rvsdg.core.utils import _logger, _LogWrap - -from numba_rvsdg.core.transformations import ( - restructure_loop, - restructure_branch, -) - - -@dataclass(frozen=True) -class ByteFlow: - """ByteFlow class. - - The ByteFlow class represents the bytecode and its relation with - corresponding structured control flow graph (SCFG). - - Attributes - ---------- - bc: dis.Bytecode - The dis.Bytecode object representing the bytecode. - scfg: SCFG - The SCFG object representing the control flow of - the bytecode. - """ - - bc: dis.Bytecode - scfg: "SCFG" - - @staticmethod - def from_bytecode(code) -> "ByteFlow": - """Creates a ByteFlow object from the given python - function. - - This method uses dis.Bytecode to parse the bytecode - generated from the given Python function. - It returns a ByteFlow object with the corresponding - bytecode and SCFG. - - Parameters - ---------- - code: Python Function - The Python Function from which ByteFlow is to - be generated. - - Returns - ------- - byteflow: ByteFlow - The resulting ByteFlow object. - """ - bc = dis.Bytecode(code) - _logger.debug("Bytecode\n%s", _LogWrap(lambda: bc.dis())) - - flowinfo = FlowInfo.from_bytecode(bc) - scfg = flowinfo.build_basicblocks() - return ByteFlow(bc=bc, scfg=scfg) - - def _join_returns(self): - """Joins the return blocks within the corresponding SCFG.""" - self.scfg.join_returns() - - def _restructure_loop(self): - """Restructures the loops within the corresponding SCFG using - the algorithm LOOP RESTRUCTURING from section 4.1 of Bahmann2015. - It applies the restructuring operation to both the main SCFG - and any subregions within it. - """ - restructure_loop(self.scfg.region) - for region in _iter_subregions(self.scfg): - restructure_loop(region) - - def _restructure_branch(self): - """Restructures the branches within the corresponding SCFG. It applies - the restructuring operation to both the main SCFG and any - subregions within it. - """ - restructure_branch(self.scfg.region) - for region in _iter_subregions(self.scfg): - restructure_branch(region) - - def restructure(self): - """Applies join_returns, restructure_loop and restructure_branch - in the respective order on the SCFG. Applies a series of - restructuring operations to it. The operations include - joining return blocks, restructuring loop constructs, and - restructuring branch constructs. - """ - # close - self.scfg.join_returns() - # handle loop - self._restructure_loop() - # handle branch - self._restructure_branch() - - -def _iter_subregions(scfg: "SCFG"): - for node in scfg.graph.values(): - if isinstance(node, RegionBlock): - yield node - yield from _iter_subregions(node.subregion) diff --git a/numba_rvsdg/core/datastructures/flow_info.py b/numba_rvsdg/core/datastructures/flow_info.py deleted file mode 100644 index c906bd2f..00000000 --- a/numba_rvsdg/core/datastructures/flow_info.py +++ /dev/null @@ -1,141 +0,0 @@ -import dis - -from typing import Set, Tuple, Dict, Sequence -from dataclasses import dataclass, field - -from numba_rvsdg.core.datastructures.basic_block import PythonBytecodeBlock -from numba_rvsdg.core.datastructures import block_names -from numba_rvsdg.core.datastructures.scfg import SCFG -from numba_rvsdg.core.utils import ( - is_conditional_jump, - _next_inst_offset, - is_unconditional_jump, - is_exiting, - _prev_inst_offset, -) - - -@dataclass() -class FlowInfo: - """The FlowInfo class is responsible for converting bytecode into a - ByteFlow object. - - Attributes - ---------- - block_offsets: Set[int] - A set that marks the starting offsets of basic blocks in the bytecode. - jump_insts: Dict[int, Tuple[int, ...]] - A dictionary that contains jump instructions and their target offsets. - last_offset: int - The offset of the last bytecode instruction. - """ - - block_offsets: Set[int] = field(default_factory=set) - - jump_insts: Dict[int, Tuple[int, ...]] = field(default_factory=dict) - - last_offset: int = field(default=0) - - def _add_jump_inst(self, offset: int, targets: Sequence[int]): - """Internal method to add a jump instruction to the FlowInfo. - - This method adds the target offsets of the jump instruction - to the block_offsets set and updates the jump_insts dictionary. - - Parameters - ---------- - offset: int - The given target offset. - targets: Sequence[int] - target jump instrcutions. - """ - for off in targets: - assert isinstance(off, int) - self.block_offsets.add(off) - self.jump_insts[offset] = tuple(targets) - - @staticmethod - def from_bytecode(bc: dis.Bytecode) -> "FlowInfo": - """Static method that builds the structured control flow graph (SCFG) - from the given `dis.Bytecode` object bc. - - This method analyzes the bytecode instructions, marks the start of - basic blocks, and records jump instructions and their target offsets. - It builds the structured control flow graph (SCFG) from the given - `dis.Bytecode` object and returns a FlowInfo object. - - Parameters - ---------- - bc: dis.Bytecode - Bytecode from which flowinfo is to be constructed. - - Returns - ------- - flowinfo: FlowInfo - FlowInfo object representing the given bytecode. - """ - flowinfo = FlowInfo() - - for inst in bc: - # Handle jump-target instruction - if inst.offset == 0 or inst.is_jump_target: - flowinfo.block_offsets.add(inst.offset) - # Handle by op - if is_conditional_jump(inst.opname): - flowinfo._add_jump_inst( - inst.offset, (_next_inst_offset(inst.offset), inst.argval) - ) - elif is_unconditional_jump(inst.opname): - flowinfo._add_jump_inst(inst.offset, (inst.argval,)) - elif is_exiting(inst.opname): - flowinfo._add_jump_inst(inst.offset, ()) - - flowinfo.last_offset = inst.offset - return flowinfo - - def build_basicblocks(self: "FlowInfo", end_offset=None) -> "SCFG": - """Builds a graph of basic blocks based on the flow information. - - It creates a structured control flow graph (SCFG) object, assigns - names to the blocks, and defines the block boundaries, jump targets, - and backedges. It returns an SCFG object representing the control - flow graph. - - Parameters - ---------- - end_offset: int - The byte offset of the last instruction. - - Returns - ------- - scfg: SCFG - SCFG object corresponding to the bytecode contained within the - current FlowInfo object. - """ - scfg = SCFG() - offsets = sorted(self.block_offsets) - # enumerate names - names = { - offset: scfg.name_gen.new_block_name(block_names.PYTHON_BYTECODE) - for offset in offsets - } - if end_offset is None: - end_offset = _next_inst_offset(self.last_offset) - - for begin, end in zip(offsets, [*offsets[1:], end_offset]): - name = names[begin] - targets: Tuple[str, ...] - term_offset = _prev_inst_offset(end) - if term_offset not in self.jump_insts: - # implicit jump - targets = (names[end],) - else: - targets = tuple(names[o] for o in self.jump_insts[term_offset]) - block = PythonBytecodeBlock( - name=name, - begin=begin, - end=end, - _jump_targets=targets, - ) - scfg.add_block(block) - return scfg diff --git a/numba_rvsdg/core/datastructures/scfg.py b/numba_rvsdg/core/datastructures/scfg.py index 2a32af64..4b894fb1 100644 --- a/numba_rvsdg/core/datastructures/scfg.py +++ b/numba_rvsdg/core/datastructures/scfg.py @@ -1,10 +1,11 @@ import dis import yaml from textwrap import indent -from typing import Set, Tuple, Dict, List, Iterator +from typing import Set, Tuple, Dict, List, Iterator, Sequence from dataclasses import dataclass, field from collections import deque from collections.abc import Mapping +from numba_rvsdg.core.utils import _logger, _LogWrap from numba_rvsdg.core.datastructures.basic_block import ( BasicBlock, @@ -32,6 +33,13 @@ SYNTH_EXIT_LATCH, SYNTH_EXIT_BRANCH, ) +from numba_rvsdg.core.utils import ( + is_conditional_jump, + _next_inst_offset, + is_unconditional_jump, + is_exiting, + _prev_inst_offset, +) @dataclass(frozen=True) @@ -985,6 +993,124 @@ def view(self, name: str = None): SCFGRenderer(self).view(name) + def restructure_loop(self): + """Restructures the loops within the corresponding SCFG using + the algorithm LOOP RESTRUCTURING from section 4.1 of Bahmann2015. + It applies the restructuring operation to both the main SCFG + and any subregions within it. + """ + from numba_rvsdg.core.transformations import restructure_loop + + restructure_loop(self.region) + for region in _iter_subregions(self): + restructure_loop(region) + + def restructure_branch(self): + """Restructures the branches within the corresponding SCFG. It applies + the restructuring operation to both the main SCFG and any + subregions within it. + """ + from numba_rvsdg.core.transformations import restructure_branch + + restructure_branch(self.region) + for region in _iter_subregions(self): + restructure_branch(region) + + def restructure(self): + """Applies join_returns, restructure_loop and restructure_branch + in the respective order on the SCFG. Applies a series of + restructuring operations to it. The operations include + joining return blocks, restructuring loop constructs, and + restructuring branch constructs. + """ + # close + self.join_returns() + # handle loop + self.restructure_loop() + # handle branch + self.restructure_branch() + + @staticmethod + def from_bytecode(function): + bc = dis.Bytecode(function) + return SCFG._from_bytecode(bc) + + @staticmethod + def _from_bytecode(code: list, end_offset=None): + _logger.debug("Bytecode\n%s", _LogWrap(lambda: code.dis())) + block_offsets: Set[int] = set() + jump_insts: Dict[int, Tuple[int, ...]] = {} + last_offset: int = 0 + + def _add_jump_inst(offset: int, targets: Sequence[int]): + """Internal method to add a jump instruction. + + This method adds the target offsets of the jump instruction + to the block_offsets set and updates the jump_insts dictionary. + + Parameters + ---------- + offset: int + The given target offset. + targets: Sequence[int] + target jump instrcutions. + """ + for off in targets: + assert isinstance(off, int) + block_offsets.add(off) + jump_insts[offset] = tuple(targets) + + for inst in code: + # Handle jump-target instruction + if inst.offset == 0 or inst.is_jump_target: + block_offsets.add(inst.offset) + # Handle by op + if is_conditional_jump(inst.opname): + _add_jump_inst( + inst.offset, (_next_inst_offset(inst.offset), inst.argval) + ) + elif is_unconditional_jump(inst.opname): + _add_jump_inst(inst.offset, (inst.argval,)) + elif is_exiting(inst.opname): + _add_jump_inst(inst.offset, ()) + + last_offset = inst.offset + + scfg = SCFG() + offsets = sorted(block_offsets) + # enumerate names + names = { + offset: scfg.name_gen.new_block_name(PYTHON_BYTECODE) + for offset in offsets + } + if end_offset is None: + end_offset = _next_inst_offset(last_offset) + + for begin, end in zip(offsets, [*offsets[1:], end_offset]): + name = names[begin] + targets: Tuple[str, ...] + term_offset = _prev_inst_offset(end) + if term_offset not in jump_insts: + # implicit jump + targets = (names[end],) + else: + targets = tuple(names[o] for o in jump_insts[term_offset]) + block = PythonBytecodeBlock( + name=name, + begin=begin, + end=end, + _jump_targets=targets, + ) + scfg.add_block(block) + return scfg + + +def _iter_subregions(scfg: "SCFG"): + for node in scfg.graph.values(): + if isinstance(node, RegionBlock): + yield node + yield from _iter_subregions(node.subregion) + class AbstractGraphView(Mapping): """Abstract Graph View class. From 018aa54d8ddd5ba630e457e52c1e6db65abaa17e Mon Sep 17 00:00:00 2001 From: kc611 Date: Fri, 7 Jul 2023 21:21:49 +0530 Subject: [PATCH 20/22] Simplified graph rendering --- numba_rvsdg/rendering/rendering.py | 186 ++++++----------------------- 1 file changed, 37 insertions(+), 149 deletions(-) diff --git a/numba_rvsdg/rendering/rendering.py b/numba_rvsdg/rendering/rendering.py index e3b4ef3f..a6c14318 100644 --- a/numba_rvsdg/rendering/rendering.py +++ b/numba_rvsdg/rendering/rendering.py @@ -8,19 +8,29 @@ SyntheticBlock, ) from numba_rvsdg.core.datastructures.scfg import SCFG -from numba_rvsdg.core.datastructures.byte_flow import ByteFlow -import dis -from typing import Dict -class BaseRenderer: - """Base Renderer class. +class SCFGRenderer: + """The `SCFGRenderer` class is used to render the visual + representation of a `SCFG` object. + + Attributes + ---------- + g: Digraph + The graphviz Digraph object that represents the entire graph upon + which the current SCFG is to be rendered. - This is the base class for all types of graph renderers. It defines two - methods `render_block` and `render_edges` that define how the blocks and - edges of the graph are rendered respectively. """ + def __init__(self, scfg: SCFG): + from graphviz import Digraph + + self.g = Digraph() + # render nodes + for name, block in scfg.graph.items(): + self.render_block(self.g, name, block) + self.render_edges(scfg) + def render_block( self, digraph: "Digraph", name: str, block: BasicBlock # noqa ): @@ -90,115 +100,6 @@ def find_base_header(block: BasicBlock): else: raise Exception("unreachable " + str(src_block)) - -class ByteFlowRenderer(BaseRenderer): - """The `ByteFlowRenderer` class is used to render the visual - representation of a `ByteFlow` object. - - Attributes - ---------- - g: Digraph - The graphviz Digraph object that represents the entire graph upon - which the current ByteFlow is to be rendered. - bcmap: Dict[int, dis.Instruction] - Mapping of bytecode offset to instruction. - - """ - - def __init__(self): - from graphviz import Digraph - - self.g = Digraph() - - def render_region_block( - self, digraph: "Digraph", name: str, regionblock: RegionBlock # noqa - ): - # render subgraph - with digraph.subgraph(name=f"cluster_{name}") as subg: - color = "blue" - if regionblock.kind == "branch": - color = "green" - if regionblock.kind == "tail": - color = "purple" - if regionblock.kind == "head": - color = "red" - subg.attr(color=color, label=regionblock.name) - for name, block in regionblock.subregion.graph.items(): - self.render_block(subg, name, block) - - def render_basic_block( - self, digraph: "Digraph", name: str, block: BasicBlock # noqa - ): - if name.startswith("python_bytecode"): - instlist = block.get_instructions(self.bcmap) - body = name + r"\l" - body += r"\l".join( - [f"{inst.offset:3}: {inst.opname}" for inst in instlist] + [""] - ) - else: - body = name + r"\l" - - digraph.node(str(name), shape="rect", label=body) - - def render_control_variable_block( - self, digraph: "Digraph", name: str, block: BasicBlock # noqa - ): - if isinstance(name, str): - body = name + r"\l" - body += r"\l".join( - (f"{k} = {v}" for k, v in block.variable_assignment.items()) - ) - else: - raise Exception("Unknown name type: " + name) - digraph.node(str(name), shape="rect", label=body) - - def render_branching_block( - self, digraph: "Digraph", name: str, block: BasicBlock # noqa - ): - if isinstance(name, str): - body = name + r"\l" - body += rf"variable: {block.variable}\l" - body += r"\l".join( - (f"{k}=>{v}" for k, v in block.branch_value_table.items()) - ) - else: - raise Exception("Unknown name type: " + name) - digraph.node(str(name), shape="rect", label=body) - - def render_byteflow(self, byteflow: ByteFlow): - """Renders the provided `ByteFlow` object.""" - self.bcmap_from_bytecode(byteflow.bc) - # render nodes - for name, block in byteflow.scfg.graph.items(): - self.render_block(self.g, name, block) - self.render_edges(byteflow.scfg) - return self.g - - def bcmap_from_bytecode(self, bc: dis.Bytecode): - self.bcmap: Dict[int, dis.Instruction] = SCFG.bcmap_from_bytecode(bc) - - -class SCFGRenderer(BaseRenderer): - """The `SCFGRenderer` class is used to render the visual - representation of a `SCFG` object. - - Attributes - ---------- - g: Digraph - The graphviz Digraph object that represents the entire graph upon - which the current SCFG is to be rendered. - - """ - - def __init__(self, scfg: SCFG): - from graphviz import Digraph - - self.g = Digraph() - # render nodes - for name, block in scfg.graph.items(): - self.render_block(self.g, name, block) - self.render_edges(scfg) - def render_region_block( self, digraph: "Digraph", name: str, regionblock: RegionBlock # noqa ): @@ -293,7 +194,7 @@ def view(self, name: str): def render_func(func): """The `render_func`` function takes a `func` parameter as the Python - function to be transformed and rendered and renders the byte flow + function to be transformed and rendered and renders the SCFG representation of the bytecode of the function. Parameters @@ -301,53 +202,40 @@ def render_func(func): func: Python function The Python function for which bytecode is to be rendered. """ - render_flow(ByteFlow.from_bytecode(func)) + render_scfg(SCFG.from_bytecode(func)) -def render_flow(flow): - """Renders multiple ByteFlow representations across various SCFG +def render_scfg(scfg): + """Renders multiple SCFG representations across various transformations. - The `render_flow`` function takes a `flow` parameter as the `ByteFlow` + The `render_scfg`` function takes a `scfg` parameter as the `SCFG` to be transformed and rendered and performs the following operations: - - Renders the pure `ByteFlow` representation of the function using - `ByteFlowRenderer` and displays it as a document named "before". + - Renders the pure `SCFG` representation of the function using + `SCFGRenderer` and displays it as a document named "before". - - Joins the return blocks in the `ByteFlow` object graph and renders + - Joins the return blocks in the `SCFG` object graph and renders the graph, displaying it as a document named "closed". - - Restructures the loops recursively in the `ByteFlow` object graph + - Restructures the loops recursively in the `SCFG` object graph and renders the graph, displaying it as named "loop restructured". - - Restructures the branch recursively in the `ByteFlow` object graph + - Restructures the branch recursively in the `SCFG` object graph and renders the graph, displaying it as named "branch restructured". Parameters ---------- - flow: ByteFlow - The ByteFlow object to be trnasformed and rendered. + scfg: SCFG + The SCFG object to be trnasformed and rendered. """ - ByteFlowRenderer().render_byteflow(flow).view("before") - - cflow = flow._join_returns() - ByteFlowRenderer().render_byteflow(cflow).view("closed") + scfg.view("before") - lflow = cflow._restructure_loop() - ByteFlowRenderer().render_byteflow(lflow).view("loop restructured") + scfg.join_returns() + scfg.view("closed") - bflow = lflow._restructure_branch() - ByteFlowRenderer().render_byteflow(bflow).view("branch restructured") + scfg.restructure_loop() + scfg.view("loop restructured") - -def render_scfg(scfg): - """The `render_scfg` function takes a `scfg` parameter as the SCFG - object to be transformed and rendered and renders the graphviz - representation of the SCFG. - - Parameters - ---------- - scfg: SCFG - The structured control flow graph (SCFG) to be rendered. - """ - ByteFlowRenderer().render_scfg(scfg).view("scfg") + scfg.restructure_branch() + scfg.view("branch restructured") From 9000b3871c458b49884e92a83068f33b0ac2bc55 Mon Sep 17 00:00:00 2001 From: kc611 Date: Fri, 7 Jul 2023 21:22:22 +0530 Subject: [PATCH 21/22] Adapted tests according to the changes --- numba_rvsdg/tests/test_byteflow.py | 275 ----------------------------- numba_rvsdg/tests/test_figures.py | 26 +-- numba_rvsdg/tests/test_scc.py | 14 +- numba_rvsdg/tests/test_scfg.py | 222 ++++++++++++++++++++++- 4 files changed, 231 insertions(+), 306 deletions(-) delete mode 100644 numba_rvsdg/tests/test_byteflow.py diff --git a/numba_rvsdg/tests/test_byteflow.py b/numba_rvsdg/tests/test_byteflow.py deleted file mode 100644 index d3b54ea5..00000000 --- a/numba_rvsdg/tests/test_byteflow.py +++ /dev/null @@ -1,275 +0,0 @@ -from dis import Bytecode, Instruction, Positions - -import unittest -from numba_rvsdg.core.datastructures.basic_block import PythonBytecodeBlock -from numba_rvsdg.core.datastructures.byte_flow import ByteFlow -from numba_rvsdg.core.datastructures.scfg import SCFG, NameGenerator -from numba_rvsdg.core.datastructures.flow_info import FlowInfo -from numba_rvsdg.core.datastructures import block_names - - -def fun(): - x = 1 - return x - - -bytecode = Bytecode(fun) -# If the function definition line changes, just change the variable below, -# rest of it will adjust as long as function remains the same -func_def_line = 11 - - -class TestBCMapFromBytecode(unittest.TestCase): - def test(self): - expected = { - 0: Instruction( - opname="RESUME", - opcode=151, - arg=0, - argval=0, - argrepr="", - offset=0, - starts_line=func_def_line, - is_jump_target=False, - positions=Positions( - lineno=func_def_line, - end_lineno=func_def_line, - col_offset=0, - end_col_offset=0, - ), - ), - 2: Instruction( - opname="LOAD_CONST", - opcode=100, - arg=1, - argval=1, - argrepr="1", - offset=2, - starts_line=func_def_line + 1, - is_jump_target=False, - positions=Positions( - lineno=func_def_line + 1, - end_lineno=func_def_line + 1, - col_offset=8, - end_col_offset=9, - ), - ), - 4: Instruction( - opname="STORE_FAST", - opcode=125, - arg=0, - argval="x", - argrepr="x", - offset=4, - starts_line=None, - is_jump_target=False, - positions=Positions( - lineno=func_def_line + 1, - end_lineno=func_def_line + 1, - col_offset=4, - end_col_offset=5, - ), - ), - 6: Instruction( - opname="LOAD_FAST", - opcode=124, - arg=0, - argval="x", - argrepr="x", - offset=6, - starts_line=func_def_line + 2, - is_jump_target=False, - positions=Positions( - lineno=func_def_line + 2, - end_lineno=func_def_line + 2, - col_offset=11, - end_col_offset=12, - ), - ), - 8: Instruction( - opname="RETURN_VALUE", - opcode=83, - arg=None, - argval=None, - argrepr="", - offset=8, - starts_line=None, - is_jump_target=False, - positions=Positions( - lineno=func_def_line + 2, - end_lineno=func_def_line + 2, - col_offset=4, - end_col_offset=12, - ), - ), - } - received = SCFG.bcmap_from_bytecode(bytecode) - self.assertEqual(expected, received) - - -class TestPythonBytecodeBlock(unittest.TestCase): - def test_constructor(self): - name_gen = NameGenerator() - block = PythonBytecodeBlock( - name=name_gen.new_block_name(block_names.PYTHON_BYTECODE), - begin=0, - end=8, - _jump_targets=(), - ) - self.assertEqual(block.name, "python_bytecode_block_0") - self.assertEqual(block.begin, 0) - self.assertEqual(block.end, 8) - self.assertFalse(block.fallthrough) - self.assertTrue(block.is_exiting) - self.assertEqual(block.jump_targets, ()) - self.assertEqual(block.backedges, ()) - - def test_is_jump_target(self): - name_gen = NameGenerator() - block = PythonBytecodeBlock( - name=name_gen.new_block_name(block_names.PYTHON_BYTECODE), - begin=0, - end=8, - _jump_targets=( - name_gen.new_block_name(block_names.PYTHON_BYTECODE), - ), - ) - self.assertEqual(block.jump_targets, ("python_bytecode_block_1",)) - self.assertFalse(block.is_exiting) - - def test_get_instructions(self): - name_gen = NameGenerator() - block = PythonBytecodeBlock( - name=name_gen.new_block_name(block_names.PYTHON_BYTECODE), - begin=0, - end=8, - _jump_targets=(), - ) - expected = [ - Instruction( - opname="RESUME", - opcode=151, - arg=0, - argval=0, - argrepr="", - offset=0, - starts_line=func_def_line, - is_jump_target=False, - positions=Positions( - lineno=func_def_line, - end_lineno=func_def_line, - col_offset=0, - end_col_offset=0, - ), - ), - Instruction( - opname="LOAD_CONST", - opcode=100, - arg=1, - argval=1, - argrepr="1", - offset=2, - starts_line=func_def_line + 1, - is_jump_target=False, - positions=Positions( - lineno=func_def_line + 1, - end_lineno=func_def_line + 1, - col_offset=8, - end_col_offset=9, - ), - ), - Instruction( - opname="STORE_FAST", - opcode=125, - arg=0, - argval="x", - argrepr="x", - offset=4, - starts_line=None, - is_jump_target=False, - positions=Positions( - lineno=func_def_line + 1, - end_lineno=func_def_line + 1, - col_offset=4, - end_col_offset=5, - ), - ), - Instruction( - opname="LOAD_FAST", - opcode=124, - arg=0, - argval="x", - argrepr="x", - offset=6, - starts_line=func_def_line + 2, - is_jump_target=False, - positions=Positions( - lineno=func_def_line + 2, - end_lineno=func_def_line + 2, - col_offset=11, - end_col_offset=12, - ), - ), - ] - - received = block.get_instructions(SCFG.bcmap_from_bytecode(bytecode)) - self.assertEqual(expected, received) - - -class TestFlowInfo(unittest.TestCase): - def test_constructor(self): - flowinfo = FlowInfo() - self.assertEqual(len(flowinfo.block_offsets), 0) - self.assertEqual(len(flowinfo.jump_insts), 0) - - def test_from_bytecode(self): - expected = FlowInfo( - block_offsets={0}, jump_insts={8: ()}, last_offset=8 - ) - - received = FlowInfo.from_bytecode(bytecode) - self.assertEqual(expected, received) - - def test_build_basic_blocks(self): - name_gen = NameGenerator() - new_name = name_gen.new_block_name(block_names.PYTHON_BYTECODE) - expected = SCFG( - graph={ - new_name: PythonBytecodeBlock( - name=new_name, - begin=0, - end=10, - _jump_targets=(), - ) - } - ) - received = FlowInfo.from_bytecode(bytecode).build_basicblocks() - self.assertEqual(expected, received) - - -class TestByteFlow(unittest.TestCase): - def test_constructor(self): - byteflow = ByteFlow([], []) - self.assertEqual(len(byteflow.bc), 0) - self.assertEqual(len(byteflow.scfg), 0) - - def test_from_bytecode(self): - name_gen = NameGenerator() - new_name = name_gen.new_block_name(block_names.PYTHON_BYTECODE) - scfg = SCFG( - graph={ - new_name: PythonBytecodeBlock( - name=new_name, - begin=0, - end=10, - _jump_targets=(), - ) - } - ) - expected = ByteFlow(bc=bytecode, scfg=scfg) - received = ByteFlow.from_bytecode(fun) - self.assertEqual(expected.scfg, received.scfg) - - -if __name__ == "__main__": - unittest.main() diff --git a/numba_rvsdg/tests/test_figures.py b/numba_rvsdg/tests/test_figures.py index 249e0f03..63d47893 100644 --- a/numba_rvsdg/tests/test_figures.py +++ b/numba_rvsdg/tests/test_figures.py @@ -1,5 +1,3 @@ -from numba_rvsdg.core.datastructures.byte_flow import ByteFlow -from numba_rvsdg.core.datastructures.flow_info import FlowInfo from numba_rvsdg.core.datastructures.scfg import SCFG from numba_rvsdg.tests.test_utils import SCFGComparator import dis @@ -412,7 +410,7 @@ class TestBahmannFigures(SCFGComparator): def test_figure_3(self): # Figure 3 of the paper - # fake bytecode just good enough for FlowInfo + # fake bytecode just good enough for SCFG bc = [ dis.Instruction("OP", 1, None, None, "", 0, None, False), dis.Instruction( @@ -437,18 +435,16 @@ def test_figure_3(self): "RETURN_VALUE", 1, None, None, "", 20, None, False ), ] - flow = FlowInfo.from_bytecode(bc) - scfg = flow.build_basicblocks() - byteflow = ByteFlow(bc=bc, scfg=scfg) - byteflow.restructure() + scfg = SCFG.from_bytecode(bc) + scfg.restructure() x, _ = SCFG.from_yaml(fig_3_yaml) - self.assertSCFGEqual(x, byteflow.scfg) + self.assertSCFGEqual(x, scfg) def test_figure_4(self): # Figure 4 of the paper - # fake bytecode just good enough for FlowInfo + # fake bytecode just good enough for SCFG bc = [ dis.Instruction("OP", 1, None, None, "", 0, None, False), dis.Instruction( @@ -470,14 +466,8 @@ def test_figure_4(self): "RETURN_VALUE", 1, None, None, "", 18, None, False ), ] - flow = FlowInfo.from_bytecode(bc) - scfg = flow.build_basicblocks() - byteflow = ByteFlow(bc=bc, scfg=scfg) - byteflow.restructure() + scfg = SCFG.from_bytecode(bc) + scfg.restructure() x, _ = SCFG.from_yaml(fig_4_yaml) - self.assertSCFGEqual(x, byteflow.scfg) - - -x = TestBahmannFigures() -x.test_figure_3() + self.assertSCFGEqual(x, scfg) diff --git a/numba_rvsdg/tests/test_scc.py b/numba_rvsdg/tests/test_scc.py index b3ba2d75..60169e59 100644 --- a/numba_rvsdg/tests/test_scc.py +++ b/numba_rvsdg/tests/test_scc.py @@ -1,5 +1,5 @@ -from numba_rvsdg.core.datastructures.byte_flow import ByteFlow -from numba_rvsdg.rendering.rendering import render_flow +from numba_rvsdg.core.datastructures.scfg import SCFG +from numba_rvsdg.rendering.rendering import render_scfg def scc(G): @@ -46,14 +46,14 @@ def scc(G): return out -def make_flow(func): - return ByteFlow.from_bytecode(func) +def make_scfg(func): + return SCFG.from_bytecode(func) def test_scc(): - f = make_flow(scc) - f.restructure() + scfg = make_scfg(scc) + scfg.restructure() if __name__ == "__main__": - render_flow(make_flow(scc)) + render_scfg(make_scfg(scc)) diff --git a/numba_rvsdg/tests/test_scfg.py b/numba_rvsdg/tests/test_scfg.py index 02149efc..17bb5d72 100644 --- a/numba_rvsdg/tests/test_scfg.py +++ b/numba_rvsdg/tests/test_scfg.py @@ -1,4 +1,4 @@ -from unittest import main, TestCase +from unittest import TestCase from textwrap import dedent from numba_rvsdg.core.datastructures.scfg import SCFG, NameGenerator @@ -8,8 +8,10 @@ RegionBlock, PythonBytecodeBlock, ) -from numba_rvsdg.core.datastructures.byte_flow import ByteFlow from numba_rvsdg.core.datastructures import block_names +from dis import Bytecode, Instruction, Positions + +import unittest class TestSCFGConversion(SCFGComparator): @@ -191,18 +193,226 @@ def foo(n): self.foo = foo def test_concealed_region_view_iter(self): - flow = ByteFlow.from_bytecode(self.foo) - flow._restructure_loop() + scfg = SCFG.from_bytecode(self.foo) + scfg._restructure_loop() expected = [ ("python_bytecode_block_0", PythonBytecodeBlock), ("loop_region_0", RegionBlock), ("python_bytecode_block_3", PythonBytecodeBlock), ] received = list( - ((k, type(v)) for k, v in flow.scfg.concealed_region_view.items()) + ((k, type(v)) for k, v in scfg.concealed_region_view.items()) + ) + self.assertEqual(expected, received) + + +def fun(): + x = 1 + return x + + +bytecode = Bytecode(fun) +# If the function definition line changes, just change the variable below, +# rest of it will adjust as long as function remains the same +func_def_line = 11 + + +class TestBCMapFromBytecode(unittest.TestCase): + def test(self): + expected = { + 0: Instruction( + opname="RESUME", + opcode=151, + arg=0, + argval=0, + argrepr="", + offset=0, + starts_line=func_def_line, + is_jump_target=False, + positions=Positions( + lineno=func_def_line, + end_lineno=func_def_line, + col_offset=0, + end_col_offset=0, + ), + ), + 2: Instruction( + opname="LOAD_CONST", + opcode=100, + arg=1, + argval=1, + argrepr="1", + offset=2, + starts_line=func_def_line + 1, + is_jump_target=False, + positions=Positions( + lineno=func_def_line + 1, + end_lineno=func_def_line + 1, + col_offset=8, + end_col_offset=9, + ), + ), + 4: Instruction( + opname="STORE_FAST", + opcode=125, + arg=0, + argval="x", + argrepr="x", + offset=4, + starts_line=None, + is_jump_target=False, + positions=Positions( + lineno=func_def_line + 1, + end_lineno=func_def_line + 1, + col_offset=4, + end_col_offset=5, + ), + ), + 6: Instruction( + opname="LOAD_FAST", + opcode=124, + arg=0, + argval="x", + argrepr="x", + offset=6, + starts_line=func_def_line + 2, + is_jump_target=False, + positions=Positions( + lineno=func_def_line + 2, + end_lineno=func_def_line + 2, + col_offset=11, + end_col_offset=12, + ), + ), + 8: Instruction( + opname="RETURN_VALUE", + opcode=83, + arg=None, + argval=None, + argrepr="", + offset=8, + starts_line=None, + is_jump_target=False, + positions=Positions( + lineno=func_def_line + 2, + end_lineno=func_def_line + 2, + col_offset=4, + end_col_offset=12, + ), + ), + } + received = SCFG.bcmap_from_bytecode(bytecode) + self.assertEqual(expected, received) + + +class TestPythonBytecodeBlock(unittest.TestCase): + def test_constructor(self): + name_gen = NameGenerator() + block = PythonBytecodeBlock( + name=name_gen.new_block_name(block_names.PYTHON_BYTECODE), + begin=0, + end=8, + _jump_targets=(), + ) + self.assertEqual(block.name, "python_bytecode_block_0") + self.assertEqual(block.begin, 0) + self.assertEqual(block.end, 8) + self.assertFalse(block.fallthrough) + self.assertTrue(block.is_exiting) + self.assertEqual(block.jump_targets, ()) + self.assertEqual(block.backedges, ()) + + def test_is_jump_target(self): + name_gen = NameGenerator() + block = PythonBytecodeBlock( + name=name_gen.new_block_name(block_names.PYTHON_BYTECODE), + begin=0, + end=8, + _jump_targets=( + name_gen.new_block_name(block_names.PYTHON_BYTECODE), + ), ) + self.assertEqual(block.jump_targets, ("python_bytecode_block_1",)) + self.assertFalse(block.is_exiting) + + def test_get_instructions(self): + name_gen = NameGenerator() + block = PythonBytecodeBlock( + name=name_gen.new_block_name(block_names.PYTHON_BYTECODE), + begin=0, + end=8, + _jump_targets=(), + ) + expected = [ + Instruction( + opname="RESUME", + opcode=151, + arg=0, + argval=0, + argrepr="", + offset=0, + starts_line=func_def_line, + is_jump_target=False, + positions=Positions( + lineno=func_def_line, + end_lineno=func_def_line, + col_offset=0, + end_col_offset=0, + ), + ), + Instruction( + opname="LOAD_CONST", + opcode=100, + arg=1, + argval=1, + argrepr="1", + offset=2, + starts_line=func_def_line + 1, + is_jump_target=False, + positions=Positions( + lineno=func_def_line + 1, + end_lineno=func_def_line + 1, + col_offset=8, + end_col_offset=9, + ), + ), + Instruction( + opname="STORE_FAST", + opcode=125, + arg=0, + argval="x", + argrepr="x", + offset=4, + starts_line=None, + is_jump_target=False, + positions=Positions( + lineno=func_def_line + 1, + end_lineno=func_def_line + 1, + col_offset=4, + end_col_offset=5, + ), + ), + Instruction( + opname="LOAD_FAST", + opcode=124, + arg=0, + argval="x", + argrepr="x", + offset=6, + starts_line=func_def_line + 2, + is_jump_target=False, + positions=Positions( + lineno=func_def_line + 2, + end_lineno=func_def_line + 2, + col_offset=11, + end_col_offset=12, + ), + ), + ] + + received = block.get_instructions(SCFG.bcmap_from_bytecode(bytecode)) self.assertEqual(expected, received) if __name__ == "__main__": - main() + unittest.main() From 709453e12cca47f63ec04584540e088d6205a27b Mon Sep 17 00:00:00 2001 From: kc611 Date: Fri, 7 Jul 2023 21:22:33 +0530 Subject: [PATCH 22/22] Adapted simulator to changes --- numba_rvsdg/tests/simulator.py | 17 ++--- numba_rvsdg/tests/test_simulate.py | 117 ++++++++++++----------------- 2 files changed, 54 insertions(+), 80 deletions(-) diff --git a/numba_rvsdg/tests/simulator.py b/numba_rvsdg/tests/simulator.py index 984c3fd7..bbeea815 100644 --- a/numba_rvsdg/tests/simulator.py +++ b/numba_rvsdg/tests/simulator.py @@ -1,6 +1,5 @@ from collections import ChainMap from dis import Instruction -from numba_rvsdg.core.datastructures.byte_flow import ByteFlow from numba_rvsdg.core.datastructures.basic_block import ( PythonBytecodeBlock, RegionBlock, @@ -23,8 +22,8 @@ class Simulator: Parameters ---------- - flow: ByteFlow - The ByteFlow to be simulated. + scfg: SCFG + The SCFG to be simulated. globals: dict of any The globals to become available during simulation @@ -49,12 +48,10 @@ class Simulator: """ - def __init__(self, flow: ByteFlow, globals: dict): - self.flow = flow - self.scfg = flow.scfg + def __init__(self, scfg, globals: dict): + self.scfg = scfg self.globals = ChainMap(globals, builtins.__dict__) - self.bcmap = {inst.offset: inst for inst in flow.bc} self.varmap = dict() self.ctrl_varmap = dict() self.stack = [] @@ -70,7 +67,7 @@ def get_block(self, name: str): `region_stack`. That is to say, if we have recursed into regions, the BasicBlock is returned from the current region (the top region of the region_stack). Otherwise the BasicBlock is returned from the initial - ByteFlow supplied to the simulator. The method `run_RegionBlock` is + SCFG supplied to the simulator. The method `run_RegionBlock` is responsible for maintaining the `region_stack`. Parameters @@ -87,9 +84,9 @@ def get_block(self, name: str): # Recursed into regions, return block from region if self.region_stack: return self.region_stack[-1].subregion[name] - # Not recursed into regions, return block from ByteFlow + # Not recursed into regions, return block from SCFG else: - return self.flow.scfg[name] + return self.scfg[name] def run(self, args): """Run the given simulator with given args. diff --git a/numba_rvsdg/tests/test_simulate.py b/numba_rvsdg/tests/test_simulate.py index 9333bfd8..1e122f3b 100644 --- a/numba_rvsdg/tests/test_simulate.py +++ b/numba_rvsdg/tests/test_simulate.py @@ -1,35 +1,12 @@ -from numba_rvsdg.core.datastructures.byte_flow import ByteFlow +from numba_rvsdg.core.datastructures.scfg import SCFG from numba_rvsdg.tests.simulator import Simulator import unittest -# flow = ByteFlow.from_bytecode(foo) -# #pprint(flow.scfg) -# flow.restructure() -# #pprint(flow.scfg) -# # pprint(rtsflow.scfg) -# ByteFlowRenderer().render_byteflow(flow).view() -# print(dis(foo)) -# -# sim = Simulator(flow, foo.__globals__) -# ret = sim.run(dict(x=1)) -# assert ret == foo(x=1) -# -# #sim = Simulator(flow, foo.__globals__) -# #ret = sim.run(dict(x=100)) -# #assert ret == foo(x=100) - -# You can use the following snipppet to visually debug the restructured -# byteflow: -# -# ByteFlowRenderer().render_byteflow(flow).view() -# -# - class SimulatorTest(unittest.TestCase): - def _run(self, func, flow, kwargs): + def _run(self, func, scfg, kwargs): with self.subTest(): - sim = Simulator(flow, func.__globals__) + sim = Simulator(scfg, func.__globals__) self.assertEqual(sim.run(kwargs), func(**kwargs)) def test_simple_branch(self): @@ -41,13 +18,13 @@ def foo(x): c += 1000 return c - flow = ByteFlow.from_bytecode(foo) - flow.restructure() + scfg = SCFG.from_bytecode(foo) + scfg.restructure() # if case - self._run(foo, flow, {"x": 1}) + self._run(foo, scfg, {"x": 1}) # else case - self._run(foo, flow, {"x": 0}) + self._run(foo, scfg, {"x": 0}) def test_simple_for_loop(self): def foo(x): @@ -56,15 +33,15 @@ def foo(x): c += i return c - flow = ByteFlow.from_bytecode(foo) - flow.restructure() + scfg = SCFG.from_bytecode(foo) + scfg.restructure() # loop bypass case - self._run(foo, flow, {"x": 0}) + self._run(foo, scfg, {"x": 0}) # loop case - self._run(foo, flow, {"x": 2}) + self._run(foo, scfg, {"x": 2}) # extended loop case - self._run(foo, flow, {"x": 100}) + self._run(foo, scfg, {"x": 100}) def test_simple_while_loop(self): def foo(x): @@ -75,15 +52,15 @@ def foo(x): i += 1 return c - flow = ByteFlow.from_bytecode(foo) - flow.restructure() + scfg = SCFG.from_bytecode(foo) + scfg.restructure() # loop bypass case - self._run(foo, flow, {"x": 0}) + self._run(foo, scfg, {"x": 0}) # loop case - self._run(foo, flow, {"x": 2}) + self._run(foo, scfg, {"x": 2}) # extended loop case - self._run(foo, flow, {"x": 100}) + self._run(foo, scfg, {"x": 100}) def test_for_loop_with_exit(self): def foo(x): @@ -94,15 +71,15 @@ def foo(x): break return c - flow = ByteFlow.from_bytecode(foo) - flow.restructure() + scfg = SCFG.from_bytecode(foo) + scfg.restructure() # loop bypass case - self._run(foo, flow, {"x": 0}) + self._run(foo, scfg, {"x": 0}) # loop case - self._run(foo, flow, {"x": 2}) + self._run(foo, scfg, {"x": 2}) # break case - self._run(foo, flow, {"x": 15}) + self._run(foo, scfg, {"x": 15}) def test_nested_for_loop_with_break_and_continue(self): def foo(x): @@ -118,17 +95,17 @@ def foo(x): break return c - flow = ByteFlow.from_bytecode(foo) - flow.restructure() + scfg = SCFG.from_bytecode(foo) + scfg.restructure() # no loop - self._run(foo, flow, {"x": 0}) + self._run(foo, scfg, {"x": 0}) # only continue - self._run(foo, flow, {"x": 1}) + self._run(foo, scfg, {"x": 1}) # no break - self._run(foo, flow, {"x": 4}) + self._run(foo, scfg, {"x": 4}) # will break - self._run(foo, flow, {"x": 5}) + self._run(foo, scfg, {"x": 5}) def test_for_loop_with_multiple_backedges(self): def foo(x): @@ -142,26 +119,26 @@ def foo(x): c += 1 return c - flow = ByteFlow.from_bytecode(foo) - flow.restructure() + scfg = SCFG.from_bytecode(foo) + scfg.restructure() # loop bypass - self._run(foo, flow, {"x": 0}) + self._run(foo, scfg, {"x": 0}) # default on every iteration - self._run(foo, flow, {"x": 2}) + self._run(foo, scfg, {"x": 2}) # adding 100, via the if clause - self._run(foo, flow, {"x": 4}) + self._run(foo, scfg, {"x": 4}) # adding 1000, via the elif clause - self._run(foo, flow, {"x": 7}) + self._run(foo, scfg, {"x": 7}) def test_andor(self): def foo(x, y): return (x > 0 and x < 10) or (y > 0 and y < 10) - flow = ByteFlow.from_bytecode(foo) - flow.restructure() + scfg = SCFG.from_bytecode(foo) + scfg.restructure() - self._run(foo, flow, {"x": 5, "y": 5}) + self._run(foo, scfg, {"x": 5, "y": 5}) def test_while_count(self): def foo(s, e): @@ -172,23 +149,23 @@ def foo(s, e): i += 1 return c - flow = ByteFlow.from_bytecode(foo) - flow.restructure() + scfg = SCFG.from_bytecode(foo) + scfg.restructure() # no looping - self._run(foo, flow, {"s": 0, "e": 0}) + self._run(foo, scfg, {"s": 0, "e": 0}) # single execution - self._run(foo, flow, {"s": 0, "e": 1}) + self._run(foo, scfg, {"s": 0, "e": 1}) # mutiple iterations - self._run(foo, flow, {"s": 0, "e": 5}) + self._run(foo, scfg, {"s": 0, "e": 5}) # no looping - self._run(foo, flow, {"s": 23, "e": 0}) + self._run(foo, scfg, {"s": 23, "e": 0}) # single execution - self._run(foo, flow, {"s": 23, "e": 24}) + self._run(foo, scfg, {"s": 23, "e": 24}) # mutiple iterations - self._run(foo, flow, {"s": 23, "e": 28}) + self._run(foo, scfg, {"s": 23, "e": 28}) -# if __name__ == "__main__": -# unittest.main() +if __name__ == "__main__": + unittest.main()