Skip to content

Commit eb9354e

Browse files
committed
Auto merge of rust-lang#116564 - oli-obk:evaluated_static_in_metadata, r=RalfJung,cjgillot
Store static initializers in metadata instead of the MIR of statics. This means that adding generic statics would be even more difficult, as we can't evaluate statics from other crates anymore, but the subtle issue I have encountered make me think that having this be an explicit problem is better. The issue is that ```rust static mut FOO: &mut u32 = &mut 42; static mut BAR = unsafe { FOO }; ``` gets different allocations, instead of referring to the same one. This is also true for non-static mut, but promotion makes `static FOO: &u32 = &42;` annoying to demo. Fixes rust-lang#61345 ## Why is this being done? In order to ensure all crates see the same nested allocations (which is the last issue that needs fixing before we can stabilize [`const_mut_refs`](rust-lang#57349)), I am working on creating anonymous (from the Rust side, to LLVM it's like a regular static item) static items for the nested allocations in a static. If we evaluate the static item in a downstream crate again, we will end up duplicating its nested allocations (and in some cases, like the `match` case, even duplicate the main allocation).
2 parents ee9c7c9 + d500a17 commit eb9354e

Some content is hidden

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

46 files changed

+439
-191
lines changed

compiler/rustc_const_eval/messages.ftl

+2
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,8 @@ const_eval_realloc_or_alloc_with_offset =
313313
*[other] {""}
314314
} {$ptr} which does not point to the beginning of an object
315315
316+
const_eval_recursive_static = encountered static that tried to initialize itself with itself
317+
316318
const_eval_remainder_by_zero =
317319
calculating the remainder with a divisor of zero
318320
const_eval_remainder_overflow =

