Add support for passing Python strings and lists directly as arguments to
the OM evaluator's instantiate method:
* Fix Attribute value creation to work well with list evaluation.
* Add StringType C API to construct typed strings in Python bindings
* Implement conversion between Python lists and OM ListAttr
This changes the OM dialect Class/ExternClass to use a return style
field name/type specifier syntax and, for non-extern class, a terminator
op in the style of hw.output.
BoolAttr's are IntegerAttr's, check them first.
IntegerAttr's that happen to have the characteristics of
BoolAttr will accordingly become Python boolean values.
Unclear where these come from but we do lower booleans
to MLIR bool constants so make sure to handle that.
Add test for object model IR with bool constants.
This exposes the ObjectValue Location in the Python bindings using the
C API for MlirLocation. The Location is useful for debugging the
source that created the Object in the first place.
Both the upstream MLIR IntegerAttr and OM IntegerAttr are backed by an
arbitrary precision integer. However, the upstream Python bindings
don't have any mechanism to return an integer larger than 64 bits back
to Python, even though Python ints are also arbitrary precision
integers.
To support this, we can handle this where we explicitly convert OM
IntegerAttrs to Python values. The simplest thing is to print a string
representation of the arbitrary precision integer, and parse that to a
Python int. This adds the necessary C API and Python binding for a "to
string" method, and uses it in the attribute_to_var function.
There are smarter ways we can handle the conversion, but the "to
string" API seems generally useful, so I'm using that in the
conversion for now.
In some OM dialect constructs, it is possible to receive
EvaluatorValue::Reference values. In the Python bindings, where we are
converting an EvaluatorValue to a Python value, we need to dereference
the Reference, to get at the underlying EvaluatorValue that was set
during evaluation.
This adds the necessary C APIs, and updates the Python bindings to use
them. If we encounter a Reference, we dereference it and recursively
call the converter function.
A Python test was added using an example IR from the Evaluator unit
tests, which delays evaluation and introduces references.
In order to use the types in Python with isinstance, etc., we need to
provide the mlir_type_subclass bindings. This adds the C API
boilerplate and binds the types.
This change adds evaluator support for frozen paths.
Two new EvaluatorValues have been added, one for BasePaths, which
represent fragments of paths through the instance hiearchy, and one for
Paths, which represent a full path to a hardware component.
When the evaluator instantiates an OM class, it is expected that it will
usually have to pass in an empty basepath, which signifies that the
class is at the top of the hierarchy. To facilitate this, we exposed the
ability to create an empty basepath value in the python API.
Path values can be transformed in to strings, where in they are printed
as FIRRTL style target paths. In the future, we may want a richer API.
Deleted paths are represented as Path values with the targetKind
attribute nulled out, which is handled specially in the string
serializer.
This PR does an overhaul of how path related operations work in the OM dialect.
When OM classes are created from FIRRTL, a base path is now fed through the
class hieararchy. All paths to hardware objects are now created relative to
the base path. The `path` op has been renamed to `path_create`, and now takes
a basepath SSA parameter, which means that it is impossible to create a path
which is not relative to some base.
A second set of operations to create "frozen paths" have been added. In
FreezePaths, we lower all path operations to hard-coded strings, which allows
us to remove the hardware from the OM IR and still be valid. These operations
have been changed to return `FrozenPathType`, which is to make sure that people
don't try to inter-mix the two kinds of path types. The intention is that the
evaluator will only understand frozen paths.
The PathAttr in OM no longer represents the entire target path, but just the
list of instances which lead up to it.
This also adds an OM utility for parsing FIRRTL style target strings, which is
used in the `frozenpath_create` MLIR assembly format. The existing APIs
contained in FIRRTL were not suitable since these new strings do not include
the circuit name, and we need some restrictions for parsing base paths, which
can not target components. The new parser is a bit more resilient to badly
formed path strings. In the future it may be possible to reunite these two
bodies of code.
The existing python support for paths had to be removed.
This commit adds support for graph regions for evaluator.
`ReferenceValue`, a new subclass of `EvaluatorValue`, is added to behave as pointers. `ReferenceValue` can be used as alias to different values and is created for `class.object.field` operation because `class.object.field` can access fields across class hierarchies and the fields might not be initilized yet. `RefenceValue` is not exposed to outside of evaluator implemenation. `EvaluatorValue::finalize` shrinks intermidiate `RefenceValue` in the evaluator value.
Evaluation algorithm is changed to worklist-based iteration. `instantiae` method first traverses the whole IR including sub-class, and create partially evaluaed values for all values and add these values to the worklist. After that we evaluate values until there is no partially evaluaed value.
Fix https://github.com/llvm/circt/issues/5834
This is quite invasive. This converts from the functiontype printer to the moduletype printer.
---------
Co-authored-by: Mike Urbach <mikeurbach@gmail.com>
Implement the proper `__hash__` and `__eq__` methods for Object, such that it
can be used in dictionaries. This is required to ensure we use mlir object
reference for equality and hashing, rather than the default methods provided
by python.
Hashable::
"In Python, a "hashable" object is an object that has a hash value that remains
constant throughout its lifetime and can be compared to other objects.
User-defined classes need to implement the `__hash__()` and `__eq__()` methods
to be hashable."
In the OM dialect, we want there to be "reference semantics". In other words,
two objects with identical classes and fields should not be "the same" if they
are created from separate instances in the elaborated object graph that the
Evaluator sees. In addition, due to presence of cyclic dependencies, value
equivalence cannot be determined and reference semantics is required.
Implement the python bindings for the `om.integer` attribute. Add support to
create `om::IntegerAttr` from `mlir::IntegerAttr`. Also ensure all tests use
`om::IntegerAttr` instead of `mlir::IntegerAttr`.
Now that the Python bindings for ClassType can return a Python object
of the actual ClassType Python class, it would be useful to get the
ClassType's name from such an object. This includes the usual C API
boilerplate to call the accessor in C++, and the usual Python wrapper
to return a string through pybind.
A relatively new feature of MLIR Python bindings allows returned
MlirTypes to automatically be downcast to the actual concrete Python
class for the type. To enable this, a C API to get the type's TypeID
must be provided, and the rest just works. Opt-in for the OM dialect's
ClassType.
This PR add Evaluator support for Map values.
* EvaluatorValue now takes a MLIR context as a member to make it easy to
construct attributes from evaluated values.
* MapValue is added and created from om.map_create.
* CAPIs for MapType and StringType are added.
This is a PR for new representation of Evaluator and list_create support.
`std::variant` has been used as runtime representation of Evaluator but it's difficult to use it as
data structure for more structured values such as list or map. So this PR instead introduces a base class `EvaluatorValue ` and derived classes, `AttributeValue`, `ListValue` and `ObjectValue`, as runtime representation of values.
Also terminology `ObjectValue` is changed to `EvaluatorValue` since runtime values will not be limited to Object.
Python and C bindings are changed accordingly.
This API previously accepted a Python dataclass as input, and used it
as a template to guide the logic to pull fields out of the
instantiated Object and build an instance of the requested dataclass.
This allowed the API to be strongly typed, by accepting a dataclass
type as input and returning an instance of that dataclass as
output. However, this requires the user to specify a-priori what
fields should be present in the resulting Object, and this may not
always be known.
By simply returning an Object, and adding the appropriate Python
conversions to Object's __getattr__, we can generically return Objects
that behave just like the previous dataclasses, without specifying the
fields up front. This also avoids rewrapping the Objects in
dataclasses.
In the future, we can add back a similar form of type safety when this
would be useful, potentially using Protocols similarly to how
dataclasses were used as a template before.
This adds support for Objects in the Fields of other Objects. This is
handled in the pure Python layer of the Evaluator API, which is
enhanced to support recursively instantiating Objects. The
functionality for creating a dataclass instance from an instantiated
Object is factored out into a new helper. In the case an Object field
is itself an Object, the new helper is called recursively with the
child dataclass and the child Object.
A simple integration test for this behavior is added.
This uncovered undefined behavior in the previous use of shared
pointers, so this patch depends on PR #5275.
This adds the usual Python API structure and dialect registration
boilerplate, as well as Python APIs around the Evaluator library.
The C++ implementations with pybind are intended to be as minimal and
straightforward as possible, simply wrapping the CAPI.
In order to provide a handy user-facing API, a couple of the C++
implementations are wrapped in pure Python to provide a nicer
interface:
The Evaluator constructor is wrapped in Python to also set up a logger
and diagnostic handler that uses the logger. This allows the CAPI and
C++ implementation to avoid dealing with Diagnostics. The CAPI simply
returns null on failure, and the C++ implementation simply throws a
Python error in this case. The pure Python diagnostic handler ensure
the error and any notes are logged before the error is thrown.
The Evaluator instantiate method is also wrapped in Python to handle
a few things required to provide a handy, type-safe API. It takes care
of accepting Python objects for actual parameters, and converting
these to Attributes using an existing helper function. It does a
similar conversion back to Python objects for primitive fields. It
also handles some minor metaprogramming to inspect an incoming
dataclass to determine its fields, extract them from the instantiated
Object, and return an instance of the dataclass.
Note that this initial implementation only supports Attributes in
Object fields in the pure Python wrapper around instantiation. Support
for Objects in Object fields will be added shortly in a subsequent
patch.