-
Notifications
You must be signed in to change notification settings - Fork 12.9k
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
Alignment of the bytes
of Allocation
to match align
parameter
#100467
Changes from 12 commits
0fe75b8
f37fe37
fde4235
e0e8e00
a7b7f19
f7a991b
e2ed272
51269b8
bca203e
0ddff36
a72a057
04f29dc
2fd7606
ab1a61f
b87f5ef
cade1c1
c31d404
e993680
17ac36b
c2e142b
99f6708
a12d111
f75649b
8db066f
f075a12
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ use std::hash; | |
use std::iter; | ||
use std::ops::{Deref, Range}; | ||
use std::ptr; | ||
use std::mem::MaybeUninit; | ||
|
||
use rustc_ast::Mutability; | ||
use rustc_data_structures::intern::Interned; | ||
|
@@ -207,8 +208,19 @@ impl<Prov> Allocation<Prov> { | |
align: Align, | ||
mutability: Mutability, | ||
) -> Self { | ||
let bytes = Box::<[u8]>::from(slice.into()); | ||
let size = Size::from_bytes(bytes.len()); | ||
let slice: Cow<'a, [u8]> = slice.into(); | ||
let size = Size::from_bytes(slice.len()); | ||
let align_usize: usize = align.bytes().try_into().unwrap(); | ||
let layout = std::alloc::Layout::from_size_align(slice.len(), align_usize).unwrap(); | ||
let bytes = unsafe { | ||
let buf = std::alloc::alloc(layout); | ||
let mut boxed = Box::<[MaybeUninit<u8>]>::from_raw(std::slice::from_raw_parts_mut(buf as *mut MaybeUninit<u8>, slice.len())); | ||
the8472 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
MaybeUninit::write_slice(&mut boxed, &slice); | ||
boxed.assume_init() | ||
}; | ||
|
||
assert!(bytes.as_ptr() as u64 % align.bytes() == 0); | ||
|
||
Self { | ||
bytes, | ||
relocations: Relocations::new(), | ||
|
@@ -228,22 +240,29 @@ impl<Prov> Allocation<Prov> { | |
/// | ||
/// If `panic_on_fail` is true, this will never return `Err`. | ||
pub fn uninit<'tcx>(size: Size, align: Align, panic_on_fail: bool) -> InterpResult<'tcx, Self> { | ||
let bytes = Box::<[u8]>::try_new_zeroed_slice(size.bytes_usize()).map_err(|_| { | ||
// This results in an error that can happen non-deterministically, since the memory | ||
// available to the compiler can change between runs. Normally queries are always | ||
// deterministic. However, we can be non-deterministic here because all uses of const | ||
// evaluation (including ConstProp!) will make compilation fail (via hard error | ||
// or ICE) upon encountering a `MemoryExhausted` error. | ||
if panic_on_fail { | ||
panic!("Allocation::uninit called with panic_on_fail had allocation failure") | ||
} | ||
ty::tls::with(|tcx| { | ||
tcx.sess.delay_span_bug(DUMMY_SP, "exhausted memory during interpretation") | ||
}); | ||
InterpError::ResourceExhaustion(ResourceExhaustionInfo::MemoryExhausted) | ||
})?; | ||
// SAFETY: the box was zero-allocated, which is a valid initial value for Box<[u8]> | ||
let bytes = unsafe { bytes.assume_init() }; | ||
let align_usize: usize = align.bytes().try_into().unwrap(); | ||
let layout = std::alloc::Layout::from_size_align(size.bytes_usize(), align_usize).unwrap(); | ||
let vec_align = unsafe { | ||
// https://doc.rust-lang.org/nightly/std/alloc/trait.GlobalAlloc.html#tymethod.alloc | ||
// std::alloc::alloc returns null to indicate an allocation failure: | ||
// "Returning a null pointer indicates that either memory is exhausted | ||
// or layout does not meet this allocator’s size or alignment constraints." | ||
let buf = std::alloc::alloc(layout); | ||
// Handle allocation failure. | ||
if buf.is_null() { | ||
if panic_on_fail { | ||
panic!("Allocation::uninit called with panic_on_fail had allocation failure") | ||
} | ||
ty::tls::with(|tcx| { | ||
tcx.sess.delay_span_bug(DUMMY_SP, "exhausted memory during interpretation") | ||
}); | ||
Err(InterpError::ResourceExhaustion(ResourceExhaustionInfo::MemoryExhausted))? | ||
} | ||
Vec::from_raw_parts(buf as *mut u8, size.bytes_usize(), size.bytes_usize()) | ||
}; | ||
|
||
let bytes = vec_align.into_boxed_slice(); | ||
assert!(bytes.as_ptr() as u64 % align.bytes() == 0); | ||
Ok(Allocation { | ||
bytes, | ||
relocations: Relocations::new(), | ||
|
@@ -265,7 +284,17 @@ impl Allocation { | |
mut adjust_ptr: impl FnMut(Pointer<AllocId>) -> Result<Pointer<Prov>, Err>, | ||
) -> Result<Allocation<Prov, Extra>, Err> { | ||
// Compute new pointer provenance, which also adjusts the bytes. | ||
let mut bytes = self.bytes; | ||
// Realign the pointer | ||
let align_usize: usize = self.align.bytes().try_into().unwrap(); | ||
let layout = std::alloc::Layout::from_size_align(self.bytes.len(), align_usize).unwrap(); | ||
let mut bytes = unsafe { | ||
let buf = std::alloc::alloc(layout); | ||
let mut boxed = Box::<[MaybeUninit<u8>]>::from_raw(std::slice::from_raw_parts_mut(buf as *mut MaybeUninit<u8>, self.bytes.len())); | ||
the8472 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
MaybeUninit::write_slice(&mut boxed, &self.bytes); | ||
boxed.assume_init() | ||
}; | ||
assert!(bytes.as_ptr() as usize % align_usize == 0); | ||
|
||
let mut new_relocations = Vec::with_capacity(self.relocations.0.len()); | ||
let ptr_size = cx.data_layout().pointer_size.bytes_usize(); | ||
let endian = cx.data_layout().endian; | ||
|
@@ -278,6 +307,8 @@ impl Allocation { | |
write_target_uint(endian, ptr_bytes, ptr_offset.bytes().into()).unwrap(); | ||
new_relocations.push((offset, ptr_prov)); | ||
} | ||
assert!(bytes.as_ptr() as u64 % self.align.bytes() == 0); | ||
|
||
// Create allocation. | ||
Ok(Allocation { | ||
bytes, | ||
|
@@ -321,6 +352,11 @@ impl<Prov, Extra> Allocation<Prov, Extra> { | |
|
||
/// Byte accessors. | ||
impl<Prov: Provenance, Extra> Allocation<Prov, Extra> { | ||
/// Get the pointer of the [u8] of bytes. | ||
pub fn get_bytes_addr(&self) -> Size { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is deliberately the host address, right? So it should have return type Also, to actually allow C code to use this address, we need to be a bit more careful with aliasing here. The pointer you are exposing here will be invalidated the next time someone mutates this memory, causing UB in Miri itself due to violating the Rust aliasing rules. Under my proposal where There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point -- thanks for the suggestion, that makes sense -- |
||
Size::from_bytes(self.bytes.as_ptr() as u64) | ||
} | ||
|
||
/// This is the entirely abstraction-violating way to just grab the raw bytes without | ||
/// caring about relocations. It just deduplicates some code between `read_scalar` | ||
/// and `get_bytes_internal`. | ||
|
+2 −0 | .gitignore | |
+38 −0 | Cargo.lock | |
+2 −0 | Cargo.toml | |
+8 −0 | README.md | |
+16 −0 | ffi_tests/Cargo.lock | |
+9 −0 | ffi_tests/Cargo.toml | |
+7 −0 | ffi_tests/build.rs | |
+ − | ffi_tests/src/libtestlib.so | |
+48 −0 | ffi_tests/src/main.rs | |
+34 −0 | ffi_tests/src/test.c | |
+1 −1 | rust-version | |
+13 −0 | src/bin/miri.rs | |
+5 −0 | src/eval.rs | |
+20 −23 | src/intptrcast.rs | |
+30 −4 | src/machine.rs | |
+363 −0 | src/shims/ffi_support.rs | |
+14 −3 | src/shims/foreign_items.rs | |
+1 −0 | src/shims/mod.rs | |
+20 −1 | tests/compiletest.rs | |
+10 −0 | tests/extern-so/libcode.version | |
+31 −0 | tests/extern-so/test.c | |
+13 −0 | tests/fail/extern-so/function_not_in_SO.rs | |
+14 −0 | tests/fail/extern-so/function_not_in_SO.stderr | |
+19 −0 | tests/fail/issue-miri-2432.rs | |
+15 −0 | tests/fail/issue-miri-2432.stderr | |
+54 −0 | tests/pass/extern-so/call_extern_c_fcts.rs | |
+2 −0 | tests/pass/extern-so/call_extern_c_fcts.stdout | |
+2 −2 | ui_test/src/lib.rs |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think my biggest concern is that this introduces unsafe code deep in the core of the interpreter engine. This is a rather significant cost and risk that the interpreter has to carry, even the compile-time interpreter that definitely does not want or need this.
At the least, we should find a way to factor this such that all the unsafe code lives in the Miri repository. Even that is still not great though, it is exactly the kind of global cost I was worried about.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see your point, but I don't think we can align the bytes of the allocation without
unsafe
code.If we want to factor this out into Miri, then one option could be to refactor
Allocation
to exposebytes
so that thebytes
alignment can be done on the Miri side. Although, this would be a bigger change toAllocation
.What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No we certainly don't want to expose
bytes
. But we can makeAllocation
generic over how bytes are allocated.You can add a new type parameter for that:
and then define a suitable trait
and add a bound
Bytes: AllocationBytes
whereveer needed.The trait needs to then contain all the operations you need. The implementation in rustc can use
Box<[u8]>
and be completely safe, and that way only Miri needs unsafe code.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah that's smart -- I had tried earlier to make
Allocation
generic over the allocator for bytes which didn't work. But this makes sense!