pyodide_webassembly_runtime_layer/
global.rs1use pyo3::{intern, prelude::*, sync::PyOnceLock};
2use wasm_runtime_layer::{
3 backend::{AsContext, AsContextMut, Val, WasmGlobal},
4 GlobalType,
5};
6
7use crate::{
8 conversion::{create_js_object, instanceof, ToPy, ValExt, ValTypeExt},
9 Engine,
10};
11
12#[derive(Debug)]
18pub struct Global {
19 global: Py<PyAny>,
21 ty: GlobalType,
23}
24
25impl Clone for Global {
26 fn clone(&self) -> Self {
27 Python::attach(|py| Self {
28 global: self.global.clone_ref(py),
29 ty: self.ty,
30 })
31 }
32}
33
34impl WasmGlobal<Engine> for Global {
35 fn new(_ctx: impl AsContextMut<Engine>, value: Val<Engine>, mutable: bool) -> Self {
36 Python::attach(|py| -> Result<Self, PyErr> {
37 #[cfg(feature = "tracing")]
38 tracing::debug!(?value, mutable, "Global::new");
39
40 let ty = GlobalType::new(value.ty(), mutable);
41
42 let desc = create_js_object(py)?;
43 desc.setattr(intern!(py, "value"), value.ty().as_js_descriptor())?;
44 desc.setattr(intern!(py, "mutable"), mutable)?;
45
46 let value = value.to_py(py)?;
47
48 let global = web_assembly_global_new(py)?.call1((desc, value))?;
49
50 Ok(Self {
51 global: global.unbind(),
52 ty,
53 })
54 })
55 .expect("Global::new should not fail")
56 }
57
58 fn ty(&self, _ctx: impl AsContext<Engine>) -> GlobalType {
59 self.ty
60 }
61
62 fn set(&self, _ctx: impl AsContextMut<Engine>, new_value: Val<Engine>) -> anyhow::Result<()> {
63 if !self.ty.mutable() {
64 return Err(anyhow::anyhow!("Global is not mutable"));
65 }
66
67 Python::attach(|py| {
68 let global = self.global.bind(py);
69
70 #[cfg(feature = "tracing")]
71 tracing::debug!(global = %global, ?self.ty, ?new_value, "Global::set");
72
73 let new_value = new_value.to_py(py)?;
74
75 global.setattr(intern!(py, "value"), new_value)?;
76
77 Ok(())
78 })
79 }
80
81 fn get(&self, _ctx: impl AsContextMut<Engine>) -> Val<Engine> {
82 Python::attach(|py| {
83 let global = self.global.bind(py);
84
85 #[cfg(feature = "tracing")]
86 tracing::debug!(global = %global, ?self.ty, "Global::get");
87
88 let value = global.getattr(intern!(py, "value"))?;
89
90 Val::from_py_typed(value, self.ty.content())
91 })
92 .expect("Global::get should not fail")
93 }
94}
95
96impl ToPy for Global {
97 fn to_py(&self, py: Python) -> Result<Py<PyAny>, PyErr> {
98 #[cfg(feature = "tracing")]
99 tracing::trace!(value = %self.global, ?self.ty, "Global::to_py");
100
101 Ok(self.global.clone_ref(py))
102 }
103}
104
105impl Global {
106 pub(crate) fn from_exported_global(
108 global: Bound<PyAny>,
109 ty: GlobalType,
110 ) -> anyhow::Result<Self> {
111 if !instanceof(&global, web_assembly_global(global.py())?)? {
112 anyhow::bail!("expected WebAssembly.Global but found {global}");
113 }
114
115 #[cfg(feature = "tracing")]
116 tracing::debug!(global = %global, ?ty, "Global::from_exported_global");
117
118 Ok(Self {
119 global: global.unbind(),
120 ty,
121 })
122 }
123}
124
125fn web_assembly_global(py: Python<'_>) -> Result<&Bound<'_, PyAny>, PyErr> {
126 static WEB_ASSEMBLY_GLOBAL: PyOnceLock<Py<PyAny>> = PyOnceLock::new();
127 WEB_ASSEMBLY_GLOBAL.import(py, "js.WebAssembly", "Global")
128}
129
130fn web_assembly_global_new(py: Python<'_>) -> Result<&Bound<'_, PyAny>, PyErr> {
131 static WEB_ASSEMBLY_GLOBAL_NEW: PyOnceLock<Py<PyAny>> = PyOnceLock::new();
132 WEB_ASSEMBLY_GLOBAL_NEW.import(py, "js.WebAssembly.Global", "new")
133}