Skip to content

Commit

Permalink
enable scfg.restructure()
Browse files Browse the repository at this point in the history
This moves the loop and branch restructuring code from the `ByteFlow`
class to the `SCFG` class. The `ByteFlow` class is scheduled for
eventual deletion anyway -- so this is a first step in that direction.

This is needed now, such that `SCFG` generated from Python abstract
syntax trees can also be transformed.
  • Loading branch information
esc committed Apr 16, 2024
1 parent 3850458 commit 6e30edb
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 120 deletions.
102 changes: 1 addition & 101 deletions numba_rvsdg/core/datastructures/byte_flow.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
import dis
from copy import deepcopy
from dataclasses import dataclass
from typing import Generator, Callable
from typing import Callable

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:
Expand Down Expand Up @@ -60,96 +53,3 @@ def from_bytecode(code: Callable) -> "ByteFlow": # type: ignore
flowinfo = FlowInfo.from_bytecode(bc)
scfg = flowinfo.build_basicblocks()
return ByteFlow(bc=bc, scfg=scfg)

def _join_returns(self) -> "ByteFlow":
"""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)

def _restructure_loop(self) -> "ByteFlow":
"""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
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.
"""
scfg = deepcopy(self.scfg)
restructure_loop(scfg.region)
for region in _iter_subregions(scfg):
restructure_loop(region)
return ByteFlow(bc=self.bc, scfg=scfg)

def _restructure_branch(self) -> "ByteFlow":
"""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
the updated SCFG.
Returns
-------
byteflow: ByteFlow
The new ByteFlow object with updated SCFG.
"""
scfg = deepcopy(self.scfg)
restructure_branch(scfg.region)
for region in _iter_subregions(scfg):
restructure_branch(region)
return ByteFlow(bc=self.bc, scfg=scfg)

def restructure(self) -> "ByteFlow":
"""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
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.
"""
scfg = deepcopy(self.scfg)
# close
scfg.join_returns()
# handle loop
restructure_loop(scfg.region)
for region in _iter_subregions(scfg):
restructure_loop(region)
# handle branch
restructure_branch(scfg.region)
for region in _iter_subregions(scfg):
restructure_branch(region)
return ByteFlow(bc=self.bc, scfg=scfg)


def _iter_subregions(scfg: SCFG) -> Generator[RegionBlock, SCFG, None]:
for node in scfg.graph.values():
if isinstance(node, RegionBlock):
yield node
assert node.subregion is not None
yield from _iter_subregions(node.subregion)
49 changes: 48 additions & 1 deletion numba_rvsdg/core/datastructures/scfg.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
)



@dataclass(frozen=True)
class NameGenerator:
"""Unique Name Generator.
Expand Down Expand Up @@ -672,7 +673,8 @@ def join_returns(self) -> None:
"""Close the CFG.
A closed CFG is a CFG with a unique entry and exit node that have no
predescessors and no successors respectively.
predescessors and no successors respectively. Transformation is applied
in-place.
"""
# for all nodes that contain a return
return_nodes = [
Expand All @@ -683,6 +685,51 @@ def join_returns(self) -> None:
return_solo_name = self.name_gen.new_block_name(SYNTH_RETURN)
self.insert_SyntheticReturn(return_solo_name, return_nodes, [])

def iter_subregions(self) -> Generator[RegionBlock, "SCFG", None]:
""" Iterate over all subregions of this CFG. """
for node in self.graph.values():
if isinstance(node, RegionBlock):
yield node
assert node.subregion is not None
yield from node.subregion.iter_subregions()

def restructure_loop(self) -> None:
""" Apply LOOP RESTRUCTURING transform.
Performs the operation to restructure loop constructs using the
algorithm LOOP RESTRUCTURING from section 4.1 of Bahmann2015. It
applies an in-place restructuring operation to both the main SCFG and
any subregions within it.
"""
# Avoid cyclic imports
from numba_rvsdg.core.transformations import restructure_loop

restructure_loop(self.region)
for region in self.iter_subregions():
restructure_loop(region)

def restructure_branch(self) -> None:
"""Apply BRANCH RESTRUCTURING transform.
Performs the operation to restructure branch constructs using the
algorithm BRANCH RESTRUCTURING from section 4.2 of Bahmann2015. It
applies an in-place restructuring operation to both the main SCFG and
any subregions within it.
"""
# Avoid cyclic imports
from numba_rvsdg.core.transformations import restructure_branch

restructure_branch(self.region)
for region in self.iter_subregions():
restructure_branch(region)

def restructure(self) -> None:
self.join_returns()
self.restructure_loop()
self.restructure_branch()

def join_tails_and_exits(
self, tails: List[str], exits: List[str]
) -> Tuple[str, str]:
Expand Down
11 changes: 4 additions & 7 deletions numba_rvsdg/tests/test_figures.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# mypy: ignore-errors

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
Expand Down Expand Up @@ -441,11 +440,10 @@ def test_figure_3(self):
]
flow = FlowInfo.from_bytecode(bc)
scfg = flow.build_basicblocks()
byteflow = ByteFlow(bc=bc, scfg=scfg)
byteflow = byteflow.restructure()
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
Expand Down Expand Up @@ -474,8 +472,7 @@ def test_figure_4(self):
]
flow = FlowInfo.from_bytecode(bc)
scfg = flow.build_basicblocks()
byteflow = ByteFlow(bc=bc, scfg=scfg)
byteflow = byteflow.restructure()
scfg.restructure()

