-
Notifications
You must be signed in to change notification settings - Fork 385
Weak memory emulation using store buffers #1963
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
46 commits
Select commit
Hold shift + click to select a range
8d36e8b
Add weak memory config option
cbeuw 16315b1
Add test cases
cbeuw e7698f4
Implement weak memory emulation
cbeuw aca3b3a
set_at_index sets the default value (0) if index doesn't exist in the…
cbeuw cf26658
Comment out and provide context to C++20 test
cbeuw ecdab5f
Clearer boundries between alloc metadata with multiple buffers and an…
cbeuw 53f4887
Use a new AllocationMap to store store buffers in the same allocation
cbeuw a71b103
Add imperfectly overlapping test
cbeuw bf7fe68
Add -Zmiri-disable-weak-memory-emulation to README
cbeuw 11ca975
Move type definitions together and clarify fetch_store on empty buffer
cbeuw 32627d5
Disable weak memory emulation on scheduler-dependent data race tests
cbeuw f729f28
Move cpp20_rwc_syncs into compile-fail
cbeuw 89138a6
Add more top-level comments
cbeuw 62b514e
Update README
cbeuw 773131b
Improve privacy and comments
cbeuw 7d874db
Add tests showing weak memory behaviours
cbeuw 6040c9f
Refactor store buffer search conditions
cbeuw 13e3465
Reduce the number of runs in consistency tests
cbeuw 8739e45
Move data_race and weak_memory into a submodule
cbeuw 335667c
Move buffered functions into their own ext trait
cbeuw 5a4a1bf
Remove incorrect comment
cbeuw 6b54c92
Throw UB on imperfectly overlapping access
cbeuw 577054c
Rename variables in AllocationMap
cbeuw 9214537
Put the initialisation value into the store buffer
cbeuw e2002b4
Amend experimental thread support warnings
cbeuw 31c0141
Replace yield_now() with spin loop hint
cbeuw 5ddd4ef
Spelling, punctuation and grammar
cbeuw 6d27f18
Update src/concurrency/weak_memory.rs
cbeuw dafd813
Move transmute into a separate function
cbeuw 7dcb19e
Add rust-only operation tests
cbeuw 2321b15
Differentiate between not multithreading and temp disabling race dete…
cbeuw 226ed41
Destroy store buffers on non-racy non-atomic accesses
cbeuw 613d60d
Allow non-racy mixed size accesses
cbeuw bfa5645
Split extra_cpp tests into sound and unsafe
cbeuw 6a73ded
Update experimental threading warning
cbeuw a7c832b
Wording improvements
cbeuw ceb173d
Move logic out of machine.rs
cbeuw 4a07f78
Forbade all racing mixed size atomic accesses
cbeuw 8215702
Refer to GitHub issue on overwritten init value
cbeuw c731071
Give flag temp disabling race detector a better name
cbeuw 6d0c76e
Specify only perfectly overlapping accesses can race
cbeuw 65f39bd
Move tests to new directories
cbeuw 1379036
Simplify known C++20 inconsistency test
cbeuw 6fb7c13
Remove unused lifetimes
cbeuw bf7a5c4
Add more backgrounds on lazy store buffers
cbeuw 1b32d14
Make racy imperfectly overlapping atomic access unsupported instead o…
cbeuw File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,278 @@ | ||
//! Implements a map from allocation ranges to data. | ||
//! This is somewhat similar to RangeMap, but the ranges | ||
//! and data are discrete and non-splittable. An allocation in the | ||
//! map will always have the same range until explicitly removed | ||
|
||
use rustc_target::abi::Size; | ||
use std::ops::{Index, IndexMut, Range}; | ||
|
||
use rustc_const_eval::interpret::AllocRange; | ||
|
||
#[derive(Clone, Debug)] | ||
struct Elem<T> { | ||
/// The range covered by this element; never empty. | ||
range: AllocRange, | ||
/// The data stored for this element. | ||
data: T, | ||
} | ||
|
||
/// Index of an allocation within the map | ||
type Position = usize; | ||
|
||
#[derive(Clone, Debug)] | ||
pub struct AllocationMap<T> { | ||
v: Vec<Elem<T>>, | ||
} | ||
|
||
#[derive(Clone, Debug, PartialEq)] | ||
pub enum AccessType { | ||
/// The access perfectly overlaps (same offset and range) with the exsiting allocation | ||
PerfectlyOverlapping(Position), | ||
/// The access does not touch any exising allocation | ||
Empty(Position), | ||
/// The access overlaps with one or more existing allocations | ||
ImperfectlyOverlapping(Range<Position>), | ||
} | ||
|
||
impl<T> AllocationMap<T> { | ||
pub fn new() -> Self { | ||
Self { v: Vec::new() } | ||
} | ||
|
||
/// Finds the position of the allocation containing the given offset. If the offset is not | ||
/// in an existing allocation, then returns Err containing the position | ||
/// where such allocation should be inserted | ||
fn find_offset(&self, offset: Size) -> Result<Position, Position> { | ||
// We do a binary search. | ||
let mut left = 0usize; // inclusive | ||
let mut right = self.v.len(); // exclusive | ||
loop { | ||
if left == right { | ||
// No element contains the given offset. But the | ||
// position is where such element should be placed at. | ||
return Err(left); | ||
} | ||
let candidate = left.checked_add(right).unwrap() / 2; | ||
let elem = &self.v[candidate]; | ||
if offset < elem.range.start { | ||
// We are too far right (offset is further left). | ||
debug_assert!(candidate < right); // we are making progress | ||
right = candidate; | ||
} else if offset >= elem.range.end() { | ||
// We are too far left (offset is further right). | ||
debug_assert!(candidate >= left); // we are making progress | ||
left = candidate + 1; | ||
} else { | ||
// This is it! | ||
return Ok(candidate); | ||
} | ||
} | ||
} | ||
|
||
/// Determines whether a given access on `range` overlaps with | ||
/// an existing allocation | ||
pub fn access_type(&self, range: AllocRange) -> AccessType { | ||
match self.find_offset(range.start) { | ||
Ok(pos) => { | ||
// Start of the range belongs to an existing object, now let's check the overlapping situation | ||
let elem = &self.v[pos]; | ||
// FIXME: derive Eq for AllocRange in rustc | ||
if elem.range.start == range.start && elem.range.size == range.size { | ||
// Happy case: perfectly overlapping access | ||
AccessType::PerfectlyOverlapping(pos) | ||
} else { | ||
// FIXME: add a last() method to AllocRange that returns the last inclusive offset (end() is exclusive) | ||
let end_pos = match self.find_offset(range.end() - Size::from_bytes(1)) { | ||
// If the end lands in an existing object, add one to get the exclusive position | ||
Ok(inclusive_pos) => inclusive_pos + 1, | ||
Err(exclusive_pos) => exclusive_pos, | ||
}; | ||
|
||
AccessType::ImperfectlyOverlapping(pos..end_pos) | ||
} | ||
} | ||
Err(pos) => { | ||
// Start of the range doesn't belong to an existing object | ||
match self.find_offset(range.end() - Size::from_bytes(1)) { | ||
// Neither does the end | ||
Err(end_pos) => | ||
if pos == end_pos { | ||
// There's nothing between the start and the end, so the range thing is empty | ||
AccessType::Empty(pos) | ||
} else { | ||
// Otherwise we have entirely covered an existing object | ||
AccessType::ImperfectlyOverlapping(pos..end_pos) | ||
}, | ||
// Otherwise at least part of it overlaps with something else | ||
Ok(end_pos) => AccessType::ImperfectlyOverlapping(pos..end_pos + 1), | ||
} | ||
} | ||
} | ||
} | ||
|
||
/// Inserts an object and its occupied range at given position | ||
// The Position can be calculated from AllocRange, but the only user of AllocationMap | ||
// always calls access_type before calling insert/index/index_mut, and we don't | ||
// want to repeat the binary search on each time, so we ask the caller to supply Position | ||
pub fn insert_at_pos(&mut self, pos: Position, range: AllocRange, data: T) { | ||
self.v.insert(pos, Elem { range, data }); | ||
// If we aren't the first element, then our start must be greater than the preivous element's end | ||
if pos > 0 { | ||
debug_assert!(self.v[pos - 1].range.end() <= range.start); | ||
} | ||
// If we aren't the last element, then our end must be smaller than next element's start | ||
if pos < self.v.len() - 1 { | ||
debug_assert!(range.end() <= self.v[pos + 1].range.start); | ||
} | ||
} | ||
|
||
pub fn remove_pos_range(&mut self, pos_range: Range<Position>) { | ||
self.v.drain(pos_range); | ||
} | ||
|
||
pub fn remove_from_pos(&mut self, pos: Position) { | ||
self.v.remove(pos); | ||
} | ||
} | ||
|
||
impl<T> Index<Position> for AllocationMap<T> { | ||
type Output = T; | ||
|
||
fn index(&self, pos: Position) -> &Self::Output { | ||
&self.v[pos].data | ||
} | ||
} | ||
|
||
impl<T> IndexMut<Position> for AllocationMap<T> { | ||
fn index_mut(&mut self, pos: Position) -> &mut Self::Output { | ||
&mut self.v[pos].data | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use rustc_const_eval::interpret::alloc_range; | ||
|
||
use super::*; | ||
|
||
#[test] | ||
fn empty_map() { | ||
// FIXME: make Size::from_bytes const | ||
let four = Size::from_bytes(4); | ||
let map = AllocationMap::<()>::new(); | ||
|
||
// Correctly tells where we should insert the first element (at position 0) | ||
assert_eq!(map.find_offset(Size::from_bytes(3)), Err(0)); | ||
|
||
// Correctly tells the access type along with the supposed position | ||
assert_eq!(map.access_type(alloc_range(Size::ZERO, four)), AccessType::Empty(0)); | ||
} | ||
|
||
#[test] | ||
#[should_panic] | ||
fn no_overlapping_inserts() { | ||
let four = Size::from_bytes(4); | ||
|
||
let mut map = AllocationMap::<&str>::new(); | ||
|
||
// |_|_|_|_|#|#|#|#|_|_|_|_|... | ||
// 0 1 2 3 4 5 6 7 8 9 a b c d | ||
map.insert_at_pos(0, alloc_range(four, four), "#"); | ||
// |_|_|_|_|#|#|#|#|_|_|_|_|... | ||
// 0 ^ ^ ^ ^ 5 6 7 8 9 a b c d | ||
map.insert_at_pos(0, alloc_range(Size::from_bytes(1), four), "@"); | ||
} | ||
|
||
#[test] | ||
fn boundaries() { | ||
let four = Size::from_bytes(4); | ||
|
||
let mut map = AllocationMap::<&str>::new(); | ||
|
||
// |#|#|#|#|_|_|... | ||
// 0 1 2 3 4 5 | ||
map.insert_at_pos(0, alloc_range(Size::ZERO, four), "#"); | ||
// |#|#|#|#|_|_|... | ||
// 0 1 2 3 ^ 5 | ||
assert_eq!(map.find_offset(four), Err(1)); | ||
// |#|#|#|#|_|_|_|_|_|... | ||
// 0 1 2 3 ^ ^ ^ ^ 8 | ||
assert_eq!(map.access_type(alloc_range(four, four)), AccessType::Empty(1)); | ||
|
||
let eight = Size::from_bytes(8); | ||
// |#|#|#|#|_|_|_|_|@|@|@|@|_|_|... | ||
// 0 1 2 3 4 5 6 7 8 9 a b c d | ||
map.insert_at_pos(1, alloc_range(eight, four), "@"); | ||
// |#|#|#|#|_|_|_|_|@|@|@|@|_|_|... | ||
// 0 1 2 3 4 5 6 ^ 8 9 a b c d | ||
assert_eq!(map.find_offset(Size::from_bytes(7)), Err(1)); | ||
// |#|#|#|#|_|_|_|_|@|@|@|@|_|_|... | ||
// 0 1 2 3 ^ ^ ^ ^ 8 9 a b c d | ||
assert_eq!(map.access_type(alloc_range(four, four)), AccessType::Empty(1)); | ||
} | ||
|
||
#[test] | ||
fn perfectly_overlapping() { | ||
let four = Size::from_bytes(4); | ||
|
||
let mut map = AllocationMap::<&str>::new(); | ||
|
||
// |#|#|#|#|_|_|... | ||
// 0 1 2 3 4 5 | ||
map.insert_at_pos(0, alloc_range(Size::ZERO, four), "#"); | ||
// |#|#|#|#|_|_|... | ||
// ^ ^ ^ ^ 4 5 | ||
assert_eq!(map.find_offset(Size::ZERO), Ok(0)); | ||
assert_eq!( | ||
map.access_type(alloc_range(Size::ZERO, four)), | ||
AccessType::PerfectlyOverlapping(0) | ||
); | ||
|
||
// |#|#|#|#|@|@|@|@|_|... | ||
// 0 1 2 3 4 5 6 7 8 | ||
map.insert_at_pos(1, alloc_range(four, four), "@"); | ||
// |#|#|#|#|@|@|@|@|_|... | ||
// 0 1 2 3 ^ ^ ^ ^ 8 | ||
assert_eq!(map.find_offset(four), Ok(1)); | ||
assert_eq!(map.access_type(alloc_range(four, four)), AccessType::PerfectlyOverlapping(1)); | ||
} | ||
|
||
#[test] | ||
fn straddling() { | ||
let four = Size::from_bytes(4); | ||
|
||
let mut map = AllocationMap::<&str>::new(); | ||
|
||
// |_|_|_|_|#|#|#|#|_|_|_|_|... | ||
// 0 1 2 3 4 5 6 7 8 9 a b c d | ||
map.insert_at_pos(0, alloc_range(four, four), "#"); | ||
// |_|_|_|_|#|#|#|#|_|_|_|_|... | ||
// 0 1 ^ ^ ^ ^ 6 7 8 9 a b c d | ||
assert_eq!( | ||
map.access_type(alloc_range(Size::from_bytes(2), four)), | ||
AccessType::ImperfectlyOverlapping(0..1) | ||
); | ||
// |_|_|_|_|#|#|#|#|_|_|_|_|... | ||
// 0 1 2 3 4 5 ^ ^ ^ ^ a b c d | ||
assert_eq!( | ||
map.access_type(alloc_range(Size::from_bytes(6), four)), | ||
AccessType::ImperfectlyOverlapping(0..1) | ||
); | ||
// |_|_|_|_|#|#|#|#|_|_|_|_|... | ||
// 0 1 ^ ^ ^ ^ ^ ^ ^ ^ a b c d | ||
assert_eq!( | ||
map.access_type(alloc_range(Size::from_bytes(2), Size::from_bytes(8))), | ||
AccessType::ImperfectlyOverlapping(0..1) | ||
); | ||
|
||
// |_|_|_|_|#|#|#|#|_|_|@|@|_|_|... | ||
// 0 1 2 3 4 5 6 7 8 9 a b c d | ||
map.insert_at_pos(1, alloc_range(Size::from_bytes(10), Size::from_bytes(2)), "@"); | ||
// |_|_|_|_|#|#|#|#|_|_|@|@|_|_|... | ||
// 0 1 2 3 4 5 ^ ^ ^ ^ ^ ^ ^ ^ | ||
assert_eq!( | ||
map.access_type(alloc_range(Size::from_bytes(6), Size::from_bytes(8))), | ||
AccessType::ImperfectlyOverlapping(0..2) | ||
); | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.