avoid re-checking the offset while iterating an array/slice

This commit is contained in:
Ralf Jung 2023-09-02 16:12:57 +02:00
parent b1ebf002c3
commit ea9a24e32e
5 changed files with 57 additions and 15 deletions

View File

@ -26,7 +26,7 @@ pub use self::machine::{compile_time_machine, AllocMap, Machine, MayLeak, StackP
pub use self::memory::{AllocKind, AllocRef, AllocRefMut, FnVal, Memory, MemoryKind}; pub use self::memory::{AllocKind, AllocRef, AllocRefMut, FnVal, Memory, MemoryKind};
pub use self::operand::{ImmTy, Immediate, OpTy, Readable}; pub use self::operand::{ImmTy, Immediate, OpTy, Readable};
pub use self::place::{MPlaceTy, MemPlaceMeta, PlaceTy, Writeable}; pub use self::place::{MPlaceTy, MemPlaceMeta, PlaceTy, Writeable};
pub use self::projection::Projectable; pub use self::projection::{OffsetMode, Projectable};
pub use self::terminator::FnArg; pub use self::terminator::FnArg;
pub use self::validity::{CtfeValidationMode, RefTracking}; pub use self::validity::{CtfeValidationMode, RefTracking};
pub use self::visitor::ValueVisitor; pub use self::visitor::ValueVisitor;

View File

@ -14,7 +14,8 @@ use rustc_target::abi::{self, Abi, Align, HasDataLayout, Size};
use super::{ use super::{
alloc_range, from_known_layout, mir_assign_valid_types, AllocId, Frame, InterpCx, InterpResult, alloc_range, from_known_layout, mir_assign_valid_types, AllocId, Frame, InterpCx, InterpResult,
MPlaceTy, Machine, MemPlace, MemPlaceMeta, PlaceTy, Pointer, Projectable, Provenance, Scalar, MPlaceTy, Machine, MemPlace, MemPlaceMeta, OffsetMode, PlaceTy, Pointer, Projectable,
Provenance, Scalar,
}; };
/// An `Immediate` represents a single immediate self-contained Rust value. /// An `Immediate` represents a single immediate self-contained Rust value.
@ -297,6 +298,7 @@ impl<'tcx, Prov: Provenance> Projectable<'tcx, Prov> for ImmTy<'tcx, Prov> {
fn offset_with_meta<'mir, M: Machine<'mir, 'tcx, Provenance = Prov>>( fn offset_with_meta<'mir, M: Machine<'mir, 'tcx, Provenance = Prov>>(
&self, &self,
offset: Size, offset: Size,
_mode: OffsetMode,
meta: MemPlaceMeta<Prov>, meta: MemPlaceMeta<Prov>,
layout: TyAndLayout<'tcx>, layout: TyAndLayout<'tcx>,
ecx: &InterpCx<'mir, 'tcx, M>, ecx: &InterpCx<'mir, 'tcx, M>,
@ -391,12 +393,13 @@ impl<'tcx, Prov: Provenance> Projectable<'tcx, Prov> for OpTy<'tcx, Prov> {
fn offset_with_meta<'mir, M: Machine<'mir, 'tcx, Provenance = Prov>>( fn offset_with_meta<'mir, M: Machine<'mir, 'tcx, Provenance = Prov>>(
&self, &self,
offset: Size, offset: Size,
mode: OffsetMode,
meta: MemPlaceMeta<Prov>, meta: MemPlaceMeta<Prov>,
layout: TyAndLayout<'tcx>, layout: TyAndLayout<'tcx>,
ecx: &InterpCx<'mir, 'tcx, M>, ecx: &InterpCx<'mir, 'tcx, M>,
) -> InterpResult<'tcx, Self> { ) -> InterpResult<'tcx, Self> {
match self.as_mplace_or_imm() { match self.as_mplace_or_imm() {
Left(mplace) => Ok(mplace.offset_with_meta(offset, meta, layout, ecx)?.into()), Left(mplace) => Ok(mplace.offset_with_meta(offset, mode, meta, layout, ecx)?.into()),
Right(imm) => { Right(imm) => {
assert_matches!(meta, MemPlaceMeta::None); // no place to store metadata here assert_matches!(meta, MemPlaceMeta::None); // no place to store metadata here
// Every part of an uninit is uninit. // Every part of an uninit is uninit.

View File

@ -16,8 +16,8 @@ use rustc_target::abi::{Abi, Align, FieldIdx, HasDataLayout, Size, FIRST_VARIANT
use super::{ use super::{
alloc_range, mir_assign_valid_types, AllocId, AllocRef, AllocRefMut, ImmTy, Immediate, alloc_range, mir_assign_valid_types, AllocId, AllocRef, AllocRefMut, ImmTy, Immediate,
InterpCx, InterpResult, Machine, MemoryKind, OpTy, Operand, Pointer, PointerArithmetic, InterpCx, InterpResult, Machine, MemoryKind, OffsetMode, OpTy, Operand, Pointer,
Projectable, Provenance, Readable, Scalar, PointerArithmetic, Projectable, Provenance, Readable, Scalar,
}; };
#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug)] #[derive(Copy, Clone, Hash, PartialEq, Eq, Debug)]
@ -91,6 +91,7 @@ impl<Prov: Provenance> MemPlace<Prov> {
fn offset_with_meta_<'mir, 'tcx, M: Machine<'mir, 'tcx, Provenance = Prov>>( fn offset_with_meta_<'mir, 'tcx, M: Machine<'mir, 'tcx, Provenance = Prov>>(
self, self,
offset: Size, offset: Size,
mode: OffsetMode,
meta: MemPlaceMeta<Prov>, meta: MemPlaceMeta<Prov>,
ecx: &InterpCx<'mir, 'tcx, M>, ecx: &InterpCx<'mir, 'tcx, M>,
) -> InterpResult<'tcx, Self> { ) -> InterpResult<'tcx, Self> {
@ -101,8 +102,12 @@ impl<Prov: Provenance> MemPlace<Prov> {
if offset > ecx.data_layout().max_size_of_val() { if offset > ecx.data_layout().max_size_of_val() {
throw_ub!(PointerArithOverflow); throw_ub!(PointerArithOverflow);
} }
let offset: i64 = offset.bytes().try_into().unwrap(); let ptr = match mode {
let ptr = ecx.ptr_offset_inbounds(self.ptr, offset)?; OffsetMode::Inbounds => {
ecx.ptr_offset_inbounds(self.ptr, offset.bytes().try_into().unwrap())?
}
OffsetMode::Wrapping => self.ptr.wrapping_offset(offset, ecx),
};
Ok(MemPlace { ptr, meta }) Ok(MemPlace { ptr, meta })
} }
} }
@ -194,12 +199,13 @@ impl<'tcx, Prov: Provenance> Projectable<'tcx, Prov> for MPlaceTy<'tcx, Prov> {
fn offset_with_meta<'mir, M: Machine<'mir, 'tcx, Provenance = Prov>>( fn offset_with_meta<'mir, M: Machine<'mir, 'tcx, Provenance = Prov>>(
&self, &self,
offset: Size, offset: Size,
mode: OffsetMode,
meta: MemPlaceMeta<Prov>, meta: MemPlaceMeta<Prov>,
layout: TyAndLayout<'tcx>, layout: TyAndLayout<'tcx>,
ecx: &InterpCx<'mir, 'tcx, M>, ecx: &InterpCx<'mir, 'tcx, M>,
) -> InterpResult<'tcx, Self> { ) -> InterpResult<'tcx, Self> {
Ok(MPlaceTy { Ok(MPlaceTy {
mplace: self.mplace.offset_with_meta_(offset, meta, ecx)?, mplace: self.mplace.offset_with_meta_(offset, mode, meta, ecx)?,
align: self.align.restrict_for_offset(offset), align: self.align.restrict_for_offset(offset),
layout, layout,
}) })
@ -306,12 +312,13 @@ impl<'tcx, Prov: Provenance> Projectable<'tcx, Prov> for PlaceTy<'tcx, Prov> {
fn offset_with_meta<'mir, M: Machine<'mir, 'tcx, Provenance = Prov>>( fn offset_with_meta<'mir, M: Machine<'mir, 'tcx, Provenance = Prov>>(
&self, &self,
offset: Size, offset: Size,
mode: OffsetMode,
meta: MemPlaceMeta<Prov>, meta: MemPlaceMeta<Prov>,
layout: TyAndLayout<'tcx>, layout: TyAndLayout<'tcx>,
ecx: &InterpCx<'mir, 'tcx, M>, ecx: &InterpCx<'mir, 'tcx, M>,
) -> InterpResult<'tcx, Self> { ) -> InterpResult<'tcx, Self> {
Ok(match self.as_mplace_or_local() { Ok(match self.as_mplace_or_local() {
Left(mplace) => mplace.offset_with_meta(offset, meta, layout, ecx)?.into(), Left(mplace) => mplace.offset_with_meta(offset, mode, meta, layout, ecx)?.into(),
Right((frame, local, old_offset)) => { Right((frame, local, old_offset)) => {
debug_assert!(layout.is_sized(), "unsized locals should live in memory"); debug_assert!(layout.is_sized(), "unsized locals should live in memory");
assert_matches!(meta, MemPlaceMeta::None); // we couldn't store it anyway... assert_matches!(meta, MemPlaceMeta::None); // we couldn't store it anyway...
@ -952,7 +959,13 @@ where
&mut Operand::Indirect(mplace) => mplace, // this already was an indirect local &mut Operand::Indirect(mplace) => mplace, // this already was an indirect local
}; };
if let Some(offset) = offset { if let Some(offset) = offset {
whole_local.offset_with_meta_(offset, MemPlaceMeta::None, self)? // This offset is always inbounds, no need to check it again.
whole_local.offset_with_meta_(
offset,
OffsetMode::Wrapping,
MemPlaceMeta::None,
self,
)?
} else { } else {
// Preserve wide place metadata, do not call `offset`. // Preserve wide place metadata, do not call `offset`.
whole_local whole_local

View File

@ -19,6 +19,15 @@ use rustc_target::abi::{self, VariantIdx};
use super::{InterpCx, InterpResult, MPlaceTy, Machine, MemPlaceMeta, OpTy, Provenance, Scalar}; use super::{InterpCx, InterpResult, MPlaceTy, Machine, MemPlaceMeta, OpTy, Provenance, Scalar};
/// Describes the constraints placed on offset-projections.
#[derive(Copy, Clone, Debug)]
pub enum OffsetMode {
/// The offset has to be inbounds, like `ptr::offset`.
Inbounds,
/// No constraints, just wrap around the edge of the address space.
Wrapping,
}
/// A thing that we can project into, and that has a layout. /// A thing that we can project into, and that has a layout.
pub trait Projectable<'tcx, Prov: Provenance>: Sized + std::fmt::Debug { pub trait Projectable<'tcx, Prov: Provenance>: Sized + std::fmt::Debug {
/// Get the layout. /// Get the layout.
@ -53,6 +62,7 @@ pub trait Projectable<'tcx, Prov: Provenance>: Sized + std::fmt::Debug {
fn offset_with_meta<'mir, M: Machine<'mir, 'tcx, Provenance = Prov>>( fn offset_with_meta<'mir, M: Machine<'mir, 'tcx, Provenance = Prov>>(
&self, &self,
offset: Size, offset: Size,
mode: OffsetMode,
meta: MemPlaceMeta<Prov>, meta: MemPlaceMeta<Prov>,
layout: TyAndLayout<'tcx>, layout: TyAndLayout<'tcx>,
ecx: &InterpCx<'mir, 'tcx, M>, ecx: &InterpCx<'mir, 'tcx, M>,
@ -65,7 +75,7 @@ pub trait Projectable<'tcx, Prov: Provenance>: Sized + std::fmt::Debug {
ecx: &InterpCx<'mir, 'tcx, M>, ecx: &InterpCx<'mir, 'tcx, M>,
) -> InterpResult<'tcx, Self> { ) -> InterpResult<'tcx, Self> {
assert!(layout.is_sized()); assert!(layout.is_sized());
self.offset_with_meta(offset, MemPlaceMeta::None, layout, ecx) self.offset_with_meta(offset, OffsetMode::Inbounds, MemPlaceMeta::None, layout, ecx)
} }
fn transmute<'mir, M: Machine<'mir, 'tcx, Provenance = Prov>>( fn transmute<'mir, M: Machine<'mir, 'tcx, Provenance = Prov>>(
@ -75,7 +85,7 @@ pub trait Projectable<'tcx, Prov: Provenance>: Sized + std::fmt::Debug {
) -> InterpResult<'tcx, Self> { ) -> InterpResult<'tcx, Self> {
assert!(self.layout().is_sized() && layout.is_sized()); assert!(self.layout().is_sized() && layout.is_sized());
assert_eq!(self.layout().size, layout.size); assert_eq!(self.layout().size, layout.size);
self.offset_with_meta(Size::ZERO, MemPlaceMeta::None, layout, ecx) self.offset_with_meta(Size::ZERO, OffsetMode::Wrapping, MemPlaceMeta::None, layout, ecx)
} }
/// Convert this to an `OpTy`. This might be an irreversible transformation, but is useful for /// Convert this to an `OpTy`. This might be an irreversible transformation, but is useful for
@ -102,7 +112,17 @@ impl<'tcx, 'a, Prov: Provenance, P: Projectable<'tcx, Prov>> ArrayIterator<'tcx,
ecx: &InterpCx<'mir, 'tcx, M>, ecx: &InterpCx<'mir, 'tcx, M>,
) -> InterpResult<'tcx, Option<(u64, P)>> { ) -> InterpResult<'tcx, Option<(u64, P)>> {
let Some(idx) = self.range.next() else { return Ok(None) }; let Some(idx) = self.range.next() else { return Ok(None) };
Ok(Some((idx, self.base.offset(self.stride * idx, self.field_layout, ecx)?))) // We use `Wrapping` here since the offset has already been checked when the iterator was created.
Ok(Some((
idx,
self.base.offset_with_meta(
self.stride * idx,
OffsetMode::Wrapping,
MemPlaceMeta::None,
self.field_layout,
ecx,
)?,
)))
} }
} }
@ -157,7 +177,7 @@ where
(MemPlaceMeta::None, offset) (MemPlaceMeta::None, offset)
}; };
base.offset_with_meta(offset, meta, field_layout, self) base.offset_with_meta(offset, OffsetMode::Inbounds, meta, field_layout, self)
} }
/// Downcasting to an enum variant. /// Downcasting to an enum variant.
@ -246,6 +266,9 @@ where
}; };
let len = base.len(self)?; let len = base.len(self)?;
let field_layout = base.layout().field(self, 0); let field_layout = base.layout().field(self, 0);
// Ensure that all the offsets are in-bounds once, up-front.
base.offset(len * stride, self.layout_of(self.tcx.types.unit).unwrap(), self)?;
// Create the iterator.
Ok(ArrayIterator { base, range: 0..len, stride, field_layout, _phantom: PhantomData }) Ok(ArrayIterator { base, range: 0..len, stride, field_layout, _phantom: PhantomData })
} }
@ -303,7 +326,7 @@ where
}; };
let layout = self.layout_of(ty)?; let layout = self.layout_of(ty)?;
base.offset_with_meta(from_offset, meta, layout, self) base.offset_with_meta(from_offset, OffsetMode::Inbounds, meta, layout, self)
} }
/// Applying a general projection /// Applying a general projection

View File

@ -88,4 +88,7 @@ const PARTIAL_POINTER: () = unsafe {
const VALID_ENUM1: E = { let e = E::A; e }; const VALID_ENUM1: E = { let e = E::A; e };
const VALID_ENUM2: Result<&'static [u8], ()> = { let e = Err(()); e }; const VALID_ENUM2: Result<&'static [u8], ()> = { let e = Err(()); e };
// Htting the (non-integer) array code in validation with an immediate local.
const VALID_ARRAY: [Option<i32>; 0] = { let e = [None; 0]; e };
fn main() {} fn main() {}