Skip to content

Commit ae7b1c1

Browse files
committed
Auto merge of rust-lang#127442 - saethlin:alloc-decoding-lock, r=oli-obk
Try to fix ICE from re-interning an AllocId with different allocation contents As far as I can tell, based on my investigation in rust-lang#126741, the racy decoding scheme implemented here was never fully correct, but the arrangement of Allocations that's required to ICE the compiler requires some very specific MIR optimizations to create. As far as I can tell, GVN likes to create the problematic pattern, which is why we're noticing this problem now. So the solution here is to not do racy decoding. If two threads race to decoding an AllocId, one of them is going to sit on a lock until the other is done.
2 parents ee0fd6c + 107cf98 commit ae7b1c1

File tree

1 file changed

+25
-89
lines changed
  • compiler/rustc_middle/src/mir/interpret

1 file changed

+25
-89
lines changed

compiler/rustc_middle/src/mir/interpret/mod.rs

+25-89
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,13 @@ use std::fmt;
1212
use std::io;
1313
use std::io::{Read, Write};
1414
use std::num::NonZero;
15-
use std::sync::atomic::{AtomicU32, Ordering};
1615

17-
use smallvec::{smallvec, SmallVec};
1816
use tracing::{debug, trace};
1917

2018
use rustc_ast::LitKind;
2119
use rustc_attr::InlineAttr;
2220
use rustc_data_structures::fx::FxHashMap;
23-
use rustc_data_structures::sync::{HashMapExt, Lock};
21+
use rustc_data_structures::sync::Lock;
2422
use rustc_errors::ErrorGuaranteed;
2523
use rustc_hir::def_id::{DefId, LocalDefId};
2624
use rustc_macros::{HashStable, TyDecodable, TyEncodable, TypeFoldable, TypeVisitable};
@@ -159,14 +157,9 @@ pub fn specialized_encode_alloc_id<'tcx, E: TyEncoder<I = TyCtxt<'tcx>>>(
159157
}
160158
}
161159

162-
// Used to avoid infinite recursion when decoding cyclic allocations.
163-
type DecodingSessionId = NonZero<u32>;
164-
165160
#[derive(Clone)]
166161
enum State {
167162
Empty,
168-
InProgressNonAlloc(SmallVec<[DecodingSessionId; 1]>),
169-
InProgress(SmallVec<[DecodingSessionId; 1]>, AllocId),
170163
Done(AllocId),
171164
}
172165

