Skip to content

Commit 7b57152

Browse files
committed
Auto merge of #99101 - RalfJung:interpret-projections, r=oli-obk
interpret: refactor projection handling code Moves our projection handling code into a common file, and avoids the use of a general mplace-based fallback function by have more specialized implementations. mplace_index (and the other slice-related functions) could be more efficient by copy-pasting the body of operand_index. Or we could do some trait magic to share the code between them. But for now this is probably fine. This is the common part of #99013 and #99097. I am seeing some strange perf results so this probably should be its own change so we know which diff caused which perf changes... r? `@oli-obk`
2 parents 1c7b36d + 04b3cd9 commit 7b57152

File tree

7 files changed

+540
-432
lines changed

7 files changed

+540
-432
lines changed

compiler/rustc_const_eval/src/interpret/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ mod memory;
99
mod operand;
1010
mod operator;
1111
mod place;
12+
mod projection;
1213
mod step;
1314
mod terminator;
1415
mod traits;

compiler/rustc_const_eval/src/interpret/operand.rs

+79-133
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
//! Functions concerning immediate values and operands, and reading from operands.
22
//! All high-level functions to read from memory work on operands as sources.
33
4-
use std::convert::TryFrom;
54
use std::fmt::Write;
65

76
use rustc_hir::def::Namespace;
@@ -15,7 +14,7 @@ use rustc_target::abi::{VariantIdx, Variants};
1514

1615
use super::{
1716
alloc_range, from_known_layout, mir_assign_valid_types, AllocId, ConstValue, Frame, GlobalId,
18-
InterpCx, InterpResult, MPlaceTy, Machine, MemPlace, Place, PlaceTy, Pointer,
17+
InterpCx, InterpResult, MPlaceTy, Machine, MemPlace, MemPlaceMeta, Place, PlaceTy, Pointer,
1918
PointerArithmetic, Provenance, Scalar, ScalarMaybeUninit,
2019
};
2120

@@ -253,6 +252,11 @@ impl<'tcx, Tag: Provenance> ImmTy<'tcx, Tag> {
253252
ImmTy { imm, layout }
254253
}
255254

255+
#[inline]
256+
pub fn uninit(layout: TyAndLayout<'tcx>) -> Self {
257+
ImmTy { imm: Immediate::Uninit, layout }
258+
}
259+
256260
#[inline]
257261
pub fn try_from_uint(i: impl Into<u128>, layout: TyAndLayout<'tcx>) -> Option<Self> {
258262
Some(Self::from_scalar(Scalar::try_from_uint(i, layout.size)?, layout))
@@ -280,6 +284,41 @@ impl<'tcx, Tag: Provenance> ImmTy<'tcx, Tag> {
280284
}
281285
}
282286

287+
impl<'tcx, Tag: Provenance> OpTy<'tcx, Tag> {
288+
pub fn len(&self, cx: &impl HasDataLayout) -> InterpResult<'tcx, u64> {
289+
if self.layout.is_unsized() {
290+
// There are no unsized immediates.
291+
self.assert_mem_place().len(cx)
292+
} else {
293+
match self.layout.fields {
294+
abi::FieldsShape::Array { count, .. } => Ok(count),
295+
_ => bug!("len not supported on sized type {:?}", self.layout.ty),
296+
}
297+
}
298+
}
299+
300+
pub fn offset(
301+
&self,
302+
offset: Size,
303+
meta: MemPlaceMeta<Tag>,
304+
layout: TyAndLayout<'tcx>,
305+
cx: &impl HasDataLayout,
306+
) -> InterpResult<'tcx, Self> {
307+
match self.try_as_mplace() {
308+
Ok(mplace) => Ok(mplace.offset(offset, meta, layout, cx)?.into()),
309+
Err(imm) => {
310+
assert!(
311+
matches!(*imm, Immediate::Uninit),
312+
"Scalar/ScalarPair cannot be offset into"
313+
);
314+
assert!(!meta.has_meta()); // no place to store metadata here
315+
// Every part of an uninit is uninit.
316+
Ok(ImmTy::uninit(layout).into())
317+
}
318+
}
319+
}
320+
}
321+
283322
impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
284323
/// Try reading an immediate in memory; this is interesting particularly for `ScalarPair`.
285324
/// Returns `None` if the layout does not permit loading this as a value.
@@ -296,11 +335,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
296335
}
297336

298337
let Some(alloc) = self.get_place_alloc(mplace)? else {
299-
return Ok(Some(ImmTy {
300-
// zero-sized type can be left uninit
301-
imm: Immediate::Uninit,
302-
layout: mplace.layout,
303-
}));
338+
// zero-sized type can be left uninit
339+
return Ok(Some(ImmTy::uninit(mplace.layout)));
304340
};
305341

