diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..77fd050 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,43 @@ +exclude: | + (?x)^( + docs/.* + )$ +repos: + # Checks for debug statements and merge conflicts + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: debug-statements + - id: check-merge-conflict + # Pyupgrade: upgrades older python syntax to newer one + - repo: https://github.com/asottile/pyupgrade + rev: v3.3.1 + hooks: + - id: pyupgrade + args: ["--py38-plus"] + # Black + - repo: https://github.com/psf/black + rev: 23.1.0 + hooks: + - id: black + language_version: python3 + args: ["--line-length=79"] + # Autoflake: removes unused imports and variables + - repo: https://github.com/humitos/mirrors-autoflake.git + rev: v1.1 + hooks: + - id: autoflake + args: ['--in-place', '--remove-all-unused-imports', '--remove-unused-variable'] + # Manual Linting: Flake 8 + - repo: https://github.com/pycqa/flake8 + rev: 6.0.0 + hooks: + - id: flake8 + # Static Type checking: MyPy + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.1.1 + hooks: + - id: mypy + additional_dependencies: + - types-filelock + - types-setuptools diff --git a/numba_rvsdg/core/datastructures/basic_block.py b/numba_rvsdg/core/datastructures/basic_block.py index a823277..263e09c 100644 --- a/numba_rvsdg/core/datastructures/basic_block.py +++ b/numba_rvsdg/core/datastructures/basic_block.py @@ -1,7 +1,6 @@ import dis -from collections import ChainMap from typing import Tuple, Dict, List -from dataclasses import dataclass, field, replace +from dataclasses import dataclass, replace from numba_rvsdg.core.utils import _next_inst_offset @@ -202,35 +201,35 @@ class SyntheticBlock(BasicBlock): """The SyntheticBlock represents a artificially added block in a structured control flow graph (SCFG). """ - pass + @dataclass(frozen=True) class SyntheticExit(SyntheticBlock): """The SyntheticExit class represents a artificially added exit block in a structured control flow graph (SCFG). """ - pass + @dataclass(frozen=True) class SyntheticReturn(SyntheticBlock): """The SyntheticReturn class represents a artificially added return block in a structured control flow graph (SCFG). """ - pass + @dataclass(frozen=True) class SyntheticTail(SyntheticBlock): """The SyntheticTail class represents a artificially added tail block in a structured control flow graph (SCFG). """ - pass + @dataclass(frozen=True) class SyntheticFill(SyntheticBlock): """The SyntheticFill class represents a artificially added fill block in a structured control flow graph (SCFG). """ - pass + @dataclass(frozen=True) class SyntheticAssignment(SyntheticBlock): @@ -247,6 +246,7 @@ class SyntheticAssignment(SyntheticBlock): the variable name to the value that is is assigned when the block is executed. """ + variable_assignment: dict = None @@ -264,6 +264,7 @@ class SyntheticBranch(SyntheticBlock): The value table maps variable values to the repective jump target to be executed on the basis of that value. """ + variable: str = None branch_value_table: dict = None @@ -320,23 +321,20 @@ class SyntheticHead(SyntheticBranch): """The SyntheticHead class represents a artificially added head block in a structured control flow graph (SCFG). """ - pass @dataclass(frozen=True) class SyntheticExitingLatch(SyntheticBranch): - """The SyntheticExitingLatch class represents a artificially added exiting latch - block in a structured control flow graph (SCFG). + """The SyntheticExitingLatch class represents a artificially added + exiting latch block in a structured control flow graph (SCFG). """ - pass @dataclass(frozen=True) class SyntheticExitBranch(SyntheticBranch): - """The SyntheticExitBranch class represents a artificially added exit branch - block in a structured control flow graph (SCFG). + """The SyntheticExitBranch class represents a artificially added + exit branch block in a structured control flow graph (SCFG). """ - pass @dataclass(frozen=True) @@ -364,7 +362,7 @@ class RegionBlock(BasicBlock): kind: str = None parent_region: "RegionBlock" = None header: str = None - subregion: "SCFG" = None + subregion: "SCFG" = None # noqa exiting: str = None def replace_header(self, new_header): @@ -384,5 +382,5 @@ def replace_exiting(self, new_exiting): ---------- new_exiting: str The new exiting block of the region represented by the RegionBlock. - """ + """ object.__setattr__(self, "exiting", new_exiting) diff --git a/numba_rvsdg/core/datastructures/block_names.py b/numba_rvsdg/core/datastructures/block_names.py index 9855307..07ac40d 100644 --- a/numba_rvsdg/core/datastructures/block_names.py +++ b/numba_rvsdg/core/datastructures/block_names.py @@ -1,4 +1,3 @@ - BASIC = "basic" PYTHON_BYTECODE = "python_bytecode" diff --git a/numba_rvsdg/core/datastructures/byte_flow.py b/numba_rvsdg/core/datastructures/byte_flow.py index ecb4555..9b1ebb0 100644 --- a/numba_rvsdg/core/datastructures/byte_flow.py +++ b/numba_rvsdg/core/datastructures/byte_flow.py @@ -7,7 +7,10 @@ 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 +from numba_rvsdg.core.transformations import ( + restructure_loop, + restructure_branch, +) @dataclass(frozen=True) @@ -97,10 +100,10 @@ def _restructure_loop(self): 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 - the restructuring operation to both the main SCFG and any - subregions within it. It returns a new ByteFlow object with + Creates a deep copy of the SCFG and performs the operation to + restructure branch constructs within the control flow. 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 diff --git a/numba_rvsdg/core/datastructures/flow_info.py b/numba_rvsdg/core/datastructures/flow_info.py index 8afb9ca..562bf86 100644 --- a/numba_rvsdg/core/datastructures/flow_info.py +++ b/numba_rvsdg/core/datastructures/flow_info.py @@ -115,9 +115,10 @@ def build_basicblocks(self: "FlowInfo", end_offset=None) -> "SCFG": scfg = SCFG() offsets = sorted(self.block_offsets) # enumerate names - names = dict( - (offset, scfg.name_gen.new_block_name(block_names.PYTHON_BYTECODE)) for offset in offsets - ) + 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) diff --git a/numba_rvsdg/core/datastructures/scfg.py b/numba_rvsdg/core/datastructures/scfg.py index 7965267..16f9607 100644 --- a/numba_rvsdg/core/datastructures/scfg.py +++ b/numba_rvsdg/core/datastructures/scfg.py @@ -19,28 +19,30 @@ ) from numba_rvsdg.core.datastructures import block_names + @dataclass(frozen=True) class NameGenerator: """Unique Name Generator. - The NameGenerator class is responsible for generating unique names + The NameGenerator class is responsible for generating unique names for blocks, regions, and variables within the SCFG. Attributes ---------- kinds: dict[str, int] - A dictionary that keeps track of the current index for each kind of name. + A dictionary that keeps track of the current index for each kind + of name. """ kinds: dict[str, int] = field(default_factory=dict) def new_block_name(self, kind: str) -> str: - """Generate a new unique name for a block of the specified kind. + """Generate a new unique name for a block of the specified kind. This method checks if the given string 'kind' already exists - in the kinds dictionary attribute. If it exists, the respective - index is incremented, if it doesn't then a new index (starting - from zero) is asigned to the given kind. This ensures that + in the kinds dictionary attribute. If it exists, the respective + index is incremented, if it doesn't then a new index (starting + from zero) is asigned to the given kind. This ensures that the given name is unique by a combination of it's kind and it's index. It returns the generated name. @@ -56,21 +58,21 @@ def new_block_name(self, kind: str) -> str: """ if kind in self.kinds.keys(): idx = self.kinds[kind] - name = str(kind) + '_block_' + str(idx) + name = str(kind) + "_block_" + str(idx) self.kinds[kind] = idx + 1 else: idx = 0 - name = str(kind) + '_block_' + str(idx) + name = str(kind) + "_block_" + str(idx) self.kinds[kind] = idx + 1 return name def new_region_name(self, kind: str) -> str: - """Generate a new unique name for a region of the specified kind. + """Generate a new unique name for a region of the specified kind. This method checks if the given string 'kind' already exists - in the kinds dictionary attribute. If it exists, the respective - index is incremented, if it doesn't then a new index (starting - from zero) is asigned to the given kind. This ensures that + in the kinds dictionary attribute. If it exists, the respective + index is incremented, if it doesn't then a new index (starting + from zero) is asigned to the given kind. This ensures that the given name is unique by a combination of it's kind and it's index. It returns the generated name. @@ -86,23 +88,23 @@ def new_region_name(self, kind: str) -> str: """ if kind in self.kinds.keys(): idx = self.kinds[kind] - name = str(kind) + '_region_' + str(idx) + name = str(kind) + "_region_" + str(idx) self.kinds[kind] = idx + 1 else: idx = 0 - name = str(kind) + '_region_' + str(idx) + name = str(kind) + "_region_" + str(idx) self.kinds[kind] = idx + 1 return name def new_var_name(self, kind: str) -> str: - """Generate a new unique name for a variable of the specified kind. + """Generate a new unique name for a variable of the specified kind. This method checks if the given string 'kind' already exists - in the kinds dictionary attribute. If it exists, the respective - index is incremented, if it doesn't then a new index (starting - from zero) is asigned to the given kind. This ensures that - the given name is unique by a combination of it's kind and it's index. - It returns the generated name. + in the kinds dictionary attribute. If it exists, the respective + index is incremented, if it doesn't then a new index (starting + from zero) is asigned to the given kind. This ensures that + the given name is unique by a combination of it's kind and it's + index. It returns the generated name. Parameters ---------- @@ -116,11 +118,11 @@ def new_var_name(self, kind: str) -> str: """ if kind in self.kinds.keys(): idx = self.kinds[kind] - name = str(kind) + '_var_' + str(idx) + name = str(kind) + "_var_" + str(idx) self.kinds[kind] = idx + 1 else: idx = 0 - name = str(kind) + '_var_' + str(idx) + name = str(kind) + "_var_" + str(idx) self.kinds[kind] = idx + 1 return name @@ -129,15 +131,18 @@ def new_var_name(self, kind: str) -> str: class SCFG: """SCFG (Structured Control Flow Graph) class. - The SCFG class represents a map of names to blocks within the control flow graph. + The SCFG class represents a map of names to blocks within the control + flow graph. Attributes ---------- graph: Dict[str, BasicBlock] - A dictionary that maps names to corresponding BasicBlock objects within the control flow graph. + A dictionary that maps names to corresponding BasicBlock objects + within the control flow graph. name_gen: NameGenerator - A NameGenerator object that provides unique names for blocks, regions, and variables. + A NameGenerator object that provides unique names for blocks, + regions, and variables. """ graph: Dict[str, BasicBlock] = field(default_factory=dict) @@ -151,9 +156,14 @@ class SCFG: def __post_init__(self): name = self.name_gen.new_region_name("meta") - new_region = RegionBlock(name=name, kind="meta", header=None, - exiting=None, parent_region=None, - subregion=self) + new_region = RegionBlock( + name=name, + kind="meta", + header=None, + exiting=None, + parent_region=None, + subregion=self, + ) object.__setattr__(self, "region", new_region) def __getitem__(self, index): @@ -191,7 +201,7 @@ def __iter__(self): """Returns an iterator over the blocks in the SCFG. Returns an iterator that yields the names and corresponding blocks - in the SCFG. It follows a breadth-first search + in the SCFG. It follows a breadth-first search traversal starting from the head block. Returns @@ -220,16 +230,16 @@ def __iter__(self): continue # yield the name, block combo yield (name, block) - # if this is a region, recursively yield everything from that region + # If this is a region, recursively yield everything from that + # specific region. if type(block) == RegionBlock: - for i in block.subregion: - yield i + yield from block.subregion # finally add any jump_targets to the list of names to visit to_visit.extend(block.jump_targets) @property def concealed_region_view(self): - """A property that returns a ConcealedRegionView object, representing + """A property that returns a ConcealedRegionView object, representing a concealed view of the control flow graph. Returns @@ -242,8 +252,8 @@ def concealed_region_view(self): def exclude_blocks(self, exclude_blocks: Set[str]) -> Iterator[str]: """Returns an iterator over the blocks in the SCFG with exclusions. - Returns an iterator over all nodes (blocks) in the control flow graph - that are not present in the exclude_blocks set. It filters out the + Returns an iterator over all nodes (blocks) in the control flow graph + that are not present in the exclude_blocks set. It filters out the excluded blocks and yields the remaining blocks. Parameters @@ -281,10 +291,11 @@ def find_head(self) -> str: return next(iter(heads)) def compute_scc(self) -> List[Set[str]]: - """Computes the strongly connected components (SCC) of the current SCFG. + """Computes the strongly connected components (SCC) of the current + SCFG. - This method of SCFG computes the strongly connected components of the - graph using Tarjan's algorithm. The implementation is at the + This method of SCFG computes the strongly connected components of + the graph using Tarjan's algorithm. The implementation is at the scc function from the numba_rvsdg.networkx_vendored.scc module. It returns a list of sets, where each set represents an SCC in the graph. SCCs are useful for detecting loops in the graph. @@ -337,7 +348,9 @@ def find_headers_and_entries( headers: Set[str] = set() for outside in self.exclude_blocks(subgraph): - nodes_jump_in_loop = subgraph.intersection(self.graph[outside]._jump_targets) + nodes_jump_in_loop = subgraph.intersection( + self.graph[outside]._jump_targets + ) headers.update(nodes_jump_in_loop) if nodes_jump_in_loop: entries.add(outside) @@ -350,7 +363,9 @@ def find_headers_and_entries( # to it's parent region block's graph. if self.region.kind != "meta": parent_region = self.region.parent_region - _, entries = parent_region.subregion.find_headers_and_entries({self.region.name}) + _, entries = parent_region.subregion.find_headers_and_entries( + {self.region.name} + ) return sorted(headers), sorted(entries) def find_exiting_and_exits( @@ -449,8 +464,11 @@ def remove_blocks(self, names: Set[str]): del self.graph[name] def insert_block( - self, new_name: str, predecessors: Set[str], successors: Set[str], - block_type: SyntheticBlock + self, + new_name: str, + predecessors: Set[str], + successors: Set[str], + block_type: SyntheticBlock, ): """Inserts a new synthetic block into the SCFG between the given successors and predecessors. @@ -500,7 +518,10 @@ def insert_block( self.add_block(block.replace_jump_targets(jump_targets=tuple(jt))) def insert_SyntheticExit( - self, new_name: str, predecessors: Set[str], successors: Set[str], + self, + new_name: str, + predecessors: Set[str], + successors: Set[str], ): """Inserts a synthetic exit block into the SCFG. Parameters same as insert_block method. @@ -512,7 +533,10 @@ def insert_SyntheticExit( self.insert_block(new_name, predecessors, successors, SyntheticExit) def insert_SyntheticTail( - self, new_name: str, predecessors: Set[str], successors: Set[str], + self, + new_name: str, + predecessors: Set[str], + successors: Set[str], ): """Inserts a synthetic tail block into the SCFG. Parameters same as insert_block method. @@ -524,7 +548,10 @@ def insert_SyntheticTail( self.insert_block(new_name, predecessors, successors, SyntheticTail) def insert_SyntheticReturn( - self, new_name: str, predecessors: Set[str], successors: Set[str], + self, + new_name: str, + predecessors: Set[str], + successors: Set[str], ): """Inserts a synthetic return block into the SCFG. Parameters same as insert_block method. @@ -536,7 +563,10 @@ def insert_SyntheticReturn( self.insert_block(new_name, predecessors, successors, SyntheticReturn) def insert_SyntheticFill( - self, new_name: str, predecessors: Set[str], successors: Set[str], + self, + new_name: str, + predecessors: Set[str], + successors: Set[str], ): """Inserts a synthetic fill block into the SCFG. Parameters same as insert_block method. @@ -574,7 +604,9 @@ 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_names.SYNTH_ASSIGN) + synth_assign = self.name_gen.new_block_name( + block_names.SYNTH_ASSIGN + ) variable_assignment = {} variable_assignment[branch_variable] = branch_variable_value synth_assign_block = SyntheticAssignment( @@ -593,7 +625,9 @@ def insert_block_and_control_blocks( 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.pop(name).replace_jump_targets( + jump_targets=tuple(jt) + ) ) # initialize new block, which will hold the branching table new_block = SyntheticHead( @@ -613,11 +647,17 @@ def join_returns(self): predescessors and no successors respectively. """ # for all nodes that contain a return - return_nodes = [node for node in self.graph if self.graph[node].is_exiting] + return_nodes = [ + node for node in self.graph if self.graph[node].is_exiting + ] # 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) - self.insert_SyntheticReturn(return_solo_name, return_nodes, tuple()) + return_solo_name = self.name_gen.new_block_name( + block_names.SYNTH_RETURN + ) + self.insert_SyntheticReturn( + return_solo_name, return_nodes, tuple() + ) def join_tails_and_exits(self, tails: Set[str], exits: Set[str]): """Joins the tails and exits of the SCFG. @@ -645,28 +685,37 @@ 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_names.SYNTH_EXIT) + solo_exit_name = self.name_gen.new_block_name( + block_names.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_names.SYNTH_TAIL) + solo_tail_name = self.name_gen.new_block_name( + block_names.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_names.SYNTH_TAIL) - solo_exit_name = self.name_gen.new_block_name(block_names.SYNTH_EXIT) + solo_tail_name = self.name_gen.new_block_name( + block_names.SYNTH_TAIL + ) + solo_exit_name = self.name_gen.new_block_name( + block_names.SYNTH_EXIT + ) self.insert_SyntheticTail(solo_tail_name, tails, exits) - self.insert_SyntheticExit(solo_exit_name, set((solo_tail_name,)), exits) + self.insert_SyntheticExit(solo_exit_name, {solo_tail_name}, exits) return solo_tail_name, solo_exit_name @staticmethod def bcmap_from_bytecode(bc: dis.Bytecode): - """Static method that creates a bytecode map from a `dis.Bytecode` object. + """Static method that creates a bytecode map from a `dis.Bytecode` + object. Parameters ---------- @@ -769,14 +818,14 @@ def to_yaml(self): for key, value in scfg_graph.items(): jump_targets = [i for i in value._jump_targets] - jump_targets = str(jump_targets).replace("\'", "\"") + 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("\'", "\"") + back_edges = str(back_edges).replace("'", '"') jump_target_str += f""" be: {back_edges}""" yaml_string += dedent(jump_target_str) @@ -784,11 +833,11 @@ def to_yaml(self): return yaml_string def to_dict(self): - """Converts the SCFG object to a dictionary representation. + """Converts the SCFG object to a dictionary representation. - This method returns a dictionary representing the control flow - graph. It iterates over the graph dictionary and generates a - dictionary entry for each block, including jump targets and + This method returns a dictionary representing the control flow + graph. It iterates over the graph dictionary and generates a + dictionary entry for each block, including jump targets and backedges if present. Returns @@ -806,11 +855,11 @@ def to_dict(self): graph_dict[key] = curr_dict return graph_dict - def view(self, name: str=None): + def view(self, name: str = None): """View the current SCFG as a external PDF file. - This method internally creates a SCFGRenderer corresponding to - the current state of SCFG and calls it's view method to view the + This method internally creates a SCFGRenderer corresponding to + the current state of SCFG and calls it's view method to view the graph as a graphviz generated external PDF file. Parameters @@ -819,8 +868,10 @@ def view(self, name: str=None): Name to be given to the external graphviz generated PDF file. """ from numba_rvsdg.rendering.rendering import SCFGRenderer + SCFGRenderer(self).view(name) + class AbstractGraphView(Mapping): """Abstract Graph View class. @@ -942,7 +993,10 @@ def region_view_iterator(self, head: str = None) -> Iterator[str]: # Initialise housekeeping datastructures: # A set because we only need lookup and have unique items and a deque # because we need a first in, first out (FIFO) structure. - to_visit, seen = deque([head if head else self.scfg.find_head()]), set() + to_visit, seen = ( + deque([head if head else self.scfg.find_head()]), + set(), + ) while to_visit: # get the next name on the list name = to_visit.popleft() diff --git a/numba_rvsdg/core/transformations.py b/numba_rvsdg/core/transformations.py index cfff6c1..65b0ffb 100644 --- a/numba_rvsdg/core/transformations.py +++ b/numba_rvsdg/core/transformations.py @@ -32,7 +32,7 @@ def loop_restructure_helper(scfg: SCFG, loop: Set[str]): headers, entries = scfg.find_headers_and_entries(loop) exiting_blocks, exit_blocks = scfg.find_exiting_and_exits(loop) - #assert len(entries) == 1 + # assert len(entries) == 1 headers_were_unified = False # If there are multiple headers, insert assignment and control blocks, @@ -49,17 +49,26 @@ def loop_restructure_helper(scfg: SCFG, loop: Set[str]): # backedge to the loop header) we can exit early, since the condition for # SCFG is fullfilled. backedge_blocks = [ - block for block in loop if set(headers).intersection(scfg[block].jump_targets) + block + for block in loop + if set(headers).intersection(scfg[block].jump_targets) ] - if (len(backedge_blocks) == 1 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)) + if ( + len(backedge_blocks) == 1 + 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) + ) return # The synthetic exiting latch and synthetic exit need to be created # based on the state of the cfg. If there are multiple exits, we need a # SyntheticExit, otherwise we only need a SyntheticExitingLatch - synth_exiting_latch = scfg.name_gen.new_block_name(block_names.SYNTH_EXIT_LATCH) + synth_exiting_latch = scfg.name_gen.new_block_name( + block_names.SYNTH_EXIT_LATCH + ) # Set a flag, this will determine the variable assignment and block # insertion later on needs_synth_exit = len(exit_blocks) > 1 @@ -77,11 +86,15 @@ def loop_restructure_helper(scfg: SCFG, loop: Set[str]): backedge_variable = scfg.name_gen.new_var_name("backedge") # Now we setup the lookup tables for the various control variables, # depending on the state of the CFG and what is needed - exit_value_table = dict(((i, j) for i, j in enumerate(exit_blocks))) + exit_value_table = {i: j for i, j in enumerate(exit_blocks)} if needs_synth_exit: - backedge_value_table = dict((i, j) for i, j in enumerate((loop_head, synth_exit))) + backedge_value_table = { + i: j for i, j in enumerate((loop_head, synth_exit)) + } else: - backedge_value_table = dict((i, j) for i, j in enumerate((loop_head, next(iter(exit_blocks))))) + backedge_value_table = { + i: j for i, j in enumerate((loop_head, next(iter(exit_blocks)))) + } if headers_were_unified: header_value_table = scfg[solo_head_name].branch_value_table else: @@ -112,16 +125,24 @@ def reverse_lookup(d, value): # If the target is an exit block if jt in exit_blocks: # Create a new assignment name and record it - synth_assign = scfg.name_gen.new_block_name(block_names.SYNTH_ASSIGN) + synth_assign = scfg.name_gen.new_block_name( + block_names.SYNTH_ASSIGN + ) new_blocks.add(synth_assign) # Setup the table for the variable assignment variable_assignment = {} # Setup the variables in the assignment table to point to # the correct blocks if needs_synth_exit: - variable_assignment[exit_variable] = reverse_lookup(exit_value_table, jt) - variable_assignment[backedge_variable] = reverse_lookup(backedge_value_table, - synth_exit if needs_synth_exit else next(iter(exit_blocks))) + variable_assignment[exit_variable] = reverse_lookup( + exit_value_table, jt + ) + variable_assignment[backedge_variable] = reverse_lookup( + backedge_value_table, + synth_exit + if needs_synth_exit + else next(iter(exit_blocks)), + ) # Create the actual control variable block synth_assign_block = SyntheticAssignment( name=synth_assign, @@ -138,14 +159,20 @@ def reverse_lookup(d, value): # If the target is the loop_head elif jt in headers and (name not in doms[jt] or name == jt): # Create the assignment and record it - synth_assign = scfg.name_gen.new_block_name(block_names.SYNTH_ASSIGN) + synth_assign = scfg.name_gen.new_block_name( + block_names.SYNTH_ASSIGN + ) new_blocks.add(synth_assign) # Setup the variables in the assignment table to point to # the correct blocks variable_assignment = {} - variable_assignment[backedge_variable] = reverse_lookup(backedge_value_table, loop_head) + variable_assignment[backedge_variable] = reverse_lookup( + backedge_value_table, loop_head + ) if needs_synth_exit: - variable_assignment[exit_variable] = reverse_lookup(header_value_table, jt) + variable_assignment[exit_variable] = reverse_lookup( + header_value_table, jt + ) # Update the backedge block - remove any existing backedges # that point to the headers, no need to add a backedge, # since it will be contained in the SyntheticExitingLatch @@ -155,7 +182,9 @@ def reverse_lookup(d, value): for h in headers: if h in jts: jts.remove(h) - scfg.add_block(block.replace_jump_targets(jump_targets=tuple(jts))) + scfg.add_block( + block.replace_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( @@ -168,9 +197,12 @@ def reverse_lookup(d, value): scfg.add_block(synth_assign_block) # Update the jump targets again, order matters new_jt[new_jt.index(jt)] = synth_assign - # finally, replace the jump_targets for this block with the new ones + # 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.pop(name).replace_jump_targets( + jump_targets=tuple(new_jt) + ) ) # Add any new blocks to the loop. loop.update(new_blocks) @@ -178,7 +210,10 @@ def reverse_lookup(d, value): # Insert the exiting latch, add it to the loop and to the graph. synth_exiting_latch_block = SyntheticExitingLatch( name=synth_exiting_latch, - _jump_targets=(synth_exit if needs_synth_exit else next(iter(exit_blocks)), loop_head), + _jump_targets=( + 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, @@ -211,7 +246,8 @@ def restructure_loop(parent_region: RegionBlock): loops: List[Set[str]] = [ nodes for nodes in scc - if len(nodes) > 1 or next(iter(nodes)) in scfg[next(iter(nodes))].jump_targets + if len(nodes) > 1 + or next(iter(nodes)) in scfg[next(iter(nodes))].jump_targets ] _logger.debug( @@ -243,9 +279,6 @@ def find_head_blocks(scfg: SCFG, begin: str) -> Set[str]: def find_branch_regions(scfg: SCFG, begin: str, end: str) -> Set[str]: # identify branch regions doms = _doms(scfg) - postdoms = _post_doms(scfg) - postimmdoms = _imm_doms(postdoms) - immdoms = _imm_doms(doms) branch_regions = [] jump_targets = scfg.graph[begin].jump_targets for bra_start in jump_targets: @@ -277,7 +310,7 @@ def _find_branch_regions(scfg: SCFG, begin: str, end: str) -> Set[str]: def find_tail_blocks( scfg: SCFG, begin: Set[str], head_region_blocks, branch_regions ): - tail_subregion = set((b for b in scfg.graph.keys())) + tail_subregion = {b for b in scfg.graph.keys()} tail_subregion.difference_update(head_region_blocks) for reg in branch_regions: if not reg: @@ -291,29 +324,39 @@ def find_tail_blocks( return tail_subregion -def update_exiting(region_block: RegionBlock, new_region_header: str, new_region_name: str): +def update_exiting( + region_block: RegionBlock, new_region_header: str, new_region_name: str +): # Recursively updates the exiting blocks of a regionblock region_exiting = region_block.exiting - region_exiting_block: BasicBlock = region_block.subregion.graph.pop(region_exiting) + region_exiting_block: BasicBlock = region_block.subregion.graph.pop( + 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)) + 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 = region_exiting_block.replace_backedges( + backedges=tuple(be) + ) if isinstance(region_exiting_block, RegionBlock): - region_exiting_block = update_exiting(region_exiting_block, - new_region_header, - new_region_name) + 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(scfg: SCFG, region_blocks, region_kind, parent_region: RegionBlock): +def extract_region( + scfg: SCFG, region_blocks, region_kind, parent_region: RegionBlock +): headers, entries = scfg.find_headers_and_entries(region_blocks) exiting_blocks, exit_blocks = scfg.find_exiting_and_exits(region_blocks) assert len(headers) == 1 @@ -324,7 +367,8 @@ def extract_region(scfg: SCFG, region_blocks, region_kind, parent_region: Region # Generate a new region name region_name = scfg.name_gen.new_region_name(region_kind) head_subgraph = SCFG( - {name: scfg.graph[name] for name in region_blocks}, name_gen=scfg.name_gen + {name: scfg.graph[name] for name in region_blocks}, + name_gen=scfg.name_gen, ) # For all entries, replace the header as a jump target @@ -362,7 +406,7 @@ def extract_region(scfg: SCFG, region_blocks, region_kind, parent_region: Region header=region_header, subregion=head_subgraph, exiting=region_exiting, - parent_region=parent_region + parent_region=parent_region, ) scfg.remove_blocks(region_blocks) scfg.graph[region_name] = region @@ -429,14 +473,20 @@ def restructure_branch(parent_region: RegionBlock): if inner_nodes: # Insert SyntheticTail exiting_blocks, _ = scfg.find_exiting_and_exits(inner_nodes) - tail_headers, _ = scfg.find_headers_and_entries(tail_region_blocks) + tail_headers, _ = scfg.find_headers_and_entries( + tail_region_blocks + ) _, _ = scfg.join_tails_and_exits(exiting_blocks, tail_headers) else: # Insert SyntheticFill, a placeholder for an empty branch region tail_headers, _ = scfg.find_headers_and_entries(tail_region_blocks) - synthetic_branch_block_name = scfg.name_gen.new_block_name(block_names.SYNTH_FILL) - scfg.insert_SyntheticFill(synthetic_branch_block_name, (begin,), tail_headers) + synthetic_branch_block_name = scfg.name_gen.new_block_name( + block_names.SYNTH_FILL + ) + scfg.insert_SyntheticFill( + synthetic_branch_block_name, (begin,), tail_headers + ) # Recompute regions. head_region_blocks = find_head_blocks(scfg, begin) @@ -551,11 +601,13 @@ def _find_dominators_internal(entries, nodes, preds_table, succs_table): import functools if not entries: - raise RuntimeError("no entry points: dominator algorithm " "cannot be seeded") + raise RuntimeError( + "no entry points: dominator algorithm " "cannot be seeded" + ) doms = {} for e in entries: - doms[e] = set([e]) + doms[e] = {e} todo = [] for n in nodes: @@ -567,10 +619,12 @@ def _find_dominators_internal(entries, nodes, preds_table, succs_table): n = todo.pop() if n in entries: continue - new_doms = set([n]) + new_doms = {n} preds = preds_table[n] if preds: - new_doms |= functools.reduce(set.intersection, [doms[p] for p in preds]) + new_doms |= functools.reduce( + set.intersection, [doms[p] for p in preds] + ) if new_doms != doms[n]: assert len(new_doms) < len(doms[n]) doms[n] = new_doms diff --git a/numba_rvsdg/core/utils.py b/numba_rvsdg/core/utils.py index f3123e5..a951233 100644 --- a/numba_rvsdg/core/utils.py +++ b/numba_rvsdg/core/utils.py @@ -1,4 +1,3 @@ -import dis import logging _logger = logging.getLogger(__name__) diff --git a/numba_rvsdg/networkx_vendored/scc.py b/numba_rvsdg/networkx_vendored/scc.py index 15e849c..1352e4a 100644 --- a/numba_rvsdg/networkx_vendored/scc.py +++ b/numba_rvsdg/networkx_vendored/scc.py @@ -1,5 +1,5 @@ """ -SCC from https://github.com/networkx/networkx/blob/41a760273eed666e6f966cf14ea524dec56d678b/networkx/algorithms/components/strongly_connected.py#L16 +SCC from https://github.com/networkx/networkx/blob/41a760273eed666e6f966cf14ea524dec56d678b/networkx/algorithms/components/strongly_connected.py#L16 # noqa LICENSE: https://github.com/networkx/networkx/blob/main/LICENSE.txt @@ -37,7 +37,9 @@ def scc(G): queue.pop() if lowlink[v] == preorder[v]: scc = {v} - while scc_queue and preorder[scc_queue[-1]] > preorder[v]: + while ( + scc_queue and preorder[scc_queue[-1]] > preorder[v] + ): k = scc_queue.pop() scc.add(k) scc_found.update(scc) diff --git a/numba_rvsdg/rendering/rendering.py b/numba_rvsdg/rendering/rendering.py index da71d11..d0671c8 100644 --- a/numba_rvsdg/rendering/rendering.py +++ b/numba_rvsdg/rendering/rendering.py @@ -4,28 +4,28 @@ RegionBlock, PythonBytecodeBlock, SyntheticAssignment, - SyntheticExitingLatch, - SyntheticExitBranch, SyntheticBranch, SyntheticBlock, - SyntheticHead, - SyntheticExit, ) 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(object): + +class BaseRenderer: """Base Renderer class. 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. + methods `render_block` and `render_edges` that define how the blocks and + edges of the graph are rendered respectively. """ - def render_block(self, digraph: "Digraph", name: str, block: BasicBlock): - """Function that defines how the BasicBlcoks in a graph should be rendered. + def render_block( + self, digraph: "Digraph", name: str, block: BasicBlock # noqa + ): + """Function that defines how the BasicBlocks in a graph should be + rendered. Parameters ---------- @@ -63,6 +63,7 @@ def render_edges(self, scfg: SCFG): """ blocks = dict(scfg) + def find_base_header(block: BasicBlock): if isinstance(block, RegionBlock): block = blocks[block.header] @@ -83,7 +84,11 @@ def find_base_header(block: BasicBlock): 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" + str(src_block.name), + str(dst_name), + style="dashed", + color="grey", + constraint="0", ) else: raise Exception("unreachable " + str(src_block)) @@ -109,7 +114,7 @@ def __init__(self): self.g = Digraph() def render_region_block( - self, digraph: "Digraph", name: str, regionblock: RegionBlock + self, digraph: "Digraph", name: str, regionblock: RegionBlock # noqa ): # render subgraph with digraph.subgraph(name=f"cluster_{name}") as subg: @@ -124,24 +129,26 @@ def render_region_block( 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): - if name.startswith('python_bytecode'): + 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 + "\l" - body += "\l".join( + body = name + r"\l" + body += r"\l".join( [f"{inst.offset:3}: {inst.opname}" for inst in instlist] + [""] ) else: - body = name + "\l" + body = name + r"\l" digraph.node(str(name), shape="rect", label=body) def render_control_variable_block( - self, digraph: "Digraph", name: str, block: BasicBlock + self, digraph: "Digraph", name: str, block: BasicBlock # noqa ): if isinstance(name, str): - body = name + "\l" - body += "\l".join( + body = name + r"\l" + body += r"\l".join( (f"{k} = {v}" for k, v in block.variable_assignment.items()) ) else: @@ -149,12 +156,12 @@ def render_control_variable_block( digraph.node(str(name), shape="rect", label=body) def render_branching_block( - self, digraph: "Digraph", name: str, block: BasicBlock + self, digraph: "Digraph", name: str, block: BasicBlock # noqa ): if isinstance(name, str): - body = name + "\l" - body += f"variable: {block.variable}\l" - body += "\l".join( + 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: @@ -197,7 +204,7 @@ def __init__(self, scfg: SCFG): self.render_edges(scfg) def render_region_block( - self, digraph: "Digraph", name: str, regionblock: RegionBlock + self, digraph: "Digraph", name: str, regionblock: RegionBlock # noqa ): # render subgraph with digraph.subgraph(name=f"cluster_{name}") as subg: @@ -208,57 +215,74 @@ def render_region_block( color = "purple" if regionblock.kind == "head": color = "red" - label = regionblock.name + \ - "\njump targets: " + str(regionblock.jump_targets) + \ - "\nback edges: " + str(regionblock.backedges) + label = ( + regionblock.name + + "\njump targets: " + + str(regionblock.jump_targets) + + "\nback edges: " + + str(regionblock.backedges) + ) subg.attr(color=color, label=label) 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): - body = name + "\l"+ \ - "\njump targets: " + str(block.jump_targets) + \ - "\nback edges: " + str(block.backedges) + def render_basic_block( + self, digraph: "Digraph", name: str, block: BasicBlock # noqa + ): + body = ( + name + + r"\l" + + "\njump targets: " + + str(block.jump_targets) + + "\nback edges: " + + str(block.backedges) + ) digraph.node(str(name), shape="rect", label=body) def render_control_variable_block( - self, digraph: "Digraph", name: str, block: BasicBlock + self, digraph: "Digraph", name: str, block: BasicBlock # noqa ): if isinstance(name, str): - body = name + "\l" - body += "\l".join( + body = name + r"\l" + body += r"\l".join( (f"{k} = {v}" for k, v in block.variable_assignment.items()) ) - body += \ - "\njump targets: " + str(block.jump_targets) + \ - "\nback edges: " + str(block.backedges) + body += ( + "\njump targets: " + + str(block.jump_targets) + + "\nback edges: " + + str(block.backedges) + ) 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 + self, digraph: "Digraph", name: str, block: BasicBlock # noqa ): if isinstance(name, str): - body = name + "\l" - body += f"variable: {block.variable}\l" - body += "\l".join( + 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()) ) - body += \ - "\njump targets: " + str(block.jump_targets) + \ - "\nback edges: " + str(block.backedges) + body += ( + "\njump targets: " + + str(block.jump_targets) + + "\nback edges: " + + str(block.backedges) + ) else: raise Exception("Unknown name type: " + name) digraph.node(str(name), shape="rect", label=body) def view(self, name: str): - """Method used to view the current SCFG as an external graphviz generated - PDF file. + """Method used to view the current SCFG as an external graphviz + generated PDF file. Parameters ---------- @@ -285,13 +309,23 @@ def render_func(func): def render_flow(flow): - """Renders multiple ByteFlow representations across various SCFG transformations. + """Renders multiple ByteFlow representations across various SCFG + transformations. + + The `render_flow`` function takes a `flow` parameter as the `ByteFlow` + 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". + + - Joins the return blocks in the `ByteFlow` object graph and renders + the graph, displaying it as a document named "closed". + + - Restructures the loops recursively in the `ByteFlow` object graph + and renders the graph, displaying it as named "loop restructured". - The `render_flow`` function takes a `flow` parameter as the `ByteFlow` 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". - - Joins the return blocks in the `ByteFlow` object graph and renders the graph, displaying it as a document named "closed". - - Restructures the loops recursively in the `ByteFlow` object graph and renders the graph, displaying it as named "loop restructured". - - Restructures the branch recursively in the `ByteFlow` object graph and renders the graph, displaying it as named "branch restructured". + - Restructures the branch recursively in the `ByteFlow` object graph + and renders the graph, displaying it as named "branch restructured". Parameters ---------- diff --git a/numba_rvsdg/tests/mock_asm.py b/numba_rvsdg/tests/mock_asm.py index 315451f..76c0c92 100644 --- a/numba_rvsdg/tests/mock_asm.py +++ b/numba_rvsdg/tests/mock_asm.py @@ -6,11 +6,8 @@ from dataclasses import dataclass from enum import IntEnum -from pprint import pprint -from io import StringIO from typing import IO import random -import textwrap class Opcode(IntEnum): @@ -131,10 +128,11 @@ class VM: - program counter (PC) - an output buffer for printing; - a LAST_CTR register to store the last accessed counter. - - a table that maps PC location of `ctr` instruction to the counter value + - a table that maps PC location of `ctr` instruction to the counter + value - The VM do not have another other input source beside the instruction stream. - Therefore, a program behavior is known statically. + The VM do not have another other input source beside the instruction + stream. Therefore, a program behavior is known statically. """ @@ -210,10 +208,7 @@ def generate_program(self, min_base_length=5, max_base_length=30) -> str: # generate BB indent = " " * 4 for i in range(size): - bb: list[str] = [ - f"label BB{i}", - f"{indent}print P{i}" - ] + bb: list[str] = [f"label BB{i}", f"{indent}print P{i}"] [kind] = self.rng.choices(["goto", "brctr", ""], [1, 10, 20]) if kind == "goto": target = self.rng.randrange(size) diff --git a/numba_rvsdg/tests/simulator.py b/numba_rvsdg/tests/simulator.py index 861d7dd..984c3fd 100644 --- a/numba_rvsdg/tests/simulator.py +++ b/numba_rvsdg/tests/simulator.py @@ -1,9 +1,7 @@ from collections import ChainMap from dis import Instruction from numba_rvsdg.core.datastructures.byte_flow import ByteFlow -from numba_rvsdg.core.datastructures.scfg import SCFG from numba_rvsdg.core.datastructures.basic_block import ( - BasicBlock, PythonBytecodeBlock, RegionBlock, SyntheticBlock, @@ -52,7 +50,6 @@ class Simulator: """ def __init__(self, flow: ByteFlow, globals: dict): - self.flow = flow self.scfg = flow.scfg self.globals = ChainMap(globals, builtins.__dict__) @@ -153,16 +150,17 @@ def run_RegionBlock(self, name: str): """Run region. Execute all BasicBlocks in this region. Stay within the region, only - return the action when we jump out of the region or when we return from - within the region. + return the action when we jump out of the region or when we return + from within the region. Special attention is directed at the use of the `region_stack` here. - Since the blocks for the subregion are stored in the `region.subregion` - graph, we need to use a region aware `get_blocks` in methods such as - `run_BasicBlock` so that we get the correct `BasicBlock`. The net effect - of placing the `region` onto the `region_stack` is that `run_BasicBlock` - will be able to fetch the correct name from the `region.subregion` - graph, and thus be able to run the correct sequence of blocks. + Since the blocks for the subregion are stored in the + `region.subregion` graph, we need to use a region aware + `get_blocks` in methods such as `run_BasicBlock` so that we get + the correct `BasicBlock`. The net effect of placing the `region` + onto the `region_stack` is that `run_BasicBlock` will be able to + fetch the correct name from the `region.subregion` graph, and thus + be able to run the correct sequence of blocks. Parameters ---------- @@ -197,11 +195,11 @@ def run_RegionBlock(self, name: str): else: break # break and return action else: - assert False, "unreachable" # in case of coding errors + assert False, "unreachable" # in case of coding errors # Pop the region from the region stack again and return the final # action for this region popped = self.region_stack.pop() - assert (popped == region) + assert popped == region return action def run_PythonBytecodeBlock(self, name: str): @@ -230,7 +228,7 @@ def run_synth_block(self, name: str): print("----", name) print(f"control variable map: {self.ctrl_varmap}") block = self.get_block(name) - handler = getattr(self, 'synth_' + block.__class__.__name__) + handler = getattr(self, "synth_" + block.__class__.__name__) handler(name, block) def run_inst(self, inst: Instruction): @@ -250,12 +248,14 @@ def run_inst(self, inst: Instruction): print(f"variable map after: {self.varmap}") print(f"stack after: {self.stack}") - ### Synthetic Instructions ### + ### Synthetic Instructions ### # noqa def synth_SyntheticAssignment(self, control_name, block): self.ctrl_varmap.update(block.variable_assignment) def _synth_branch(self, control_name, block): - jump_target = block.branch_value_table[self.ctrl_varmap[block.variable]] + jump_target = block.branch_value_table[ + self.ctrl_varmap[block.variable] + ] self.branch = bool(block._jump_targets.index(jump_target)) def synth_SyntheticExitingLatch(self, control_name, block): @@ -285,7 +285,7 @@ def synth_SyntheticBlock(self, control_name, block): def synth_SyntheticBranch(self, control_name, block): raise NotImplementedError("SyntheticBranch should not be instantiated") - ### Bytecode Instructions ### + ### Bytecode Instructions ### # noqa def op_LOAD_CONST(self, inst): self.stack.append(inst.argval) @@ -300,7 +300,6 @@ def op_LOAD_FAST(self, inst): def op_LOAD_GLOBAL(self, inst): v = self.globals[inst.argval] if inst.argrepr.startswith("NULL"): - append_null = True self.stack.append(v) self.stack.append(None) else: @@ -377,16 +376,10 @@ def op_RESUME(self, inst): def op_PRECALL(self, inst): pass - def op_CALL_FUNCTION(self, inst): - args = [self.stack.pop() for _ in range(inst.argval)][::-1] - fn = self.stack.pop() - res = fn(*args) - self.stack.append(res) - def op_CALL(self, inst): args = [self.stack.pop() for _ in range(inst.argval)][::-1] first, second = self.stack.pop(), self.stack.pop() - if first == None: + if first is None: func = second else: raise NotImplementedError diff --git a/numba_rvsdg/tests/test_byteflow.py b/numba_rvsdg/tests/test_byteflow.py index a0341fd..a92db24 100644 --- a/numba_rvsdg/tests/test_byteflow.py +++ b/numba_rvsdg/tests/test_byteflow.py @@ -7,14 +7,17 @@ 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 = 10 +# 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): @@ -114,7 +117,7 @@ def test_constructor(self): _jump_targets=(), backedges=(), ) - self.assertEqual(block.name, 'python_bytecode_block_0') + self.assertEqual(block.name, "python_bytecode_block_0") self.assertEqual(block.begin, 0) self.assertEqual(block.end, 8) self.assertFalse(block.fallthrough) @@ -128,10 +131,12 @@ def test_is_jump_target(self): 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),), + _jump_targets=( + name_gen.new_block_name(block_names.PYTHON_BYTECODE), + ), backedges=(), ) - self.assertEqual(block.jump_targets, ('python_bytecode_block_1',)) + self.assertEqual(block.jump_targets, ("python_bytecode_block_1",)) self.assertFalse(block.is_exiting) def test_get_instructions(self): @@ -221,8 +226,9 @@ def test_constructor(self): self.assertEqual(len(flowinfo.jump_insts), 0) def test_from_bytecode(self): - - expected = FlowInfo(block_offsets={0}, jump_insts={8: ()}, last_offset=8) + expected = FlowInfo( + block_offsets={0}, jump_insts={8: ()}, last_offset=8 + ) received = FlowInfo.from_bytecode(bytecode) self.assertEqual(expected, received) diff --git a/numba_rvsdg/tests/test_fig4.py b/numba_rvsdg/tests/test_fig4.py index b54ff7b..7d9a046 100644 --- a/numba_rvsdg/tests/test_fig4.py +++ b/numba_rvsdg/tests/test_fig4.py @@ -31,6 +31,7 @@ def make_flow(): scfg = flow.build_basicblocks() return ByteFlow(bc=bc, scfg=scfg) + def test_fig4(): f = make_flow() f.restructure() diff --git a/numba_rvsdg/tests/test_mock_asm.py b/numba_rvsdg/tests/test_mock_asm.py index b79ee19..6773a5f 100644 --- a/numba_rvsdg/tests/test_mock_asm.py +++ b/numba_rvsdg/tests/test_mock_asm.py @@ -1,8 +1,4 @@ -from dataclasses import dataclass -from enum import IntEnum -from pprint import pprint from io import StringIO -from typing import IO import random import textwrap @@ -10,7 +6,8 @@ def test_mock_asm(): - asm = textwrap.dedent(""" + asm = textwrap.dedent( + """ print Start goto A label A @@ -19,7 +16,8 @@ def test_mock_asm(): brctr A B label B print B - """) + """ + ) instlist = parse(asm) assert instlist[0].operands.text == "Start" @@ -39,7 +37,8 @@ def test_mock_asm(): def test_double_exchange_loop(): - asm = textwrap.dedent(""" + asm = textwrap.dedent( + """ print Start label A print A @@ -51,7 +50,8 @@ def test_double_exchange_loop(): brctr A Exit label Exit print Exit - """) + """ + ) instlist = parse(asm) with StringIO() as buf: VM(buf).run(instlist) @@ -70,7 +70,6 @@ def test_program_gen(): print(str(i).center(80, "=")) asm = pg.generate_program() - instlist = parse(asm) with StringIO() as buf: terminated = VM(buf).run(instlist, max_step=1000) diff --git a/numba_rvsdg/tests/test_scc.py b/numba_rvsdg/tests/test_scc.py index 8def674..b3ba2d7 100644 --- a/numba_rvsdg/tests/test_scc.py +++ b/numba_rvsdg/tests/test_scc.py @@ -1,6 +1,7 @@ from numba_rvsdg.core.datastructures.byte_flow import ByteFlow from numba_rvsdg.rendering.rendering import render_flow + def scc(G): preorder = {} lowlink = {} @@ -33,7 +34,9 @@ def scc(G): queue.pop() if lowlink[v] == preorder[v]: scc = {v} - while scc_queue and preorder[scc_queue[-1]] > preorder[v]: + while ( + scc_queue and preorder[scc_queue[-1]] > preorder[v] + ): k = scc_queue.pop() scc.add(k) scc_found.update(scc) @@ -46,9 +49,11 @@ def scc(G): def make_flow(func): return ByteFlow.from_bytecode(func) + def test_scc(): f = make_flow(scc) f.restructure() + if __name__ == "__main__": render_flow(make_flow(scc)) diff --git a/numba_rvsdg/tests/test_scfg.py b/numba_rvsdg/tests/test_scfg.py index 6d920e9..2bb3e3b 100644 --- a/numba_rvsdg/tests/test_scfg.py +++ b/numba_rvsdg/tests/test_scfg.py @@ -1,21 +1,22 @@ - from unittest import main, TestCase from textwrap import dedent from numba_rvsdg.core.datastructures.scfg import SCFG, NameGenerator from numba_rvsdg.tests.test_utils import SCFGComparator -from numba_rvsdg.core.datastructures.basic_block import (BasicBlock, - RegionBlock, - PythonBytecodeBlock, - ) +from numba_rvsdg.core.datastructures.basic_block import ( + BasicBlock, + RegionBlock, + PythonBytecodeBlock, +) from numba_rvsdg.core.datastructures.byte_flow import ByteFlow from numba_rvsdg.core.datastructures import block_names -class TestSCFGConversion(SCFGComparator): +class TestSCFGConversion(SCFGComparator): def test_yaml_conversion(self): # Case # 1: Acyclic graph, no back-edges - cases = [""" + cases = [ + """ "0": jt: ["1", "2"] "1": @@ -26,7 +27,7 @@ def test_yaml_conversion(self): jt: ["4"] "4": jt: []""", - # Case # 2: Cyclic graph, no back edges + # Case # 2: Cyclic graph, no back edges """ "0": jt: ["1", "2"] @@ -40,7 +41,7 @@ def test_yaml_conversion(self): jt: [] "5": jt: ["3", "4"]""", - # Case # 3: Graph with backedges + # Case # 3: Graph with backedges """ "0": jt: ["1"] @@ -52,84 +53,71 @@ def test_yaml_conversion(self): jt: [] "4": jt: ["2", "3"] - be: ["2"]"""] + be: ["2"]""", + ] for case in cases: case = dedent(case) scfg, block_dict = SCFG.from_yaml(case) - self.assertYAMLEqual(case, scfg.to_yaml(), {'0': block_dict['0']}) + self.assertYAMLEqual(case, scfg.to_yaml(), {"0": block_dict["0"]}) 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": []}}, - # 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"]}}, - # Case # 3: Graph with backedges - { - "0": - {"jt": ["1"]}, - "1": - {"jt": ["2", "3"]}, - "2": - {"jt": ["4"]}, - "3": - {"jt": []}, - "4": - {"jt": ["2", "3"], - "be": ["2"]}}] + cases = [ + { + "0": {"jt": ["1", "2"]}, + "1": {"jt": ["3"]}, + "2": {"jt": ["4"]}, + "3": {"jt": ["4"]}, + "4": {"jt": []}, + }, + # 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"]}, + }, + # Case # 3: Graph with backedges + { + "0": {"jt": ["1"]}, + "1": {"jt": ["2", "3"]}, + "2": {"jt": ["4"]}, + "3": {"jt": []}, + "4": {"jt": ["2", "3"], "be": ["2"]}, + }, + ] for case in cases: scfg, block_dict = SCFG.from_dict(case) - self.assertDictEqual(case, scfg.to_dict(), {'0': block_dict['0']}) + self.assertDictEqual(case, scfg.to_dict(), {"0": block_dict["0"]}) class TestSCFGIterator(SCFGComparator): - def test_scfg_iter(self): name_gen = NameGenerator() block_0 = name_gen.new_block_name(block_names.BASIC) block_1 = name_gen.new_block_name(block_names.BASIC) expected = [ - (block_0, BasicBlock(name=block_0, - _jump_targets=(block_1,))), + (block_0, BasicBlock(name=block_0, _jump_targets=(block_1,))), (block_1, BasicBlock(name=block_1)), ] - scfg, _ = SCFG.from_yaml(""" + scfg, _ = SCFG.from_yaml( + """ "0": jt: ["1"] "1": jt: [] - """) + """ + ) received = list(scfg) self.assertEqual(expected, received) class TestConcealedRegionView(TestCase): - def setUp(self): - def foo(n): c = 0 for i in range(n): @@ -139,13 +127,19 @@ def foo(n): self.foo = foo def test_concealed_region_view_iter(self): - flow = ByteFlow.from_bytecode(self.foo) restructured = 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())) + 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() + ) + ) self.assertEqual(expected, received) diff --git a/numba_rvsdg/tests/test_transforms.py b/numba_rvsdg/tests/test_transforms.py index d0fa921..096b6ca 100644 --- a/numba_rvsdg/tests/test_transforms.py +++ b/numba_rvsdg/tests/test_transforms.py @@ -1,12 +1,12 @@ - from unittest import main 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 from numba_rvsdg.core.transformations import loop_restructure_helper from numba_rvsdg.tests.test_utils import SCFGComparator from numba_rvsdg.core.datastructures import block_names + class TestInsertBlock(SCFGComparator): def test_linear(self): original = """ @@ -27,8 +27,7 @@ def test_linear(self): expected_scfg, _ = SCFG.from_yaml(expected) new_name = original_scfg.name_gen.new_block_name(block_names.BASIC) original_scfg.insert_block( - new_name, (block_dict["0"],), (block_dict["1"],), - BasicBlock + new_name, (block_dict["0"],), (block_dict["1"],), BasicBlock ) self.assertSCFGEqual(expected_scfg, original_scfg) @@ -55,11 +54,19 @@ def test_dual_predecessor(self): expected_scfg, expected_block_dict = SCFG.from_yaml(expected) new_name = original_scfg.name_gen.new_block_name(block_names.BASIC) original_scfg.insert_block( - new_name, (block_dict["0"], block_dict["1"]), (block_dict["2"],), - BasicBlock + new_name, + (block_dict["0"], block_dict["1"]), + (block_dict["2"],), + BasicBlock, + ) + self.assertSCFGEqual( + expected_scfg, + original_scfg, + { + block_dict["0"]: expected_block_dict["0"], + block_dict["1"]: expected_block_dict["1"], + }, ) - self.assertSCFGEqual(expected_scfg, original_scfg, {block_dict["0"]: expected_block_dict["0"], - block_dict["1"]: expected_block_dict["1"]}) def test_dual_successor(self): original = """ @@ -86,7 +93,7 @@ def test_dual_successor(self): original_scfg.name_gen.new_block_name(block_names.BASIC), (block_dict["0"],), (block_dict["1"], block_dict["2"]), - BasicBlock + BasicBlock, ) self.assertSCFGEqual(expected_scfg, original_scfg) @@ -123,7 +130,7 @@ def test_dual_predecessor_and_dual_successor(self): original_scfg.name_gen.new_block_name(block_names.BASIC), (block_dict["1"], block_dict["2"]), (block_dict["3"], block_dict["4"]), - BasicBlock + BasicBlock, ) self.assertSCFGEqual(expected_scfg, original_scfg) @@ -160,10 +167,16 @@ def test_dual_predecessor_and_dual_successor_with_additional_arcs(self): original_scfg.name_gen.new_block_name(block_names.BASIC), (block_dict["1"], block_dict["2"]), (block_dict["3"], block_dict["4"]), - BasicBlock + BasicBlock, + ) + self.assertSCFGEqual( + expected_scfg, + original_scfg, + { + block_dict["0"]: expected_block_dict["0"], + block_dict["2"]: expected_block_dict["2"], + }, ) - self.assertSCFGEqual(expected_scfg, original_scfg, {block_dict["0"]: expected_block_dict["0"], - block_dict["2"]: expected_block_dict["2"]}) class TestJoinReturns(SCFGComparator): @@ -253,7 +266,10 @@ def test_join_tails_and_exits_case_01(self): self.assertSCFGEqual(expected_scfg, original_scfg) self.assertEqual(block_dict["0"], solo_tail_name) - self.assertEqual(expected_scfg.name_gen.new_block_name(block_names.SYNTH_EXIT), solo_exit_name) + self.assertEqual( + expected_scfg.name_gen.new_block_name(block_names.SYNTH_EXIT), + solo_exit_name, + ) def test_join_tails_and_exits_case_02_01(self): original = """ @@ -288,7 +304,10 @@ def test_join_tails_and_exits_case_02_01(self): ) self.assertSCFGEqual(expected_scfg, original_scfg) - self.assertEqual(expected_scfg.name_gen.new_block_name(block_names.SYNTH_TAIL), solo_tail_name) + self.assertEqual( + expected_scfg.name_gen.new_block_name(block_names.SYNTH_TAIL), + solo_tail_name, + ) self.assertEqual(block_dict["3"], solo_exit_name) def test_join_tails_and_exits_case_02_02(self): @@ -324,11 +343,13 @@ def test_join_tails_and_exits_case_02_02(self): tails, exits ) self.assertSCFGEqual(expected_scfg, original_scfg) - self.assertEqual(expected_scfg.name_gen.new_block_name(block_names.SYNTH_TAIL), solo_tail_name) + self.assertEqual( + expected_scfg.name_gen.new_block_name(block_names.SYNTH_TAIL), + solo_tail_name, + ) self.assertEqual(block_dict["3"], solo_exit_name) def test_join_tails_and_exits_case_03_01(self): - original = """ "0": jt: ["1", "2"] @@ -370,11 +391,16 @@ def test_join_tails_and_exits_case_03_01(self): tails, exits ) self.assertSCFGEqual(expected_scfg, original_scfg) - self.assertEqual(expected_scfg.name_gen.new_block_name(block_names.SYNTH_TAIL), solo_tail_name) - self.assertEqual(expected_scfg.name_gen.new_block_name(block_names.SYNTH_EXIT), solo_exit_name) + self.assertEqual( + expected_scfg.name_gen.new_block_name(block_names.SYNTH_TAIL), + solo_tail_name, + ) + self.assertEqual( + expected_scfg.name_gen.new_block_name(block_names.SYNTH_EXIT), + solo_exit_name, + ) def test_join_tails_and_exits_case_03_02(self): - original = """ "0": jt: ["1", "2"] @@ -415,12 +441,17 @@ def test_join_tails_and_exits_case_03_02(self): tails, exits ) self.assertSCFGEqual(expected_scfg, original_scfg) - self.assertEqual(expected_scfg.name_gen.new_block_name(block_names.SYNTH_TAIL), solo_tail_name) - self.assertEqual(expected_scfg.name_gen.new_block_name(block_names.SYNTH_EXIT), solo_exit_name) + self.assertEqual( + expected_scfg.name_gen.new_block_name(block_names.SYNTH_TAIL), + solo_tail_name, + ) + self.assertEqual( + expected_scfg.name_gen.new_block_name(block_names.SYNTH_EXIT), + solo_exit_name, + ) class TestLoopRestructure(SCFGComparator): - def test_no_op_mono(self): """Loop consists of a single Block.""" original = """ @@ -470,7 +501,9 @@ def test_no_op(self): """ original_scfg, block_dict = SCFG.from_yaml(original) expected_scfg, _ = SCFG.from_yaml(expected) - loop_restructure_helper(original_scfg, set({block_dict["1"], block_dict["2"]})) + loop_restructure_helper( + original_scfg, set({block_dict["1"], block_dict["2"]}) + ) self.assertSCFGEqual(expected_scfg, original_scfg) def test_backedge_not_exiting(self): @@ -507,7 +540,9 @@ def test_backedge_not_exiting(self): """ original_scfg, block_dict = SCFG.from_yaml(original) expected_scfg, _ = SCFG.from_yaml(expected) - loop_restructure_helper(original_scfg, set({block_dict["1"], block_dict["2"]})) + loop_restructure_helper( + original_scfg, set({block_dict["1"], block_dict["2"]}) + ) self.assertSCFGEqual(expected_scfg, original_scfg) def test_multi_back_edge_with_backedge_from_header(self): @@ -542,7 +577,9 @@ def test_multi_back_edge_with_backedge_from_header(self): """ original_scfg, block_dict = SCFG.from_yaml(original) expected_scfg, _ = SCFG.from_yaml(expected) - loop_restructure_helper(original_scfg, set({block_dict["1"], block_dict["2"]})) + loop_restructure_helper( + original_scfg, set({block_dict["1"], block_dict["2"]}) + ) self.assertSCFGEqual(expected_scfg, original_scfg) def test_double_exit(self): @@ -586,11 +623,14 @@ def test_double_exit(self): """ original_scfg, block_dict = SCFG.from_yaml(original) expected_scfg, _ = SCFG.from_yaml(expected) - loop_restructure_helper(original_scfg, set({block_dict["1"], block_dict["2"], block_dict["3"]})) + loop_restructure_helper( + original_scfg, + set({block_dict["1"], block_dict["2"], block_dict["3"]}), + ) self.assertSCFGEqual(expected_scfg, original_scfg) def test_double_header(self): - """ This is like the example from Bahman2015 fig. 3 -- + """This is like the example from Bahman2015 fig. 3 -- but with one exiting block removed.""" original = """ "0": @@ -637,11 +677,21 @@ def test_double_header(self): """ original_scfg, block_dict = SCFG.from_yaml(original) expected_scfg, _ = SCFG.from_yaml(expected) - loop_restructure_helper(original_scfg, set({block_dict["1"], block_dict["2"], block_dict["3"], block_dict["4"]})) + loop_restructure_helper( + original_scfg, + set( + { + block_dict["1"], + block_dict["2"], + block_dict["3"], + block_dict["4"], + } + ), + ) self.assertSCFGEqual(expected_scfg, original_scfg) def test_double_header_double_exiting(self): - """ This is like the example from Bahman2015 fig. 3. + """This is like the example from Bahman2015 fig. 3. Two headers that need to be multiplexed to, on additional branch that becomes the exiting latch and one branch that becomes the exit. @@ -704,8 +754,19 @@ def test_double_header_double_exiting(self): """ original_scfg, block_dict = SCFG.from_yaml(original) expected_scfg, _ = SCFG.from_yaml(expected) - loop_restructure_helper(original_scfg, set({block_dict["1"], block_dict["2"], block_dict["3"], block_dict["4"]})) + loop_restructure_helper( + original_scfg, + set( + { + block_dict["1"], + block_dict["2"], + block_dict["3"], + block_dict["4"], + } + ), + ) self.assertSCFGEqual(expected_scfg, original_scfg) + if __name__ == "__main__": main() diff --git a/numba_rvsdg/tests/test_utils.py b/numba_rvsdg/tests/test_utils.py index 778753d..f1a9c4f 100644 --- a/numba_rvsdg/tests/test_utils.py +++ b/numba_rvsdg/tests/test_utils.py @@ -4,8 +4,11 @@ from numba_rvsdg.core.datastructures.scfg import SCFG from numba_rvsdg.core.datastructures.basic_block import BasicBlock + class SCFGComparator(TestCase): - def assertSCFGEqual(self, first_scfg: SCFG, second_scfg: SCFG, head_map=None): + def assertSCFGEqual( + self, first_scfg: SCFG, second_scfg: SCFG, head_map=None + ): if head_map: # If more than one head the corresponding map needs to be provided block_mapping = head_map @@ -17,7 +20,9 @@ def assertSCFGEqual(self, first_scfg: SCFG, second_scfg: SCFG, head_map=None): stack = [first_head] # Assert number of blocks are equal in both SCFGs - assert len(first_scfg.graph) == len(second_scfg.graph), "Number of blocks in both graphs are not equal" + assert len(first_scfg.graph) == len( + second_scfg.graph + ), "Number of blocks in both graphs are not equal" seen = set() while stack: @@ -26,7 +31,8 @@ def assertSCFGEqual(self, first_scfg: SCFG, second_scfg: SCFG, head_map=None): continue seen.add(node_name) node: BasicBlock = first_scfg[node_name] - # Assert that there's a corresponding mapping of current node in second scfg + # 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] @@ -35,9 +41,9 @@ def assertSCFGEqual(self, first_scfg: SCFG, second_scfg: SCFG, head_map=None): assert len(node.jump_targets) == len(second_node.jump_targets) assert len(node.backedges) == len(second_node.backedges) - # Add the jump targets as corresponding nodes in block mapping dictionary - # Since order must be same we can simply add zip fucntionality as the - # correspondence function for nodes + # 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): block_mapping[jt1] = jt2 stack.append(jt1) @@ -46,14 +52,22 @@ def assertSCFGEqual(self, first_scfg: SCFG, second_scfg: SCFG, head_map=None): block_mapping[be1] = be2 stack.append(be1) - def assertYAMLEqual(self, first_yaml: SCFG, second_yaml: SCFG, head_map: dict): - self.assertDictEqual(yaml.safe_load(first_yaml), yaml.safe_load(second_yaml), head_map) + def assertYAMLEqual( + self, first_yaml: SCFG, second_yaml: SCFG, head_map: dict + ): + self.assertDictEqual( + yaml.safe_load(first_yaml), yaml.safe_load(second_yaml), head_map + ) - def assertDictEqual(self, first_yaml: str, second_yaml: str, head_map: dict): + def assertDictEqual( + self, first_yaml: str, second_yaml: str, head_map: dict + ): block_mapping = head_map stack = list(block_mapping.keys()) # Assert number of blocks are equal in both SCFGs - assert len(first_yaml) == len(second_yaml), "Number of blocks in both graphs are not equal" + assert len(first_yaml) == len( + second_yaml + ), "Number of blocks in both graphs are not equal" seen = set() while stack: @@ -62,19 +76,20 @@ def assertDictEqual(self, first_yaml: str, second_yaml: str, head_map: dict): 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 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] # 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(node["jt"]) == len(second_node["jt"]) + if "be" in node.keys(): + assert len(node["be"]) == len(second_node["be"]) - # Add the jump targets as corresponding nodes in block mapping dictionary - # Since order must be same we can simply add zip fucntionality as the - # correspondence function for nodes - for jt1, jt2 in zip(node['jt'], second_node['jt']): + # 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"]): block_mapping[jt1] = jt2 stack.append(jt1)