-
Notifications
You must be signed in to change notification settings - Fork 220
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
Add slice access #634
Add slice access #634
Conversation
I think those |
The proposed changeset allows the user to call an |
Well if the whole |
I don't understand why this is wrong: impl<T> UnsafeSliceAccess<T> for VecStorage<T> {
unsafe fn unsafe_slice(&self) -> &[T] {
self.0.as_slice()
}
} As I understand it, constructing a slice isn't UB regardless of its pointer or length. Accessing such a slice may or may not be UB, but unsafe code commonly involves nonlocal concerns. Even in this one line function,
|
If you have I might be bit too paranoid here, but unsafe is quite hard to reason about. https://www.reddit.com/r/rust/comments/95vxdy/understanding_ub_with_stdmemuninitialized/ |
Right; accessing an uninitialized We agree that blindly accessing Moreover, this isn't |
I wonder: is this whole discussion because |
I pushed a separate branch with 9c2d800 showing what If this is the right direction but we don't want to increase the version requirement right now, I would be happy to keep this new unified |
e4e2983
to
2010379
Compare
I rearranged my changes and force-pushed the branch for this PR. The first commit adds The second commit reworks |
Note that for f32 every bit pattern isn't allowed: Signaling NaN is a UB. I am not sure if slice is just syntactic sugar from compilers POV. But yeah probably constructing slice isn't UB. I think having |
👍 |
Related: #646 |
I note in #646 (comment) why I now believe that slices must always refer to initialized elements in current Rust, contrary to my earlier comments here. |
This PR looks otherwise good, but |
Ah, the |
This vector contains uninitialized data, so it should use MaybeUninit.
cf24a74
to
d79e603
Compare
bors r+ |
634: Add slice access r=WaDelma a=willglynn This PR adds slice access to certain storages. Slices allow the user to bolt on an accelerator framework like OpenCL (#553) for high throughput component processing. My thoughts behind this particular design are #553 (comment) and #553 (comment). ## Checklist * [x] I've added tests for all code changes and additions (where applicable) * [x] I've added a demonstration of the new feature to one or more examples * [x] I've updated the book to reflect my changes * [x] Usage of new public items is shown in the API docs ## API changes Non-breaking, strictly additive: * Add `DenseVecStorage<T>::as_slice() -> &[T]` and `as_mut_slice() -> &mut [T]`. * Add `VecStorage::unsafe_slice() -> &[T]` and `unsafe_mut_slice() -> &mut [T]`. * Add `DefaultVecStorage<T>`, which works like `VecStorage<T>`, requires `T: Default`, and provides `as_slice() -> &[T]` and `as_mut_slice() -> &mut [T]`. * Add `as` or `unsafe` `[_mut]_slice` directly on `Storage` as appropriate. Co-authored-by: Will Glynn <[email protected]>
Build succeeded |
53: Add slice access r=azriel91 a=willglynn I added slice access to `specs` (amethyst/specs#634) to support OpenCL (amethyst/specs#553). Usage looks like: ```rust impl<'a> System<'a> for SomeSystem { type SystemData = (WriteStorage<'a, Something>, ); fn run(&mut self, (mut some_storage, ): Self::SystemData) { // 1. get a mutable slice of the component storage let slice = some_storage.unprotected_storage_mut().as_mut_slice(); // 2. process the slice with OpenCL or whatever // 3. there is no step 3 } } ``` Components can be processed using an OpenCL kernel: ```c struct Something { float foo; float bar; float baz; }; __kernel void process_something(__global struct Something* slice) { __global struct Something* something = &slice[get_global_id(0)]; // do stuff with this data } ``` There's a catch when using slices from `DefaultVecStorage<T>` or `VecStorage<T>`. The slices are sparse, which means the kernel will either uselessly process `T::default()` or perform unchecked access into a `MaybeUninit<T>` for any entity which does not have component `T`. Exposing a slice from `hibitset::BitSet` would allow an OpenCL kernel check the storage mask itself: ```c struct Something { float foo; float bar; float baz; }; #define BITS_PER_SIZE_T (sizeof(size_t) * 8) __kernel void process_something(__global size_t* bitset_layer0, __global struct Something* slice) { // check that this index contains data size_t mask_index = get_global_id(0) / BITS_PER_SIZE_T; size_t shift = get_global_id(0) % BITS_PER_SIZE_T; if ((bitset_layer0[mask_index] >> shift) & 1 == 0) { return; } __global struct Something* something = &slice[get_global_id(0)]; // do stuff with this data } ``` This PR adds `layer{0,1,2}_as_slice(&self) -> &[usize]` to `BitSet`, as well as trivial constants which make correct usage more obvious. Layer 3 is specified to be a single `usize` so I didn't see any benefit to exposing it as a slice. Co-authored-by: Will Glynn <[email protected]>
This PR adds slice access to certain storages. Slices allow the user to bolt on an accelerator framework like OpenCL (#553) for high throughput component processing. My thoughts behind this particular design are #553 (comment) and #553 (comment).
Checklist
API changes
Non-breaking, strictly additive:
DenseVecStorage<T>::as_slice() -> &[T]
andas_mut_slice() -> &mut [T]
.VecStorage::unsafe_slice() -> &[T]
andunsafe_mut_slice() -> &mut [T]
.DefaultVecStorage<T>
, which works likeVecStorage<T>
, requiresT: Default
, and providesas_slice() -> &[T]
andas_mut_slice() -> &mut [T]
.as
orunsafe
[_mut]_slice
directly onStorage
as appropriate.