Skip to content

Commit 9007296

Browse files
committed
Add check_mplace_ptr convenience method; provide ptr-normalization methods for mplace and op to avoid repeated int-to-ptr casting during validation.
Also change Memory::copy to work on `Pointer` instead of `Scalar`. Also rename some methods from to_* to assert_* that will panic if their precondition is not met.
1 parent eed52de commit 9007296

File tree

8 files changed

+108
-66
lines changed

8 files changed

+108
-66
lines changed

src/librustc_mir/const_eval.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ fn op_to_const<'tcx>(
109109
// `Immediate` is when we are called from `const_field`, and that `Immediate`
110110
// comes from a constant so it can happen have `Undef`, because the indirect
111111
// memory that was read had undefined bytes.
112-
let mplace = op.to_mem_place();
112+
let mplace = op.assert_mem_place();
113113
let ptr = mplace.ptr.to_ptr().unwrap();
114114
let alloc = ecx.tcx.alloc_map.lock().unwrap_memory(ptr.alloc_id);
115115
ConstValue::ByRef { offset: ptr.offset, align: mplace.align, alloc }

src/librustc_mir/interpret/memory.rs

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -214,10 +214,8 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
214214
None => Size::from_bytes(self.get(ptr.alloc_id)?.bytes.len() as u64),
215215
};
216216
self.copy(
217-
ptr.into(),
218-
Align::from_bytes(1).unwrap(), // old_align anyway gets checked below by `deallocate`
219-
new_ptr.into(),
220-
new_align,
217+
ptr,
218+
new_ptr,
221219
old_size.min(new_size),
222220
/*nonoverlapping*/ true,
223221
)?;
@@ -310,6 +308,9 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
310308
/// `Pointer` they need. And even if you already have a `Pointer`, call this method
311309
/// to make sure it is sufficiently aligned and not dangling. Not doing that may
312310
/// cause ICEs.
311+
///
312+
/// Most of the time you should use `check_mplace_access`, but when you just have a pointer,
313+
/// this method is still appropriate.
313314
pub fn check_ptr_access(
314315
&self,
315316
sptr: Scalar<M::PointerTag>,
@@ -751,39 +752,26 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
751752
self.get(ptr.alloc_id)?.read_c_str(self, ptr)
752753
}
753754

754-
/// Performs appropriate bounds checks.
755+
/// Expects the caller to have checked bounds and alignment.
755756
pub fn copy(
756757
&mut self,
757-
src: Scalar<M::PointerTag>,
758-
src_align: Align,
759-
dest: Scalar<M::PointerTag>,
760-
dest_align: Align,
758+
src: Pointer<M::PointerTag>,
759+
dest: Pointer<M::PointerTag>,
761760
size: Size,
762761
nonoverlapping: bool,
763762
) -> InterpResult<'tcx> {
764-
self.copy_repeatedly(src, src_align, dest, dest_align, size, 1, nonoverlapping)
763+
self.copy_repeatedly(src, dest, size, 1, nonoverlapping)
765764
}
766765

767-
/// Performs appropriate bounds checks.
766+
/// Expects the caller to have checked bounds and alignment.
768767
pub fn copy_repeatedly(
769768
&mut self,
770-
src: Scalar<M::PointerTag>,
771-
src_align: Align,
772-
dest: Scalar<M::PointerTag>,
773-
dest_align: Align,
769+
src: Pointer<M::PointerTag>,
770+
dest: Pointer<M::PointerTag>,
774771
size: Size,
775772
length: u64,
776773
nonoverlapping: bool,
777774
) -> InterpResult<'tcx> {
778-
// We need to check *both* before early-aborting due to the size being 0.
779-
let (src, dest) = match (self.check_ptr_access(src, size, src_align)?,
780-
self.check_ptr_access(dest, size * length, dest_align)?)
781-
{
782-
(Some(src), Some(dest)) => (src, dest),
783-
// One of the two sizes is 0.
784-
_ => return Ok(()),
785-
};
786-
787775
// first copy the relocations to a temporary buffer, because
788776
// `get_bytes_mut` will clear the relocations, which is correct,
789777
// since we don't want to keep any relocations at the target.

