js_wasm_runtime_layer/
memory.rs

1use js_sys::{ArrayBuffer, Object, Reflect, Uint8Array, WebAssembly};
2use wasm_bindgen::{JsCast as _, JsValue};
3use wasm_runtime_layer::{
4    backend::{AsContext, AsContextMut, WasmMemory},
5    MemoryType,
6};
7
8use crate::{conversion::ToStoredJs, Engine, JsErrorMsg, StoreInner};
9
10#[derive(Debug, Clone)]
11/// WebAssembly memory
12pub struct Memory {
13    /// The id of the memory in the store
14    pub id: usize,
15}
16
17#[derive(Debug)]
18/// Holds the inner state of the memory
19pub(crate) struct MemoryInner {
20    /// The memory value
21    pub value: WebAssembly::Memory,
22    /// The memory type
23    pub ty: MemoryType,
24}
25
26impl MemoryInner {
27    /// Returns a `Uint8Array` view of the memory
28    pub(crate) fn as_uint8array(&self, offset: u32, len: u32) -> Uint8Array {
29        let buffer = self.value.buffer();
30        let buffer = buffer.dyn_ref::<ArrayBuffer>().unwrap();
31
32        Uint8Array::new_with_byte_offset_and_length(buffer, offset, len)
33    }
34}
35
36impl ToStoredJs for Memory {
37    type Repr = WebAssembly::Memory;
38    fn to_stored_js<T>(&self, store: &StoreInner<T>) -> WebAssembly::Memory {
39        let memory = &store.memories[self.id];
40
41        memory.value.clone()
42    }
43}
44
45impl Memory {
46    /// Construct a memory from an exported memory object
47    pub(crate) fn from_exported_memory<T>(
48        store: &mut StoreInner<T>,
49        value: JsValue,
50        ty: MemoryType,
51    ) -> Option<Self> {
52        let memory: &WebAssembly::Memory = value.dyn_ref()?;
53
54        Some(store.insert_memory(MemoryInner {
55            value: memory.clone(),
56            ty,
57        }))
58    }
59}
60
61impl WasmMemory<Engine> for Memory {
62    fn new(mut ctx: impl AsContextMut<Engine>, ty: MemoryType) -> anyhow::Result<Self> {
63        let desc = Object::new();
64        Reflect::set(&desc, &"initial".into(), &ty.initial_pages().into()).unwrap();
65        if let Some(maximum) = ty.maximum_pages() {
66            Reflect::set(&desc, &"maximum".into(), &maximum.into()).unwrap();
67        }
68
69        let memory = WebAssembly::Memory::new(&desc).map_err(JsErrorMsg::from)?;
70
71        let ctx: &mut StoreInner<_> = &mut *ctx.as_context_mut();
72
73        Ok(ctx.insert_memory(MemoryInner { value: memory, ty }))
74    }
75
76    fn ty(&self, ctx: impl AsContext<Engine>) -> MemoryType {
77        ctx.as_context().memories[self.id].ty
78    }
79
80    fn grow(&self, mut ctx: impl AsContextMut<Engine>, additional: u32) -> anyhow::Result<u32> {
81        let ctx: &mut StoreInner<_> = &mut *ctx.as_context_mut();
82
83        let inner = &mut ctx.memories[self.id];
84        Ok(inner.value.grow(additional))
85    }
86
87    fn current_pages(&self, _: impl AsContext<Engine>) -> u32 {
88        todo!("Memory::current_pages is not yet supported in the js_wasm_runtime_layer backend")
89    }
90
91    fn read(
92        &self,
93        ctx: impl AsContext<Engine>,
94        offset: usize,
95        buffer: &mut [u8],
96    ) -> anyhow::Result<()> {
97        let ctx: &StoreInner<_> = &*ctx.as_context();
98        let memory = &ctx.memories[self.id];
99
100        memory
101            .as_uint8array(offset as _, buffer.len() as _)
102            .copy_to(buffer);
103
104        Ok(())
105    }
106
107    fn write(
108        &self,
109        mut ctx: impl AsContextMut<Engine>,
110        offset: usize,
111        buffer: &[u8],
112    ) -> anyhow::Result<()> {
113        let ctx: &mut StoreInner<_> = &mut *ctx.as_context_mut();
114
115        let inner = &mut ctx.memories[self.id];
116        let dst = inner.as_uint8array(offset as _, buffer.len() as _);
117
118        #[cfg(feature = "tracing")]
119        tracing::debug!("writing {buffer:?} into guest");
120        dst.copy_from(buffer);
121
122        Ok(())
123    }
124}