compiler/rustc_const_eval/src/const_eval/error.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use crate::interpret::{ErrorHandled, InterpError, InterpErrorInfo, MachineStopTy
1919
pub enum ConstEvalErrKind {
2020
ConstAccessesMutGlobal,
2121
ModifiedGlobal,
22+
RecursiveStatic,
2223
AssertFailure(AssertKind<ConstInt>),
2324
Panic { msg: Symbol, line: u32, col: u32, file: Symbol },
2425
}
@@ -31,13 +32,14 @@ impl MachineStopType for ConstEvalErrKind {
3132
ConstAccessesMutGlobal => const_eval_const_accesses_mut_global,
3233
ModifiedGlobal => const_eval_modified_global,
3334
Panic { .. } => const_eval_panic,
35+
RecursiveStatic => const_eval_recursive_static,
3436
AssertFailure(x) => x.diagnostic_message(),
3537
}
3638
}
3739
fn add_args(self: Box<Self>, adder: &mut dyn FnMut(DiagnosticArgName, DiagnosticArgValue)) {
3840
use ConstEvalErrKind::*;
3941
match *self {
40-
ConstAccessesMutGlobal | ModifiedGlobal => {}
42+
RecursiveStatic | ConstAccessesMutGlobal | ModifiedGlobal => {}
4143
AssertFailure(kind) => kind.add_args(adder),
4244
Panic { msg, line, col, file } => {
4345
adder("msg".into(), msg.into_diagnostic_arg());

compiler/rustc_const_eval/src/const_eval/eval_queries.rs

+56-26
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ use either::{Left, Right};
22

33
use rustc_hir::def::DefKind;
44
use rustc_middle::mir::interpret::{AllocId, ErrorHandled, InterpErrorInfo};
5-
use rustc_middle::mir::pretty::write_allocation_bytes;
65
use rustc_middle::mir::{self, ConstAlloc, ConstValue};
76
use rustc_middle::traits::Reveal;
87
use rustc_middle::ty::layout::LayoutOf;
98
use rustc_middle::ty::print::with_no_trimmed_paths;
109
use rustc_middle::ty::{self, TyCtxt};
10+
use rustc_span::def_id::LocalDefId;
1111
use rustc_span::Span;
1212
use rustc_target::abi::{self, Abi};
1313

@@ -17,8 +17,9 @@ use crate::errors;
1717
use crate::errors::ConstEvalError;
1818
use crate::interpret::eval_nullary_intrinsic;
1919
use crate::interpret::{
20-
intern_const_alloc_recursive, CtfeValidationMode, GlobalId, Immediate, InternKind, InterpCx,
21-
InterpError, InterpResult, MPlaceTy, MemoryKind, OpTy, RefTracking, StackPopCleanup,
20+
create_static_alloc, intern_const_alloc_recursive, take_static_root_alloc, CtfeValidationMode,
21+
GlobalId, Immediate, InternKind, InterpCx, InterpError, InterpResult, MPlaceTy, MemoryKind,
22+
OpTy, RefTracking, StackPopCleanup,
2223
};
2324

2425
// Returns a pointer to where the result lives
@@ -46,7 +47,21 @@ fn eval_body_using_ecx<'mir, 'tcx>(
4647
);
4748
let layout = ecx.layout_of(body.bound_return_ty().instantiate(tcx, cid.instance.args))?;
4849
assert!(layout.is_sized());
49-
let ret = ecx.allocate(layout, MemoryKind::Stack)?;
50+
51+
let intern_kind = if cid.promoted.is_some() {
52+
InternKind::Promoted
53+
} else {
54+
match tcx.static_mutability(cid.instance.def_id()) {
55+
Some(m) => InternKind::Static(m),
56+
None => InternKind::Constant,
57+
}
58+
};
59+
60+
let ret = if let InternKind::Static(_) = intern_kind {
61+
create_static_alloc(ecx, cid.instance.def_id(), layout)?
62+
} else {
63+
ecx.allocate(layout, MemoryKind::Stack)?
64+
};
5065

5166
trace!(
5267
"eval_body_using_ecx: pushing stack frame for global: {}{}",
@@ -66,14 +81,6 @@ fn eval_body_using_ecx<'mir, 'tcx>(
6681
while ecx.step()? {}
6782

6883
// Intern the result
69-
let intern_kind = if cid.promoted.is_some() {
70-
InternKind::Promoted
71-
} else {
72-
match tcx.static_mutability(cid.instance.def_id()) {
73-
Some(m) => InternKind::Static(m),
74-
None => InternKind::Constant,
75-
}
76-
};
7784
intern_const_alloc_recursive(ecx, intern_kind, &ret)?;
7885

7986
Ok(ret)
@@ -249,11 +256,37 @@ pub fn eval_to_const_value_raw_provider<'tcx>(
249256
tcx.eval_to_allocation_raw(key).map(|val| turn_into_const_value(tcx, val, key))
250257
}
251258

259+
#[instrument(skip(tcx), level = "debug")]
260+
pub fn eval_static_initializer_provider<'tcx>(
261+
tcx: TyCtxt<'tcx>,
262+
def_id: LocalDefId,
263+
) -> ::rustc_middle::mir::interpret::EvalStaticInitializerRawResult<'tcx> {
264+
assert!(tcx.is_static(def_id.to_def_id()));
265+
266+
let instance = ty::Instance::mono(tcx, def_id.to_def_id());
267+
let cid = rustc_middle::mir::interpret::GlobalId { instance, promoted: None };
268+
let mut ecx = InterpCx::new(
269+
tcx,
270+
tcx.def_span(def_id),
271+
ty::ParamEnv::reveal_all(),
272+
// Statics (and promoteds inside statics) may access other statics, because unlike consts
273+
// they do not have to behave "as if" they were evaluated at runtime.
274+
CompileTimeInterpreter::new(CanAccessMutGlobal::Yes, CheckAlignment::Error),
275+
);
276+
let alloc_id = eval_in_interpreter(&mut ecx, cid, true)?.alloc_id;
277+
let alloc = take_static_root_alloc(&mut ecx, alloc_id);
278+
let alloc = tcx.mk_const_alloc(alloc);
279+
Ok(alloc)
280+
}
281+
252282
#[instrument(skip(tcx), level = "debug")]
253283
pub fn eval_to_allocation_raw_provider<'tcx>(
254284
tcx: TyCtxt<'tcx>,
255285
key: ty::ParamEnvAnd<'tcx, GlobalId<'tcx>>,
256286
) -> ::rustc_middle::mir::interpret::EvalToAllocationRawResult<'tcx> {
287+
// This shouldn't be used for statics, since statics are conceptually places,
288+
// not values -- so what we do here could break pointer identity.
289+
assert!(key.value.promoted.is_some() || !tcx.is_static(key.value.instance.def_id()));
257290
// Const eval always happens in Reveal::All mode in order to be able to use the hidden types of
258291
// opaque types. This is needed for trivial things like `size_of`, but also for using associated
259292
// types that are not specified in the opaque type.
@@ -273,7 +306,7 @@ pub fn eval_to_allocation_raw_provider<'tcx>(
273306
let def = cid.instance.def.def_id();
274307
let is_static = tcx.is_static(def);
275308

276-
let ecx = InterpCx::new(
309+
let mut ecx = InterpCx::new(
277310
tcx,
278311
tcx.def_span(def),
279312
key.param_env,
@@ -283,19 +316,19 @@ pub fn eval_to_allocation_raw_provider<'tcx>(
283316
// so we have to reject reading mutable global memory.
284317
CompileTimeInterpreter::new(CanAccessMutGlobal::from(is_static), CheckAlignment::Error),
285318
);
286-
eval_in_interpreter(ecx, cid, is_static)
319+
eval_in_interpreter(&mut ecx, cid, is_static)
287320
}
288321

289322
pub fn eval_in_interpreter<'mir, 'tcx>(
290-
mut ecx: InterpCx<'mir, 'tcx, CompileTimeInterpreter<'mir, 'tcx>>,
323+
ecx: &mut InterpCx<'mir, 'tcx, CompileTimeInterpreter<'mir, 'tcx>>,
291324
cid: GlobalId<'tcx>,
292325
is_static: bool,
293326
) -> ::rustc_middle::mir::interpret::EvalToAllocationRawResult<'tcx> {
294327
// `is_static` just means "in static", it could still be a promoted!
295328
debug_assert_eq!(is_static, ecx.tcx.static_mutability(cid.instance.def_id()).is_some());
296329

297330
let res = ecx.load_mir(cid.instance.def, cid.promoted);
298-
match res.and_then(|body| eval_body_using_ecx(&mut ecx, cid, body)) {
331+
match res.and_then(|body| eval_body_using_ecx(ecx, cid, body)) {
299332
Err(error) => {
300333
let (error, backtrace) = error.into_parts();
301334
backtrace.print_backtrace();
@@ -330,8 +363,11 @@ pub fn eval_in_interpreter<'mir, 'tcx>(
330363
}
331364
Ok(mplace) => {
332365
// Since evaluation had no errors, validate the resulting constant.
333-
// This is a separate `try` block to provide more targeted error reporting.
366+
367+
// Temporarily allow access to the static_root_alloc_id for the purpose of validation.
368+
let static_root_alloc_id = ecx.machine.static_root_alloc_id.take();
334369
let validation = const_validate_mplace(&ecx, &mplace, cid);
370+
ecx.machine.static_root_alloc_id = static_root_alloc_id;
335371

336372
let alloc_id = mplace.ptr().provenance.unwrap().alloc_id();
337373

@@ -383,15 +419,9 @@ pub fn const_report_error<'mir, 'tcx>(
383419

384420
let ub_note = matches!(error, InterpError::UndefinedBehavior(_)).then(|| {});
385421

386-
let alloc = ecx.tcx.global_alloc(alloc_id).unwrap_memory().inner();
387-
let mut bytes = String::new();
388-
if alloc.size() != abi::Size::ZERO {
389-
bytes = "\n".into();
390-
// FIXME(translation) there might be pieces that are translatable.
391-
write_allocation_bytes(*ecx.tcx, alloc, &mut bytes, " ").unwrap();
392-
}
393-
let raw_bytes =
394-
errors::RawBytesNote { size: alloc.size().bytes(), align: alloc.align.bytes(), bytes };
422+
let bytes = ecx.print_alloc_bytes_for_diagnostics(alloc_id);
423+
let (size, align, _) = ecx.get_alloc_info(alloc_id);
424+
let raw_bytes = errors::RawBytesNote { size: size.bytes(), align: align.bytes(), bytes };
395425

396426
crate::const_eval::report(
397427
*ecx.tcx,

compiler/rustc_const_eval/src/const_eval/machine.rs

+15
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ pub struct CompileTimeInterpreter<'mir, 'tcx> {
5858

5959
/// Whether to check alignment during evaluation.
6060
pub(super) check_alignment: CheckAlignment,
61+
62+
/// Used to prevent reads from a static's base allocation, as that may allow for self-initialization.
63+
pub(crate) static_root_alloc_id: Option<AllocId>,
6164
}
6265

6366
#[derive(Copy, Clone)]
@@ -91,6 +94,7 @@ impl<'mir, 'tcx> CompileTimeInterpreter<'mir, 'tcx> {
9194
stack: Vec::new(),
9295
can_access_mut_global,
9396
check_alignment,
97+
static_root_alloc_id: None,
9498
}
9599
}
96100
}
@@ -746,6 +750,17 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
746750
// Everything else is fine.
747751
Ok(())
748752
}
753+
754+
fn before_alloc_read(
755+
ecx: &InterpCx<'mir, 'tcx, Self>,
756+
alloc_id: AllocId,
757+
) -> InterpResult<'tcx> {
758+
if Some(alloc_id) == ecx.machine.static_root_alloc_id {
759+
Err(ConstEvalErrKind::RecursiveStatic.into())
760+
} else {
761+
Ok(())
762+
}
763+
}
749764
}
750765

751766
// Please do not add any code below the above `Machine` trait impl. I (oli-obk) plan more cleanups

compiler/rustc_const_eval/src/interpret/cast.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
153153
);
154154
}
155155

