rust/compiler/rustc_const_eval/src/interpret/projection.rs

307 lines
12 KiB
Rust
Raw Normal View History

//! This file implements "place projections"; basically a symmetric API for 3 types: MPlaceTy, OpTy, PlaceTy.
//!
//! OpTy and PlaceTy generally work by "let's see if we are actually an MPlaceTy, and do something custom if not".
//! For PlaceTy, the custom thing is basically always to call `force_allocation` and then use the MPlaceTy logic anyway.
//! For OpTy, the custom thing on field pojections has to be pretty clever (since `Operand::Immediate` can have fields),
//! but for array/slice operations it only has to worry about `Operand::Uninit`. That makes the value part trivial,
//! but we still need to do bounds checking and adjust the layout. To not duplicate that with MPlaceTy, we actually
//! implement the logic on OpTy, and MPlaceTy calls that.
use rustc_middle::mir;
use rustc_middle::ty;
use rustc_middle::ty::layout::{LayoutOf, TyAndLayout};
use rustc_middle::ty::Ty;
use rustc_middle::ty::TyCtxt;
use rustc_target::abi::HasDataLayout;
use rustc_target::abi::Size;
use rustc_target::abi::{self, VariantIdx};
use super::{InterpCx, InterpResult, MPlaceTy, Machine, MemPlaceMeta, OpTy, Provenance, Scalar};
/// A thing that we can project into, and that has a layout.
pub trait Projectable<'tcx, Prov: Provenance>: Sized + std::fmt::Debug {
/// Get the layout.
fn layout(&self) -> TyAndLayout<'tcx>;
/// Get the metadata of a wide value.
fn meta<'mir, M: Machine<'mir, 'tcx, Provenance = Prov>>(
&self,
ecx: &InterpCx<'mir, 'tcx, M>,
) -> InterpResult<'tcx, MemPlaceMeta<M::Provenance>>;
fn len<'mir, M: Machine<'mir, 'tcx, Provenance = Prov>>(
&self,
ecx: &InterpCx<'mir, 'tcx, M>,
) -> InterpResult<'tcx, u64> {
self.meta(ecx)?.len(self.layout(), ecx)
}
/// Offset the value by the given amount, replacing the layout and metadata.
fn offset_with_meta(
&self,
offset: Size,
meta: MemPlaceMeta<Prov>,
layout: TyAndLayout<'tcx>,
cx: &impl HasDataLayout,
) -> InterpResult<'tcx, Self>;
fn offset(
&self,
offset: Size,
layout: TyAndLayout<'tcx>,
cx: &impl HasDataLayout,
) -> InterpResult<'tcx, Self> {
assert!(layout.is_sized());
self.offset_with_meta(offset, MemPlaceMeta::None, layout, cx)
}
fn transmute(
&self,
layout: TyAndLayout<'tcx>,
cx: &impl HasDataLayout,
) -> InterpResult<'tcx, Self> {
assert_eq!(self.layout().size, layout.size);
self.offset_with_meta(Size::ZERO, MemPlaceMeta::None, layout, cx)
}
/// Convert this to an `OpTy`. This might be an irreversible transformation, but is useful for
/// reading from this thing.
fn to_op<'mir, M: Machine<'mir, 'tcx, Provenance = Prov>>(
&self,
ecx: &InterpCx<'mir, 'tcx, M>,
) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>>;
}
// FIXME: Working around https://github.com/rust-lang/rust/issues/54385
impl<'mir, 'tcx: 'mir, Prov, M> InterpCx<'mir, 'tcx, M>
where
Prov: Provenance + 'static,
M: Machine<'mir, 'tcx, Provenance = Prov>,
{
/// Offset a pointer to project to a field of a struct/union. Unlike `place_field`, this is
/// always possible without allocating, so it can take `&self`. Also return the field's layout.
/// This supports both struct and array fields, but not slices!
///
/// This also works for arrays, but then the `usize` index type is restricting.
/// For indexing into arrays, use `mplace_index`.
pub fn project_field<P: Projectable<'tcx, M::Provenance>>(
&self,
base: &P,
field: usize,
) -> InterpResult<'tcx, P> {
// Slices nominally have length 0, so they will panic somewhere in `fields.offset`.
debug_assert!(
!matches!(base.layout().ty.kind(), ty::Slice(..)),
"`field` projection called on a slice -- call `index` projection instead"
);
let offset = base.layout().fields.offset(field);
let field_layout = base.layout().field(self, field);
// Offset may need adjustment for unsized fields.
let (meta, offset) = if field_layout.is_unsized() {
if base.layout().is_sized() {
// An unsized field of a sized type? Sure...
// But const-prop actually feeds us such nonsense MIR!
throw_inval!(ConstPropNonsense);
}
let base_meta = base.meta(self)?;
// Re-use parent metadata to determine dynamic field layout.
// With custom DSTS, this *will* execute user-defined code, but the same
// happens at run-time so that's okay.
match self.size_and_align_of(&base_meta, &field_layout)? {
Some((_, align)) => (base_meta, offset.align_to(align)),
None => {
// For unsized types with an extern type tail we perform no adjustments.
// NOTE: keep this in sync with `PlaceRef::project_field` in the codegen backend.
assert!(matches!(base_meta, MemPlaceMeta::None));
(base_meta, offset)
}
}
} else {
// base_meta could be present; we might be accessing a sized field of an unsized
// struct.
(MemPlaceMeta::None, offset)
};
base.offset_with_meta(offset, meta, field_layout, self)
}
/// Downcasting to an enum variant.
pub fn project_downcast<P: Projectable<'tcx, M::Provenance>>(
&self,
base: &P,
variant: VariantIdx,
) -> InterpResult<'tcx, P> {
assert!(!base.meta(self)?.has_meta());
// Downcasts only change the layout.
// (In particular, no check about whether this is even the active variant -- that's by design,
// see https://github.com/rust-lang/rust/issues/93688#issuecomment-1032929496.)
// So we just "offset" by 0.
let layout = base.layout().for_variant(self, variant);
if layout.abi.is_uninhabited() {
// `read_discriminant` should have excluded uninhabited variants... but ConstProp calls
// us on dead code.
throw_inval!(ConstPropNonsense)
}
// This cannot be `transmute` as variants *can* have a smaller size than the entire enum.
base.offset(Size::ZERO, layout, self)
}
/// Compute the offset and field layout for accessing the given index.
pub fn project_index<P: Projectable<'tcx, M::Provenance>>(
&self,
base: &P,
index: u64,
) -> InterpResult<'tcx, P> {
// Not using the layout method because we want to compute on u64
let (offset, field_layout) = match base.layout().fields {
abi::FieldsShape::Array { stride, count: _ } => {
// `count` is nonsense for slices, use the dynamic length instead.
let len = base.len(self)?;
if index >= len {
// This can only be reached in ConstProp and non-rustc-MIR.
throw_ub!(BoundsCheckFailed { len, index });
}
let offset = stride * index; // `Size` multiplication
// All fields have the same layout.
let field_layout = base.layout().field(self, 0);
(offset, field_layout)
}
_ => span_bug!(
self.cur_span(),
"`mplace_index` called on non-array type {:?}",
base.layout().ty
),
};
base.offset(offset, field_layout, self)
}
fn project_constant_index<P: Projectable<'tcx, M::Provenance>>(
&self,
base: &P,
offset: u64,
min_length: u64,
from_end: bool,
) -> InterpResult<'tcx, P> {
let n = base.len(self)?;
if n < min_length {
// This can only be reached in ConstProp and non-rustc-MIR.
throw_ub!(BoundsCheckFailed { len: min_length, index: n });
}
let index = if from_end {
assert!(0 < offset && offset <= min_length);
n.checked_sub(offset).unwrap()
} else {
assert!(offset < min_length);
offset
};
self.project_index(base, index)
}
/// Iterates over all fields of an array. Much more efficient than doing the
/// same by repeatedly calling `operand_index`.
pub fn project_array_fields<'a, P: Projectable<'tcx, M::Provenance>>(
&self,
base: &'a P,
) -> InterpResult<'tcx, impl Iterator<Item = InterpResult<'tcx, P>> + 'a>
where
'tcx: 'a,
{
let abi::FieldsShape::Array { stride, .. } = base.layout().fields else {
span_bug!(self.cur_span(), "operand_array_fields: expected an array layout");
};
let len = base.len(self)?;
let field_layout = base.layout().field(self, 0);
let tcx: TyCtxt<'tcx> = *self.tcx;
// `Size` multiplication
Ok((0..len).map(move |i| {
base.offset_with_meta(stride * i, MemPlaceMeta::None, field_layout, &tcx)
}))
}
/// Subslicing
fn project_subslice<P: Projectable<'tcx, M::Provenance>>(
&self,
base: &P,
from: u64,
to: u64,
from_end: bool,
) -> InterpResult<'tcx, P> {
let len = base.len(self)?; // also asserts that we have a type where this makes sense
let actual_to = if from_end {
if from.checked_add(to).map_or(true, |to| to > len) {
// This can only be reached in ConstProp and non-rustc-MIR.
throw_ub!(BoundsCheckFailed { len: len, index: from.saturating_add(to) });
}
len.checked_sub(to).unwrap()
} else {
to
};
// Not using layout method because that works with usize, and does not work with slices
// (that have count 0 in their layout).
let from_offset = match base.layout().fields {
abi::FieldsShape::Array { stride, .. } => stride * from, // `Size` multiplication is checked
_ => {
span_bug!(
self.cur_span(),
"unexpected layout of index access: {:#?}",
base.layout()
)
}
};
// Compute meta and new layout
let inner_len = actual_to.checked_sub(from).unwrap();
let (meta, ty) = match base.layout().ty.kind() {
// It is not nice to match on the type, but that seems to be the only way to
// implement this.
ty::Array(inner, _) => {
(MemPlaceMeta::None, Ty::new_array(self.tcx.tcx, *inner, inner_len))
}
ty::Slice(..) => {
let len = Scalar::from_target_usize(inner_len, self);
(MemPlaceMeta::Meta(len), base.layout().ty)
}
_ => {
span_bug!(
self.cur_span(),
"cannot subslice non-array type: `{:?}`",
base.layout().ty
)
}
};
let layout = self.layout_of(ty)?;
base.offset_with_meta(from_offset, meta, layout, self)
}
/// Applying a general projection
#[instrument(skip(self), level = "trace")]
pub fn project<P>(&self, base: &P, proj_elem: mir::PlaceElem<'tcx>) -> InterpResult<'tcx, P>
where
P: Projectable<'tcx, M::Provenance> + From<MPlaceTy<'tcx, M::Provenance>> + std::fmt::Debug,
{
use rustc_middle::mir::ProjectionElem::*;
Ok(match proj_elem {
OpaqueCast(ty) => base.transmute(self.layout_of(ty)?, self)?,
Field(field, _) => self.project_field(base, field.index())?,
Downcast(_, variant) => self.project_downcast(base, variant)?,
Deref => self.deref_operand(&base.to_op(self)?)?.into(),
Index(local) => {
let layout = self.layout_of(self.tcx.types.usize)?;
let n = self.local_to_op(self.frame(), local, Some(layout))?;
let n = self.read_target_usize(&n)?;
self.project_index(base, n)?
}
ConstantIndex { offset, min_length, from_end } => {
self.project_constant_index(base, offset, min_length, from_end)?
}
Subslice { from, to, from_end } => self.project_subslice(base, from, to, from_end)?,
})
}
}