tblgen/lib.rs
1// Original work Copyright 2016 Alexander Stocko <as@coder.gg>.
2// Modified work Copyright 2023 Daan Vanoverloop
3// See the COPYRIGHT file at the top-level directory of this distribution.
4//
5// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8// option. This file may not be copied, modified, or distributed
9// except according to those terms.
10
11//! This crate provides raw bindings and a safe wrapper for [TableGen](https://llvm.org/docs/TableGen/),
12//! a domain-specific language used by the [LLVM project](https://llvm.org/).
13//!
14//! The goal of this crate is to enable users to develop custom [TableGen backends](https://llvm.org/docs/TableGen/BackGuide.html)
15//! in Rust. Hence the primary use case of this crate are procedural macros that
16//! generate Rust code from TableGen description files.
17//!
18//! # Safety
19//!
20//! This crate aims to be completely safe.
21//!
22//! # Supported LLVM Versions
23//!
24//! An installation of LLVM is required to use this crate.
25//! The versions of LLVM currently supported are 16.x.x (default) and 17.x.x.
26//! Different LLVM version can be selected using features flags (llvm16-0 or
27//! llvm17-0).
28//!
29//! The `TABLEGEN_<version>_PREFIX` environment variable can be used to specify
30//! a custom directory of the LLVM installation.
31//!
32//! # Examples
33//!
34//! The following example parse simple TableGen code provided as a `&str` and
35//! iterates over classes and defs defined in this file.
36//!
37//! ```rust
38//! use tblgen::{RecordKeeper, TableGenParser};
39//!
40//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
41//! let keeper: RecordKeeper = TableGenParser::new()
42//! .add_source(
43//! r#"
44//! class A;
45//! def D: A;
46//! "#,
47//! )?
48//! .parse()?;
49//! assert_eq!(keeper.classes().next().unwrap().0, Ok("A"));
50//! assert_eq!(keeper.defs().next().unwrap().0, Ok("D"));
51//! assert_eq!(
52//! keeper.all_derived_definitions("A").next().unwrap().name(),
53//! Ok("D")
54//! );
55//! # Ok(())
56//! # }
57//! ```
58//!
59//! By adding include paths, external TableGen files can be included.
60//!
61//! ```rust
62//! use std::path::Path;
63//! use tblgen::{RecordKeeper, TableGenParser};
64//!
65//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
66//! let keeper: RecordKeeper = TableGenParser::new()
67//! .add_source(r#"include "mlir/IR/OpBase.td""#)?
68//! .add_include_directory(&format!(
69//! "{}/include",
70//! std::env::var("TABLEGEN_200_PREFIX")?
71//! ))
72//! .parse()?;
73//! let i32_def = keeper.def("I32").expect("has I32 def");
74//! assert!(i32_def.subclass_of("I"));
75//! assert_eq!(i32_def.int_value("bitwidth"), Ok(32));
76//! # Ok(())
77//! # }
78//! ```
79//!
80//! You can also pass an included filename directly.
81//!
82//! ```rust
83//! use std::path::Path;
84//! use tblgen::{RecordKeeper, TableGenParser};
85//!
86//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
87//! let keeper: RecordKeeper = TableGenParser::new()
88//! .add_source_file("mlir/IR/OpBase.td")
89//! .add_include_directory(&format!(
90//! "{}/include",
91//! std::env::var("TABLEGEN_200_PREFIX")?
92//! ))
93//! .parse()?;
94//! let i32_def = keeper.def("I32").expect("has I32 def");
95//! assert!(i32_def.subclass_of("I"));
96//! assert_eq!(i32_def.int_value("bitwidth"), Ok(32));
97//! # Ok(())
98//! # }
99//! ```
100//!
101//! # API Stability
102//!
103//! LLVM does not provide a stable C API for TableGen, and the C API provided by
104//! this crate is not stable. Furthermore, the safe wrapper does not provide a
105//! stable interface either, since this crate is still in early development.
106
107pub mod error;
108pub mod init;
109/// TableGen records and record values.
110pub mod record;
111/// TableGen record keeper.
112pub mod record_keeper;
113mod string_ref;
114mod util;
115
116/// This module contains raw bindings for TableGen. Note that these bindings are
117/// unstable and can change at any time.
118#[allow(non_upper_case_globals)]
119#[allow(non_camel_case_types)]
120#[allow(non_snake_case)]
121pub mod raw {
122 include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
123}
124
125use std::{
126 ffi::{CStr, CString},
127 marker::PhantomData,
128 sync::Mutex,
129};
130
131pub use error::Error;
132use error::TableGenError;
133pub use init::TypedInit;
134pub use record::{Record, RecordValue};
135pub use record_keeper::RecordKeeper;
136
137use raw::{
138 TableGenParserRef, tableGenAddIncludeDirectory, tableGenAddSource, tableGenAddSourceFile,
139 tableGenFree, tableGenGet, tableGenParse,
140};
141use string_ref::StringRef;
142
143// TableGen only exposes `TableGenParseFile` in its API.
144// However, this function uses global state and therefore it is not thread safe.
145// Until they remove this hack, we have to deal with it ourselves.
146static TABLEGEN_PARSE_LOCK: Mutex<()> = Mutex::new(());
147
148/// Builder struct that parses TableGen source files and builds a
149/// [`RecordKeeper`].
150#[derive(Debug, PartialEq, Eq)]
151pub struct TableGenParser<'s> {
152 raw: TableGenParserRef,
153 source_strings: Vec<CString>,
154 _source_ref: PhantomData<&'s str>,
155}
156
157impl Default for TableGenParser<'_> {
158 fn default() -> Self {
159 Self::new()
160 }
161}
162
163impl<'s> TableGenParser<'s> {
164 /// Initalizes a new TableGen parser.
165 pub fn new() -> Self {
166 Self {
167 raw: unsafe { tableGenGet() },
168 source_strings: Vec::new(),
169 _source_ref: PhantomData,
170 }
171 }
172
173 /// Adds the given path to the list of included directories.
174 pub fn add_include_directory(self, include: &str) -> Self {
175 unsafe { tableGenAddIncludeDirectory(self.raw, StringRef::from(include).to_raw()) }
176 self
177 }
178
179 /// Reads TableGen source code from the file at the given path.
180 pub fn add_source_file(self, source: &str) -> Self {
181 unsafe { tableGenAddSourceFile(self.raw, StringRef::from(source).to_raw()) }
182 self
183 }
184
185 /// Adds the given TableGen source string.
186 ///
187 /// The string must be null-terminated and is not copied, hence it is
188 /// required to live until the source code is parsed.
189 pub fn add_source_raw(self, source: &'s CStr) -> Result<Self, Error> {
190 if unsafe { tableGenAddSource(self.raw, source.as_ptr()) > 0 } {
191 Ok(self)
192 } else {
193 Err(TableGenError::InvalidSource.into())
194 }
195 }
196
197 /// Adds the given TableGen source string.
198 ///
199 /// The string is copied into a null-terminated [`CString`].
200 pub fn add_source(mut self, source: &str) -> Result<Self, Error> {
201 let string = CString::new(source).map_err(TableGenError::from)?;
202 self.source_strings.push(string);
203 if unsafe {
204 tableGenAddSource(
205 self.raw,
206 self.source_strings.last().expect("not empty").as_ptr(),
207 ) > 0
208 } {
209 Ok(self)
210 } else {
211 Err(TableGenError::InvalidSource.into())
212 }
213 }
214
215 pub fn source_info(&self) -> SourceInfo<'_> {
216 SourceInfo(self)
217 }
218
219 /// Parses the TableGen source files and returns a [`RecordKeeper`].
220 ///
221 /// Due to limitations of TableGen, parsing TableGen is not thread-safe.
222 /// In order to provide thread-safety, this method ensures that any
223 /// concurrent parse operations are executed sequentially.
224 pub fn parse(self) -> Result<RecordKeeper<'s>, Error> {
225 unsafe {
226 let guard = TABLEGEN_PARSE_LOCK.lock().unwrap();
227 let keeper = tableGenParse(self.raw);
228 let res = if !keeper.is_null() {
229 Ok(RecordKeeper::from_raw(keeper, self))
230 } else {
231 Err(TableGenError::Parse.into())
232 };
233 drop(guard);
234 res
235 }
236 }
237}
238
239impl Drop for TableGenParser<'_> {
240 fn drop(&mut self) {
241 unsafe {
242 tableGenFree(self.raw);
243 }
244 }
245}
246
247/// Reference to TableGen source file.
248///
249/// See [`TableGenParser::source_info`](TableGenParser::source_info) and
250/// [`RecordKeeper::source_info`](RecordKeeper::source_info).
251#[derive(Clone, Copy, Debug, PartialEq, Eq)]
252pub struct SourceInfo<'a>(pub(crate) &'a TableGenParser<'a>);