about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_smir/src/rustc_smir/mod.rs42
-rw-r--r--compiler/stable_mir/src/mir/body.rs114
-rw-r--r--compiler/stable_mir/src/mir/visit.rs33
-rw-r--r--tests/ui-fulldeps/stable-mir/projections.rs173
4 files changed, 355 insertions, 7 deletions
diff --git a/compiler/rustc_smir/src/rustc_smir/mod.rs b/compiler/rustc_smir/src/rustc_smir/mod.rs
index 170f53a7a69..f36ae1b071f 100644
--- a/compiler/rustc_smir/src/rustc_smir/mod.rs
+++ b/compiler/rustc_smir/src/rustc_smir/mod.rs
@@ -682,10 +682,44 @@ 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,
+            },
+            // MIR includes an `Option<Symbol>` argument for `Downcast` that is the name of the
+            // variant, used for printing MIR. However this information should also be accessible
+            // via a lookup using the `VariantIdx`. The `Option<Symbol>` argument is therefore
+            // dropped when converting to Stable MIR. A brief justification for this decision can be
+            // found at https://github.com/rust-lang/rust/pull/117517#issuecomment-1811683486
+            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 +727,8 @@ 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: opaque(&self.projs) }
     }
 }
 
diff --git a/compiler/stable_mir/src/mir/body.rs b/compiler/stable_mir/src/mir/body.rs
index 06933783685..351e7bb69c3 100644
--- a/compiler/stable_mir/src/mir/body.rs
+++ b/compiler/stable_mir/src/mir/body.rs
@@ -398,22 +398,128 @@ 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<ProjectionElem>,
+}
+
+// In MIR ProjectionElem is parameterized on the second Field argument and the Index argument. This
+// is so it can be used for both Places (for which the projection elements are of type
+// ProjectionElem<Local, Ty>) and user-provided type annotations (for which the projection elements
+// are of type ProjectionElem<(), ()>). In SMIR we don't need this generality, so we just use
+// ProjectionElem for Places.
+#[derive(Clone, Debug, Eq, PartialEq)]
+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, Ty),
+
+    /// 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(Local),
+
+    /// 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.
+    Downcast(VariantIdx),
+
+    /// Like an explicit cast from an opaque type to a concrete type, but without
+    /// requiring an intermediate variable.
+    OpaqueCast(Ty),
+
+    /// 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(Ty),
 }
 
 #[derive(Clone, Debug, Eq, PartialEq)]
 pub struct UserTypeProjection {
     pub base: UserTypeAnnotationIndex,
-    pub projection: String,
+
+    pub projection: Opaque,
 }
 
 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,
