wain-exec

crates.io CI

wain-exec is a crate to execute a WebAssembly abstract syntax tree. Execution logic is defined in spec

This crate is part of larger wain project.

Installation

toml [dependencies] wain-exec = "0"

Usage

This crate assumes given Wasm syntax tree was validated.

It takes wain_ast::Module value and validates it. The tree can be parsed by wain-syntax-binary and wain-syntax-text parsers and validated by wain-validate validator:

Using wain_exec::execute() is the easiest way. It invokes a start function in given module if presents. Otherwise it invokes a function exported as _start.

```rust extern crate wainsyntaxbinary; extern crate wainvalidate; extern crate wainexec;

use std::fs; use std::process::exit; use wainsyntaxbinary::parse; use wainvalidate::validate; use wainexec::execute;

// Read wasm binary let source = fs::read("foo.wasm").unwrap();

// Parse binary into syntax tree let tree = match parse(&source) { Ok(tree) => tree, Err(err) => { eprintln!("Could not parse: {}", err); exit(1); } };

// Validate module if let Err(err) = validate(&tree) { eprintln!("This .wasm file is invalid!: {}", err); exit(1); }

// Execute module if let Err(trap) = execute(&tree.module) { eprintln!("Execution was trapped: {}", trap); exit(1); } ```

Or invoke specific exported function with arguments

```rust // ...(snip)

use wain_exec::{machine, DefaultImporter, Value}; use std::io;

// Create default importer to call external function supported by default let stdin = io::stdin(); let stdout = io::stdout(); let importer = DefaultImporter::with_stdio(stdin.lock(), stdout.lock());

// Make abstract machine instance let mut machine = match Machine::instantiate(&tree.module, importer) { Ok(m) => m, Err(err) => { eprintln!("could not instantiate module: {}", err); exit(1); } };

// Let's say int add(int, int) is exported match machine.invoke("add", &[Value::I32(10), Value::I32(32)]) { Ok(ret) => { // ret is type of Option<Value> where it contains Some value when the invoked // function returned a value. Otherwise it's None value. if let Some(Value::I32(i)) = ret { println!("10 + 32 = {}", i); } else { unreachable!(); } } Err(trap) => eprintln!("Execution was trapped: {}", trap), } ```

Trap is returned as Err part of Result.

wain_exec::execute() buffers stdin and stdout by default for now (this behavior may change in the future). If this behavior is not acceptable, please specify your io::Write/io::Read values for stdout/stdin at wain_exec::Machine::new(). Then run the module by wain_exec::Machine::execute().

By default, only following C functions are supported in env module are supported as external functions

But you can implement your own struct which implements wain_exec::Importer for defining external functions from Rust side.

```rust extern crate wainexec; extern crate wainast; use wainexec::{Machine, Stack, Memory, Importer, ImportInvokeError, ImportInvalidError} use wainast::ValType;

struct YourOwnImporter { // ... }

impl Importer for YourOwnImporter { fn validate(&self, name: &str, params: &[ValType], ret: Option) -> Option { // name is a name of function to validate. params and ret are the function's signature. // Return ImportInvalidError::NotFound when the name is unknown. // Return ImportInvalidError::SignatureMismatch when signature does not match. // wainexec::checkfunc_signature() utility is would be useful for the check. } fn call(&mut self, name: &str, stack: &mut Stack, memory: &mut Memory) -> Result<(), ImportInvokeError> { // Implement your own function call. name is a name of function and you have full access // to stack and linear memory. Pop values from stack for getting arguments and push value to // set return value. // Note: Consistency between imported function signature and implementation of this method // is your responsibility. // On invocation failure, return ImportInvokeError::Fatal. It is trapped by interpreter and it // stops execution immediately. }; }

let ast = ...; // Parse abstract syntax tree and validate it

let mut machine = Machine::instantiate(&ast.module, YourOwnImporter{ /* ... */ }).unwrap(); let run = machine.execute().unwrap(); ```

Working examples can be seen at examples/api/ directory

Please read documentation (not yet) for details.

Implementation

Thanks to validation, checks at runtime are minimal (e.g. function signature on indirect call).

  1. Allocate memory, table, global variables. Initialize stack
  2. Interpret syntax tree nodes pushing/popping values to/from stack

Currently wain interprets a Wasm syntax tree directly. I'm planning to define an intermediate representation which can be interpreted faster.

Entrypoint is 'start function' which is defined either

  1. Function set in start section
  2. Exported function named _start in export section

The 1. is a standard entrypoint but Clang does not emit start section. Instead it handles _start function as entrypoint. wain implements both entrypoints (1. is prioritized).

License

the MIT license