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_build/src/build/custom/mod.rs
.
Typical usage will look like this:
#![feature(core_intrinsics, custom_mir)]
#![allow(internal_features)]
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()
}
}
}
The 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:
- An optional return type annotation in the form of
type RET = ...;
. This may be required if the compiler cannot infer the type of RET. - 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)]
#![allow(internal_features)]
#![allow(unused_assignments)]
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 = Vec::push(v, value), ReturnTo(pop), UnwindContinue())
}
pop = {
Call(popped = Vec::pop(v), ReturnTo(drop), UnwindContinue())
}
drop = {
Drop(popped, ReturnTo(ret), UnwindContinue())
}
ret = {
Return()
}
}
}
#[custom_mir(dialect = "runtime", phase = "optimized")]
fn annotated_return_type() -> (i32, bool) {
mir! {
type RET = (i32, bool);
{
RET.0 = 1;
RET.1 = true;
Return()
}
}
}
We 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()
}
}
}
error[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 1 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
,Len
, andCopyForDeref
have associated functions.- Unary and binary operations use their normal Rust syntax -
a * b
,!c
, etc. - The binary operation
Offset
can be created viaOffset
. - Checked binary operations are represented by wrapping the associated binop in
Checked
. - Array repetition syntax (
[foo; 10]
) creates the associated rvalue.
§Terminators
Goto
,Return
,Unreachable
andDrop
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, with special syntax:Call(ret_val = function(arg1, arg2, ...), ReturnTo(next_block), UnwindContinue())
.TailCall
does not have a return destination or next block, so its syntax is justTailCall(function(arg1, arg2, ...))
.
Macros§
- mir
Experimental Macro for generating custom MIR. - place
Experimental Helper macro that allows you to treat a value expression like a place expression.
Structs§
- Return
ToArg Experimental - Unwind
Action Arg Experimental
Enums§
- Basic
Block Experimental Type representing basic blocks. - Unwind
Terminate Reason Experimental The reason we are terminating the process during unwinding.
Functions§
- Assume
Experimental - Call
Experimental Call a function. - Cast
PtrTo Ptr Experimental Emits aCastKind::PtrToPtr
cast. - Cast
Transmute Experimental Emits aCastKind::Transmute
cast. - Checked
Experimental - Copy
ForDeref Experimental - Deinit
Experimental - Discriminant
Experimental Gets the discriminant of a place. - Drop
Experimental Drop the contents of a place. - Field
Experimental Access the field with the given index of some place. - Goto
Experimental - Len
Experimental - Move
Experimental - Offset
Experimental - PtrMetadata
Experimental - Retag
Experimental - Return
Experimental - Return
To Experimental - SetDiscriminant
Experimental - Static
Experimental - Static
Mut Experimental - Storage
Dead Experimental - Storage
Live Experimental - Tail
Call Experimental Call a function. - Unreachable
Experimental - Unwind
Cleanup Experimental An unwind action that continues execution in a given basic blok. - Unwind
Continue Experimental An unwind action that continues unwinding. - Unwind
Resume Experimental A terminator that resumes the unwinding. - Unwind
Terminate Experimental An unwind action that terminates the execution. - Unwind
Unreachable Experimental An unwind action that triggers undefined behaviour. - Variant
Experimental Adds a variant projection with the given index to the place.