Initial Integration
In order to use stable_mir
in your crate, you will need to do the following:
- Use a nightly toolchain that includes the
stable_mir
crate. - Install at least the following rustup components: “rustc-dev” and “llvm-tools”
- Declare
stable_mir
as an external crate at the top of your crate:
extern crate stable_mir;
For 1 and 2, we highly recommend adding a “rust-toolchain.toml” file to your project.
We also recommend to pin down a specific nightly version, to ensure all users and tests are using the same compiler
version.
Therefore, the same stable_mir
crate version. E.g.:
# Example of a rust-toolchain.toml
[toolchain]
# Update the date to change the toolchain version.
channel = "nightly-2024-06-17"
components = ["llvm-tools", "rustc-dev", "rust-src"]
Initializing StableMIR
There’s currently no stable way to initialize the Rust compiler and StableMIR. See #0069 for more details.
Instead, StableMIR includes two unstable workarounds to give you a quick start.
The run
and run_with_tcx
macros, both from present in the rustc_smir
crate.
In order to use the run
macro, you first need to declare the following external crates:
extern crate rustc_driver;
extern crate rustc_interface;
#[macro_use]
extern crate rustc_smir;
// This one you should know already. =)
extern crate stable_mir;
Then start the driver using the run!()
macro:
let result = run!(rustc_args, callback_fn);
This macro takes two arguments:
- A vector with the command arguments to the compiler.
- A callback function, which can either be a closure expression or a function ident.
- This callback function shouldn’t take any argument, and it should return a
ControlFlow<B,C>
.
- This callback function shouldn’t take any argument, and it should return a
It will trigger the compilation in the current process, with the provided arguments, and invoke the callback after the compiler ran all its analyses, but before code generation.
The macro will return a Result<C, CompilerError<B>>
representing the compilation result and the callback return value.
The second option is the run_with_tcx!()
macro, which is very similar to the run
macro.
The main difference is that this macro passes a copy of the Rust compiler context (TyCtxt
) to the callback,
which allows the user to also make calls to internal compiler APIs.
Note that this option also requires the declaration of the rustc_middle
external crate, i.e., you should now have the
following declarations:
extern crate rustc_driver;
extern crate rustc_interface;
extern crate rustc_middle; // This one is new!
#[macro_use]
extern crate rustc_smir;
extern crate stable_mir;
Scope of StableMIR objects
StableMIR objects should not be used outside the scope of the callback function. Any usage outside this scope can panic or return an incorrect value.
For example, the following code is valid, since the logic we are storing is only used while the callback function is running:
fn print_items(rustc_args: Vec<String>) {
let _result = run!(rustc_args, || {
for item in stable_mir::all_local_items() {
// Using items inside the callback!
println!(" - {}", item.name())
}
});
}
However, the following usage isn’t valid, and stable_mir
will panic when we invoke the name()
function.
fn broken_print_items(rustc_args: Vec<String>) {
let result = run!(rustc_args, || { ControlFlow::Continue(stable_mir::all_local_items())});
if let ControlFlow::Continue(items) = result {
for item in items {
// Using item outside the callback function is wrong!
println!(" - {}", item.name())
}
}
}
StableMIR objects should also not be shared across different threads.
Analyzing crate definitions
TODO
Analyzing monomorphic instances
TODO