Skip to content

Commit e195fe8

Browse files
committed
Encode allocation kind in AllocId bits.
1 parent 334753f commit e195fe8

File tree

39 files changed

+291
-208
lines changed

39 files changed

+291
-208
lines changed

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

+143-63
Original file line numberDiff line numberDiff line change
@@ -119,14 +119,15 @@ mod pointer;
119119
mod queries;
120120
mod value;
121121

122+
use std::collections::hash_map::Entry;
122123
use std::fmt;
123124
use std::io;
124125
use std::io::{Read, Write};
125126
use std::num::{NonZeroU32, NonZeroU64};
126127
use std::sync::atomic::{AtomicU32, Ordering};
127128

128129
use rustc_ast::LitKind;
129-
use rustc_data_structures::fx::FxHashMap;
130+
use rustc_data_structures::fx::{FxHashMap, FxIndexSet};
130131
use rustc_data_structures::sync::{HashMapExt, Lock};
131132
use rustc_data_structures::tiny_list::TinyList;
132133
use rustc_errors::ErrorGuaranteed;
@@ -204,25 +205,71 @@ pub enum LitToConstError {
204205
Reported(ErrorGuaranteed),
205206
}
206207

208+
/// We encode the address space in the top 2 bits of the id.
209+
#[rustc_pass_by_value]
207210
#[derive(Copy, Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
208-
pub struct AllocId(pub NonZeroU64);
211+
pub struct AllocId(NonZeroU64);
212+
213+
impl AllocId {
214+
const TAG_MASK: u64 = 0b11 << 62;
215+
const ID_MASK: u64 = !Self::TAG_MASK;
216+
217+
#[inline]
218+
fn new(index: usize, tag: AllocDiscriminant) -> AllocId {
219+
let index: u64 = index.try_into().unwrap();
220+
// SAFETY: `tag` is only 2 bits, so is in range for `u8`.
221+
let tag = unsafe { std::mem::transmute::<AllocDiscriminant, u8>(tag) };
222+
let tag = (tag as u64) << 62;
223+
AllocId(NonZeroU64::new(index | tag).unwrap())
224+
}
225+
226+
#[inline]
227+
fn alloc_discriminant(self) -> AllocDiscriminant {
228+
let tag = (self.0.get() >> 62) as u8;
229+
// SAFETY: `tag` is only 2 bits, so is in range for `AllocDiscriminant`.
230+
unsafe { std::mem::transmute::<u8, AllocDiscriminant>(tag) }
231+
}
232+
233+
#[inline]
234+
fn get(self) -> usize {
235+
(self.0.get() & Self::ID_MASK).try_into().unwrap()
236+
}
237+
238+
// This is used by miri debugging.
239+
pub fn unchecked_new(idx: NonZeroU64) -> AllocId {
240+
AllocId(idx)
241+
}
242+
pub fn unchecked_get(self) -> NonZeroU64 {
243+
self.0
244+
}
245+
}
209246

210247
// We want the `Debug` output to be readable as it is used by `derive(Debug)` for
211248
// all the Miri types.
212249
impl fmt::Debug for AllocId {
213250
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
214-
if f.alternate() { write!(f, "a{}", self.0) } else { write!(f, "alloc{}", self.0) }
251+
let mut prefix = match self.alloc_discriminant() {
252+
AllocDiscriminant::Memory => "alloc",
253+
AllocDiscriminant::Static => "static",
254+
AllocDiscriminant::Fn => "fn",
255+
AllocDiscriminant::VTable => "vtable",
256+
};
257+
if f.alternate() {
258+
prefix = &prefix[..1];
259+
}
260+
write!(f, "{}{}", prefix, self.get())
215261
}
216262
}
217263

218264
// No "Display" since AllocIds are not usually user-visible.
219265

220-
#[derive(TyDecodable, TyEncodable)]
266+
#[repr(u8)]
267+
#[derive(Copy, Clone, Debug, PartialEq, Eq, TyDecodable, TyEncodable)]
221268
enum AllocDiscriminant {
222-
Alloc,
269+
Memory = 0,
270+
Static,
223271
Fn,
224272
VTable,
225-
Static,
226273
}
227274

