pyodide_webassembly_runtime_layer/
table.rs1use pyo3::{intern, prelude::*, sync::PyOnceLock};
2use wasm_runtime_layer::{
3 backend::{AsContext, AsContextMut, Ref, WasmTable},
4 RefType, TableType,
5};
6
7use crate::{
8 conversion::{create_js_object, instanceof, RefExt, RefTypeExt, ToPy},
9 Engine,
10};
11
12#[derive(Debug)]
13pub struct Table {
19 table: Py<PyAny>,
21 ty: TableType,
23}
24
25impl Clone for Table {
26 fn clone(&self) -> Self {
27 Python::attach(|py| Self {
28 table: self.table.clone_ref(py),
29 ty: self.ty,
30 })
31 }
32}
33
34impl WasmTable<Engine> for Table {
35 fn new(
36 _ctx: impl AsContextMut<Engine>,
37 ty: TableType,
38 init: Ref<Engine>,
39 ) -> anyhow::Result<Self> {
40 Python::attach(|py| -> anyhow::Result<Self> {
41 #[cfg(feature = "tracing")]
42 tracing::debug!(?ty, ?init, "Table::new");
43
44 let desc = create_js_object(py)?;
45 desc.setattr(intern!(py, "element"), ty.element().as_js_descriptor())?;
46 desc.setattr(intern!(py, "initial"), ty.minimum())?;
47 if let Some(max) = ty.maximum() {
48 desc.setattr(intern!(py, "maximum"), max)?;
49 }
50
51 let init = init.to_py(py)?;
52
53 let table = web_assembly_table_new(py)?.call1((desc, init))?;
54
55 Ok(Self {
56 table: table.unbind(),
57 ty,
58 })
59 })
60 }
61
62 fn ty(&self, _ctx: impl AsContext<Engine>) -> TableType {
64 self.ty
65 }
66
67 fn size(&self, _ctx: impl AsContext<Engine>) -> u32 {
69 Python::attach(|py| -> Result<u32, PyErr> {
70 let table = self.table.bind(py);
71
72 #[cfg(feature = "tracing")]
73 tracing::debug!(table = %table, ?self.ty, "Table::size");
74
75 table.getattr(intern!(py, "length"))?.extract()
76 })
77 .expect("Table::size should not fail")
78 }
79
80 fn grow(
82 &self,
83 _ctx: impl AsContextMut<Engine>,
84 delta: u32,
85 init: Ref<Engine>,
86 ) -> anyhow::Result<u32> {
87 Python::attach(|py| {
88 let table = self.table.bind(py);
89
90 #[cfg(feature = "tracing")]
91 tracing::debug!(table = %table, ?self.ty, delta, ?init, "Table::grow");
92
93 let init = init.to_py(py)?;
94
95 let old_len = table
96 .call_method1(intern!(py, "grow"), (delta, init))?
97 .extract()?;
98
99 Ok(old_len)
100 })
101 }
102
103 fn get(&self, _ctx: impl AsContextMut<Engine>, index: u32) -> Option<Ref<Engine>> {
105 Python::attach(|py| {
106 let table = self.table.bind(py);
107
108 #[cfg(feature = "tracing")]
109 tracing::debug!(table = %table, ?self.ty, index, "Table::get");
110
111 let value = table.call_method1(intern!(py, "get"), (index,)).ok()?;
112
113 Some(Ref::from_py_typed(value, self.ty.element()).expect("Table::get should not fail"))
114 })
115 }
116
117 fn set(
119 &self,
120 _ctx: impl AsContextMut<Engine>,
121 index: u32,
122 elem: Ref<Engine>,
123 ) -> anyhow::Result<()> {
124 Python::attach(|py| {
125 let table = self.table.bind(py);
126
127 #[cfg(feature = "tracing")]
128 tracing::debug!(table = %table, ?self.ty, index, ?elem, "Table::set");
129
130 let elem = elem.to_py(py)?;
131
132 table.call_method1(intern!(py, "set"), (index, elem))?;
133
134 Ok(())
135 })
136 }
137}
138
139impl ToPy for Table {
140 fn to_py(&self, py: Python) -> Result<Py<PyAny>, PyErr> {
141 #[cfg(feature = "tracing")]
142 tracing::trace!(table = %self.table, ?self.ty, "Table::to_py");
143
144 Ok(self.table.clone_ref(py))
145 }
146}
147
148impl Table {
149 pub(crate) fn from_exported_table(table: Bound<PyAny>, ty: TableType) -> anyhow::Result<Self> {
151 if !instanceof(&table, web_assembly_table(table.py())?)? {
152 anyhow::bail!("expected WebAssembly.Table but found {table}");
153 }
154
155 #[cfg(feature = "tracing")]
156 tracing::debug!(table = %table, ?ty, "Table::from_exported_table");
157
158 let table_length: u32 = table.getattr(intern!(table.py(), "length"))?.extract()?;
159
160 assert!(table_length >= ty.minimum());
161 assert_eq!(ty.element(), RefType::FuncRef);
162
163 Ok(Self {
164 table: table.unbind(),
165 ty,
166 })
167 }
168}
169
170fn web_assembly_table(py: Python<'_>) -> Result<&Bound<'_, PyAny>, PyErr> {
171 static WEB_ASSEMBLY_TABLE: PyOnceLock<Py<PyAny>> = PyOnceLock::new();
172 WEB_ASSEMBLY_TABLE.import(py, "js.WebAssembly", "Table")
173}
174
175fn web_assembly_table_new(py: Python<'_>) -> Result<&Bound<'_, PyAny>, PyErr> {
176 static WEB_ASSEMBLY_TABLE_NEW: PyOnceLock<Py<PyAny>> = PyOnceLock::new();
177 WEB_ASSEMBLY_TABLE_NEW.import(py, "js.WebAssembly.Table", "new")
178}