1use std::collections::BTreeMap;
2
3use crate::*;
4
5pub struct Plugin {
7 pub modules: BTreeMap<String, Module>,
9
10 pub linker: Linker<Internal>,
12 pub store: Store<Internal>,
13
14 pub instance: Option<Instance>,
16 pub instance_pre: InstancePre<Internal>,
17
18 instantiations: usize,
22
23 pub timer_id: uuid::Uuid,
25
26 pub(crate) cancel_handle: sdk::ExtismCancelHandle,
28
29 pub(crate) runtime: Option<Runtime>,
32}
33
34impl InternalExt for Plugin {
35 fn store(&self) -> &Store<Internal> {
36 &self.store
37 }
38
39 fn store_mut(&mut self) -> &mut Store<Internal> {
40 &mut self.store
41 }
42
43 fn linker(&self) -> &Linker<Internal> {
44 &self.linker
45 }
46
47 fn linker_mut(&mut self) -> &mut Linker<Internal> {
48 &mut self.linker
49 }
50
51 fn linker_and_store(&mut self) -> (&mut Linker<Internal>, &mut Store<Internal>) {
52 (&mut self.linker, &mut self.store)
53 }
54}
55
56const EXPORT_MODULE_NAME: &str = "env";
57
58fn profiling_strategy() -> ProfilingStrategy {
59 match std::env::var("EXTISM_PROFILE").as_deref() {
60 Ok("perf") => ProfilingStrategy::PerfMap,
61 Ok(x) => {
62 log::warn!("Invalid value for EXTISM_PROFILE: {x}");
63 ProfilingStrategy::None
64 }
65 Err(_) => ProfilingStrategy::None,
66 }
67}
68
69fn calculate_available_memory(
70 available_pages: &mut Option<u32>,
71 modules: &BTreeMap<String, Module>,
72) -> anyhow::Result<()> {
73 let available_pages = match available_pages {
74 Some(p) => p,
75 None => return Ok(()),
76 };
77
78 let max_pages = *available_pages;
79 let mut fail_memory_check = false;
80 let mut total_memory_needed = 0;
81 for (name, module) in modules.iter() {
82 if name == "env" {
83 continue;
84 }
85 let mut memories = 0;
86 for export in module.exports() {
87 if let Some(memory) = export.ty().memory() {
88 memories += 1;
89 let memory_max = memory.maximum();
90 match memory_max {
91 None => anyhow::bail!("Unbounded memory in module {name}, when `memory.max_pages` is set in the manifest all modules \
92 must have a maximum bound set on an exported memory"),
93 Some(m) => {
94 total_memory_needed += m;
95 if !fail_memory_check {
96 continue;
97 }
98
99 *available_pages = available_pages.saturating_sub(m as u32);
100 if *available_pages == 0 {
101 fail_memory_check = true;
102 }
103 }
104 }
105 }
106 }
107
108 if memories == 0 {
109 anyhow::bail!("No memory exported from module {name}, when `memory.max_pages` is set in the manifest all modules must \
110 have a maximum bound set on an exported memory");
111 }
112 }
113
114 if fail_memory_check {
115 anyhow::bail!("Not enough memory configured to run the provided plugin, `memory.max_pages` is set to {max_pages} in the manifest \
116 but {total_memory_needed} pages are needed by the plugin");
117 }
118
119 Ok(())
120}
121
122impl Plugin {
123 pub fn new<'a>(
125 wasm: impl AsRef<[u8]>,
126 imports: impl IntoIterator<Item = &'a Function>,
127 with_wasi: bool,
128 ) -> Result<Plugin, Error> {
129 let engine = Engine::new(
132 Config::new()
133 .epoch_interruption(true)
134 .debug_info(std::env::var("EXTISM_DEBUG").is_ok())
135 .profiler(profiling_strategy())
136 .cache_config_load_default()?,
137 )?;
138 let mut imports = imports.into_iter();
139 let (manifest, modules) = Manifest::new(&engine, wasm.as_ref())?;
140
141 let mut available_pages = manifest.as_ref().memory.max_pages;
145 calculate_available_memory(&mut available_pages, &modules)?;
146 log::trace!("Available pages: {available_pages:?}");
147
148 let mut store = Store::new(
149 &engine,
150 Internal::new(manifest, with_wasi, available_pages)?,
151 );
152 store.epoch_deadline_callback(|_internal| Ok(wasmtime::UpdateDeadline::Continue(1)));
153
154 if available_pages.is_some() {
155 store.limiter(|internal| internal.memory_limiter.as_mut().unwrap());
156 }
157 let mut linker = Linker::new(&engine);
158 linker.allow_shadowing(true);
159
160 if with_wasi {
162 wasmtime_wasi::add_to_linker(&mut linker, |x: &mut Internal| {
163 &mut x.wasi.as_mut().unwrap().ctx
164 })?;
165
166 #[cfg(feature = "nn")]
167 wasmtime_wasi_nn::add_to_linker(&mut linker, |x: &mut Internal| {
168 &mut x.wasi.as_mut().unwrap().nn
169 })?;
170 }
171 let (main_name, main) = modules.get("main").map(|x| ("main", x)).unwrap_or_else(|| {
173 let entry = modules.iter().last().unwrap();
174 (entry.0.as_str(), entry.1)
175 });
176
177 macro_rules! define_funcs {
179 ($m:expr, { $($name:ident($($args:expr),*) $(-> $($r:expr),*)?);* $(;)?}) => {
180 match $m {
181 $(
182 concat!("extism_", stringify!($name)) => {
183 let t = FuncType::new([$($args),*], [$($($r),*)?]);
184 linker.func_new(EXPORT_MODULE_NAME, concat!("extism_", stringify!($name)), t, pdk::$name)?;
185 continue
186 }
187 )*
188 _ => ()
189 }
190 };
191 }
192
193 for (name, module) in modules.iter() {
195 if name != main_name {
196 linker.module(&mut store, name, module)?;
197 }
198 for import in module.imports() {
199 let module_name = import.module();
200 let name = import.name();
201 use wasmtime::ValType::*;
202
203 if module_name == EXPORT_MODULE_NAME {
204 define_funcs!(name, {
205 config_get(I64) -> I64;
206 var_get(I64) -> I64;
207 var_set(I64, I64);
208 http_request(I64, I64) -> I64;
209 http_status_code() -> I32;
210 log_warn(I64);
211 log_info(I64);
212 log_debug(I64);
213 log_error(I64);
214 });
215 }
216 }
217 }
218
219 for f in &mut imports {
220 let name = f.name().to_string();
221 let ns = f.namespace().unwrap_or(EXPORT_MODULE_NAME);
222 linker.func_new(ns, &name, f.ty().clone(), unsafe {
223 &*std::sync::Arc::as_ptr(&f.f)
224 })?;
225 }
226
227 let instance_pre = linker.instantiate_pre(&main)?;
228 let timer_id = uuid::Uuid::new_v4();
229 let mut plugin = Plugin {
230 modules,
231 linker,
232 instance: None,
233 instance_pre,
234 store,
235 runtime: None,
236 timer_id,
237 cancel_handle: sdk::ExtismCancelHandle {
238 id: timer_id,
239 epoch_timer_tx: None,
240 },
241 instantiations: 0,
242 };
243
244 plugin.internal_mut().store = &mut plugin.store;
245 plugin.internal_mut().linker = &mut plugin.linker;
246 Ok(plugin)
247 }
248
249 pub(crate) fn reset_store(&mut self) -> Result<(), Error> {
250 self.instance = None;
251 if self.instantiations > 5 {
252 let (main_name, main) = self
253 .modules
254 .get("main")
255 .map(|x| ("main", x))
256 .unwrap_or_else(|| {
257 let entry = self.modules.iter().last().unwrap();
258 (entry.0.as_str(), entry.1)
259 });
260
261 let engine = self.store.engine().clone();
262 let internal = self.internal();
263 self.store = Store::new(
264 &engine,
265 Internal::new(
266 internal.manifest.clone(),
267 internal.wasi.is_some(),
268 internal.available_pages,
269 )?,
270 );
271 self.store
272 .epoch_deadline_callback(|_internal| Ok(UpdateDeadline::Continue(1)));
273
274 if self.internal().available_pages.is_some() {
275 self.store
276 .limiter(|internal| internal.memory_limiter.as_mut().unwrap());
277 }
278
279 for (name, module) in self.modules.iter() {
280 if name != main_name {
281 self.linker.module(&mut self.store, name, module)?;
282 }
283 }
284 self.instantiations = 0;
285 self.instance_pre = self.linker.instantiate_pre(&main)?;
286
287 let store = &mut self.store as *mut _;
288 let linker = &mut self.linker as *mut _;
289 let internal = self.internal_mut();
290 internal.store = store;
291 internal.linker = linker;
292 }
293
294 Ok(())
295 }
296
297 pub(crate) fn instantiate(&mut self) -> Result<(), Error> {
298 self.instance = Some(self.instance_pre.instantiate(&mut self.store)?);
299 self.instantiations += 1;
300 if let Some(limiter) = &mut self.internal_mut().memory_limiter {
301 limiter.reset();
302 }
303 self.detect_runtime();
304 self.initialize_runtime()?;
305 Ok(())
306 }
307
308 pub fn get_func(&mut self, function: impl AsRef<str>) -> Option<Func> {
310 if let None = &self.instance {
311 if let Err(e) = self.instantiate() {
312 error!("Unable to instantiate: {e}");
313 return None;
314 }
315 }
316
317 if let Some(instance) = &mut self.instance {
318 instance.get_func(&mut self.store, function.as_ref())
319 } else {
320 None
321 }
322 }
323
324 pub(crate) fn set_input(&mut self, input: *const u8, mut len: usize) -> Result<(), Error> {
326 if input.is_null() {
327 len = 0;
328 }
329
330 {
331 let store = &mut self.store as *mut _;
332 let linker = &mut self.linker as *mut _;
333 let internal = self.internal_mut();
334 internal.store = store;
335 internal.linker = linker;
336 }
337
338 if len > 0 {
339 let bytes = unsafe { std::slice::from_raw_parts(input, len) };
340 trace!("Input size: {}", bytes.len());
341
342 if let Some(f) = self.linker.get(&mut self.store, "env", "extism_reset") {
343 f.into_func().unwrap().call(&mut self.store, &[], &mut [])?;
344 } else {
345 error!("Call to extism_reset failed");
346 }
347
348 let offs = self.memory_alloc_bytes(bytes)?;
349
350 if let Some(f) = self.linker.get(&mut self.store, "env", "extism_input_set") {
351 f.into_func().unwrap().call(
352 &mut self.store,
353 &[Val::I64(offs as i64), Val::I64(len as i64)],
354 &mut [],
355 )?;
356 }
357 }
358
359 Ok(())
360 }
361
362 pub fn has_wasi(&self) -> bool {
364 self.internal().wasi.is_some()
365 }
366
367 fn detect_runtime(&mut self) {
368 if let Some(init) = self.get_func("hs_init") {
372 let reactor_init = if let Some(init) = self.get_func("_initialize") {
373 if init.typed::<(), ()>(&self.store()).is_err() {
374 trace!(
375 "_initialize function found with type {:?}",
376 init.ty(self.store())
377 );
378 None
379 } else {
380 trace!("WASI reactor module detected");
381 Some(init)
382 }
383 } else {
384 None
385 };
386 self.runtime = Some(Runtime::Haskell { init, reactor_init });
387 return;
388 }
389
390 let init = if let Some(init) = self.get_func("__wasm_call_ctors") {
393 if init.typed::<(), ()>(&self.store()).is_err() {
394 trace!(
395 "__wasm_call_ctors function found with type {:?}",
396 init.ty(self.store())
397 );
398 return;
399 }
400 trace!("WASI runtime detected");
401 init
402 } else if let Some(init) = self.get_func("_initialize") {
403 if init.typed::<(), ()>(&self.store()).is_err() {
404 trace!(
405 "_initialize function found with type {:?}",
406 init.ty(self.store())
407 );
408 return;
409 }
410 trace!("Reactor module detected");
411 init
412 } else {
413 return;
414 };
415
416 self.runtime = Some(Runtime::Wasi { init });
417
418 trace!("No runtime detected");
419 }
420
421 pub(crate) fn initialize_runtime(&mut self) -> Result<(), Error> {
422 let mut store = &mut self.store;
423 if let Some(runtime) = &self.runtime {
424 trace!("Plugin::initialize_runtime");
425 match runtime {
426 Runtime::Haskell { init, reactor_init } => {
427 if let Some(reactor_init) = reactor_init {
428 reactor_init.call(&mut store, &[], &mut [])?;
429 }
430 let mut results = vec![Val::null(); init.ty(&store).results().len()];
431 init.call(
432 &mut store,
433 &[Val::I32(0), Val::I32(0)],
434 results.as_mut_slice(),
435 )?;
436 debug!("Initialized Haskell language runtime");
437 }
438 Runtime::Wasi { init } => {
439 init.call(&mut store, &[], &mut [])?;
440 debug!("Initialied WASI runtime");
441 }
442 }
443 }
444
445 Ok(())
446 }
447
448 pub(crate) fn start_timer(
451 &mut self,
452 tx: &std::sync::mpsc::SyncSender<TimerAction>,
453 ) -> Result<(), Error> {
454 let duration = self
455 .internal()
456 .manifest
457 .as_ref()
458 .timeout_ms
459 .map(std::time::Duration::from_millis);
460 self.cancel_handle.epoch_timer_tx = Some(tx.clone());
461 self.store_mut().set_epoch_deadline(1);
462 self.store
463 .epoch_deadline_callback(|_internal| Err(Error::msg("timeout")));
464 let engine: Engine = self.store().engine().clone();
465 tx.send(TimerAction::Start {
466 id: self.timer_id,
467 duration,
468 engine,
469 })?;
470 Ok(())
471 }
472
473 pub(crate) fn stop_timer(&mut self) -> Result<(), Error> {
475 if let Some(tx) = &self.cancel_handle.epoch_timer_tx {
476 tx.send(TimerAction::Stop { id: self.timer_id })?;
477 }
478 self.store
479 .epoch_deadline_callback(|_internal| Ok(wasmtime::UpdateDeadline::Continue(1)));
480 Ok(())
481 }
482}
483
484#[derive(Clone)]
486pub(crate) enum Runtime {
487 Haskell {
488 init: Func,
489 reactor_init: Option<Func>,
490 },
491 Wasi {
492 init: Func,
493 },
494}