Skip to content

Commit

Permalink
Draft: Dialect generation from ODS (#274)
Browse files Browse the repository at this point in the history
I've created an early draft of dialect generation based on the [MLIR
Python
Bindings](https://mlir.llvm.org/docs/Bindings/Python/#integration-with-ods).

It's okay if you don't have the time to review this. I admit that
there's quite a lot of ugly and hard to read code (especially the code
dealing with variadic arguments), and it might not be in a state
currently where you would want to maintain it within `melior`, hence I'm
marking it as a draft.

Not much changed since my original implementation mentioned in #262, but
I got a bit stuck on error handling in my TableGen wrapper library
([tblgen-rs](https://gitlab.com/Danacus/tblgen-rs)) and lost motivation
for a while after that. Anyway, I decided to finish what I started,
hence I am making this draft.

To build this branch, you might need to set `TABLEGEN_160_PREFIX` to
match `MLIR_SYS_160_PREFIX`.

I've added the generated dialects in a new `dialect_gen` module for now,
such that they can be compared with the original hand-written bindings
in the `dialect` module.

There are still a few issues:

- [ ] Some parts of the code are hacky and ugly, and may be hard to
read.
- [ ] Type inference is not always detected (but it should be as good as
the Python bindings at least)
- [ ] Need to add tests (already have them in a separate repository)
- [ ] Need to fix some issues with the CI

I wanted complete parity with the existing hand-written dialect
bindings, but there are some things that aren't generated as nicely. For
example, `arith::CmpiPredicate` is not generated and plain `Attribute`
is used instead for `arith::cmpi`. It might be feasible to generate
dialect specific attributes from ODS instead. Or perhaps being able to
write some function manually would be useful.

Currently I generate wrapper types around `Operation` that provide
additional methods, for example:

```rust
        pub struct AddIOp<'c> {
            operation: ::melior::ir::operation::Operation<'c>,
        }
        impl<'c> AddIOp<'c> {
            pub fn name() -> &'static str {
                "arith.addi"
            }
            pub fn operation(&self) -> &::melior::ir::operation::Operation<'c> {
                &self.operation
            }
            pub fn builder(
                location: ::melior::ir::Location<'c>,
            ) -> AddIOpBuilder<'c, AddIOp__No__Lhs, AddIOp__No__Rhs> {
                AddIOpBuilder::new(location)
            }
            pub fn result(&self) -> ::melior::ir::operation::OperationResult<'c, '_> {
                self.operation.result(0usize).expect("operation should have this result")
            }
            pub fn lhs(&self) -> ::melior::ir::Value<'c, '_> {
                self.operation
                    .operand(0usize)
                    .expect("operation should have this operand")
            }
            pub fn rhs(&self) -> ::melior::ir::Value<'c, '_> {
                self.operation
                    .operand(1usize)
                    .expect("operation should have this operand")
            }
        }
```

I then provide implementations of `Into<Operation>` and
`TryFrom<Operation>` to "cast" to and from an `Operation`.

```rust
        impl<'c> TryFrom<::melior::ir::operation::Operation<'c>> for AddIOp<'c> {
            type Error = ::melior::Error;
            fn try_from(
                operation: ::melior::ir::operation::Operation<'c>,
            ) -> Result<Self, Self::Error> {
                Ok(Self { operation })
            }
        }
        impl<'c> Into<::melior::ir::operation::Operation<'c>> for AddIOp<'c> {
            fn into(self) -> ::melior::ir::operation::Operation<'c> {
                self.operation
            }
        }
```

I wonder if it would be better to use an `OperationLike` trait, similar
to `AttributeLike`? That way we wouldn't have to call `operation` or
`into` to use the methods on `Operation`.

On a related note: should we also generate `Ref` (and `RefMut`?) types
for these operation wrappers? It might be useful to be able to cast a
`OperationRef` into a `AddIOpRef`, for example, to more easily analyze
operations in external passes (or even external analyses, which is
something else I'm working on as well:
[mlir-rust-tools](https://gitlab.com/Danacus/mlir-rust-tools)).
  • Loading branch information
Danacus authored Aug 17, 2023
1 parent 5ba698b commit f9eece9
Show file tree
Hide file tree
Showing 19 changed files with 2,143 additions and 5 deletions.
86 changes: 81 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions macro/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@ proc-macro = true

[dependencies]
convert_case = "0.6.0"
lazy_static = "1.4.0"
once_cell = "1.18.0"
proc-macro2 = "1"
quote = "1"
regex = "1.9.3"
syn = { version = "2", features = ["full"] }
tblgen = { version = "0.3.0", features = ["llvm16-0"] }
unindent = "0.2.2"

[dev-dependencies]
melior = { path = "../melior" }
Expand Down
76 changes: 76 additions & 0 deletions macro/src/dialect/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
use std::fmt::Display;

use proc_macro2::Span;
use tblgen::{
error::{SourceError, TableGenError},
SourceInfo,
};

#[derive(Debug)]
pub enum Error {
Syn(syn::Error),
TableGen(tblgen::Error),
ExpectedSuperClass(SourceError<ExpectedSuperClassError>),
ParseError,
}

impl Error {
pub fn add_source_info(self, info: SourceInfo) -> Self {
match self {
Self::TableGen(e) => e.add_source_info(info).into(),
Self::ExpectedSuperClass(e) => e.add_source_info(info).into(),
_ => self,
}
}
}

impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::Syn(e) => write!(f, "failed to parse macro input: {e}"),
Error::TableGen(e) => write!(f, "invalid ODS input: {e}"),
Error::ExpectedSuperClass(e) => write!(f, "invalid ODS input: {e}"),
Error::ParseError => write!(f, "error parsing TableGen source"),
}
}
}

impl std::error::Error for Error {}

impl From<SourceError<ExpectedSuperClassError>> for Error {
fn from(value: SourceError<ExpectedSuperClassError>) -> Self {
Self::ExpectedSuperClass(value)
}
}

impl From<SourceError<TableGenError>> for Error {
fn from(value: SourceError<TableGenError>) -> Self {
Self::TableGen(value)
}
}

impl From<syn::Error> for Error {
fn from(value: syn::Error) -> Self {
Self::Syn(value)
}
}

impl From<Error> for syn::Error {
fn from(value: Error) -> Self {
match value {
Error::Syn(e) => e,
_ => syn::Error::new(Span::call_site(), format!("{}", value)),
}
}
}

#[derive(Debug)]
pub struct ExpectedSuperClassError(pub String);

impl Display for ExpectedSuperClassError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "expected this record to be a subclass of {}", self.0)
}
}

impl std::error::Error for ExpectedSuperClassError {}
Loading

0 comments on commit f9eece9

Please sign in to comment.