sidevm_host_runtime/
run.rs

1use anyhow::{Context as _, Result};
2use phala_scheduler::TaskScheduler;
3use std::future::Future;
4use std::pin::Pin;
5use std::task::{Context, Poll};
6use wasmer::{BaseTunables, Engine, Instance, Module, Pages, RuntimeError, Store, TypedFunction};
7#[cfg(feature = "wasmer-compiler-cranelift")]
8use wasmer_compiler_cranelift::Cranelift;
9#[cfg(feature = "wasmer-compiler-llvm")]
10use wasmer_compiler_llvm::LLVM;
11use wasmer_compiler_singlepass::Singlepass;
12use phala_wasmer_tunables::LimitingTunables;
13
14use crate::env::{DynCacheOps, LogHandler};
15use crate::{async_context, env, metering::metering, VmId};
16
17#[derive(Clone)]
18pub struct WasmModule {
19    engine: WasmEngine,
20    module: Module,
21}
22
23#[derive(Clone)]
24pub struct WasmEngine {
25    inner: Engine,
26}
27
28impl Default for WasmEngine {
29    fn default() -> Self {
30        Self::new()
31    }
32}
33
34impl WasmEngine {
35    pub fn new() -> Self {
36        let compiler_env = std::env::var("WASMER_COMPILER");
37        let compiler_env = compiler_env
38            .as_ref()
39            .map(AsRef::as_ref)
40            .unwrap_or("singlepass");
41        let engine = match compiler_env {
42            "singlepass" => metering(Singlepass::default()).into(),
43            #[cfg(feature = "wasmer-compiler-cranelift")]
44            "cranelift" => metering(Cranelift::default()).into(),
45            #[cfg(feature = "wasmer-compiler-llvm")]
46            "llvm" => LLVM::default().into(),
47            _ => panic!("Unsupported compiler engine: {compiler_env}"),
48        };
49        Self { inner: engine }
50    }
51
52    pub fn compile(&self, wasm_code: &[u8]) -> Result<WasmModule> {
53        Ok(WasmModule {
54            engine: self.clone(),
55            module: Module::new(&self.inner, wasm_code)?,
56        })
57    }
58}
59
60impl WasmModule {
61    pub fn run(
62        &self,
63        args: Vec<String>,
64        config: WasmInstanceConfig,
65    ) -> Result<(WasmRun, env::Env)> {
66        let WasmInstanceConfig {
67            max_memory_pages,
68            id,
69            gas_per_breath,
70            cache_ops,
71            scheduler,
72            weight,
73            event_tx,
74            log_handler,
75        } = config;
76        let base = BaseTunables {
77            // Always use dynamic heap memory to save memory
78            static_memory_bound: Pages(0),
79            static_memory_offset_guard_size: 0,
80            dynamic_memory_offset_guard_size: page_size::get() as _,
81        };
82        let tunables = LimitingTunables::new(base, Pages(max_memory_pages));
83        let mut engine = self.engine.inner.clone();
84        engine.set_tunables(tunables);
85        let mut store = Store::new(engine);
86        let (env, import_object) =
87            env::create_env(id, &mut store, cache_ops, event_tx, log_handler, args);
88        let instance = Instance::new(&mut store, &self.module, &import_object)?;
89        let memory = instance
90            .exports
91            .get_memory("memory")
92            .context("No memory exported")?;
93        let wasm_poll_entry = instance.exports.get_typed_function(&store, "sidevm_poll")?;
94        env.set_memory(memory.clone());
95        env.set_instance(instance);
96        env.set_gas_per_breath(gas_per_breath);
97        env.set_weight(weight);
98        if let Some(scheduler) = &scheduler {
99            scheduler.reset(&id);
100        }
101        Ok((
102            WasmRun {
103                env: env.clone(),
104                wasm_poll_entry,
105                store,
106                scheduler,
107                id,
108            },
109            env,
110        ))
111    }
112}
113
114pub struct WasmInstanceConfig {
115    pub max_memory_pages: u32,
116    pub id: crate::VmId,
117    pub gas_per_breath: u64,
118    pub cache_ops: DynCacheOps,
119    pub scheduler: Option<TaskScheduler<VmId>>,
120    pub weight: u32,
121    pub event_tx: crate::OutgoingRequestChannel,
122    pub log_handler: Option<LogHandler>,
123}
124
125pub struct WasmRun {
126    id: VmId,
127    env: env::Env,
128    store: Store,
129    wasm_poll_entry: TypedFunction<(), i32>,
130    scheduler: Option<TaskScheduler<VmId>>,
131}
132
133impl Drop for WasmRun {
134    fn drop(&mut self) {
135        self.env.cleanup();
136        if let Some(scheduler) = &self.scheduler {
137            scheduler.exit(&self.id);
138        }
139    }
140}
141
142impl Future for WasmRun {
143    type Output = Result<i32, RuntimeError>;
144
145    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
146        let _guard = match &self.scheduler {
147            Some(scheduler) => Some(futures::ready!(scheduler.poll_resume(
148                cx,
149                &self.id,
150                self.env.weight()
151            ))),
152            None => None,
153        };
154        let run = self.get_mut();
155        run.env.reset_gas_to_breath(&mut run.store);
156        match async_context::set_task_cx(cx, || run.wasm_poll_entry.call(&mut run.store)) {
157            Ok(rv) => {
158                if rv == 0 {
159                    if run.env.has_more_ready() {
160                        cx.waker().wake_by_ref();
161                    }
162                    Poll::Pending
163                } else {
164                    Poll::Ready(Ok(rv))
165                }
166            }
167            Err(err) => {
168                if run.env.is_stifled(&mut run.store) {
169                    Poll::Ready(Err(RuntimeError::user(
170                        crate::env::OcallAborted::Stifled.into(),
171                    )))
172                } else {
173                    Poll::Ready(Err(err))
174                }
175            }
176        }
177    }
178}