1 //! Evaluate an exported Wasm function using the WebAssembly specification
2 //! reference interpreter.
3
4 use crate::generators::{Config, DiffValue, DiffValueType};
5 use crate::oracles::engine::{DiffEngine, DiffInstance};
6 use wasm_spec_interpreter::SpecValue;
7 use wasmtime::{Error, Result, Trap, format_err};
8
9 /// A wrapper for `wasm-spec-interpreter` as a [`DiffEngine`].
10 pub struct SpecInterpreter;
11
12 impl SpecInterpreter {
new(config: &mut Config) -> Self13 pub(crate) fn new(config: &mut Config) -> Self {
14 let config = &mut config.module_config.config;
15
16 config.min_memories = config.min_memories.min(1);
17 config.max_memories = config.max_memories.min(1);
18 config.min_tables = config.min_tables.min(1);
19 config.max_tables = config.max_tables.min(1);
20
21 config.memory64_enabled = false;
22 config.threads_enabled = false;
23 config.bulk_memory_enabled = false;
24 config.reference_types_enabled = false;
25 config.tail_call_enabled = false;
26 config.relaxed_simd_enabled = false;
27 config.custom_page_sizes_enabled = false;
28 config.wide_arithmetic_enabled = false;
29 config.extended_const_enabled = false;
30 config.exceptions_enabled = false;
31
32 Self
33 }
34 }
35
36 impl DiffEngine for SpecInterpreter {
name(&self) -> &'static str37 fn name(&self) -> &'static str {
38 "spec"
39 }
40
instantiate(&mut self, wasm: &[u8]) -> Result<Box<dyn DiffInstance>>41 fn instantiate(&mut self, wasm: &[u8]) -> Result<Box<dyn DiffInstance>> {
42 let instance = wasm_spec_interpreter::instantiate(wasm)
43 .map_err(|e| format_err!("failed to instantiate in spec interpreter: {e}"))?;
44 Ok(Box::new(SpecInstance { instance }))
45 }
46
assert_error_match(&self, err: &Error, trap: &Trap)47 fn assert_error_match(&self, err: &Error, trap: &Trap) {
48 // TODO: implement this for the spec interpreter
49 let _ = (trap, err);
50 }
51
is_non_deterministic_error(&self, err: &Error) -> bool52 fn is_non_deterministic_error(&self, err: &Error) -> bool {
53 err.to_string().contains("(Isabelle) call stack exhausted")
54 }
55 }
56
57 struct SpecInstance {
58 instance: wasm_spec_interpreter::SpecInstance,
59 }
60
61 impl DiffInstance for SpecInstance {
name(&self) -> &'static str62 fn name(&self) -> &'static str {
63 "spec"
64 }
65
evaluate( &mut self, function_name: &str, arguments: &[DiffValue], _results: &[DiffValueType], ) -> Result<Option<Vec<DiffValue>>>66 fn evaluate(
67 &mut self,
68 function_name: &str,
69 arguments: &[DiffValue],
70 _results: &[DiffValueType],
71 ) -> Result<Option<Vec<DiffValue>>> {
72 let arguments = arguments.iter().map(SpecValue::from).collect();
73 match wasm_spec_interpreter::interpret(&self.instance, function_name, Some(arguments)) {
74 Ok(results) => Ok(Some(results.into_iter().map(SpecValue::into).collect())),
75 Err(err) => Err(format_err!(err)),
76 }
77 }
78
get_global(&mut self, name: &str, _ty: DiffValueType) -> Option<DiffValue>79 fn get_global(&mut self, name: &str, _ty: DiffValueType) -> Option<DiffValue> {
80 use wasm_spec_interpreter::{SpecExport::Global, export};
81 if let Ok(Global(g)) = export(&self.instance, name) {
82 Some(g.into())
83 } else {
84 panic!("expected an exported global value at name `{name}`")
85 }
86 }
87
get_memory(&mut self, name: &str, _shared: bool) -> Option<Vec<u8>>88 fn get_memory(&mut self, name: &str, _shared: bool) -> Option<Vec<u8>> {
89 use wasm_spec_interpreter::{SpecExport::Memory, export};
90 if let Ok(Memory(m)) = export(&self.instance, name) {
91 Some(m)
92 } else {
93 panic!("expected an exported memory at name `{name}`")
94 }
95 }
96 }
97
98 impl From<&DiffValue> for SpecValue {
from(v: &DiffValue) -> Self99 fn from(v: &DiffValue) -> Self {
100 match *v {
101 DiffValue::I32(n) => SpecValue::I32(n),
102 DiffValue::I64(n) => SpecValue::I64(n),
103 DiffValue::F32(n) => SpecValue::F32(n as i32),
104 DiffValue::F64(n) => SpecValue::F64(n as i64),
105 DiffValue::V128(n) => SpecValue::V128(n.to_le_bytes().to_vec()),
106 DiffValue::FuncRef { .. }
107 | DiffValue::ExternRef { .. }
108 | DiffValue::AnyRef { .. }
109 | DiffValue::ExnRef { .. }
110 | DiffValue::ContRef { .. } => {
111 unimplemented!()
112 }
113 }
114 }
115 }
116
117 impl From<SpecValue> for DiffValue {
from(spec: SpecValue) -> DiffValue118 fn from(spec: SpecValue) -> DiffValue {
119 match spec {
120 SpecValue::I32(n) => DiffValue::I32(n),
121 SpecValue::I64(n) => DiffValue::I64(n),
122 SpecValue::F32(n) => DiffValue::F32(n as u32),
123 SpecValue::F64(n) => DiffValue::F64(n as u64),
124 SpecValue::V128(n) => {
125 assert_eq!(n.len(), 16);
126 DiffValue::V128(u128::from_le_bytes(n.as_slice().try_into().unwrap()))
127 }
128 }
129 }
130 }
131
132 /// Set up the OCaml runtime for triggering its signal handler configuration.
133 ///
134 /// Because both the OCaml runtime and Wasmtime set up signal handlers, we must
135 /// carefully decide when to instantiate them; this function allows us to
136 /// control when. Wasmtime uses these signal handlers for catching various
137 /// WebAssembly failures. On certain OSes (e.g. Linux `x86_64`), the signal
138 /// handlers interfere, observable as an uncaught `SIGSEGV`--not even caught by
139 /// libFuzzer.
140 ///
141 /// This failure can be mitigated by always running Wasmtime second in
142 /// differential fuzzing. In some cases, however, this is not possible because
143 /// which engine will execute first is unknown. This function can be explicitly
144 /// executed first, e.g., during global initialization, to avoid this issue.
setup_ocaml_runtime()145 pub fn setup_ocaml_runtime() {
146 wasm_spec_interpreter::setup_ocaml_runtime();
147 }
148
149 #[cfg(test)]
150 mod tests {
151 use super::*;
152
153 #[test]
smoke()154 fn smoke() {
155 if !wasm_spec_interpreter::support_compiled_in() {
156 return;
157 }
158 crate::oracles::engine::smoke_test_engine(|_, config| Ok(SpecInterpreter::new(config)))
159 }
160 }
161