src/librustc_mir/interpret/operand.rs

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -123,23 +123,23 @@ pub enum Operand<Tag=(), Id=AllocId> {
123123

124124
impl<Tag> Operand<Tag> {
125125
#[inline]
126-
pub fn to_mem_place(self) -> MemPlace<Tag>
126+
pub fn assert_mem_place(self) -> MemPlace<Tag>
127127
where Tag: ::std::fmt::Debug
128128
{
129129
match self {
130130
Operand::Indirect(mplace) => mplace,
131-
_ => bug!("to_mem_place: expected Operand::Indirect, got {:?}", self),
131+
_ => bug!("assert_mem_place: expected Operand::Indirect, got {:?}", self),
132132

133133
}
134134
}
135135

136136
#[inline]
137-
pub fn to_immediate(self) -> Immediate<Tag>
137+
pub fn assert_immediate(self) -> Immediate<Tag>
138138
where Tag: ::std::fmt::Debug
139139
{
140140
match self {
141141
Operand::Immediate(imm) => imm,
142-
_ => bug!("to_immediate: expected Operand::Immediate, got {:?}", self),
142+
_ => bug!("assert_immediate: expected Operand::Immediate, got {:?}", self),
143143

144144
}
145145
}
@@ -214,6 +214,19 @@ pub(super) fn from_known_layout<'tcx>(
214214
}
215215

216216
impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
217+
/// Normalice `place.ptr` to a `Pointer` if this is a place and not a ZST.
218+
/// Can be helpful to avoid lots of `force_ptr` calls later, if this place is used a lot.
219+
#[inline]
220+
pub fn normalize_op_ptr(
221+
&self,
222+
op: OpTy<'tcx, M::PointerTag>,
223+
) -> InterpResult<'tcx, OpTy<'tcx, M::PointerTag>> {
224+
match op.try_as_mplace() {
225+
Ok(mplace) => Ok(self.normalize_mplace_ptr(mplace)?.into()),
226+
Err(imm) => Ok(imm.into()), // Nothing to normalize
227+
}
228+
}
229+
217230
/// Try reading an immediate in memory; this is interesting particularly for `ScalarPair`.
218231
/// Returns `None` if the layout does not permit loading this as a value.
219232
fn try_read_immediate_from_mplace(
@@ -224,9 +237,8 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
224237
// Don't touch unsized
225238
return Ok(None);
226239
}
227-
let (ptr, ptr_align) = mplace.to_scalar_ptr_align();
228240

229-
let ptr = match self.memory.check_ptr_access(ptr, mplace.layout.size, ptr_align)? {
241+
let ptr = match self.check_mplace_access(mplace, None)? {
230242
Some(ptr) => ptr,
231243
None => return Ok(Some(ImmTy { // zero-sized type
232244
imm: Immediate::Scalar(Scalar::zst().into()),
@@ -396,7 +408,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
396408
} else {
397409
// The rest should only occur as mplace, we do not use Immediates for types
398410
// allowing such operations. This matches place_projection forcing an allocation.
399-
let mplace = base.to_mem_place();
411+
let mplace = base.assert_mem_place();
400412
self.mplace_projection(mplace, proj_elem)?.into()
401413
}
402414
})

src/librustc_mir/interpret/place.rs

Lines changed: 55 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ impl<'tcx, Tag> MPlaceTy<'tcx, Tag> {
230230
}
231231
}
232232

233+
// These are defined here because they produce a place.
233234
impl<'tcx, Tag: ::std::fmt::Debug + Copy> OpTy<'tcx, Tag> {
234235
#[inline(always)]
235236
pub fn try_as_mplace(self) -> Result<MPlaceTy<'tcx, Tag>, ImmTy<'tcx, Tag>> {
@@ -240,7 +241,7 @@ impl<'tcx, Tag: ::std::fmt::Debug + Copy> OpTy<'tcx, Tag> {
240241
}
241242

242243
#[inline(always)]
243-
pub fn to_mem_place(self) -> MPlaceTy<'tcx, Tag> {
244+
pub fn assert_mem_place(self) -> MPlaceTy<'tcx, Tag> {
244245
self.try_as_mplace().unwrap()
245246
}
246247
}
@@ -263,29 +264,29 @@ impl<'tcx, Tag: ::std::fmt::Debug> Place<Tag> {
263264
}
264265

265266
#[inline]
266-
pub fn to_mem_place(self) -> MemPlace<Tag> {
267+
pub fn assert_mem_place(self) -> MemPlace<Tag> {
267268
match self {
268269
Place::Ptr(mplace) => mplace,
269-
_ => bug!("to_mem_place: expected Place::Ptr, got {:?}", self),
270+
_ => bug!("assert_mem_place: expected Place::Ptr, got {:?}", self),
270271

271272
}
272273
}
273274

274275
#[inline]
275276
pub fn to_scalar_ptr_align(self) -> (Scalar<Tag>, Align) {
276-
self.to_mem_place().to_scalar_ptr_align()
277+
self.assert_mem_place().to_scalar_ptr_align()
277278
}
278279

279280
#[inline]
280281
pub fn to_ptr(self) -> InterpResult<'tcx, Pointer<Tag>> {
281-
self.to_mem_place().to_ptr()
282+
self.assert_mem_place().to_ptr()
282283
}
283284
}
284285

