1extern crate radix_wasmi_arena as wasmi_arena;
3
4pub mod bytecode;
5mod cache;
6pub mod code_map;
7mod config;
8pub mod executor;
9mod func_args;
10mod func_builder;
11mod func_types;
12mod resumable;
13pub mod stack;
14mod traits;
15
16#[cfg(test)]
17mod tests;
18
19pub use self::{
20 bytecode::DropKeep,
21 code_map::FuncBody,
22 config::Config,
23 func_builder::{FuncBuilder, FunctionBuilderAllocations, Instr, TranslationError},
24 resumable::{ResumableCall, ResumableInvocation, TypedResumableCall, TypedResumableInvocation},
25 stack::StackLimits,
26 traits::{CallParams, CallResults},
27};
28use self::{
29 bytecode::Instruction,
30 cache::InstanceCache,
31 code_map::CodeMap,
32 executor::execute_frame,
33 func_types::FuncTypeRegistry,
34 resumable::ResumableCallBase,
35 stack::{FuncFrame, Stack, ValueStack},
36};
37pub(crate) use self::{
38 func_args::{FuncParams, FuncResults},
39 func_types::DedupFuncType,
40};
41use super::{func::FuncEntityInternal, AsContextMut, Func};
42use crate::{
43 core::{Trap, TrapCode},
44 FuncType,
45};
46use alloc::{sync::Arc, vec::Vec};
47use core::sync::atomic::{AtomicU32, Ordering};
48use spin::{Mutex, RwLock};
49use wasmi_arena::{ArenaIndex, GuardedEntity};
50
51#[derive(Debug, Copy, Clone)]
53pub enum CallOutcome {
54 Return,
56 NestedCall(Func),
58}
59
60#[derive(Debug, Copy, Clone, PartialEq, Eq)]
66pub struct EngineIdx(u32);
67
68impl ArenaIndex for EngineIdx {
69 fn into_usize(self) -> usize {
70 self.0 as _
71 }
72
73 fn from_usize(value: usize) -> Self {
74 let value = value.try_into().unwrap_or_else(|error| {
75 panic!("index {value} is out of bounds as engine index: {error}")
76 });
77 Self(value)
78 }
79}
80
81impl EngineIdx {
82 fn new() -> Self {
84 static CURRENT_STORE_IDX: AtomicU32 = AtomicU32::new(0);
86 let next_idx = CURRENT_STORE_IDX.fetch_add(1, Ordering::AcqRel);
87 Self(next_idx)
88 }
89}
90
91type Guarded<Idx> = GuardedEntity<EngineIdx, Idx>;
93
94#[derive(Debug, Clone)]
102pub struct Engine {
103 inner: Arc<EngineInner>,
104}
105
106impl Default for Engine {
107 fn default() -> Self {
108 Self::new(&Config::default())
109 }
110}
111
112impl Engine {
113 pub fn new(config: &Config) -> Self {
119 Self {
120 inner: Arc::new(EngineInner::new(config)),
121 }
122 }
123
124 pub fn config(&self) -> Config {
126 self.inner.config()
127 }
128
129 pub(super) fn alloc_func_type(&self, func_type: FuncType) -> DedupFuncType {
131 self.inner.alloc_func_type(func_type)
132 }
133
134 pub(super) fn resolve_func_type<F, R>(&self, func_type: DedupFuncType, f: F) -> R
141 where
142 F: FnOnce(&FuncType) -> R,
143 {
144 self.inner.resolve_func_type(func_type, f)
145 }
146
147 pub(super) fn alloc_func_body<I>(
151 &self,
152 len_locals: usize,
153 max_stack_height: usize,
154 insts: I,
155 ) -> FuncBody
156 where
157 I: IntoIterator<Item = Instruction>,
158 I::IntoIter: ExactSizeIterator,
159 {
160 self.inner
161 .alloc_func_body(len_locals, max_stack_height, insts)
162 }
163
164 #[cfg(test)]
176 pub(crate) fn resolve_inst(&self, func_body: FuncBody, index: usize) -> Option<Instruction> {
177 self.inner.resolve_inst(func_body, index)
178 }
179
180 pub(crate) fn execute_func<Results>(
200 &self,
201 ctx: impl AsContextMut,
202 func: Func,
203 params: impl CallParams,
204 results: Results,
205 ) -> Result<<Results as CallResults>::Results, Trap>
206 where
207 Results: CallResults,
208 {
209 self.inner.execute_func(ctx, func, params, results)
210 }
211
212 pub(crate) fn execute_func_resumable<Results>(
235 &self,
236 ctx: impl AsContextMut,
237 func: Func,
238 params: impl CallParams,
239 results: Results,
240 ) -> Result<ResumableCallBase<<Results as CallResults>::Results>, Trap>
241 where
242 Results: CallResults,
243 {
244 self.inner
245 .execute_func_resumable(ctx, func, params, results)
246 }
247
248 pub(crate) fn resume_func<Results>(
271 &self,
272 ctx: impl AsContextMut,
273 invocation: ResumableInvocation,
274 params: impl CallParams,
275 results: Results,
276 ) -> Result<ResumableCallBase<<Results as CallResults>::Results>, Trap>
277 where
278 Results: CallResults,
279 {
280 self.inner.resume_func(ctx, invocation, params, results)
281 }
282
283 pub(crate) fn recycle_stack(&self, stack: Stack) {
285 self.inner.recycle_stack(stack)
286 }
287}
288
289#[derive(Debug)]
291pub struct EngineInner {
292 res: RwLock<EngineResources>,
294 stacks: Mutex<EngineStacks>,
300}
301
302#[derive(Debug)]
306pub struct EngineStacks {
307 stacks: Vec<Stack>,
309 limits: StackLimits,
311 keep: usize,
313}
314
315impl EngineStacks {
316 pub fn new(config: &Config) -> Self {
318 Self {
319 stacks: Vec::new(),
320 limits: config.stack_limits(),
321 keep: config.cached_stacks(),
322 }
323 }
324
325 pub fn reuse_or_new(&mut self) -> Stack {
327 match self.stacks.pop() {
328 Some(stack) => stack,
329 None => Stack::new(self.limits),
330 }
331 }
332
333 pub fn recycle(&mut self, stack: Stack) {
335 if !stack.is_empty() && self.stacks.len() < self.keep {
336 self.stacks.push(stack);
337 }
338 }
339}
340
341impl EngineInner {
342 fn new(config: &Config) -> Self {
344 Self {
345 res: RwLock::new(EngineResources::new(config)),
346 stacks: Mutex::new(EngineStacks::new(config)),
347 }
348 }
349
350 fn config(&self) -> Config {
351 self.res.read().config
352 }
353
354 fn alloc_func_type(&self, func_type: FuncType) -> DedupFuncType {
355 self.res.write().func_types.alloc_func_type(func_type)
356 }
357
358 fn alloc_func_body<I>(&self, len_locals: usize, max_stack_height: usize, insts: I) -> FuncBody
359 where
360 I: IntoIterator<Item = Instruction>,
361 I::IntoIter: ExactSizeIterator,
362 {
363 self.res
364 .write()
365 .code_map
366 .alloc(len_locals, max_stack_height, insts)
367 }
368
369 fn resolve_func_type<F, R>(&self, func_type: DedupFuncType, f: F) -> R
370 where
371 F: FnOnce(&FuncType) -> R,
372 {
373 f(self.res.read().func_types.resolve_func_type(func_type))
374 }
375
376 #[cfg(test)]
377 fn resolve_inst(&self, func_body: FuncBody, index: usize) -> Option<Instruction> {
378 self.res
379 .read()
380 .code_map
381 .get_instr(func_body, index)
382 .copied()
383 }
384
385 fn execute_func<Results>(
386 &self,
387 ctx: impl AsContextMut,
388 func: Func,
389 params: impl CallParams,
390 results: Results,
391 ) -> Result<<Results as CallResults>::Results, Trap>
392 where
393 Results: CallResults,
394 {
395 let res = self.res.read();
396 let mut stack = self.stacks.lock().reuse_or_new();
397 let results = EngineExecutor::new(&res, &mut stack)
398 .execute_func(ctx, func, params, results)
399 .map_err(TaggedTrap::into_trap);
400 self.stacks.lock().recycle(stack);
401 results
402 }
403
404 fn execute_func_resumable<Results>(
405 &self,
406 mut ctx: impl AsContextMut,
407 func: Func,
408 params: impl CallParams,
409 results: Results,
410 ) -> Result<ResumableCallBase<<Results as CallResults>::Results>, Trap>
411 where
412 Results: CallResults,
413 {
414 let res = self.res.read();
415 let mut stack = self.stacks.lock().reuse_or_new();
416 let results = EngineExecutor::new(&res, &mut stack).execute_func(
417 ctx.as_context_mut(),
418 func,
419 params,
420 results,
421 );
422 match results {
423 Ok(results) => {
424 self.stacks.lock().recycle(stack);
425 Ok(ResumableCallBase::Finished(results))
426 }
427 Err(TaggedTrap::Wasm(trap)) => {
428 self.stacks.lock().recycle(stack);
429 Err(trap)
430 }
431 Err(TaggedTrap::Host {
432 host_func,
433 host_trap,
434 }) => Ok(ResumableCallBase::Resumable(ResumableInvocation::new(
435 ctx.as_context().store.engine().clone(),
436 func,
437 host_func,
438 host_trap,
439 stack,
440 ))),
441 }
442 }
443
444 pub(crate) fn resume_func<Results>(
445 &self,
446 ctx: impl AsContextMut,
447 mut invocation: ResumableInvocation,
448 params: impl CallParams,
449 results: Results,
450 ) -> Result<ResumableCallBase<<Results as CallResults>::Results>, Trap>
451 where
452 Results: CallResults,
453 {
454 let res = self.res.read();
455 let host_func = invocation.host_func();
456 let results = EngineExecutor::new(&res, &mut invocation.stack)
457 .resume_func(ctx, host_func, params, results);
458 match results {
459 Ok(results) => {
460 self.stacks.lock().recycle(invocation.take_stack());
461 Ok(ResumableCallBase::Finished(results))
462 }
463 Err(TaggedTrap::Wasm(trap)) => {
464 self.stacks.lock().recycle(invocation.take_stack());
465 Err(trap)
466 }
467 Err(TaggedTrap::Host {
468 host_func,
469 host_trap,
470 }) => {
471 invocation.update(host_func, host_trap);
472 Ok(ResumableCallBase::Resumable(invocation))
473 }
474 }
475 }
476
477 fn recycle_stack(&self, stack: Stack) {
478 self.stacks.lock().recycle(stack);
479 }
480}
481
482#[derive(Debug)]
486pub struct EngineResources {
487 config: Config,
489 code_map: CodeMap,
491 func_types: FuncTypeRegistry,
498}
499
500impl EngineResources {
501 fn new(config: &Config) -> Self {
503 let engine_idx = EngineIdx::new();
504 Self {
505 config: *config,
506 code_map: CodeMap::default(),
507 func_types: FuncTypeRegistry::new(engine_idx),
508 }
509 }
510}
511
512#[derive(Debug)]
514enum TaggedTrap {
515 Wasm(Trap),
517 Host { host_func: Func, host_trap: Trap },
519}
520
521impl TaggedTrap {
522 pub fn host(host_func: Func, host_trap: Trap) -> Self {
524 Self::Host {
525 host_func,
526 host_trap,
527 }
528 }
529
530 pub fn into_trap(self) -> Trap {
532 match self {
533 TaggedTrap::Wasm(trap) => trap,
534 TaggedTrap::Host { host_trap, .. } => host_trap,
535 }
536 }
537}
538
539impl From<Trap> for TaggedTrap {
540 fn from(trap: Trap) -> Self {
541 Self::Wasm(trap)
542 }
543}
544
545impl From<TrapCode> for TaggedTrap {
546 fn from(trap_code: TrapCode) -> Self {
547 Self::Wasm(trap_code.into())
548 }
549}
550
551#[derive(Debug)]
553pub struct EngineExecutor<'engine> {
554 res: &'engine EngineResources,
556 stack: &'engine mut Stack,
558}
559
560impl<'engine> EngineExecutor<'engine> {
561 fn new(res: &'engine EngineResources, stack: &'engine mut Stack) -> Self {
563 Self { res, stack }
564 }
565
566 fn execute_func<Results>(
576 &mut self,
577 mut ctx: impl AsContextMut,
578 func: Func,
579 params: impl CallParams,
580 results: Results,
581 ) -> Result<<Results as CallResults>::Results, TaggedTrap>
582 where
583 Results: CallResults,
584 {
585 self.initialize_args(params);
586 match func.as_internal(ctx.as_context()) {
587 FuncEntityInternal::Wasm(wasm_func) => {
588 let mut frame = self.stack.call_wasm_root(wasm_func, &self.res.code_map)?;
589 let mut cache = InstanceCache::from(frame.instance());
590 self.execute_wasm_func(ctx.as_context_mut(), &mut frame, &mut cache)?;
591 }
592 FuncEntityInternal::Host(host_func) => {
593 let host_func = host_func.clone();
594 self.stack
595 .call_host_root(ctx.as_context_mut(), host_func, &self.res.func_types)?;
596 }
597 };
598 let results = self.write_results_back(results);
599 Ok(results)
600 }
601
602 fn resume_func<Results>(
612 &mut self,
613 mut ctx: impl AsContextMut,
614 host_func: Func,
615 params: impl CallParams,
616 results: Results,
617 ) -> Result<<Results as CallResults>::Results, TaggedTrap>
618 where
619 Results: CallResults,
620 {
621 self.stack
622 .values
623 .drop(host_func.ty(ctx.as_context()).params().len());
624 self.stack.values.extend(params.call_params());
625 let mut frame = self
626 .stack
627 .pop_frame()
628 .expect("a frame must be on the call stack upon resumption");
629 let mut cache = InstanceCache::from(frame.instance());
630 self.execute_wasm_func(ctx.as_context_mut(), &mut frame, &mut cache)?;
631 let results = self.write_results_back(results);
632 Ok(results)
633 }
634
635 fn initialize_args(&mut self, params: impl CallParams) {
637 self.stack.clear();
638 self.stack.values.extend(params.call_params());
639 }
640
641 fn write_results_back<Results>(&mut self, results: Results) -> <Results as CallResults>::Results
651 where
652 Results: CallResults,
653 {
654 results.call_results(self.stack.values.drain())
655 }
656
657 fn execute_wasm_func(
663 &mut self,
664 mut ctx: impl AsContextMut,
665 frame: &mut FuncFrame,
666 cache: &mut InstanceCache,
667 ) -> Result<(), TaggedTrap> {
668 'outer: loop {
669 match self.execute_frame(ctx.as_context_mut(), frame, cache)? {
670 CallOutcome::Return => match self.stack.return_wasm() {
671 Some(caller) => {
672 *frame = caller;
673 continue 'outer;
674 }
675 None => return Ok(()),
676 },
677 CallOutcome::NestedCall(called_func) => {
678 match called_func.as_internal(ctx.as_context()) {
679 FuncEntityInternal::Wasm(wasm_func) => {
680 *frame = self.stack.call_wasm(frame, wasm_func, &self.res.code_map)?;
681 }
682 FuncEntityInternal::Host(host_func) => {
683 cache.reset_default_memory_bytes();
684 let host_func = host_func.clone();
685 self.stack
686 .call_host(
687 ctx.as_context_mut(),
688 frame,
689 host_func,
690 &self.res.func_types,
691 )
692 .or_else(|trap| {
693 self.stack.push_frame(*frame)?;
695 Err(TaggedTrap::host(called_func, trap))
696 })?;
697 }
698 }
699 }
700 }
701 }
702 }
703
704 #[inline(always)]
710 fn execute_frame(
711 &mut self,
712 mut ctx: impl AsContextMut,
713 frame: &mut FuncFrame,
714 cache: &mut InstanceCache,
715 ) -> Result<CallOutcome, Trap> {
716 #[inline]
721 #[cold]
722 fn make_trap(code: TrapCode) -> Trap {
723 code.into()
724 }
725
726 let value_stack = &mut self.stack.values;
727 execute_frame(
728 &mut ctx.as_context_mut().store.inner,
729 value_stack,
730 cache,
731 frame,
732 )
733 .map_err(make_trap)
734 }
735}