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