diff --git a/compiler/rustc_smir/src/rustc_smir/mod.rs b/compiler/rustc_smir/src/rustc_smir/mod.rs index 27596c08f1c..83bacfb8263 100644 --- a/compiler/rustc_smir/src/rustc_smir/mod.rs +++ b/compiler/rustc_smir/src/rustc_smir/mod.rs @@ -682,10 +682,39 @@ impl<'tcx> Stable<'tcx> for mir::ConstOperand<'tcx> { impl<'tcx> Stable<'tcx> for mir::Place<'tcx> { type T = stable_mir::mir::Place; - fn stable(&self, _: &mut Tables<'tcx>) -> Self::T { + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { stable_mir::mir::Place { local: self.local.as_usize(), - projection: format!("{:?}", self.projection), + projection: self.projection.iter().map(|e| e.stable(tables)).collect(), + } + } +} + +impl<'tcx> Stable<'tcx> for mir::PlaceElem<'tcx> { + type T = stable_mir::mir::ProjectionElem; + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + use mir::ProjectionElem::*; + match self { + Deref => stable_mir::mir::ProjectionElem::Deref, + Field(idx, ty) => { + stable_mir::mir::ProjectionElem::Field(idx.stable(tables), ty.stable(tables)) + } + Index(local) => stable_mir::mir::ProjectionElem::Index(local.stable(tables)), + ConstantIndex { offset, min_length, from_end } => { + stable_mir::mir::ProjectionElem::ConstantIndex { + offset: *offset, + min_length: *min_length, + from_end: *from_end, + } + } + Subslice { from, to, from_end } => stable_mir::mir::ProjectionElem::Subslice { + from: *from, + to: *to, + from_end: *from_end, + }, + Downcast(_, idx) => stable_mir::mir::ProjectionElem::Downcast(idx.stable(tables)), + OpaqueCast(ty) => stable_mir::mir::ProjectionElem::OpaqueCast(ty.stable(tables)), + Subtype(ty) => stable_mir::mir::ProjectionElem::Subtype(ty.stable(tables)), } } } @@ -693,8 +722,40 @@ impl<'tcx> Stable<'tcx> for mir::Place<'tcx> { impl<'tcx> Stable<'tcx> for mir::UserTypeProjection { type T = stable_mir::mir::UserTypeProjection; - fn stable(&self, _: &mut Tables<'tcx>) -> Self::T { - UserTypeProjection { base: self.base.as_usize(), projection: format!("{:?}", self.projs) } + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + UserTypeProjection { + base: self.base.as_usize(), + projection: self.projs.iter().map(|e| e.stable(tables)).collect(), + } + } +} + +// ProjectionKind is nearly identical to PlaceElem, except its generic arguments are units. We +// therefore don't need to resolve any arguments with the generic types. +impl<'tcx> Stable<'tcx> for mir::ProjectionKind { + type T = stable_mir::mir::ProjectionElem<(), ()>; + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + use mir::ProjectionElem::*; + match self { + Deref => stable_mir::mir::ProjectionElem::Deref, + Field(idx, ty) => stable_mir::mir::ProjectionElem::Field(idx.stable(tables), *ty), + Index(local) => stable_mir::mir::ProjectionElem::Index(*local), + ConstantIndex { offset, min_length, from_end } => { + stable_mir::mir::ProjectionElem::ConstantIndex { + offset: *offset, + min_length: *min_length, + from_end: *from_end, + } + } + Subslice { from, to, from_end } => stable_mir::mir::ProjectionElem::Subslice { + from: *from, + to: *to, + from_end: *from_end, + }, + Downcast(_, idx) => stable_mir::mir::ProjectionElem::Downcast(idx.stable(tables)), + OpaqueCast(ty) => stable_mir::mir::ProjectionElem::OpaqueCast(*ty), + Subtype(ty) => stable_mir::mir::ProjectionElem::Subtype(*ty), + } } } diff --git a/compiler/stable_mir/src/mir/body.rs b/compiler/stable_mir/src/mir/body.rs index 06933783685..69e83efdf43 100644 --- a/compiler/stable_mir/src/mir/body.rs +++ b/compiler/stable_mir/src/mir/body.rs @@ -398,22 +398,133 @@ pub enum Operand { pub struct Place { pub local: Local, /// projection out of a place (access a field, deref a pointer, etc) - pub projection: String, + pub projection: Vec>, +} + +// TODO(klinvill): in MIR ProjectionElem is parameterized on the second Field argument and the Index +// argument. This is so it can be used for both the rust provided Places (for which the projection +// elements are of type ProjectionElem) and user-provided type annotations (for which the +// projection elements are of type ProjectionElem<(), ()>). Should we do the same thing in Stable MIR? +#[derive(Clone, Debug)] +pub enum ProjectionElem { + /// Dereference projections (e.g. `*_1`) project to the address referenced by the base place. + Deref, + + /// A field projection (e.g., `f` in `_1.f`) project to a field in the base place. The field is + /// referenced by source-order index rather than the name of the field. The fields type is also + /// given. + Field(FieldIdx, T), + + /// Index into a slice/array. The value of the index is computed at runtime using the `V` + /// argument. + /// + /// Note that this does not also dereference, and so it does not exactly correspond to slice + /// indexing in Rust. In other words, in the below Rust code: + /// + /// ```rust + /// let x = &[1, 2, 3, 4]; + /// let i = 2; + /// x[i]; + /// ``` + /// + /// The `x[i]` is turned into a `Deref` followed by an `Index`, not just an `Index`. The same + /// thing is true of the `ConstantIndex` and `Subslice` projections below. + Index(V), + + /// Index into a slice/array given by offsets. + /// + /// These indices are generated by slice patterns. Easiest to explain by example: + /// + /// ```ignore (illustrative) + /// [X, _, .._, _, _] => { offset: 0, min_length: 4, from_end: false }, + /// [_, X, .._, _, _] => { offset: 1, min_length: 4, from_end: false }, + /// [_, _, .._, X, _] => { offset: 2, min_length: 4, from_end: true }, + /// [_, _, .._, _, X] => { offset: 1, min_length: 4, from_end: true }, + /// ``` + ConstantIndex { + /// index or -index (in Python terms), depending on from_end + offset: u64, + /// The thing being indexed must be at least this long. For arrays this + /// is always the exact length. + min_length: u64, + /// Counting backwards from end? This is always false when indexing an + /// array. + from_end: bool, + }, + + /// Projects a slice from the base place. + /// + /// These indices are generated by slice patterns. If `from_end` is true, this represents + /// `slice[from..slice.len() - to]`. Otherwise it represents `array[from..to]`. + Subslice { + from: u64, + to: u64, + /// Whether `to` counts from the start or end of the array/slice. + from_end: bool, + }, + + /// "Downcast" to a variant of an enum or a coroutine. + // + // TODO(klinvill): MIR includes an Option argument that is the name of the variant, used + // for printing MIR. However I don't see it used anywhere. Is such a field needed or can we just + // include the VariantIdx which could be used to recover the field name if needed? + Downcast(VariantIdx), + + /// Like an explicit cast from an opaque type to a concrete type, but without + /// requiring an intermediate variable. + OpaqueCast(T), + + /// A `Subtype(T)` projection is applied to any `StatementKind::Assign` where + /// type of lvalue doesn't match the type of rvalue, the primary goal is making subtyping + /// explicit during optimizations and codegen. + /// + /// This projection doesn't impact the runtime behavior of the program except for potentially changing + /// some type metadata of the interpreter or codegen backend. + Subtype(T), } #[derive(Clone, Debug, Eq, PartialEq)] pub struct UserTypeProjection { pub base: UserTypeAnnotationIndex, - pub projection: String, + + /// `UserTypeProjection` projections need neither the `V` parameter for `Index` nor the `T` for + /// `Field`. + pub projection: Vec>, } pub type Local = usize; pub const RETURN_LOCAL: Local = 0; +/// The source-order index of a field in a variant. +/// +/// For example, in the following types, +/// ```rust +/// enum Demo1 { +/// Variant0 { a: bool, b: i32 }, +/// Variant1 { c: u8, d: u64 }, +/// } +/// struct Demo2 { e: u8, f: u16, g: u8 } +/// ``` +/// `a`'s `FieldIdx` is `0`, +/// `b`'s `FieldIdx` is `1`, +/// `c`'s `FieldIdx` is `0`, and +/// `g`'s `FieldIdx` is `2`. type FieldIdx = usize; /// The source-order index of a variant in a type. +/// +/// For example, in the following types, +/// ```rust +/// enum Demo1 { +/// Variant0 { a: bool, b: i32 }, +/// Variant1 { c: u8, d: u64 }, +/// } +/// struct Demo2 { e: u8, f: u16, g: u8 } +/// ``` +/// `a` is in the variant with the `VariantIdx` of `0`, +/// `c` is in the variant with the `VariantIdx` of `1`, and +/// `g` is in the variant with the `VariantIdx` of `0`. pub type VariantIdx = usize; type UserTypeAnnotationIndex = usize; @@ -536,6 +647,8 @@ impl Constant { } impl Place { + // TODO(klinvill): What is the expected behavior of this function? Should it resolve down the + // chain of projections so that `*(_1.f)` would end up returning the type referenced by `f`? pub fn ty(&self, locals: &[LocalDecl]) -> Ty { let _start_ty = locals[self.local].ty; todo!("Implement projection") diff --git a/tests/ui-fulldeps/stable-mir/crate-info.rs b/tests/ui-fulldeps/stable-mir/crate-info.rs index ed6b786f5e1..c6aaea14872 100644 --- a/tests/ui-fulldeps/stable-mir/crate-info.rs +++ b/tests/ui-fulldeps/stable-mir/crate-info.rs @@ -23,6 +23,7 @@ use rustc_hir::def::DefKind; use rustc_middle::ty::TyCtxt; use rustc_smir::rustc_internal; use stable_mir::mir::mono::Instance; +use stable_mir::mir::{ProjectionElem, Rvalue, StatementKind}; use stable_mir::ty::{RigidTy, TyKind}; use std::assert_matches::assert_matches; use std::io::Write; @@ -163,6 +164,99 @@ fn test_stable_mir(_tcx: TyCtxt<'_>) -> ControlFlow<()> { stable_mir::ty::TyKind::RigidTy(stable_mir::ty::RigidTy::Bool) ); + let projections_fn = get_item(&items, (DefKind::Fn, "projections")).unwrap(); + let body = projections_fn.body(); + assert_eq!(body.blocks.len(), 4); + // The first statement assigns `&s.c` to a local. The projections include a deref for `s`, since + // `s` is passed as a reference argument, and a field access for field `c`. + match &body.blocks[0].statements[0].kind { + StatementKind::Assign( + stable_mir::mir::Place { local: _, projection: local_proj }, + Rvalue::Ref(_, _, stable_mir::mir::Place { local: _, projection: r_proj }), + ) => { + // We can't match on vecs, only on slices. Comparing for equality wouldn't be any easier + // since we'd then have to add in the expected local and region values instead of + // matching on wildcards. + assert_matches!(local_proj[..], []); + match &r_proj[..] { + // Similarly we can't match against a type, only against its kind. + [ProjectionElem::Deref, ProjectionElem::Field(2, ty)] => assert_matches!( + ty.kind(), + TyKind::RigidTy(RigidTy::Uint(stable_mir::ty::UintTy::U8)) + ), + other => panic!( + "Unable to match against expected rvalue projection. Expected the projection \ + for `s.c`, which is a Deref and u8 Field. Got: {:?}", + other + ), + }; + } + other => panic!( + "Unable to match against expected Assign statement with a Ref rvalue. Expected the \ + statement to assign `&s.c` to a local. Got: {:?}", + other + ), + }; + // This statement assigns `slice[1]` to a local. The projections include a deref for `slice`, + // since `slice` is a reference, and an index. + match &body.blocks[2].statements[0].kind { + StatementKind::Assign( + stable_mir::mir::Place { local: _, projection: local_proj }, + Rvalue::Use(stable_mir::mir::Operand::Copy(stable_mir::mir::Place { + local: _, + projection: r_proj, + })), + ) => { + // We can't match on vecs, only on slices. Comparing for equality wouldn't be any easier + // since we'd then have to add in the expected local values instead of matching on + // wildcards. + assert_matches!(local_proj[..], []); + assert_matches!(r_proj[..], [ProjectionElem::Deref, ProjectionElem::Index(_)]); + } + other => panic!( + "Unable to match against expected Assign statement with a Use rvalue. Expected the \ + statement to assign `slice[1]` to a local. Got: {:?}", + other + ), + }; + // The first terminator gets a slice of an array via the Index operation. Specifically it + // performs `&vals[1..3]`. There are no projections in this case, the arguments are just locals. + match &body.blocks[0].terminator.kind { + stable_mir::mir::TerminatorKind::Call { args, .. } => + // We can't match on vecs, only on slices. Comparing for equality wouldn't be any easier + // since we'd then have to add in the expected local values instead of matching on + // wildcards. + { + match &args[..] { + [ + stable_mir::mir::Operand::Move(stable_mir::mir::Place { + local: _, + projection: arg1_proj, + }), + stable_mir::mir::Operand::Move(stable_mir::mir::Place { + local: _, + projection: arg2_proj, + }), + ] => { + assert_matches!(arg1_proj[..], []); + assert_matches!(arg2_proj[..], []); + } + other => { + panic!( + "Unable to match against expected arguments to Index call. Expected two \ + move operands. Got: {:?}", + other + ) + } + } + } + other => panic!( + "Unable to match against expected Call terminator. Expected a terminator that calls \ + the Index operation. Got: {:?}", + other + ), + }; + ControlFlow::Continue(()) } @@ -242,6 +336,15 @@ fn generate_input(path: &str) -> std::io::Result<()> { }} else {{ 'b' }} + }} + + pub struct Struct1 {{ _a: u8, _b: u16, c: u8 }} + + pub fn projections(s: &Struct1) -> u8 {{ + let v = &s.c; + let vals = [1, 2, 3, 4]; + let slice = &vals[1..3]; + v + slice[1] }}"# )?; Ok(())