Crates.io | wain |
lib.rs | wain |
version | 0.1.7 |
source | src |
created_at | 2020-02-23 15:43:04.180476 |
updated_at | 2023-11-18 05:02:12.051355 |
description | WebAssembly interpreter written in Safe Rust with zero dependencies |
homepage | https://github.com/rhysd/wain |
repository | https://github.com/rhysd/wain |
max_upload_size | |
id | 211786 |
size | 33,966 |
wain is a WebAssembly INterpreter written in Safe Rust from scratch with zero dependencies. An implementation of WebAssembly.
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.
wain
crate is not published yet. Please clone this repository and build the project by cargo build
.
Minimum supported Rust version is 1.45.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
wain
commandRun 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:
int putchar(int)
and int getchar()
are implemented as external functions by defaultwain consists of multiple crates.
.wasm
files).
Implementation for Wasm binary format spec. It parses &[u8]
value into
wain_ast::Root
abstract syntax tree.wat
files).
Implementation for Wasm text format spec. It parses &str
value into
wain_ast::Root
abstract syntax treewain-*
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.
extern crate wain_syntax_binary;
extern crate wain_validate;
extern crate wain_exec;
use std::fs;
use std::process::exit;
use wain_syntax_binary::parse;
use wain_validate::validate;
use wain_exec::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
// ...(snip)
use wain_exec::{Runtime, 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 runtime. It instantiates a module
let mut runtime = match Runtime::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 runtime.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
int putchar(int)
(in wasm (func (param i32) (result i32))
)int getchar(void)
(in wasm (func (param) (result i32))
)void *memcpy(void *, void *, size_t)
(in wasm (func (param i32 i32 i32) (result i32))
)void abort(void)
(in wasm (func (param) (result))
)But you can implement your own struct which implements wain_exec::Importer
for defining external
functions from Rust side.
extern crate wain_exec;
extern crate wain_ast;
use wain_exec::{Runtime, Stack, Memory, Importer, ImportInvokeError, ImportInvalidError}
use wain_ast::ValType;
struct YourOwnImporter {
// ...
}
impl Importer for YourOwnImporter {
fn validate(&self, name: &str, params: &[ValType], ret: Option<ValType>) -> Option<ImportInvalidError> {
// `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.
// wain_exec::check_func_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 runtime = Runtime::instantiate(&ast.module, YourOwnImporter{ /* ... */ }).unwrap();
let result = runtime.invoke("do_something", &[]);
To know the usage of APIs, working examples are available at examples/api/.
WASI support
Wasm features after MVP support (threads, SIMD, multiple return values, ...)
Compare benchmarks with other Wasm implementations
Self-hosting interpreter. Compile wain into Wasm and run it by itself
Here I note some points on each phase of interpretation.
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.
.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 treeswain_ast::Root
) resolving identifiers.
Identifiers may refer things not defined yet (forward references) so .wat
file cannot be parsed
into common Wasm syntax trees directlyValidation is done by traversing a given Wasm syntax tree in wain-validate crate. Conforming spec, following things are validated:
select
. Since almost all instructions
are not polymorphic, almost all type checks can be done in validationConforming the spec, wain validates instructions after unreachable
instruction. For example,
(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.
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).
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
start
section_start
in export sectionThe 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).