1 //! Interpret WebAssembly modules using the OCaml spec interpreter.
2 //!
3 //! ```
4 //! # use wasm_spec_interpreter::{SpecValue, interpret, instantiate};
5 //! let module = wat::parse_file("tests/add.wat").unwrap();
6 //! let instance = instantiate(&module).unwrap();
7 //! let parameters = vec![SpecValue::I32(42), SpecValue::I32(1)];
8 //! let results = interpret(&instance, "add", Some(parameters)).unwrap();
9 //! assert_eq!(results, &[SpecValue::I32(43)]);
10 //! ```
11 //!
12 //! ### Warning
13 //!
14 //! The OCaml runtime is [not re-entrant]. The code below must ensure that only
15 //! one Rust thread is executing at a time (using the `INTERPRET` lock) or we
16 //! may observe `SIGSEGV` failures, e.g., while running `cargo test`.
17 //!
18 //! [not re-entrant]:
19 //! https://ocaml.org/manual/intfc.html#ss:parallel-execution-long-running-c-code
20 //!
21 //! ### Warning
22 //!
23 //! This module uses an unsafe approach (`OCamlRuntime::init_persistent()` +
24 //! `OCamlRuntime::recover_handle()`) to initializing the `OCamlRuntime` based
25 //! on some [discussion] with `ocaml-interop` crate authors. This approach was
26 //! their recommendation to resolve seeing errors like `boxroot is not setup`
27 //! followed by a `SIGSEGV`; this is similar to the testing approach [they use].
28 //! Use this approach with care and note that it is only as safe as the OCaml
29 //! code running underneath.
30 //!
31 //! [discussion]: https://github.com/tezedge/ocaml-interop/issues/35
32 //! [they use]:
33 //! https://github.com/tezedge/ocaml-interop/blob/master/testing/rust-caller/src/lib.rs
34
35 use crate::{SpecExport, SpecInstance, SpecValue};
36 use ocaml_interop::{BoxRoot, OCamlRuntime, ToOCaml};
37 use std::sync::Mutex;
38
39 static INTERPRET: Mutex<()> = Mutex::new(());
40
41 /// Instantiate the WebAssembly module in the spec interpreter.
instantiate(module: &[u8]) -> Result<SpecInstance, String>42 pub fn instantiate(module: &[u8]) -> Result<SpecInstance, String> {
43 let _lock = INTERPRET.lock().unwrap();
44 OCamlRuntime::init_persistent();
45 let ocaml_runtime = unsafe { OCamlRuntime::recover_handle() };
46
47 let module = module.to_boxroot(ocaml_runtime);
48 let instance = ocaml_bindings::instantiate(ocaml_runtime, &module);
49 instance.to_rust(ocaml_runtime)
50 }
51
52 /// Interpret the exported function `name` with the given `parameters`.
interpret( instance: &SpecInstance, name: &str, parameters: Option<Vec<SpecValue>>, ) -> Result<Vec<SpecValue>, String>53 pub fn interpret(
54 instance: &SpecInstance,
55 name: &str,
56 parameters: Option<Vec<SpecValue>>,
57 ) -> Result<Vec<SpecValue>, String> {
58 let _lock = INTERPRET.lock().unwrap();
59 OCamlRuntime::init_persistent();
60 let ocaml_runtime = unsafe { OCamlRuntime::recover_handle() };
61
62 // Prepare the box-rooted parameters.
63 let instance = instance.to_boxroot(ocaml_runtime);
64 let name = name.to_string().to_boxroot(ocaml_runtime);
65 let parameters = parameters.to_boxroot(ocaml_runtime);
66
67 // Interpret the function.
68 let results = ocaml_bindings::interpret(ocaml_runtime, &instance, &name, ¶meters);
69 results.to_rust(&ocaml_runtime)
70 }
71
72 /// Interpret the first function in the passed WebAssembly module (in Wasm form,
73 /// currently, not WAT), optionally with the given parameters. If no parameters
74 /// are provided, the function is invoked with zeroed parameters.
interpret_legacy( module: &[u8], opt_parameters: Option<Vec<SpecValue>>, ) -> Result<Vec<SpecValue>, String>75 pub fn interpret_legacy(
76 module: &[u8],
77 opt_parameters: Option<Vec<SpecValue>>,
78 ) -> Result<Vec<SpecValue>, String> {
79 let _lock = INTERPRET.lock().unwrap();
80 OCamlRuntime::init_persistent();
81 let ocaml_runtime = unsafe { OCamlRuntime::recover_handle() };
82
83 // Parse and execute, returning results converted to Rust.
84 let module = module.to_boxroot(ocaml_runtime);
85 let opt_parameters = opt_parameters.to_boxroot(ocaml_runtime);
86 let results = ocaml_bindings::interpret_legacy(ocaml_runtime, &module, &opt_parameters);
87 results.to_rust(ocaml_runtime)
88 }
89
90 /// Retrieve the export given by `name`.
export(instance: &SpecInstance, name: &str) -> Result<SpecExport, String>91 pub fn export(instance: &SpecInstance, name: &str) -> Result<SpecExport, String> {
92 let _lock = INTERPRET.lock().unwrap();
93 OCamlRuntime::init_persistent();
94 let ocaml_runtime = unsafe { OCamlRuntime::recover_handle() };
95
96 // Prepare the box-rooted parameters.
97 let instance = instance.to_boxroot(ocaml_runtime);
98 let name = name.to_string().to_boxroot(ocaml_runtime);
99
100 // Export the value.
101 let results = ocaml_bindings::export(ocaml_runtime, &instance, &name);
102 results.to_rust(&ocaml_runtime)
103 }
104
105 // Here we declare which functions we will use from the OCaml library. See
106 // https://docs.rs/ocaml-interop/0.8.4/ocaml_interop/index.html#example.
107 mod ocaml_bindings {
108 use super::*;
109 use ocaml_interop::{
110 FromOCaml, OCaml, OCamlBytes, OCamlInt32, OCamlInt64, OCamlList, impl_conv_ocaml_variant,
111 ocaml,
112 };
113
114 // Using this macro converts the enum both ways: Rust to OCaml and OCaml to
115 // Rust. See
116 // https://docs.rs/ocaml-interop/0.8.4/ocaml_interop/macro.impl_conv_ocaml_variant.html.
117 impl_conv_ocaml_variant! {
118 SpecValue {
119 SpecValue::I32(i: OCamlInt32),
120 SpecValue::I64(i: OCamlInt64),
121 SpecValue::F32(i: OCamlInt32),
122 SpecValue::F64(i: OCamlInt64),
123 SpecValue::V128(i: OCamlBytes),
124 }
125 }
126
127 // We need to also convert the `SpecExport` enum.
128 impl_conv_ocaml_variant! {
129 SpecExport {
130 SpecExport::Global(i: SpecValue),
131 SpecExport::Memory(i: OCamlBytes),
132 }
133 }
134
135 // We manually show `SpecInstance` how to convert itself to and from OCaml.
136 unsafe impl FromOCaml<SpecInstance> for SpecInstance {
from_ocaml(v: OCaml<SpecInstance>) -> Self137 fn from_ocaml(v: OCaml<SpecInstance>) -> Self {
138 Self {
139 repr: BoxRoot::new(v),
140 }
141 }
142 }
143 unsafe impl ToOCaml<SpecInstance> for SpecInstance {
to_ocaml<'a>(&self, cr: &'a mut OCamlRuntime) -> OCaml<'a, SpecInstance>144 fn to_ocaml<'a>(&self, cr: &'a mut OCamlRuntime) -> OCaml<'a, SpecInstance> {
145 BoxRoot::get(&self.repr, cr)
146 }
147 }
148
149 // These functions must be exposed from OCaml with:
150 // `Callback.register "interpret" interpret`
151 //
152 // In Rust, these functions look like:
153 // `pub fn interpret(_: &mut OCamlRuntime, ...: OCamlRef<...>) -> BoxRoot<...>;`
154 //
155 // The `ocaml!` macro does not understand documentation, so the
156 // documentation is included here:
157 // - `instantiate`: clear the global store and instantiate a new WebAssembly
158 // module from bytes
159 // - `interpret`: given an instance, call the function exported at `name`
160 // - `interpret_legacy`: starting from bytes, instantiate and execute the
161 // first exported function
162 // - `export`: given an instance, get the value of the export at `name`
163 ocaml! {
164 pub fn instantiate(module: OCamlBytes) -> Result<SpecInstance, String>;
165 pub fn interpret(instance: SpecInstance, name: String, params: Option<OCamlList<SpecValue>>) -> Result<OCamlList<SpecValue>, String>;
166 pub fn interpret_legacy(module: OCamlBytes, params: Option<OCamlList<SpecValue>>) -> Result<OCamlList<SpecValue>, String>;
167 pub fn export(instance: SpecInstance, name: String) -> Result<SpecExport, String>;
168 }
169 }
170
171 /// Initialize a persistent OCaml runtime.
172 ///
173 /// When used for fuzzing differentially with engines that also use signal
174 /// handlers, this function provides a way to explicitly set up the OCaml
175 /// runtime and configure its signal handlers.
setup_ocaml_runtime()176 pub fn setup_ocaml_runtime() {
177 let _lock = INTERPRET.lock().unwrap();
178 OCamlRuntime::init_persistent();
179 }
180
181 #[cfg(test)]
182 mod tests {
183 use super::*;
184
185 #[test]
invalid_function_name()186 fn invalid_function_name() {
187 let module = wat::parse_file("tests/add.wat").unwrap();
188 let instance = instantiate(&module).unwrap();
189 let results = interpret(
190 &instance,
191 "not-the-right-name",
192 Some(vec![SpecValue::I32(0), SpecValue::I32(0)]),
193 );
194 assert_eq!(results, Err("Not_found".to_string()));
195 }
196
197 #[test]
multiple_invocation()198 fn multiple_invocation() {
199 let module = wat::parse_file("tests/add.wat").unwrap();
200 let instance = instantiate(&module).unwrap();
201
202 let results1 = interpret(
203 &instance,
204 "add",
205 Some(vec![SpecValue::I32(42), SpecValue::I32(1)]),
206 )
207 .unwrap();
208 let results2 = interpret(
209 &instance,
210 "add",
211 Some(vec![SpecValue::I32(1), SpecValue::I32(42)]),
212 )
213 .unwrap();
214 assert_eq!(results1, results2);
215
216 let results3 = interpret(
217 &instance,
218 "add",
219 Some(vec![SpecValue::I32(20), SpecValue::I32(23)]),
220 )
221 .unwrap();
222 assert_eq!(results2, results3);
223 }
224
225 #[test]
multiple_invocation_legacy()226 fn multiple_invocation_legacy() {
227 let module = wat::parse_file("tests/add.wat").unwrap();
228
229 let results1 =
230 interpret_legacy(&module, Some(vec![SpecValue::I32(42), SpecValue::I32(1)])).unwrap();
231 let results2 =
232 interpret_legacy(&module, Some(vec![SpecValue::I32(1), SpecValue::I32(42)])).unwrap();
233 assert_eq!(results1, results2);
234
235 let results3 =
236 interpret_legacy(&module, Some(vec![SpecValue::I32(20), SpecValue::I32(23)])).unwrap();
237 assert_eq!(results2, results3);
238 }
239
240 #[test]
oob()241 fn oob() {
242 let module = wat::parse_file("tests/oob.wat").unwrap();
243 let instance = instantiate(&module).unwrap();
244 let results = interpret(&instance, "oob", None);
245 assert_eq!(
246 results,
247 Err("Error(_, \"(Isabelle) trap: load\")".to_string())
248 );
249 }
250
251 #[test]
oob_legacy()252 fn oob_legacy() {
253 let module = wat::parse_file("tests/oob.wat").unwrap();
254 let results = interpret_legacy(&module, None);
255 assert_eq!(
256 results,
257 Err("Error(_, \"(Isabelle) trap: load\")".to_string())
258 );
259 }
260
261 #[test]
simd_not()262 fn simd_not() {
263 let module = wat::parse_file("tests/simd_not.wat").unwrap();
264 let instance = instantiate(&module).unwrap();
265
266 let parameters = Some(vec![SpecValue::V128(vec![
267 0, 255, 0, 0, 255, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0,
268 ])]);
269 let results = interpret(&instance, "simd_not", parameters).unwrap();
270
271 assert_eq!(
272 results,
273 vec![SpecValue::V128(vec![
274 255, 0, 255, 255, 0, 255, 255, 255, 255, 0, 255, 255, 255, 255, 255, 255
275 ])]
276 );
277 }
278
279 #[test]
simd_not_legacy()280 fn simd_not_legacy() {
281 let module = wat::parse_file("tests/simd_not.wat").unwrap();
282
283 let parameters = Some(vec![SpecValue::V128(vec![
284 0, 255, 0, 0, 255, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0,
285 ])]);
286 let results = interpret_legacy(&module, parameters).unwrap();
287
288 assert_eq!(
289 results,
290 vec![SpecValue::V128(vec![
291 255, 0, 255, 255, 0, 255, 255, 255, 255, 0, 255, 255, 255, 255, 255, 255
292 ])]
293 );
294 }
295
296 // See issue https://github.com/bytecodealliance/wasmtime/issues/4671.
297 #[test]
order_of_params()298 fn order_of_params() {
299 let module = wat::parse_file("tests/shr_s.wat").unwrap();
300 let instance = instantiate(&module).unwrap();
301
302 let parameters = Some(vec![
303 SpecValue::I32(1795123818),
304 SpecValue::I32(-2147483648),
305 ]);
306 let results = interpret(&instance, "test", parameters).unwrap();
307
308 assert_eq!(results, vec![SpecValue::I32(1795123818)]);
309 }
310
311 // See issue https://github.com/bytecodealliance/wasmtime/issues/4671.
312 #[test]
order_of_params_legacy()313 fn order_of_params_legacy() {
314 let module = wat::parse_file("tests/shr_s.wat").unwrap();
315
316 let parameters = Some(vec![
317 SpecValue::I32(1795123818),
318 SpecValue::I32(-2147483648),
319 ]);
320 let results = interpret_legacy(&module, parameters).unwrap();
321
322 assert_eq!(results, vec![SpecValue::I32(1795123818)]);
323 }
324
325 #[test]
load_store_and_export()326 fn load_store_and_export() {
327 let module = wat::parse_file("tests/memory.wat").unwrap();
328 let instance = instantiate(&module).unwrap();
329
330 // Store 42 at offset 4.
331 let _ = interpret(
332 &instance,
333 "store_i32",
334 Some(vec![SpecValue::I32(4), SpecValue::I32(42)]),
335 );
336
337 // Load an i32 from offset 4.
338 let loaded = interpret(&instance, "load_i32", Some(vec![SpecValue::I32(4)]));
339
340 // Check stored value was retrieved.
341 assert_eq!(loaded.unwrap(), vec![SpecValue::I32(42)]);
342
343 // Retrieve the memory exported with name "mem" and check that the
344 // 32-bit value at byte offset 4 of memory is 42.
345 let export = export(&instance, "mem");
346 match export.unwrap() {
347 SpecExport::Global(_) => panic!("incorrect export"),
348 SpecExport::Memory(m) => {
349 assert_eq!(&m[0..10], [0, 0, 0, 0, 42, 0, 0, 0, 0, 0]);
350 }
351 }
352 }
353 }
354