Skip to content

Commit b158598

Browse files
committed
Add stable MIR Projections support based on MIR structure
This commit includes richer projections for both Places and UserTypeProjections. However, the tests only touch on Places. There are also outstanding TODOs regarding how projections should be resolved to produce Place types, and regarding if UserTypeProjections should just contain ProjectionElem<(),()> objects as in MIR.
1 parent 0f44eb3 commit b158598

File tree

3 files changed

+283
-6
lines changed

3 files changed

+283
-6
lines changed

compiler/rustc_smir/src/rustc_smir/mod.rs

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -682,19 +682,80 @@ impl<'tcx> Stable<'tcx> for mir::ConstOperand<'tcx> {
682682

683683
impl<'tcx> Stable<'tcx> for mir::Place<'tcx> {
684684
type T = stable_mir::mir::Place;
685-
fn stable(&self, _: &mut Tables<'tcx>) -> Self::T {
685+
fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T {
686686
stable_mir::mir::Place {
687687
local: self.local.as_usize(),
688-
projection: format!("{:?}", self.projection),
688+
projection: self.projection.iter().map(|e| e.stable(tables)).collect(),
689+
}
690+
}
691+
}
692+
693+
impl<'tcx> Stable<'tcx> for mir::PlaceElem<'tcx> {
694+
type T = stable_mir::mir::ProjectionElem<stable_mir::mir::Local, stable_mir::ty::Ty>;
695+
fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T {
696+
use mir::ProjectionElem::*;
697+
match self {
698+
Deref => stable_mir::mir::ProjectionElem::Deref,
699+
Field(idx, ty) => {
700+
stable_mir::mir::ProjectionElem::Field(idx.stable(tables), ty.stable(tables))
701+
}
702+
Index(local) => stable_mir::mir::ProjectionElem::Index(local.stable(tables)),
703+
ConstantIndex { offset, min_length, from_end } => {
704+
stable_mir::mir::ProjectionElem::ConstantIndex {
705+
offset: *offset,
706+
min_length: *min_length,
707+
from_end: *from_end,
708+
}
709+
}
710+
Subslice { from, to, from_end } => stable_mir::mir::ProjectionElem::Subslice {
711+
from: *from,
712+
to: *to,
713+
from_end: *from_end,
714+
},
715+
Downcast(_, idx) => stable_mir::mir::ProjectionElem::Downcast(idx.stable(tables)),
716+
OpaqueCast(ty) => stable_mir::mir::ProjectionElem::OpaqueCast(ty.stable(tables)),
717+
Subtype(ty) => stable_mir::mir::ProjectionElem::Subtype(ty.stable(tables)),
689718
}
690719
}
691720
}
692721