306342
// It may seem like all types with `Scalar` or `ScalarPair` ABI are fair game at this point.
@@ -367,6 +403,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
367403
/// This flag exists only for validity checking.
368404
///
369405
/// This is an internal function that should not usually be used; call `read_immediate` instead.
406+
/// ConstProp needs it, though.
370407
pub fn read_immediate_raw(
371408
&self,
372409
src: &OpTy<'tcx, M::PointerTag>,
@@ -421,123 +458,28 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
421458
Ok(str)
422459
}
423460

424-
/// Projection functions
425-
pub fn operand_field(
426-
&self,
427-
op: &OpTy<'tcx, M::PointerTag>,
428-
field: usize,
429-
) -> InterpResult<'tcx, OpTy<'tcx, M::PointerTag>> {
430-
let base = match op.try_as_mplace() {
431-
Ok(ref mplace) => {
432-
// We can reuse the mplace field computation logic for indirect operands.
433-
let field = self.mplace_field(mplace, field)?;
434-
return Ok(field.into());
435-
}
436-
Err(value) => value,
437-
};
438-
439-
let field_layout = base.layout.field(self, field);
440-
let offset = base.layout.fields.offset(field);
441-
// This makes several assumptions about what layouts we will encounter; we match what
442-
// codegen does as good as we can (see `extract_field` in `rustc_codegen_ssa/src/mir/operand.rs`).
443-
let field_val: Immediate<_> = match (*base, base.layout.abi) {
444-
// the field contains no information, can be left uninit
445-
_ if field_layout.is_zst() => Immediate::Uninit,
446-
// the field covers the entire type
447-
_ if field_layout.size == base.layout.size => {
448-
assert!(match (base.layout.abi, field_layout.abi) {
449-
(Abi::Scalar(..), Abi::Scalar(..)) => true,
450-
(Abi::ScalarPair(..), Abi::ScalarPair(..)) => true,
451-
_ => false,
452-
});
453-
assert!(offset.bytes() == 0);
454-
*base
455-
}
456-
// extract fields from types with `ScalarPair` ABI
457-
(Immediate::ScalarPair(a_val, b_val), Abi::ScalarPair(a, b)) => {
458-
assert!(matches!(field_layout.abi, Abi::Scalar(..)));
459-
Immediate::from(if offset.bytes() == 0 {
460-
debug_assert_eq!(field_layout.size, a.size(self));
461-
a_val
462-
} else {
463-
debug_assert_eq!(offset, a.size(self).align_to(b.align(self).abi));
464-
debug_assert_eq!(field_layout.size, b.size(self));
465-
b_val
466-
})
467-
}
468-
_ => span_bug!(
469-
self.cur_span(),
470-
"invalid field access on immediate {}, layout {:#?}",
471-
base,
472-
base.layout
473-
),
474-
};
475-
476-
Ok(OpTy { op: Operand::Immediate(field_val), layout: field_layout, align: None })
477-
}
478-
479-
pub fn operand_index(
480-
&self,
481-
op: &OpTy<'tcx, M::PointerTag>,
482-
index: u64,
483-
) -> InterpResult<'tcx, OpTy<'tcx, M::PointerTag>> {
484-
if let Ok(index) = usize::try_from(index) {
485-
// We can just treat this as a field.
486-
self.operand_field(op, index)
487-
} else {
488-
// Indexing into a big array. This must be an mplace.
489-
let mplace = op.assert_mem_place();
490-
Ok(self.mplace_index(&mplace, index)?.into())
491-
}
492-
}
493-
494-
pub fn operand_downcast(
495-
&self,
496-
op: &OpTy<'tcx, M::PointerTag>,
497-
variant: VariantIdx,
498-
) -> InterpResult<'tcx, OpTy<'tcx, M::PointerTag>> {
499-
Ok(match op.try_as_mplace() {
500-
Ok(ref mplace) => self.mplace_downcast(mplace, variant)?.into(),
501-
Err(..) => {
502-
// Downcasts only change the layout.
503-
// (In particular, no check about whether this is even the active variant -- that's by design,
504-
// see https://github.com/rust-lang/rust/issues/93688#issuecomment-1032929496.)
505-
let layout = op.layout.for_variant(self, variant);
506-
OpTy { layout, ..*op }
507-
}
508-
})
509-
}
510-
511-
#[instrument(skip(self), level = "debug")]
512-
pub fn operand_projection(
513-
&self,
514-
base: &OpTy<'tcx, M::PointerTag>,
515-
proj_elem: mir::PlaceElem<'tcx>,
516-
) -> InterpResult<'tcx, OpTy<'tcx, M::PointerTag>> {
517-
use rustc_middle::mir::ProjectionElem::*;
518-
Ok(match proj_elem {
519-
Field(field, _) => self.operand_field(base, field.index())?,
520-
Downcast(_, variant) => self.operand_downcast(base, variant)?,
521-
Deref => self.deref_operand(base)?.into(),
522-
Subslice { .. } | ConstantIndex { .. } | Index(_) => {
523-
// The rest should only occur as mplace, we do not use Immediates for types
524-
// allowing such operations. This matches place_projection forcing an allocation.
525-
let mplace = base.assert_mem_place();
526-
self.mplace_projection(&mplace, proj_elem)?.into()
527-
}
528-
})
529-
}
530-
531461
/// Converts a repr(simd) operand into an operand where `place_index` accesses the SIMD elements.
532462
/// Also returns the number of elements.
463+
///
464+
/// Can (but does not always) trigger UB if `op` is uninitialized.
533465
pub fn operand_to_simd(
534466
&self,
535-
base: &OpTy<'tcx, M::PointerTag>,
467+
op: &OpTy<'tcx, M::PointerTag>,
536468
) -> InterpResult<'tcx, (MPlaceTy<'tcx, M::PointerTag>, u64)> {
537469
// Basically we just transmute this place into an array following simd_size_and_type.
538470
// This only works in memory, but repr(simd) types should never be immediates anyway.
539-
assert!(base.layout.ty.is_simd());
540-
self.mplace_to_simd(&base.assert_mem_place())
471+
assert!(op.layout.ty.is_simd());
472+
match op.try_as_mplace() {
473+
Ok(mplace) => self.mplace_to_simd(&mplace),
474+
Err(imm) => match *imm {
475+
Immediate::Uninit => {
476+
throw_ub!(InvalidUninitBytes(None))
477+
}
478+
Immediate::Scalar(..) | Immediate::ScalarPair(..) => {
479+
bug!("arrays/slices can never have Scalar/ScalarPair layout")
480+
}
481+
},
482+
}
541483
}
542484

