Skip to content

Commit 08a7982

Browse files
committed
Auto merge of #105356 - JakobDegen:more-custom-mir, r=oli-obk
Custom MIR: Many more improvements Commits are each atomic changes, best reviewed one at a time, with the exception that the last commit includes all the documentation. ### First commit Unsafetyck was not correctly disabled before for `dialect = "built"` custom MIR. This is fixed and a regression test is added. ### Second commit Implements `Discriminant`, `SetDiscriminant`, and `SwitchInt`. ### Third commit Implements indexing, field, and variant projections. ### Fourth commit Documents the previous commits and everything else. There is some amount of weirdness here due to having to beat Rust syntax into cooperating with MIR concepts, but it hopefully should not be too much. All of it is documented. r? `@oli-obk`
2 parents ab055aa + 62bde9b commit 08a7982

File tree

1 file changed

+240
-22
lines changed

1 file changed

+240
-22
lines changed

core/src/intrinsics/mir.rs

Lines changed: 240 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,14 @@
2121
//! #[custom_mir(dialect = "built")]
2222
//! pub fn simple(x: i32) -> i32 {
2323
//! mir!(
24-
//! let temp1: i32;
25-
//! let temp2: _;
24+
//! let temp2: i32;
2625
//!
2726
//! {
28-
//! temp1 = x;
29-
//! Goto(exit)
27+
//! let temp1 = x;
28+
//! Goto(my_second_block)
3029
//! }
3130
//!
32-
//! exit = {
31+
//! my_second_block = {
3332
//! temp2 = Move(temp1);
3433
//! RET = temp2;
3534
//! Return()
@@ -38,22 +37,168 @@
3837
//! }
3938
//! ```
4039
//!
41-
//! Hopefully most of this is fairly self-explanatory. Expanding on some notable details:
40+
//! The `custom_mir` attribute tells the compiler to treat the function as being custom MIR. This
41+
//! attribute only works on functions - there is no way to insert custom MIR into the middle of
42+
//! another function. The `dialect` and `phase` parameters indicate which [version of MIR][dialect
43+
//! docs] you are inserting here. Generally you'll want to use `#![custom_mir(dialect = "built")]`
44+
//! if you want your MIR to be modified by the full MIR pipeline, or `#![custom_mir(dialect =
45+
//! "runtime", phase = "optimized")] if you don't.
4246
//!
43-
//! - The `custom_mir` attribute tells the compiler to treat the function as being custom MIR. This
44-
//! attribute only works on functions - there is no way to insert custom MIR into the middle of
45-
//! another function.
46-
//! - The `dialect` and `phase` parameters indicate which version of MIR you are inserting here.
47-
//! This will normally be the phase that corresponds to the thing you are trying to test. The
48-
//! phase can be omitted for dialects that have just one.
49-
//! - You should define your function signature like you normally would. Externally, this function
50-
//! can be called like any other function.
51-
//! - Type inference works - you don't have to spell out the type of all of your locals.
47+
//! [dialect docs]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/mir/enum.MirPhase.html
5248
//!
53-
//! For now, all statements and terminators are parsed from nested invocations of the special
54-
//! functions provided in this module. We additionally want to (but do not yet) support more
55-
//! "normal" Rust syntax in places where it makes sense. Also, most kinds of instructions are not
56-
//! supported yet.
49+
//! The input to the [`mir!`] macro is:
50+
//!
51+
//! - A possibly empty list of local declarations. Locals can also be declared inline on
52+
//! assignments via `let`. Type inference generally works. Shadowing does not.
53+
//! - A list of basic blocks. The first of these is the start block and is where execution begins.
54+
//! All blocks other than the start block need to be given a name, so that they can be referred
55+
//! to later.
56+
//! - Each block is a list of semicolon terminated statements, followed by a terminator. The
57+
//! syntax for the various statements and terminators is designed to be as similar as possible
58+
//! to the syntax for analogous concepts in native Rust. See below for a list.
59+
//!
60+
//! # Examples
61+
//!
62+
//! ```rust
63+
//! #![feature(core_intrinsics, custom_mir)]
64+
//!
65+
//! extern crate core;
66+
//! use core::intrinsics::mir::*;
67+
//!
68+
//! #[custom_mir(dialect = "built")]
69+
//! pub fn choose_load(a: &i32, b: &i32, c: bool) -> i32 {
70+
//! mir!(
71+
//! {
72+
//! match c {
73+
//! true => t,
74+
//! _ => f,
75+
//! }
76+
//! }
77+
//!
78+
//! t = {
79+
//! let temp = a;
80+
//! Goto(load_and_exit)
81+
//! }
82+
//!
83+
//! f = {
84+
//! temp = b;
85+
//! Goto(load_and_exit)
86+
//! }
87+
//!
88+
//! load_and_exit = {
89+
//! RET = *temp;
90+
//! Return()
91+
//! }
92+
//! )
93+
//! }
94+
//!
95+
//! #[custom_mir(dialect = "built")]
96+
//! fn unwrap_unchecked<T>(opt: Option<T>) -> T {
97+
//! mir!({
98+
//! RET = Move(Field(Variant(opt, 1), 0));
99+
//! Return()
100+
//! })
101+
//! }
102+
//! ```
103+
//!
104+
//! We can also set off compilation failures that happen in sufficiently late stages of the
105+
//! compiler:
106+
//!
107+
//! ```rust,compile_fail
108+
//! #![feature(core_intrinsics, custom_mir)]
109+
//!
110+
//! extern crate core;
111+
//! use core::intrinsics::mir::*;
112+
//!
113+
//! #[custom_mir(dialect = "built")]
114+
//! fn borrow_error(should_init: bool) -> i32 {
115+
//! mir!(
116+
//! let temp: i32;
117+
//!
118+
//! {
119+
//! match should_init {
120+
//! true => init,
121+
//! _ => use_temp,
122+
//! }
123+
//! }
124+
//!
125+
//! init = {
126+
//! temp = 0;
127+
//! Goto(use_temp)
128+
//! }
129+
//!
130+
//! use_temp = {
131+
//! RET = temp;
132+
//! Return()
133+
//! }
134+
//! )
135+
//! }
136+
//! ```
137+
//!
138+
//! ```text
139+
//! error[E0381]: used binding is possibly-uninitialized
140+
//! --> test.rs:24:13
141+
//! |
142+
//! 8 | / mir!(
143+
//! 9 | | let temp: i32;
144+
//! 10 | |
145+
//! 11 | | {
146+
//! ... |
147+
//! 19 | | temp = 0;
148+
//! | | -------- binding initialized here in some conditions
149+
//! ... |
150+
//! 24 | | RET = temp;
151+
//! | | ^^^^^^^^^^ value used here but it is possibly-uninitialized
152+
//! 25 | | Return()
153+
//! 26 | | }
154+
//! 27 | | )
155+
//! | |_____- binding declared here but left uninitialized
156+
//!
157+
//! error: aborting due to previous error
158+
//!
159+
//! For more information about this error, try `rustc --explain E0381`.
160+
//! ```
161+
//!
162+
//! # Syntax
163+
//!
164+
//! The lists below are an exhaustive description of how various MIR constructs can be created.
165+
//! Anything missing from the list should be assumed to not be supported, PRs welcome.
166+
//!
167+
//! #### Locals
168+
//!
169+
//! - The `_0` return local can always be accessed via `RET`.
170+
//! - Arguments can be accessed via their regular name.
171+
//! - All other locals need to be declared with `let` somewhere and then can be accessed by name.
172+
//!
173+
//! #### Places
174+
//! - Locals implicit convert to places.
175+
//! - Field accesses, derefs, and indexing work normally.
176+
//! - Fields in variants can be accessed via the [`Variant`] and [`Field`] associated functions,
177+
//! see their documentation for details.
178+
//!
179+
//! #### Operands
180+
//! - Places implicitly convert to `Copy` operands.
181+
//! - `Move` operands can be created via [`Move`].
182+
//! - Const blocks, literals, named constants, and const params all just work.
183+
//! - [`Static`] and [`StaticMut`] can be used to create `&T` and `*mut T`s to statics. These are
184+
//! constants in MIR and the only way to access statics.
185+
//!
186+
//! #### Statements
187+
//! - Assign statements work via normal Rust assignment.
188+
//! - [`Retag`] statements have an associated function.
189+
//!
190+
//! #### Rvalues
191+
//!
192+
//! - Operands implicitly convert to `Use` rvalues.
193+
//! - `&`, `&mut`, `addr_of!`, and `addr_of_mut!` all work to create their associated rvalue.
194+
//! - [`Discriminant`] has an associated function.
195+
//!
196+
//! #### Terminators
197+
//!
198+
//! - [`Goto`] and [`Return`] have associated functions.
199+
//! - `match some_int_operand` becomes a `SwitchInt`. Each arm should be `literal => basic_block`
200+
//! - The exception is the last arm, which must be `_ => basic_block` and corresponds to the
201+
//! otherwise branch.
57202
//!
58203
59204
#![unstable(
@@ -69,9 +214,10 @@
69214
pub struct BasicBlock;
70215

