Skip to content

Commit 199d6e5

Browse files
committed
Beef up tests a little and ...
minor tweaks to control flow code to make things clearer.
1 parent b8c8d57 commit 199d6e5

File tree

8 files changed

+91
-46
lines changed

8 files changed

+91
-46
lines changed

control_flow/bb.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
#!/usr/bin/env python
2-
# Copyright (c) 2021, 2023 by Rocky Bernstein <rb@dustyfeet.com>
1+
# Copyright (c) 2021, 2023-2024 by Rocky Bernstein <rb@dustyfeet.com>
32
import sys
43
from xdis import next_offset
54
from xdis.version_info import PYTHON_VERSION_TRIPLE, IS_PYPY
@@ -23,6 +22,7 @@
2322
BB_LOOP,
2423
BB_NOFOLLOW,
2524
BB_RETURN,
25+
FLAG2NAME,
2626
)
2727

2828
# The byte code versions we support
@@ -141,7 +141,8 @@ def __repr__(self):
141141
else:
142142
exception_text = ""
143143
if len(self.flags) > 0:
144-
flag_text = f", flags={sorted(self.flags)}"
144+
flag_str = ",".join(FLAG2NAME[flag] for flag in sorted(self.flags))
145+
flag_text = ", flags={%s}"% flag_str
145146
else:
146147
flag_text = ""
147148
return "BasicBlock(#%d range: %s%s, follow_offset=%s, edge_count=%d%s%s)" % (

control_flow/cfg.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
# Copyright (c) 2021 by Rocky Bernstein <rb@dustyfeet.com>
1+
# Copyright (c) 2021, 2024 by Rocky Bernstein <rb@dustyfeet.com>
22
#
33
from operator import attrgetter
4+
from typing import Dict, Optional, Tuple
45
from control_flow.graph import (
56
DiGraph,
67
Node,
8+
TreeGraph,
79
jump_flags,
810
BB_JUMP_CONDITIONAL,
911
BB_LOOP,
@@ -13,7 +15,7 @@
1315
)
1416

1517

16-
class ControlFlowGraph(object):
18+
class ControlFlowGraph:
1719
"""
1820
Performs the control-flow analysis on set of basic blocks. It
1921
iterates over its bytecode and builds basic blocks with flag
@@ -25,13 +27,13 @@ def __init__(self, bb_mgr):
2527
self.block_offsets = {}
2628
self.seen_blocks = set()
2729
self.blocks = bb_mgr.bb_list
28-
self.offset2block = {}
29-
self.offset2block_sorted = {}
30+
self.offset2block: Dict[int, Node] = {}
31+
self.offset2block_sorted: Tuple[int, Node] = tuple()
3032
self.block_nodes = {}
3133
self.graph = None
3234
self.entry_node = None
3335
self.exit_node = bb_mgr.exit_block
34-
self.dom_tree = None
36+
self.dom_tree: Optional[TreeGraph] = None
3537
self.analyze(self.blocks, bb_mgr.exit_block)
3638

3739
def analyze(self, blocks, exit_block):
@@ -42,7 +44,7 @@ def analyze(self, blocks, exit_block):
4244
assert (
4345
len(blocks) >= 2
4446
), "Should have at least a start block and an exception exit block"
45-
self.entry = blocks[1]
47+
self.entry_node = blocks[1]
4648
self.build_flowgraph(blocks, exit_block)
4749

4850
def build_flowgraph(self, blocks, exit_block):
@@ -191,7 +193,7 @@ def get_node(self, offset: int) -> Node:
191193
block = self.offset2block_sorted[0]
192194

193195
# FIXME: use binary search
194-
for next_offset, block in self.offset2block_sorted:
196+
for _, block in self.offset2block_sorted:
195197
if block.bb.start_offset <= offset <= block.bb.end_offset:
196198
break
197199
pass

control_flow/dominators.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# -*- coding: utf-8 -*-
2-
# Copyright (c) 2021, 2023 by Rocky Bernstein <rb@dustyfeet.com>
2+
# Copyright (c) 2021, 2023, 2024 by Rocky Bernstein <rb@dustyfeet.com>
33
"""
44
Dominator tree
55
@@ -18,7 +18,7 @@ def __str__(self) -> str:
1818
return f"DominatorSet<{sorted_set}>"
1919

2020

21-
class DominatorTree(object):
21+
class DominatorTree:
2222
"""
2323
Handles the dominator trees (dominator/post-dominator), and the
2424
computation of the dominance/post-dominance frontier.
@@ -27,6 +27,7 @@ class DominatorTree(object):
2727
def __init__(self, cfg, debug=False):
2828
self.cfg = cfg
2929
self.debug = debug
30+
self.root = cfg.entry_node
3031
self.build()
3132

3233
def build(self):
@@ -243,7 +244,8 @@ def build_dom_set1(node, do_pdoms, debug=False):
243244
node.bb.dom_set |= child.bb.dom_set
244245

245246

246-
# Note: this has to be done after calling tree
247+
# Note: this has to be done after calling tree()
248+
# which builds the dominator tree.
247249
def dfs_forest(t, do_pdoms):
248250
"""
249251
Builds data flow graph using Depth-First search.

control_flow/graph.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
:copyright: (c) 2014 by Romain Gaucher (@rgaucher)
88
"""
99

10+
from typing import Optional
11+
1012
# First or Basic block that we entered on. Usually
1113
# at offset 0.
1214
# Does this need to be a set?
@@ -310,7 +312,7 @@ def postorder_traverse1(self, node):
310312
def write_dot(
311313
name: str,
312314
prefix: str,
313-
graph: DiGraph,
315+
graph: Optional[DiGraph],
314316
write_png: bool = False,
315317
debug=True,
316318
dominator_info_format=False,
@@ -321,6 +323,8 @@ def write_dot(
321323
322324
dot is converted to PNG and dumped if `write_bool` is True.
323325
"""
326+
if graph is None:
327+
return
324328
path_safe = name.translate(name.maketrans(" <>", "_[]"))
325329
dot_path = f"{prefix}{path_safe}.dot"
326330
open(dot_path, "w").write(graph.to_dot(False, dominator_info_format))

pytest/example_fns.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
def two_basic_blocks():
2-
"""An example of code with a two basic blocks. The body of the code
3-
and one for execption-branching exit.
1+
def one_basic_block():
2+
"""An example of code with a one basic block.
43
"""
54
return 5
65

pytest/test_bb.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
"""Test control_flow.bb: basic blocks and basd-block management"""
22
from control_flow.bb import basic_blocks, BB_ENTRY, BB_EXIT, BB_RETURN
3-
from example_fns import two_basic_blocks, if_else_blocks
3+
from example_fns import one_basic_block, if_else_blocks
44

5-
debug = False
6-
if debug:
5+
DEBUG = False
6+
if DEBUG:
77
import dis
88

99

@@ -15,7 +15,7 @@ def check_blocks(bb_list: list):
1515
exit_count = 0
1616
return_count = 0
1717
for bb in bb_list:
18-
if debug:
18+
if DEBUG:
1919
print(bb)
2020
if BB_ENTRY in bb.flags:
2121
entry_count += 1
@@ -34,8 +34,8 @@ def check_blocks(bb_list: list):
3434
def test_basic():
3535

3636
offset2inst_index = {}
37-
for fn in (two_basic_blocks, if_else_blocks):
38-
if debug:
37+
for fn in (one_basic_block, if_else_blocks):
38+
if DEBUG:
3939
print(fn.__name__)
4040
dis.dis(fn)
4141
print()

pytest/test_cfg.py

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,37 @@
1-
"""Test control_flow.bb: basic blocks and basd-block management"""
1+
"""Test control_flow.bb: basic blocks and basic-block management"""
2+
23
from typing import Callable
34
from xdis.bytecode import get_instructions_bytes
45
from xdis.std import opc
56
from control_flow.bb import basic_blocks
67
from control_flow.cfg import ControlFlowGraph
7-
from control_flow.graph import write_dot
8-
from example_fns import two_basic_blocks, if_else_blocks
8+
from control_flow.graph import BB_ENTRY, write_dot
9+
from example_fns import one_basic_block, if_else_blocks
910

10-
debug = True
11-
if debug:
11+
DEBUG = True
12+
if DEBUG:
1213
import dis
1314

1415

15-
def check_cfg(fn: Callable, cfg: ControlFlowGraph):
16+
def check_cfg(fn: Callable, cfg: ControlFlowGraph, check_dict: dict):
17+
"""
18+
Check validity of congtrol-flow graph `cfg`. Values in `check_dict()`
19+
are used to assist.
20+
"""
1621
bytecode = fn.__code__.co_code
1722

23+
# Prefix used in assert failures:
24+
prefix = f"In {fn.__name__}:"
25+
26+
assert cfg.entry_node, f"{prefix} control-flow-graph should have non-null entry"
27+
assert (
28+
BB_ENTRY in cfg.entry_node.flags
29+
), f"{prefix}: the root should be marked as an entry node"
30+
31+
assert (
32+
len(cfg.blocks) == check_dict["count"]
33+
), f"{prefix} graph should have {check_dict['count']} blocks"
34+
1835
# Check that all get_node returns the correct node
1936
# for all instruction offsets in bytecode
2037
current_block = cfg.offset2block[0]
@@ -42,7 +59,7 @@ def check_cfg(fn: Callable, cfg: ControlFlowGraph):
4259
# asking for each offset above
4360
assert all(
4461
(inst.offset in offset2block for inst in get_instructions_bytes(bytecode, opc))
45-
)
62+
), (f"{prefix}" "all offsets should be covered by cfg.offset2block")
4663

4764
# Assert offset originally was in offset2block or was added in cache
4865
assert len(offset2block) == cached_offsets + cache_diff
@@ -51,16 +68,19 @@ def check_cfg(fn: Callable, cfg: ControlFlowGraph):
5168

5269
def test_basic():
5370
offset2inst_index = {}
54-
for fn in (two_basic_blocks, if_else_blocks):
55-
if debug:
71+
for fn, check_dict in (
72+
(one_basic_block, {"count": 2}),
73+
(if_else_blocks, {"count": 5}),
74+
):
75+
if DEBUG:
5676
print(fn.__name__)
5777
dis.dis(fn)
5878
print()
5979
bb_mgr = basic_blocks(fn.__code__, offset2inst_index)
6080
cfg = ControlFlowGraph(bb_mgr)
61-
if debug:
81+
if DEBUG:
6282
write_dot(fn.__name__, "/tmp/test_cfg-", cfg.graph, write_png=True)
63-
check_cfg(fn, cfg)
83+
check_cfg(fn, cfg, check_dict)
6484

6585

6686
if __name__ == "__main__":

pytest/test_dom.py

100644100755
Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,58 @@
1+
#!/usr/bin/env python
12
"""Test control_flow.bb: basic blocks and basd-block management"""
2-
from typing import Callable
33

44
# from xdis.bytecode import get_instructions_bytes
55
# from xdis.std import opc
66
from control_flow.bb import basic_blocks
77
from control_flow.cfg import ControlFlowGraph
88
from control_flow.dominators import DominatorTree, dfs_forest, build_dom_set
9-
from control_flow.graph import write_dot
10-
from example_fns import two_basic_blocks, if_else_blocks
9+
from control_flow.graph import BB_ENTRY, write_dot
10+
from example_fns import if_else_blocks, one_basic_block
1111

12-
debug = False
13-
if debug:
12+
DEBUG = True
13+
if DEBUG:
1414
import dis
1515

1616

17-
def check_dom(fn: Callable, dt, cfg: ControlFlowGraph):
17+
def check_dom(
18+
dom_tree: DominatorTree, check_dict: dict, fn_name: str, dead_code_count: int = 0
19+
):
20+
21+
# Prefix used in assert failures:
22+
prefix = f"In {fn_name}:"
23+
24+
assert dom_tree.root, f"{prefix} tree should have non-null root"
25+
assert (
26+
BB_ENTRY in dom_tree.root.flags
27+
), f"{prefix}: the root should be marked as an entry node"
28+
assert check_dict["count"] == len(dom_tree.doms) - dead_code_count
29+
assert (
30+
dom_tree.cfg.graph.nodes == dom_tree.root.dom_set
31+
), f"{prefix} the root node should dominate all nodes"
1832
return
1933

2034

2135
def test_basic():
2236
offset2inst_index = {}
23-
for fn in (two_basic_blocks, if_else_blocks):
37+
for fn, check_dict in (
38+
(one_basic_block, {"count": 2}),
39+
(if_else_blocks, {"count": 5}),
40+
):
2441
name = fn.__name__
25-
if debug:
42+
if DEBUG:
2643
print(name)
2744
dis.dis(fn)
2845
print()
2946
bb_mgr = basic_blocks(fn.__code__, offset2inst_index)
3047
cfg = ControlFlowGraph(bb_mgr)
31-
if debug:
48+
if DEBUG:
3249
write_dot(name, "/tmp/test_dom-", cfg.graph, write_png=True)
33-
dt = DominatorTree(cfg)
34-
cfg.dom_tree = dt.tree(False)
50+
dom_tree = DominatorTree(cfg)
51+
cfg.dom_tree = dom_tree.tree(False)
3552
dfs_forest(cfg.dom_tree, False)
3653
build_dom_set(cfg.dom_tree, False)
3754
write_dot(name, "/tmp/test_dom-dom-", cfg.dom_tree, write_png=True)
38-
check_dom(fn, dt, cfg)
55+
check_dom(dom_tree, check_dict, fn.__name__)
3956

4057

4158
if __name__ == "__main__":

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy