intarsia_macros/lib.rs
1//! Procedural macros for integrating [ISLE] DSL with Intarsia optimizers.
2//!
3//! This crate provides macros to help integrate ISLE-generated code into your Rust optimizer
4//! framework. [ISLE] (Instruction Selection Lowering Expressions) is a domain-specific language
5//! for pattern matching and rewriting, originally developed for [Cranelift].
6//!
7//! [ISLE]: https://github.com/bytecodealliance/wasmtime/blob/main/cranelift/isle/docs/language-reference.md
8//! [Cranelift]: https://cranelift.dev/
9//!
10//! # Overview
11//!
12//! The macros fall into three categories:
13//!
14//! ## 1. Accessor Generation (Non-Multi)
15//!
16//! For basic ISLE operators that match/construct single nodes:
17//! - [`isle_extractor!`] - Pattern matching (e-node → children IDs)
18//! - [`isle_constructor!`] - Node construction (children IDs → e-node)
19//! - [`isle_accessors!`] - Both extractor and constructor
20//!
21//! ## 2. Multi-Accessor Generation
22//!
23//! For ISLE multi-extractors/constructors that explore all nodes in an e-class:
24//! - [`isle_multi_extractor!`] - Match all nodes in an e-class
25//! - [`isle_multi_constructor!`] - Construct and add to results vector
26//! - [`isle_multi_accessors!`] - Both multi-extractor and multi-constructor
27//!
28//! ## 3. Integration Setup
29//!
30//! - [`isle_integration!`] - Generate required type definitions
31//! - [`isle_integration_full!`] - Module declaration + type definitions
32//!
33//! # Quick Start Example
34//!
35//! ```ignore
36//! // In your optimizer module (e.g., src/optimizer.rs)
37//! use intarsia_macros::{isle_integration, isle_accessors};
38//!
39//! // 1. Declare the ISLE-generated rules module
40//! #[allow(dead_code, unused_variables, unused_imports, non_snake_case)]
41//! #[path = "isle/rules.rs"]
42//! pub(crate) mod rules;
43//!
44//! // 2. Generate required ISLE type definitions
45//! isle_integration!();
46//!
47//! // 3. In your Context implementation, generate accessor functions
48//! impl rules::Context for MyContext {
49//! isle_accessors! {
50//! And(extractor_and, constructor_and, 2);
51//! Or(extractor_or, constructor_or, 2);
52//! Not(extractor_not, constructor_not, 1);
53//! }
54//! // ... other required methods
55//! }
56//! ```
57//!
58//! # Build Script Integration
59//!
60//! Use [`intarsia-build`] to compile ISLE files in your `build.rs`:
61//!
62//! [`intarsia-build`]: https://docs.rs/intarsia-build/
63//!
64//! ```no_run
65//! // build.rs
66//! fn main() -> Result<(), Box<dyn std::error::Error>> {
67//! intarsia_build::compile_isle_auto()?;
68//! Ok(())
69//! }
70//! ```
71
72use proc_macro::TokenStream;
73use quote::quote;
74use syn::{Error, Ident, LitInt, Token, parse::Parse, parse::ParseStream, parse_macro_input};
75
76// Parser for isle_integration macro arguments
77struct IsleIntegrationArgs {
78 max_returns: Option<usize>,
79}
80
81impl Parse for IsleIntegrationArgs {
82 fn parse(input: ParseStream) -> syn::Result<Self> {
83 if input.is_empty() {
84 return Ok(IsleIntegrationArgs { max_returns: None });
85 }
86
87 let key: Ident = input.parse()?;
88 if key != "max_returns" {
89 return Err(Error::new_spanned(key, "Expected 'max_returns'"));
90 }
91 let _colon: Token![:] = input.parse()?;
92 let value: LitInt = input.parse()?;
93 let max_returns = value.base10_parse()?;
94
95 // Optional trailing comma
96 let _ = input.parse::<Token![,]>();
97
98 Ok(IsleIntegrationArgs {
99 max_returns: Some(max_returns),
100 })
101 }
102}
103
104/// Generate type definitions required by ISLE-generated code.
105///
106/// This macro generates `ConstructorVec<T>` and `MAX_ISLE_RETURNS` which are referenced
107/// by ISLE-generated multi-constructor functions. You still need to manually declare the
108/// `rules` module with a [`#[path]`][path-attr] attribute.
109///
110/// See [`isle_integration_full!`] for a version that also declares the module.
111///
112/// [path-attr]: https://doc.rust-lang.org/reference/items/modules.html#the-path-attribute
113///
114/// # Arguments
115///
116/// * `max_returns: usize` - (Optional) Maximum number of values a multi-constructor can return.
117/// Defaults to 100. Increase if you have rules generating many alternatives.
118///
119/// # Example
120///
121/// ```ignore
122/// // In your optimizer module (e.g., src/optimizer/mod.rs)
123/// use intarsia_macros::isle_integration;
124///
125/// // First, declare the rules module with the path to generated code
126/// #[allow(dead_code, unused_variables, unused_imports, non_snake_case)]
127/// #[allow(irrefutable_let_patterns, unused_assignments, non_camel_case_types)]
128/// #[allow(unreachable_patterns, unreachable_code)]
129/// #[path = "isle/rules.rs"]
130/// pub(crate) mod rules;
131///
132/// // Then, generate the required type definitions
133/// isle_integration!();
134///
135/// // Or with custom max_returns:
136/// isle_integration!(max_returns: 200);
137/// ```
138///
139/// # Generated Code
140///
141/// ```ignore
142/// /// Type alias for vectors returned by ISLE multi-constructors.
143/// pub type ConstructorVec<T> = Vec<T>;
144///
145/// /// Maximum number of values an ISLE multi-constructor can return.
146/// pub const MAX_ISLE_RETURNS: usize = 100;
147/// ```
148#[proc_macro]
149pub fn isle_integration(input: TokenStream) -> TokenStream {
150 let args = parse_macro_input!(input as IsleIntegrationArgs);
151 let max_returns = args.max_returns.unwrap_or(100);
152
153 let output = quote! {
154 /// Type alias for vectors returned by ISLE multi-constructors.
155 ///
156 /// ISLE multi-constructors can return multiple values. This type
157 /// is used internally by the generated code.
158 pub type ConstructorVec<T> = Vec<T>;
159
160 /// Maximum number of values an ISLE multi-constructor can return.
161 ///
162 /// This constant is used by ISLE-generated code to limit the number
163 /// of alternatives explored. Increase this if you have rules that
164 /// generate many alternatives.
165 pub const MAX_ISLE_RETURNS: usize = #max_returns;
166 };
167
168 output.into()
169}
170
171// Parser for isle_integration_full macro arguments
172struct IsleIntegrationFullArgs {
173 path: String,
174 max_returns: Option<usize>,
175}
176
177impl Parse for IsleIntegrationFullArgs {
178 fn parse(input: ParseStream) -> syn::Result<Self> {
179 // Parse "path"
180 let path_key: Ident = input.parse()?;
181 if path_key != "path" {
182 return Err(Error::new_spanned(
183 path_key,
184 "Expected 'path' as first argument",
185 ));
186 }
187 let _colon: Token![:] = input.parse()?;
188 let path_lit: syn::LitStr = input.parse()?;
189 let path = path_lit.value();
190
191 // Optional comma
192 let has_comma = input.parse::<Token![,]>().is_ok();
193
194 let max_returns = if has_comma && !input.is_empty() {
195 // Parse "max_returns"
196 let max_returns_key: Ident = input.parse()?;
197 if max_returns_key != "max_returns" {
198 return Err(Error::new_spanned(
199 max_returns_key,
200 "Expected 'max_returns'",
201 ));
202 }
203 let _colon: Token![:] = input.parse()?;
204 let value: LitInt = input.parse()?;
205 Some(value.base10_parse()?)
206 } else {
207 None
208 };
209
210 // Optional trailing comma
211 let _ = input.parse::<Token![,]>();
212
213 Ok(IsleIntegrationFullArgs { path, max_returns })
214 }
215}
216
217/// Complete ISLE integration including module declaration and type definitions.
218///
219/// This is a convenience macro that combines module declaration (with appropriate `#[allow]`
220/// attributes) and [`isle_integration!`] in a single invocation.
221///
222/// Use this for simpler integration, or use [`isle_integration!`] if you need custom
223/// module attributes.
224///
225/// # Arguments
226///
227/// * `path: "..."` - Path to the generated `.rs` file, relative to current module
228/// * `max_returns: usize` - (Optional) Maximum number of values a multi-constructor can return
229///
230/// # Example
231///
232/// ```ignore
233/// use intarsia_macros::isle_integration_full;
234///
235/// // In your optimizer module, assuming isle/rules.rs exists
236/// isle_integration_full! {
237/// path: "isle/rules.rs",
238/// }
239///
240/// // With custom max_returns
241/// isle_integration_full! {
242/// path: "isle/rules.rs",
243/// max_returns: 200,
244/// }
245/// ```
246///
247/// # Generated Code
248///
249/// This generates a `rules` module declaration with lint suppressions plus the type
250/// definitions from [`isle_integration!`].
251#[proc_macro]
252pub fn isle_integration_full(input: TokenStream) -> TokenStream {
253 let args = parse_macro_input!(input as IsleIntegrationFullArgs);
254 let path = args.path;
255 let name = path
256 .split('/')
257 .last()
258 .and_then(|s| s.strip_suffix(".rs"))
259 .unwrap_or("rules");
260 let mod_name = Ident::new(name, proc_macro2::Span::call_site());
261 let max_returns = args.max_returns.unwrap_or(100);
262
263 let output = quote! {
264 // Declare the rules module with appropriate attributes
265 #[allow(dead_code, unused_variables, unused_imports, non_snake_case)]
266 #[allow(irrefutable_let_patterns, unused_assignments, non_camel_case_types)]
267 #[allow(unreachable_patterns, unreachable_code)]
268 #[path = #path]
269 pub(crate) mod #mod_name;
270
271 /// Type alias for vectors returned by ISLE multi-constructors.
272 ///
273 /// ISLE multi-constructors can return multiple values. This type
274 /// is used internally by the generated code.
275 pub type ConstructorVec<T> = Vec<T>;
276
277 /// Maximum number of values an ISLE multi-constructor can return.
278 ///
279 /// This constant is used by ISLE-generated code to limit the number
280 /// of alternatives explored. Increase this if you have rules that
281 /// generate many alternatives.
282 pub const MAX_ISLE_RETURNS: usize = #max_returns;
283
284 /// Import the context trait and wrapper type generated by ISLE.
285 use #mod_name::{Context, ContextIterWrapper}; // Pull in the Context trait and ContextIterWrapper generated by ISLE.
286 };
287
288 output.into()
289}