Module core::intrinsics::mir
source · custom_mir
)Expand description
Rustc internal tooling for hand-writing MIR.
If for some reasons you are not writing rustc tests and have found yourself considering using this feature, turn back. This is exceptionally unstable. There is no attempt at all to make anything work besides those things which the rustc test suite happened to need. If you make a typo you’ll probably ICE. Really, this is not the solution to your problems. Consider instead supporting the stable MIR project group.
The documentation for this module describes how to use this feature. If you are interested in
hacking on the implementation, most of that documentation lives at
rustc_mir_building/src/build/custom/mod.rs
.
Typical usage will look like this:
#![feature(core_intrinsics, custom_mir)]
extern crate core;
use core::intrinsics::mir::*;
#[custom_mir(dialect = "built")]
pub fn simple(x: i32) -> i32 {
mir!(
let temp2: i32;
{
let temp1 = x;
Goto(my_second_block)
}
my_second_block = {
temp2 = Move(temp1);
RET = temp2;
Return()
}
)
}
RunThe custom_mir
attribute tells the compiler to treat the function as being custom MIR. This
attribute only works on functions - there is no way to insert custom MIR into the middle of
another function. The dialect
and phase
parameters indicate which version of MIR you are inserting here. Generally you’ll want to use #![custom_mir(dialect = "built")]
if you want your MIR to be modified by the full MIR pipeline, or `#![custom_mir(dialect =
“runtime”, phase = “optimized”)] if you don’t.
The input to the mir!
macro is:
- A possibly empty list of local declarations. Locals can also be declared inline on
assignments via
let
. Type inference generally works. Shadowing does not. - A list of basic blocks. The first of these is the start block and is where execution begins.
All blocks other than the start block need to be given a name, so that they can be referred
to later.
- Each block is a list of semicolon terminated statements, followed by a terminator. The syntax for the various statements and terminators is designed to be as similar as possible to the syntax for analogous concepts in native Rust. See below for a list.
Examples
#![feature(core_intrinsics, custom_mir)]
extern crate core;
use core::intrinsics::mir::*;
#[custom_mir(dialect = "built")]
pub fn choose_load(a: &i32, b: &i32, c: bool) -> i32 {
mir!(
{
match c {
true => t,
_ => f,
}
}
t = {
let temp = a;
Goto(load_and_exit)
}
f = {
temp = b;
Goto(load_and_exit)
}
load_and_exit = {
RET = *temp;
Return()
}
)
}
#[custom_mir(dialect = "built")]
fn unwrap_unchecked<T>(opt: Option<T>) -> T {
mir!({
RET = Move(Field(Variant(opt, 1), 0));
Return()
})
}
#[custom_mir(dialect = "runtime", phase = "optimized")]
fn push_and_pop<T>(v: &mut Vec<T>, value: T) {
mir!(
let unused;
let popped;
{
Call(unused, pop, Vec::push(v, value))
}
pop = {
Call(popped, drop, Vec::pop(v))
}
drop = {
Drop(popped, ret)
}
ret = {
Return()
}
)
}
RunWe can also set off compilation failures that happen in sufficiently late stages of the compiler:
#![feature(core_intrinsics, custom_mir)]
extern crate core;
use core::intrinsics::mir::*;
#[custom_mir(dialect = "built")]
fn borrow_error(should_init: bool) -> i32 {
mir!(
let temp: i32;
{
match should_init {
true => init,
_ => use_temp,
}
}
init = {
temp = 0;
Goto(use_temp)
}
use_temp = {
RET = temp;
Return()
}
)
}
Runerror[E0381]: used binding is possibly-uninitialized
--> test.rs:24:13
|
8 | / mir!(
9 | | let temp: i32;
10 | |
11 | | {
... |
19 | | temp = 0;
| | -------- binding initialized here in some conditions
... |
24 | | RET = temp;
| | ^^^^^^^^^^ value used here but it is possibly-uninitialized
25 | | Return()
26 | | }
27 | | )
| |_____- binding declared here but left uninitialized
error: aborting due to previous error
For more information about this error, try `rustc --explain E0381`.
Syntax
The lists below are an exhaustive description of how various MIR constructs can be created. Anything missing from the list should be assumed to not be supported, PRs welcome.
Locals
- The
_0
return local can always be accessed viaRET
. - Arguments can be accessed via their regular name.
- All other locals need to be declared with
let
somewhere and then can be accessed by name.
Places
- Locals implicit convert to places.
- Field accesses, derefs, and indexing work normally.
- Fields in variants can be accessed via the
Variant
andField
associated functions, see their documentation for details.
Operands
- Places implicitly convert to
Copy
operands. Move
operands can be created viaMove
.- Const blocks, literals, named constants, and const params all just work.
Static
andStaticMut
can be used to create&T
and*mut T
s to statics. These are constants in MIR and the only way to access statics.
Statements
- Assign statements work via normal Rust assignment.
Retag
,StorageLive
,StorageDead
,Deinit
statements have an associated function.
Rvalues
- Operands implicitly convert to
Use
rvalues. &
,&mut
,addr_of!
, andaddr_of_mut!
all work to create their associated rvalue.Discriminant
andLen
have associated functions.- Unary and binary operations use their normal Rust syntax -
a * b
,!c
, etc. - Checked binary operations are represented by wrapping the associated binop in
Checked
. - Array repetition syntax (
[foo; 10]
) creates the associated rvalue.
Terminators
Custom MIR does not currently support cleanup blocks or non-trivial unwind paths. As such, there are no resume and abort terminators, and terminators that might unwind do not have any way to indicate the unwind block.
Goto
,Return
,Unreachable
,Drop
, andDropAndReplace
have associated functions.match some_int_operand
becomes aSwitchInt
. Each arm should beliteral => basic_block
- The exception is the last arm, which must be
_ => basic_block
and corresponds to the otherwise branch.
- The exception is the last arm, which must be
Call
has an associated function as well. The third argument of this function is a normal function call expresion, for examplemy_other_function(a, 5)
.