presser
can help you when copying data into raw buffers. One primary use-case is copying data into
graphics-api-allocated buffers which will then be accessed by the GPU. Common methods for doing this
right now in Rust can often invoke UB in subtle and hard-to-see ways. For example, viewing an allocated
but uninitialized buffer as an &mut [u8]
is instantly undefined behavior*, and transmute
ing even a
T: Copy
type which has any padding bytes in its layout as a &[u8]
to be the source of a copy is
also instantly undefined behavior, in both cases because it is invalid to create a reference to an invalid
value (and uninitialized memory is an invalid u8
), even if your code never actually accesses that memory.
This immediately makes what seems like the most straightforward way to copy data into buffers unsound 😬
* If you're currently thinking to yourself "bah! what's the issue? surely an uninit u8 is just any random bit pattern
and that's fine we don't care," check out this blog post by
@RalfJung, one of the people leading the effort to better define Rust's memory and execution model. As is explored
in that blog post, anuninitpiece of memory is not simplyan arbitrary bit pattern, it is a wholly separate
state about a piece of memory, outside of its value, which lets the compiler perform optimizations that reorder,
delete, and otherwise change the actual execution flow of your program in ways that cannot be described simply
by "the value could havesomepossible bit pattern". LLVM and Clang are changing themselves to require special
noundef
attribute to perform many important optimizations that are otherwise unsound. For a concrete example
of the sorts of problems this can cause, see this issue @scottmcm hit.
```rust
struct MyDataStruct { a: u8, b: u32, }
let my_data = MyDataStruct { a: 0, b: 42 };
// 🚨 MyDataStruct contains 3 padding bytes after a
, which are
// uninit, therefore getting a slice that includes them is UB!
let mydatabytes: &[u8] = transmute(&my_data);
// allocate an uninit buffer of some size let mybuffer: MyBufferType = someapi.allocbuffersize(2048);
// 🚨 this is UB for the same reason, these bytes are uninit! let bufferasbytes: &mut [u8] = slice::fromrawparts(mybuffer.ptr(), mybuffer.size());
// 🚨 this is UB because not only are both slices invalid, // this is not ensuring proper alignment! bufferasbytes.copyfromslice(mydatabytes); ```
presser
helps with this by allowing you to view raw allocated memory of some size as a "Slab
" of memory and then
provides safe, valid ways to copy data into that memory:
```rust
struct MyDataStruct { a: u8, b: u32, }
let my_data = MyDataStruct { a: 0, b: 42 };
// allocate an uninit buffer of some size let mybuffer: MyBufferType = someapi.allocbuffersize(2048);
// use RawAllocation
helper to allow access to a presser Slab
.
// alternatively, you could implement the Slab
on your buffer type directly if that
// type is owned by your code!
let rawallocation = presser::RawAllocation::fromrawparts(mybuffer.ptr(), my_buffer.size());
// here we assert that we have exclusive access to the data in the buffer, and get the actual
// Slab
to use to copy into.
let slab = unsafe { rawallocation.borrowas_slab(); }
// now we may safely copy my_data
into my_buffer
, starting at a minimum offset of 0 into the buffer
let copyrecord = presser::copytooffset(&mydata, &mut slab, 0)?;
// note that due to alignment requirements of my_data
, the actual start of the bytes of
// my_data
may be placed at a different offset than requested. so, we check the returned
// CopyRecord
to check the actual start offset of the copied data.
let actualstartoffset = copyrecord.copystart_offset;
```
Note that actually accessing the copied data is a completely separate issue which presser
does not
(as of now) concern itself with. BE CAREFUL!
See more in the git main
docs
or the released version docs.
We welcome community contributions to this project.
Please read our Contributor Guide for more information on how to get started. Please also read our Contributor Terms before you make any contributions.
Any contribution intentionally submitted for inclusion in an Embark Studios project, shall comply with the Rust standard licensing model (MIT OR Apache 2.0) and therefore be dual licensed as described below, without any additional terms or conditions:
This contribution is dual licensed under EITHER OF
at your option.
For clarity, "your" refers to Embark or any other licensee/user of the contribution.