OR-1 dataflow CPU sketch
1"""Serialize IRGraph back to dfasm source text.
2
3This module provides the serialize() function that converts an IRGraph (at any
4pipeline stage) back to valid dfasm source, enabling inspection of IR after
5lowering, resolution, placement, or allocation.
6
7The serializer emits:
8- inst_def lines for nodes: {qualified_ref}|pe{N} <| {mnemonic}[, {const}]
9- plain_edge lines for edges: {source} |> {dest}:{port}
10- data_def lines: {name}|sm{id}:{cell} = {value}
11- FUNCTION regions: $name |> { ...body... }
12- LOCATION regions: bare directive tag followed by body
13
14Names inside FUNCTION regions are unqualified (prefix stripped).
15Anonymous nodes (__anon_*) are always emitted as inst_def + plain_edge,
16never as inline syntax.
17"""
18
19from asm.ir import (
20 IRGraph, IRNode, IREdge, IRRegion, RegionKind, IRDataDef
21)
22from asm.opcodes import OP_TO_MNEMONIC
23from cm_inst import Port
24
25
26def serialize(graph: IRGraph) -> str:
27 """Serialize an IRGraph to dfasm source text.
28
29 Converts an IRGraph back to valid dfasm source at any pipeline stage.
30 Useful for inspecting IR after lowering, resolution, placement, or allocation.
31
32 Args:
33 graph: The IRGraph to serialize
34
35 Returns:
36 A string containing valid dfasm source text
37 """
38 if not graph.nodes and not graph.regions and not graph.data_defs and not graph.edges:
39 return ""
40
41 lines = []
42
43 # Collect all nodes that are inside regions for later exclusion from top-level output
44 nodes_in_regions: set[str] = set()
45 edges_in_regions: set[tuple[str, str, Port]] = set() # Track edges by (source, dest, port) tuple
46 data_defs_in_regions: set[str] = set()
47
48 # First pass: collect what's inside regions
49 for region in graph.regions:
50 for node_name in region.body.nodes.keys():
51 nodes_in_regions.add(node_name)
52 for edge in region.body.edges:
53 edges_in_regions.add((edge.source, edge.dest, edge.port))
54 for data_def in region.body.data_defs:
55 data_defs_in_regions.add(data_def.name)
56
57 # Emit regions in order
58 for region in graph.regions:
59 lines.append(_serialize_region(region, graph))
60
61 # Emit top-level nodes (not inside any region)
62 for name, node in graph.nodes.items():
63 if name not in nodes_in_regions:
64 lines.append(_serialize_node(name, node, func_scope=None))
65
66 # Emit top-level edges (not inside any region)
67 for edge in graph.edges:
68 edge_key = (edge.source, edge.dest, edge.port)
69 if edge_key not in edges_in_regions:
70 lines.append(_serialize_edge(edge))
71
72 # Emit top-level data_defs (not inside any region)
73 for data_def in graph.data_defs:
74 if data_def.name not in data_defs_in_regions:
75 lines.append(_serialize_data_def(data_def))
76
77 # Filter out empty lines and join
78 output = '\n'.join(line for line in lines if line.strip())
79 return output + '\n' if output else ""
80
81
82def _serialize_region(region: IRRegion, parent_graph: IRGraph) -> str:
83 """Serialize a single region (FUNCTION or LOCATION).
84
85 Args:
86 region: The IRRegion to serialize
87 parent_graph: Parent IRGraph (for context)
88
89 Returns:
90 String containing the serialized region
91 """
92 lines = []
93
94 if region.kind == RegionKind.FUNCTION:
95 # FUNCTION regions: $name |> { ...body... }
96 lines.append(f"{region.tag} |> {{")
97
98 # Serialize body with function scope for name unqualification
99 func_scope = region.tag
100 for name, node in region.body.nodes.items():
101 lines.append(_serialize_node(name, node, func_scope=func_scope))
102
103 # Edges inside function
104 for edge in region.body.edges:
105 lines.append(_serialize_edge(edge, func_scope=func_scope))
106
107 # Data defs inside function
108 for data_def in region.body.data_defs:
109 lines.append(_serialize_data_def(data_def))
110
111 lines.append("}")
112
113 elif region.kind == RegionKind.LOCATION:
114 # LOCATION regions: bare directive tag with trailing colon, then body
115 lines.append(f"{region.tag}:")
116
117 # Serialize body (no function scope for locations)
118 for name, node in region.body.nodes.items():
119 lines.append(_serialize_node(name, node, func_scope=None))
120
121 for edge in region.body.edges:
122 lines.append(_serialize_edge(edge))
123
124 for data_def in region.body.data_defs:
125 lines.append(_serialize_data_def(data_def))
126
127 return '\n'.join(lines)
128
129
130def _serialize_node(name: str, node: IRNode, func_scope: str | None) -> str:
131 """Serialize a single IR node as an inst_def line.
132
133 Args:
134 name: The node name
135 node: The IRNode
136 func_scope: If provided, unqualify the name by stripping this prefix
137
138 Returns:
139 String containing the inst_def line
140 """
141 # Unqualify the name if inside a function
142 if func_scope and name.startswith(f"{func_scope}."):
143 display_name = name[len(func_scope) + 1:]
144 else:
145 display_name = name
146
147 # Get mnemonic
148 try:
149 mnemonic = OP_TO_MNEMONIC[node.opcode]
150 except (KeyError, TypeError):
151 # Fallback if mnemonic not found
152 mnemonic = str(node.opcode).lower()
153
154 # Build inst_def line: {ref}|pe{N} <| {mnemonic}[, {const}]
155 pe_part = f"|pe{node.pe}" if node.pe is not None else ""
156 line = f"{display_name}{pe_part} <| {mnemonic}"
157
158 # Add const if present
159 if node.const is not None:
160 line += f", {node.const}"
161
162 return line
163
164
165def _serialize_edge(edge: IREdge, func_scope: str | None = None) -> str:
166 """Serialize a single edge as a plain_edge line.
167
168 Args:
169 edge: The IREdge
170 func_scope: If provided, unqualify names by stripping this prefix
171
172 Returns:
173 String containing the plain_edge line
174 """
175 # Unqualify names if inside a function scope
176 source = edge.source
177 dest = edge.dest
178
179 if func_scope:
180 if source.startswith(f"{func_scope}."):
181 source = source[len(func_scope) + 1:]
182 if dest.startswith(f"{func_scope}."):
183 dest = dest[len(func_scope) + 1:]
184
185 # Format port as :L or :R
186 port_str = ":L" if edge.port == Port.L else ":R"
187
188 return f"{source} |> {dest}{port_str}"
189
190
191def _serialize_data_def(data_def: IRDataDef) -> str:
192 """Serialize a single data definition.
193
194 Args:
195 data_def: The IRDataDef
196
197 Returns:
198 String containing the data_def line
199 """
200 sm_part = f"|sm{data_def.sm_id}" if data_def.sm_id is not None else ""
201 cell_part = f":{data_def.cell_addr}" if data_def.cell_addr is not None else ""
202
203 # Format value as hex if larger than a byte, decimal otherwise
204 value_str = f"0x{data_def.value:x}" if data_def.value > 255 else str(data_def.value)
205
206 return f"{data_def.name}{sm_part}{cell_part} = {value_str}"