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

Debug generator rewriter #1206

Draft
wants to merge 69 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
1b7eac4
WIP metaclass name inference
leonardt Nov 19, 2022
6f859d1
Remove extra line
leonardt Nov 19, 2022
8ecac9e
Update smart bits tests
leonardt Nov 21, 2022
a561b98
Update tests
leonardt Nov 21, 2022
d9f3db1
Improve port view logic
leonardt Nov 22, 2022
1cbea3a
Remove debug print
leonardt Nov 22, 2022
0ac14ce
WIP namer dict
leonardt Nov 23, 2022
b226492
Finish NamerDict patterng
leonardt Nov 24, 2022
6c0e8c9
Add self namer logic to Generator2
leonardt Nov 28, 2022
b6a7745
Merge remote-tracking branch 'origin/master' into namer-dict
leonardt Nov 28, 2022
0205f30
Merge branch 'master' into namer-dict
leonardt Dec 6, 2022
3d3ec57
Update gold
leonardt Dec 6, 2022
43059e0
Prototype debug transformer
leonardt Apr 4, 2022
6289d9c
Use inline True
leonardt Apr 4, 2022
eb0a37d
Add preliminary bench
leonardt Apr 21, 2022
120b33b
use inspect.getfile
leonardt Apr 21, 2022
e2cb6c8
Use exec/compile pattern
leonardt Apr 21, 2022
1b9372f
Run pass later
leonardt Apr 21, 2022
6bb660b
Rename helper
leonardt Apr 21, 2022
76a11b3
Include col_offset
leonardt Apr 21, 2022
7266f62
Migrate to libcst
leonardt Oct 6, 2022
dd6242e
Simple default logic
leonardt Nov 1, 2022
0b64609
Remove file/line logic
leonardt Dec 6, 2022
a3541f0
Use namer-dict pattern
leonardt Dec 6, 2022
9d06e7a
Remove debug print
leonardt Dec 6, 2022
31b0df4
Update bench
leonardt Dec 6, 2022
100e4e4
Fix flag
leonardt Dec 6, 2022
8b96493
Fix regression, update golds
leonardt Dec 6, 2022
aa277aa
Update golds
leonardt Dec 7, 2022
8942956
Update golds, add lstrip logic
leonardt Dec 7, 2022
a1c7b68
Add comment
leonardt Dec 7, 2022
58a2c01
Merge branch 'master' into namer-dict
leonardt Dec 13, 2022
2432a0c
Guard behind debug mode flag
leonardt Dec 13, 2022
f6e3e0d
Fix conftest
leonardt Dec 13, 2022
0b1cd45
Separate debug mode and namer dict flag
leonardt Dec 13, 2022
00653f8
Fix guard
leonardt Dec 13, 2022
b63e58f
Merge branch 'namer-dict' into debug-generator
leonardt Dec 13, 2022
3705065
Fix conftest
leonardt Dec 13, 2022
9ff6176
Merge branch 'namer-dict' into debug-generator
leonardt Dec 13, 2022
9726578
Add config flag
leonardt Dec 13, 2022
67ee66b
Update configure pattern
leonardt Dec 13, 2022
60a4382
Fix typo
leonardt Dec 13, 2022
3fc3dea
Merge branch 'namer-dict' into debug-generator
leonardt Dec 13, 2022
57cff72
Fix bench
leonardt Dec 13, 2022
ae23884
update gold
leonardt Dec 13, 2022
ca331fb
Fix guard
leonardt Dec 13, 2022
bd29c11
Fix config
leonardt Dec 13, 2022
a3a2452
Temporarily disable namer-dict globally
leonardt Dec 15, 2022
d0812eb
Add namer dict tests
leonardt Dec 15, 2022
e4b0226
Add generator test
leonardt Dec 15, 2022
8b96d18
Add collision logic
leonardt Dec 15, 2022
411205b
Update comment
leonardt Dec 15, 2022
397baf6
Add missing gold files
leonardt Dec 15, 2022
3295e3e
Refactor
leonardt Dec 16, 2022
b3cd6d5
Add lazy logic
leonardt Dec 16, 2022
ede8c1c
Add comment
leonardt Dec 16, 2022
911ae9f
Formatting
leonardt Dec 16, 2022
c4a4a12
Formatting
leonardt Dec 16, 2022
84db2cc
Add note on commit for golds
leonardt Dec 16, 2022
c943ca3
Revert wire changes
leonardt Dec 16, 2022
6196798
Rework register logic
leonardt Dec 16, 2022
24c10ac
Cleanup smartbits logic
leonardt Dec 16, 2022
568afb6
Avoid LazyNamedValue logic
leonardt Dec 16, 2022
bd88826
Update smartbits protocol logic
leonardt Dec 16, 2022
dbbbc9a
Merge branch 'master' into namer-dict
leonardt Dec 16, 2022
2a32127
Add missing gold
leonardt Dec 16, 2022
4833d11
Merge branch 'namer-dict' into debug-generator
leonardt Dec 16, 2022
0404c3c
Remove metadata logic
leonardt Dec 16, 2022
a6b7028
Need namerdict for generator test
leonardt Dec 16, 2022
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
46 changes: 46 additions & 0 deletions benchmarks/debug.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import pygal
import timeit
import magma as m


