mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-25 00:03:43 +00:00
Auto merge of #117517 - klinvill:smir-projections, r=ouz-a
Add richer structure for Stable MIR Projections Resolves https://github.com/rust-lang/project-stable-mir/issues/49. Projections in Stable MIR are currently just strings. This PR replaces that representation with a richer structure, namely projections become vectors of `ProjectionElem`s, just as in MIR. The `ProjectionElem` enum is heavily based off of the MIR `ProjectionElem`. This PR is a draft since there are several outstanding issues to resolve, including: - How should `UserTypeProjection`s be represented in Stable MIR? In MIR, the projections are just a vector of `ProjectionElem<(),()>`, meaning `ProjectionElem`s that don't have Local or Type arguments (for `Index`, `Field`, etc. objects). Should `UserTypeProjection`s be represented this way in Stable MIR as well? Or is there a more user-friendly representation that wouldn't drag along all the `ProjectionElem` variants that presumably can't appear? - What is the expected behavior of a `Place`'s `ty` function? Should it resolve down the chain of projections so that something like `*_1.f` would return the type referenced by field `f`? - Tests should be added for `UserTypeProjection`
This commit is contained in:
commit
698fcc8219
@ -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) }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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")
|
||||
|
@ -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) {
|
||||
|
173
tests/ui-fulldeps/stable-mir/projections.rs
Normal file
173
tests/ui-fulldeps/stable-mir/projections.rs
Normal file
@ -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(())
|
||||
}
|
Loading…
Reference in New Issue
Block a user