summaryrefslogtreecommitdiffstats
path: root/compiler/stable_mir/src/mir
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/stable_mir/src/mir')
-rw-r--r--compiler/stable_mir/src/mir/alloc.rs83
-rw-r--r--compiler/stable_mir/src/mir/body.rs622
-rw-r--r--compiler/stable_mir/src/mir/mono.rs206
-rw-r--r--compiler/stable_mir/src/mir/pretty.rs483
-rw-r--r--compiler/stable_mir/src/mir/visit.rs78
5 files changed, 1397 insertions, 75 deletions
diff --git a/compiler/stable_mir/src/mir/alloc.rs b/compiler/stable_mir/src/mir/alloc.rs
new file mode 100644
index 000000000..c780042ff
--- /dev/null
+++ b/compiler/stable_mir/src/mir/alloc.rs
@@ -0,0 +1,83 @@
+//! This module provides methods to retrieve allocation information, such as static variables.
+use crate::mir::mono::{Instance, StaticDef};
+use crate::target::{Endian, MachineInfo};
+use crate::ty::{Allocation, Binder, ExistentialTraitRef, IndexedVal, Ty};
+use crate::{with, Error};
+use std::io::Read;
+
+/// An allocation in the SMIR global memory can be either a function pointer,
+/// a static, or a "real" allocation with some data in it.
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub enum GlobalAlloc {
+ /// The alloc ID is used as a function pointer.
+ Function(Instance),
+ /// This alloc ID points to a symbolic (not-reified) vtable.
+ /// The `None` trait ref is used to represent auto traits.
+ VTable(Ty, Option<Binder<ExistentialTraitRef>>),
+ /// The alloc ID points to a "lazy" static variable that did not get computed (yet).
+ /// This is also used to break the cycle in recursive statics.
+ Static(StaticDef),
+ /// The alloc ID points to memory.
+ Memory(Allocation),
+}
+
+impl From<AllocId> for GlobalAlloc {
+ fn from(value: AllocId) -> Self {
+ with(|cx| cx.global_alloc(value))
+ }
+}
+
+impl GlobalAlloc {
+ /// Retrieve the allocation id for a global allocation if it exists.
+ ///
+ /// For `[GlobalAlloc::VTable]`, this will return the allocation for the VTable of the given
+ /// type for the optional trait if the type implements the trait.
+ ///
+ /// This method will always return `None` for allocations other than `[GlobalAlloc::VTable]`.
+ pub fn vtable_allocation(&self) -> Option<AllocId> {
+ with(|cx| cx.vtable_allocation(self))
+ }
+}
+
+/// A unique identification number for each provenance
+#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
+pub struct AllocId(usize);
+
+impl IndexedVal for AllocId {
+ fn to_val(index: usize) -> Self {
+ AllocId(index)
+ }
+ fn to_index(&self) -> usize {
+ self.0
+ }
+}
+
+/// Utility function used to read an allocation data into a unassigned integer.
+pub(crate) fn read_target_uint(mut bytes: &[u8]) -> Result<u128, Error> {
+ let mut buf = [0u8; std::mem::size_of::<u128>()];
+ match MachineInfo::target_endianess() {
+ Endian::Little => {
+ bytes.read(&mut buf)?;
+ Ok(u128::from_le_bytes(buf))
+ }
+ Endian::Big => {
+ bytes.read(&mut buf[16 - bytes.len()..])?;
+ Ok(u128::from_be_bytes(buf))
+ }
+ }
+}
+
+/// Utility function used to read an allocation data into an assigned integer.
+pub(crate) fn read_target_int(mut bytes: &[u8]) -> Result<i128, Error> {
+ let mut buf = [0u8; std::mem::size_of::<i128>()];
+ match MachineInfo::target_endianess() {
+ Endian::Little => {
+ bytes.read(&mut buf)?;
+ Ok(i128::from_le_bytes(buf))
+ }
+ Endian::Big => {
+ bytes.read(&mut buf[16 - bytes.len()..])?;
+ Ok(i128::from_be_bytes(buf))
+ }
+ }
+}
diff --git a/compiler/stable_mir/src/mir/body.rs b/compiler/stable_mir/src/mir/body.rs
index 069337836..b8fd9370a 100644
--- a/compiler/stable_mir/src/mir/body.rs
+++ b/compiler/stable_mir/src/mir/body.rs
@@ -1,36 +1,59 @@
-use crate::ty::{AdtDef, ClosureDef, Const, CoroutineDef, GenericArgs, Movability, Region, Ty};
-use crate::Opaque;
-use crate::Span;
-
+use crate::mir::pretty::{function_body, pretty_statement, pretty_terminator};
+use crate::ty::{
+ AdtDef, ClosureDef, Const, CoroutineDef, GenericArgs, Movability, Region, RigidTy, Ty, TyKind,
+ VariantIdx,
+};
+use crate::{Error, Opaque, Span, Symbol};
+use std::io;
/// The SMIR representation of a single function.
#[derive(Clone, Debug)]
pub struct Body {
pub blocks: Vec<BasicBlock>,
- // Declarations of locals within the function.
- //
- // The first local is the return value pointer, followed by `arg_count`
- // locals for the function arguments, followed by any user-declared
- // variables and temporaries.
+ /// Declarations of locals within the function.
+ ///
+ /// The first local is the return value pointer, followed by `arg_count`
+ /// locals for the function arguments, followed by any user-declared
+ /// variables and temporaries.
pub(super) locals: LocalDecls,
- // The number of arguments this function takes.
+ /// The number of arguments this function takes.
pub(super) arg_count: usize,
+
+ /// Debug information pertaining to user variables, including captures.
+ pub var_debug_info: Vec<VarDebugInfo>,
+
+ /// Mark an argument (which must be a tuple) as getting passed as its individual components.
+ ///
+ /// This is used for the "rust-call" ABI such as closures.
+ pub(super) spread_arg: Option<Local>,
+
+ /// The span that covers the entire function body.
+ pub span: Span,
}
+pub type BasicBlockIdx = usize;
+
impl Body {
/// Constructs a `Body`.
///
/// A constructor is required to build a `Body` from outside the crate
/// because the `arg_count` and `locals` fields are private.
- pub fn new(blocks: Vec<BasicBlock>, locals: LocalDecls, arg_count: usize) -> Self {
+ pub fn new(
+ blocks: Vec<BasicBlock>,
+ locals: LocalDecls,
+ arg_count: usize,
+ var_debug_info: Vec<VarDebugInfo>,
+ spread_arg: Option<Local>,
+ span: Span,
+ ) -> Self {
// If locals doesn't contain enough entries, it can lead to panics in
// `ret_local`, `arg_locals`, and `inner_locals`.
assert!(
locals.len() > arg_count,
"A Body must contain at least a local for the return value and each of the function's arguments"
);
- Self { blocks, locals, arg_count }
+ Self { blocks, locals, arg_count, var_debug_info, spread_arg, span }
}
/// Return local that holds this function's return value.
@@ -56,6 +79,44 @@ impl Body {
pub fn locals(&self) -> &[LocalDecl] {
&self.locals
}
+
+ /// Get the local declaration for this local.
+ pub fn local_decl(&self, local: Local) -> Option<&LocalDecl> {
+ self.locals.get(local)
+ }
+
+ /// Get an iterator for all local declarations.
+ pub fn local_decls(&self) -> impl Iterator<Item = (Local, &LocalDecl)> {
+ self.locals.iter().enumerate()
+ }
+
+ pub fn dump<W: io::Write>(&self, w: &mut W) -> io::Result<()> {
+ writeln!(w, "{}", function_body(self))?;
+ self.blocks
+ .iter()
+ .enumerate()
+ .map(|(index, block)| -> io::Result<()> {
+ writeln!(w, " bb{}: {{", index)?;
+ let _ = block
+ .statements
+ .iter()
+ .map(|statement| -> io::Result<()> {
+ writeln!(w, "{}", pretty_statement(&statement.kind))?;
+ Ok(())
+ })
+ .collect::<Vec<_>>();
+ pretty_terminator(&block.terminator.kind, w)?;
+ writeln!(w, "").unwrap();
+ writeln!(w, " }}").unwrap();
+ Ok(())
+ })
+ .collect::<Result<Vec<_>, _>>()?;
+ Ok(())
+ }
+
+ pub fn spread_arg(&self) -> Option<Local> {
+ self.spread_arg
+ }
}
type LocalDecls = Vec<LocalDecl>;
@@ -64,9 +125,10 @@ type LocalDecls = Vec<LocalDecl>;
pub struct LocalDecl {
pub ty: Ty,
pub span: Span,
+ pub mutability: Mutability,
}
-#[derive(Clone, Debug)]
+#[derive(Clone, PartialEq, Eq, Debug)]
pub struct BasicBlock {
pub statements: Vec<Statement>,
pub terminator: Terminator,
@@ -78,15 +140,22 @@ pub struct Terminator {
pub span: Span,
}
+impl Terminator {
+ pub fn successors(&self) -> Successors {
+ self.kind.successors()
+ }
+}
+
+pub type Successors = Vec<BasicBlockIdx>;
+
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum TerminatorKind {
Goto {
- target: usize,
+ target: BasicBlockIdx,
},
SwitchInt {
discr: Operand,
- targets: Vec<SwitchTarget>,
- otherwise: usize,
+ targets: SwitchTargets,
},
Resume,
Abort,
@@ -94,34 +163,81 @@ pub enum TerminatorKind {
Unreachable,
Drop {
place: Place,
- target: usize,
+ target: BasicBlockIdx,
unwind: UnwindAction,
},
Call {
func: Operand,
args: Vec<Operand>,
destination: Place,
- target: Option<usize>,
+ target: Option<BasicBlockIdx>,
unwind: UnwindAction,
},
Assert {
cond: Operand,
expected: bool,
msg: AssertMessage,
- target: usize,
+ target: BasicBlockIdx,
unwind: UnwindAction,
},
- CoroutineDrop,
InlineAsm {
template: String,
operands: Vec<InlineAsmOperand>,
options: String,
line_spans: String,
- destination: Option<usize>,
+ destination: Option<BasicBlockIdx>,
unwind: UnwindAction,
},
}
+impl TerminatorKind {
+ pub fn successors(&self) -> Successors {
+ use self::TerminatorKind::*;
+ match *self {
+ Call { target: Some(t), unwind: UnwindAction::Cleanup(u), .. }
+ | Drop { target: t, unwind: UnwindAction::Cleanup(u), .. }
+ | Assert { target: t, unwind: UnwindAction::Cleanup(u), .. }
+ | InlineAsm { destination: Some(t), unwind: UnwindAction::Cleanup(u), .. } => {
+ vec![t, u]
+ }
+ Goto { target: t }
+ | Call { target: None, unwind: UnwindAction::Cleanup(t), .. }
+ | Call { target: Some(t), unwind: _, .. }
+ | Drop { target: t, unwind: _, .. }
+ | Assert { target: t, unwind: _, .. }
+ | InlineAsm { destination: None, unwind: UnwindAction::Cleanup(t), .. }
+ | InlineAsm { destination: Some(t), unwind: _, .. } => {
+ vec![t]
+ }
+
+ Return
+ | Resume
+ | Abort
+ | Unreachable
+ | Call { target: None, unwind: _, .. }
+ | InlineAsm { destination: None, unwind: _, .. } => {
+ vec![]
+ }
+ SwitchInt { ref targets, .. } => targets.all_targets(),
+ }
+ }
+
+ pub fn unwind(&self) -> Option<&UnwindAction> {
+ match *self {
+ TerminatorKind::Goto { .. }
+ | TerminatorKind::Return
+ | TerminatorKind::Unreachable
+ | TerminatorKind::Resume
+ | TerminatorKind::Abort
+ | TerminatorKind::SwitchInt { .. } => None,
+ TerminatorKind::Call { ref unwind, .. }
+ | TerminatorKind::Assert { ref unwind, .. }
+ | TerminatorKind::Drop { ref unwind, .. }
+ | TerminatorKind::InlineAsm { ref unwind, .. } => Some(unwind),
+ }
+ }
+}
+
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct InlineAsmOperand {
pub in_value: Option<Operand>,
@@ -131,12 +247,12 @@ pub struct InlineAsmOperand {
pub raw_rpr: String,
}
-#[derive(Clone, Debug, Eq, PartialEq)]
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum UnwindAction {
Continue,
Unreachable,
Terminate,
- Cleanup(usize),
+ Cleanup(BasicBlockIdx),
}
#[derive(Clone, Debug, Eq, PartialEq)]
@@ -151,7 +267,58 @@ pub enum AssertMessage {
MisalignedPointerDereference { required: Operand, found: Operand },
}
-#[derive(Clone, Debug, Eq, PartialEq)]
+impl AssertMessage {
+ pub fn description(&self) -> Result<&'static str, Error> {
+ match self {
+ AssertMessage::Overflow(BinOp::Add, _, _) => Ok("attempt to add with overflow"),
+ AssertMessage::Overflow(BinOp::Sub, _, _) => Ok("attempt to subtract with overflow"),
+ AssertMessage::Overflow(BinOp::Mul, _, _) => Ok("attempt to multiply with overflow"),
+ AssertMessage::Overflow(BinOp::Div, _, _) => Ok("attempt to divide with overflow"),
+ AssertMessage::Overflow(BinOp::Rem, _, _) => {
+ Ok("attempt to calculate the remainder with overflow")
+ }
+ AssertMessage::OverflowNeg(_) => Ok("attempt to negate with overflow"),
+ AssertMessage::Overflow(BinOp::Shr, _, _) => Ok("attempt to shift right with overflow"),
+ AssertMessage::Overflow(BinOp::Shl, _, _) => Ok("attempt to shift left with overflow"),
+ AssertMessage::Overflow(op, _, _) => Err(error!("`{:?}` cannot overflow", op)),
+ AssertMessage::DivisionByZero(_) => Ok("attempt to divide by zero"),
+ AssertMessage::RemainderByZero(_) => {
+ Ok("attempt to calculate the remainder with a divisor of zero")
+ }
+ AssertMessage::ResumedAfterReturn(CoroutineKind::Coroutine) => {
+ Ok("coroutine resumed after completion")
+ }
+ AssertMessage::ResumedAfterReturn(CoroutineKind::Async(_)) => {
+ Ok("`async fn` resumed after completion")
+ }
+ AssertMessage::ResumedAfterReturn(CoroutineKind::Gen(_)) => {
+ Ok("`async gen fn` resumed after completion")
+ }
+ AssertMessage::ResumedAfterReturn(CoroutineKind::AsyncGen(_)) => {
+ Ok("`gen fn` should just keep returning `AssertMessage::None` after completion")
+ }
+ AssertMessage::ResumedAfterPanic(CoroutineKind::Coroutine) => {
+ Ok("coroutine resumed after panicking")
+ }
+ AssertMessage::ResumedAfterPanic(CoroutineKind::Async(_)) => {
+ Ok("`async fn` resumed after panicking")
+ }
+ AssertMessage::ResumedAfterPanic(CoroutineKind::Gen(_)) => {
+ Ok("`async gen fn` resumed after panicking")
+ }
+ AssertMessage::ResumedAfterPanic(CoroutineKind::AsyncGen(_)) => {
+ Ok("`gen fn` should just keep returning `AssertMessage::None` after panicking")
+ }
+
+ AssertMessage::BoundsCheck { .. } => Ok("index out of bounds"),
+ AssertMessage::MisalignedPointerDereference { .. } => {
+ Ok("misaligned pointer dereference")
+ }
+ }
+ }
+}
+
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum BinOp {
Add,
AddUnchecked,
@@ -177,7 +344,47 @@ pub enum BinOp {
Offset,
}
-#[derive(Clone, Debug, Eq, PartialEq)]
+impl BinOp {
+ /// Return the type of this operation for the given input Ty.
+ /// This function does not perform type checking, and it currently doesn't handle SIMD.
+ pub fn ty(&self, lhs_ty: Ty, rhs_ty: Ty) -> Ty {
+ match self {
+ BinOp::Add
+ | BinOp::AddUnchecked
+ | BinOp::Sub
+ | BinOp::SubUnchecked
+ | BinOp::Mul
+ | BinOp::MulUnchecked
+ | BinOp::Div
+ | BinOp::Rem
+ | BinOp::BitXor
+ | BinOp::BitAnd
+ | BinOp::BitOr => {
+ assert_eq!(lhs_ty, rhs_ty);
+ assert!(lhs_ty.kind().is_primitive());
+ lhs_ty
+ }
+ BinOp::Shl | BinOp::ShlUnchecked | BinOp::Shr | BinOp::ShrUnchecked => {
+ assert!(lhs_ty.kind().is_primitive());
+ assert!(rhs_ty.kind().is_primitive());
+ lhs_ty
+ }
+ BinOp::Offset => {
+ assert!(lhs_ty.kind().is_raw_ptr());
+ assert!(rhs_ty.kind().is_integral());
+ lhs_ty
+ }
+ BinOp::Eq | BinOp::Lt | BinOp::Le | BinOp::Ne | BinOp::Ge | BinOp::Gt => {
+ assert_eq!(lhs_ty, rhs_ty);
+ let lhs_kind = lhs_ty.kind();
+ assert!(lhs_kind.is_primitive() || lhs_kind.is_raw_ptr() || lhs_kind.is_fn_ptr());
+ Ty::bool_ty()
+ }
+ }
+ }
+}
+
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum UnOp {
Not,
Neg,
@@ -188,9 +395,10 @@ pub enum CoroutineKind {
Async(CoroutineSource),
Coroutine,
Gen(CoroutineSource),
+ AsyncGen(CoroutineSource),
}
-#[derive(Clone, Debug, Eq, PartialEq)]
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum CoroutineSource {
Block,
Closure,
@@ -214,7 +422,7 @@ pub enum FakeReadCause {
}
/// Describes what kind of retag is to be performed
-#[derive(Clone, Debug, Eq, PartialEq, Hash)]
+#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum RetagKind {
FnEntry,
TwoPhase,
@@ -222,7 +430,7 @@ pub enum RetagKind {
Default,
}
-#[derive(Clone, Debug, Eq, PartialEq, Hash)]
+#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum Variance {
Covariant,
Invariant,
@@ -378,6 +586,63 @@ pub enum Rvalue {
Use(Operand),
}
+impl Rvalue {
+ pub fn ty(&self, locals: &[LocalDecl]) -> Result<Ty, Error> {
+ match self {
+ Rvalue::Use(operand) => operand.ty(locals),
+ Rvalue::Repeat(operand, count) => {
+ Ok(Ty::new_array_with_const_len(operand.ty(locals)?, count.clone()))
+ }
+ Rvalue::ThreadLocalRef(did) => Ok(did.ty()),
+ Rvalue::Ref(reg, bk, place) => {
+ let place_ty = place.ty(locals)?;
+ Ok(Ty::new_ref(reg.clone(), place_ty, bk.to_mutable_lossy()))
+ }
+ Rvalue::AddressOf(mutability, place) => {
+ let place_ty = place.ty(locals)?;
+ Ok(Ty::new_ptr(place_ty, *mutability))
+ }
+ Rvalue::Len(..) => Ok(Ty::usize_ty()),
+ Rvalue::Cast(.., ty) => Ok(*ty),
+ Rvalue::BinaryOp(op, lhs, rhs) => {
+ let lhs_ty = lhs.ty(locals)?;
+ let rhs_ty = rhs.ty(locals)?;
+ Ok(op.ty(lhs_ty, rhs_ty))
+ }
+ Rvalue::CheckedBinaryOp(op, lhs, rhs) => {
+ let lhs_ty = lhs.ty(locals)?;
+ let rhs_ty = rhs.ty(locals)?;
+ let ty = op.ty(lhs_ty, rhs_ty);
+ Ok(Ty::new_tuple(&[ty, Ty::bool_ty()]))
+ }
+ Rvalue::UnaryOp(UnOp::Not | UnOp::Neg, operand) => operand.ty(locals),
+ Rvalue::Discriminant(place) => {
+ let place_ty = place.ty(locals)?;
+ place_ty
+ .kind()
+ .discriminant_ty()
+ .ok_or_else(|| error!("Expected a `RigidTy` but found: {place_ty:?}"))
+ }
+ Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(..), _) => {
+ Ok(Ty::usize_ty())
+ }
+ Rvalue::Aggregate(ak, ops) => match *ak {
+ AggregateKind::Array(ty) => Ty::try_new_array(ty, ops.len() as u64),
+ AggregateKind::Tuple => Ok(Ty::new_tuple(
+ &ops.iter().map(|op| op.ty(locals)).collect::<Result<Vec<_>, _>>()?,
+ )),
+ AggregateKind::Adt(def, _, ref args, _, _) => Ok(def.ty_with_args(args)),
+ AggregateKind::Closure(def, ref args) => Ok(Ty::new_closure(def, args.clone())),
+ AggregateKind::Coroutine(def, ref args, mov) => {
+ Ok(Ty::new_coroutine(def, args.clone(), mov))
+ }
+ },
+ Rvalue::ShallowInitBox(_, ty) => Ok(Ty::new_box(*ty)),
+ Rvalue::CopyForDeref(place) => place.ty(locals),
+ }
+ }
+}
+
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum AggregateKind {
Array(Ty),
@@ -398,23 +663,188 @@ 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>,
+}
+
+impl From<Local> for Place {
+ fn from(local: Local) -> Self {
+ Place { local, projection: vec![] }
+ }
+}
+
+/// Debug information pertaining to a user variable.
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct VarDebugInfo {
+ /// The variable name.
+ pub name: Symbol,
+
+ /// Source info of the user variable, including the scope
+ /// within which the variable is visible (to debuginfo).
+ pub source_info: SourceInfo,
+
+ /// The user variable's data is split across several fragments,
+ /// each described by a `VarDebugInfoFragment`.
+ pub composite: Option<VarDebugInfoFragment>,
+
+ /// Where the data for this user variable is to be found.
+ pub value: VarDebugInfoContents,
+
+ /// When present, indicates what argument number this variable is in the function that it
+ /// originated from (starting from 1). Note, if MIR inlining is enabled, then this is the
+ /// argument number in the original function before it was inlined.
+ pub argument_index: Option<u16>,
+}
+
+impl VarDebugInfo {
+ /// Return a local variable if this info is related to one.
+ pub fn local(&self) -> Option<Local> {
+ match &self.value {
+ VarDebugInfoContents::Place(place) if place.projection.is_empty() => Some(place.local),
+ VarDebugInfoContents::Place(_) | VarDebugInfoContents::Const(_) => None,
+ }
+ }
+
+ /// Return a constant if this info is related to one.
+ pub fn constant(&self) -> Option<&ConstOperand> {
+ match &self.value {
+ VarDebugInfoContents::Place(_) => None,
+ VarDebugInfoContents::Const(const_op) => Some(const_op),
+ }
+ }
+}
+
+pub type SourceScope = u32;
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct SourceInfo {
+ pub span: Span,
+ pub scope: SourceScope,
+}
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct VarDebugInfoFragment {
+ pub ty: Ty,
+ pub projection: Vec<ProjectionElem>,
+}
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub enum VarDebugInfoContents {
+ Place(Place),
+ Const(ConstOperand),
+}
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct ConstOperand {
+ pub span: Span,
+ pub user_ty: Option<UserTypeAnnotationIndex>,
+ pub const_: Const,
+}
+
+// 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;
-type FieldIdx = usize;
-
-/// The source-order index of a variant in a type.
-pub type VariantIdx = usize;
+/// 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`.
+pub type FieldIdx = usize;
type UserTypeAnnotationIndex = usize;
@@ -425,13 +855,45 @@ pub struct Constant {
pub literal: Const,
}
+/// The possible branch sites of a [TerminatorKind::SwitchInt].
#[derive(Clone, Debug, Eq, PartialEq)]
-pub struct SwitchTarget {
- pub value: u128,
- pub target: usize,
+pub struct SwitchTargets {
+ /// The conditional branches where the first element represents the value that guards this
+ /// branch, and the second element is the branch target.
+ branches: Vec<(u128, BasicBlockIdx)>,
+ /// The `otherwise` branch which will be taken in case none of the conditional branches are
+ /// satisfied.
+ otherwise: BasicBlockIdx,
}
-#[derive(Clone, Debug, Eq, PartialEq)]
+impl SwitchTargets {
+ /// All possible targets including the `otherwise` target.
+ pub fn all_targets(&self) -> Successors {
+ self.branches.iter().map(|(_, target)| *target).chain(Some(self.otherwise)).collect()
+ }
+
+ /// The `otherwise` branch target.
+ pub fn otherwise(&self) -> BasicBlockIdx {
+ self.otherwise
+ }
+
+ /// The conditional targets which are only taken if the pattern matches the given value.
+ pub fn branches(&self) -> impl Iterator<Item = (u128, BasicBlockIdx)> + '_ {
+ self.branches.iter().copied()
+ }
+
+ /// The number of targets including `otherwise`.
+ pub fn len(&self) -> usize {
+ self.branches.len() + 1
+ }
+
+ /// Create a new SwitchTargets from the given branches and `otherwise` target.
+ pub fn new(branches: Vec<(u128, BasicBlockIdx)>, otherwise: BasicBlockIdx) -> SwitchTargets {
+ SwitchTargets { branches, otherwise }
+ }
+}
+
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum BorrowKind {
/// Data must be immutable and is aliasable.
Shared,
@@ -449,14 +911,25 @@ pub enum BorrowKind {
},
}
-#[derive(Clone, Debug, Eq, PartialEq)]
+impl BorrowKind {
+ pub fn to_mutable_lossy(self) -> Mutability {
+ match self {
+ BorrowKind::Mut { .. } => Mutability::Mut,
+ BorrowKind::Shared => Mutability::Not,
+ // FIXME: There's no type corresponding to a shallow borrow, so use `&` as an approximation.
+ BorrowKind::Fake => Mutability::Not,
+ }
+ }
+}
+
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum MutBorrowKind {
Default,
TwoPhaseBorrow,
ClosureCapture,
}
-#[derive(Clone, Debug, PartialEq, Eq)]
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum Mutability {
Not,
Mut,
@@ -468,7 +941,7 @@ pub enum Safety {
Normal,
}
-#[derive(Clone, Debug, Eq, PartialEq)]
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum PointerCoercion {
/// Go from a fn-item type to a fn-pointer type.
ReifyFnPointer,
@@ -495,7 +968,7 @@ pub enum PointerCoercion {
Unsize,
}
-#[derive(Clone, Debug, Eq, PartialEq)]
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum CastKind {
PointerExposeAddress,
PointerFromExposedAddress,
@@ -521,10 +994,16 @@ pub enum NullOp {
}
impl Operand {
- pub fn ty(&self, locals: &[LocalDecl]) -> Ty {
+ /// Get the type of an operand relative to the local declaration.
+ ///
+ /// In order to retrieve the correct type, the `locals` argument must match the list of all
+ /// locals from the function body where this operand originates from.
+ ///
+ /// Errors indicate a malformed operand or incompatible locals list.
+ pub fn ty(&self, locals: &[LocalDecl]) -> Result<Ty, Error> {
match self {
Operand::Copy(place) | Operand::Move(place) => place.ty(locals),
- Operand::Constant(c) => c.ty(),
+ Operand::Constant(c) => Ok(c.ty()),
}
}
}
@@ -536,8 +1015,57 @@ impl Constant {
}
impl Place {
- pub fn ty(&self, locals: &[LocalDecl]) -> Ty {
- let _start_ty = locals[self.local].ty;
- todo!("Implement projection")
+ /// Resolve down the chain of projections to get the type referenced at the end of it.
+ /// E.g.:
+ /// Calling `ty()` on `var.field` should return the type of `field`.
+ ///
+ /// In order to retrieve the correct type, the `locals` argument must match the list of all
+ /// locals from the function body where this place originates from.
+ pub fn ty(&self, locals: &[LocalDecl]) -> Result<Ty, Error> {
+ let start_ty = locals[self.local].ty;
+ self.projection.iter().fold(Ok(start_ty), |place_ty, elem| {
+ let ty = place_ty?;
+ match elem {
+ ProjectionElem::Deref => Self::deref_ty(ty),
+ ProjectionElem::Field(_idx, fty) => Ok(*fty),
+ ProjectionElem::Index(_) | ProjectionElem::ConstantIndex { .. } => {
+ Self::index_ty(ty)
+ }
+ ProjectionElem::Subslice { from, to, from_end } => {
+ Self::subslice_ty(ty, from, to, from_end)
+ }
+ ProjectionElem::Downcast(_) => Ok(ty),
+ ProjectionElem::OpaqueCast(ty) | ProjectionElem::Subtype(ty) => Ok(*ty),
+ }
+ })
+ }
+
+ fn index_ty(ty: Ty) -> Result<Ty, Error> {
+ ty.kind().builtin_index().ok_or_else(|| error!("Cannot index non-array type: {ty:?}"))
+ }
+
+ fn subslice_ty(ty: Ty, from: &u64, to: &u64, from_end: &bool) -> Result<Ty, Error> {
+ let ty_kind = ty.kind();
+ match ty_kind {
+ TyKind::RigidTy(RigidTy::Slice(..)) => Ok(ty),
+ TyKind::RigidTy(RigidTy::Array(inner, _)) if !from_end => Ty::try_new_array(
+ inner,
+ to.checked_sub(*from).ok_or_else(|| error!("Subslice overflow: {from}..{to}"))?,
+ ),
+ TyKind::RigidTy(RigidTy::Array(inner, size)) => {
+ let size = size.eval_target_usize()?;
+ let len = size - from - to;
+ Ty::try_new_array(inner, len)
+ }
+ _ => Err(Error(format!("Cannot subslice non-array type: `{ty_kind:?}`"))),
+ }
+ }
+
+ fn deref_ty(ty: Ty) -> Result<Ty, Error> {
+ let deref_ty = ty
+ .kind()
+ .builtin_deref(true)
+ .ok_or_else(|| error!("Cannot dereference type: {ty:?}"))?;
+ Ok(deref_ty.ty)
}
}
diff --git a/compiler/stable_mir/src/mir/mono.rs b/compiler/stable_mir/src/mir/mono.rs
index 8f5333498..6c791ae85 100644
--- a/compiler/stable_mir/src/mir/mono.rs
+++ b/compiler/stable_mir/src/mir/mono.rs
@@ -1,16 +1,18 @@
+use crate::abi::FnAbi;
+use crate::crate_def::CrateDef;
use crate::mir::Body;
-use crate::ty::{FnDef, GenericArgs, IndexedVal, Ty};
-use crate::{with, CrateItem, DefId, Error, Opaque};
-use std::fmt::Debug;
+use crate::ty::{Allocation, ClosureDef, ClosureKind, FnDef, GenericArgs, IndexedVal, Ty};
+use crate::{with, CrateItem, DefId, Error, ItemKind, Opaque, Symbol};
+use std::fmt::{Debug, Formatter};
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum MonoItem {
Fn(Instance),
Static(StaticDef),
GlobalAsm(Opaque),
}
-#[derive(Copy, Clone, Debug)]
+#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct Instance {
/// The type of instance.
pub kind: InstanceKind,
@@ -19,33 +21,75 @@ pub struct Instance {
pub def: InstanceDef,
}
-#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum InstanceKind {
/// A user defined item.
Item,
/// A compiler intrinsic function.
Intrinsic,
/// A virtual function definition stored in a VTable.
- Virtual,
+ /// The `idx` field indicates the position in the VTable for this instance.
+ Virtual { idx: usize },
/// A compiler generated shim.
Shim,
}
impl Instance {
+ /// Get the arguments this instance was instantiated with.
+ pub fn args(&self) -> GenericArgs {
+ with(|cx| cx.instance_args(self.def))
+ }
+
/// Get the body of an Instance. The body will be eagerly monomorphized.
- pub fn body(&self) -> Body {
+ pub fn body(&self) -> Option<Body> {
with(|context| context.instance_body(self.def))
}
+ /// Check whether this instance has a body available.
+ ///
+ /// This call is much cheaper than `instance.body().is_some()`, since it doesn't try to build
+ /// the StableMIR body.
+ pub fn has_body(&self) -> bool {
+ with(|cx| cx.has_body(self.def.def_id()))
+ }
+
+ pub fn is_foreign_item(&self) -> bool {
+ with(|cx| cx.is_foreign_item(self.def.def_id()))
+ }
+
/// Get the instance type with generic substitutions applied and lifetimes erased.
pub fn ty(&self) -> Ty {
with(|context| context.instance_ty(self.def))
}
- pub fn mangled_name(&self) -> String {
+ /// Retrieve information about this instance binary interface.
+ pub fn fn_abi(&self) -> Result<FnAbi, Error> {
+ with(|cx| cx.instance_abi(self.def))
+ }
+
+ /// Retrieve the instance's mangled name used for calling the given instance.
+ ///
+ /// This will also look up the correct name of instances from upstream crates.
+ pub fn mangled_name(&self) -> Symbol {
with(|context| context.instance_mangled_name(self.def))
}
+ /// Retrieve the instance name for diagnostic messages.
+ ///
+ /// This will return the specialized name, e.g., `std::vec::Vec<u8>::new`.
+ pub fn name(&self) -> Symbol {
+ with(|context| context.instance_name(self.def, false))
+ }
+
+ /// Return a trimmed name of the given instance including its args.
+ ///
+ /// If a symbol name can only be imported from one place for a type, and as
+ /// long as it was not glob-imported anywhere in the current crate, we trim its
+ /// path and print only the name.
+ pub fn trimmed_name(&self) -> Symbol {
+ with(|context| context.instance_name(self.def, true))
+ }
+
/// Resolve an instance starting from a function definition and generic arguments.
pub fn resolve(def: FnDef, args: &GenericArgs) -> Result<Instance, crate::Error> {
with(|context| {
@@ -54,6 +98,64 @@ impl Instance {
})
})
}
+
+ /// Resolve the drop in place for a given type.
+ pub fn resolve_drop_in_place(ty: Ty) -> Instance {
+ with(|cx| cx.resolve_drop_in_place(ty))
+ }
+
+ /// Resolve an instance for a given function pointer.
+ pub fn resolve_for_fn_ptr(def: FnDef, args: &GenericArgs) -> Result<Instance, crate::Error> {
+ with(|context| {
+ context.resolve_for_fn_ptr(def, args).ok_or_else(|| {
+ crate::Error::new(format!("Failed to resolve `{def:?}` with `{args:?}`"))
+ })
+ })
+ }
+
+ /// Resolve a closure with the expected kind.
+ pub fn resolve_closure(
+ def: ClosureDef,
+ args: &GenericArgs,
+ kind: ClosureKind,
+ ) -> Result<Instance, crate::Error> {
+ with(|context| {
+ context.resolve_closure(def, args, kind).ok_or_else(|| {
+ crate::Error::new(format!("Failed to resolve `{def:?}` with `{args:?}`"))
+ })
+ })
+ }
+
+ /// Check whether this instance is an empty shim.
+ ///
+ /// Allow users to check if this shim can be ignored when called directly.
+ ///
+ /// We have decided not to export different types of Shims to StableMIR users, however, this
+ /// is a query that can be very helpful for users when processing DropGlue.
+ ///
+ /// When generating code for a Drop terminator, users can ignore an empty drop glue.
+ /// These shims are only needed to generate a valid Drop call done via VTable.
+ pub fn is_empty_shim(&self) -> bool {
+ self.kind == InstanceKind::Shim && with(|cx| cx.is_empty_drop_shim(self.def))
+ }
+
+ /// Try to constant evaluate the instance into a constant with the given type.
+ ///
+ /// This can be used to retrieve a constant that represents an intrinsic return such as
+ /// `type_id`.
+ pub fn try_const_eval(&self, const_ty: Ty) -> Result<Allocation, Error> {
+ with(|cx| cx.eval_instance(self.def, const_ty))
+ }
+}
+
+impl Debug for Instance {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("Instance")
+ .field("kind", &self.kind)
+ .field("def", &self.mangled_name())
+ .field("args", &self.args())
+ .finish()
+ }
}
/// Try to convert a crate item into an instance.
@@ -63,8 +165,9 @@ impl TryFrom<CrateItem> for Instance {
fn try_from(item: CrateItem) -> Result<Self, Self::Error> {
with(|context| {
- if !context.requires_monomorphization(item.0) {
- Ok(context.mono_instance(item))
+ let def_id = item.def_id();
+ if !context.requires_monomorphization(def_id) {
+ Ok(context.mono_instance(def_id))
} else {
Err(Error::new("Item requires monomorphization".to_string()))
}
@@ -78,19 +181,86 @@ impl TryFrom<Instance> for CrateItem {
type Error = crate::Error;
fn try_from(value: Instance) -> Result<Self, Self::Error> {
- if value.kind == InstanceKind::Item {
- Ok(CrateItem(with(|context| context.instance_def_id(value.def))))
+ with(|context| {
+ if value.kind == InstanceKind::Item && context.has_body(value.def.def_id()) {
+ Ok(CrateItem(context.instance_def_id(value.def)))
+ } else {
+ Err(Error::new(format!("Item kind `{:?}` cannot be converted", value.kind)))
+ }
+ })
+ }
+}
+
+impl From<Instance> for MonoItem {
+ fn from(value: Instance) -> Self {
+ MonoItem::Fn(value)
+ }
+}
+
+impl From<StaticDef> for MonoItem {
+ fn from(value: StaticDef) -> Self {
+ MonoItem::Static(value)
+ }
+}
+
+impl From<StaticDef> for CrateItem {
+ fn from(value: StaticDef) -> Self {
+ CrateItem(value.0)
+ }
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+pub struct InstanceDef(usize);
+
+impl CrateDef for InstanceDef {
+ fn def_id(&self) -> DefId {
+ with(|context| context.instance_def_id(*self))
+ }
+}
+
+crate_def! {
+ /// Holds information about a static variable definition.
+ pub StaticDef;
+}
+
+impl TryFrom<CrateItem> for StaticDef {
+ type Error = crate::Error;
+
+ fn try_from(value: CrateItem) -> Result<Self, Self::Error> {
+ if matches!(value.kind(), ItemKind::Static) {
+ Ok(StaticDef(value.0))
} else {
- Err(Error::new(format!("Item kind `{:?}` cannot be converted", value.kind)))
+ Err(Error::new(format!("Expected a static item, but found: {value:?}")))
}
}
}
-#[derive(Clone, Copy, Debug, PartialEq, Eq)]
-pub struct InstanceDef(usize);
+impl TryFrom<Instance> for StaticDef {
+ type Error = crate::Error;
+
+ fn try_from(value: Instance) -> Result<Self, Self::Error> {
+ StaticDef::try_from(CrateItem::try_from(value)?)
+ }
+}
-#[derive(Clone, Copy, PartialEq, Eq, Debug)]
-pub struct StaticDef(pub DefId);
+impl From<StaticDef> for Instance {
+ fn from(value: StaticDef) -> Self {
+ // A static definition should always be convertible to an instance.
+ with(|cx| cx.mono_instance(value.def_id()))
+ }
+}
+
+impl StaticDef {
+ /// Return the type of this static definition.
+ pub fn ty(&self) -> Ty {
+ with(|cx| cx.def_ty(self.0))
+ }
+
+ /// Evaluate a static's initializer, returning the allocation of the initializer's memory.
+ pub fn eval_initializer(&self) -> Result<Allocation, Error> {
+ with(|cx| cx.eval_static_initializer(*self))
+ }
+}
impl IndexedVal for InstanceDef {
fn to_val(index: usize) -> Self {
diff --git a/compiler/stable_mir/src/mir/pretty.rs b/compiler/stable_mir/src/mir/pretty.rs
new file mode 100644
index 000000000..8b7b488d3
--- /dev/null
+++ b/compiler/stable_mir/src/mir/pretty.rs
@@ -0,0 +1,483 @@
+use crate::crate_def::CrateDef;
+use crate::mir::{Operand, Rvalue, StatementKind, UnwindAction};
+use crate::ty::{DynKind, FloatTy, IntTy, RigidTy, TyKind, UintTy};
+use crate::{with, Body, CrateItem, Mutability};
+use std::io::Write;
+use std::{io, iter};
+
+use super::{AssertMessage, BinOp, TerminatorKind};
+
+pub fn function_name(item: CrateItem) -> String {
+ let mut pretty_name = String::new();
+ let body = item.body();
+ pretty_name.push_str("fn ");
+ pretty_name.push_str(item.name().as_str());
+ if body.arg_locals().is_empty() {
+ pretty_name.push_str("()");
+ } else {
+ pretty_name.push_str("(");
+ }
+ body.arg_locals().iter().enumerate().for_each(|(index, local)| {
+ pretty_name.push_str(format!("_{}: ", index).as_str());
+ pretty_name.push_str(&pretty_ty(local.ty.kind()));
+ });
+ if !body.arg_locals().is_empty() {
+ pretty_name.push_str(")");
+ }
+ let return_local = body.ret_local();
+ pretty_name.push_str(" -> ");
+ pretty_name.push_str(&pretty_ty(return_local.ty.kind()));
+ pretty_name.push_str(" {");
+ pretty_name
+}
+
+pub fn function_body(body: &Body) -> String {
+ let mut pretty_body = String::new();
+ body.inner_locals().iter().enumerate().for_each(|(index, local)| {
+ pretty_body.push_str(" ");
+ pretty_body.push_str(format!("let {}", ret_mutability(&local.mutability)).as_str());
+ pretty_body.push_str(format!("_{}: ", index).as_str());
+ pretty_body.push_str(format!("{}", pretty_ty(local.ty.kind())).as_str());
+ pretty_body.push_str(";\n");
+ });
+ pretty_body.push_str("}");
+ pretty_body
+}
+
+pub fn ret_mutability(mutability: &Mutability) -> String {
+ match mutability {
+ Mutability::Not => "".to_string(),
+ Mutability::Mut => "mut ".to_string(),
+ }
+}
+
+pub fn pretty_statement(statement: &StatementKind) -> String {
+ let mut pretty = String::new();
+ match statement {
+ StatementKind::Assign(place, rval) => {
+ pretty.push_str(format!(" _{} = ", place.local).as_str());
+ pretty.push_str(format!("{}", &pretty_rvalue(rval)).as_str());
+ }
+ // FIXME: Add rest of the statements
+ StatementKind::FakeRead(_, _) => {
+ return String::from("StatementKind::FakeRead:Unimplemented");
+ }
+ StatementKind::SetDiscriminant { .. } => {
+ return String::from("StatementKind::SetDiscriminant:Unimplemented");
+ }
+ StatementKind::Deinit(_) => return String::from("StatementKind::Deinit:Unimplemented"),
+ StatementKind::StorageLive(_) => {
+ return String::from("StatementKind::StorageLive:Unimplemented");
+ }
+ StatementKind::StorageDead(_) => {
+ return String::from("StatementKind::StorageDead:Unimplemented");
+ }
+ StatementKind::Retag(_, _) => return String::from("StatementKind::Retag:Unimplemented"),
+ StatementKind::PlaceMention(_) => {
+ return String::from("StatementKind::PlaceMention:Unimplemented");
+ }
+ StatementKind::AscribeUserType { .. } => {
+ return String::from("StatementKind::AscribeUserType:Unimplemented");
+ }
+ StatementKind::Coverage(_) => return String::from("StatementKind::Coverage:Unimplemented"),
+ StatementKind::Intrinsic(_) => {
+ return String::from("StatementKind::Intrinsic:Unimplemented");
+ }
+ StatementKind::ConstEvalCounter => {
+ return String::from("StatementKind::ConstEvalCounter:Unimplemented");
+ }
+ StatementKind::Nop => return String::from("StatementKind::Nop:Unimplemented"),
+ }
+ pretty
+}
+
+pub fn pretty_terminator<W: io::Write>(terminator: &TerminatorKind, w: &mut W) -> io::Result<()> {
+ write!(w, "{}", pretty_terminator_head(terminator))?;
+ let successors = terminator.successors();
+ let successor_count = successors.len();
+ let labels = pretty_successor_labels(terminator);
+
+ let show_unwind = !matches!(terminator.unwind(), None | Some(UnwindAction::Cleanup(_)));
+ let fmt_unwind = |fmt: &mut dyn Write| -> io::Result<()> {
+ write!(fmt, "unwind ")?;
+ match terminator.unwind() {
+ None | Some(UnwindAction::Cleanup(_)) => unreachable!(),
+ Some(UnwindAction::Continue) => write!(fmt, "continue"),
+ Some(UnwindAction::Unreachable) => write!(fmt, "unreachable"),
+ Some(UnwindAction::Terminate) => write!(fmt, "terminate"),
+ }
+ };
+
+ match (successor_count, show_unwind) {
+ (0, false) => Ok(()),
+ (0, true) => {
+ write!(w, " -> ")?;
+ fmt_unwind(w)?;
+ Ok(())
+ }
+ (1, false) => {
+ write!(w, " -> {:?}", successors[0])?;
+ Ok(())
+ }
+ _ => {
+ write!(w, " -> [")?;
+ for (i, target) in successors.iter().enumerate() {
+ if i > 0 {
+ write!(w, ", ")?;
+ }
+ write!(w, "{}: bb{:?}", labels[i], target)?;
+ }
+ if show_unwind {
+ write!(w, ", ")?;
+ fmt_unwind(w)?;
+ }
+ write!(w, "]")
+ }
+ }?;
+
+ Ok(())
+}
+
+pub fn pretty_terminator_head(terminator: &TerminatorKind) -> String {
+ use self::TerminatorKind::*;
+ let mut pretty = String::new();
+ match terminator {
+ Goto { .. } => format!(" goto"),
+ SwitchInt { discr, .. } => {
+ format!(" switchInt(_{})", pretty_operand(discr))
+ }
+ Resume => format!(" resume"),
+ Abort => format!(" abort"),
+ Return => format!(" return"),
+ Unreachable => format!(" unreachable"),
+ Drop { place, .. } => format!(" drop(_{:?})", place.local),
+ Call { func, args, destination, .. } => {
+ pretty.push_str(" ");
+ pretty.push_str(format!("_{} = ", destination.local).as_str());
+ pretty.push_str(&pretty_operand(func));
+ pretty.push_str("(");
+ args.iter().enumerate().for_each(|(i, arg)| {
+ if i > 0 {
+ pretty.push_str(", ");
+ }
+ pretty.push_str(&pretty_operand(arg));
+ });
+ pretty.push_str(")");
+ pretty
+ }
+ Assert { cond, expected, msg, target: _, unwind: _ } => {
+ pretty.push_str(" assert(");
+ if !expected {
+ pretty.push_str("!");
+ }
+ pretty.push_str(format!("{} bool),", &pretty_operand(cond)).as_str());
+ pretty.push_str(&pretty_assert_message(msg));
+ pretty.push_str(")");
+ pretty
+ }
+ InlineAsm { .. } => todo!(),
+ }
+}
+
+pub fn pretty_successor_labels(terminator: &TerminatorKind) -> Vec<String> {
+ use self::TerminatorKind::*;
+ match terminator {
+ Resume | Abort | Return | Unreachable => vec![],
+ Goto { .. } => vec!["".to_string()],
+ SwitchInt { targets, .. } => targets
+ .branches()
+ .map(|(val, _target)| format!("{val}"))
+ .chain(iter::once("otherwise".into()))
+ .collect(),
+ Drop { unwind: UnwindAction::Cleanup(_), .. } => vec!["return".into(), "unwind".into()],
+ Drop { unwind: _, .. } => vec!["return".into()],
+ Call { target: Some(_), unwind: UnwindAction::Cleanup(_), .. } => {
+ vec!["return".into(), "unwind".into()]
+ }
+ Call { target: Some(_), unwind: _, .. } => vec!["return".into()],
+ Call { target: None, unwind: UnwindAction::Cleanup(_), .. } => vec!["unwind".into()],
+ Call { target: None, unwind: _, .. } => vec![],
+ Assert { unwind: UnwindAction::Cleanup(_), .. } => {
+ vec!["success".into(), "unwind".into()]
+ }
+ Assert { unwind: _, .. } => vec!["success".into()],
+ InlineAsm { .. } => todo!(),
+ }
+}
+
+pub fn pretty_assert_message(msg: &AssertMessage) -> String {
+ let mut pretty = String::new();
+ match msg {
+ AssertMessage::BoundsCheck { len, index } => {
+ let pretty_len = pretty_operand(len);
+ let pretty_index = pretty_operand(index);
+ pretty.push_str(format!("\"index out of bounds: the length is {{}} but the index is {{}}\", {pretty_len}, {pretty_index}").as_str());
+ pretty
+ }
+ AssertMessage::Overflow(BinOp::Add, l, r) => {
+ let pretty_l = pretty_operand(l);
+ let pretty_r = pretty_operand(r);
+ pretty.push_str(format!("\"attempt to compute `{{}} + {{}}`, which would overflow\", {pretty_l}, {pretty_r}").as_str());
+ pretty
+ }
+ AssertMessage::Overflow(BinOp::Sub, l, r) => {
+ let pretty_l = pretty_operand(l);
+ let pretty_r = pretty_operand(r);
+ pretty.push_str(format!("\"attempt to compute `{{}} - {{}}`, which would overflow\", {pretty_l}, {pretty_r}").as_str());
+ pretty
+ }
+ AssertMessage::Overflow(BinOp::Mul, l, r) => {
+ let pretty_l = pretty_operand(l);
+ let pretty_r = pretty_operand(r);
+ pretty.push_str(format!("\"attempt to compute `{{}} * {{}}`, which would overflow\", {pretty_l}, {pretty_r}").as_str());
+ pretty
+ }
+ AssertMessage::Overflow(BinOp::Div, l, r) => {
+ let pretty_l = pretty_operand(l);
+ let pretty_r = pretty_operand(r);
+ pretty.push_str(format!("\"attempt to compute `{{}} / {{}}`, which would overflow\", {pretty_l}, {pretty_r}").as_str());
+ pretty
+ }
+ AssertMessage::Overflow(BinOp::Rem, l, r) => {
+ let pretty_l = pretty_operand(l);
+ let pretty_r = pretty_operand(r);
+ pretty.push_str(format!("\"attempt to compute `{{}} % {{}}`, which would overflow\", {pretty_l}, {pretty_r}").as_str());
+ pretty
+ }
+ AssertMessage::Overflow(BinOp::Shr, _, r) => {
+ let pretty_r = pretty_operand(r);
+ pretty.push_str(
+ format!("\"attempt to shift right by `{{}}`, which would overflow\", {pretty_r}")
+ .as_str(),
+ );
+ pretty
+ }
+ AssertMessage::Overflow(BinOp::Shl, _, r) => {
+ let pretty_r = pretty_operand(r);
+ pretty.push_str(
+ format!("\"attempt to shift left by `{{}}`, which would overflow\", {pretty_r}")
+ .as_str(),
+ );
+ pretty
+ }
+ AssertMessage::Overflow(op, _, _) => unreachable!("`{:?}` cannot overflow", op),
+ AssertMessage::OverflowNeg(op) => {
+ let pretty_op = pretty_operand(op);
+ pretty.push_str(
+ format!("\"attempt to negate `{{}}`, which would overflow\", {pretty_op}").as_str(),
+ );
+ pretty
+ }
+ AssertMessage::DivisionByZero(op) => {
+ let pretty_op = pretty_operand(op);
+ pretty.push_str(format!("\"attempt to divide `{{}}` by zero\", {pretty_op}").as_str());
+ pretty
+ }
+ AssertMessage::RemainderByZero(op) => {
+ let pretty_op = pretty_operand(op);
+ pretty.push_str(
+ format!("\"attempt to calculate the remainder of `{{}}` with a divisor of zero\", {pretty_op}").as_str(),
+ );
+ pretty
+ }
+ AssertMessage::MisalignedPointerDereference { required, found } => {
+ let pretty_required = pretty_operand(required);
+ let pretty_found = pretty_operand(found);
+ pretty.push_str(format!("\"misaligned pointer dereference: address must be a multiple of {{}} but is {{}}\",{pretty_required}, {pretty_found}").as_str());
+ pretty
+ }
+ AssertMessage::ResumedAfterReturn(_) | AssertMessage::ResumedAfterPanic(_) => {
+ msg.description().unwrap().to_string()
+ }
+ }
+}
+
+pub fn pretty_operand(operand: &Operand) -> String {
+ let mut pretty = String::new();
+ match operand {
+ Operand::Copy(copy) => {
+ pretty.push_str("");
+ pretty.push_str(format!("{}", copy.local).as_str());
+ }
+ Operand::Move(mv) => {
+ pretty.push_str("move ");
+ pretty.push_str(format!("_{}", mv.local).as_str());
+ }
+ Operand::Constant(cnst) => {
+ pretty.push_str("const ");
+ pretty.push_str(with(|cx| cx.const_literal(&cnst.literal)).as_str());
+ }
+ }
+ pretty
+}
+
+pub fn pretty_rvalue(rval: &Rvalue) -> String {
+ let mut pretty = String::new();
+ match rval {
+ Rvalue::AddressOf(muta, addr) => {
+ pretty.push_str("&raw ");
+ pretty.push_str(&ret_mutability(muta));
+ pretty.push_str(format!("(*_{})", addr.local).as_str());
+ }
+ Rvalue::Aggregate(aggregatekind, operands) => {
+ pretty.push_str(format!("{:#?}", aggregatekind).as_str());
+ pretty.push_str("(");
+ operands.iter().enumerate().for_each(|(i, op)| {
+ pretty.push_str(&pretty_operand(op));
+ if i != operands.len() - 1 {
+ pretty.push_str(", ");
+ }
+ });
+ pretty.push_str(")");
+ }
+ Rvalue::BinaryOp(bin, op, op2) => {
+ pretty.push_str(&pretty_operand(op));
+ pretty.push_str(" ");
+ pretty.push_str(format!("{:#?}", bin).as_str());
+ pretty.push_str(" ");
+ pretty.push_str(&pretty_operand(op2));
+ }
+ Rvalue::Cast(_, op, ty) => {
+ pretty.push_str(&pretty_operand(op));
+ pretty.push_str(" as ");
+ pretty.push_str(&pretty_ty(ty.kind()));
+ }
+ Rvalue::CheckedBinaryOp(bin, op1, op2) => {
+ pretty.push_str(&pretty_operand(op1));
+ pretty.push_str(" ");
+ pretty.push_str(format!("{:#?}", bin).as_str());
+ pretty.push_str(" ");
+ pretty.push_str(&pretty_operand(op2));
+ }
+ Rvalue::CopyForDeref(deref) => {
+ pretty.push_str("CopyForDeref");
+ pretty.push_str(format!("{}", deref.local).as_str());
+ }
+ Rvalue::Discriminant(place) => {
+ pretty.push_str("discriminant");
+ pretty.push_str(format!("{}", place.local).as_str());
+ }
+ Rvalue::Len(len) => {
+ pretty.push_str("len");
+ pretty.push_str(format!("{}", len.local).as_str());
+ }
+ Rvalue::Ref(_, borrowkind, place) => {
+ pretty.push_str("ref");
+ pretty.push_str(format!("{:#?}", borrowkind).as_str());
+ pretty.push_str(format!("{}", place.local).as_str());
+ }
+ Rvalue::Repeat(op, cnst) => {
+ pretty.push_str(&pretty_operand(op));
+ pretty.push_str(" ");
+ pretty.push_str(&pretty_ty(cnst.ty().kind()));
+ }
+ Rvalue::ShallowInitBox(_, _) => (),
+ Rvalue::ThreadLocalRef(item) => {
+ pretty.push_str("thread_local_ref");
+ pretty.push_str(format!("{:#?}", item).as_str());
+ }
+ Rvalue::NullaryOp(nul, ty) => {
+ pretty.push_str(format!("{:#?}", nul).as_str());
+ pretty.push_str(&pretty_ty(ty.kind()));
+ pretty.push_str(" ");
+ }
+ Rvalue::UnaryOp(un, op) => {
+ pretty.push_str(&pretty_operand(op));
+ pretty.push_str(" ");
+ pretty.push_str(format!("{:#?}", un).as_str());
+ }
+ Rvalue::Use(op) => pretty.push_str(&pretty_operand(op)),
+ }
+ pretty
+}
+
+pub fn pretty_ty(ty: TyKind) -> String {
+ let mut pretty = String::new();
+ match ty {
+ TyKind::RigidTy(rigid_ty) => match rigid_ty {
+ RigidTy::Bool => "bool".to_string(),
+ RigidTy::Char => "char".to_string(),
+ RigidTy::Int(i) => match i {
+ IntTy::Isize => "isize".to_string(),
+ IntTy::I8 => "i8".to_string(),
+ IntTy::I16 => "i16".to_string(),
+ IntTy::I32 => "i32".to_string(),
+ IntTy::I64 => "i64".to_string(),
+ IntTy::I128 => "i128".to_string(),
+ },
+ RigidTy::Uint(u) => match u {
+ UintTy::Usize => "usize".to_string(),
+ UintTy::U8 => "u8".to_string(),
+ UintTy::U16 => "u16".to_string(),
+ UintTy::U32 => "u32".to_string(),
+ UintTy::U64 => "u64".to_string(),
+ UintTy::U128 => "u128".to_string(),
+ },
+ RigidTy::Float(f) => match f {
+ FloatTy::F32 => "f32".to_string(),
+ FloatTy::F64 => "f64".to_string(),
+ },
+ RigidTy::Adt(def, _) => {
+ format!("{:#?}", with(|cx| cx.def_ty(def.0)))
+ }
+ RigidTy::Str => "str".to_string(),
+ RigidTy::Array(ty, len) => {
+ format!("[{}; {}]", pretty_ty(ty.kind()), with(|cx| cx.const_literal(&len)))
+ }
+ RigidTy::Slice(ty) => {
+ format!("[{}]", pretty_ty(ty.kind()))
+ }
+ RigidTy::RawPtr(ty, mutability) => {
+ pretty.push_str("*");
+ match mutability {
+ Mutability::Not => pretty.push_str("const "),
+ Mutability::Mut => pretty.push_str("mut "),
+ }
+ pretty.push_str(&pretty_ty(ty.kind()));
+ pretty
+ }
+ RigidTy::Ref(_, ty, mutability) => match mutability {
+ Mutability::Not => format!("&{}", pretty_ty(ty.kind())),
+ Mutability::Mut => format!("&mut {}", pretty_ty(ty.kind())),
+ },
+ RigidTy::FnDef(_, _) => format!("{:#?}", rigid_ty),
+ RigidTy::FnPtr(_) => format!("{:#?}", rigid_ty),
+ RigidTy::Closure(_, _) => format!("{:#?}", rigid_ty),
+ RigidTy::Coroutine(_, _, _) => format!("{:#?}", rigid_ty),
+ RigidTy::Dynamic(data, region, repr) => {
+ // FIXME: Fix binder printing, it looks ugly now
+ pretty.push_str("(");
+ match repr {
+ DynKind::Dyn => pretty.push_str("dyn "),
+ DynKind::DynStar => pretty.push_str("dyn* "),
+ }
+ pretty.push_str(format!("{:#?}", data).as_str());
+ pretty.push_str(format!(" + {:#?} )", region).as_str());
+ pretty
+ }
+ RigidTy::Never => "!".to_string(),
+ RigidTy::Tuple(tuple) => {
+ if tuple.is_empty() {
+ "()".to_string()
+ } else {
+ let mut tuple_str = String::new();
+ tuple_str.push_str("(");
+ tuple.iter().enumerate().for_each(|(i, ty)| {
+ tuple_str.push_str(&pretty_ty(ty.kind()));
+ if i != tuple.len() - 1 {
+ tuple_str.push_str(", ");
+ }
+ });
+ tuple_str.push_str(")");
+ tuple_str
+ }
+ }
+ _ => format!("{:#?}", rigid_ty),
+ },
+ TyKind::Alias(_, _) => format!("{:#?}", ty),
+ TyKind::Param(param_ty) => {
+ format!("{:#?}", param_ty.name)
+ }
+ TyKind::Bound(_, _) => format!("{:#?}", ty),
+ }
+}
diff --git a/compiler/stable_mir/src/mir/visit.rs b/compiler/stable_mir/src/mir/visit.rs
index 806dced71..ab57ff0f8 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);
}
@@ -119,8 +128,12 @@ pub trait MirVisitor {
self.super_assert_msg(msg, location)
}
+ fn visit_var_debug_info(&mut self, var_debug_info: &VarDebugInfo) {
+ self.super_var_debug_info(var_debug_info);
+ }
+
fn super_body(&mut self, body: &Body) {
- let Body { blocks, locals: _, arg_count } = body;
+ let Body { blocks, locals: _, arg_count, var_debug_info, spread_arg: _, span } = body;
for bb in blocks {
self.visit_basic_block(bb);
@@ -133,9 +146,15 @@ pub trait MirVisitor {
}
let local_start = arg_count + 1;
- for (idx, arg) in body.arg_locals().iter().enumerate() {
+ for (idx, arg) in body.inner_locals().iter().enumerate() {
self.visit_local_decl(idx + local_start, arg)
}
+
+ for info in var_debug_info.iter() {
+ self.visit_var_debug_info(info);
+ }
+
+ self.visit_span(span)
}
fn super_basic_block(&mut self, bb: &BasicBlock) {
@@ -148,7 +167,7 @@ pub trait MirVisitor {
fn super_local_decl(&mut self, local: Local, decl: &LocalDecl) {
let _ = local;
- let LocalDecl { ty, span } = decl;
+ let LocalDecl { ty, span, .. } = decl;
self.visit_ty(ty, Location(*span));
}
@@ -215,13 +234,12 @@ pub trait MirVisitor {
fn super_terminator(&mut self, term: &Terminator, location: Location) {
let Terminator { kind, span } = term;
- self.visit_span(&span);
+ self.visit_span(span);
match kind {
TerminatorKind::Goto { .. }
| TerminatorKind::Resume
| TerminatorKind::Abort
- | TerminatorKind::Unreachable
- | TerminatorKind::CoroutineDrop => {}
+ | TerminatorKind::Unreachable => {}
TerminatorKind::Assert { cond, expected: _, msg, target: _, unwind: _ } => {
self.visit_operand(cond, location);
self.visit_assert_msg(msg, location);
@@ -251,7 +269,7 @@ pub trait MirVisitor {
let local = RETURN_LOCAL;
self.visit_local(&local, PlaceContext::NON_MUTATING, location);
}
- TerminatorKind::SwitchInt { discr, targets: _, otherwise: _ } => {
+ TerminatorKind::SwitchInt { discr, targets: _ } => {
self.visit_operand(discr, location);
}
}
@@ -264,7 +282,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) {
@@ -351,6 +391,24 @@ pub trait MirVisitor {
let _ = args;
}
+ fn super_var_debug_info(&mut self, var_debug_info: &VarDebugInfo) {
+ let VarDebugInfo { source_info, composite, value, name: _, argument_index: _ } =
+ var_debug_info;
+ self.visit_span(&source_info.span);
+ let location = Location(source_info.span);
+ if let Some(composite) = composite {
+ self.visit_ty(&composite.ty, location);
+ }
+ match value {
+ VarDebugInfoContents::Place(place) => {
+ self.visit_place(place, PlaceContext::NON_USE, location);
+ }
+ VarDebugInfoContents::Const(constant) => {
+ self.visit_const(&constant.const_, location);
+ }
+ }
+ }
+
fn super_assert_msg(&mut self, msg: &AssertMessage, location: Location) {
match msg {
AssertMessage::BoundsCheck { len, index } => {
@@ -386,7 +444,7 @@ pub trait MirVisitor {
fn visit_opaque(_: &Opaque) {}
/// The location of a statement / terminator in the code and the CFG.
-#[derive(Clone, Copy, PartialEq, Eq)]
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Location(Span);
impl Location {
@@ -396,7 +454,7 @@ impl Location {
}
/// Information about a place's usage.
-#[derive(Copy, Clone, PartialEq, Eq, Hash)]
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct PlaceContext {
/// Whether the access is mutable or not. Keep this private so we can increment the type in a
/// backward compatible manner.