def gen_circuit(debug_mode, n=8):
if debug_mode == 2:
m.config.config.use_generator_debug_rewriter = True
else:
m.config.config.use_generator_debug_rewriter = False

class Foo(m.Generator2):
if debug_mode == 2:
def __init__(self, n):
self.io = m.IO(I=m.In(m.Bits[n]), O=m.Out(m.Bit))
x = m.Bits[n]()
x @= ~self.io.I
self.io.O @= x.reduce_xor()
elif debug_mode == 1:
def __init__(self, n):
m.config.set_debug_mode(True)
self.io = m.IO(I=m.In(m.Bits[n]), O=m.Out(m.Bit))
x = m.Bits[n]()
x @= ~self.io.I
self.io.O @= x.reduce_xor()
m.config.set_debug_mode(False)
else:
def __init__(self, n):
self.io = m.IO(I=m.In(m.Bits[n]), O=m.Out(m.Bit))
x = m.Bits[n]()
x @= ~self.io.I
self.io.O @= x.reduce_xor()
Foo(n)


data = {}

for debug_mode in [0, 1, 2]:
data[debug_mode] = timeit.Timer(lambda:
gen_circuit(debug_mode)).timeit(number=10)

bar = pygal.Bar()
bar.add('Off', data[0])
bar.add('Old', data[1])
bar.add('New', data[2])
bar.render_to_file("debug.svg")
4 changes: 4 additions & 0 deletions benchmarks/debug.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 5 additions & 1 deletion conftest.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import pytest
import magma.config
from magma.config import config as magma_config
from magma.util import reset_global_context


def pytest_configure(config):
magma_config.compile_dir = 'callee_file_dir'
magma_config.use_namer_dict = True
magma_config.use_generator_debug_rewriter = True
# TODO: Enable this globally for testing
# revert a3a2452168f2251277a14548d053a9ca7a45bff7 for golds
# magma_config.use_namer_dict = True
magma_config.use_uinspect = True


Expand Down
1 change: 1 addition & 0 deletions magma/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ def set_mantle_target(t):
from magma.when import when, elsewhen, otherwise
from magma.value_utils import fill
from magma.bind2 import bind2, make_bind_ports
from magma.debug_rewriter import debug

################################################################################
# BEGIN ALIASES
Expand Down
5 changes: 3 additions & 2 deletions magma/backend/check_wiring_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ def check_wiring_context(i, o):
"""
Ensures that i and o come from the same definition context
"""
orig_i, orig_o = i, o
if isinstance(o, Slice):
o = o.value
if isinstance(i, Slice):
Expand Down Expand Up @@ -53,5 +54,5 @@ def check_wiring_context(i, o):
o_inst.defn is i_inst.defn):
return
raise MagmaCompileException(
f"Cannot wire {o.debug_name} to {i.debug_name} because they are"
" not from the same definition context")
f"Cannot wire {orig_o.debug_name} to {orig_i.debug_name} because they "
"are not from the same definition context")
99 changes: 84 additions & 15 deletions magma/circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@
from functools import wraps
import functools
import operator
from collections import namedtuple
from collections import namedtuple, Counter
import os
from typing import Callable, Dict, List
from typing import Callable, Dict, List, Union

import six
from . import cache_definition
from .common import deprecated, setattrs, OrderedIdentitySet
from .interface import *
Expand All @@ -28,6 +27,7 @@
pass

