1 //! Evaluate an exported Wasm function using the wasmi interpreter.
2
3 use crate::generators::{Config, DiffValue, DiffValueType};
4 use crate::oracles::engine::{DiffEngine, DiffInstance};
5 use wasmtime::{Error, Result, Trap, error::Context as _};
6
7 /// A wrapper for `wasmi` as a [`DiffEngine`].
8 pub struct WasmiEngine {
9 engine: wasmi::Engine,
10 }
11
12 impl WasmiEngine {
new(config: &mut Config) -> Self13 pub(crate) fn new(config: &mut Config) -> Self {
14 let config = &mut config.module_config.config;
15 // Force generated Wasm modules to never have features that Wasmi doesn't support.
16 config.relaxed_simd_enabled = false;
17 config.threads_enabled = false;
18 config.exceptions_enabled = false;
19 config.gc_enabled = false;
20
21 let mut wasmi_config = wasmi::Config::default();
22 wasmi_config
23 .consume_fuel(false)
24 .floats(true)
25 .wasm_mutable_global(true)
26 .wasm_sign_extension(config.sign_extension_ops_enabled)
27 .wasm_saturating_float_to_int(config.saturating_float_to_int_enabled)
28 .wasm_multi_value(config.multi_value_enabled)
29 .wasm_bulk_memory(config.bulk_memory_enabled)
30 .wasm_reference_types(config.reference_types_enabled)
31 .wasm_tail_call(config.tail_call_enabled)
32 .wasm_multi_memory(config.max_memories > 1)
33 .wasm_extended_const(config.extended_const_enabled)
34 .wasm_custom_page_sizes(config.custom_page_sizes_enabled)
35 .wasm_memory64(config.memory64_enabled)
36 .wasm_simd(config.simd_enabled)
37 .wasm_wide_arithmetic(config.wide_arithmetic_enabled);
38 Self {
39 engine: wasmi::Engine::new(&wasmi_config),
40 }
41 }
42
trap_code(&self, err: &Error) -> Option<wasmi::TrapCode>43 fn trap_code(&self, err: &Error) -> Option<wasmi::TrapCode> {
44 let err = err.downcast_ref::<wasmi::Error>()?;
45 if let Some(code) = err.as_trap_code() {
46 return Some(code);
47 }
48
49 match err.kind() {
50 wasmi::errors::ErrorKind::Instantiation(
51 wasmi::errors::InstantiationError::ElementSegmentDoesNotFit { .. },
52 ) => Some(wasmi::TrapCode::TableOutOfBounds),
53 wasmi::errors::ErrorKind::Memory(wasmi::errors::MemoryError::OutOfBoundsAccess) => {
54 Some(wasmi::TrapCode::MemoryOutOfBounds)
55 }
56 wasmi::errors::ErrorKind::Table(wasmi::errors::TableError::CopyOutOfBounds) => {
57 Some(wasmi::TrapCode::TableOutOfBounds)
58 }
59 _ => {
60 log::trace!("unknown wasmi error: {:?}", err.kind());
61 None
62 }
63 }
64 }
65 }
66
67 impl DiffEngine for WasmiEngine {
name(&self) -> &'static str68 fn name(&self) -> &'static str {
69 "wasmi"
70 }
71
instantiate(&mut self, wasm: &[u8]) -> Result<Box<dyn DiffInstance>>72 fn instantiate(&mut self, wasm: &[u8]) -> Result<Box<dyn DiffInstance>> {
73 let module =
74 wasmi::Module::new(&self.engine, wasm).context("unable to validate Wasm module")?;
75 let mut store = wasmi::Store::new(&self.engine, ());
76 let instance = wasmi::Linker::<()>::new(&self.engine)
77 .instantiate_and_start(&mut store, &module)
78 .context("unable to instantiate module in wasmi")?;
79 Ok(Box::new(WasmiInstance { store, instance }))
80 }
81
assert_error_match(&self, lhs: &Error, rhs: &Trap)82 fn assert_error_match(&self, lhs: &Error, rhs: &Trap) {
83 match self.trap_code(lhs) {
84 Some(code) => assert_eq!(wasmi_to_wasmtime_trap_code(code), *rhs),
85 None => panic!("unexpected wasmi error {lhs:?}"),
86 }
87 }
88
is_non_deterministic_error(&self, err: &Error) -> bool89 fn is_non_deterministic_error(&self, err: &Error) -> bool {
90 matches!(self.trap_code(err), Some(wasmi::TrapCode::StackOverflow))
91 }
92 }
93
94 /// Converts `wasmi` trap code to `wasmtime` trap code.
wasmi_to_wasmtime_trap_code(trap: wasmi::TrapCode) -> Trap95 fn wasmi_to_wasmtime_trap_code(trap: wasmi::TrapCode) -> Trap {
96 use wasmi::TrapCode;
97 match trap {
98 TrapCode::UnreachableCodeReached => Trap::UnreachableCodeReached,
99 TrapCode::MemoryOutOfBounds => Trap::MemoryOutOfBounds,
100 TrapCode::TableOutOfBounds => Trap::TableOutOfBounds,
101 TrapCode::IndirectCallToNull => Trap::IndirectCallToNull,
102 TrapCode::IntegerDivisionByZero => Trap::IntegerDivisionByZero,
103 TrapCode::IntegerOverflow => Trap::IntegerOverflow,
104 TrapCode::BadConversionToInteger => Trap::BadConversionToInteger,
105 TrapCode::StackOverflow => Trap::StackOverflow,
106 TrapCode::BadSignature => Trap::BadSignature,
107 TrapCode::OutOfFuel => unimplemented!("built-in fuel metering is unused"),
108 TrapCode::GrowthOperationLimited => unimplemented!("resource limiter is unused"),
109 }
110 }
111
112 /// A wrapper for `wasmi` Wasm instances.
113 struct WasmiInstance {
114 store: wasmi::Store<()>,
115 instance: wasmi::Instance,
116 }
117
118 impl DiffInstance for WasmiInstance {
name(&self) -> &'static str119 fn name(&self) -> &'static str {
120 "wasmi"
121 }
122
evaluate( &mut self, function_name: &str, arguments: &[DiffValue], result_tys: &[DiffValueType], ) -> Result<Option<Vec<DiffValue>>>123 fn evaluate(
124 &mut self,
125 function_name: &str,
126 arguments: &[DiffValue],
127 result_tys: &[DiffValueType],
128 ) -> Result<Option<Vec<DiffValue>>> {
129 let function = self
130 .instance
131 .get_export(&self.store, function_name)
132 .and_then(wasmi::Extern::into_func)
133 .unwrap();
134 let arguments: Vec<_> = arguments.iter().map(|x| x.into()).collect();
135 let mut results = vec![wasmi::Val::I32(0); result_tys.len()];
136 function
137 .call(&mut self.store, &arguments, &mut results)
138 .context("wasmi function trap")?;
139 Ok(Some(results.into_iter().map(Into::into).collect()))
140 }
141
get_global(&mut self, name: &str, _ty: DiffValueType) -> Option<DiffValue>142 fn get_global(&mut self, name: &str, _ty: DiffValueType) -> Option<DiffValue> {
143 Some(
144 self.instance
145 .get_export(&self.store, name)
146 .unwrap()
147 .into_global()
148 .unwrap()
149 .get(&self.store)
150 .into(),
151 )
152 }
153
get_memory(&mut self, name: &str, shared: bool) -> Option<Vec<u8>>154 fn get_memory(&mut self, name: &str, shared: bool) -> Option<Vec<u8>> {
155 assert!(!shared);
156 Some(
157 self.instance
158 .get_export(&self.store, name)
159 .unwrap()
160 .into_memory()
161 .unwrap()
162 .data(&self.store)
163 .to_vec(),
164 )
165 }
166 }
167
168 impl From<&DiffValue> for wasmi::Val {
from(v: &DiffValue) -> Self169 fn from(v: &DiffValue) -> Self {
170 use wasmi::Val as WasmiValue;
171 match *v {
172 DiffValue::I32(n) => WasmiValue::I32(n),
173 DiffValue::I64(n) => WasmiValue::I64(n),
174 DiffValue::F32(n) => WasmiValue::F32(wasmi::F32::from_bits(n)),
175 DiffValue::F64(n) => WasmiValue::F64(wasmi::F64::from_bits(n)),
176 DiffValue::V128(n) => WasmiValue::V128(wasmi::V128::from(n)),
177 DiffValue::FuncRef { null } => {
178 assert!(null);
179 WasmiValue::default(wasmi::ValType::FuncRef)
180 }
181 DiffValue::ExternRef { null } => {
182 assert!(null);
183 WasmiValue::default(wasmi::ValType::ExternRef)
184 }
185 DiffValue::AnyRef { .. } => unimplemented!(),
186 DiffValue::ExnRef { .. } => unimplemented!(),
187 DiffValue::ContRef { .. } => unimplemented!(),
188 }
189 }
190 }
191
192 impl From<wasmi::Val> for DiffValue {
from(value: wasmi::Val) -> Self193 fn from(value: wasmi::Val) -> Self {
194 use wasmi::Val as WasmiValue;
195 match value {
196 WasmiValue::I32(n) => DiffValue::I32(n),
197 WasmiValue::I64(n) => DiffValue::I64(n),
198 WasmiValue::F32(n) => DiffValue::F32(n.to_bits()),
199 WasmiValue::F64(n) => DiffValue::F64(n.to_bits()),
200 WasmiValue::V128(n) => DiffValue::V128(n.as_u128()),
201 WasmiValue::FuncRef(f) => DiffValue::FuncRef { null: f.is_null() },
202 WasmiValue::ExternRef(e) => DiffValue::ExternRef { null: e.is_null() },
203 }
204 }
205 }
206
207 #[cfg(test)]
208 mod tests {
209 use super::*;
210
211 #[test]
smoke()212 fn smoke() {
213 crate::oracles::engine::smoke_test_engine(|_, config| Ok(WasmiEngine::new(config)))
214 }
215 }
216