Rollup merge of #118029 - saethlin:allocid-gc, r=RalfJung
Expand Miri's BorTag GC to a Provenance GC As suggested in https://github.com/rust-lang/miri/issues/3080#issuecomment-1732505573 We previously solved memory growth issues associated with the Stacked Borrows and Tree Borrows runtimes with a GC. But of course we also have state accumulation associated with whole allocations elsewhere in the interpreter, and this PR starts tackling those. To do this, we expand the visitor for the GC so that it can visit a BorTag or an AllocId. Instead of collecting all live AllocIds into a single HashSet, we just collect from the Machine itself then go through an accessor `InterpCx::is_alloc_live` which checks a number of allocation data structures in the core interpreter. This avoids the overhead of all the inserts that collecting their keys would require. r? ``@RalfJung``
This commit is contained in:
commit
cbadb2e1c0
45 changed files with 459 additions and 316 deletions
|
@ -107,6 +107,14 @@ impl<K: Hash + Eq, V> interpret::AllocMap<K, V> for FxIndexMap<K, V> {
|
||||||
FxIndexMap::contains_key(self, k)
|
FxIndexMap::contains_key(self, k)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn contains_key_ref<Q: ?Sized + Hash + Eq>(&self, k: &Q) -> bool
|
||||||
|
where
|
||||||
|
K: Borrow<Q>,
|
||||||
|
{
|
||||||
|
FxIndexMap::contains_key(self, k)
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn insert(&mut self, k: K, v: V) -> Option<V> {
|
fn insert(&mut self, k: K, v: V) -> Option<V> {
|
||||||
FxIndexMap::insert(self, k, v)
|
FxIndexMap::insert(self, k, v)
|
||||||
|
|
|
@ -49,6 +49,14 @@ pub trait AllocMap<K: Hash + Eq, V> {
|
||||||
where
|
where
|
||||||
K: Borrow<Q>;
|
K: Borrow<Q>;
|
||||||
|
|
||||||
|
/// Callers should prefer [`AllocMap::contains_key`] when it is possible to call because it may
|
||||||
|
/// be more efficient. This function exists for callers that only have a shared reference
|
||||||
|
/// (which might make it slightly less efficient than `contains_key`, e.g. if
|
||||||
|
/// the data is stored inside a `RefCell`).
|
||||||
|
fn contains_key_ref<Q: ?Sized + Hash + Eq>(&self, k: &Q) -> bool
|
||||||
|
where
|
||||||
|
K: Borrow<Q>;
|
||||||
|
|
||||||
/// Inserts a new entry into the map.
|
/// Inserts a new entry into the map.
|
||||||
fn insert(&mut self, k: K, v: V) -> Option<V>;
|
fn insert(&mut self, k: K, v: V) -> Option<V>;
|
||||||
|
|
||||||
|
|
|
@ -692,6 +692,15 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
||||||
Ok((&mut alloc.extra, machine))
|
Ok((&mut alloc.extra, machine))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check whether an allocation is live. This is faster than calling
|
||||||
|
/// [`InterpCx::get_alloc_info`] if all you need to check is whether the kind is
|
||||||
|
/// [`AllocKind::Dead`] because it doesn't have to look up the type and layout of statics.
|
||||||
|
pub fn is_alloc_live(&self, id: AllocId) -> bool {
|
||||||
|
self.tcx.try_get_global_alloc(id).is_some()
|
||||||
|
|| self.memory.alloc_map.contains_key_ref(&id)
|
||||||
|
|| self.memory.extra_fn_ptr_map.contains_key(&id)
|
||||||
|
}
|
||||||
|
|
||||||
/// Obtain the size and alignment of an allocation, even if that allocation has
|
/// Obtain the size and alignment of an allocation, even if that allocation has
|
||||||
/// been deallocated.
|
/// been deallocated.
|
||||||
pub fn get_alloc_info(&self, id: AllocId) -> (Size, Align, AllocKind) {
|
pub fn get_alloc_info(&self, id: AllocId) -> (Size, Align, AllocKind) {
|
||||||
|
|
|
@ -525,13 +525,6 @@ impl<'tcx> TyCtxt<'tcx> {
|
||||||
self.alloc_map.lock().reserve()
|
self.alloc_map.lock().reserve()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Miri's provenance GC needs to see all live allocations. The interpreter manages most
|
|
||||||
/// allocations but some are managed by [`TyCtxt`] and without this method the interpreter
|
|
||||||
/// doesn't know their [`AllocId`]s are in use.
|
|
||||||
pub fn iter_allocs<F: FnMut(AllocId)>(self, func: F) {
|
|
||||||
self.alloc_map.lock().alloc_map.keys().copied().for_each(func)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reserves a new ID *if* this allocation has not been dedup-reserved before.
|
/// Reserves a new ID *if* this allocation has not been dedup-reserved before.
|
||||||
/// Should only be used for "symbolic" allocations (function pointers, vtables, statics), we
|
/// Should only be used for "symbolic" allocations (function pointers, vtables, statics), we
|
||||||
/// don't want to dedup IDs for "real" memory!
|
/// don't want to dedup IDs for "real" memory!
|
||||||
|
|
|
@ -25,7 +25,16 @@ cat /tmp/toolstate/toolstates.json
|
||||||
python3 "$X_PY" test --stage 2 check-tools
|
python3 "$X_PY" test --stage 2 check-tools
|
||||||
python3 "$X_PY" test --stage 2 src/tools/clippy
|
python3 "$X_PY" test --stage 2 src/tools/clippy
|
||||||
python3 "$X_PY" test --stage 2 src/tools/rustfmt
|
python3 "$X_PY" test --stage 2 src/tools/rustfmt
|
||||||
python3 "$X_PY" test --stage 2 src/tools/miri
|
|
||||||
|
# Testing Miri is a bit more complicated.
|
||||||
|
# We set the GC interval to the shortest possible value (0 would be off) to increase the chance
|
||||||
|
# that bugs which only surface when the GC runs at a specific time are more likely to cause CI to fail.
|
||||||
|
# This significantly increases the runtime of our test suite, or we'd do this in PR CI too.
|
||||||
|
if [[ -z "${PR_CI_JOB:-}" ]]; then
|
||||||
|
MIRIFLAGS=-Zmiri-provenance-gc=1 python3 "$X_PY" test --stage 2 src/tools/miri
|
||||||
|
else
|
||||||
|
python3 "$X_PY" test --stage 2 src/tools/miri
|
||||||
|
fi
|
||||||
# We natively run this script on x86_64-unknown-linux-gnu and x86_64-pc-windows-msvc.
|
# We natively run this script on x86_64-unknown-linux-gnu and x86_64-pc-windows-msvc.
|
||||||
# Also cover some other targets via cross-testing, in particular all tier 1 targets.
|
# Also cover some other targets via cross-testing, in particular all tier 1 targets.
|
||||||
export BOOTSTRAP_SKIP_TARGET_SANITY=1 # we don't need `cc` for these targets
|
export BOOTSTRAP_SKIP_TARGET_SANITY=1 # we don't need `cc` for these targets
|
||||||
|
|
2
src/tools/miri/.github/workflows/ci.yml
vendored
2
src/tools/miri/.github/workflows/ci.yml
vendored
|
@ -37,7 +37,7 @@ jobs:
|
||||||
|
|
||||||
- name: Set the tag GC interval to 1 on linux
|
- name: Set the tag GC interval to 1 on linux
|
||||||
if: runner.os == 'Linux'
|
if: runner.os == 'Linux'
|
||||||
run: echo "MIRIFLAGS=-Zmiri-tag-gc=1" >> $GITHUB_ENV
|
run: echo "MIRIFLAGS=-Zmiri-provenance-gc=1" >> $GITHUB_ENV
|
||||||
|
|
||||||
# Cache the global cargo directory, but NOT the local `target` directory which
|
# Cache the global cargo directory, but NOT the local `target` directory which
|
||||||
# we cannot reuse anyway when the nightly changes (and it grows quite large
|
# we cannot reuse anyway when the nightly changes (and it grows quite large
|
||||||
|
|
|
@ -411,10 +411,10 @@ to Miri failing to detect cases of undefined behavior in a program.
|
||||||
without an explicit value), `none` means it never recurses, `scalar` means it only recurses for
|
without an explicit value), `none` means it never recurses, `scalar` means it only recurses for
|
||||||
types where we would also emit `noalias` annotations in the generated LLVM IR (types passed as
|
types where we would also emit `noalias` annotations in the generated LLVM IR (types passed as
|
||||||
individual scalars or pairs of scalars). Setting this to `none` is **unsound**.
|
individual scalars or pairs of scalars). Setting this to `none` is **unsound**.
|
||||||
* `-Zmiri-tag-gc=<blocks>` configures how often the pointer tag garbage collector runs. The default
|
* `-Zmiri-provenance-gc=<blocks>` configures how often the pointer provenance garbage collector runs.
|
||||||
is to search for and remove unreachable tags once every `10000` basic blocks. Setting this to
|
The default is to search for and remove unreachable provenance once every `10000` basic blocks. Setting
|
||||||
`0` disables the garbage collector, which causes some programs to have explosive memory usage
|
this to `0` disables the garbage collector, which causes some programs to have explosive memory
|
||||||
and/or super-linear runtime.
|
usage and/or super-linear runtime.
|
||||||
* `-Zmiri-track-alloc-id=<id1>,<id2>,...` shows a backtrace when the given allocations are
|
* `-Zmiri-track-alloc-id=<id1>,<id2>,...` shows a backtrace when the given allocations are
|
||||||
being allocated or freed. This helps in debugging memory leaks and
|
being allocated or freed. This helps in debugging memory leaks and
|
||||||
use after free bugs. Specifying this argument multiple times does not overwrite the previous
|
use after free bugs. Specifying this argument multiple times does not overwrite the previous
|
||||||
|
|
|
@ -531,10 +531,10 @@ fn main() {
|
||||||
Err(err) => show_error!("-Zmiri-report-progress requires a `u32`: {}", err),
|
Err(err) => show_error!("-Zmiri-report-progress requires a `u32`: {}", err),
|
||||||
};
|
};
|
||||||
miri_config.report_progress = Some(interval);
|
miri_config.report_progress = Some(interval);
|
||||||
} else if let Some(param) = arg.strip_prefix("-Zmiri-tag-gc=") {
|
} else if let Some(param) = arg.strip_prefix("-Zmiri-provenance-gc=") {
|
||||||
let interval = match param.parse::<u32>() {
|
let interval = match param.parse::<u32>() {
|
||||||
Ok(i) => i,
|
Ok(i) => i,
|
||||||
Err(err) => show_error!("-Zmiri-tag-gc requires a `u32`: {}", err),
|
Err(err) => show_error!("-Zmiri-provenance-gc requires a `u32`: {}", err),
|
||||||
};
|
};
|
||||||
miri_config.gc_interval = interval;
|
miri_config.gc_interval = interval;
|
||||||
} else if let Some(param) = arg.strip_prefix("-Zmiri-measureme=") {
|
} else if let Some(param) = arg.strip_prefix("-Zmiri-measureme=") {
|
||||||
|
|
|
@ -75,8 +75,8 @@ pub struct FrameState {
|
||||||
protected_tags: SmallVec<[(AllocId, BorTag); 2]>,
|
protected_tags: SmallVec<[(AllocId, BorTag); 2]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VisitTags for FrameState {
|
impl VisitProvenance for FrameState {
|
||||||
fn visit_tags(&self, _visit: &mut dyn FnMut(BorTag)) {
|
fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
|
||||||
// `protected_tags` are already recorded by `GlobalStateInner`.
|
// `protected_tags` are already recorded by `GlobalStateInner`.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,10 +110,10 @@ pub struct GlobalStateInner {
|
||||||
unique_is_unique: bool,
|
unique_is_unique: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VisitTags for GlobalStateInner {
|
impl VisitProvenance for GlobalStateInner {
|
||||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||||
for &tag in self.protected_tags.keys() {
|
for &tag in self.protected_tags.keys() {
|
||||||
visit(tag);
|
visit(None, Some(tag));
|
||||||
}
|
}
|
||||||
// The only other candidate is base_ptr_tags, and that does not need visiting since we don't ever
|
// The only other candidate is base_ptr_tags, and that does not need visiting since we don't ever
|
||||||
// GC the bottommost/root tag.
|
// GC the bottommost/root tag.
|
||||||
|
@ -236,6 +236,10 @@ impl GlobalStateInner {
|
||||||
tag
|
tag
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn remove_unreachable_allocs(&mut self, allocs: &LiveAllocs<'_, '_, '_>) {
|
||||||
|
self.base_ptr_tags.retain(|id, _| allocs.is_live(*id));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Which borrow tracking method to use
|
/// Which borrow tracking method to use
|
||||||
|
@ -503,11 +507,11 @@ impl AllocState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VisitTags for AllocState {
|
impl VisitProvenance for AllocState {
|
||||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||||
match self {
|
match self {
|
||||||
AllocState::StackedBorrows(sb) => sb.visit_tags(visit),
|
AllocState::StackedBorrows(sb) => sb.visit_provenance(visit),
|
||||||
AllocState::TreeBorrows(tb) => tb.visit_tags(visit),
|
AllocState::TreeBorrows(tb) => tb.visit_provenance(visit),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -462,10 +462,10 @@ impl Stacks {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VisitTags for Stacks {
|
impl VisitProvenance for Stacks {
|
||||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||||
for tag in self.exposed_tags.iter().copied() {
|
for tag in self.exposed_tags.iter().copied() {
|
||||||
visit(tag);
|
visit(None, Some(tag));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -200,7 +200,7 @@ impl<'tcx> Tree {
|
||||||
/// Climb the tree to get the tag of a distant ancestor.
|
/// Climb the tree to get the tag of a distant ancestor.
|
||||||
/// Allows operations on tags that are unreachable by the program
|
/// Allows operations on tags that are unreachable by the program
|
||||||
/// but still exist in the tree. Not guaranteed to perform consistently
|
/// but still exist in the tree. Not guaranteed to perform consistently
|
||||||
/// if `tag-gc=1`.
|
/// if `provenance-gc=1`.
|
||||||
fn nth_parent(&self, tag: BorTag, nth_parent: u8) -> Option<BorTag> {
|
fn nth_parent(&self, tag: BorTag, nth_parent: u8) -> Option<BorTag> {
|
||||||
let mut idx = self.tag_mapping.get(&tag).unwrap();
|
let mut idx = self.tag_mapping.get(&tag).unwrap();
|
||||||
for _ in 0..nth_parent {
|
for _ in 0..nth_parent {
|
||||||
|
|
|
@ -742,11 +742,11 @@ impl Tree {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VisitTags for Tree {
|
impl VisitProvenance for Tree {
|
||||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||||
// To ensure that the root never gets removed, we visit it
|
// To ensure that the root never gets removed, we visit it
|
||||||
// (the `root` node of `Tree` is not an `Option<_>`)
|
// (the `root` node of `Tree` is not an `Option<_>`)
|
||||||
visit(self.nodes.get(self.root).unwrap().tag)
|
visit(None, Some(self.nodes.get(self.root).unwrap().tag))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -790,9 +790,9 @@ pub struct VClockAlloc {
|
||||||
alloc_ranges: RefCell<RangeMap<MemoryCellClocks>>,
|
alloc_ranges: RefCell<RangeMap<MemoryCellClocks>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VisitTags for VClockAlloc {
|
impl VisitProvenance for VClockAlloc {
|
||||||
fn visit_tags(&self, _visit: &mut dyn FnMut(BorTag)) {
|
fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
|
||||||
// No tags here.
|
// No tags or allocIds here.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1404,8 +1404,8 @@ pub struct GlobalState {
|
||||||
pub track_outdated_loads: bool,
|
pub track_outdated_loads: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VisitTags for GlobalState {
|
impl VisitProvenance for GlobalState {
|
||||||
fn visit_tags(&self, _visit: &mut dyn FnMut(BorTag)) {
|
fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
|
||||||
// We don't have any tags.
|
// We don't have any tags.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,10 +45,10 @@ pub(super) struct InitOnce<'mir, 'tcx> {
|
||||||
data_race: VClock,
|
data_race: VClock,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'mir, 'tcx> VisitTags for InitOnce<'mir, 'tcx> {
|
impl<'mir, 'tcx> VisitProvenance for InitOnce<'mir, 'tcx> {
|
||||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||||
for waiter in self.waiters.iter() {
|
for waiter in self.waiters.iter() {
|
||||||
waiter.callback.visit_tags(visit);
|
waiter.callback.visit_provenance(visit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -181,10 +181,10 @@ pub(crate) struct SynchronizationState<'mir, 'tcx> {
|
||||||
pub(super) init_onces: IndexVec<InitOnceId, InitOnce<'mir, 'tcx>>,
|
pub(super) init_onces: IndexVec<InitOnceId, InitOnce<'mir, 'tcx>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'mir, 'tcx> VisitTags for SynchronizationState<'mir, 'tcx> {
|
impl<'mir, 'tcx> VisitProvenance for SynchronizationState<'mir, 'tcx> {
|
||||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||||
for init_once in self.init_onces.iter() {
|
for init_once in self.init_onces.iter() {
|
||||||
init_once.visit_tags(visit);
|
init_once.visit_provenance(visit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ pub enum TlsAllocAction {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trait for callbacks that can be executed when some event happens, such as after a timeout.
|
/// Trait for callbacks that can be executed when some event happens, such as after a timeout.
|
||||||
pub trait MachineCallback<'mir, 'tcx>: VisitTags {
|
pub trait MachineCallback<'mir, 'tcx>: VisitProvenance {
|
||||||
fn call(&self, ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>) -> InterpResult<'tcx>;
|
fn call(&self, ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>) -> InterpResult<'tcx>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -228,8 +228,8 @@ impl<'mir, 'tcx> Thread<'mir, 'tcx> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VisitTags for Thread<'_, '_> {
|
impl VisitProvenance for Thread<'_, '_> {
|
||||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||||
let Thread {
|
let Thread {
|
||||||
panic_payloads: panic_payload,
|
panic_payloads: panic_payload,
|
||||||
last_error,
|
last_error,
|
||||||
|
@ -242,17 +242,17 @@ impl VisitTags for Thread<'_, '_> {
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
for payload in panic_payload {
|
for payload in panic_payload {
|
||||||
payload.visit_tags(visit);
|
payload.visit_provenance(visit);
|
||||||
}
|
}
|
||||||
last_error.visit_tags(visit);
|
last_error.visit_provenance(visit);
|
||||||
for frame in stack {
|
for frame in stack {
|
||||||
frame.visit_tags(visit)
|
frame.visit_provenance(visit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VisitTags for Frame<'_, '_, Provenance, FrameExtra<'_>> {
|
impl VisitProvenance for Frame<'_, '_, Provenance, FrameExtra<'_>> {
|
||||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||||
let Frame {
|
let Frame {
|
||||||
return_place,
|
return_place,
|
||||||
locals,
|
locals,
|
||||||
|
@ -266,22 +266,22 @@ impl VisitTags for Frame<'_, '_, Provenance, FrameExtra<'_>> {
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
// Return place.
|
// Return place.
|
||||||
return_place.visit_tags(visit);
|
return_place.visit_provenance(visit);
|
||||||
// Locals.
|
// Locals.
|
||||||
for local in locals.iter() {
|
for local in locals.iter() {
|
||||||
match local.as_mplace_or_imm() {
|
match local.as_mplace_or_imm() {
|
||||||
None => {}
|
None => {}
|
||||||
Some(Either::Left((ptr, meta))) => {
|
Some(Either::Left((ptr, meta))) => {
|
||||||
ptr.visit_tags(visit);
|
ptr.visit_provenance(visit);
|
||||||
meta.visit_tags(visit);
|
meta.visit_provenance(visit);
|
||||||
}
|
}
|
||||||
Some(Either::Right(imm)) => {
|
Some(Either::Right(imm)) => {
|
||||||
imm.visit_tags(visit);
|
imm.visit_provenance(visit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extra.visit_tags(visit);
|
extra.visit_provenance(visit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -341,8 +341,8 @@ pub struct ThreadManager<'mir, 'tcx> {
|
||||||
timeout_callbacks: FxHashMap<ThreadId, TimeoutCallbackInfo<'mir, 'tcx>>,
|
timeout_callbacks: FxHashMap<ThreadId, TimeoutCallbackInfo<'mir, 'tcx>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VisitTags for ThreadManager<'_, '_> {
|
impl VisitProvenance for ThreadManager<'_, '_> {
|
||||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||||
let ThreadManager {
|
let ThreadManager {
|
||||||
threads,
|
threads,
|
||||||
thread_local_alloc_ids,
|
thread_local_alloc_ids,
|
||||||
|
@ -353,15 +353,15 @@ impl VisitTags for ThreadManager<'_, '_> {
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
for thread in threads {
|
for thread in threads {
|
||||||
thread.visit_tags(visit);
|
thread.visit_provenance(visit);
|
||||||
}
|
}
|
||||||
for ptr in thread_local_alloc_ids.borrow().values() {
|
for ptr in thread_local_alloc_ids.borrow().values() {
|
||||||
ptr.visit_tags(visit);
|
ptr.visit_provenance(visit);
|
||||||
}
|
}
|
||||||
for callback in timeout_callbacks.values() {
|
for callback in timeout_callbacks.values() {
|
||||||
callback.callback.visit_tags(visit);
|
callback.callback.visit_provenance(visit);
|
||||||
}
|
}
|
||||||
sync.visit_tags(visit);
|
sync.visit_provenance(visit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -108,15 +108,15 @@ pub struct StoreBufferAlloc {
|
||||||
store_buffers: RefCell<RangeObjectMap<StoreBuffer>>,
|
store_buffers: RefCell<RangeObjectMap<StoreBuffer>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VisitTags for StoreBufferAlloc {
|
impl VisitProvenance for StoreBufferAlloc {
|
||||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||||
let Self { store_buffers } = self;
|
let Self { store_buffers } = self;
|
||||||
for val in store_buffers
|
for val in store_buffers
|
||||||
.borrow()
|
.borrow()
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|buf| buf.buffer.iter().map(|element| &element.val))
|
.flat_map(|buf| buf.buffer.iter().map(|element| &element.val))
|
||||||
{
|
{
|
||||||
val.visit_tags(visit);
|
val.visit_provenance(visit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,9 +46,21 @@ pub struct GlobalStateInner {
|
||||||
provenance_mode: ProvenanceMode,
|
provenance_mode: ProvenanceMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VisitTags for GlobalStateInner {
|
impl VisitProvenance for GlobalStateInner {
|
||||||
fn visit_tags(&self, _visit: &mut dyn FnMut(BorTag)) {
|
fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
|
||||||
// Nothing to visit here.
|
let GlobalStateInner {
|
||||||
|
int_to_ptr_map: _,
|
||||||
|
base_addr: _,
|
||||||
|
exposed: _,
|
||||||
|
next_base_addr: _,
|
||||||
|
provenance_mode: _,
|
||||||
|
} = self;
|
||||||
|
// Though base_addr, int_to_ptr_map, and exposed contain AllocIds, we do not want to visit them.
|
||||||
|
// int_to_ptr_map and exposed must contain only live allocations, and those
|
||||||
|
// are never garbage collected.
|
||||||
|
// base_addr is only relevant if we have a pointer to an AllocId and need to look up its
|
||||||
|
// base address; so if an AllocId is not reachable from somewhere else we can remove it
|
||||||
|
// here.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,6 +74,12 @@ impl GlobalStateInner {
|
||||||
provenance_mode: config.provenance_mode,
|
provenance_mode: config.provenance_mode,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn remove_unreachable_allocs(&mut self, allocs: &LiveAllocs<'_, '_, '_>) {
|
||||||
|
// `exposed` and `int_to_ptr_map` are cleared immediately when an allocation
|
||||||
|
// is freed, so `base_addr` is the only one we have to clean up based on the GC.
|
||||||
|
self.base_addr.retain(|id, _| allocs.is_live(*id));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Shifts `addr` to make it aligned with `align` by rounding `addr` to the smallest multiple
|
/// Shifts `addr` to make it aligned with `align` by rounding `addr` to the smallest multiple
|
||||||
|
@ -107,7 +125,7 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
|
||||||
// We only use this provenance if it has been exposed.
|
// We only use this provenance if it has been exposed.
|
||||||
if global_state.exposed.contains(&alloc_id) {
|
if global_state.exposed.contains(&alloc_id) {
|
||||||
// This must still be live, since we remove allocations from `int_to_ptr_map` when they get freed.
|
// This must still be live, since we remove allocations from `int_to_ptr_map` when they get freed.
|
||||||
debug_assert!(!matches!(ecx.get_alloc_info(alloc_id).2, AllocKind::Dead));
|
debug_assert!(ecx.is_alloc_live(alloc_id));
|
||||||
Some(alloc_id)
|
Some(alloc_id)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -181,12 +199,19 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
|
||||||
let ecx = self.eval_context_mut();
|
let ecx = self.eval_context_mut();
|
||||||
let global_state = ecx.machine.intptrcast.get_mut();
|
let global_state = ecx.machine.intptrcast.get_mut();
|
||||||
// In strict mode, we don't need this, so we can save some cycles by not tracking it.
|
// In strict mode, we don't need this, so we can save some cycles by not tracking it.
|
||||||
if global_state.provenance_mode != ProvenanceMode::Strict {
|
if global_state.provenance_mode == ProvenanceMode::Strict {
|
||||||
trace!("Exposing allocation id {alloc_id:?}");
|
return Ok(());
|
||||||
global_state.exposed.insert(alloc_id);
|
}
|
||||||
if ecx.machine.borrow_tracker.is_some() {
|
// Exposing a dead alloc is a no-op, because it's not possible to get a dead allocation
|
||||||
ecx.expose_tag(alloc_id, tag)?;
|
// via int2ptr.
|
||||||
}
|
if !ecx.is_alloc_live(alloc_id) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
trace!("Exposing allocation id {alloc_id:?}");
|
||||||
|
let global_state = ecx.machine.intptrcast.get_mut();
|
||||||
|
global_state.exposed.insert(alloc_id);
|
||||||
|
if ecx.machine.borrow_tracker.is_some() {
|
||||||
|
ecx.expose_tag(alloc_id, tag)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,9 +77,9 @@ mod intptrcast;
|
||||||
mod machine;
|
mod machine;
|
||||||
mod mono_hash_map;
|
mod mono_hash_map;
|
||||||
mod operator;
|
mod operator;
|
||||||
|
mod provenance_gc;
|
||||||
mod range_map;
|
mod range_map;
|
||||||
mod shims;
|
mod shims;
|
||||||
mod tag_gc;
|
|
||||||
|
|
||||||
// Establish a "crate-wide prelude": we often import `crate::*`.
|
// Establish a "crate-wide prelude": we often import `crate::*`.
|
||||||
|
|
||||||
|
@ -125,8 +125,8 @@ pub use crate::machine::{
|
||||||
};
|
};
|
||||||
pub use crate::mono_hash_map::MonoHashMap;
|
pub use crate::mono_hash_map::MonoHashMap;
|
||||||
pub use crate::operator::EvalContextExt as _;
|
pub use crate::operator::EvalContextExt as _;
|
||||||
|
pub use crate::provenance_gc::{EvalContextExt as _, VisitProvenance, VisitWith, LiveAllocs};
|
||||||
pub use crate::range_map::RangeMap;
|
pub use crate::range_map::RangeMap;
|
||||||
pub use crate::tag_gc::{EvalContextExt as _, VisitTags};
|
|
||||||
|
|
||||||
/// Insert rustc arguments at the beginning of the argument list that Miri wants to be
|
/// Insert rustc arguments at the beginning of the argument list that Miri wants to be
|
||||||
/// set per default, for maximal validation power.
|
/// set per default, for maximal validation power.
|
||||||
|
|
|
@ -77,12 +77,12 @@ impl<'tcx> std::fmt::Debug for FrameExtra<'tcx> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VisitTags for FrameExtra<'_> {
|
impl VisitProvenance for FrameExtra<'_> {
|
||||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||||
let FrameExtra { catch_unwind, borrow_tracker, timing: _, is_user_relevant: _ } = self;
|
let FrameExtra { catch_unwind, borrow_tracker, timing: _, is_user_relevant: _ } = self;
|
||||||
|
|
||||||
catch_unwind.visit_tags(visit);
|
catch_unwind.visit_provenance(visit);
|
||||||
borrow_tracker.visit_tags(visit);
|
borrow_tracker.visit_provenance(visit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -311,13 +311,13 @@ pub struct AllocExtra<'tcx> {
|
||||||
pub backtrace: Option<Vec<FrameInfo<'tcx>>>,
|
pub backtrace: Option<Vec<FrameInfo<'tcx>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VisitTags for AllocExtra<'_> {
|
impl VisitProvenance for AllocExtra<'_> {
|
||||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||||
let AllocExtra { borrow_tracker, data_race, weak_memory, backtrace: _ } = self;
|
let AllocExtra { borrow_tracker, data_race, weak_memory, backtrace: _ } = self;
|
||||||
|
|
||||||
borrow_tracker.visit_tags(visit);
|
borrow_tracker.visit_provenance(visit);
|
||||||
data_race.visit_tags(visit);
|
data_race.visit_provenance(visit);
|
||||||
weak_memory.visit_tags(visit);
|
weak_memory.visit_provenance(visit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -793,8 +793,8 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VisitTags for MiriMachine<'_, '_> {
|
impl VisitProvenance for MiriMachine<'_, '_> {
|
||||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
let MiriMachine {
|
let MiriMachine {
|
||||||
threads,
|
threads,
|
||||||
|
@ -843,20 +843,20 @@ impl VisitTags for MiriMachine<'_, '_> {
|
||||||
allocation_spans: _,
|
allocation_spans: _,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
threads.visit_tags(visit);
|
threads.visit_provenance(visit);
|
||||||
tls.visit_tags(visit);
|
tls.visit_provenance(visit);
|
||||||
env_vars.visit_tags(visit);
|
env_vars.visit_provenance(visit);
|
||||||
dir_handler.visit_tags(visit);
|
dir_handler.visit_provenance(visit);
|
||||||
file_handler.visit_tags(visit);
|
file_handler.visit_provenance(visit);
|
||||||
data_race.visit_tags(visit);
|
data_race.visit_provenance(visit);
|
||||||
borrow_tracker.visit_tags(visit);
|
borrow_tracker.visit_provenance(visit);
|
||||||
intptrcast.visit_tags(visit);
|
intptrcast.visit_provenance(visit);
|
||||||
main_fn_ret_place.visit_tags(visit);
|
main_fn_ret_place.visit_provenance(visit);
|
||||||
argc.visit_tags(visit);
|
argc.visit_provenance(visit);
|
||||||
argv.visit_tags(visit);
|
argv.visit_provenance(visit);
|
||||||
cmd_line.visit_tags(visit);
|
cmd_line.visit_provenance(visit);
|
||||||
for ptr in extern_statics.values() {
|
for ptr in extern_statics.values() {
|
||||||
ptr.visit_tags(visit);
|
ptr.visit_provenance(visit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1380,7 +1380,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
|
||||||
// where it mistakenly removes an important tag become visible.
|
// where it mistakenly removes an important tag become visible.
|
||||||
if ecx.machine.gc_interval > 0 && ecx.machine.since_gc >= ecx.machine.gc_interval {
|
if ecx.machine.gc_interval > 0 && ecx.machine.since_gc >= ecx.machine.gc_interval {
|
||||||
ecx.machine.since_gc = 0;
|
ecx.machine.since_gc = 0;
|
||||||
ecx.garbage_collect_tags()?;
|
ecx.run_provenance_gc();
|
||||||
}
|
}
|
||||||
|
|
||||||
// These are our preemption points.
|
// These are our preemption points.
|
||||||
|
|
|
@ -46,6 +46,14 @@ impl<K: Hash + Eq, V> AllocMap<K, V> for MonoHashMap<K, V> {
|
||||||
self.0.get_mut().contains_key(k)
|
self.0.get_mut().contains_key(k)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn contains_key_ref<Q: ?Sized + Hash + Eq>(&self, k: &Q) -> bool
|
||||||
|
where
|
||||||
|
K: Borrow<Q>,
|
||||||
|
{
|
||||||
|
self.0.borrow().contains_key(k)
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn insert(&mut self, k: K, v: V) -> Option<V> {
|
fn insert(&mut self, k: K, v: V) -> Option<V> {
|
||||||
self.0.get_mut().insert(k, Box::new(v)).map(|x| *x)
|
self.0.get_mut().insert(k, Box::new(v)).map(|x| *x)
|
||||||
|
|
209
src/tools/miri/src/provenance_gc.rs
Normal file
209
src/tools/miri/src/provenance_gc.rs
Normal file
|
@ -0,0 +1,209 @@
|
||||||
|
use either::Either;
|
||||||
|
|
||||||
|
use rustc_data_structures::fx::FxHashSet;
|
||||||
|
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
pub type VisitWith<'a> = dyn FnMut(Option<AllocId>, Option<BorTag>) + 'a;
|
||||||
|
|
||||||
|
pub trait VisitProvenance {
|
||||||
|
fn visit_provenance(&self, visit: &mut VisitWith<'_>);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: VisitProvenance> VisitProvenance for Option<T> {
|
||||||
|
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||||
|
if let Some(x) = self {
|
||||||
|
x.visit_provenance(visit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: VisitProvenance> VisitProvenance for std::cell::RefCell<T> {
|
||||||
|
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||||
|
self.borrow().visit_provenance(visit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VisitProvenance for BorTag {
|
||||||
|
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||||
|
visit(None, Some(*self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VisitProvenance for AllocId {
|
||||||
|
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||||
|
visit(Some(*self), None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VisitProvenance for Provenance {
|
||||||
|
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||||
|
if let Provenance::Concrete { alloc_id, tag, .. } = self {
|
||||||
|
visit(Some(*alloc_id), Some(*tag));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VisitProvenance for Pointer<Provenance> {
|
||||||
|
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||||
|
let (prov, _offset) = self.into_parts();
|
||||||
|
prov.visit_provenance(visit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VisitProvenance for Pointer<Option<Provenance>> {
|
||||||
|
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||||
|
let (prov, _offset) = self.into_parts();
|
||||||
|
prov.visit_provenance(visit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VisitProvenance for Scalar<Provenance> {
|
||||||
|
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||||
|
match self {
|
||||||
|
Scalar::Ptr(ptr, _) => ptr.visit_provenance(visit),
|
||||||
|
Scalar::Int(_) => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VisitProvenance for Immediate<Provenance> {
|
||||||
|
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||||
|
match self {
|
||||||
|
Immediate::Scalar(s) => {
|
||||||
|
s.visit_provenance(visit);
|
||||||
|
}
|
||||||
|
Immediate::ScalarPair(s1, s2) => {
|
||||||
|
s1.visit_provenance(visit);
|
||||||
|
s2.visit_provenance(visit);
|
||||||
|
}
|
||||||
|
Immediate::Uninit => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VisitProvenance for MemPlaceMeta<Provenance> {
|
||||||
|
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||||
|
match self {
|
||||||
|
MemPlaceMeta::Meta(m) => m.visit_provenance(visit),
|
||||||
|
MemPlaceMeta::None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VisitProvenance for ImmTy<'_, Provenance> {
|
||||||
|
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||||
|
(**self).visit_provenance(visit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VisitProvenance for MPlaceTy<'_, Provenance> {
|
||||||
|
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||||
|
self.ptr().visit_provenance(visit);
|
||||||
|
self.meta().visit_provenance(visit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VisitProvenance for PlaceTy<'_, Provenance> {
|
||||||
|
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||||
|
match self.as_mplace_or_local() {
|
||||||
|
Either::Left(mplace) => mplace.visit_provenance(visit),
|
||||||
|
Either::Right(_) => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VisitProvenance for OpTy<'_, Provenance> {
|
||||||
|
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||||
|
match self.as_mplace_or_imm() {
|
||||||
|
Either::Left(mplace) => mplace.visit_provenance(visit),
|
||||||
|
Either::Right(imm) => imm.visit_provenance(visit),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VisitProvenance for Allocation<Provenance, AllocExtra<'_>> {
|
||||||
|
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||||
|
for prov in self.provenance().provenances() {
|
||||||
|
prov.visit_provenance(visit);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.extra.visit_provenance(visit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VisitProvenance for crate::MiriInterpCx<'_, '_> {
|
||||||
|
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||||
|
// Visit the contents of the allocations and the IDs themselves, to account for all
|
||||||
|
// live allocation IDs and all provenance in the allocation bytes, even if they are leaked.
|
||||||
|
// We do *not* visit all the `AllocId` of the live allocations; we tried that and adding
|
||||||
|
// them all to the live set is too expensive. Instead we later do liveness check by
|
||||||
|
// checking both "is this alloc id live" and "is it mentioned anywhere else in
|
||||||
|
// the interpreter state".
|
||||||
|
self.memory.alloc_map().iter(|it| {
|
||||||
|
for (_id, (_kind, alloc)) in it {
|
||||||
|
alloc.visit_provenance(visit);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// And all the other machine values.
|
||||||
|
self.machine.visit_provenance(visit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct LiveAllocs<'a, 'mir, 'tcx> {
|
||||||
|
collected: FxHashSet<AllocId>,
|
||||||
|
ecx: &'a MiriInterpCx<'mir, 'tcx>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LiveAllocs<'_, '_, '_> {
|
||||||
|
pub fn is_live(&self, id: AllocId) -> bool {
|
||||||
|
self.collected.contains(&id) ||
|
||||||
|
self.ecx.is_alloc_live(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
|
||||||
|
pub trait EvalContextExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> {
|
||||||
|
fn run_provenance_gc(&mut self) {
|
||||||
|
|
||||||
|
// We collect all tags from various parts of the interpreter, but also
|
||||||
|
let this = self.eval_context_mut();
|
||||||
|
|
||||||
|
let mut tags = FxHashSet::default();
|
||||||
|
let mut alloc_ids = FxHashSet::default();
|
||||||
|
this.visit_provenance(&mut |id, tag| {
|
||||||
|
if let Some(id) = id {
|
||||||
|
alloc_ids.insert(id);
|
||||||
|
}
|
||||||
|
if let Some(tag) = tag {
|
||||||
|
tags.insert(tag);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
self.remove_unreachable_tags(tags);
|
||||||
|
self.remove_unreachable_allocs(alloc_ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_unreachable_tags(&mut self, tags: FxHashSet<BorTag>) {
|
||||||
|
let this = self.eval_context_mut();
|
||||||
|
this.memory.alloc_map().iter(|it| {
|
||||||
|
for (_id, (_kind, alloc)) in it {
|
||||||
|
if let Some(bt) = &alloc.extra.borrow_tracker {
|
||||||
|
bt.remove_unreachable_tags(&tags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_unreachable_allocs(&mut self, allocs: FxHashSet<AllocId>) {
|
||||||
|
let this = self.eval_context_ref();
|
||||||
|
let allocs = LiveAllocs {
|
||||||
|
ecx: this,
|
||||||
|
collected: allocs,
|
||||||
|
};
|
||||||
|
this.machine.allocation_spans.borrow_mut().retain(|id, _| allocs.is_live(*id));
|
||||||
|
this.machine.intptrcast.borrow_mut().remove_unreachable_allocs(&allocs);
|
||||||
|
if let Some(borrow_tracker) = &this.machine.borrow_tracker {
|
||||||
|
borrow_tracker.borrow_mut().remove_unreachable_allocs(&allocs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -37,13 +37,13 @@ pub struct EnvVars<'tcx> {
|
||||||
pub(crate) environ: Option<MPlaceTy<'tcx, Provenance>>,
|
pub(crate) environ: Option<MPlaceTy<'tcx, Provenance>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VisitTags for EnvVars<'_> {
|
impl VisitProvenance for EnvVars<'_> {
|
||||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||||
let EnvVars { map, environ } = self;
|
let EnvVars { map, environ } = self;
|
||||||
|
|
||||||
environ.visit_tags(visit);
|
environ.visit_provenance(visit);
|
||||||
for ptr in map.values() {
|
for ptr in map.values() {
|
||||||
ptr.visit_tags(visit);
|
ptr.visit_provenance(visit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -459,6 +459,10 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
|
||||||
// shim, add it to the corresponding submodule.
|
// shim, add it to the corresponding submodule.
|
||||||
match link_name.as_str() {
|
match link_name.as_str() {
|
||||||
// Miri-specific extern functions
|
// Miri-specific extern functions
|
||||||
|
"miri_run_provenance_gc" => {
|
||||||
|
let [] = this.check_shim(abi, Abi::Rust, link_name, args)?;
|
||||||
|
this.run_provenance_gc();
|
||||||
|
}
|
||||||
"miri_get_alloc_id" => {
|
"miri_get_alloc_id" => {
|
||||||
let [ptr] = this.check_shim(abi, Abi::Rust, link_name, args)?;
|
let [ptr] = this.check_shim(abi, Abi::Rust, link_name, args)?;
|
||||||
let ptr = this.read_pointer(ptr)?;
|
let ptr = this.read_pointer(ptr)?;
|
||||||
|
|
|
@ -35,12 +35,12 @@ pub struct CatchUnwindData<'tcx> {
|
||||||
ret: mir::BasicBlock,
|
ret: mir::BasicBlock,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VisitTags for CatchUnwindData<'_> {
|
impl VisitProvenance for CatchUnwindData<'_> {
|
||||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||||
let CatchUnwindData { catch_fn, data, dest, ret: _ } = self;
|
let CatchUnwindData { catch_fn, data, dest, ret: _ } = self;
|
||||||
catch_fn.visit_tags(visit);
|
catch_fn.visit_provenance(visit);
|
||||||
data.visit_tags(visit);
|
data.visit_provenance(visit);
|
||||||
dest.visit_tags(visit);
|
dest.visit_provenance(visit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -274,8 +274,8 @@ struct UnblockCallback {
|
||||||
thread_to_unblock: ThreadId,
|
thread_to_unblock: ThreadId,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VisitTags for UnblockCallback {
|
impl VisitProvenance for UnblockCallback {
|
||||||
fn visit_tags(&self, _visit: &mut dyn FnMut(BorTag)) {}
|
fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'mir, 'tcx: 'mir> MachineCallback<'mir, 'tcx> for UnblockCallback {
|
impl<'mir, 'tcx: 'mir> MachineCallback<'mir, 'tcx> for UnblockCallback {
|
||||||
|
|
|
@ -207,15 +207,15 @@ impl<'tcx> TlsData<'tcx> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VisitTags for TlsData<'_> {
|
impl VisitProvenance for TlsData<'_> {
|
||||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||||
let TlsData { keys, macos_thread_dtors, next_key: _ } = self;
|
let TlsData { keys, macos_thread_dtors, next_key: _ } = self;
|
||||||
|
|
||||||
for scalar in keys.values().flat_map(|v| v.data.values()) {
|
for scalar in keys.values().flat_map(|v| v.data.values()) {
|
||||||
scalar.visit_tags(visit);
|
scalar.visit_provenance(visit);
|
||||||
}
|
}
|
||||||
for (_, scalar) in macos_thread_dtors.values() {
|
for (_, scalar) in macos_thread_dtors.values() {
|
||||||
scalar.visit_tags(visit);
|
scalar.visit_provenance(visit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -288,8 +288,8 @@ pub struct FileHandler {
|
||||||
pub handles: BTreeMap<i32, Box<dyn FileDescriptor>>,
|
pub handles: BTreeMap<i32, Box<dyn FileDescriptor>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VisitTags for FileHandler {
|
impl VisitProvenance for FileHandler {
|
||||||
fn visit_tags(&self, _visit: &mut dyn FnMut(BorTag)) {
|
fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
|
||||||
// All our FileDescriptor do not have any tags.
|
// All our FileDescriptor do not have any tags.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -490,12 +490,12 @@ impl Default for DirHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VisitTags for DirHandler {
|
impl VisitProvenance for DirHandler {
|
||||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||||
let DirHandler { streams, next_id: _ } = self;
|
let DirHandler { streams, next_id: _ } = self;
|
||||||
|
|
||||||
for dir in streams.values() {
|
for dir in streams.values() {
|
||||||
dir.entry.visit_tags(visit);
|
dir.entry.visit_provenance(visit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -182,10 +182,10 @@ pub fn futex<'tcx>(
|
||||||
dest: PlaceTy<'tcx, Provenance>,
|
dest: PlaceTy<'tcx, Provenance>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'tcx> VisitTags for Callback<'tcx> {
|
impl<'tcx> VisitProvenance for Callback<'tcx> {
|
||||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||||
let Callback { thread: _, addr_usize: _, dest } = self;
|
let Callback { thread: _, addr_usize: _, dest } = self;
|
||||||
dest.visit_tags(visit);
|
dest.visit_provenance(visit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -886,10 +886,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
|
||||||
dest: PlaceTy<'tcx, Provenance>,
|
dest: PlaceTy<'tcx, Provenance>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'tcx> VisitTags for Callback<'tcx> {
|
impl<'tcx> VisitProvenance for Callback<'tcx> {
|
||||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||||
let Callback { active_thread: _, mutex_id: _, id: _, dest } = self;
|
let Callback { active_thread: _, mutex_id: _, id: _, dest } = self;
|
||||||
dest.visit_tags(visit);
|
dest.visit_provenance(visit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -204,10 +204,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
|
||||||
pending_place: PlaceTy<'tcx, Provenance>,
|
pending_place: PlaceTy<'tcx, Provenance>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'tcx> VisitTags for Callback<'tcx> {
|
impl<'tcx> VisitProvenance for Callback<'tcx> {
|
||||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||||
let Callback { init_once_id: _, pending_place } = self;
|
let Callback { init_once_id: _, pending_place } = self;
|
||||||
pending_place.visit_tags(visit);
|
pending_place.visit_provenance(visit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -337,10 +337,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
|
||||||
dest: PlaceTy<'tcx, Provenance>,
|
dest: PlaceTy<'tcx, Provenance>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'tcx> VisitTags for Callback<'tcx> {
|
impl<'tcx> VisitProvenance for Callback<'tcx> {
|
||||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||||
let Callback { thread: _, addr: _, dest } = self;
|
let Callback { thread: _, addr: _, dest } = self;
|
||||||
dest.visit_tags(visit);
|
dest.visit_provenance(visit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -441,10 +441,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
|
||||||
dest: PlaceTy<'tcx, Provenance>,
|
dest: PlaceTy<'tcx, Provenance>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'tcx> VisitTags for Callback<'tcx> {
|
impl<'tcx> VisitProvenance for Callback<'tcx> {
|
||||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||||
let Callback { thread: _, condvar_id: _, lock_id: _, mode: _, dest } = self;
|
let Callback { thread: _, condvar_id: _, lock_id: _, mode: _, dest } = self;
|
||||||
dest.visit_tags(visit);
|
dest.visit_provenance(visit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,169 +0,0 @@
|
||||||
use either::Either;
|
|
||||||
|
|
||||||
use rustc_data_structures::fx::FxHashSet;
|
|
||||||
|
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
pub trait VisitTags {
|
|
||||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag));
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: VisitTags> VisitTags for Option<T> {
|
|
||||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
|
||||||
if let Some(x) = self {
|
|
||||||
x.visit_tags(visit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: VisitTags> VisitTags for std::cell::RefCell<T> {
|
|
||||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
|
||||||
self.borrow().visit_tags(visit)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VisitTags for BorTag {
|
|
||||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
|
||||||
visit(*self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VisitTags for Provenance {
|
|
||||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
|
||||||
if let Provenance::Concrete { tag, .. } = self {
|
|
||||||
visit(*tag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VisitTags for Pointer<Provenance> {
|
|
||||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
|
||||||
let (prov, _offset) = self.into_parts();
|
|
||||||
prov.visit_tags(visit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VisitTags for Pointer<Option<Provenance>> {
|
|
||||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
|
||||||
let (prov, _offset) = self.into_parts();
|
|
||||||
prov.visit_tags(visit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VisitTags for Scalar<Provenance> {
|
|
||||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
|
||||||
match self {
|
|
||||||
Scalar::Ptr(ptr, _) => ptr.visit_tags(visit),
|
|
||||||
Scalar::Int(_) => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VisitTags for Immediate<Provenance> {
|
|
||||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
|
||||||
match self {
|
|
||||||
Immediate::Scalar(s) => {
|
|
||||||
s.visit_tags(visit);
|
|
||||||
}
|
|
||||||
Immediate::ScalarPair(s1, s2) => {
|
|
||||||
s1.visit_tags(visit);
|
|
||||||
s2.visit_tags(visit);
|
|
||||||
}
|
|
||||||
Immediate::Uninit => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VisitTags for MemPlaceMeta<Provenance> {
|
|
||||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
|
||||||
match self {
|
|
||||||
MemPlaceMeta::Meta(m) => m.visit_tags(visit),
|
|
||||||
MemPlaceMeta::None => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VisitTags for ImmTy<'_, Provenance> {
|
|
||||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
|
||||||
(**self).visit_tags(visit)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VisitTags for MPlaceTy<'_, Provenance> {
|
|
||||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
|
||||||
self.ptr().visit_tags(visit);
|
|
||||||
self.meta().visit_tags(visit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VisitTags for PlaceTy<'_, Provenance> {
|
|
||||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
|
||||||
match self.as_mplace_or_local() {
|
|
||||||
Either::Left(mplace) => mplace.visit_tags(visit),
|
|
||||||
Either::Right(_) => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VisitTags for OpTy<'_, Provenance> {
|
|
||||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
|
||||||
match self.as_mplace_or_imm() {
|
|
||||||
Either::Left(mplace) => mplace.visit_tags(visit),
|
|
||||||
Either::Right(imm) => imm.visit_tags(visit),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VisitTags for Allocation<Provenance, AllocExtra<'_>> {
|
|
||||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
|
||||||
for prov in self.provenance().provenances() {
|
|
||||||
prov.visit_tags(visit);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.extra.visit_tags(visit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VisitTags for crate::MiriInterpCx<'_, '_> {
|
|
||||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
|
||||||
// Memory.
|
|
||||||
self.memory.alloc_map().iter(|it| {
|
|
||||||
for (_id, (_kind, alloc)) in it {
|
|
||||||
alloc.visit_tags(visit);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// And all the other machine values.
|
|
||||||
self.machine.visit_tags(visit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
|
|
||||||
pub trait EvalContextExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> {
|
|
||||||
fn garbage_collect_tags(&mut self) -> InterpResult<'tcx> {
|
|
||||||
let this = self.eval_context_mut();
|
|
||||||
// No reason to do anything at all if stacked borrows is off.
|
|
||||||
if this.machine.borrow_tracker.is_none() {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut tags = FxHashSet::default();
|
|
||||||
this.visit_tags(&mut |tag| {
|
|
||||||
tags.insert(tag);
|
|
||||||
});
|
|
||||||
self.remove_unreachable_tags(tags);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_unreachable_tags(&mut self, tags: FxHashSet<BorTag>) {
|
|
||||||
let this = self.eval_context_mut();
|
|
||||||
this.memory.alloc_map().iter(|it| {
|
|
||||||
for (_id, (_kind, alloc)) in it {
|
|
||||||
if let Some(bt) = &alloc.extra.borrow_tracker {
|
|
||||||
bt.remove_unreachable_tags(&tags);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
//@compile-flags: -Zmiri-tree-borrows -Zmiri-tag-gc=0
|
//@compile-flags: -Zmiri-tree-borrows -Zmiri-provenance-gc=0
|
||||||
|
|
||||||
// Check how a Reserved with interior mutability
|
// Check how a Reserved with interior mutability
|
||||||
// responds to a Foreign Write under a Protector
|
// responds to a Foreign Write under a Protector
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
//@compile-flags: -Zmiri-tree-borrows -Zmiri-tag-gc=0
|
//@compile-flags: -Zmiri-tree-borrows -Zmiri-provenance-gc=0
|
||||||
|
|
||||||
#[path = "../../../utils/mod.rs"]
|
#[path = "../../../utils/mod.rs"]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
|
21
src/tools/miri/tests/pass-dep/extra_fn_ptr_gc.rs
Normal file
21
src/tools/miri/tests/pass-dep/extra_fn_ptr_gc.rs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
//@ignore-target-windows: No libc on Windows
|
||||||
|
//@compile-flags: -Zmiri-permissive-provenance
|
||||||
|
|
||||||
|
#[path = "../utils/mod.rs"]
|
||||||
|
mod utils;
|
||||||
|
|
||||||
|
type GetEntropyFn = unsafe extern "C" fn(*mut u8, libc::size_t) -> libc::c_int;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let name = "getentropy\0";
|
||||||
|
let addr = unsafe { libc::dlsym(libc::RTLD_DEFAULT, name.as_ptr() as *const _) as usize };
|
||||||
|
// If the GC does not account for the extra_fn_ptr entry that this dlsym just added, this GC
|
||||||
|
// run will delete our entry for the base addr of the function pointer we will transmute to,
|
||||||
|
// and the call through the function pointer will report UB.
|
||||||
|
utils::run_provenance_gc();
|
||||||
|
|
||||||
|
let ptr = addr as *mut libc::c_void;
|
||||||
|
let func: GetEntropyFn = unsafe { std::mem::transmute(ptr) };
|
||||||
|
let dest = &mut [0u8];
|
||||||
|
unsafe { func(dest.as_mut_ptr(), dest.len()) };
|
||||||
|
}
|
|
@ -1,4 +1,6 @@
|
||||||
//@compile-flags: -Zmiri-ignore-leaks -Zmiri-disable-stacked-borrows
|
//@compile-flags: -Zmiri-ignore-leaks -Zmiri-disable-stacked-borrows -Zmiri-provenance-gc=10000
|
||||||
|
// This test's runtime explodes if the GC interval is set to 1 (which we do in CI), so we
|
||||||
|
// override it internally back to the default frequency.
|
||||||
|
|
||||||
// The following tests check whether our weak memory emulation produces
|
// The following tests check whether our weak memory emulation produces
|
||||||
// any inconsistent execution outcomes
|
// any inconsistent execution outcomes
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
//@compile-flags: -Zmiri-tree-borrows -Zmiri-tag-gc=0
|
//@compile-flags: -Zmiri-tree-borrows -Zmiri-provenance-gc=0
|
||||||
#[path = "../../utils/mod.rs"]
|
#[path = "../../utils/mod.rs"]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
//@compile-flags: -Zmiri-tree-borrows -Zmiri-tag-gc=0
|
//@compile-flags: -Zmiri-tree-borrows -Zmiri-provenance-gc=0
|
||||||
|
|
||||||
// Check that a protector goes back to normal behavior when the function
|
// Check that a protector goes back to normal behavior when the function
|
||||||
// returns.
|
// returns.
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
//@compile-flags: -Zmiri-tree-borrows -Zmiri-tag-gc=0
|
//@compile-flags: -Zmiri-tree-borrows -Zmiri-provenance-gc=0
|
||||||
|
|
||||||
#[path = "../../utils/mod.rs"]
|
#[path = "../../utils/mod.rs"]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
//@compile-flags: -Zmiri-tree-borrows -Zmiri-tag-gc=0
|
//@compile-flags: -Zmiri-tree-borrows -Zmiri-provenance-gc=0
|
||||||
|
|
||||||
#[path = "../../utils/mod.rs"]
|
#[path = "../../utils/mod.rs"]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
//@compile-flags: -Zmiri-tree-borrows -Zmiri-tag-gc=0
|
//@compile-flags: -Zmiri-tree-borrows -Zmiri-provenance-gc=0
|
||||||
|
|
||||||
#[path = "../../utils/mod.rs"]
|
#[path = "../../utils/mod.rs"]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
//@revisions: default uniq
|
//@revisions: default uniq
|
||||||
//@compile-flags: -Zmiri-tree-borrows -Zmiri-tag-gc=0
|
//@compile-flags: -Zmiri-tree-borrows -Zmiri-provenance-gc=0
|
||||||
//@[uniq]compile-flags: -Zmiri-unique-is-unique
|
//@[uniq]compile-flags: -Zmiri-unique-is-unique
|
||||||
|
|
||||||
#![feature(ptr_internals)]
|
#![feature(ptr_internals)]
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
//@revisions: default uniq
|
//@revisions: default uniq
|
||||||
//@compile-flags: -Zmiri-tree-borrows -Zmiri-tag-gc=0
|
//@compile-flags: -Zmiri-tree-borrows -Zmiri-provenance-gc=0
|
||||||
//@[uniq]compile-flags: -Zmiri-unique-is-unique
|
//@[uniq]compile-flags: -Zmiri-unique-is-unique
|
||||||
|
|
||||||
#![feature(vec_into_raw_parts)]
|
#![feature(vec_into_raw_parts)]
|
||||||
|
|
|
@ -84,7 +84,7 @@ extern "Rust" {
|
||||||
///
|
///
|
||||||
/// The format of what this emits is unstable and may change at any time. In particular, users should be
|
/// The format of what this emits is unstable and may change at any time. In particular, users should be
|
||||||
/// aware that Miri will periodically attempt to garbage collect the contents of all stacks. Callers of
|
/// aware that Miri will periodically attempt to garbage collect the contents of all stacks. Callers of
|
||||||
/// this function may wish to pass `-Zmiri-tag-gc=0` to disable the GC.
|
/// this function may wish to pass `-Zmiri-provenance-gc=0` to disable the GC.
|
||||||
///
|
///
|
||||||
/// This function is extremely unstable. At any time the format of its output may change, its signature may
|
/// This function is extremely unstable. At any time the format of its output may change, its signature may
|
||||||
/// change, or it may be removed entirely.
|
/// change, or it may be removed entirely.
|
||||||
|
@ -137,4 +137,9 @@ extern "Rust" {
|
||||||
out: *mut std::ffi::c_char,
|
out: *mut std::ffi::c_char,
|
||||||
out_size: usize,
|
out_size: usize,
|
||||||
) -> usize;
|
) -> usize;
|
||||||
|
|
||||||
|
/// Run the provenance GC. The GC will run automatically at some cadence,
|
||||||
|
/// but in tests we want to for sure run it at certain points to check
|
||||||
|
/// that it doesn't break anything.
|
||||||
|
pub fn miri_run_provenance_gc();
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,3 +9,10 @@ mod miri_extern;
|
||||||
|
|
||||||
pub use fs::*;
|
pub use fs::*;
|
||||||
pub use miri_extern::*;
|
pub use miri_extern::*;
|
||||||
|
|
||||||
|
pub fn run_provenance_gc() {
|
||||||
|
// SAFETY: No preconditions. The GC is fine to run at any time.
|
||||||
|
unsafe {
|
||||||
|
miri_run_provenance_gc()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue