Skip to content

Commit 4fc6b33

Browse files
committed
Auto merge of #114011 - RalfJung:place-projection, r=oli-obk
interpret: Unify projections for MPlaceTy, PlaceTy, OpTy For ~forever, we didn't really have proper shared code for handling projections into those three types. This is mostly because `PlaceTy` projections require `&mut self`: they might have to `force_allocate` to be able to represent a project part-way into a local. This PR finally fixes that, by enhancing `Place::Local` with an `offset` so that such an optimized place can point into a part of a place without having requiring an in-memory representation. If we later write to that place, we will still do `force_allocate` -- for now we don't have an optimized path in `write_immediate` that would avoid allocation for partial overwrites of immediately stored locals. But in `write_immediate` we have `&mut self` so at least this no longer pollutes all our type signatures. (Ironically, I seem to distantly remember that many years ago, `Place::Local` *did* have an `offset`, and I removed it to simplify things. I guess I didn't realize why it was so useful... I am also not sure if this was actually used to achieve place projection on `&self` back then.) The `offset` had type `Option<Size>`, where `None` represent "no projection was applied". This is needed because locals *can* be unsized (when they are arguments) but `Place::Local` cannot store metadata: if the offset is `None`, this refers to the entire local, so we can use the metadata of the local itself (which must be indirect); if a projection gets applied, since the local is indirect, it will turn into a `Place::Ptr`. (Note that even for indirect locals we can have `Place::Local`: when the local appears in MIR, we always start with `Place::Local`, and only check `frame.locals` later. We could eagerly normalize to `Place::Ptr` but I don't think that would actually simplify things much.) Having done all that, we can finally properly abstract projections: we have a new `Projectable` trait that has the basic methods required for projecting, and then all projection methods are implemented for anything that implements that trait. We can even implement it for `ImmTy`! (Not that we need that, but it seems neat.) The visitor can be greatly simplified; it doesn't need its own trait any more but it can use the `Projectable` trait. We also don't need the separate `Mut` visitor any more; that was required only to reflect that projections on `PlaceTy` needed `&mut self`. It is possible that there are some more `&mut self` that can now become `&self`... I guess we'll notice that over time. r? `@oli-obk`
2 parents 23405bb + d127600 commit 4fc6b33

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+1180
-1315
lines changed

compiler/rustc_abi/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1189,7 +1189,7 @@ impl FieldsShape {
11891189
}
11901190
FieldsShape::Array { stride, count } => {
11911191
let i = u64::try_from(i).unwrap();
1192-
assert!(i < count);
1192+
assert!(i < count, "tried to access field {} of array with {} fields", i, count);
11931193
stride * i
11941194
}
11951195
FieldsShape::Arbitrary { ref offsets, .. } => offsets[FieldIdx::from_usize(i)],

compiler/rustc_const_eval/messages.ftl

+4-3
Original file line numberDiff line numberDiff line change
@@ -408,8 +408,11 @@ const_eval_undefined_behavior =
408408
const_eval_undefined_behavior_note =
409409
The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior.
410410
411+
const_eval_uninhabited_enum_tag = {$front_matter}: encountered an uninhabited enum variant
412+
const_eval_uninhabited_enum_variant_read =
413+
read discriminant of an uninhabited enum variant
411414
const_eval_uninhabited_enum_variant_written =
412-
writing discriminant of an uninhabited enum
415+
writing discriminant of an uninhabited enum variant
413416
const_eval_uninhabited_val = {$front_matter}: encountered a value of uninhabited type `{$ty}`
414417
const_eval_uninit = {$front_matter}: encountered uninitialized bytes
415418
const_eval_uninit_bool = {$front_matter}: encountered uninitialized memory, but expected a boolean
@@ -423,8 +426,6 @@ const_eval_uninit_int = {$front_matter}: encountered uninitialized memory, but e
423426
const_eval_uninit_raw_ptr = {$front_matter}: encountered uninitialized memory, but expected a raw pointer
424427
const_eval_uninit_ref = {$front_matter}: encountered uninitialized memory, but expected a reference
425428
const_eval_uninit_str = {$front_matter}: encountered uninitialized data in `str`
426-
const_eval_uninit_unsized_local =
427-
unsized local is used while uninitialized
428429
const_eval_unreachable = entering unreachable code
429430
const_eval_unreachable_unwind =
430431
unwinding past a stack frame that does not allow unwinding