285286
impl<'tcx, Tag: ::std::fmt::Debug> PlaceTy<'tcx, Tag> {
286287
#[inline]
287-
pub fn to_mem_place(self) -> MPlaceTy<'tcx, Tag> {
288-
MPlaceTy { mplace: self.place.to_mem_place(), layout: self.layout }
288+
pub fn assert_mem_place(self) -> MPlaceTy<'tcx, Tag> {
289+
MPlaceTy { mplace: self.place.assert_mem_place(), layout: self.layout }
289290
}
290291
}
291292

@@ -322,8 +323,8 @@ where
322323
Ok(MPlaceTy { mplace, layout })
323324
}
324325

325-
// Take an operand, representing a pointer, and dereference it to a place -- that
326-
// will always be a MemPlace. Lives in `place.rs` because it creates a place.
326+
/// Take an operand, representing a pointer, and dereference it to a place -- that
327+
/// will always be a MemPlace. Lives in `place.rs` because it creates a place.
327328
pub fn deref_operand(
328329
&self,
329330
src: OpTy<'tcx, M::PointerTag>,
@@ -333,6 +334,38 @@ where
333334
self.ref_to_mplace(val)
334335
}
335336

337+
/// Check if the given place is good for memory access with the given
338+
/// size, falling back to the layout's size if `None` (in the latter case,
339+
/// this must be a statically sized type).
340+
///
341+
/// On success, returns `None` for zero-sized accesses (where nothing else is
342+
/// left to do) and a `Pointer` to use for the actual access otherwise.
343+
#[inline]
344+
pub fn check_mplace_access(
345+
&self,
346+
place: MPlaceTy<'tcx, M::PointerTag>,
347+
size: Option<Size>,
348+
) -> InterpResult<'tcx, Option<Pointer<M::PointerTag>>> {
349+
let size = size.unwrap_or_else(|| {
350+
assert!(!place.layout.is_unsized());
351+
assert!(place.meta.is_none());
352+
place.layout.size
353+
});
354+
self.memory.check_ptr_access(place.ptr, size, place.align)
355+
}
356+
357+
/// Normalice `place.ptr` to a `Pointer` if this is not a ZST.
358+
/// Can be helpful to avoid lots of `force_ptr` calls later, if this place is used a lot.
359+
pub fn normalize_mplace_ptr(
360+
&self,
361+
mut place: MPlaceTy<'tcx, M::PointerTag>,
362+
) -> InterpResult<'tcx, MPlaceTy<'tcx, M::PointerTag>> {
363+
if !place.layout.is_zst() {
364+
place.mplace.ptr = self.force_ptr(place.mplace.ptr)?.into();
365+
}
366+
Ok(place)
367+
}
368+
336369
/// Offset a pointer to project to a field. Unlike `place_field`, this is always
337370
/// possible without allocating, so it can take `&self`. Also return the field's layout.
338371
/// This supports both struct and array fields.
@@ -741,14 +774,12 @@ where
741774
value: Immediate<M::PointerTag>,
742775
dest: MPlaceTy<'tcx, M::PointerTag>,
743776
) -> InterpResult<'tcx> {
744-
let (ptr, ptr_align) = dest.to_scalar_ptr_align();
745777
// Note that it is really important that the type here is the right one, and matches the
746778
// type things are read at. In case `src_val` is a `ScalarPair`, we don't do any magic here
747779
// to handle padding properly, which is only correct if we never look at this data with the
748780
// wrong type.
749-
assert!(!dest.layout.is_unsized());
750781

751-
let ptr = match self.memory.check_ptr_access(ptr, dest.layout.size, ptr_align)? {
782+
let ptr = match self.check_mplace_access(dest, None)? {
752783
Some(ptr) => ptr,
753784
None => return Ok(()), // zero-sized access
754785
};
@@ -850,14 +881,21 @@ where
850881
dest.layout.size
851882
});
852883
assert_eq!(src.meta, dest.meta, "Can only copy between equally-sized instances");
884+
885+
let src = self.check_mplace_access(src, Some(size))?;
886+
let dest = self.check_mplace_access(dest, Some(size))?;
887+
let (src_ptr, dest_ptr) = match (src, dest) {
888+
(Some(src_ptr), Some(dest_ptr)) => (src_ptr, dest_ptr),
889+
(None, None) => return Ok(()), // zero-sized copy
890+
_ => bug!("The pointers should both be Some or both None"),
891+
};
892+
853893
self.memory.copy(
854-
src.ptr, src.align,
855-
dest.ptr, dest.align,
894+
src_ptr,
895+
dest_ptr,
856896
size,
857897
/*nonoverlapping*/ true,
858-
)?;
859-
860-
Ok(())
898+
)
861899
}
862900

