wasmtime_interface_types/
lib.rs

1//! A small crate to handle WebAssembly interface types in wasmtime.
2//!
3//! Note that this is intended to follow the [official proposal][proposal] and
4//! is highly susceptible to change/breakage/etc.
5//!
6//! [proposal]: https://github.com/webassembly/webidl-bindings
7
8#![deny(missing_docs)]
9
10use anyhow::{bail, format_err, Result};
11use std::convert::TryFrom;
12use std::str;
13use wasm_webidl_bindings::ast;
14use wasmtime::Val;
15use wasmtime_environ::ir;
16use wasmtime_runtime::{Export, InstanceHandle};
17
18mod value;
19pub use value::Value;
20
21/// A data structure intended to hold a parsed representation of the wasm
22/// interface types of a module.
23///
24/// The expected usage pattern is to create this next to wasmtime data
25/// structures and then use this to process arguments into wasm arguments as
26/// appropriate for bound functions.
27pub struct ModuleData {
28    inner: Option<Inner>,
29    wasi_module_name: Option<String>,
30}
31
32struct Inner {
33    module: walrus::Module,
34}
35
36/// Representation of a binding of an exported function.
37///
38/// Can be used to learn about binding expressions and/or binding types.
39pub struct ExportBinding<'a> {
40    kind: ExportBindingKind<'a>,
41}
42
43enum ExportBindingKind<'a> {
44    Rich {
45        section: &'a ast::WebidlBindings,
46        binding: &'a ast::ExportBinding,
47    },
48    Raw(ir::Signature),
49}
50
51impl ModuleData {
52    /// Parses a raw binary wasm file, extracting information about wasm
53    /// interface types.
54    ///
55    /// Returns an error if the wasm file is malformed.
56    pub fn new(wasm: &[u8]) -> Result<ModuleData> {
57        // Perform a fast search through the module for the right custom
58        // section. Actually parsing out the interface types data is currently a
59        // pretty expensive operation so we want to only do that if we actually
60        // find the right section.
61        let mut reader = wasmparser::ModuleReader::new(wasm)?;
62        let mut found = false;
63        let mut wasi_module_name = None;
64        while !reader.eof() {
65            let section = reader.read()?;
66
67            match section.code {
68                wasmparser::SectionCode::Custom { name, .. } => {
69                    if name == "webidl-bindings" {
70                        found = true;
71                        break;
72                    }
73                }
74
75                // If we see the import section then see if we can find a wasi
76                // module import which we can later use to register the wasi
77                // implementation automatically.
78                wasmparser::SectionCode::Import => {
79                    let section = section.get_import_section_reader()?;
80                    for import in section {
81                        let import = import?;
82                        if wasmtime_wasi::is_wasi_module(import.module) {
83                            wasi_module_name = Some(import.module.to_string());
84                        }
85                    }
86                }
87                _ => {}
88            }
89        }
90        if !found {
91            return Ok(ModuleData {
92                inner: None,
93                wasi_module_name,
94            });
95        }
96
97        // Ok, perform the more expensive parsing. WebAssembly interface types
98        // are super experimental and under development. To get something
99        // quickly up and running we're using the same crate as `wasm-bindgen`,
100        // a producer of wasm interface types, the `wasm-webidl-bindings` crate.
101        // This crate relies on `walrus` which has its own IR for a wasm module.
102        // Ideally we'd do all this during cranelift's own parsing of the wasm
103        // module and we wouldn't have to reparse here purely for this one use
104        // case.
105        //
106        // For now though this is "fast enough" and good enough for some demos,
107        // but for full-on production quality engines we'll want to integrate
108        // this much more tightly with the rest of wasmtime.
109        let module = walrus::ModuleConfig::new()
110            .on_parse(wasm_webidl_bindings::binary::on_parse)
111            .parse(wasm)?;
112
113        Ok(ModuleData {
114            inner: Some(Inner { module }),
115            wasi_module_name,
116        })
117    }
118
119    /// Detects if WASI support is needed: returns module name that is requested.
120    pub fn find_wasi_module_name(&self) -> Option<String> {
121        self.wasi_module_name.clone()
122    }
123
124    /// Invokes wasmtime function with a `&[Value]` list. `Value` the set of
125    /// wasm interface types.
126    pub fn invoke_export(
127        &self,
128        instance: &wasmtime::Instance,
129        export: &str,
130        args: &[Value],
131    ) -> Result<Vec<Value>> {
132        let mut handle = instance.handle().clone();
133
134        let binding = self.binding_for_export(&mut handle, export)?;
135        let incoming = binding.param_bindings()?;
136        let outgoing = binding.result_bindings()?;
137
138        let f = instance
139            .get_export(export)
140            .ok_or_else(|| format_err!("failed to find export `{}`", export))?
141            .func()
142            .ok_or_else(|| format_err!("`{}` is not a function", export))?
143            .clone();
144
145        let mut cx = InstanceTranslateContext(instance.clone());
146        let wasm_args = translate_incoming(&mut cx, &incoming, args)?
147            .into_iter()
148            .map(|rv| rv.into())
149            .collect::<Vec<_>>();
150        let wasm_results = f.call(&wasm_args)?;
151        translate_outgoing(&mut cx, &outgoing, &wasm_results)
152    }
153
154    /// Returns an appropriate binding for the `name` export in this module
155    /// which has also been instantiated as `instance` provided here.
156    ///
157    /// Returns an error if `name` is not present in the module.
158    pub fn binding_for_export(
159        &self,
160        instance: &mut InstanceHandle,
161        name: &str,
162    ) -> Result<ExportBinding<'_>> {
163        if let Some(binding) = self.interface_binding_for_export(name) {
164            return Ok(binding);
165        }
166        let signature = match instance.lookup(name) {
167            Some(Export::Function { signature, .. }) => signature,
168            Some(_) => bail!("`{}` is not a function", name),
169            None => bail!("failed to find export `{}`", name),
170        };
171        Ok(ExportBinding {
172            kind: ExportBindingKind::Raw(signature),
173        })
174    }
175
176    fn interface_binding_for_export(&self, name: &str) -> Option<ExportBinding<'_>> {
177        let inner = self.inner.as_ref()?;
178        let bindings = inner.module.customs.get_typed::<ast::WebidlBindings>()?;
179        let export = inner.module.exports.iter().find(|e| e.name == name)?;
180        let id = match export.item {
181            walrus::ExportItem::Function(f) => f,
182            _ => panic!(),
183        };
184        let (_, bind) = bindings.binds.iter().find(|(_, b)| b.func == id)?;
185        let binding = bindings.bindings.get(bind.binding)?;
186        let binding = match binding {
187            ast::FunctionBinding::Export(export) => export,
188            ast::FunctionBinding::Import(_) => return None,
189        };
190        Some(ExportBinding {
191            kind: ExportBindingKind::Rich {
192                binding,
193                section: bindings,
194            },
195        })
196    }
197}
198
199impl ExportBinding<'_> {
200    /// Returns the list of binding expressions used to create the parameters
201    /// for this binding.
202    pub fn param_bindings(&self) -> Result<Vec<ast::IncomingBindingExpression>> {
203        match &self.kind {
204            ExportBindingKind::Rich { binding, .. } => Ok(binding.params.bindings.clone()),
205            ExportBindingKind::Raw(sig) => sig
206                .params
207                .iter()
208                .skip(2) // skip the VMContext arguments
209                .enumerate()
210                .map(|(i, param)| default_incoming(i, param))
211                .collect(),
212        }
213    }
214
215    /// Returns the list of scalar types used for this binding
216    pub fn param_types(&self) -> Result<Vec<ast::WebidlScalarType>> {
217        match &self.kind {
218            ExportBindingKind::Rich {
219                binding, section, ..
220            } => {
221                let id = match binding.webidl_ty {
222                    ast::WebidlTypeRef::Id(id) => id,
223                    ast::WebidlTypeRef::Scalar(_) => {
224                        bail!("webidl types for functions cannot be scalar")
225                    }
226                };
227                let ty = section
228                    .types
229                    .get::<ast::WebidlCompoundType>(id)
230                    .ok_or_else(|| format_err!("invalid webidl custom section"))?;
231                let func = match ty {
232                    ast::WebidlCompoundType::Function(f) => f,
233                    _ => bail!("webidl type for function must be of function type"),
234                };
235                func.params
236                    .iter()
237                    .map(|param| match param {
238                        ast::WebidlTypeRef::Id(_) => bail!("function arguments cannot be compound"),
239                        ast::WebidlTypeRef::Scalar(s) => Ok(*s),
240                    })
241                    .collect()
242            }
243            ExportBindingKind::Raw(sig) => sig.params.iter().skip(2).map(abi2ast).collect(),
244        }
245    }
246
247    /// Returns the list of binding expressions used to extract the return
248    /// values of this binding.
249    pub fn result_bindings(&self) -> Result<Vec<ast::OutgoingBindingExpression>> {
250        match &self.kind {
251            ExportBindingKind::Rich { binding, .. } => Ok(binding.result.bindings.clone()),
252            ExportBindingKind::Raw(sig) => sig
253                .returns
254                .iter()
255                .enumerate()
256                .map(|(i, param)| default_outgoing(i, param))
257                .collect(),
258        }
259    }
260}
261
262fn default_incoming(idx: usize, param: &ir::AbiParam) -> Result<ast::IncomingBindingExpression> {
263    let get = ast::IncomingBindingExpressionGet { idx: idx as u32 };
264    let ty = if param.value_type == ir::types::I32 {
265        walrus::ValType::I32
266    } else if param.value_type == ir::types::I64 {
267        walrus::ValType::I64
268    } else if param.value_type == ir::types::F32 {
269        walrus::ValType::F32
270    } else if param.value_type == ir::types::F64 {
271        walrus::ValType::F64
272    } else {
273        bail!("unsupported type {:?}", param.value_type)
274    };
275    Ok(ast::IncomingBindingExpressionAs {
276        ty,
277        expr: Box::new(get.into()),
278    }
279    .into())
280}
281
282fn default_outgoing(idx: usize, param: &ir::AbiParam) -> Result<ast::OutgoingBindingExpression> {
283    let ty = abi2ast(param)?;
284    Ok(ast::OutgoingBindingExpressionAs {
285        ty: ty.into(),
286        idx: idx as u32,
287    }
288    .into())
289}
290
291fn abi2ast(param: &ir::AbiParam) -> Result<ast::WebidlScalarType> {
292    Ok(if param.value_type == ir::types::I32 {
293        ast::WebidlScalarType::Long
294    } else if param.value_type == ir::types::I64 {
295        ast::WebidlScalarType::LongLong
296    } else if param.value_type == ir::types::F32 {
297        ast::WebidlScalarType::UnrestrictedFloat
298    } else if param.value_type == ir::types::F64 {
299        ast::WebidlScalarType::UnrestrictedDouble
300    } else {
301        bail!("unsupported type {:?}", param.value_type)
302    })
303}
304
305trait TranslateContext {
306    fn invoke_alloc(&mut self, alloc_func_name: &str, len: i32) -> Result<i32>;
307    unsafe fn get_memory(&mut self) -> Result<&mut [u8]>;
308}
309
310struct InstanceTranslateContext(pub wasmtime::Instance);
311
312impl TranslateContext for InstanceTranslateContext {
313    fn invoke_alloc(&mut self, alloc_func_name: &str, len: i32) -> Result<i32> {
314        let alloc = self
315            .0
316            .get_export(alloc_func_name)
317            .ok_or_else(|| format_err!("failed to find alloc function `{}`", alloc_func_name))?
318            .func()
319            .ok_or_else(|| format_err!("`{}` is not a (alloc) function", alloc_func_name))?
320            .clone();
321        let alloc_args = vec![wasmtime::Val::I32(len)];
322        let results = alloc.call(&alloc_args)?;
323        if results.len() != 1 {
324            bail!("allocator function wrong number of results");
325        }
326        Ok(match results[0] {
327            wasmtime::Val::I32(i) => i,
328            _ => bail!("allocator function bad return type"),
329        })
330    }
331    unsafe fn get_memory(&mut self) -> Result<&mut [u8]> {
332        let memory = self
333            .0
334            .get_export("memory")
335            .ok_or_else(|| format_err!("failed to find `memory` export"))?
336            .memory()
337            .ok_or_else(|| format_err!("`memory` is not a memory"))?
338            .clone();
339        let ptr = memory.data_ptr();
340        let len = memory.data_size();
341        Ok(std::slice::from_raw_parts_mut(ptr, len))
342    }
343}
344
345fn translate_incoming(
346    cx: &mut dyn TranslateContext,
347    bindings: &[ast::IncomingBindingExpression],
348    args: &[Value],
349) -> Result<Vec<Val>> {
350    let get = |expr: &ast::IncomingBindingExpression| match expr {
351        ast::IncomingBindingExpression::Get(g) => args
352            .get(g.idx as usize)
353            .ok_or_else(|| format_err!("argument index out of bounds: {}", g.idx)),
354        _ => bail!("unsupported incoming binding expr {:?}", expr),
355    };
356
357    let mut copy = |alloc_func_name: &str, bytes: &[u8]| -> Result<(i32, i32)> {
358        let len = i32::try_from(bytes.len()).map_err(|_| format_err!("length overflow"))?;
359        let ptr = cx.invoke_alloc(alloc_func_name, len)?;
360        unsafe {
361            let raw = cx.get_memory()?;
362            raw[ptr as usize..][..bytes.len()].copy_from_slice(bytes)
363        }
364
365        Ok((ptr, len))
366    };
367
368    let mut wasm = Vec::new();
369
370    for expr in bindings {
371        match expr {
372            ast::IncomingBindingExpression::AllocUtf8Str(g) => {
373                let val = match get(&g.expr)? {
374                    Value::String(s) => s,
375                    _ => bail!("expected a string"),
376                };
377                let (ptr, len) = copy(&g.alloc_func_name, val.as_bytes())?;
378                wasm.push(Val::I32(ptr));
379                wasm.push(Val::I32(len));
380            }
381            ast::IncomingBindingExpression::As(g) => {
382                let val = get(&g.expr)?;
383                match g.ty {
384                    walrus::ValType::I32 => match val {
385                        Value::I32(i) => wasm.push(Val::I32(*i)),
386                        Value::U32(i) => wasm.push(Val::I32(*i as i32)),
387                        _ => bail!("cannot convert {:?} to `i32`", val),
388                    },
389                    walrus::ValType::I64 => match val {
390                        Value::I32(i) => wasm.push(Val::I64((*i).into())),
391                        Value::U32(i) => wasm.push(Val::I64((*i).into())),
392                        Value::I64(i) => wasm.push(Val::I64(*i)),
393                        Value::U64(i) => wasm.push(Val::I64(*i as i64)),
394                        _ => bail!("cannot convert {:?} to `i64`", val),
395                    },
396                    walrus::ValType::F32 => match val {
397                        Value::F32(i) => wasm.push(Val::F32(i.to_bits())),
398                        _ => bail!("cannot convert {:?} to `f32`", val),
399                    },
400                    walrus::ValType::F64 => match val {
401                        Value::F32(i) => wasm.push(Val::F64((*i as f64).to_bits())),
402                        Value::F64(i) => wasm.push(Val::F64(i.to_bits())),
403                        _ => bail!("cannot convert {:?} to `f64`", val),
404                    },
405                    walrus::ValType::V128 | walrus::ValType::Anyref => {
406                        bail!("unsupported `as` type {:?}", g.ty);
407                    }
408                }
409            }
410            _ => bail!("unsupported incoming binding expr {:?}", expr),
411        }
412    }
413
414    Ok(wasm)
415}
416
417fn translate_outgoing(
418    cx: &mut dyn TranslateContext,
419    bindings: &[ast::OutgoingBindingExpression],
420    args: &[Val],
421) -> Result<Vec<Value>> {
422    let mut values = Vec::new();
423
424    let get = |idx: u32| {
425        args.get(idx as usize)
426            .cloned()
427            .ok_or_else(|| format_err!("argument index out of bounds: {}", idx))
428    };
429
430    for expr in bindings {
431        match expr {
432            ast::OutgoingBindingExpression::As(a) => {
433                let arg = get(a.idx)?;
434                match a.ty {
435                    ast::WebidlTypeRef::Scalar(ast::WebidlScalarType::UnsignedLong) => match arg {
436                        Val::I32(a) => values.push(Value::U32(a as u32)),
437                        _ => bail!("can't convert {:?} to unsigned long", arg),
438                    },
439                    ast::WebidlTypeRef::Scalar(ast::WebidlScalarType::Long) => match arg {
440                        Val::I32(a) => values.push(Value::I32(a)),
441                        _ => bail!("can't convert {:?} to long", arg),
442                    },
443                    ast::WebidlTypeRef::Scalar(ast::WebidlScalarType::LongLong) => match arg {
444                        Val::I32(a) => values.push(Value::I64(a as i64)),
445                        Val::I64(a) => values.push(Value::I64(a)),
446                        _ => bail!("can't convert {:?} to long long", arg),
447                    },
448                    ast::WebidlTypeRef::Scalar(ast::WebidlScalarType::UnsignedLongLong) => {
449                        match arg {
450                            Val::I32(a) => values.push(Value::U64(a as u64)),
451                            Val::I64(a) => values.push(Value::U64(a as u64)),
452                            _ => bail!("can't convert {:?} to unsigned long long", arg),
453                        }
454                    }
455                    ast::WebidlTypeRef::Scalar(ast::WebidlScalarType::Float) => match arg {
456                        Val::F32(a) => values.push(Value::F32(f32::from_bits(a))),
457                        _ => bail!("can't convert {:?} to float", arg),
458                    },
459                    ast::WebidlTypeRef::Scalar(ast::WebidlScalarType::Double) => match arg {
460                        Val::F32(a) => values.push(Value::F64(f32::from_bits(a) as f64)),
461                        Val::F64(a) => values.push(Value::F64(f64::from_bits(a))),
462                        _ => bail!("can't convert {:?} to double", arg),
463                    },
464                    _ => bail!("unsupported outgoing binding expr {:?}", expr),
465                }
466            }
467            ast::OutgoingBindingExpression::Utf8Str(e) => {
468                if e.ty != ast::WebidlScalarType::DomString.into() {
469                    bail!("utf-8 strings must go into dom-string")
470                }
471                let offset = match get(e.offset)? {
472                    Val::I32(a) => a,
473                    _ => bail!("offset must be an i32"),
474                };
475                let length = match get(e.length)? {
476                    Val::I32(a) => a,
477                    _ => bail!("length must be an i32"),
478                };
479                let bytes = unsafe { &cx.get_memory()?[offset as usize..][..length as usize] };
480                values.push(Value::String(str::from_utf8(bytes).unwrap().to_string()));
481            }
482            _ => {
483                drop(cx);
484                bail!("unsupported outgoing binding expr {:?}", expr);
485            }
486        }
487    }
488
489    Ok(values)
490}