Skip to content

Remove miri hack #1

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 1 commit into from
Feb 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -146,11 +146,12 @@ jobs:
component: miri
- run: cargo miri test --workspace -- --test-threads=1
env:
MIRIFLAGS: -Zmiri-symbolic-alignment-check -Zmiri-tag-raw-pointers -Zmiri-disable-isolation
# tests/padding.rs intentional contains tests for cases that are incompatible with -Zmiri-check-number-validity.
- run: cargo miri test --test test -- --test-threads=1
MIRIFLAGS: -Zmiri-check-number-validity -Zmiri-tag-raw-pointers -Zmiri-disable-isolation
- run: cargo miri test --workspace -- --test-threads=1
env:
MIRIFLAGS: -Zmiri-check-number-validity -Zmiri-symbolic-alignment-check -Zmiri-tag-raw-pointers -Zmiri-disable-isolation
# -Zmiri-symbolic-alignment-check is incompatible with the code that does manual integer arithmetic to ensure alignment.
RUSTFLAGS: ${{ env.RUSTFLAGS }} --cfg atomic_memcpy_symbolic_alignment_check_compat

san:
strategy:
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Note: In this file, do not use the hard wrap in the middle of a sentence for com

## [Unreleased]

- Fix "unsupported operation: unable to turn pointer into raw bytes" Miri error. ([#1](https://github.com/taiki-e/atomic-memcpy/pull/1))

## [0.1.0] - 2022-02-12

Initial release
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@ See [P1478R1][p1478r1] for more.
- If the alignment of the type being copied is the same as the pointer width, `atomic_load` is possible to produce an assembly roughly equivalent to the case of using volatile read + atomic fence on many platforms. (See [`tests/asm-test/asm`][asm-test] directory for more).
- If the alignment of the type being copied is smaller than the pointer width, there will be some performance degradation. However, it is implemented in such a way that it does not cause extreme performance degradation at least on x86_64. (See [the implementation comments of `atomic_load`][implementation] for more.) It is possible that there is still room for improvement, especially on non-x86_64 platforms.
- Optimization for the case where the alignment of the type being copied is larger than the pointer width has not yet been fully investigated. It is possible that there is still room for improvement, especially on 32-bit platforms where `AtomicU64` is available.
- If the type being copied contains uninitialized bytes (e.g., padding), it is incompatible with `-Zmiri-check-number-validity`. This will probably not be resolved until something like `AtomicMaybeUninit` is supported.
- If the type being copied contains uninitialized bytes (e.g., padding), it is incompatible with `-Zmiri-check-number-validity`. This will probably not be resolved until something like `AtomicMaybeUninit` is supported. **Note**: Due to [Miri cannot track uninitialized bytes on a per byte basis for partially initialized scalars][rust-lang/rust#69488], Miri may report this case as an access to an uninitialized byte, regardless of whether the uninitialized byte is actually accessed or not.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just noticed this by coincidence, and I am trying to understand what happens here.

If I understand correctly, this code effectively performs a copy at a type like u64, and due to rust-lang/rust#69488, that "inflates" uninit which causes problems. However, if that is the case, it will cause problems even without -Zmiri-check-number-validity, the problems are just slightly delayed: if x is uninit, x = 1 will fail in Miri even without that flag.

Moreover, arguably, this is not just a Miri limitation, it actually accurately reflects the reference. I have ideas for fixing rust-lang/rust#69488; they will not fix the problem described here.

Finally, if we start emitting noundef flags for LLVM on our integer types (which the reference says we can), then the approach in this crate will be problematic even if we ignore Rust UB and consider only LLVM UB.

So, I agree that AtomicMaybeUninit is the solution -- but until we have that, the problem isn't just a limitation in Miri, it is a limitation in Rust generally.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I agree that the current readme is incorrect. (Filed #5 to fix readme)


[p1478r1]: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1478r1.html
[implementation]: https://github.com/taiki-e/atomic-memcpy/blob/279d7041e48fae0943a50102ebab97e7ed3977ae/src/lib.rs#L359-L403
[asm-test]: tests/asm-test/asm
[rust-lang/rust#69488]: https://github.com/rust-lang/rust/issues/69488

## License

Expand Down
56 changes: 12 additions & 44 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,13 +213,17 @@ mod imp {
sync::atomic::{AtomicU16, AtomicUsize, Ordering},
};

#[cfg(not(atomic_memcpy_symbolic_alignment_check_compat))]
#[cfg(target_pointer_width = "32")]
type Half = u16;
#[cfg(not(atomic_memcpy_symbolic_alignment_check_compat))]
#[cfg(target_pointer_width = "32")]
type AtomicHalf = AtomicU16;

#[cfg(not(atomic_memcpy_symbolic_alignment_check_compat))]
#[cfg(target_pointer_width = "64")]
type Half = u32;
#[cfg(not(atomic_memcpy_symbolic_alignment_check_compat))]
#[cfg(target_pointer_width = "64")]
type AtomicHalf = AtomicU32;

Expand Down Expand Up @@ -327,9 +331,10 @@ mod imp {
}
}

#[cfg(not(atomic_memcpy_symbolic_alignment_check_compat))]
#[cfg(not(target_pointer_width = "16"))]
#[cfg_attr(feature = "inline-always", inline(always))]
#[cfg_attr(not(feature = "inline-always"), inline)]
#[cfg(not(target_pointer_width = "16"))]
pub(super) unsafe fn atomic_load_half(&mut self) {
use super::{AtomicHalf, Half};
debug_assert!(self.remaining() >= mem::size_of::<Half>());
Expand Down Expand Up @@ -423,45 +428,15 @@ mod imp {
return result;
}

// HACK: Miri cannot track uninitialized bytes on a per byte basis for
// partially initialized scalars: https://github.com/rust-lang/rust/issues/69488
//
// This hack allows miri to properly track the use of uninitialized
// bytes. See also tests/uninit.rs that is a test to check if
// valgrind/sanitizer/miri can properly detect the use of uninitialized
// bytes.
//
// Note: With or without this hack, atomic load/store of integers
// containing uninitialized bytes is technically an undefined behavior.
// The only purpose of this hack is to make sure that Miri errors for
// atomic load/store of integers containing uninitialized bytes
// (which is probably not a problem and uncharted territory at best [1] [2] [3],
// and can be detected by `-Zmiri-check-number-validity` [4]),
// do not mask Miri errors for the use of uninitialized bytes (which is definitely a problem).
// See also tests/padding.rs.
//
// [1] https://github.com/crossbeam-rs/crossbeam/issues/315
// [2] https://github.com/rust-lang/unsafe-code-guidelines/issues/158
// [3] https://github.com/rust-lang/unsafe-code-guidelines/issues/71
// [4] https://github.com/rust-lang/miri/pull/1904
//
// rust-lang/rust#69488 affects only CTFE(compile-time function evaluation)/Miri
// and atomic operations cannot be called in const context, so our code
// is only affected in the case of cfg(miri).
if cfg!(miri) {
let mut state = load::LoadState::new(result.as_mut_ptr(), src);
state.atomic_load_u8(state.remaining());
debug_assert_eq!(state.remaining(), 0);
return result;
}

// Branch 1: If the alignment of `T` is less than usize, but `T` can be read as
// at least one or more usize, compute the align offset and read it
// like `(&[AtomicU8], &[AtomicUsize], &[AtomicU8])`.
if mem::align_of::<T>() < mem::align_of::<AtomicUsize>()
&& mem::size_of::<T>() >= mem::size_of::<usize>() * 4
{
let mut state = load::LoadState::new(result.as_mut_ptr(), src);
// -Zmiri-symbolic-alignment-check is incompatible with the code that does manual integer arithmetic to ensure alignment.
#[cfg(not(atomic_memcpy_symbolic_alignment_check_compat))]
#[cfg(not(target_pointer_width = "16"))]
{
// Since the caller guarantees that the pointer is properly aligned,
Expand Down Expand Up @@ -702,9 +677,10 @@ mod imp {
}
}

#[cfg(not(atomic_memcpy_symbolic_alignment_check_compat))]
#[cfg(not(target_pointer_width = "16"))]
#[cfg_attr(feature = "inline-always", inline(always))]
#[cfg_attr(not(feature = "inline-always"), inline)]
#[cfg(not(target_pointer_width = "16"))]
pub(super) unsafe fn atomic_store_half(&mut self) {
use super::{AtomicHalf, Half};
debug_assert!(self.remaining() >= mem::size_of::<Half>());
Expand Down Expand Up @@ -769,25 +745,17 @@ mod imp {
return;
}

// HACK: See the `atomic_load` function for the detailed comment.
if cfg!(miri) {
let mut state = store::StoreState::new(dst, &*val);
state.atomic_store_u8(state.remaining());
debug_assert_eq!(state.remaining(), 0);
mem::forget(guard);
return;
}

// Branch 1: If the alignment of `T` is less than usize, but `T` can be write as
// at least one or more usize, compute the align offset and write it
// like `(&[AtomicU8], &[AtomicUsize], &[AtomicU8])`.
if mem::align_of::<T>() < mem::align_of::<AtomicUsize>()
&& mem::size_of::<T>() >= mem::size_of::<usize>() * 4
{
let mut state = store::StoreState::new(dst, &*val);
// See the `atomic_load` function for the detailed comment.
#[cfg(not(atomic_memcpy_symbolic_alignment_check_compat))]
#[cfg(not(target_pointer_width = "16"))]
{
// See the `atomic_load` function for the detailed comment.
if mem::align_of::<T>() >= mem::align_of::<Half>() {
if dst as usize % mem::size_of::<usize>() == 0 {
// SAFETY:
Expand Down
20 changes: 14 additions & 6 deletions tests/padding.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
// https://github.com/rust-lang/unsafe-code-guidelines/issues/71
// https://github.com/rust-lang/miri/pull/1904
//
// With miri:
// MIRIFLAGS='-Zmiri-check-number-validity' cargo miri test --test padding -- --test-threads=1

use std::{cell::UnsafeCell, mem, sync::atomic::Ordering};

use atomic_memcpy::{atomic_load, atomic_store};

#[test]
fn enum_padding() {
// Miri cannot track uninitialized bytes on a per byte basis for partially
// initialized scalars: https://github.com/rust-lang/rust/issues/69488
// See also https://github.com/crossbeam-rs/crossbeam/issues/748#issuecomment-1022432401
if cfg!(miri) {
return;
}

#[allow(dead_code)]
#[repr(align(8))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
Expand Down Expand Up @@ -37,6 +38,13 @@ fn enum_padding() {

#[test]
fn union_padding() {
// Miri cannot track uninitialized bytes on a per byte basis for partially
// initialized scalars: https://github.com/rust-lang/rust/issues/69488
// See also https://github.com/crossbeam-rs/crossbeam/issues/748#issuecomment-1022432401
if cfg!(miri) {
return;
}

#[allow(dead_code)]
#[repr(C, align(8))]
#[derive(Clone, Copy)]
Expand Down
11 changes: 11 additions & 0 deletions tests/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,17 @@ fn basic_unit() {
}
}

#[test]
fn basic_ptr() {
unsafe {
let x = UnsafeCell::<*mut u8>::new(ptr::null_mut());
assert!(atomic_load(x.get(), Ordering::Relaxed).assume_init().is_null());
let mut v = 0u8;
atomic_store(x.get(), &mut v, Ordering::Relaxed);
assert!(!atomic_load(x.get(), Ordering::Relaxed).assume_init().is_null());
}
}

#[cfg(not(feature = "no-panic"))]
#[track_caller]
fn assert_panic<T: std::fmt::Debug>(f: impl FnOnce() -> T) -> std::string::String {
Expand Down