Skip to main content

sim_lib_server/
wasm.rs

1use std::{
2    collections::BTreeMap,
3    sync::{Arc, Mutex, OnceLock},
4};
5
6use sim_kernel::{Error, Result};
7use sim_wasm_abi::{Handle, WasmFrameLimits, WasmRuntime, WasmiRuntime};
8
9#[derive(Clone)]
10pub(crate) struct WasmRegion {
11    pub(crate) runtime: Arc<dyn WasmRuntime>,
12    pub(crate) module: Handle,
13}
14
15fn wasm_regions() -> &'static Mutex<BTreeMap<String, Arc<WasmRegion>>> {
16    static REGISTRY: OnceLock<Mutex<BTreeMap<String, Arc<WasmRegion>>>> = OnceLock::new();
17    REGISTRY.get_or_init(|| Mutex::new(BTreeMap::new()))
18}
19
20/// Registers a wasm guest module under `region` using the default runtime.
21///
22/// Instantiates `wasm_bytes` with a wasmi runtime at default frame limits and
23/// stores it in the process-wide region registry.
24pub fn register_wasm_region(region: &str, wasm_bytes: &[u8]) -> Result<()> {
25    let runtime: Arc<dyn WasmRuntime> =
26        Arc::new(WasmiRuntime::with_limits(WasmFrameLimits::default()));
27    register_wasm_region_with_runtime(region, runtime, wasm_bytes)
28}
29
30pub(crate) fn register_wasm_region_with_runtime(
31    region: &str,
32    runtime: Arc<dyn WasmRuntime>,
33    wasm_bytes: &[u8],
34) -> Result<()> {
35    let module = runtime.instantiate_bytes(wasm_bytes)?;
36    let mut registry = wasm_regions()
37        .lock()
38        .map_err(|_| Error::HostError("wasm region registry mutex poisoned".to_owned()))?;
39    registry.insert(region.to_owned(), Arc::new(WasmRegion { runtime, module }));
40    Ok(())
41}
42
43pub(crate) fn lookup_wasm_region(region: &str) -> Result<Arc<WasmRegion>> {
44    let registry = wasm_regions()
45        .lock()
46        .map_err(|_| Error::HostError("wasm region registry mutex poisoned".to_owned()))?;
47    registry
48        .get(region)
49        .cloned()
50        .ok_or_else(|| Error::Eval(format!("unknown wasm region {region}")))
51}
52
53#[cfg(test)]
54mod tests {
55    use super::{lookup_wasm_region, register_wasm_region};
56    use std::sync::atomic::{AtomicU64, Ordering};
57
58    use sim_kernel::Error;
59    use wat::parse_str as wat_parse_str;
60
61    static NEXT_REGION_ID: AtomicU64 = AtomicU64::new(1);
62
63    fn unique_region_name() -> String {
64        format!(
65            "test-r6-region-{}",
66            NEXT_REGION_ID.fetch_add(1, Ordering::Relaxed)
67        )
68    }
69
70    fn minimal_wasm_guest_bytes() -> Vec<u8> {
71        wat_parse_str(
72            r#"(module
73                (memory (export "memory") 1)
74                (func (export "sim_alloc") (param i32) (result i32) i32.const 0)
75                (func (export "sim_manifest") (result i64) i64.const 0)
76                (func (export "sim_exports") (result i64) i64.const 0)
77                (func (export "sim_call") (param i32 i32 i32 i32) (result i64) i64.const 0)
78            )"#,
79        )
80        .expect("hand-written wasm guest fixture should assemble")
81    }
82
83    #[test]
84    fn register_wasm_region_instantiates_a_wasmi_module() {
85        let region = unique_region_name();
86        let bytes = minimal_wasm_guest_bytes();
87
88        register_wasm_region(&region, &bytes).unwrap();
89
90        let registered = lookup_wasm_region(&region).unwrap();
91        assert_eq!(registered.module.0, 1);
92    }
93
94    #[test]
95    fn lookup_wasm_region_reports_unknown_region_clearly() {
96        let err = match lookup_wasm_region("missing-r6-region") {
97            Ok(_) => panic!("expected unknown wasm region lookup to fail"),
98            Err(err) => err,
99        };
100        assert!(
101            matches!(err, Error::Eval(message) if message == "unknown wasm region missing-r6-region")
102        );
103    }
104}