-
Notifications
You must be signed in to change notification settings - Fork 144
Expand file tree
/
Copy pathcontext.rs
More file actions
433 lines (379 loc) · 13.3 KB
/
context.rs
File metadata and controls
433 lines (379 loc) · 13.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
use std::{
alloc::Layout,
cell::{Ref, RefMut},
marker::PhantomData,
mem::size_of,
panic::AssertUnwindSafe,
ptr::NonNull,
sync::atomic::{AtomicBool, Ordering},
};
use anyhow::anyhow;
use bytemuck::{Pod, Zeroable};
use feather_common::Game;
use feather_ecs::EntityBuilder;
use quill_common::Component;
use serde::de::DeserializeOwned;
use vec_arena::Arena;
use wasmer::{FromToNativeWasmType, Instance};
use crate::{host_function::WasmHostFunction, thread_pinned::ThreadPinned, PluginId};
mod native;
mod wasm;
/// Wraps a pointer into a plugin's memory space.
#[derive(Copy, Clone, PartialEq, Eq, Zeroable)]
#[repr(transparent)]
pub struct PluginPtr<T> {
pub ptr: u64,
pub _marker: PhantomData<*const T>,
}
impl<T> PluginPtr<T> {
pub fn as_native(&self) -> *const T {
self.ptr as usize as *const T
}
/// # Safety
/// Adding `n` to this pointer
/// must produce a pointer within the same allocated
/// object.
#[must_use = "PluginPtr::add returns a new pointer"]
pub unsafe fn add(self, n: usize) -> Self {
Self {
ptr: self.ptr + (n * size_of::<T>()) as u64,
_marker: self._marker,
}
}
/// # Safety
/// The cast must be valid.
pub unsafe fn cast<U>(self) -> PluginPtr<U> {
PluginPtr {
ptr: self.ptr,
_marker: PhantomData,
}
}
}
unsafe impl<T: Copy + 'static> Pod for PluginPtr<T> {}
/// Wraps a pointer into a plugin's memory space.
#[derive(Copy, Clone, PartialEq, Eq, Zeroable)]
#[repr(transparent)]
pub struct PluginPtrMut<T> {
pub ptr: u64,
pub _marker: PhantomData<*mut T>,
}
impl<T> PluginPtrMut<T> {
pub fn as_native(&self) -> *mut T {
self.ptr as usize as *mut T
}
/// # Safety
/// A null pointer must be valid in the context it is used.
pub unsafe fn null() -> Self {
Self {
ptr: 0,
_marker: PhantomData,
}
}
/// # Safety
/// Adding `n` to this pointer
/// must produce a pointer within the same allocated
/// object.
#[must_use = "PluginPtrMut::add returns a new pointer"]
pub unsafe fn add(self, n: usize) -> Self {
Self {
ptr: self.ptr + (n * size_of::<T>()) as u64,
_marker: self._marker,
}
}
/// # Safety
/// The cast must be valid.
pub unsafe fn cast<U>(self) -> PluginPtrMut<U> {
PluginPtrMut {
ptr: self.ptr,
_marker: PhantomData,
}
}
}
unsafe impl<T: Copy + 'static> Pod for PluginPtrMut<T> {}
unsafe impl<T: Copy> FromToNativeWasmType for PluginPtr<T> {
type Native = i64;
fn from_native(native: Self::Native) -> Self {
Self {
ptr: native as u64,
_marker: PhantomData,
}
}
fn to_native(self) -> Self::Native {
self.ptr as i64
}
}
unsafe impl<T: Copy> FromToNativeWasmType for PluginPtrMut<T> {
type Native = i64;
fn from_native(native: Self::Native) -> Self {
Self {
ptr: native as u64,
_marker: PhantomData,
}
}
fn to_native(self) -> Self::Native {
self.ptr as i64
}
}
/// Context of a running plugin.
///
/// Provides methods to access plugin memory,
/// invoke exported functions, and access the `Game`.
///
/// This type abstracts over WASM or native plugins,
/// providing the same interface for both.
///
/// # Safety
/// The `native` version of the plugin context
/// dereferences raw pointers. We assume pointers
/// passed by plugins are valid. Most functions
/// will cause undefined behavior if these constraints
/// are violated.
///
/// We type-encode that a pointer originates from a plugin
/// using the `PluginPtr` structs. Methods that
/// dereference pointers take instances of these
/// structs. Since creating a `PluginPtr` is unsafe,
/// `PluginContext` methods don't have to be marked
/// unsafe.
///
/// On WASM targets, the plugin is never trusted,
/// and pointer accesses are checked. Undefined behavior
/// can never occur as a result of malicious plugin input.
pub struct PluginContext {
inner: Inner,
/// Whether the plugin is currently being invoked
/// on the main thread.
/// If this is `true`, then plugin functions are on the call stack.
invoking_on_main_thread: AtomicBool,
/// The current `Game`.
///
/// Set to `None` if `invoking_on_main_thread` is `false`.
/// Otherwise, must point to a valid game. The pointer
/// must be cleared after the plugin finishes executing
/// or we risk a dangling reference.
game: ThreadPinned<Option<NonNull<Game>>>,
/// ID of the plugin.
id: PluginId,
/// Active entity builders for the plugin.
pub entity_builders: ThreadPinned<Arena<EntityBuilder>>,
}
impl PluginContext {
/// Creates a new WASM plugin context.
pub fn new_wasm(id: PluginId) -> Self {
Self {
inner: Inner::Wasm(ThreadPinned::new(wasm::WasmPluginContext::new())),
invoking_on_main_thread: AtomicBool::new(false),
game: ThreadPinned::new(None),
id,
entity_builders: ThreadPinned::new(Arena::new()),
}
}
/// Creates a new native plugin context.
pub fn new_native(id: PluginId) -> Self {
Self {
inner: Inner::Native(native::NativePluginContext::new()),
invoking_on_main_thread: AtomicBool::new(false),
game: ThreadPinned::new(None),
id,
entity_builders: ThreadPinned::new(Arena::new()),
}
}
pub fn init_with_instance(&self, instance: &Instance) -> anyhow::Result<()> {
match &self.inner {
Inner::Wasm(w) => w.borrow_mut().init_with_instance(instance),
Inner::Native(_) => panic!("cannot initialize native plugin context"),
}
}
/// Enters the plugin context, invoking a function inside the plugin.
///
/// # Panics
/// Panics if we are already inside the plugin context.
/// Panics if not called on the main thread.
pub fn enter<R>(&self, game: &mut Game, callback: impl FnOnce() -> R) -> R {
let was_already_entered = self.invoking_on_main_thread.swap(true, Ordering::SeqCst);
assert!(!was_already_entered, "cannot recursively invoke a plugin");
*self.game.borrow_mut() = Some(NonNull::from(game));
// If a panic occurs, we need to catch it so
// we clear `self.game`. Otherwise, we get
// a dangling pointer.
let result = std::panic::catch_unwind(AssertUnwindSafe(callback));
self.invoking_on_main_thread.store(false, Ordering::SeqCst);
*self.game.borrow_mut() = None;
self.bump_reset();
result.unwrap()
}
/// Gets a mutable reference to the `Game`.
///
/// # Panics
/// Panics if the plugin is not currently being
/// invoked on the main thread.
pub fn game_mut(&self) -> RefMut<Game> {
let ptr = self.game.borrow_mut();
RefMut::map(ptr, |ptr| {
let game_ptr = ptr.expect("plugin is not exeuctugin");
assert!(self.invoking_on_main_thread.load(Ordering::Relaxed));
// SAFETY: `game_ptr` points to a valid `Game` whenever
// the plugin is executing. If the plugin is not
// executing, then we already panicked when unwrapping `ptr`.
unsafe { &mut *game_ptr.as_ptr() }
})
}
/// Gets the plugin ID.
pub fn plugin_id(&self) -> PluginId {
self.id
}
/// Accesses a byte slice in the plugin's memory space.
///
/// # Safety
/// **WASM**: mutating plugin memory or invoking
/// plugin functions while this byte slice is
/// alive is undefined behavior.
/// **Native**: `ptr` must be valid.
pub unsafe fn deref_bytes(&self, ptr: PluginPtr<u8>, len: u32) -> anyhow::Result<&[u8]> {
match &self.inner {
Inner::Wasm(w) => {
let w = w.borrow();
let bytes = w.deref_bytes(ptr, len)?;
Ok(unsafe { std::slice::from_raw_parts(bytes.as_ptr(), bytes.len()) })
}
Inner::Native(n) => n.deref_bytes(ptr, len),
}
}
/// Accesses a byte slice in the plugin's memory space.
///
/// # Safety
/// **WASM**: accessing plugin memory or invoking
/// plugin functions while this byte slice is
/// alive is undefined behavior.
/// **Native**: `ptr` must be valid and the aliasing
/// rules must not be violated.
pub unsafe fn deref_bytes_mut(
&self,
ptr: PluginPtrMut<u8>,
len: u32,
) -> anyhow::Result<&mut [u8]> {
match &self.inner {
Inner::Wasm(w) => {
let w = w.borrow();
let bytes = w.deref_bytes_mut(ptr, len)?;
Ok(unsafe { std::slice::from_raw_parts_mut(bytes.as_mut_ptr(), bytes.len()) })
}
Inner::Native(n) => n.deref_bytes_mut(ptr, len),
}
}
/// Accesses a `Pod` value in the plugin's memory space.
pub fn read_pod<T: Pod>(&self, ptr: PluginPtr<T>) -> anyhow::Result<T> {
// SAFETY: we do not return a reference to these
// bytes.
unsafe {
let bytes = self.deref_bytes(ptr.cast(), size_of::<T>() as u32)?;
bytemuck::try_from_bytes(bytes)
.map_err(|_| anyhow!("badly aligned data"))
.map(|val| *val)
}
}
/// Accesses a `bincode`-encoded value in the plugin's memory space.
pub fn read_bincode<T: DeserializeOwned>(
&self,
ptr: PluginPtr<u8>,
len: u32,
) -> anyhow::Result<T> {
// SAFETY: we do not return a reference to these
// bytes.
unsafe {
let bytes = self.deref_bytes(ptr.cast(), len)?;
bincode::deserialize(bytes).map_err(From::from)
}
}
/// Accesses a `json`-encoded value in the plugin's memory space.
pub fn read_json<T: DeserializeOwned>(
&self,
ptr: PluginPtr<u8>,
len: u32,
) -> anyhow::Result<T> {
// SAFETY: we do not return a reference to these
// bytes.
unsafe {
let bytes = self.deref_bytes(ptr.cast(), len)?;
serde_json::from_slice(bytes).map_err(From::from)
}
}
/// Deserializes a component value in the plugin's memory space.
pub fn read_component<T: Component>(&self, ptr: PluginPtr<u8>, len: u32) -> anyhow::Result<T> {
// SAFETY: we do not return a reference to these
// bytes.
unsafe {
let bytes = self.deref_bytes(ptr.cast(), len)?;
T::from_bytes(bytes)
.ok_or_else(|| anyhow!("malformed component"))
.map(|(component, _bytes_read)| component)
}
}
/// Reads a string from the plugin's memory space.
pub fn read_string(&self, ptr: PluginPtr<u8>, len: u32) -> anyhow::Result<String> {
// SAFETY: we do not return a reference to these bytes.
unsafe {
let bytes = self.deref_bytes(ptr.cast(), len)?;
let string = std::str::from_utf8(bytes)?.to_owned();
Ok(string)
}
}
/// Reads a `Vec<u8>` from the plugin's memory space.
pub fn read_bytes(&self, ptr: PluginPtr<u8>, len: u32) -> anyhow::Result<Vec<u8>> {
// SAFETY: we do not return a reference to these bytes.
unsafe {
let bytes = self.deref_bytes(ptr.cast(), len)?;
Ok(bytes.to_owned())
}
}
/// Allocates some memory within the plugin's bump
/// allocator.
///
/// The memory is reset after the plugin finishes
/// executing the current system.
pub fn bump_allocate(&self, layout: Layout) -> anyhow::Result<PluginPtrMut<u8>> {
match &self.inner {
Inner::Wasm(w) => w.borrow().bump_allocate(layout),
Inner::Native(n) => n.bump_allocate(layout),
}
}
/// Bump allocates some memory, then copies `data` into it.
pub fn bump_allocate_and_write_bytes(&self, data: &[u8]) -> anyhow::Result<PluginPtrMut<u8>> {
let layout = Layout::array::<u8>(data.len())?;
let ptr = self.bump_allocate(layout)?;
// SAFETY: our access to these bytes is isolated to the
// current function. `ptr` is valid as it was just allocated.
unsafe {
self.write_bytes(ptr, data)?;
}
Ok(ptr)
}
/// Writes `data` to `ptr`.
///
/// # Safety
/// **WASM**: No concerns.
/// **NATIVE**: `ptr` must point to a slice
/// of at least `len` valid bytes.
pub unsafe fn write_bytes(&self, ptr: PluginPtrMut<u8>, data: &[u8]) -> anyhow::Result<()> {
let bytes = self.deref_bytes_mut(ptr, data.len() as u32)?;
bytes.copy_from_slice(data);
Ok(())
}
/// Writes a `Pod` type to `ptr`.
pub fn write_pod<T: Pod>(&self, ptr: PluginPtrMut<T>, value: T) -> anyhow::Result<()> {
// SAFETY: Unlike `write_bytes`, we know `ptr` is valid for values
// of type `T` because of its type parameter.
unsafe { self.write_bytes(ptr.cast(), bytemuck::bytes_of(&value)) }
}
/// Deallocates all bump-allocated memory.
fn bump_reset(&self) {
match &self.inner {
Inner::Wasm(w) => w.borrow().bump_reset(),
Inner::Native(n) => n.bump_reset(),
}
}
}
enum Inner {
Wasm(ThreadPinned<wasm::WasmPluginContext>),
Native(native::NativePluginContext),
}