156-
self.copy_op(src, dest, /*allow_transmute*/ true)?;
156+
self.copy_op_allow_transmute(src, dest)?;
157157
}
158158
}
159159
Ok(())
@@ -441,7 +441,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
441441
if src_field.layout.is_1zst() && cast_ty_field.is_1zst() {
442442
// Skip 1-ZST fields.
443443
} else if src_field.layout.ty == cast_ty_field.ty {
444-
self.copy_op(&src_field, &dst_field, /*allow_transmute*/ false)?;
444+
self.copy_op(&src_field, &dst_field)?;
445445
} else {
446446
if found_cast_field {
447447
span_bug!(self.cur_span(), "unsize_into: more than one field to cast");

compiler/rustc_const_eval/src/interpret/eval_context.rs

+13-1
Original file line numberDiff line numberDiff line change
@@ -899,7 +899,19 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
899899
.local_to_op(self.frame(), mir::RETURN_PLACE, None)
900900
.expect("return place should always be live");
901901
let dest = self.frame().return_place.clone();
902-
let err = self.copy_op(&op, &dest, /*allow_transmute*/ true);
902+
let err = if self.stack().len() == 1 {
903+
// The initializer of constants and statics will get validated separately
904+
// after the constant has been fully evaluated. While we could fall back to the default
905+
// code path, that will cause -Zenforce-validity to cycle on static initializers.
906+
// Reading from a static's memory is not allowed during its evaluation, and will always
907+
// trigger a cycle error. Validation must read from the memory of the current item.
908+
// For Miri this means we do not validate the root frame return value,
909+
// but Miri anyway calls `read_target_isize` on that so separate validation
910+
// is not needed.
911+
self.copy_op_no_dest_validation(&op, &dest)
912+
} else {
913+
self.copy_op_allow_transmute(&op, &dest)
914+
};
903915
trace!("return value: {:?}", self.dump_place(&dest));
904916
// We delay actually short-circuiting on this error until *after* the stack frame is
905917
// popped, since we want this error to be attributed to the caller, whose type defines

compiler/rustc_const_eval/src/interpret/intern.rs

+27-5
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ pub enum InternKind {
8585
///
8686
/// This *cannot raise an interpreter error*. Doing so is left to validation, which
8787
/// tracks where in the value we are and thus can show much better error messages.
88+
///
89+
/// For `InternKind::Static` the root allocation will not be interned, but must be handled by the caller.
8890
#[instrument(level = "debug", skip(ecx))]
8991
pub fn intern_const_alloc_recursive<
9092
'mir,
@@ -97,12 +99,12 @@ pub fn intern_const_alloc_recursive<
9799
) -> Result<(), ErrorGuaranteed> {
98100
// We are interning recursively, and for mutability we are distinguishing the "root" allocation
99101
// that we are starting in, and all other allocations that we are encountering recursively.
100-
let (base_mutability, inner_mutability) = match intern_kind {
102+
let (base_mutability, inner_mutability, is_static) = match intern_kind {
101103
InternKind::Constant | InternKind::Promoted => {
102104
// Completely immutable. Interning anything mutably here can only lead to unsoundness,
103105
// since all consts are conceptually independent values but share the same underlying
104106
// memory.
105-
(Mutability::Not, Mutability::Not)
107+
(Mutability::Not, Mutability::Not, false)
106108
}
107109
InternKind::Static(Mutability::Not) => {
108110
(
@@ -115,22 +117,31 @@ pub fn intern_const_alloc_recursive<
115117
// Inner allocations are never mutable. They can only arise via the "tail
116118
// expression" / "outer scope" rule, and we treat them consistently with `const`.
117119
Mutability::Not,
120+
true,
118121
)
119122
}
120123
InternKind::Static(Mutability::Mut) => {
121124
// Just make everything mutable. We accept code like
122125
// `static mut X = &mut [42]`, so even inner allocations need to be mutable.
123-
(Mutability::Mut, Mutability::Mut)
126+
(Mutability::Mut, Mutability::Mut, true)
124127
}
125128
};
126129

127130
// Intern the base allocation, and initialize todo list for recursive interning.
128131
let base_alloc_id = ret.ptr().provenance.unwrap().alloc_id();
132+
trace!(?base_alloc_id, ?base_mutability);
129133
// First we intern the base allocation, as it requires a different mutability.
130134
// This gives us the initial set of nested allocations, which will then all be processed
131135
// recursively in the loop below.
132-
let mut todo: Vec<_> =
133-
intern_shallow(ecx, base_alloc_id, base_mutability).unwrap().map(|prov| prov).collect();
136+
let mut todo: Vec<_> = if is_static {
137+
// Do not steal the root allocation, we need it later for `take_static_root_alloc`
138+
// But still change its mutability to match the requested one.
139+
let alloc = ecx.memory.alloc_map.get_mut(&base_alloc_id).unwrap();
140+
alloc.1.mutability = base_mutability;
141+
alloc.1.provenance().ptrs().iter().map(|&(_, prov)| prov).collect()
142+
} else {
143+
intern_shallow(ecx, base_alloc_id, base_mutability).unwrap().map(|prov| prov).collect()
144+
};
134145
// We need to distinguish "has just been interned" from "was already in `tcx`",
135146
// so we track this in a separate set.
136147
let mut just_interned: FxHashSet<_> = std::iter::once(base_alloc_id).collect();
@@ -148,7 +159,17 @@ pub fn intern_const_alloc_recursive<
148159
// before validation, and interning doesn't know the type of anything, this means we can't show
149160
// better errors. Maybe we should consider doing validation before interning in the future.
150161
while let Some(prov) = todo.pop() {
162+
trace!(?prov);
151163
let alloc_id = prov.alloc_id();
164+
165+
if base_alloc_id == alloc_id && is_static {
166+
// This is a pointer to the static itself. It's ok for a static to refer to itself,
167+
// even mutably. Whether that mutable pointer is legal at all is checked in validation.
168+
// See tests/ui/statics/recursive_interior_mut.rs for how such a situation can occur.
169+
// We also already collected all the nested allocations, so there's no need to do that again.
170+
continue;
171+
}
172+
152173
// Crucially, we check this *before* checking whether the `alloc_id`
153174
// has already been interned. The point of this check is to ensure that when
154175
// there are multiple pointers to the same allocation, they are *all* immutable.
@@ -176,6 +197,7 @@ pub fn intern_const_alloc_recursive<
176197
// `&None::<Cell<i32>>` lead to promotion that can produce mutable pointers. We rely
177198
// on the promotion analysis not screwing up to ensure that it is sound to intern
178199
// promoteds as immutable.
200+
trace!("found bad mutable pointer");
179201
found_bad_mutable_pointer = true;
180202
}
181203
if ecx.tcx.try_get_global_alloc(alloc_id).is_some() {

0 commit comments

Comments
 (0)