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, circuit_name: str = ""):
250  self.circuit_namecircuit_name = circuit_name
251  self.edgesedges = set()
252 
253  @staticmethod
254  def current():
255  bb = _current_backedge_builder.get(None)
256  if bb is None:
257  raise RuntimeError("No backedge builder found in context!")
258  return bb
259 
260  @staticmethod
261  def create(*args, **kwargs):
262  return BackedgeBuilder.current()._create(*args, **kwargs)
263 
264  def _create(self,
265  type: ir.Type,
266  port_name: str,
267  op_view,
268  instance_of: ir.Operation = None,
269  loc: ir.Location = None):
270  edge = BackedgeBuilder.Edge(self, type, port_name, op_view, instance_of,
271  loc)
272  self.edgesedges.add(edge)
273  return edge
274 
275  def __enter__(self):
276  self.old_bb_tokenold_bb_token = _current_backedge_builder.set(self)
277 
278  def __exit__(self, exc_type, exc_value, traceback):
279  if exc_value is not None:
280  return
281  _current_backedge_builder.reset(self.old_bb_tokenold_bb_token)
282  errors = []
283  for edge in list(self.edgesedges):
284  # TODO: Make this use `UnconnectedSignalError`.
285  msg = "Backedge: " + edge.port_name + "\n"
286  if edge.instance_of is not None:
287  msg += "InstanceOf: " + str(edge.instance_of).split(" {")[0] + "\n"
288  if edge.op_view is not None:
289  op = edge.op_view.operation
290  msg += "Instance: " + str(op)
291  errors.append(msg)
292 
293  if errors:
294  errors.insert(
295  0, f"Uninitialized backedges remain in module '{self.circuit_name}'")
296  raise RuntimeError("\n".join(errors))
297 
298 
299 class OpOperand:
300  __slots__ = ["index", "operation", "value", "backedge_owner"]
301 
302  def __init__(self,
303  operation: ir.Operation,
304  index: int,
305  value,
306  backedge_owner=None):
307  if not isinstance(index, int):
308  raise TypeError("Index must be int")
309  self.indexindex = index
310 
311  if not hasattr(operation, "operands"):
312  raise TypeError("Operation must be have 'operands' attribute")
313  self.operationoperation = operation
314 
315  self.valuevalue = value
316  self.backedge_ownerbackedge_owner = backedge_owner
317 
318  @property
319  def type(self):
320  return self.valuevalue.type
321 
322 
324  """Helper class to incrementally construct an instance of an operation that
325  names its operands and results"""
326 
327  def __init__(self,
328  cls,
329  data_type=None,
330  input_port_mapping=None,
331  pre_args=None,
332  post_args=None,
333  needs_result_type=False,
334  **kwargs):
335  # Set defaults
336  if input_port_mapping is None:
337  input_port_mapping = {}
338  if pre_args is None:
339  pre_args = []
340  if post_args is None:
341  post_args = []
342 
343  # Set result_indices to name each result.
344  result_names = self.result_names()
345  result_indices = {}
346  for i in range(len(result_names)):
347  result_indices[result_names[i]] = i
348 
349  # Set operand_indices to name each operand. Give them an initial value,
350  # either from input_port_mapping or a default value.
351  backedges = {}
352  operand_indices = {}
353  operand_values = []
354  operand_names = self.operand_names()
355  for i in range(len(operand_names)):
356  arg_name = operand_names[i]
357  operand_indices[arg_name] = i
358  if arg_name in input_port_mapping:
359  value = get_value(input_port_mapping[arg_name])
360  operand = value
361  else:
362  backedge = self.create_default_valuecreate_default_value(i, data_type, arg_name)
363  backedges[i] = backedge
364  operand = backedge.result
365  operand_values.append(operand)
366 
367  # Some ops take a list of operand values rather than splatting them out.
368  if isinstance(data_type, list):
369  operand_values = [operand_values]
370 
371  # In many cases, result types are inferred, and we do not need to pass
372  # data_type to the underlying constructor. It must be provided to
373  # NamedValueOpView in cases where we need to build backedges, but should
374  # generally not be passed to the underlying constructor in this case. There
375  # are some oddball ops that must pass it, even when building backedges, and
376  # these set needs_result_type=True.
377  if data_type is not None and (needs_result_type or len(backedges) == 0):
378  pre_args.insert(0, data_type)
379 
380  self.opviewopview = cls(*pre_args, *operand_values, *post_args, **kwargs)
381  self.operand_indicesoperand_indices = operand_indices
382  self.result_indicesresult_indices = result_indices
383  self.backedgesbackedges = backedges
384 
385  def __getattr__(self, name):
386  # Check for the attribute in the arg name set.
387  if "operand_indices" in dir(self) and name in self.operand_indicesoperand_indices:
388  index = self.operand_indicesoperand_indices[name]
389  value = self.opviewopview.operands[index]
390  return OpOperand(self.opviewopview.operation, index, value, self)
391 
392  # Check for the attribute in the result name set.
393  if "result_indices" in dir(self) and name in self.result_indicesresult_indices:
394  index = self.result_indicesresult_indices[name]
395  value = self.opviewopview.results[index]
396  return OpOperand(self.opviewopview.operation, index, value, self)
397 
398  # Forward "attributes" attribute from the operation.
399  if name == "attributes":
400  return self.opviewopview.operation.attributes
401 
402  # If we fell through to here, the name isn't a result.
403  raise AttributeError(f"unknown port name {name}")
404 
405  def create_default_value(self, index, data_type, arg_name):
406  return BackedgeBuilder.create(data_type, arg_name, self)
407 
408  @property
409  def operation(self):
410  """Get the operation associated with this builder."""
411  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 __init__(self, str circuit_name="")
Definition: support.py:249
def _create(self, ir.Type type, str port_name, op_view, ir.Operation instance_of=None, ir.Location loc=None)
Definition: support.py:269
def __exit__(self, exc_type, exc_value, traceback)
Definition: support.py:278
def create(*args, **kwargs)
Definition: support.py:261
def create_default_value(self, index, data_type, arg_name)
Definition: support.py:405
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:334
def __getattr__(self, name)
Definition: support.py:385
def __init__(self, ir.Operation operation, int index, value, backedge_owner=None)
Definition: support.py:306
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