693722
impl<'tcx> Stable<'tcx> for mir::UserTypeProjection {
694723
type T = stable_mir::mir::UserTypeProjection;
695724

696-
fn stable(&self, _: &mut Tables<'tcx>) -> Self::T {
697-
UserTypeProjection { base: self.base.as_usize(), projection: format!("{:?}", self.projs) }
725+
fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T {
726+
UserTypeProjection {
727+
base: self.base.as_usize(),
728+
projection: self.projs.iter().map(|e| e.stable(tables)).collect(),
729+
}
730+
}
731+
}
732+
733+
// ProjectionKind is nearly identical to PlaceElem, except its generic arguments are units. We
734+
// therefore don't need to resolve any arguments with the generic types.
735+
impl<'tcx> Stable<'tcx> for mir::ProjectionKind {
736+
type T = stable_mir::mir::ProjectionElem<(), ()>;
737+
fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T {
738+
use mir::ProjectionElem::*;
739+
match self {
740+
Deref => stable_mir::mir::ProjectionElem::Deref,
741+
Field(idx, ty) => stable_mir::mir::ProjectionElem::Field(idx.stable(tables), *ty),
742+
Index(local) => stable_mir::mir::ProjectionElem::Index(*local),
743+
ConstantIndex { offset, min_length, from_end } => {
744+
stable_mir::mir::ProjectionElem::ConstantIndex {
745+
offset: *offset,
746+
min_length: *min_length,
747+
from_end: *from_end,
748+
}
749+
}
750+
Subslice { from, to, from_end } => stable_mir::mir::ProjectionElem::Subslice {
751+
from: *from,
752+
to: *to,
753+
from_end: *from_end,
754+
},
755+
Downcast(_, idx) => stable_mir::mir::ProjectionElem::Downcast(idx.stable(tables)),
756+
OpaqueCast(ty) => stable_mir::mir::ProjectionElem::OpaqueCast(*ty),
757+
Subtype(ty) => stable_mir::mir::ProjectionElem::Subtype(*ty),
758+
}
698759
}
699760
}
700761

compiler/stable_mir/src/mir/body.rs

Lines changed: 115 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -398,22 +398,133 @@ pub enum Operand {
398398
pub struct Place {
399399
pub local: Local,
400400
/// projection out of a place (access a field, deref a pointer, etc)
401-
pub projection: String,
401+
pub projection: Vec<ProjectionElem<Local, Ty>>,
402+
}
403+
404+
// TODO(klinvill): in MIR ProjectionElem is parameterized on the second Field argument and the Index
405+
// argument. This is so it can be used for both the rust provided Places (for which the projection
406+
// elements are of type ProjectionElem<Local, Ty>) and user-provided type annotations (for which the
407+
// projection elements are of type ProjectionElem<(), ()>). Should we do the same thing in Stable MIR?
408+
#[derive(Clone, Debug)]
409+
pub enum ProjectionElem<V, T> {
410+
/// Dereference projections (e.g. `*_1`) project to the address referenced by the base place.
411+
Deref,
412+
413+
/// A field projection (e.g., `f` in `_1.f`) project to a field in the base place. The field is
414+
/// referenced by source-order index rather than the name of the field. The fields type is also
415+
/// given.
416+
Field(FieldIdx, T),
417+
418+
/// Index into a slice/array. The value of the index is computed at runtime using the `V`
419+
/// argument.
420+
///
421+
/// Note that this does not also dereference, and so it does not exactly correspond to slice
422+
/// indexing in Rust. In other words, in the below Rust code:
423+
///
424+
/// ```rust
425+
/// let x = &[1, 2, 3, 4];
426+
/// let i = 2;
427+
/// x[i];
428+
/// ```
429+
///
430+
/// The `x[i]` is turned into a `Deref` followed by an `Index`, not just an `Index`. The same
431+
/// thing is true of the `ConstantIndex` and `Subslice` projections below.
432+
Index(V),
433+
434+
/// Index into a slice/array given by offsets.
435+
///
436+
/// These indices are generated by slice patterns. Easiest to explain by example:
437+
///
438+
/// ```ignore (illustrative)
439+
/// [X, _, .._, _, _] => { offset: 0, min_length: 4, from_end: false },
440+
/// [_, X, .._, _, _] => { offset: 1, min_length: 4, from_end: false },
441+
/// [_, _, .._, X, _] => { offset: 2, min_length: 4, from_end: true },
442+
/// [_, _, .._, _, X] => { offset: 1, min_length: 4, from_end: true },
443+
/// ```
444+
ConstantIndex {
445+
/// index or -index (in Python terms), depending on from_end
446+
offset: u64,
447+
/// The thing being indexed must be at least this long. For arrays this
448+
/// is always the exact length.
449+
min_length: u64,
450+
/// Counting backwards from end? This is always false when indexing an
451+
/// array.
452+
from_end: bool,
453+
},
454+
455+
/// Projects a slice from the base place.
456+
///
457+
/// These indices are generated by slice patterns. If `from_end` is true, this represents
458+
/// `slice[from..slice.len() - to]`. Otherwise it represents `array[from..to]`.
459+
Subslice {
460+
from: u64,
461+
to: u64,
462+
/// Whether `to` counts from the start or end of the array/slice.
463+
from_end: bool,
464+
},
465+
466+
/// "Downcast" to a variant of an enum or a coroutine.
467+
//
468+
// TODO(klinvill): MIR includes an Option<Symbol> argument that is the name of the variant, used
469+
// for printing MIR. However I don't see it used anywhere. Is such a field needed or can we just
470+
// include the VariantIdx which could be used to recover the field name if needed?
471+
Downcast(VariantIdx),
472+
473+
/// Like an explicit cast from an opaque type to a concrete type, but without
474+
/// requiring an intermediate variable.
475+
OpaqueCast(T),
476+
477+
/// A `Subtype(T)` projection is applied to any `StatementKind::Assign` where
478+
/// type of lvalue doesn't match the type of rvalue, the primary goal is making subtyping
479+
/// explicit during optimizations and codegen.
480+
///
481+
/// This projection doesn't impact the runtime behavior of the program except for potentially changing
482+
/// some type metadata of the interpreter or codegen backend.
483+
Subtype(T),
402484
}
403485

404486
#[derive(Clone, Debug, Eq, PartialEq)]
405487
pub struct UserTypeProjection {
406488
pub base: UserTypeAnnotationIndex,
407-
pub projection: String,
489+
490+
/// `UserTypeProjection` projections need neither the `V` parameter for `Index` nor the `T` for
491+
/// `Field`.
492+
pub projection: Vec<ProjectionElem<(), ()>>,
408493
}
409494

410495
pub type Local = usize;
411496

412497
pub const RETURN_LOCAL: Local = 0;
413498

499+
/// The source-order index of a field in a variant.
500+
///
501+
/// For example, in the following types,
502+
/// ```rust
503+
/// enum Demo1 {
504+
/// Variant0 { a: bool, b: i32 },
505+
/// Variant1 { c: u8, d: u64 },
506+
/// }
507+
/// struct Demo2 { e: u8, f: u16, g: u8 }
508+
/// ```
509+
/// `a`'s `FieldIdx` is `0`,
510+
/// `b`'s `FieldIdx` is `1`,
511+
/// `c`'s `FieldIdx` is `0`, and
512+
/// `g`'s `FieldIdx` is `2`.
414513
type FieldIdx = usize;
415514

416515
/// The source-order index of a variant in a type.
516+
///
517+
/// For example, in the following types,
518+
/// ```rust
519+
/// enum Demo1 {
520+
/// Variant0 { a: bool, b: i32 },
521+
/// Variant1 { c: u8, d: u64 },
522+
/// }
523+
/// struct Demo2 { e: u8, f: u16, g: u8 }
524+
/// ```
525+
/// `a` is in the variant with the `VariantIdx` of `0`,
526+
/// `c` is in the variant with the `VariantIdx` of `1`, and
527+
/// `g` is in the variant with the `VariantIdx` of `0`.
417528
pub type VariantIdx = usize;
418529

419530
type UserTypeAnnotationIndex = usize;
@@ -536,6 +647,8 @@ impl Constant {
536647
}
537648

538649
impl Place {
650+
// TODO(klinvill): What is the expected behavior of this function? Should it resolve down the
651+
// chain of projections so that `*(_1.f)` would end up returning the type referenced by `f`?
539652
pub fn ty(&self, locals: &[LocalDecl]) -> Ty {
540653
let _start_ty = locals[self.local].ty;
541654
todo!("Implement projection")

tests/ui-fulldeps/stable-mir/crate-info.rs

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use rustc_hir::def::DefKind;
2323
use rustc_middle::ty::TyCtxt;
2424
use rustc_smir::rustc_internal;
2525
use stable_mir::mir::mono::Instance;
26+
use stable_mir::mir::{ProjectionElem, Rvalue, StatementKind};
2627
use stable_mir::ty::{RigidTy, TyKind};
2728
use std::assert_matches::assert_matches;
2829
use std::io::Write;
@@ -163,6 +164,99 @@ fn test_stable_mir(_tcx: TyCtxt<'_>) -> ControlFlow<()> {
163164
stable_mir::ty::TyKind::RigidTy(stable_mir::ty::RigidTy::Bool)
164165
);
165166

167+
let projections_fn = get_item(&items, (DefKind::Fn, "projections")).unwrap();
168+
let body = projections_fn.body();
169+
assert_eq!(body.blocks.len(), 4);
170+
// The first statement assigns `&s.c` to a local. The projections include a deref for `s`, since
171+
// `s` is passed as a reference argument, and a field access for field `c`.
172+
match &body.blocks[0].statements[0].kind {
173+
StatementKind::Assign(
174+
stable_mir::mir::Place { local: _, projection: local_proj },
175+
Rvalue::Ref(_, _, stable_mir::mir::Place { local: _, projection: r_proj }),
176+
) => {
177+
// We can't match on vecs, only on slices. Comparing for equality wouldn't be any easier
178+
// since we'd then have to add in the expected local and region values instead of
179+
// matching on wildcards.
180+
assert_matches!(local_proj[..], []);
181+
match &r_proj[..] {
182+
// Similarly we can't match against a type, only against its kind.
183+
[ProjectionElem::Deref, ProjectionElem::Field(2, ty)] => assert_matches!(
184+
ty.kind(),
185+
TyKind::RigidTy(RigidTy::Uint(stable_mir::ty::UintTy::U8))
186+
),
187+
other => panic!(
188+
"Unable to match against expected rvalue projection. Expected the projection \
189+
for `s.c`, which is a Deref and u8 Field. Got: {:?}",
190+
other
191+
),
192+
};
193+
}
194+
other => panic!(
195+
"Unable to match against expected Assign statement with a Ref rvalue. Expected the \
196+
statement to assign `&s.c` to a local. Got: {:?}",
197+
other
198+
),
199+
};
200+
// This statement assigns `slice[1]` to a local. The projections include a deref for `slice`,
201+
// since `slice` is a reference, and an index.
202+
match &body.blocks[2].statements[0].kind {
203+
StatementKind::Assign(
204+
stable_mir::mir::Place { local: _, projection: local_proj },
205+
Rvalue::Use(stable_mir::mir::Operand::Copy(stable_mir::mir::Place {
206+
local: _,
207+
projection: r_proj,
208+
})),
209+
) => {
210+
// We can't match on vecs, only on slices. Comparing for equality wouldn't be any easier
211+
// since we'd then have to add in the expected local values instead of matching on
212+
// wildcards.
213+
assert_matches!(local_proj[..], []);
214+
assert_matches!(r_proj[..], [ProjectionElem::Deref, ProjectionElem::Index(_)]);
215+
}
216+
other => panic!(
217+
"Unable to match against expected Assign statement with a Use rvalue. Expected the \
218+
statement to assign `slice[1]` to a local. Got: {:?}",
219+
other
220+
),
221+
};
222+
// The first terminator gets a slice of an array via the Index operation. Specifically it
223+
// performs `&vals[1..3]`. There are no projections in this case, the arguments are just locals.
224+
match &body.blocks[0].terminator.kind {
225+
stable_mir::mir::TerminatorKind::Call { args, .. } =>
226+
// We can't match on vecs, only on slices. Comparing for equality wouldn't be any easier
227+
// since we'd then have to add in the expected local values instead of matching on
228+
// wildcards.
229+
{
230+
match &args[..] {
231+
[
232+
stable_mir::mir::Operand::Move(stable_mir::mir::Place {
233+
local: _,
234+
projection: arg1_proj,
235+
}),
236+
stable_mir::mir::Operand::Move(stable_mir::mir::Place {
237+
local: _,
238+
projection: arg2_proj,
239+
}),
240+
] => {
241+
assert_matches!(arg1_proj[..], []);
242+
assert_matches!(arg2_proj[..], []);
243+
}
244+
other => {
245+
panic!(
246+
"Unable to match against expected arguments to Index call. Expected two \
247+
move operands. Got: {:?}",
248+
other
249+
)
250+
}
251+
}
252+
}
253+
other => panic!(
254+
"Unable to match against expected Call terminator. Expected a terminator that calls \
255+
the Index operation. Got: {:?}",
256+
other
257+
),
258+
};
259+
166260
ControlFlow::Continue(())
167261
}
168262

@@ -242,6 +336,15 @@ fn generate_input(path: &str) -> std::io::Result<()> {
242336
}} else {{
243337
'b'
244338
}}
339+
}}
340+
341+
pub struct Struct1 {{ _a: u8, _b: u16, c: u8 }}
342+
343+
pub fn projections(s: &Struct1) -> u8 {{
344+
let v = &s.c;
345+
let vals = [1, 2, 3, 4];
346+
let slice = &vals[1..3];
347+
v + slice[1]
245348
}}"#
246349
)?;
247350
Ok(())

0 commit comments

Comments
 (0)