863901
/// Copies the data from an operand to a place. The layouts may disagree, but they must

src/librustc_mir/interpret/step.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -209,17 +209,18 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
209209
let dest = self.force_allocation(dest)?;
210210
let length = dest.len(self)?;
211211

212-
if length > 0 {
213-
// write the first
212+
if let Some(first_ptr) = self.check_mplace_access(dest, None)? {
213+
// Write the first.
214214
let first = self.mplace_field(dest, 0)?;
215215
self.copy_op(op, first.into())?;
216216

217217
if length > 1 {
218-
// copy the rest
219-
let (dest, dest_align) = first.to_scalar_ptr_align();
220-
let rest = dest.ptr_offset(first.layout.size, self)?;
218+
let elem_size = first.layout.size;
219+
// Copy the rest. This is performance-sensitive code
220+
// for big static/const arrays!
221+
let rest_ptr = first_ptr.offset(elem_size, self)?;
221222
self.memory.copy_repeatedly(
222-
dest, dest_align, rest, dest_align, first.layout.size, length - 1, true
223+
first_ptr, rest_ptr, elem_size, length - 1, /*nonoverlapping:*/true
223224
)?;
224225
}
225226
}

src/librustc_mir/interpret/terminator.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
426426
}
427427
None => {
428428
// Unsized self.
429-
args[0].to_mem_place()
429+
args[0].assert_mem_place()
430430
}
431431
};
432432
// Find and consult vtable

src/librustc_mir/interpret/validity.rs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -440,9 +440,11 @@ impl<'rt, 'mir, 'tcx, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
440440
}
441441
}
442442
}
443-
// Check if we have encountered this pointer+layout combination
444-
// before. Proceed recursively even for ZST, no
445-
// reason to skip them! E.g., `!` is a ZST and we want to validate it.
443+
// Proceed recursively even for ZST, no reason to skip them!
444+
// `!` is a ZST and we want to validate it.
445+
// Normalize before handing `place` to tracking because that will
446+
// check for duplicates.
447+
let place = self.ecx.normalize_mplace_ptr(place)?;
446448
let path = &self.path;
447449
ref_tracking.track(place, || {
448450
// We need to clone the path anyway, make sure it gets created
@@ -548,7 +550,7 @@ impl<'rt, 'mir, 'tcx, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
548550
) -> InterpResult<'tcx> {
549551
match op.layout.ty.sty {
550552
ty::Str => {
551-
let mplace = op.to_mem_place(); // strings are never immediate
553+
let mplace = op.assert_mem_place(); // strings are never immediate
552554
try_validation!(self.ecx.read_str(mplace),
553555
"uninitialized or non-UTF-8 data in str", self.path);
554556
}
@@ -565,7 +567,7 @@ impl<'rt, 'mir, 'tcx, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
565567
return Ok(());
566568
}
567569
// non-ZST array cannot be immediate, slices are never immediate
568-
let mplace = op.to_mem_place();
570+
let mplace = op.assert_mem_place();
569571
// This is the length of the array/slice.
570572
let len = mplace.len(self.ecx)?;
571573
// zero length slices have nothing to be checked
@@ -576,8 +578,8 @@ impl<'rt, 'mir, 'tcx, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
576578
let ty_size = self.ecx.layout_of(tys)?.size;
577579
// This is the size in bytes of the whole array.
578580
let size = ty_size * len;
579-
580-
let ptr = self.ecx.force_ptr(mplace.ptr)?;
581+
// Size is not 0, get a pointer (no cast because we normalized in validate_operand).
582+
let ptr = mplace.ptr.assert_ptr();
581583

582584
// NOTE: Keep this in sync with the handling of integer and float
583585
// types above, in `visit_primitive`.
@@ -633,7 +635,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
633635
/// `ref_tracking_for_consts` can be `None` to avoid recursive checking below references.
634636
/// This also toggles between "run-time" (no recursion) and "compile-time" (with recursion)
635637
/// validation (e.g., pointer values are fine in integers at runtime) and various other const
636-
/// specific validation checks
638+
/// specific validation checks.
637639
pub fn validate_operand(
638640
&self,
639641
op: OpTy<'tcx, M::PointerTag>,
@@ -653,6 +655,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
653655
};
654656

655657
// Run it
658+
let op = self.normalize_op_ptr(op)?; // avoid doing ptr-to-int all the time
656659
visitor.visit_value(op)
657660
}
658661
}

0 commit comments

Comments
 (0)