Skip to main content

mlirformat_macros/
lib.rs

1//! `#[mlir::op]` and `#[mlir::dialect]`. Neither writes conversion code: `op` writes each
2//! field's MLIR role as a constant (`Op::FIELDS`: `mlirformat::Field`) and forwards the
3//! conversions to the runtime; `dialect` turns an enum of ops into a registrable op table.
4//!
5//! ```ignore
6//! #[mlir::op("arith.maxsi", format = "$lhs `,` $rhs attr-dict `:` type($result)", results(result))]
7//! pub struct ArithMax<'a> {
8//!     #[slot(result)] pub lhs: Value<'a>,     // operand; its type is type variable `result`
9//!     #[slot(result)] pub rhs: Value<'a>,
10//!     pub result: Type<'a>,                   // type variable (result #0 by `results(...)`)
11//! }
12//! ```
13//!
14//! A field's role is its Rust type: a `Type` is a type variable, a `Region` a region, anything
15//! else but a `Value` a property. A marker is written for an operand (`#[slot(T)]`), a successor,
16//! a role the type does not say (`#[attr]` on a `Type`: a type attribute), or to carry options.
17//! A `Vec` field is variadic; MLIR names are the field names unless `name = "..."`:
18//!
19//! ```text
20//! #[slot(T)]                       operand(s) typed by type variable T
21//! #[dtype] #[dtype(i1)] #[dtype(i1 = x)] #[dtype(of = attr)] #[dtype(eq = x)] #[dtype(is = index)] #[dtype(el = x)]
22//!                                  type variable; how custom form infers it when unspelled
23//! #[attr] #[attr(int = "i32")] #[attr(dialect = "arith.overflow", default = "<none>")]
24//!                                  property (`bool`: unit attribute; enum: case value typed
25//!                                  `int`, default i64); a dialect attribute is
26//!                                  spelled without its prefix, its default is omitted
27//! #[region]  #[successor]
28//! ```
29//!
30//! `attrs` (discardable attributes) and `loc` fields are added. Options: `format = "..."`,
31//! `results(a, b)`, `custom` (no `Print` / `Parse` impl: written by hand). Without `format` (and
32//! not `custom`) the op has no custom form: always printed generic.
33//!
34//! `#[derive(mlir::Cases)]` on an integer enum: `TryFrom<i64>` by case value (the variant's
35//! position; hidden `__N` gap variants take none).
36//!
37//! `#[mlir::dialect("ns", ops = [A, B], ..)]` on a unit struct: the dialect, its op structs and
38//! its traits (`dependents = [..]`, `materializer`, `canonicalizer`); `params = f` structures
39//! `!ns.x<...>` / `#ns.x<...>` bodies (`Dialect::parse_params`).
40
41use proc_macro::TokenStream;
42use syn::{Error, ItemEnum, ItemStruct, parse_macro_input};
43
44mod cases;
45mod dialect;
46mod op;
47mod syntax;
48
49#[proc_macro_attribute]
50pub fn op(args: TokenStream, item: TokenStream) -> TokenStream {
51    let args = parse_macro_input!(args as op::OpArgs);
52    let item = parse_macro_input!(item as ItemStruct);
53    args.expand(item).unwrap_or_else(Error::into_compile_error).into()
54}
55
56#[proc_macro_attribute]
57pub fn dialect(args: TokenStream, item: TokenStream) -> TokenStream {
58    let args = parse_macro_input!(args as dialect::DialectArgs);
59    let item = parse_macro_input!(item as ItemStruct);
60    args.expand(item).unwrap_or_else(Error::into_compile_error).into()
61}
62
63#[proc_macro_derive(Cases)]
64pub fn cases(item: TokenStream) -> TokenStream {
65    let item = parse_macro_input!(item as ItemEnum);
66    cases::expand(&item).unwrap_or_else(Error::into_compile_error).into()
67}
68
69/// `syntax!`: the ODS `custom<X>` directives, as rules the format interpreter reads and writes
70/// from. Crate internal: `mlirformat::syntax` is its only caller.
71#[proc_macro]
72pub fn syntax(input: TokenStream) -> TokenStream {
73    let rules = parse_macro_input!(input as syntax::Rules);
74    rules.expand().unwrap_or_else(Error::into_compile_error).into()
75}