sidevm_host_runtime/
run.rs1use 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 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}