compiler/rustc_const_eval/src/const_eval/mod.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,8 @@ pub(crate) fn try_destructure_mir_constant_for_diagnostics<'tcx>(
101101
return None;
102102
}
103103
ty::Adt(def, _) => {
104-
let variant = ecx.read_discriminant(&op).ok()?.1;
105-
let down = ecx.operand_downcast(&op, variant).ok()?;
104+
let variant = ecx.read_discriminant(&op).ok()?;
105+
let down = ecx.project_downcast(&op, variant).ok()?;
106106
(def.variants()[variant].fields.len(), Some(variant), down)
107107
}
108108
ty::Tuple(args) => (args.len(), None, op),
@@ -111,7 +111,7 @@ pub(crate) fn try_destructure_mir_constant_for_diagnostics<'tcx>(
111111

112112
let fields_iter = (0..field_count)
113113
.map(|i| {
114-
let field_op = ecx.operand_field(&down, i).ok()?;
114+
let field_op = ecx.project_field(&down, i).ok()?;
115115
let val = op_to_const(&ecx, &field_op);
116116
Some((val, field_op.layout.ty))
117117
})

compiler/rustc_const_eval/src/const_eval/valtrees.rs

+10-12
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ use super::eval_queries::{mk_eval_cx, op_to_const};
22
use super::machine::CompileTimeEvalContext;
33
use super::{ValTreeCreationError, ValTreeCreationResult, VALTREE_MAX_NODES};
44
use crate::const_eval::CanAccessStatics;
5+
use crate::interpret::MPlaceTy;
56
use crate::interpret::{
67
intern_const_alloc_recursive, ConstValue, ImmTy, Immediate, InternKind, MemPlaceMeta,
7-
MemoryKind, PlaceTy, Scalar,
8+
MemoryKind, PlaceTy, Projectable, Scalar,
89
};
9-
use crate::interpret::{MPlaceTy, Value};
1010
use rustc_middle::ty::{self, ScalarInt, Ty, TyCtxt};
1111
use rustc_span::source_map::DUMMY_SP;
1212
use rustc_target::abi::{Align, FieldIdx, VariantIdx, FIRST_VARIANT};
@@ -20,15 +20,15 @@ fn branches<'tcx>(
2020
num_nodes: &mut usize,
2121
) -> ValTreeCreationResult<'tcx> {
2222
let place = match variant {
23-
Some(variant) => ecx.mplace_downcast(&place, variant).unwrap(),
23+
Some(variant) => ecx.project_downcast(place, variant).unwrap(),
2424
None => *place,
2525
};
2626
let variant = variant.map(|variant| Some(ty::ValTree::Leaf(ScalarInt::from(variant.as_u32()))));
2727
debug!(?place, ?variant);
2828

2929
let mut fields = Vec::with_capacity(n);
3030
for i in 0..n {
31-
let field = ecx.mplace_field(&place, i).unwrap();
31+
let field = ecx.project_field(&place, i).unwrap();
3232
let valtree = const_to_valtree_inner(ecx, &field, num_nodes)?;
3333
fields.push(Some(valtree));
3434
}
@@ -55,13 +55,11 @@ fn slice_branches<'tcx>(
5555
place: &MPlaceTy<'tcx>,
5656
num_nodes: &mut usize,
5757
) -> ValTreeCreationResult<'tcx> {
58-
let n = place
59-
.len(&ecx.tcx.tcx)
60-
.unwrap_or_else(|_| panic!("expected to use len of place {:?}", place));
58+
let n = place.len(ecx).unwrap_or_else(|_| panic!("expected to use len of place {:?}", place));
6159

6260
let mut elems = Vec::with_capacity(n as usize);
6361
for i in 0..n {
64-
let place_elem = ecx.mplace_index(place, i).unwrap();
62+
let place_elem = ecx.project_index(place, i).unwrap();
6563
let valtree = const_to_valtree_inner(ecx, &place_elem, num_nodes)?;
6664
elems.push(valtree);
6765
}
@@ -132,7 +130,7 @@ pub(crate) fn const_to_valtree_inner<'tcx>(
132130
bug!("uninhabited types should have errored and never gotten converted to valtree")
133131
}
134132

