1 //! Evaluate an exported Wasm function using the wasmi interpreter.
2 
3 use crate::generators::{DiffValue, DiffValueType, ModuleConfig};
4 use crate::oracles::engine::{DiffEngine, DiffInstance};
5 use anyhow::{bail, Context, Error, Result};
6 use wasmtime::Trap;
7 
8 /// A wrapper for `wasmi` as a [`DiffEngine`].
9 pub struct WasmiEngine;
10 
11 impl WasmiEngine {
12     /// Build a new [`WasmiEngine`] but only if the configuration does not rely
13     /// on features that `wasmi` does not support.
14     pub fn new(config: &ModuleConfig) -> Result<Self> {
15         if config.config.reference_types_enabled {
16             bail!("wasmi does not support reference types")
17         }
18         if config.config.simd_enabled {
19             bail!("wasmi does not support SIMD")
20         }
21         if config.config.multi_value_enabled {
22             bail!("wasmi does not support multi-value")
23         }
24         if config.config.saturating_float_to_int_enabled {
25             bail!("wasmi does not support saturating float-to-int conversions")
26         }
27         if config.config.sign_extension_enabled {
28             bail!("wasmi does not support sign-extension")
29         }
30         if config.config.memory64_enabled {
31             bail!("wasmi does not support memory64");
32         }
33         if config.config.bulk_memory_enabled {
34             bail!("wasmi does not support bulk memory");
35         }
36         if config.config.threads_enabled {
37             bail!("wasmi does not support threads");
38         }
39         Ok(Self)
40     }
41 }
42 
43 impl DiffEngine for WasmiEngine {
44     fn name(&self) -> &'static str {
45         "wasmi"
46     }
47 
48     fn instantiate(&mut self, wasm: &[u8]) -> Result<Box<dyn DiffInstance>> {
49         let module = wasmi::Module::from_buffer(wasm).context("unable to validate Wasm module")?;
50         let instance = wasmi::ModuleInstance::new(&module, &wasmi::ImportsBuilder::default())
51             .context("unable to instantiate module in wasmi")?;
52         let instance = instance.assert_no_start();
53         Ok(Box::new(WasmiInstance { module, instance }))
54     }
55 
56     fn assert_error_match(&self, trap: &Trap, err: Error) {
57         // TODO: should implement this for `wasmi`
58         drop((trap, err));
59     }
60 }
61 
62 /// A wrapper for `wasmi` Wasm instances.
63 struct WasmiInstance {
64     #[allow(dead_code)] // reason = "the module must live as long as its reference"
65     module: wasmi::Module,
66     instance: wasmi::ModuleRef,
67 }
68 
69 impl DiffInstance for WasmiInstance {
70     fn name(&self) -> &'static str {
71         "wasmi"
72     }
73 
74     fn evaluate(
75         &mut self,
76         function_name: &str,
77         arguments: &[DiffValue],
78         _results: &[DiffValueType],
79     ) -> Result<Option<Vec<DiffValue>>> {
80         let arguments: Vec<_> = arguments.iter().map(wasmi::RuntimeValue::from).collect();
81         let export = self
82             .instance
83             .export_by_name(function_name)
84             .context(format!(
85                 "unable to find function '{}' in wasmi instance",
86                 function_name
87             ))?;
88         let function = export.as_func().context("wasmi export is not a function")?;
89         let result = wasmi::FuncInstance::invoke(&function, &arguments, &mut wasmi::NopExternals)
90             .context("failed while invoking function in wasmi")?;
91         Ok(Some(if let Some(result) = result {
92             vec![result.into()]
93         } else {
94             vec![]
95         }))
96     }
97 
98     fn get_global(&mut self, name: &str, _ty: DiffValueType) -> Option<DiffValue> {
99         match self.instance.export_by_name(name) {
100             Some(wasmi::ExternVal::Global(g)) => Some(g.get().into()),
101             _ => unreachable!(),
102         }
103     }
104 
105     fn get_memory(&mut self, name: &str, shared: bool) -> Option<Vec<u8>> {
106         assert!(!shared);
107         match self.instance.export_by_name(name) {
108             Some(wasmi::ExternVal::Memory(m)) => {
109                 // `wasmi` memory may be stored non-contiguously; copy
110                 // it out to a contiguous chunk.
111                 let mut buffer: Vec<u8> = vec![0; m.current_size().0 * 65536];
112                 m.get_into(0, &mut buffer[..])
113                     .expect("can access wasmi memory");
114                 Some(buffer)
115             }
116             _ => unreachable!(),
117         }
118     }
119 }
120 
121 impl From<&DiffValue> for wasmi::RuntimeValue {
122     fn from(v: &DiffValue) -> Self {
123         use wasmi::RuntimeValue::*;
124         match *v {
125             DiffValue::I32(n) => I32(n),
126             DiffValue::I64(n) => I64(n),
127             DiffValue::F32(n) => F32(wasmi::nan_preserving_float::F32::from_bits(n)),
128             DiffValue::F64(n) => F64(wasmi::nan_preserving_float::F64::from_bits(n)),
129             DiffValue::V128(_) | DiffValue::FuncRef { .. } | DiffValue::ExternRef { .. } => {
130                 unimplemented!()
131             }
132         }
133     }
134 }
135 
136 impl Into<DiffValue> for wasmi::RuntimeValue {
137     fn into(self) -> DiffValue {
138         use wasmi::RuntimeValue::*;
139         match self {
140             I32(n) => DiffValue::I32(n),
141             I64(n) => DiffValue::I64(n),
142             F32(n) => DiffValue::F32(n.to_bits()),
143             F64(n) => DiffValue::F64(n.to_bits()),
144         }
145     }
146 }
147 
148 #[test]
149 fn smoke() {
150     crate::oracles::engine::smoke_test_engine(|config| WasmiEngine::new(&config.module_config))
151 }
152