Skip to content

Commit 6a179d1

Browse files
committed
reduce unsafe code, and remove alignment requirements
1 parent 4bbaa0f commit 6a179d1

File tree

2 files changed

+70
-101
lines changed

2 files changed

+70
-101
lines changed

crates/bevy_ecs/src/system/commands.rs

+11-18
Original file line numberDiff line numberDiff line change
@@ -14,43 +14,36 @@ pub trait Command: Send + Sync + 'static {
1414

1515
/// # Safety
1616
///
17-
/// This function is only used when the provided `world` is a `&mut World` and
18-
/// is only ever called once, so it's safe to consume `command`.
19-
unsafe fn write_command_on_mut_world<T: Command>(command: *mut u8, world: *mut u8) {
20-
let world = &mut *world.cast::<World>();
21-
let command = command.cast::<T>().read();
17+
/// This function is only every associated when the `command` bytes is the associated
18+
/// [`Commands`] `T` type. Also this only read the data via `read_unaligned` to unaligned
19+
/// accesses are safe.
20+
unsafe fn write_command_on_mut_world<T: Command>(command: *mut u8, world: &mut World) {
21+
let command = command.cast::<T>().read_unaligned();
2222
command.write(world);
2323
}
2424

2525
/// A queue of [`Command`]s.
2626
#[derive(Default)]
2727
pub struct CommandQueue {
28-
commands: AnyStack,
28+
commands: AnyStack<World>,
2929
}
3030

3131
impl CommandQueue {
3232
/// Execute the queued [`Command`]s in the world.
3333
/// This clears the queue.
3434
pub fn apply(&mut self, world: &mut World) {
3535
world.flush();
36-
// SAFE:
37-
// `apply`: the provided function `write_command_on_mut_world` safely
38-
// handle the provided [`World`], drops the associate `Command`, and
39-
// clears the inner [`AnyStack`].
40-
//
41-
// `clear`: is safely used because the call to `apply` above
42-
// ensures each added command is dropped.
43-
unsafe {
44-
self.commands.apply(world as *mut World as *mut u8);
45-
self.commands.clear();
46-
}
36+
// Note: the provided function `write_command_on_mut_world` safely
37+
// drops the associated `Command`.
38+
self.commands.consume(world);
4739
}
4840

4941
/// Push a [`Command`] onto the queue.
5042
#[inline]
5143
pub fn push<T: Command>(&mut self, command: T) {
5244
// SAFE: `write_command_on_mut_world` safely casts `command` back to its original type
53-
// and safely casts the `user_data` back to a `World`.
45+
// and only access the command via `read_unaligned`.
46+
// Also it correctly `drops` the command.
5447
unsafe {
5548
self.commands.push(command, write_command_on_mut_world::<T>);
5649
}

crates/bevy_utils/src/anystack.rs

+59-83
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
struct AnyStackMeta {
1+
struct AnyStackMeta<T> {
22
offset: usize,
3-
func: unsafe fn(value: *mut u8, user_data: *mut u8),
3+
func: unsafe fn(value: *mut u8, user_data: &mut T),
44
}
55

6-
pub struct AnyStack {
6+
pub struct AnyStack<T> {
77
bytes: Vec<u8>,
8-
metas: Vec<AnyStackMeta>,
8+
metas: Vec<AnyStackMeta<T>>,
99
}
1010

11-
impl Default for AnyStack {
11+
impl<T> Default for AnyStack<T> {
1212
fn default() -> Self {
1313
Self {
1414
bytes: vec![],
@@ -17,97 +17,89 @@ impl Default for AnyStack {
1717
}
1818
}
1919

20-
unsafe impl Send for AnyStack {}
21-
unsafe impl Sync for AnyStack {}
20+
// SAFE: All values pushed onto the stack are required to be [`Send`]
21+
unsafe impl<T> Send for AnyStack<T> {}
2222

23-
impl AnyStack {
23+
// SAFE: All values pushed onto the stack are required to be [`Sync`]
24+
unsafe impl<T> Sync for AnyStack<T> {}
25+
26+
impl<T> AnyStack<T> {
27+
////Constructs a new, empty `AnyStack<T>`.
28+
/// The stack will not allocate until elements are pushed onto it.
2429
#[inline]
2530
pub fn new() -> Self {
2631
Self::default()
2732
}
2833

34+
/// Returns the number of elements in the [`AnyStack`], also referred to as its ‘length’.
2935
#[inline]
3036
pub fn len(&self) -> usize {
3137
self.metas.len()
3238
}
3339

40+
/// Returns true if the [`AnyStack`] contains no elements.
3441
#[inline]
3542
pub fn is_empty(&self) -> bool {
3643
self.len() == 0
3744
}
3845

39-
/// Clears in internal bytes and metas storage.
40-
///
41-
/// # Safety
42-
///
43-
/// This does not [`drop`] the pushed values.
44-
/// The pushed values must be dropped via [`AnyStack::apply`] and
45-
/// the provided `func` before calling this function.
46-
#[inline]
47-
pub unsafe fn clear(&mut self) {
48-
self.bytes.clear();
49-
self.metas.clear();
50-
}
51-
5246
/// Push a new `value` onto the stack.
5347
///
5448
/// # Safety
5549
///
56-
/// `func` must safely handle the provided `value` bytes and `user_data` bytes.
50+
/// `func` must safely handle the provided `value` bytes.
51+
/// _NOTE_: the bytes provided to the given function may **NOT** be aligned. If you wish
52+
/// to read the value, consider using [`std::ptr::read_unalinged`].
53+
///
5754
/// [`AnyStack`] does not drop the contained member.
58-
/// The user should manually call [`AnyStack::apply`] and in the implementation
59-
/// of the provided `func` function from [`AnyStack::push`], the element should
60-
/// be dropped.
61-
pub unsafe fn push<U>(
62-
&mut self,
63-
value: U,
64-
func: unsafe fn(value: *mut u8, user_data: *mut u8),
65-
) {
66-
let align = std::mem::align_of::<U>();
55+
/// The user should manually call [`AnyStack::consume`] and in the implementation
56+
/// of the provided `func` the element should be dropped.
57+
#[inline]
58+
pub unsafe fn push<U>(&mut self, value: U, func: unsafe fn(value: *mut u8, user_data: &mut T))
59+
where
60+
U: Send + Sync,
61+
{
6762
let size = std::mem::size_of::<U>();
6863

69-
if self.is_empty() {
70-
self.bytes.reserve(size);
71-
}
72-
7364
let old_len = self.bytes.len();
7465

75-
let aligned_offset = loop {
76-
let aligned_offset = self.bytes.as_ptr().add(old_len).align_offset(align);
77-
78-
if old_len + aligned_offset + size > self.bytes.capacity() {
79-
self.bytes.reserve(aligned_offset + size);
80-
} else {
81-
break aligned_offset;
82-
}
83-
};
84-
85-
let offset = old_len + aligned_offset;
86-
let total_bytes = size + aligned_offset;
87-
self.bytes.set_len(old_len + total_bytes);
88-
89-
self.metas.push(AnyStackMeta { offset, func });
90-
66+
self.bytes.reserve(size);
9167
std::ptr::copy_nonoverlapping(
9268
&value as *const U as *const u8,
93-
self.bytes.as_mut_ptr().add(offset),
69+
self.bytes.as_mut_ptr().add(old_len),
9470
size,
9571
);
72+
self.bytes.set_len(old_len + size);
73+
74+
self.metas.push(AnyStackMeta {
75+
offset: old_len,
76+
func,
77+
});
9678

9779
std::mem::forget(value);
9880
}
9981

100-
/// Call each user `func` for each inserted value with `user_data`.
82+
/// Call each user `func` for each inserted value with `user_data`
83+
/// and then clears the internal bytes/metas vectors.
10184
///
102-
/// # Safety
85+
/// # Warning
10386
///
104-
/// It is up to the user to safely handle `user_data` in each of the initially
105-
/// provided `func` functions in [`AnyStack::push`].
106-
pub unsafe fn apply(&mut self, user_data: *mut u8) {
87+
/// This does not [`drop`] the pushed values.
88+
/// If the value should be dropped, the initially provided `func`
89+
/// should ensure any necessary cleanup occurs.
90+
pub fn consume(&mut self, user_data: &mut T) {
10791
let byte_ptr = self.bytes.as_mut_ptr();
10892
for meta in self.metas.iter() {
109-
(meta.func)(byte_ptr.add(meta.offset), user_data);
93+
// SAFE: The safety guarantees are promised to be held by the caller
94+
// from [`AnyStack::push`].
95+
// Also each value has it's offset correctly stored in it's assocaited meta.
96+
unsafe {
97+
(meta.func)(byte_ptr.add(meta.offset), user_data);
98+
}
11099
}
100+
101+
self.bytes.clear();
102+
self.metas.clear();
111103
}
112104
}
113105

@@ -136,18 +128,16 @@ mod test {
136128

137129
/// # Safety
138130
///
139-
/// This function does not touch the `user_data` provided,
140-
/// and this is only used when the value is a `DropCheck`.
141-
/// Lastly, this drops the `DropCheck` value and is only
131+
/// This function is only used when the value is a `DropCheck`.
132+
/// This also drops the `DropCheck` value and is only
142133
/// every call once.
143-
unsafe fn drop_check_func(bytes: *mut u8, _: *mut u8) {
144-
assert_eq!(bytes.align_offset(std::mem::align_of::<DropCheck>()), 0);
145-
let _ = bytes.cast::<DropCheck>().read();
134+
unsafe fn drop_check_func(bytes: *mut u8, _: &mut ()) {
135+
let _ = bytes.cast::<DropCheck>().read_unaligned();
146136
}
147137

148138
#[test]
149139
fn test_anystack_drop() {
150-
let mut stack = AnyStack::new();
140+
let mut stack = AnyStack::<()>::new();
151141

152142
let (dropcheck_a, drops_a) = DropCheck::new();
153143
let (dropcheck_b, drops_b) = DropCheck::new();
@@ -161,10 +151,7 @@ mod test {
161151
assert_eq!(drops_a.load(Ordering::Relaxed), 0);
162152
assert_eq!(drops_b.load(Ordering::Relaxed), 0);
163153

164-
// SAFE: The `drop_check_func` does not access the null `user_data`.
165-
unsafe {
166-
stack.apply(std::ptr::null_mut());
167-
}
154+
stack.consume(&mut ());
168155

169156
assert_eq!(drops_a.load(Ordering::Relaxed), 1);
170157
assert_eq!(drops_b.load(Ordering::Relaxed), 1);
@@ -174,11 +161,9 @@ mod test {
174161

175162
/// # Safety
176163
/// `bytes` must point to a valid `u32`
177-
/// `world` must point to a mutable `FakeWorld`.
178164
/// Since `u32` is a primitive type, it does not require a `drop` call.
179-
unsafe fn increment_fake_world_u32(bytes: *mut u8, world: *mut u8) {
180-
let world = &mut *world.cast::<FakeWorld>();
181-
world.0 += *bytes.cast::<u32>();
165+
unsafe fn increment_fake_world_u32(bytes: *mut u8, world: &mut FakeWorld) {
166+
world.0 += bytes.cast::<u32>().read_unaligned();
182167
}
183168

184169
#[test]
@@ -199,19 +184,10 @@ mod test {
199184

200185
let mut world = FakeWorld(0);
201186

202-
// SAFE: the given `user_data` is a `&mut FakeWorld` which is safely
203-
// handled in the invocation of `increment_fake_world_u32`
204-
unsafe {
205-
stack.apply(&mut world as *mut FakeWorld as *mut u8);
206-
}
187+
stack.consume(&mut world);
207188

208189
assert_eq!(world.0, 15);
209190

210-
// SAFE: the data is only `u32` so they don't need to be dropped.
211-
unsafe {
212-
stack.clear();
213-
}
214-
215191
assert!(stack.is_empty());
216192
assert_eq!(stack.len(), 0);
217193
}

0 commit comments

Comments
 (0)