228275
pub fn specialized_encode_alloc_id<'tcx, E: TyEncoder<I = TyCtxt<'tcx>>>(
@@ -233,7 +280,7 @@ pub fn specialized_encode_alloc_id<'tcx, E: TyEncoder<I = TyCtxt<'tcx>>>(
233280
match tcx.global_alloc(alloc_id) {
234281
GlobalAlloc::Memory(alloc) => {
235282
trace!("encoding {:?} with {:#?}", alloc_id, alloc);
236-
AllocDiscriminant::Alloc.encode(encoder);
283+
AllocDiscriminant::Memory.encode(encoder);
237284
alloc.encode(encoder);
238285
}
239286
GlobalAlloc::Function(fn_instance) => {
@@ -332,7 +379,7 @@ impl<'s> AllocDecodingSession<'s> {
332379
ref mut entry @ State::Empty => {
333380
// We are allowed to decode.
334381
match alloc_kind {
335-
AllocDiscriminant::Alloc => {
382+
AllocDiscriminant::Memory => {
336383
// If this is an allocation, we need to reserve an
337384
// `AllocId` so we can decode cyclic graphs.
338385
let alloc_id = decoder.interner().reserve_alloc_id();
@@ -376,7 +423,7 @@ impl<'s> AllocDecodingSession<'s> {
376423
// Now decode the actual data.
377424
let alloc_id = decoder.with_position(pos, |decoder| {
378425
match alloc_kind {
379-
AllocDiscriminant::Alloc => {
426+
AllocDiscriminant::Memory => {
380427
let alloc = <ConstAllocation<'tcx> as Decodable<_>>::decode(decoder);
381428
// We already have a reserved `AllocId`.
382429
let alloc_id = alloc_id.unwrap();
@@ -482,31 +529,49 @@ impl<'tcx> GlobalAlloc<'tcx> {
482529

483530
pub(crate) struct AllocMap<'tcx> {
484531
/// Maps `AllocId`s to their corresponding allocations.
485-
alloc_map: FxHashMap<AllocId, GlobalAlloc<'tcx>>,
486-
487-
/// Used to ensure that statics and functions only get one associated `AllocId`.
488-
/// Should never contain a `GlobalAlloc::Memory`!
489-
//
490-
// FIXME: Should we just have two separate dedup maps for statics and functions each?
491-
dedup: FxHashMap<GlobalAlloc<'tcx>, AllocId>,
532+
/// We expect this map to be sparse, as the interpreter may create local allocations that do
533+
/// not make it here.
534+
memory: FxHashMap<AllocId, ConstAllocation<'tcx>>,
492535

493536
/// The `AllocId` to assign to the next requested ID.
494537
/// Always incremented; never gets smaller.
495-
next_id: AllocId,
538+
next_memory_id: AllocId,
539+
540+
/// AllocId for statics have a tag in their AllocId.
541+
/// The index in the IndexSet is the untagged id.
542+
statics: FxIndexSet<DefId>,
543+
544+
/// AllocId for vtables have a tag in their AllocId.
545+
/// The index in the IndexSet is the untagged id.
546+
vtables: FxIndexSet<(Ty<'tcx>, Option<ty::PolyExistentialTraitRef<'tcx>>)>,
547+
548+
/// Fns have a specific tag. The index in the vec is the untagged id.
549+
/// Monomorphic functions should only get one `AllocId`. To ensure this, we use a
550+
/// deduplication map.
551+
fns: Vec<Instance<'tcx>>,
552+
fns_dedup: FxHashMap<Instance<'tcx>, AllocId>,
496553
}
497554

498555
impl<'tcx> AllocMap<'tcx> {
499556
pub(crate) fn new() -> Self {
500557
AllocMap {
501-
alloc_map: Default::default(),
502-
dedup: Default::default(),
503-
next_id: AllocId(NonZeroU64::new(1).unwrap()),
558+
memory: Default::default(),
559+
statics: Default::default(),
560+
vtables: Default::default(),
561+
fns: Default::default(),
562+
fns_dedup: Default::default(),
563+
next_memory_id: AllocId::new(1, AllocDiscriminant::Memory),
504564
}
505565
}
566+
567+
/// This allocation may only be used to back memory, ie. a `ConstAllocation`.
506568
fn reserve(&mut self) -> AllocId {
507-
let next = self.next_id;
508-
self.next_id.0 = self.next_id.0.checked_add(1).expect(
509-
"You overflowed a u64 by incrementing by 1... \
569+
let next = self.next_memory_id;
570+
self.next_memory_id.0 = self.next_memory_id.0.checked_add(1).unwrap();
571+
assert_eq!(
572+
self.next_memory_id.alloc_discriminant(),
573+
AllocDiscriminant::Memory,
574+
"You overflowed a u62 by incrementing by 1... \
510575
You've just earned yourself a free drink if we ever meet. \
511576
Seriously, how did you do that?!",
512577
);
@@ -518,35 +583,29 @@ impl<'tcx> TyCtxt<'tcx> {
518583
/// Obtains a new allocation ID that can be referenced but does not
519584
/// yet have an allocation backing it.
520585
///
586+
/// This allocation may only be used to back memory, ie. a `ConstAllocation`.
587+
///
521588
/// Make sure to call `set_alloc_id_memory` or `set_alloc_id_same_memory` before returning such
522589
/// an `AllocId` from a query.
523590
pub fn reserve_alloc_id(self) -> AllocId {
524591
self.alloc_map.lock().reserve()
525592
}
526593

527-
/// Reserves a new ID *if* this allocation has not been dedup-reserved before.
528-
/// Should only be used for "symbolic" allocations (function pointers, vtables, statics), we
529-
/// don't want to dedup IDs for "real" memory!
530-
fn reserve_and_set_dedup(self, alloc: GlobalAlloc<'tcx>) -> AllocId {
531-
let mut alloc_map = self.alloc_map.lock();
532-
match alloc {
533-
GlobalAlloc::Function(..) | GlobalAlloc::Static(..) | GlobalAlloc::VTable(..) => {}
534-
GlobalAlloc::Memory(..) => bug!("Trying to dedup-reserve memory with real data!"),
535-
}
536-
if let Some(&alloc_id) = alloc_map.dedup.get(&alloc) {
537-
return alloc_id;
538-
}
539-
let id = alloc_map.reserve();
540-
debug!("creating alloc {alloc:?} with id {id:?}");
541-
alloc_map.alloc_map.insert(id, alloc.clone());
542-
alloc_map.dedup.insert(alloc, id);
543-
id
544-
}
545-
546594
/// Generates an `AllocId` for a static or return a cached one in case this function has been
547595
/// called on the same static before.
548596
pub fn reserve_and_set_static_alloc(self, static_id: DefId) -> AllocId {
549-
self.reserve_and_set_dedup(GlobalAlloc::Static(static_id))
597+
let (index, _) = self.alloc_map.lock().statics.insert_full(static_id);
598+
AllocId::new(index, AllocDiscriminant::Static)
599+
}
600+
601+
/// Generates an `AllocId` for a (symbolic, not-reified) vtable. Will get deduplicated.
602+
pub fn reserve_and_set_vtable_alloc(
603+
self,
604+
ty: Ty<'tcx>,
605+
poly_trait_ref: Option<ty::PolyExistentialTraitRef<'tcx>>,
606+
) -> AllocId {
607+
let (index, _) = self.alloc_map.lock().vtables.insert_full((ty, poly_trait_ref));
608+
AllocId::new(index, AllocDiscriminant::VTable)
550609
}
551610

552611
/// Generates an `AllocId` for a function. Depending on the function type,
@@ -563,27 +622,30 @@ impl<'tcx> TyCtxt<'tcx> {
563622
.args
564623
.into_iter()
565624
.any(|kind| !matches!(kind.unpack(), GenericArgKind::Lifetime(_)));
566-
if is_generic {
567-
// Get a fresh ID.
568-
let mut alloc_map = self.alloc_map.lock();
569-
let id = alloc_map.reserve();
570-
alloc_map.alloc_map.insert(id, GlobalAlloc::Function(instance));
571-
id
625+
626+
let insert = |fns: &mut Vec<_>| {
627+
let len = fns.len();
628+
fns.push(instance);
629+
AllocId::new(len, AllocDiscriminant::Fn)
630+
};
631+
632+
let mut alloc_map = self.alloc_map.lock();
633+
let alloc_map = &mut *alloc_map;
634+
if !is_generic {
635+
// The function is not generic, attempt to deduplicate it.
636+
match alloc_map.fns_dedup.entry(instance) {
637+
Entry::Occupied(o) => *o.get(),
638+
Entry::Vacant(v) => {
639+
let id = insert(&mut alloc_map.fns);
640+
v.insert(id);
641+
id
642+
}
643+
}
572644
} else {
573-
// Deduplicate.
574-
self.reserve_and_set_dedup(GlobalAlloc::Function(instance))
645+
insert(&mut alloc_map.fns)
575646
}
576647
}
577648

578-
/// Generates an `AllocId` for a (symbolic, not-reified) vtable. Will get deduplicated.
579-
pub fn reserve_and_set_vtable_alloc(
580-
self,
581-
ty: Ty<'tcx>,
582-
poly_trait_ref: Option<ty::PolyExistentialTraitRef<'tcx>>,
583-
) -> AllocId {
584-
self.reserve_and_set_dedup(GlobalAlloc::VTable(ty, poly_trait_ref))
585-
}
586-
587649
/// Interns the `Allocation` and return a new `AllocId`, even if there's already an identical
588650
/// `Allocation` with a different `AllocId`.
589651
/// Statics with identical content will still point to the same `Allocation`, i.e.,
@@ -602,7 +664,23 @@ impl<'tcx> TyCtxt<'tcx> {
602664
/// local dangling pointers and allocations in constants/statics.
603665
#[inline]
604666
pub fn try_get_global_alloc(self, id: AllocId) -> Option<GlobalAlloc<'tcx>> {
605-
self.alloc_map.lock().alloc_map.get(&id).cloned()
667+
match id.alloc_discriminant() {
668+
AllocDiscriminant::Memory => {
669+
self.alloc_map.lock().memory.get(&id).copied().map(GlobalAlloc::Memory)
670+
}
671+
AllocDiscriminant::Static => {
672+
self.alloc_map.lock().statics.get_index(id.get()).copied().map(GlobalAlloc::Static)
673+
}
674+
AllocDiscriminant::Fn => {
675+
self.alloc_map.lock().fns.get(id.get()).copied().map(GlobalAlloc::Function)
676+
}
677+
AllocDiscriminant::VTable => self
678+
.alloc_map
679+
.lock()
680+
.vtables
681+
.get_index(id.get())
682+
.map(|&(ty, pred)| GlobalAlloc::VTable(ty, pred)),
683+
}
606684
}
607685

608686
#[inline]
@@ -621,15 +699,17 @@ impl<'tcx> TyCtxt<'tcx> {
621699
/// Freezes an `AllocId` created with `reserve` by pointing it at an `Allocation`. Trying to
622700
/// call this function twice, even with the same `Allocation` will ICE the compiler.
623701
pub fn set_alloc_id_memory(self, id: AllocId, mem: ConstAllocation<'tcx>) {
624-
if let Some(old) = self.alloc_map.lock().alloc_map.insert(id, GlobalAlloc::Memory(mem)) {
702+
debug_assert_eq!(id.alloc_discriminant(), AllocDiscriminant::Memory);
703+
if let Some(old) = self.alloc_map.lock().memory.insert(id, mem) {
625704
bug!("tried to set allocation ID {id:?}, but it was already existing as {old:#?}");
626705
}
627706
}
628707

629708
/// Freezes an `AllocId` created with `reserve` by pointing it at an `Allocation`. May be called
630709
/// twice for the same `(AllocId, Allocation)` pair.
631710
fn set_alloc_id_same_memory(self, id: AllocId, mem: ConstAllocation<'tcx>) {
632-
self.alloc_map.lock().alloc_map.insert_same(id, GlobalAlloc::Memory(mem));
711+
debug_assert_eq!(id.alloc_discriminant(), AllocDiscriminant::Memory);
712+
self.alloc_map.lock().memory.insert_same(id, mem);
633713
}
634714
}
635715

src/tools/miri/src/bin/miri.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -473,7 +473,7 @@ fn main() {
473473
}
474474
} else if let Some(param) = arg.strip_prefix("-Zmiri-track-alloc-id=") {
475475
let ids: Vec<miri::AllocId> = match parse_comma_list::<NonZeroU64>(param) {
476-
Ok(ids) => ids.into_iter().map(miri::AllocId).collect(),
476+
Ok(ids) => ids.into_iter().map(miri::AllocId::unchecked_new).collect(),
477477
Err(err) =>
478478
show_error!(
479479
"-Zmiri-track-alloc-id requires a comma separated list of valid non-zero `u64` arguments: {}",

src/tools/miri/src/diagnostics.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -534,14 +534,14 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> {
534534
"created tag {tag:?} with {perm} at {alloc_id:?}{range:?} derived from {orig_tag:?}"
535535
),
536536
PoppedPointerTag(item, cause) => format!("popped tracked tag for item {item:?}{cause}"),
537-
CreatedCallId(id) => format!("function call with id {id}"),
538-
CreatedAlloc(AllocId(id), size, align, kind) =>
537+
CreatedCallId(id) => format!("function call with id {id:?}"),
538+
CreatedAlloc(id, size, align, kind) =>
539539
format!(
540-
"created {kind} allocation of {size} bytes (alignment {align} bytes) with id {id}",
540+
"created {kind} allocation of {size} bytes (alignment {align} bytes) with id {id:?}",
541541
size = size.bytes(),
542542
align = align.bytes(),
543543
),
544-
FreedAlloc(AllocId(id)) => format!("freed allocation with id {id}"),
544+
FreedAlloc(id) => format!("freed allocation with id {id:?}"),
545545
RejectedIsolatedOp(ref op) =>
546546
format!("{op} was made to return an error due to isolation"),
547547
ProgressReport { .. } =>

src/tools/miri/src/shims/foreign_items.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -467,14 +467,14 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
467467
"pointer passed to miri_get_alloc_id must not be dangling, got {ptr:?}"
468468
)))
469469
})?;
470-
this.write_scalar(Scalar::from_u64(alloc_id.0.get()), dest)?;
470+
this.write_scalar(Scalar::from_u64(alloc_id.unchecked_get().get()), dest)?;
471471
}
472472
"miri_print_borrow_state" => {
473473
let [id, show_unnamed] = this.check_shim(abi, Abi::Rust, link_name, args)?;
474474
let id = this.read_scalar(id)?.to_u64()?;
475475
let show_unnamed = this.read_scalar(show_unnamed)?.to_bool()?;
476476
if let Some(id) = std::num::NonZeroU64::new(id) {
477-
this.print_borrow_state(AllocId(id), show_unnamed)?;
477+
this.print_borrow_state(AllocId::unchecked_new(id), show_unnamed)?;
478478
}
479479
}
480480
"miri_pointer_name" => {

src/tools/miri/tests/compiletest.rs

+3
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,9 @@ regexes! {
168168
r"\.rs:[0-9]+:[0-9]+(: [0-9]+:[0-9]+)?" => ".rs:LL:CC",
169169
// erase alloc ids
170170
"alloc[0-9]+" => "ALLOC",
171+
"static[0-9]+" => "STATIC",
172+
"fn[0-9]+" => "FN",
173+
"vtable[0-9]+" => "VTABLE",
171174
// erase borrow tags
172175
"<[0-9]+>" => "<TAG>",
173176
"<[0-9]+=" => "<TAG=",

src/tools/miri/tests/fail/data_race/fence_after_load.stderr

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
error: Undefined Behavior: Data race detected between (1) Write on thread `<unnamed>` and (2) Write on thread `main` at ALLOC. (2) just happened here
1+
error: Undefined Behavior: Data race detected between (1) Write on thread `<unnamed>` and (2) Write on thread `main` at STATIC. (2) just happened here
22
--> $DIR/fence_after_load.rs:LL:CC
33
|
44
LL | unsafe { V = 2 }
5-
| ^^^^^ Data race detected between (1) Write on thread `<unnamed>` and (2) Write on thread `main` at ALLOC. (2) just happened here
5+
| ^^^^^ Data race detected between (1) Write on thread `<unnamed>` and (2) Write on thread `main` at STATIC. (2) just happened here
66
|
77
help: and (1) occurred earlier here
88
--> $DIR/fence_after_load.rs:LL:CC

0 commit comments

Comments
 (0)