pyodide_webassembly_runtime_layer/
memory.rs1use pyo3::{intern, prelude::*, sync::GILOnceCell, types::PyBytes};
2use wasm_runtime_layer::{
3 backend::{AsContext, AsContextMut, WasmMemory},
4 MemoryType,
5};
6
7use crate::{
8 conversion::{create_js_object, instanceof, js_uint8_array_new, ToPy},
9 Engine,
10};
11
12#[derive(Debug)]
13pub struct Memory {
19 memory: Py<PyAny>,
21 ty: MemoryType,
23}
24
25impl Clone for Memory {
26 fn clone(&self) -> Self {
27 Python::with_gil(|py| Self {
28 memory: self.memory.clone_ref(py),
29 ty: self.ty,
30 })
31 }
32}
33
34impl WasmMemory<Engine> for Memory {
35 fn new(_ctx: impl AsContextMut<Engine>, ty: MemoryType) -> anyhow::Result<Self> {
36 Python::with_gil(|py| {
37 #[cfg(feature = "tracing")]
38 tracing::debug!(?ty, "Memory::new");
39
40 let desc = create_js_object(py)?;
41 desc.setattr(intern!(py, "initial"), ty.initial_pages())?;
42 if let Some(maximum) = ty.maximum_pages() {
43 desc.setattr(intern!(py, "maximum"), maximum)?;
44 }
45
46 let memory = web_assembly_memory_new(py)?.call1((desc,))?;
47
48 Ok(Self {
49 memory: memory.unbind(),
50 ty,
51 })
52 })
53 }
54
55 fn ty(&self, _ctx: impl AsContext<Engine>) -> MemoryType {
56 self.ty
57 }
58
59 fn grow(&self, _ctx: impl AsContextMut<Engine>, additional: u32) -> anyhow::Result<u32> {
60 Python::with_gil(|py| {
61 let memory = self.memory.bind(py);
62
63 #[cfg(feature = "tracing")]
64 tracing::debug!(memory = %memory, ?self.ty, additional, "Memory::grow");
65
66 let old_pages = memory
67 .call_method1(intern!(py, "grow"), (additional,))?
68 .extract()?;
69
70 Ok(old_pages)
71 })
72 }
73
74 fn current_pages(&self, _ctx: impl AsContext<Engine>) -> u32 {
75 const PAGE_SIZE: u64 = 1 << 16;
76
77 Python::with_gil(|py| -> Result<u32, PyErr> {
78 let memory = self.memory.bind(py);
79
80 #[cfg(feature = "tracing")]
81 tracing::debug!(memory = %memory, ?self.ty, "Memory::current_pages");
82
83 let byte_len: u64 = memory
84 .getattr(intern!(py, "buffer"))?
85 .getattr(intern!(py, "byteLength"))?
86 .extract()?;
87
88 let pages = u32::try_from(byte_len / PAGE_SIZE)?;
89 Ok(pages)
90 })
91 .expect("Memory::current_pages should not fail")
92 }
93
94 fn read(
95 &self,
96 _ctx: impl AsContext<Engine>,
97 offset: usize,
98 buffer: &mut [u8],
99 ) -> anyhow::Result<()> {
100 Python::with_gil(|py| {
101 let memory = self.memory.bind(py);
102
103 #[cfg(feature = "tracing")]
104 tracing::debug!(memory = %memory, ?self.ty, offset, len = buffer.len(), "Memory::read");
105
106 let memory = memory.getattr(intern!(py, "buffer"))?;
107 let memory = js_uint8_array_new(py)?.call1((memory, offset, buffer.len()))?;
108
109 let bytes: Bound<PyBytes> = memory.call_method0(intern!(py, "to_bytes"))?.extract()?;
110 buffer.copy_from_slice(bytes.as_bytes());
111
112 Ok(())
113 })
114 }
115
116 fn write(
117 &self,
118 _ctx: impl AsContextMut<Engine>,
119 offset: usize,
120 buffer: &[u8],
121 ) -> anyhow::Result<()> {
122 Python::with_gil(|py| {
123 let memory = self.memory.bind(py);
124
125 #[cfg(feature = "tracing")]
126 tracing::debug!(memory = %memory, ?self.ty, offset, len = buffer.len(), "Memory::write");
127
128 let memory = memory.getattr(intern!(py, "buffer"))?;
129 let memory = js_uint8_array_new(py)?.call1((memory, offset, buffer.len()))?;
130
131 memory.call_method1(intern!(py, "assign"), (buffer,))?;
132
133 Ok(())
134 })
135 }
136}
137
138impl ToPy for Memory {
139 fn to_py(&self, py: Python) -> Py<PyAny> {
140 #[cfg(feature = "tracing")]
141 tracing::trace!(value = %self.memory.bind(py), ?self.ty, "Memory::to_py");
142
143 self.memory.clone_ref(py)
144 }
145}
146
147impl Memory {
148 pub(crate) fn from_exported_memory(
150 memory: Bound<PyAny>,
151 ty: MemoryType,
152 ) -> anyhow::Result<Self> {
153 if !instanceof(&memory, web_assembly_memory(memory.py())?)? {
154 anyhow::bail!("expected WebAssembly.Memory but found {memory}");
155 }
156
157 #[cfg(feature = "tracing")]
158 tracing::debug!(memory = %memory, ?ty, "Memory::from_exported_memory");
159
160 Ok(Self {
161 memory: memory.unbind(),
162 ty,
163 })
164 }
165}
166
167fn web_assembly_memory(py: Python) -> Result<&Bound<PyAny>, PyErr> {
168 static WEB_ASSEMBLY_MEMORY: GILOnceCell<Py<PyAny>> = GILOnceCell::new();
169 WEB_ASSEMBLY_MEMORY.import(py, "js.WebAssembly", "Memory")
170}
171
172fn web_assembly_memory_new(py: Python) -> Result<&Bound<PyAny>, PyErr> {
173 static WEB_ASSEMBLY_MEMORY_NEW: GILOnceCell<Py<PyAny>> = GILOnceCell::new();
174 WEB_ASSEMBLY_MEMORY_NEW.import(py, "js.WebAssembly.Memory", "new")
175}