wasmtime_interface_types/
lib.rs1#![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
21pub struct ModuleData {
28 inner: Option<Inner>,
29 wasi_module_name: Option<String>,
30}
31
32struct Inner {
33 module: walrus::Module,
34}
35
36pub 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 pub fn new(wasm: &[u8]) -> Result<ModuleData> {
57 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 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 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 pub fn find_wasi_module_name(&self) -> Option<String> {
121 self.wasi_module_name.clone()
122 }
123
124 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 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 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) .enumerate()
210 .map(|(i, param)| default_incoming(i, param))
211 .collect(),
212 }
213 }
214
215 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 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}