Module std::intrinsics::mir

source ·
🔬This is a nightly-only experimental API. (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()
        }
    )
}
Run

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:

  • 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()
        }
    )
}
Run

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()
        }
    )
}
Run
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 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 via RET.
  • 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 and Field associated functions, see their documentation for details.
Operands
  • Places implicitly convert to Copy operands.
  • Move operands can be created via Move.
  • Const blocks, literals, named constants, and const params all just work.
  • Static and StaticMut can be used to create &T and *mut Ts to statics. These are constants in MIR and the only way to access statics.
Statements
Rvalues
  • Operands implicitly convert to Use rvalues.
  • &, &mut, addr_of!, and addr_of_mut! all work to create their associated rvalue.
  • Discriminant and Len 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, and DropAndReplace have associated functions.
  • match some_int_operand becomes a SwitchInt. Each arm should be literal => basic_block
    • The exception is the last arm, which must be _ => basic_block and corresponds to the otherwise branch.
  • Call has an associated function as well. The third argument of this function is a normal function call expresion, for example my_other_function(a, 5).

Macros

mirExperimental
Macro for generating custom MIR.
placeExperimental
Helper macro that allows you to treat a value expression like a place expression.

Structs

BasicBlockExperimental
Type representing basic blocks.

Functions

CallExperimental
CheckedExperimental
DeinitExperimental
DiscriminantExperimental
Gets the discriminant of a place.
DropExperimental
DropAndReplaceExperimental
FieldExperimental
Access the field with the given index of some place.
GotoExperimental
LenExperimental
MoveExperimental
RetagExperimental
ReturnExperimental
SetDiscriminantExperimental
StaticExperimental
StaticMutExperimental
StorageDeadExperimental
StorageLiveExperimental
UnreachableExperimental
VariantExperimental
Adds a variant projection with the given index to the place.