135-
let Ok((_, variant)) = ecx.read_discriminant(&place.into()) else {
133+
let Ok(variant) = ecx.read_discriminant(&place.into()) else {
136134
return Err(ValTreeCreationError::Other);
137135
};
138136
branches(ecx, place, def.variant(variant).fields.len(), def.is_enum().then_some(variant), num_nodes)
@@ -386,7 +384,7 @@ fn valtree_into_mplace<'tcx>(
386384
debug!(?variant);
387385

388386
(
389-
place.project_downcast(ecx, variant_idx).unwrap(),
387+
ecx.project_downcast(place, variant_idx).unwrap(),
390388
&branches[1..],
391389
Some(variant_idx),
392390
)
@@ -401,7 +399,7 @@ fn valtree_into_mplace<'tcx>(
401399
debug!(?i, ?inner_valtree);
402400

403401
let mut place_inner = match ty.kind() {
404-
ty::Str | ty::Slice(_) => ecx.mplace_index(&place, i as u64).unwrap(),
402+
ty::Str | ty::Slice(_) => ecx.project_index(place, i as u64).unwrap(),
405403
_ if !ty.is_sized(*ecx.tcx, ty::ParamEnv::empty())
406404
&& i == branches.len() - 1 =>
407405
{
@@ -441,7 +439,7 @@ fn valtree_into_mplace<'tcx>(
441439
)
442440
.unwrap()
443441
}
444-
_ => ecx.mplace_field(&place_adjusted, i).unwrap(),
442+
_ => ecx.project_field(&place_adjusted, i).unwrap(),
445443
};
446444

447445
debug!(?place_inner);

compiler/rustc_const_eval/src/errors.rs