from magma.clock import is_clock_or_nested_clock, Clock, ClockTypes
from magma.config import get_debug_mode, set_debug_mode, config, RuntimeConfig
from magma.definition_context import (
DefinitionContext,
definition_context_manager,
Expand All @@ -37,8 +37,9 @@
)
from magma.find_unconnected_ports import find_and_log_unconnected_ports
from magma.logging import root_logger, capture_logs
from magma.ref import TempNamedRef
from magma.t import In
from magma.protocol_type import MagmaProtocol
from magma.ref import TempNamedRef, AnonRef
from magma.t import In, Type
from magma.view import PortView
from magma.wire_container import WiringLog, AggregateWireable

Expand Down Expand Up @@ -204,14 +205,78 @@ def _get_intermediate_values(value):
return values


class NamerDict(dict):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._inferred_names = {}

def _set_name(self, key, value):
if isinstance(value, (Type, MagmaProtocol)):
key = TempNamedRef(key, value)
value.name = key

def _check_unique_name(
self,
key: str,
value: Union[Type, 'Circuit'],
):
"""If key has been seen more than once, "uniquify" the names by append
_{i} to them."""
values = self._inferred_names.setdefault(key, [])
if len(values) == 0:
self._set_name(key, value)
elif any(value is x for x in values):
# Already uniquified
return
else:
if len(values) == 1:
# Make the first value consistent by append _0
self._set_name(f"{key}_0", values[0])
self._set_name(f"{key}_{len(values)}", value)
values.append(value)

def _set_value_name(self, key: str, value: Type):
if not hasattr(value, "name"):
return # Interface object is a Type without a name.
if isinstance(value.name, AnonRef):
self._check_unique_name(key, value)
elif isinstance(value.name, TempNamedRef):
self._check_unique_name(value.name.name, value)

def _set_inst_name(self, key: str, value: 'Circuit'):
if not value.name:
self._check_unique_name(key, value)
else:
self._check_unique_name(value.name, value)

def __setitem__(self, key, value):
super().__setitem__(key, value)
if isinstance(value, (Type, MagmaProtocol)):
self._set_value_name(key, value)
elif isinstance(type(value), CircuitKind):
# NOTE: we check type(value) because this code is run in the Circuit
# class creation pipeline (so Circuit may not be defined yet).
self._set_inst_name(key, value)

def __hash__(self):
return hash(tuple(sorted(self.items())))


config.register(use_namer_dict=RuntimeConfig(False))


class CircuitKind(type):
def __prepare__(name, bases, **kwargs):
ctx = DefinitionContext(StagedPlacer(name))
push_definition_context(ctx, use_staged_logger=True)
if config.use_namer_dict:
return NamerDict()
return type.__prepare__(name, bases, **kwargs)

"""Metaclass for creating circuits."""
def __new__(metacls, name, bases, dct):
if isinstance(dct, NamerDict):
dct = dict(dct) # resolve to dict, no longer need name logic
# Override class name if supplied (and save class name).
cls_name = dct.get("_cls_name_", name)
name = dct.setdefault('name', name)
Expand Down Expand Up @@ -359,8 +424,7 @@ def inline_verilog(cls, inline_str, **kwargs):
m_inline_verilog(inline_str, **kwargs)


@six.add_metaclass(CircuitKind)
class AnonymousCircuitType(object):
class AnonymousCircuitType(object, metaclass=CircuitKind):
"""Abstract base class for circuits"""

_circuit_base_ = True
Expand Down Expand Up @@ -390,12 +454,18 @@ def set_debug_info(self, debug_info):
self.debug_info = debug_info

def __str__(self):
if self.name:
return f"{self.name}<{type(self)}>"
name = f"AnonymousCircuitInst{id(self)}"
interface = ", ".join(f"{name}: {type(value)}"
for name, value in self.interface.ports.items())
return f"{name}<{interface}>"
name = self.name
if not name:
name = f"AnonymousCircuitInst{id(self)}"
if type(self) is AnonymousCircuitType:
interface = ", ".join(
f"{name}: {type(value)}"
for name, value in self.interface.ports.items()
)
name += f"<{interface}>"
else:
name += f"<{type(self)}>"
return name

def __repr__(self):
args = []
Expand Down Expand Up @@ -685,8 +755,7 @@ def bind(cls, monitor, *args, compile_guard=None):
cls.bind_modules[monitor] = (args, compile_guard)


@six.add_metaclass(DefineCircuitKind)
class Circuit(CircuitType):
class Circuit(CircuitType, metaclass=DefineCircuitKind):
_circuit_base_ = True


