-
Notifications
You must be signed in to change notification settings - Fork 57
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
vulkan: Mark fn mapped_(mut_)slice()
as unsafe
#139
base: main
Are you sure you want to change the base?
Changes from all commits
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 |
---|---|---|
|
@@ -92,18 +92,24 @@ impl Allocation { | |
|
||
/// Returns a valid mapped slice if the memory is host visible, otherwise it will return None. | ||
/// The slice already references the exact memory region of the allocation, so no offset needs to be applied. | ||
pub fn mapped_slice(&self) -> Option<&[u8]> { | ||
self.mapped_ptr().map(|ptr| unsafe { | ||
std::slice::from_raw_parts(ptr.cast().as_ptr(), self.size as usize) | ||
}) | ||
/// | ||
/// # Safety | ||
/// Only to be called when the memory is known to be _fully_ initialized. | ||
pub unsafe fn mapped_slice(&self) -> Option<&[u8]> { | ||
self.mapped_ptr() | ||
.map(|ptr| std::slice::from_raw_parts(ptr.cast().as_ptr(), self.size as usize)) | ||
} | ||
|
||
/// Returns a valid mapped mutable slice if the memory is host visible, otherwise it will return None. | ||
/// The slice already references the exact memory region of the allocation, so no offset needs to be applied. | ||
pub fn mapped_slice_mut(&mut self) -> Option<&mut [u8]> { | ||
self.mapped_ptr().map(|ptr| unsafe { | ||
std::slice::from_raw_parts_mut(ptr.cast().as_ptr(), self.size as usize) | ||
}) | ||
/// | ||
/// # Safety | ||
/// Only to be called when the memory is known to be _fully_ initialized. Use [`std::ptr::copy()`] or | ||
/// [`std::ptr::copy_nonoverlapping()`] on [`mapped_ptr()`][Self::mapped_ptr()] to initialize this buffer | ||
/// from the CPU instead. | ||
pub unsafe fn mapped_mut_slice(&mut self) -> Option<&mut [u8]> { | ||
Comment on lines
+107
to
+110
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. Instead of always requiring pointers for CPU-side initialization, we can also provide a safe 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. 👍 yeah, though if we wanted it to be actually safe it would still need to be
Comment on lines
+107
to
+110
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. Note that I sneaked a little |
||
self.mapped_ptr() | ||
.map(|ptr| std::slice::from_raw_parts_mut(ptr.cast().as_ptr(), self.size as usize)) | ||
} | ||
|
||
pub fn is_null(&self) -> bool { | ||
|
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.
As a user of
gpu_allocator
, I can only get anAllocation
usingAllocator::allocate
. The memory is at that point fully initialized because any bit pattern is a valid byte. Freeing likewise takes my instance, preventing unsafe usage.Does this need to be
unsafe
?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.
According to https://crates.io/crates/presser / https://www.ralfj.de/blog/2019/07/14/uninit.html, and per the description/trigger of this PR, that is not the case.
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.
But this does not logically follow. Memory initialization is not about whether some bit pattern is a valid byte or not, it's a specific meta-state change about a piece of memory that happens the first time data is written into it after being marked uninit. Memory returned from
Allocator::allocate
is not initialized. uninit memory is not just "any bit pattern", it is a completely separate "meta-state" about a piece of memory that means it could not just be any bit pattern but it could very well not exist at all, and thus the compiler is free to make optimizations, reorder your code, etc. based on this assumption. reading an uninit byte is always ub, even if your code is valid for any bit pattern -- see readme here https://github.com/EmbarkStudios/presserThere 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, the unsafety is reading possibly uninitialized data.
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.
@attackgoat more concretely, will a
MaybeUninit<T>
API work for your use-case?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 see the danger in using data which has "random" values, especially when transmuting to
T
; but I'm not entirely convinced that this is unsafe in the context. It's more unwise. The GPU is very likely to do things to our memory as we hold it; so additional safeguards don't seem fool proof.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.
Whether the GPU is doing things or not is a wholly separate issue, though it does make things another level of unsafe. In that regard, most of the rust graphics libraries have taken the stance that the GPU overlapping access with CPU access doesn't constitute marking a function as unsafe because validating that at compile time is essentially impossible and classifying it as unsafe would mean pretty much every gpu api ever would be unsafe which just doesn't seem like it adds any value. So instead we reserve
unsafe
to demarcate when a function is extra unsafe and should be looked at even more carefully than just for synchronization with GPU access. Once again though, uninit =/= "random", in fact, if the GPU is writing data with uninit/padding and we are reading it back as&[u8]
that's actually unsound whereas reading it back as&[T]
which has the same layout as what the GPU wrote is completely sound and valid.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 prefer a strong
# Safety
warning that states the danger involved, and at least anunsafe
marker. I don't know if I'm knowledgeable enough to know aboutMaybeUnint
exactly.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.
The goal is to have an api that makes things almost-as-ergonomic (or even more ergonomic in many cases) while avoiding the most common safety pitfalls and calling out more explicitly the ways things can go wrong thru stronger safety warnings like you say :)
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.
Yes and I'll add that I like the idea of #138 because Screen 13 has the same old pattern using
bytemuck
and it would be nice to offer users a safer way of doing this.