543485
/// Read from a local. Will not actually access the local if reading from a ZST.
@@ -582,30 +524,34 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
582524
/// avoid allocations.
583525
pub fn eval_place_to_op(
584526
&self,
585-
place: mir::Place<'tcx>,
527+
mir_place: mir::Place<'tcx>,
586528
layout: Option<TyAndLayout<'tcx>>,
587529
) -> InterpResult<'tcx, OpTy<'tcx, M::PointerTag>> {
588530
// Do not use the layout passed in as argument if the base we are looking at
589531
// here is not the entire place.
590-
let layout = if place.projection.is_empty() { layout } else { None };
532+
let layout = if mir_place.projection.is_empty() { layout } else { None };
591533

592-
let base_op = self.local_to_op(self.frame(), place.local, layout)?;
593-
594-
let op = place
595-
.projection
596-
.iter()
597-
.try_fold(base_op, |op, elem| self.operand_projection(&op, elem))?;
534+
let mut op = self.local_to_op(self.frame(), mir_place.local, layout)?;
535+
// Using `try_fold` turned out to be bad for performance, hence the loop.
536+
for elem in mir_place.projection.iter() {
537+
op = self.operand_projection(&op, elem)?
538+
}
598539

599540
trace!("eval_place_to_op: got {:?}", *op);
600541
// Sanity-check the type we ended up with.
601-
debug_assert!(mir_assign_valid_types(
602-
*self.tcx,
603-
self.param_env,
604-
self.layout_of(self.subst_from_current_frame_and_normalize_erasing_regions(
605-
place.ty(&self.frame().body.local_decls, *self.tcx).ty
606-
)?)?,
607-
op.layout,
608-
));
542+
debug_assert!(
543+
mir_assign_valid_types(
544+
*self.tcx,
545+
self.param_env,
546+
self.layout_of(self.subst_from_current_frame_and_normalize_erasing_regions(
547+
mir_place.ty(&self.frame().body.local_decls, *self.tcx).ty
548+
)?)?,
549+
op.layout,
550+
),
551+
"eval_place of a MIR place with type {:?} produced an interpreter operand with type {:?}",
552+
mir_place.ty(&self.frame().body.local_decls, *self.tcx).ty,
553+
op.layout.ty,
554+
);
609555
Ok(op)
610556
}
611557

0 commit comments

Comments
 (0)