+/// ```ignore(illustrative)
+/// 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,
+/// ```ignore(illustrative)
+/// 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 +642,10 @@ impl Constant {
 }
 
 impl Place {
+    // FIXME(klinvill): This function is expected to resolve down the chain of projections to get
+    // the type referenced at the end of it. E.g. calling `ty()` on `*(_1.f)` should end up
+    // returning the type referenced by `f`. The information needed to do this may not currently be
+    // present in Stable MIR since at least an implementation for AdtDef is probably needed.
     pub fn ty(&self, locals: &[LocalDecl]) -> Ty {
         let _start_ty = locals[self.local].ty;
         todo!("Implement projection")
diff --git a/compiler/stable_mir/src/mir/visit.rs b/compiler/stable_mir/src/mir/visit.rs
index 806dced71ff..475d6e9763d 100644
--- a/compiler/stable_mir/src/mir/visit.rs
+++ b/compiler/stable_mir/src/mir/visit.rs
@@ -76,6 +76,15 @@ pub trait MirVisitor {
         self.super_place(place, ptx, location)
     }
 
+    fn visit_projection_elem(
+        &mut self,
+        elem: &ProjectionElem,
+        ptx: PlaceContext,
+        location: Location,
+    ) {
+        self.super_projection_elem(elem, ptx, location);
+    }
+
     fn visit_local(&mut self, local: &Local, ptx: PlaceContext, location: Location) {
         let _ = (local, ptx, location);
     }
@@ -264,7 +273,29 @@ pub trait MirVisitor {
     fn super_place(&mut self, place: &Place, ptx: PlaceContext, location: Location) {
         let _ = location;
         let _ = ptx;
-        visit_opaque(&Opaque(place.projection.clone()));
+        self.visit_local(&place.local, ptx, location);
+
+        for elem in &place.projection {
+            self.visit_projection_elem(elem, ptx, location);
+        }
+    }
+
+    fn super_projection_elem(
+        &mut self,
+        elem: &ProjectionElem,
+        ptx: PlaceContext,
+        location: Location,
+    ) {
+        match elem {
+            ProjectionElem::Deref => {}
+            ProjectionElem::Field(_idx, ty) => self.visit_ty(ty, location),
+            ProjectionElem::Index(local) => self.visit_local(local, ptx, location),
+            ProjectionElem::ConstantIndex { offset: _, min_length: _, from_end: _ } => {}
+            ProjectionElem::Subslice { from: _, to: _, from_end: _ } => {}
+            ProjectionElem::Downcast(_idx) => {}
+            ProjectionElem::OpaqueCast(ty) => self.visit_ty(ty, location),
+            ProjectionElem::Subtype(ty) => self.visit_ty(ty, location),
+        }
     }
 
     fn super_rvalue(&mut self, rvalue: &Rvalue, location: Location) {
diff --git a/tests/ui-fulldeps/stable-mir/projections.rs b/tests/ui-fulldeps/stable-mir/projections.rs
new file mode 100644
index 00000000000..9c649a2effc
--- /dev/null
+++ b/tests/ui-fulldeps/stable-mir/projections.rs
@@ -0,0 +1,173 @@
+// run-pass
+// Tests the Stable MIR projections API
+
+// ignore-stage1
+// ignore-cross-compile
+// ignore-remote
+// ignore-windows-gnu mingw has troubles with linking https://github.com/rust-lang/rust/pull/116837
+// edition: 2021
+
+#![feature(rustc_private)]
+#![feature(assert_matches)]
+#![feature(control_flow_enum)]
+
+extern crate rustc_hir;
+extern crate rustc_middle;
+#[macro_use]
+extern crate rustc_smir;
+extern crate rustc_driver;
+extern crate rustc_interface;
+extern crate stable_mir;
+
+use rustc_hir::def::DefKind;
+use rustc_middle::ty::TyCtxt;
+use rustc_smir::rustc_internal;
+use stable_mir::mir::{ProjectionElem, Rvalue, StatementKind};
+use stable_mir::ty::{RigidTy, TyKind};
+use std::assert_matches::assert_matches;
+use std::io::Write;
+use std::ops::ControlFlow;
+
+const CRATE_NAME: &str = "input";
+
+/// Tests projections within Place objects
+fn test_place_projections(_tcx: TyCtxt<'_>) -> ControlFlow<()> {
+    let items = stable_mir::all_local_items();
+    let body = get_item(&items, (DefKind::Fn, "projections")).unwrap().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 statements 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!(local_proj.is_empty());
+            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!(local_proj.is_empty());
+            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!(arg1_proj.is_empty());
+                    assert!(arg2_proj.is_empty());
+                }
+                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(())
+}
+
+// Use internal API to find a function in a crate.
+fn get_item<'a>(
+    items: &'a stable_mir::CrateItems,
+    item: (DefKind, &str),
+) -> Option<&'a stable_mir::CrateItem> {
+    items.iter().find(|crate_item| {
+        crate_item.kind().to_string() == format!("{:?}", item.0) && crate_item.name() == item.1
+    })
+}
+
+/// This test will generate and analyze a dummy crate using the stable mir.
+/// For that, it will first write the dummy crate into a file.
+/// Then it will create a `StableMir` using custom arguments and then
+/// it will run the compiler.
+fn main() {
+    let path = "input.rs";
+    generate_input(&path).unwrap();
+    let args = vec![
+        "rustc".to_string(),
+        "--crate-type=lib".to_string(),
+        "--crate-name".to_string(),
+        CRATE_NAME.to_string(),
+        path.to_string(),
+    ];
+    run!(args, tcx, test_place_projections(tcx)).unwrap();
+}
+
+fn generate_input(path: &str) -> std::io::Result<()> {
+    let mut file = std::fs::File::create(path)?;
+    write!(
+        file,
+        r#"
+    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(())
+}