wain

crates.io CI

wain is a WebAssembly INterpreter written in Rust from scratch with zero dependencies. An implementation of WebAssembly.

screencast

Features:

Note that this project is in progress. Before v1.0.0 means experimental. Not all of the features are implemented yet. Current status is that all the MVP implementations have been done and many tasks are remaining.

Roadmap to v1.0.0 (priority order):

Please see the task board for current progress.

This project started for fun and understanding Wasm deeply.

Installation

wain crate is not published yet. Please clone this repository and build the project by cargo build.

Minimum supported Rust version is 1.43.0.

$ cargo install wain $ wain --help

If you don't want to run text format code, it can be omitted:

```

Only run binary format files

$ cargo install wain --no-default-features --features binary ```

Usage

wain command

Run a binary format Wasm source file:

$ wain examples/hello/hello.wasm Hello, world

Run a text format Wasm source file:

$ wain examples/hello/hello.wat Hello, world

Without argument, wain command detects both binary format and text format from stdin and runs the input.

$ wain < examples/hello/hello.wasm Hello, world $ wain < examples/hello/hello.wat Hello, world

Please see examples directory for more examples.

Current restrictions are as follows:

As libraries

wain consists of multiple crates.

wain-* crates are libraries as modular implementation of WebAssembly. They can parse, validate, execute WebAssembly code.

Here is an example code to run the interpreter from Rust.

```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), } ```

By default, only following C functions are supported in env module 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(); ```

To know the usage of APIs, working examples are available at examples/api/.

Future works

How it works

Here I note some points on each phase of interpretation.

Parsing

Sequence to parse Wasm

wain-syntax-binary parses .wasm binary file into wain_ast::Root abstract syntax tree following binary format spec. Wasm binary format is designed to be parsed by basic LL(1) parser. So parsing is very straightforward. Parser implementation is smaller than 1000 lines.

In contrast, implementation of parsing text format is more complicated. wain-syntax-text parses .wat text file into wain_ast::Root abstract syntax tree following text format spec.

  1. Lex and parse .wat file into WAT sytnax tree which is dedicated for text format resolving many syntax sugars. Since multiple modules can be put in .wat file, it can be parsed into multiple trees
  2. Translate the WAT syntax trees into common Wasm syntax trees (wain_ast::Root) resolving identifiers. Identifiers may refer things not defined yet (forward references) so .wat file cannot be parsed into common Wasm syntax trees directly
  3. Compose a single module from the multiple Wasm syntax trees following spec

Validation

Validation is done by traversing a given Wasm syntax tree in wain-validate crate. Conforming spec, following things are validated:

Conforming the spec, wain validates instructions after unreachable instruction. For example,

wat (unreachable) (i64.const 0) (i32.add)

i32.add is invalid because it should take two i32 values from stack but at least one i64 value is in the stack.

Execution

wain-exec crate interprets a Wasm syntax tree conforming spec. 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 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