+11-5
Original file line numberDiff line numberDiff line change
@@ -511,7 +511,8 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {
511511
InvalidUninitBytes(Some(_)) => const_eval_invalid_uninit_bytes,
512512
DeadLocal => const_eval_dead_local,
513513
ScalarSizeMismatch(_) => const_eval_scalar_size_mismatch,
514-
UninhabitedEnumVariantWritten => const_eval_uninhabited_enum_variant_written,
514+
UninhabitedEnumVariantWritten(_) => const_eval_uninhabited_enum_variant_written,
515+
UninhabitedEnumVariantRead(_) => const_eval_uninhabited_enum_variant_read,
515516
Validation(e) => e.diagnostic_message(),
516517
Custom(x) => (x.msg)(),
517518
}
@@ -535,7 +536,8 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {
535536
| InvalidMeta(InvalidMetaKind::TooBig)
536537
| InvalidUninitBytes(None)
537538
| DeadLocal
538-
| UninhabitedEnumVariantWritten => {}
539+
| UninhabitedEnumVariantWritten(_)
540+
| UninhabitedEnumVariantRead(_) => {}
539541
BoundsCheckFailed { len, index } => {
540542
builder.set_arg("len", len);
541543
builder.set_arg("index", index);
@@ -623,6 +625,7 @@ impl<'tcx> ReportErrorExt for ValidationErrorInfo<'tcx> {
623625
UnsafeCell => const_eval_unsafe_cell,
624626
UninhabitedVal { .. } => const_eval_uninhabited_val,
625627
InvalidEnumTag { .. } => const_eval_invalid_enum_tag,
628+
UninhabitedEnumTag => const_eval_uninhabited_enum_tag,
626629
UninitEnumTag => const_eval_uninit_enum_tag,
627630
UninitStr => const_eval_uninit_str,
628631
Uninit { expected: ExpectedKind::Bool } => const_eval_uninit_bool,
@@ -760,7 +763,8 @@ impl<'tcx> ReportErrorExt for ValidationErrorInfo<'tcx> {
760763
| InvalidMetaSliceTooLarge { .. }
761764
| InvalidMetaTooLarge { .. }
762765
| DanglingPtrUseAfterFree { .. }
763-
| DanglingPtrOutOfBounds { .. } => {}
766+
| DanglingPtrOutOfBounds { .. }
767+
| UninhabitedEnumTag => {}
764768
}
765769
}
766770
}
@@ -835,7 +839,9 @@ impl<'tcx> ReportErrorExt for InvalidProgramInfo<'tcx> {
835839
rustc_middle::error::middle_adjust_for_foreign_abi_error
836840
}
837841
InvalidProgramInfo::SizeOfUnsizedType(_) => const_eval_size_of_unsized,
838-
InvalidProgramInfo::UninitUnsizedLocal => const_eval_uninit_unsized_local,
842+
InvalidProgramInfo::ConstPropNonsense => {
843+
panic!("We had const-prop nonsense, this should never be printed")
844+
}
839845
}
840846
}
841847
fn add_args<G: EmissionGuarantee>(
@@ -846,7 +852,7 @@ impl<'tcx> ReportErrorExt for InvalidProgramInfo<'tcx> {
846852
match self {
847853
InvalidProgramInfo::TooGeneric
848854
| InvalidProgramInfo::AlreadyReported(_)
849-
| InvalidProgramInfo::UninitUnsizedLocal => {}
855+
| InvalidProgramInfo::ConstPropNonsense => {}
850856
InvalidProgramInfo::Layout(e) => {
851857
let diag: DiagnosticBuilder<'_, ()> = e.into_diagnostic().into_diagnostic(handler);
852858
for (name, val) in diag.args() {

compiler/rustc_const_eval/src/interpret/cast.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -420,8 +420,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
420420
if cast_ty_field.is_zst() {
421421
continue;
422422
}
423-
let src_field = self.operand_field(src, i)?;
424-
let dst_field = self.place_field(dest, i)?;
423+
let src_field = self.project_field(src, i)?;
424+
let dst_field = self.project_field(dest, i)?;
425425
if src_field.layout.ty == cast_ty_field.ty {
426426
self.copy_op(&src_field, &dst_field, /*allow_transmute*/ false)?;
427427
} else {

compiler/rustc_const_eval/src/interpret/discriminant.rs

+48-20
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Functions for reading and writing discriminants of multi-variant layouts (enums and generators).
22
3-
use rustc_middle::ty::layout::{LayoutOf, PrimitiveExt};
3+
use rustc_middle::ty::layout::{LayoutOf, PrimitiveExt, TyAndLayout};
44
use rustc_middle::{mir, ty};
55
use rustc_target::abi::{self, TagEncoding};
66
use rustc_target::abi::{VariantIdx, Variants};
@@ -22,7 +22,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
2222
// When evaluating we will always error before even getting here, but ConstProp 'executes'
2323
// dead code, so we cannot ICE here.
2424
if dest.layout.for_variant(self, variant_index).abi.is_uninhabited() {
25-
throw_ub!(UninhabitedEnumVariantWritten)
25+
throw_ub!(UninhabitedEnumVariantWritten(variant_index))
2626
}
2727

2828
match dest.layout.variants {
@@ -47,7 +47,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
4747
let size = tag_layout.size(self);
4848
let tag_val = size.truncate(discr_val);
4949

50-
let tag_dest = self.place_field(dest, tag_field)?;
50+
let tag_dest = self.project_field(dest, tag_field)?;
5151
self.write_scalar(Scalar::from_uint(tag_val, size), &tag_dest)?;
5252
}
5353
abi::Variants::Multiple {
@@ -78,7 +78,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
7878
&niche_start_val,
7979
)?;
8080
// Write result.
81-
let niche_dest = self.place_field(dest, tag_field)?;
81+
let niche_dest = self.project_field(dest, tag_field)?;
8282
self.write_immediate(*tag_val, &niche_dest)?;
8383
}
8484
}
@@ -93,7 +93,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
9393
pub fn read_discriminant(
9494
&self,
9595
op: &OpTy<'tcx, M::Provenance>,
96-
) -> InterpResult<'tcx, (Scalar<M::Provenance>, VariantIdx)> {
96+
) -> InterpResult<'tcx, VariantIdx> {
9797
trace!("read_discriminant_value {:#?}", op.layout);
9898
// Get type and layout of the discriminant.
9999
let discr_layout = self.layout_of(op.layout.ty.discriminant_ty(*self.tcx))?;
@@ -106,19 +106,22 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
106106
// straight-forward (`TagEncoding::Direct`) or with a niche (`TagEncoding::Niche`).
107107
let (tag_scalar_layout, tag_encoding, tag_field) = match op.layout.variants {
108108
Variants::Single { index } => {
109-
let discr = match op.layout.ty.discriminant_for_variant(*self.tcx, index) {
110-
Some(discr) => {
111-
// This type actually has discriminants.
112-
assert_eq!(discr.ty, discr_layout.ty);
113-
Scalar::from_uint(discr.val, discr_layout.size)
109+
// Do some extra checks on enums.
110+
if op.layout.ty.is_enum() {
111+
// Hilariously, `Single` is used even for 0-variant enums.
112+
// (See https://github.com/rust-lang/rust/issues/89765).
113+
if matches!(op.layout.ty.kind(), ty::Adt(def, ..) if def.variants().is_empty())
114+
{
115+
throw_ub!(UninhabitedEnumVariantRead(index))
114116
}
115-
None => {
116-
// On a type without actual discriminants, variant is 0.
117-
assert_eq!(index.as_u32(), 0);
118-
Scalar::from_uint(index.as_u32(), discr_layout.size)
117+
// For consisteny with `write_discriminant`, and to make sure that
118+
// `project_downcast` cannot fail due to strange layouts, we declare immediate UB
119+
// for uninhabited variants.
120+
if op.layout.for_variant(self, index).abi.is_uninhabited() {
121+
throw_ub!(UninhabitedEnumVariantRead(index))
119122
}
120-
};
121-
return Ok((discr, index));
123+
}
124+
return Ok(index);
122125
}
123126
Variants::Multiple { tag, ref tag_encoding, tag_field, .. } => {
124127
(tag, tag_encoding, tag_field)
@@ -138,13 +141,13 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
138141
let tag_layout = self.layout_of(tag_scalar_layout.primitive().to_int_ty(*self.tcx))?;
139142

140143
// Read tag and sanity-check `tag_layout`.
141-
let tag_val = self.read_immediate(&self.operand_field(op, tag_field)?)?;
144+
let tag_val = self.read_immediate(&self.project_field(op, tag_field)?)?;
142145
assert_eq!(tag_layout.size, tag_val.layout.size);
143146
assert_eq!(tag_layout.abi.is_signed(), tag_val.layout.abi.is_signed());
144147
trace!("tag value: {}", tag_val);
145148

146149
// Figure out which discriminant and variant this corresponds to.
147-
Ok(match *tag_encoding {
150+
let index = match *tag_encoding {
148151
TagEncoding::Direct => {
149152
let scalar = tag_val.to_scalar();
150153
// Generate a specific error if `tag_val` is not an integer.
@@ -172,7 +175,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
172175
}
173176
.ok_or_else(|| err_ub!(InvalidTag(Scalar::from_uint(tag_bits, tag_layout.size))))?;
174177
// Return the cast value, and the index.
175-
(discr_val, index.0)
178+
index.0
176179
}
177180
TagEncoding::Niche { untagged_variant, ref niche_variants, niche_start } => {
178181
let tag_val = tag_val.to_scalar();
@@ -230,7 +233,32 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
230233
// Compute the size of the scalar we need to return.
231234
// No need to cast, because the variant index directly serves as discriminant and is
232235
// encoded in the tag.
233-
(Scalar::from_uint(variant.as_u32(), discr_layout.size), variant)
236+
variant
237+
}
238+
};
239+
// For consisteny with `write_discriminant`, and to make sure that `project_downcast` cannot fail due to strange layouts, we declare immediate UB for uninhabited variants.
240+
if op.layout.for_variant(self, index).abi.is_uninhabited() {
241+
throw_ub!(UninhabitedEnumVariantRead(index))
242+
}
243+
Ok(index)
244+
}
245+
246+
pub fn discriminant_for_variant(
247+
&self,
248+
layout: TyAndLayout<'tcx>,
249+
variant: VariantIdx,
250+
) -> InterpResult<'tcx, Scalar<M::Provenance>> {
251+
let discr_layout = self.layout_of(layout.ty.discriminant_ty(*self.tcx))?;
252+
Ok(match layout.ty.discriminant_for_variant(*self.tcx, variant) {
253+
Some(discr) => {
254+
// This type actually has discriminants.
255+
assert_eq!(discr.ty, discr_layout.ty);
256+
Scalar::from_uint(discr.val, discr_layout.size)
257+
}
258+
None => {
259+
// On a type without actual discriminants, variant is 0.
260+
assert_eq!(variant.as_u32(), 0);
261+
Scalar::from_uint(variant.as_u32(), discr_layout.size)
234262
}
235263
})
236264
}

compiler/rustc_const_eval/src/interpret/eval_context.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -1014,9 +1014,12 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> std::fmt::Debug
10141014
{
10151015
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
10161016
match self.place {
1017-
Place::Local { frame, local } => {
1017+
Place::Local { frame, local, offset } => {
10181018
let mut allocs = Vec::new();
10191019
write!(fmt, "{:?}", local)?;
1020+
if let Some(offset) = offset {
1021+
write!(fmt, "+{:#x}", offset.bytes())?;
1022+
}
10201023
if frame != self.ecx.frame_idx() {
10211024
write!(fmt, " ({} frames up)", self.ecx.frame_idx() - frame)?;
10221025
}

0 commit comments

Comments
 (0)