71216
macro_rules! define {
72-
($name:literal, $($sig:tt)*) => {
217+
($name:literal, $( #[ $meta:meta ] )* fn $($sig:tt)*) => {
73218
#[rustc_diagnostic_item = $name]
74-
pub $($sig)* { panic!() }
219+
$( #[ $meta ] )*
220+
pub fn $($sig)* { panic!() }
75221
}
76222
}
77223

@@ -82,8 +228,73 @@ define!("mir_retag_raw", fn RetagRaw<T>(place: T));
82228
define!("mir_move", fn Move<T>(place: T) -> T);
83229
define!("mir_static", fn Static<T>(s: T) -> &'static T);
84230
define!("mir_static_mut", fn StaticMut<T>(s: T) -> *mut T);
231+
define!(
232+
"mir_discriminant",
233+
/// Gets the discriminant of a place.
234+
fn Discriminant<T>(place: T) -> <T as ::core::marker::DiscriminantKind>::Discriminant
235+
);
236+
define!("mir_set_discriminant", fn SetDiscriminant<T>(place: T, index: u32));
237+
define!(
238+
"mir_field",
239+
/// Access the field with the given index of some place.
240+
///
241+
/// This only makes sense to use in conjunction with [`Variant`]. If the type you are looking to
242+
/// access the field of does not have variants, you can use normal field projection syntax.
243+
///
244+
/// There is no proper way to do a place projection to a variant in Rust, and so these two
245+
/// functions are a workaround. You can access a field of a variant via `Field(Variant(place,
246+
/// var_idx), field_idx)`, where `var_idx` and `field_idx` are appropriate literals. Some
247+
/// caveats:
248+
///
249+
/// - The return type of `Variant` is always `()`. Don't worry about that, the correct MIR will
250+
/// still be generated.
251+
/// - In some situations, the return type of `Field` cannot be inferred. You may need to
252+
/// annotate it on the function in these cases.
253+
/// - Since `Field` is a function call which is not a place expression, using this on the left
254+
/// hand side of an expression is rejected by the compiler. [`place!`] is a macro provided to
255+
/// work around that issue. Wrap the left hand side of an assignment in the macro to convince
256+
/// the compiler that it's ok.
257+
///
258+
/// # Examples
259+
///
260+
/// ```rust
261+
/// #![feature(custom_mir, core_intrinsics)]
262+
///
263+
/// extern crate core;
264+
/// use core::intrinsics::mir::*;
265+
///
266+
/// #[custom_mir(dialect = "built")]
267+
/// fn unwrap_deref(opt: Option<&i32>) -> i32 {
268+
/// mir!({
269+
/// RET = *Field::<&i32>(Variant(opt, 1), 0);
270+
/// Return()
271+
/// })
272+
/// }
273+
///
274+
/// #[custom_mir(dialect = "built")]
275+
/// fn set(opt: &mut Option<i32>) {
276+
/// mir!({
277+
/// place!(Field(Variant(*opt, 1), 0)) = 5;
278+
/// Return()
279+
/// })
280+
/// }
281+
/// ```
282+
fn Field<F>(place: (), field: u32) -> F
283+
);
284+
define!(
285+
"mir_variant",
286+
/// Adds a variant projection with the given index to the place.
287+
///
288+
/// See [`Field`] for documentation.
289+
fn Variant<T>(place: T, index: u32) -> ()
290+
);
291+
define!(
292+
"mir_make_place",
293+
#[doc(hidden)]
294+
fn __internal_make_place<T>(place: T) -> *mut T
295+
);
85296

86-
/// Convenience macro for generating custom MIR.
297+
/// Macro for generating custom MIR.
87298
///
88299
/// See the module documentation for syntax details. This macro is not magic - it only transforms
89300
/// your MIR into something that is easier to parse in the compiler.
@@ -139,6 +350,13 @@ pub macro mir {
139350
}}
140351
}
141352

353+
/// Helper macro that allows you to treat a value expression like a place expression.
354+
///
355+
/// See the documentation on [`Variant`] for why this is necessary and how to use it.
356+
pub macro place($e:expr) {
357+
(*::core::intrinsics::mir::__internal_make_place($e))
358+
}
359+
142360
/// Helper macro that extracts the `let` declarations out of a bunch of statements.
143361
///
144362
/// This macro is written using the "statement muncher" strategy. Each invocation parses the first

0 commit comments

Comments
 (0)