CIRCT  19.0.0git
support.py
Go to the documentation of this file.
1 # Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
2 # See https://llvm.org/LICENSE.txt for license information.
3 # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4 
5 from . import ir
6 
7 from contextlib import AbstractContextManager
8 from contextvars import ContextVar
9 from typing import List
10 
11 _current_backedge_builder = ContextVar("current_bb")
12 
13 
15  pass
16 
17 
19 
20  def __init__(self, module: str, port_names: List[str]):
21  super().__init__(
22  f"Ports {port_names} unconnected in design module {module}.")
23 
24 
25 def get_value(obj) -> ir.Value:
26  """Resolve a Value from a few supported types."""
27 
28  if isinstance(obj, ir.Value):
29  return obj
30  if hasattr(obj, "result"):
31  return obj.result
32  if hasattr(obj, "value"):
33  return obj.value
34  return None
35 
36 
37 def connect(destination, source):
38  """A convenient way to use BackedgeBuilder."""
39  if not isinstance(destination, OpOperand):
40  raise TypeError(
41  f"cannot connect to destination of type {type(destination)}. "
42  "Must be OpOperand.")
43  value = get_value(source)
44  if value is None:
45  raise TypeError(f"cannot connect from source of type {type(source)}")
46 
47  index = destination.index
48  destination.operation.operands[index] = value
49  if destination.backedge_owner and \
50  index in destination.backedge_owner.backedges:
51  destination.backedge_owner.backedges[index].erase()
52  del destination.backedge_owner.backedges[index]
53 
54 
55 def var_to_attribute(obj, none_on_fail: bool = False) -> ir.Attribute:
56  """Create an MLIR attribute from a Python object for a few common cases."""
57  if isinstance(obj, ir.Attribute):
58  return obj
59  if isinstance(obj, bool):
60  return ir.BoolAttr.get(obj)
61  if isinstance(obj, int):
62  attrTy = ir.IntegerType.get_signless(64)
63  return ir.IntegerAttr.get(attrTy, obj)
64  if isinstance(obj, str):
65  return ir.StringAttr.get(obj)
66  if isinstance(obj, list):
67  arr = [var_to_attribute(x, none_on_fail) for x in obj]
68  if all(arr):
69  return ir.ArrayAttr.get(arr)
70  return None
71  if none_on_fail:
72  return None
73  raise TypeError(f"Cannot convert type '{type(obj)}' to MLIR attribute")
74 
75 
76 # There is currently no support in MLIR for querying type types. The
77 # conversation regarding how to achieve this is ongoing and I expect it to be a
78 # long one. This is a way that works for now.
79 def type_to_pytype(t) -> ir.Type:
80 
81  if not isinstance(t, ir.Type):
82  raise TypeError("type_to_pytype only accepts MLIR Type objects")
83 
84  # If it's not the root type, assume it's already been downcasted and don't do
85  # the expensive probing below.
86  if t.__class__ != ir.Type:
87  return t
88 
89  from .dialects import esi, hw, seq
90  try:
91  return ir.IntegerType(t)
92  except ValueError:
93  pass
94  try:
95  return ir.NoneType(t)
96  except ValueError:
97  pass
98  try:
99  return hw.ArrayType(t)
100  except ValueError:
101  pass
102  try:
103  return hw.StructType(t)
104  except ValueError:
105  pass
106  try:
107  return hw.TypeAliasType(t)
108  except ValueError:
109  pass
110  try:
111  return hw.InOutType(t)
112  except ValueError:
113  pass
114  try:
115  return seq.ClockType(t)
116  except ValueError:
117  pass
118  try:
119  return esi.ChannelType(t)
120  except ValueError:
121  pass
122  try:
123  return esi.BundleType(t)
124  except ValueError:
125  pass
126 
127  raise TypeError(f"Cannot convert {repr(t)} to python type")
128 
129 
130 # There is currently no support in MLIR for querying attribute types. The
131 # conversation regarding how to achieve this is ongoing and I expect it to be a
132 # long one. This is a way that works for now.
134 
135  if attr is None:
136  return None
137  if not isinstance(attr, ir.Attribute):
138  raise TypeError("attribute_to_var only accepts MLIR Attributes")
139 
140  # If it's not the root type, assume it's already been downcasted and don't do
141  # the expensive probing below.
142  if attr.__class__ != ir.Attribute and hasattr(attr, "value"):
143  return attr.value
144 
145  from .dialects import hw, om
146  try:
147  return ir.BoolAttr(attr).value
148  except ValueError:
149  pass
150  try:
151  return ir.IntegerAttr(attr).value
152  except ValueError:
153  pass
154  try:
155  return ir.StringAttr(hw.InnerSymAttr(attr).symName).value
156  except ValueError:
157  pass
158  try:
159  return ir.StringAttr(attr).value
160  except ValueError:
161  pass
162  try:
163  return ir.FlatSymbolRefAttr(attr).value
164  except ValueError:
165  pass
166  try:
167  return ir.TypeAttr(attr).value
168  except ValueError:
169  pass
170  try:
171  arr = ir.ArrayAttr(attr)
172  return [attribute_to_var(x) for x in arr]
173  except ValueError:
174  pass
175  try:
176  dict = ir.DictAttr(attr)
177  return {i.name: attribute_to_var(i.attr) for i in dict}
178  except ValueError:
179  pass
180  try:
181  return attribute_to_var(om.ReferenceAttr(attr).inner_ref)
182  except ValueError:
183  pass
184  try:
185  ref = hw.InnerRefAttr(attr)
186  return (ir.StringAttr(ref.module).value, ir.StringAttr(ref.name).value)
187  except ValueError:
188  pass
189  try:
190  return list(map(attribute_to_var, om.ListAttr(attr)))
191  except ValueError:
192  pass
193  try:
194  return {name: attribute_to_var(value) for name, value in om.MapAttr(attr)}
195  except ValueError:
196  pass
197  try:
198  return int(str(om.OMIntegerAttr(attr)))
199  except ValueError:
200  pass
201  try:
202  return om.PathAttr(attr).value
203  except ValueError:
204  pass
205 
206  raise TypeError(f"Cannot convert {repr(attr)} to python value")
207 
208 
209 def get_self_or_inner(mlir_type):
210  from .dialects import hw
211  if type(mlir_type) is ir.Type:
212  mlir_type = type_to_pytype(mlir_type)
213  if isinstance(mlir_type, hw.TypeAliasType):
214  return type_to_pytype(mlir_type.inner_type)
215  return mlir_type
216 
217 
218 class BackedgeBuilder(AbstractContextManager):
219 
220  class Edge:
221 
222  def __init__(self,
223  creator,
224  type: ir.Type,
225  backedge_name: str,
226  op_view,
227  instance_of: ir.Operation,
228  loc: ir.Location = None):
229  self.creator: BackedgeBuilder = creator
230  self.dummy_opdummy_op = ir.Operation.create("builtin.unrealized_conversion_cast",
231  [type],
232  loc=loc)
233  self.instance_ofinstance_of = instance_of
234  self.op_viewop_view = op_view
235  self.port_nameport_name = backedge_name
236  self.erasederased = False
237 
238  @property
239  def result(self):
240  return self.dummy_opdummy_op.result
241 
242  def erase(self):
243  if self.erasederased:
244  return
245  if self in self.creator.edges:
246  self.creator.edges.remove(self)
247  self.dummy_opdummy_op.operation.erase()
248 
249  def __init__(self):
250  self.edgesedges = set()
251 
252  @staticmethod
253  def current():
254  bb = _current_backedge_builder.get(None)
255  if bb is None:
256  raise RuntimeError("No backedge builder found in context!")
257  return bb
258 
259  @staticmethod
260  def create(*args, **kwargs):
261  return BackedgeBuilder.current()._create(*args, **kwargs)
262 
263  def _create(self,
264  type: ir.Type,
265  port_name: str,
266  op_view,
267  instance_of: ir.Operation = None,
268  loc: ir.Location = None):
269  edge = BackedgeBuilder.Edge(self, type, port_name, op_view, instance_of,
270  loc)
271  self.edgesedges.add(edge)
272  return edge
273 
274  def __enter__(self):
275  self.old_bb_tokenold_bb_token = _current_backedge_builder.set(self)
276 
277  def __exit__(self, exc_type, exc_value, traceback):
278  if exc_value is not None:
279  return
280  _current_backedge_builder.reset(self.old_bb_tokenold_bb_token)
281  errors = []
282  for edge in list(self.edgesedges):
283  # TODO: Make this use `UnconnectedSignalError`.
284  msg = "Backedge: " + edge.port_name + "\n"
285  if edge.instance_of is not None:
286  msg += "InstanceOf: " + str(edge.instance_of).split(" {")[0] + "\n"
287  if edge.op_view is not None:
288  op = edge.op_view.operation
289  msg += "Instance: " + str(op)
290  errors.append(msg)
291 
292  if errors:
293  errors.insert(0, "Uninitialized backedges remain in circuit!")
294  raise RuntimeError("\n".join(errors))
295 
296 
297 class OpOperand:
298  __slots__ = ["index", "operation", "value", "backedge_owner"]
299 
300  def __init__(self,
301  operation: ir.Operation,
302  index: int,
303  value,
304  backedge_owner=None):
305  if not isinstance(index, int):
306  raise TypeError("Index must be int")
307  self.indexindex = index
308 
309  if not hasattr(operation, "operands"):
310  raise TypeError("Operation must be have 'operands' attribute")
311  self.operationoperation = operation
312 
313  self.valuevalue = value
314  self.backedge_ownerbackedge_owner = backedge_owner
315 
316  @property
317  def type(self):
318  return self.valuevalue.type
319 
320 
322  """Helper class to incrementally construct an instance of an operation that
323  names its operands and results"""
324 
325  def __init__(self,
326  cls,
327  data_type=None,
328  input_port_mapping=None,
329  pre_args=None,
330  post_args=None,
331  needs_result_type=False,
332  **kwargs):
333  # Set defaults
334  if input_port_mapping is None:
335  input_port_mapping = {}
336  if pre_args is None:
337  pre_args = []
338  if post_args is None:
339  post_args = []
340 
341  # Set result_indices to name each result.
342  result_names = self.result_names()
343  result_indices = {}
344  for i in range(len(result_names)):
345  result_indices[result_names[i]] = i
346 
347  # Set operand_indices to name each operand. Give them an initial value,
348  # either from input_port_mapping or a default value.
349  backedges = {}
350  operand_indices = {}
351  operand_values = []
352  operand_names = self.operand_names()
353  for i in range(len(operand_names)):
354  arg_name = operand_names[i]
355  operand_indices[arg_name] = i
356  if arg_name in input_port_mapping:
357  value = get_value(input_port_mapping[arg_name])
358  operand = value
359  else:
360  backedge = self.create_default_valuecreate_default_value(i, data_type, arg_name)
361  backedges[i] = backedge
362  operand = backedge.result
363  operand_values.append(operand)
364 
365  # Some ops take a list of operand values rather than splatting them out.
366  if isinstance(data_type, list):
367  operand_values = [operand_values]
368 
369  # In many cases, result types are inferred, and we do not need to pass
370  # data_type to the underlying constructor. It must be provided to
371  # NamedValueOpView in cases where we need to build backedges, but should
372  # generally not be passed to the underlying constructor in this case. There
373  # are some oddball ops that must pass it, even when building backedges, and
374  # these set needs_result_type=True.
375  if data_type is not None and (needs_result_type or len(backedges) == 0):
376  pre_args.insert(0, data_type)
377 
378  self.opviewopview = cls(*pre_args, *operand_values, *post_args, **kwargs)
379  self.operand_indicesoperand_indices = operand_indices
380  self.result_indicesresult_indices = result_indices
381  self.backedgesbackedges = backedges
382 
383  def __getattr__(self, name):
384  # Check for the attribute in the arg name set.
385  if "operand_indices" in dir(self) and name in self.operand_indicesoperand_indices:
386  index = self.operand_indicesoperand_indices[name]
387  value = self.opviewopview.operands[index]
388  return OpOperand(self.opviewopview.operation, index, value, self)
389 
390  # Check for the attribute in the result name set.
391  if "result_indices" in dir(self) and name in self.result_indicesresult_indices:
392  index = self.result_indicesresult_indices[name]
393  value = self.opviewopview.results[index]
394  return OpOperand(self.opviewopview.operation, index, value, self)
395 
396  # Forward "attributes" attribute from the operation.
397  if name == "attributes":
398  return self.opviewopview.operation.attributes
399 
400  # If we fell through to here, the name isn't a result.
401  raise AttributeError(f"unknown port name {name}")
402 
403  def create_default_value(self, index, data_type, arg_name):
404  return BackedgeBuilder.create(data_type, arg_name, self)
405 
406  @property
407  def operation(self):
408  """Get the operation associated with this builder."""
409  return self.opviewopview.operation
def __init__(self, creator, ir.Type type, str backedge_name, op_view, ir.Operation instance_of, ir.Location loc=None)
Definition: support.py:228
def _create(self, ir.Type type, str port_name, op_view, ir.Operation instance_of=None, ir.Location loc=None)
Definition: support.py:268
def __exit__(self, exc_type, exc_value, traceback)
Definition: support.py:277
def create(*args, **kwargs)
Definition: support.py:260
def create_default_value(self, index, data_type, arg_name)
Definition: support.py:403
def __init__(self, cls, data_type=None, input_port_mapping=None, pre_args=None, post_args=None, needs_result_type=False, **kwargs)
Definition: support.py:332
def __getattr__(self, name)
Definition: support.py:383
def __init__(self, ir.Operation operation, int index, value, backedge_owner=None)
Definition: support.py:304
def __init__(self, str module, List[str] port_names)
Definition: support.py:20
Bundles represent a collection of channels.
Definition: Types.h:44
Channels are the basic communication primitives.
Definition: Types.h:63
def connect(destination, source)
Definition: support.py:37
ir.Attribute var_to_attribute(obj, bool none_on_fail=False)
Definition: support.py:55
def get_self_or_inner(mlir_type)
Definition: support.py:209
ir.Type type_to_pytype(t)
Definition: support.py:79
def attribute_to_var(attr)
Definition: support.py:133
ir.Value get_value(obj)
Definition: support.py:25