@@ -180,13 +173,7 @@ pub struct AllocDecodingState {
180173
impl AllocDecodingState {
181174
#[inline]
182175
pub fn new_decoding_session(&self) -> AllocDecodingSession<'_> {
183-
static DECODER_SESSION_ID: AtomicU32 = AtomicU32::new(0);
184-
let counter = DECODER_SESSION_ID.fetch_add(1, Ordering::SeqCst);
185-
186-
// Make sure this is never zero.
187-
let session_id = DecodingSessionId::new((counter & 0x7FFFFFFF) + 1).unwrap();
188-
189-
AllocDecodingSession { state: self, session_id }
176+
AllocDecodingSession { state: self }
190177
}
191178

192179
pub fn new(data_offsets: Vec<u64>) -> Self {
@@ -200,7 +187,6 @@ impl AllocDecodingState {
200187
#[derive(Copy, Clone)]
201188
pub struct AllocDecodingSession<'s> {
202189
state: &'s AllocDecodingState,
203-
session_id: DecodingSessionId,
204190
}
205191

206192
impl<'s> AllocDecodingSession<'s> {
@@ -220,106 +206,62 @@ impl<'s> AllocDecodingSession<'s> {
220206
(alloc_kind, decoder.position())
221207
});
222208

209+
// We are going to hold this lock during the entire decoding of this allocation, which may
210+
// require that we decode other allocations. This cannot deadlock for two reasons:
211+
//
212+
// At the time of writing, it is only possible to create an allocation that contains a pointer
213+
// to itself using the const_allocate intrinsic (which is for testing only), and even attempting
214+
// to evaluate such consts blows the stack. If we ever grow a mechanism for producing
215+
// cyclic allocations, we will need a new strategy for decoding that doesn't bring back
216+
// https://github.com/rust-lang/rust/issues/126741.
217+
//
218+
// It is also impossible to create two allocations (call them A and B) where A is a pointer to B, and B
219+
// is a pointer to A, because attempting to evaluate either of those consts will produce a
220+
// query cycle, failing compilation.
221+
let mut entry = self.state.decoding_state[idx].lock();
223222
// Check the decoding state to see if it's already decoded or if we should
224223
// decode it here.
225-
let alloc_id = {
226-
let mut entry = self.state.decoding_state[idx].lock();
227-
228-
match *entry {
229-
State::Done(alloc_id) => {
230-
return alloc_id;
231-
}
232-
ref mut entry @ State::Empty => {
233-
// We are allowed to decode.
234-
match alloc_kind {
235-
AllocDiscriminant::Alloc => {
236-
// If this is an allocation, we need to reserve an
237-
// `AllocId` so we can decode cyclic graphs.
238-
let alloc_id = decoder.interner().reserve_alloc_id();
239-
*entry = State::InProgress(smallvec![self.session_id], alloc_id);
240-
Some(alloc_id)
241-
}
242-
AllocDiscriminant::Fn
243-
| AllocDiscriminant::Static
244-
| AllocDiscriminant::VTable => {
245-
// Fns and statics cannot be cyclic, and their `AllocId`
246-
// is determined later by interning.
247-
*entry = State::InProgressNonAlloc(smallvec![self.session_id]);
248-
None
249-
}
250-
}
251-
}
252-
State::InProgressNonAlloc(ref mut sessions) => {
253-
if sessions.contains(&self.session_id) {
254-
bug!("this should be unreachable");
255-
} else {
256-
// Start decoding concurrently.
257-
sessions.push(self.session_id);
258-
None
259-
}
260-
}
261-
State::InProgress(ref mut sessions, alloc_id) => {
262-
if sessions.contains(&self.session_id) {
263-
// Don't recurse.
264-
return alloc_id;
265-
} else {
266-
// Start decoding concurrently.
267-
sessions.push(self.session_id);
268-
Some(alloc_id)
269-
}
270-
}
271-
}
272-
};
224+
if let State::Done(alloc_id) = *entry {
225+
return alloc_id;
226+
}
273227

274228
// Now decode the actual data.
275229
let alloc_id = decoder.with_position(pos, |decoder| {
276230
match alloc_kind {
277231
AllocDiscriminant::Alloc => {
232+
trace!("creating memory alloc ID");
278233
let alloc = <ConstAllocation<'tcx> as Decodable<_>>::decode(decoder);
279-
// We already have a reserved `AllocId`.
280-
let alloc_id = alloc_id.unwrap();
281-
trace!("decoded alloc {:?}: {:#?}", alloc_id, alloc);
282-
decoder.interner().set_alloc_id_same_memory(alloc_id, alloc);
283-
alloc_id
234+
trace!("decoded alloc {:?}", alloc);
235+
decoder.interner().reserve_and_set_memory_alloc(alloc)
284236
}
285237
AllocDiscriminant::Fn => {
286-
assert!(alloc_id.is_none());
287238
trace!("creating fn alloc ID");
288239
let instance = ty::Instance::decode(decoder);
289240
trace!("decoded fn alloc instance: {:?}", instance);
290241
let unique = bool::decode(decoder);
291242
// Here we cannot call `reserve_and_set_fn_alloc` as that would use a query, which
292243
// is not possible in this context. That's why the allocation stores
293244
// whether it is unique or not.
294-
let alloc_id =
295-
decoder.interner().reserve_and_set_fn_alloc_internal(instance, unique);
296-
alloc_id
245+
decoder.interner().reserve_and_set_fn_alloc_internal(instance, unique)
297246
}
298247
AllocDiscriminant::VTable => {
299-
assert!(alloc_id.is_none());
300248
trace!("creating vtable alloc ID");
301249
let ty = <Ty<'_> as Decodable<D>>::decode(decoder);
302250
let poly_trait_ref =
303251
<Option<ty::PolyExistentialTraitRef<'_>> as Decodable<D>>::decode(decoder);
304252
trace!("decoded vtable alloc instance: {ty:?}, {poly_trait_ref:?}");
305-
let alloc_id =
306-
decoder.interner().reserve_and_set_vtable_alloc(ty, poly_trait_ref);
307-
alloc_id
253+
decoder.interner().reserve_and_set_vtable_alloc(ty, poly_trait_ref)
308254
}
309255
AllocDiscriminant::Static => {
310-
assert!(alloc_id.is_none());
311256
trace!("creating extern static alloc ID");
312257
let did = <DefId as Decodable<D>>::decode(decoder);
313258
trace!("decoded static def-ID: {:?}", did);
314-
let alloc_id = decoder.interner().reserve_and_set_static_alloc(did);
315-
alloc_id
259+
decoder.interner().reserve_and_set_static_alloc(did)
316260
}
317261
}
318262
});
319263

320-
self.state.decoding_state[idx].with_lock(|entry| {
321-
*entry = State::Done(alloc_id);
322-
});
264+
*entry = State::Done(alloc_id);
323265

324266
alloc_id
325267
}
@@ -563,12 +505,6 @@ impl<'tcx> TyCtxt<'tcx> {
563505
bug!("tried to set allocation ID {id:?}, but it was already existing as {old:#?}");
564506
}
565507
}
566-
567-
/// Freezes an `AllocId` created with `reserve` by pointing it at an `Allocation`. May be called
568-
/// twice for the same `(AllocId, Allocation)` pair.
569-
fn set_alloc_id_same_memory(self, id: AllocId, mem: ConstAllocation<'tcx>) {
570-
self.alloc_map.lock().alloc_map.insert_same(id, GlobalAlloc::Memory(mem));
571-
}
572508
}
573509

574510
////////////////////////////////////////////////////////////////////////////////

0 commit comments

Comments
 (0)