Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add mockasm to SCFG with a test generator for the restructuring algorithms #42

Open
wants to merge 36 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
1027588
wip: fix_iter_branch_regions
esc Apr 6, 2023
a08aadb
Merge branch 'mock_asm_for_testing' into main_mock
sklam Apr 12, 2023
f238640
Glue mockasm to scfg generation
sklam Apr 12, 2023
76b9687
Try more cases with mockasm
sklam Apr 12, 2023
f0e08a3
Checking multi-header
sklam Apr 12, 2023
a7cabfb
Merge branch 'pr/40' into main_mock
sklam Apr 12, 2023
999e240
WIP
sklam Apr 12, 2023
ff8e747
Add simulator
sklam Apr 12, 2023
a192222
Fix simulator for control label
sklam Apr 12, 2023
8108201
Avoid branching to head block
sklam Apr 12, 2023
f95a956
Add fuzzing tests and failing cases
sklam Apr 12, 2023
e30ccfa
Changes to help debug problems
sklam Apr 18, 2023
71e02f0
Protect from infinite loops in the SCFG simulator
sklam Apr 18, 2023
7637fe4
Add more failing cases
sklam Apr 18, 2023
15521c3
Tested fuzzer up to 100,000
sklam Apr 25, 2023
9866eb1
Add "doubly loop" case
sklam Apr 25, 2023
ad13ccd
Merge branch 'main' into main_mock
sklam Apr 25, 2023
beabb66
Sync rename of blockmap to SCFG
sklam Apr 25, 2023
c8d4506
Merge branch 'main' into main_mock
sklam Jun 21, 2023
74f3afa
Sync with main
sklam Jun 21, 2023
3fc7260
Enable all passing tests
sklam Jun 21, 2023
2127728
Add back MockAsmRenderer
sklam Jun 21, 2023
cde3426
Dial back fuzzer tests to reduce time consumption
sklam Jun 21, 2023
a54e1d3
Fix formatting
sklam Jun 21, 2023
9f2bc75
WIP: investigating failing tests
sklam Jun 21, 2023
c7d18cd
Fixup some tests
sklam Jun 21, 2023
20bf232
WIP
sklam Jun 22, 2023
f02e5eb
WIP
sklam Jun 22, 2023
4edac82
Ensure mockasm does not generate branches where
sklam Jun 22, 2023
af80d81
Improve fuzzer test by checking against known error kind
sklam Jun 22, 2023
3c02603
Hide debug prints
sklam Jun 22, 2023
118db30
Refactor and cleanup
sklam Jun 22, 2023
c3a5c94
Merge branch 'main' into main_mock
sklam Jun 22, 2023
358ddf3
Add missing tests/__init__.py
sklam Jun 22, 2023
43a75e0
A whole class of bug is resolved since merge main
sklam Jun 23, 2023
371b3df
Reference issue #80
sklam Jun 23, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added numba_rvsdg/tests/__init__.py
Empty file.
287 changes: 283 additions & 4 deletions numba_rvsdg/tests/mock_asm.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,27 @@
"""


from dataclasses import dataclass
from dataclasses import dataclass, field
from enum import IntEnum
from io import StringIO
from typing import IO
import random
import os

from numba_rvsdg.rendering.rendering import SCFGRenderer
from numba_rvsdg.core.datastructures.scfg import SCFG, NameGenerator
from numba_rvsdg.core.datastructures.basic_block import (
BasicBlock,
RegionBlock,
SyntheticBlock,
)
from numba_rvsdg.core.transformations import (
restructure_loop,
restructure_branch,
)


DEBUGGRAPH = int(os.environ.get("MOCKASM_DEBUGGRAPH", 0))


class Opcode(IntEnum):
Expand Down Expand Up @@ -211,11 +228,14 @@ def generate_program(self, min_base_length=5, max_base_length=30) -> str:
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)
target = self.rng.randrange(1, size) # avoid jump back to entry
bb.append(f"{indent}goto BB{target}")
elif kind == "brctr":
target0 = self.rng.randrange(size)
target1 = self.rng.randrange(size)
target0 = self.rng.randrange(1, size) # avoid jump back to entry
target1 = self.rng.randrange(1, size) # avoid jump back to entry
while target1 == target0:
# avoid same target on both side
target1 = self.rng.randrange(1, size)
ctr = self.rng.randrange(1, 10)
bb.append(f"{indent}ctr {ctr}")
bb.append(f"{indent}brctr BB{target0} BB{target1}")
Expand All @@ -224,3 +244,262 @@ def generate_program(self, min_base_length=5, max_base_length=30) -> str:
assert kind == ""
source.extend(bb)
return "\n".join(source)


# The code below are for simulating SCFG transformed MockAsm


@dataclass(frozen=True)
class MockAsmBasicBlock(BasicBlock):
bbinstlist: list[Inst] = field(default_factory=list)
bboffset: int = 0
bbtargets: tuple[int, ...] = ()


def _iter_subregions(scfg: "SCFG"):
for node in scfg.graph.values():
if isinstance(node, RegionBlock):
yield node
yield from _iter_subregions(node.subregion)


def recursive_restructure_loop(scfg: "SCFG"):
restructure_loop(scfg.region)
for region in _iter_subregions(scfg):
restructure_loop(region)


def recursive_restructure_branch(scfg: "SCFG"):
restructure_branch(scfg.region)
for region in _iter_subregions(scfg):
restructure_branch(region)


def to_scfg(instlist: list[Inst]) -> SCFG:
labels = set([0, len(instlist)])
for pc, inst in enumerate(instlist):
if isinstance(inst.operands, GotoOperands):
labels.add(inst.operands.jump_target)
if pc + 1 < len(instlist):
labels.add(pc + 1)
elif isinstance(inst.operands, BrCtrOperands):
labels.add(inst.operands.true_target)
labels.add(inst.operands.false_target)
if pc + 1 < len(instlist):
labels.add(pc + 1)
block_map_graph = {}
scfg = SCFG(block_map_graph, NameGenerator())
bb_offsets = sorted(labels)
labelmap = {}

for begin, end in zip(bb_offsets, bb_offsets[1:]):
labelmap[begin] = label = scfg.name_gen.new_block_name("mock")

for begin, end in zip(bb_offsets, bb_offsets[1:]):
bb = instlist[begin:end]
inst = bb[-1] # terminator
if isinstance(inst.operands, GotoOperands):
targets = [inst.operands.jump_target]
elif isinstance(inst.operands, BrCtrOperands):
targets = [inst.operands.true_target, inst.operands.false_target]
elif end < len(instlist):
targets = [end]
else:
targets = []

label = labelmap[begin]
block = MockAsmBasicBlock(
name=label,
bbinstlist=bb,
bboffset=begin,
bbtargets=tuple(targets),
_jump_targets=tuple(labelmap[tgt] for tgt in targets),
)
scfg.add_block(block)

# remove dead code from reachabiliy of entry block
reachable = set([labelmap[0]])
stack = [labelmap[0]]
while stack:
blk: BasicBlock = scfg.graph[stack.pop()]
for k in blk._jump_targets:
if k not in reachable:
stack.append(k)
reachable.add(k)
scfg.remove_blocks(set(scfg.graph.keys()) - reachable)

print("YAML\n", scfg.to_yaml())
scfg.join_returns()
if DEBUGGRAPH:
MockAsmRenderer(scfg).view("jointed")
recursive_restructure_loop(scfg)
if DEBUGGRAPH:
MockAsmRenderer(scfg).view("loop")
recursive_restructure_branch(scfg)
if DEBUGGRAPH:
MockAsmRenderer(scfg).view("branch")
return scfg


class MockAsmRenderer(SCFGRenderer):
def render_block(self, digraph, name: str, block: BasicBlock):
if isinstance(block, MockAsmBasicBlock):
# Extend base renderer

# format bbinstlist
instbody = []
for inst in block.bbinstlist:
instbody.append(f"\l {inst}")

body = (
name
+ "\l"
+ "\n"
+ "".join(instbody)
+ "\n"
+ "\njump targets: "
+ str(block.jump_targets)
+ "\nback edges: "
+ str(block.backedges)
)

digraph.node(str(name), shape="rect", label=body)
else:
super().render_block(digraph, name, block)


class MaxStepError(Exception):
pass


class Simulator:
DEBUG = False

def __init__(self, scfg: SCFG, buf: StringIO, max_step):
self.vm = VM(buf)
self.scfg = scfg
self.region_stack = []
self.ctrl_varmap = dict()
self.max_step = max_step
self.step = 0

def _debug_print(self, *args, **kwargs):
if self.DEBUG:
print(*args, **kwargs)

def run(self):
scfg = self.scfg
label = scfg.find_head()
while True:
action = self.run_block(self.scfg.graph[label])
# If we need to return, break and do so
if "return" in action:
break # break and return action
elif "jumpto" in action:
label = action["jumpto"]
# Otherwise check if we stay in the region and break otherwise
if label in self.scfg.graph:
continue # stay in the region
else:
break # break and return action
else:
assert False, "unreachable" # in case of coding errors

def run_block(self, block):
self._debug_print("run block", block.name)
if isinstance(block, RegionBlock):
return self.run_RegionBlock(block)
elif isinstance(block, MockAsmBasicBlock):
return self.run_MockAsmBasicBlock(block)
elif isinstance(block, SyntheticBlock):
self._debug_print(" ", block)
label = block.name
handler = getattr(self, f"synth_{type(block).__name__}")
out = handler(label, block)
self._debug_print(" ctrl_varmap dump:", self.ctrl_varmap)
return out
else:
assert False, type(block)

def run_RegionBlock(self, block: RegionBlock):
self.region_stack.append(block)

label = block.subregion.find_head()
while True:
action = self.run_block(block.subregion.graph[label])
# If we need to return, break and do so
if "return" in action:
break # break and return action
elif "jumpto" in action:
label = action["jumpto"]
# Otherwise check if we stay in the region and break otherwise
if label in block.subregion.graph:
continue # stay in the region
else:
break # break and return action
else:
assert False, "unreachable" # in case of coding errors

self.region_stack.pop()
return action

def run_MockAsmBasicBlock(self, block: MockAsmBasicBlock):
vm = self.vm
pc = block.bboffset

if self.step > self.max_step:
raise MaxStepError("step > max_step")

for inst in block.bbinstlist:
self._debug_print("inst", pc, inst)
pc = vm.eval_inst(pc, inst)
self.step += 1
if block.bbtargets:
pos = block.bbtargets.index(pc)
label = block._jump_targets[pos]
return {"jumpto": label}
else:
return {"return": None}

### Synthetic Instructions ###
def synth_SyntheticAssignment(self, control_label, block):
self.ctrl_varmap.update(block.variable_assignment)
[label] = block.jump_targets
return {"jumpto": label}

def _synth_branch(self, control_label, block):
jump_target = block.branch_value_table[
self.ctrl_varmap[block.variable]
]
return {"jumpto": jump_target}

def synth_SyntheticExitingLatch(self, control_label, block):
return self._synth_branch(control_label, block)

def synth_SyntheticHead(self, control_label, block):
return self._synth_branch(control_label, block)

def synth_SyntheticExitBranch(self, control_label, block):
return self._synth_branch(control_label, block)

def synth_SyntheticFill(self, control_label, block):
[label] = block.jump_targets
return {"jumpto": label}

def synth_SyntheticReturn(self, control_label, block):
[label] = block.jump_targets
return {"jumpto": label}

def synth_SyntheticTail(self, control_label, block):
[label] = block.jump_targets
return {"jumpto": label}

def synth_SyntheticBranch(self, control_label, block):
[label] = block.jump_targets
return {"jumpto": label}


def simulate_scfg(scfg: SCFG):
with StringIO() as buf:
Simulator(scfg, buf, max_step=1000).run()
return buf.getvalue()
Loading