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.
toml
[dependencies]
wain-exec = "0"
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
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))
)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: Optionname
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.
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 an 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).