Expand Down
4 changes: 2 additions & 2 deletions magma/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,7 @@ class ConfigManager:
__entries = {}

def __init__(self, **kwargs):
for key, value in kwargs.items():
ConfigManager.__entries[key] = value
self._register(**kwargs)

def _register(self, **kwargs):
for key, value in kwargs.items():
Expand Down Expand Up @@ -92,6 +91,7 @@ def __setitem__(self, key, value):

config = ConfigManager(
compile_dir=RuntimeConfig("normal"),
use_generator_debug_rewriter=RuntimeConfig(False),
debug_mode=RuntimeConfig(False)
)

Expand Down
60 changes: 60 additions & 0 deletions magma/debug_rewriter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import inspect
import textwrap

import libcst as cst
from magma.t import Type
from magma.ref import AnonRef, NamedRef


class Transformer(cst.CSTTransformer):
METADATA_DEPENDENCIES = (cst.metadata.PositionProvider,)

def leave_Assign(
self, original_node: cst.FunctionDef, updated_node: cst.FunctionDef
) -> cst.CSTNode:
if len(updated_node.targets) != 1:
# TODO: handle chained assigns
return updated_node
if not isinstance(updated_node.targets[0], cst.AssignTarget):
# TODO: Handle this case
return updated_node
if not isinstance(updated_node.targets[0].target, cst.Name):
return updated_node

name = updated_node.targets[0].target.value
targets = updated_node.targets + (
cst.AssignTarget(cst.parse_expression(f"self.{name}")),
)
return updated_node.with_changes(targets=targets)

def visit_FunctionDef(self, node: cst.FunctionDef) -> bool:
return True

def leave_FunctionDef(
self, original_node: cst.FunctionDef, updated_node: cst.FunctionDef
) -> cst.CSTNode:
return updated_node.with_changes(decorators=[])


def debug(fn):
indented_program_txt = inspect.getsource(fn)
program_txt = textwrap.dedent(indented_program_txt)
# Dedenting might not work because they might have a triple block quote
# string that's not indented, so as a fallback we try removing the initial
# indent at least, which is fine if the body is overindented as long as it's
# consistent
program_txt = program_txt.lstrip()
tree = cst.parse_module(program_txt)
tree = tree.visit(Transformer())

namespace = dict(**fn.__globals__)
exec(tree.code, namespace)
debug_fn = namespace[fn.__name__]

def wrapper(*args, **kwargs):
try:
return debug_fn(*args, **kwargs)
except Exception:
# TODO: Namespace issues, need to inherite fn closure
return fn(*args, **kwargs)
return wrapper
17 changes: 14 additions & 3 deletions magma/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
import functools
import weakref
from .circuit import (DefineCircuitKind, Circuit, DebugCircuit,
DebugDefineCircuitKind)
DebugDefineCircuitKind, NamerDict)
from . import cache_definition
from magma.common import ParamDict
from magma.config import config
from magma.debug_rewriter import debug
from hwtypes import BitVector


Expand Down Expand Up @@ -70,10 +72,14 @@ def _make_key(cls, *args, **kwargs):


def _make_type(cls, *args, **kwargs):
dummy = type.__new__(cls, "", (), {})
dummy = type.__new__(cls, "", (), {
"_namer_dict": NamerDict()
})
name = cls.__name__
bases = (cls._base_cls_,)
dct = cls._base_metacls_.__prepare__(name, bases)
if config.use_generator_debug_rewriter:
cls.__init__ = debug(cls.__init__)
cls.__init__(dummy, *args, **kwargs)
dct.update(dict(dummy.__dict__))
# NOTE(leonardt): We need to override the Generator2 classmethod bind with
Expand Down Expand Up @@ -110,7 +116,7 @@ def __call__(cls, *args, **kwargs):
len(args) == 3
and type(args[0]) is str
and type(args[1]) is tuple
and type(args[2]) is dict
and (type(args[2]) is dict or type(args[2]) is NamerDict)
and "__module__" in args[2]
)
if is_base_cls:
Expand Down Expand Up @@ -141,6 +147,11 @@ def __call__(cls, *args, **kwargs):
def bind(cls, monitor):
cls.bind_generators.append(monitor)

def __setattr__(cls, key, value):
if config.use_namer_dict:
cls._namer_dict[key] = value
super().__setattr__(key, value)


class _DebugGeneratorMeta(_Generator2Meta):
_base_cls_ = DebugCircuit
Expand Down
Loading