x, _ = SCFG.from_yaml(fig_4_yaml)
self.assertSCFGEqual(x, byteflow.scfg)
self.assertSCFGEqual(x, scfg)
2 changes: 1 addition & 1 deletion numba_rvsdg/tests/test_scc.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def make_flow(func):

def test_scc():
f = make_flow(scc)
f.restructure()
f.scfg.restructure()


if __name__ == "__main__":
Expand Down
4 changes: 2 additions & 2 deletions numba_rvsdg/tests/test_scfg.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ def foo(n):

def test_concealed_region_view_iter(self):
flow = ByteFlow.from_bytecode(self.foo)
restructured = flow._restructure_loop()
flow.scfg.restructure_loop()
expected = [
("python_bytecode_block_0", PythonBytecodeBlock),
("loop_region_0", RegionBlock),
Expand All @@ -203,7 +203,7 @@ def test_concealed_region_view_iter(self):
received = list(
(
(k, type(v))
for k, v in restructured.scfg.concealed_region_view.items()
for k, v in flow.scfg.concealed_region_view.items()
)
)
self.assertEqual(expected, received)
Expand Down
16 changes: 8 additions & 8 deletions numba_rvsdg/tests/test_simulate.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def foo(x):
return c

flow = ByteFlow.from_bytecode(foo)
flow = flow.restructure()
flow.scfg.restructure()

# if case
self._run(foo, flow, {"x": 1})
Expand All @@ -59,7 +59,7 @@ def foo(x):
return c

flow = ByteFlow.from_bytecode(foo)
flow = flow.restructure()
flow.scfg.restructure()

# loop bypass case
self._run(foo, flow, {"x": 0})
Expand All @@ -78,7 +78,7 @@ def foo(x):
return c

flow = ByteFlow.from_bytecode(foo)
flow = flow.restructure()
flow.scfg.restructure()

# loop bypass case
self._run(foo, flow, {"x": 0})
Expand All @@ -97,7 +97,7 @@ def foo(x):
return c

flow = ByteFlow.from_bytecode(foo)
flow = flow.restructure()
flow.scfg.restructure()

# loop bypass case
self._run(foo, flow, {"x": 0})
Expand All @@ -121,7 +121,7 @@ def foo(x):
return c

flow = ByteFlow.from_bytecode(foo)
flow = flow.restructure()
flow.scfg.restructure()

# no loop
self._run(foo, flow, {"x": 0})
Expand All @@ -145,7 +145,7 @@ def foo(x):
return c

flow = ByteFlow.from_bytecode(foo)
flow = flow.restructure()
flow.scfg.restructure()

# loop bypass
self._run(foo, flow, {"x": 0})
Expand All @@ -161,7 +161,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.scfg.restructure()

self._run(foo, flow, {"x": 5, "y": 5})

Expand All @@ -175,7 +175,7 @@ def foo(s, e):
return c

flow = ByteFlow.from_bytecode(foo)
flow = flow.restructure()
flow.scfg.restructure()

# no looping
self._run(foo, flow, {"s": 0, "e": 0})
Expand Down

0 comments on commit 6e30edb

Please sign in to comment.