#wasm-interpreter #wasm-binary #binary-format #run-wasm #interpreter #text-format

app wain

使用零依赖的Safe Rust编写的WebAssembly解释器

9个版本

0.1.7 2023年11月18日
0.1.6 2020年6月11日
0.1.5 2020年5月24日
0.1.4 2020年4月5日
0.0.0 2020年2月23日

#22WebAssembly

Download history 29/week @ 2024-04-16 14/week @ 2024-04-23 70/week @ 2024-07-30

70 每月下载量

MIT 许可证

315KB
6.5K SLoC

wain

crates.io CI

wain 是一个从头开始用Safe Rust编写的零依赖的 WebAINterpreter,是 WebAssembly 的实现。

screencast

特性

  • 无不安全代码。保证内存安全和无未定义行为
  • 无外部运行时依赖。这意味着无不安全依赖和更小的二进制大小
  • 效率。避免不必要的分配,尽可能快地执行指令而不使用不安全代码
  • 模块化实现。二进制格式解析器、文本格式解析器、验证器、执行器作为独立的库开发

请注意,此项目仍在进行中。在v1.0.0之前意味着实验性的。并非所有功能都已实现。当前状态是所有MVP实现都已完成,但还有很多任务待完成。

通往v1.0.0的道路(优先级顺序)

  • 为所有库添加足够的测试和解析器的模糊测试
  • 通过所有 规范测试
  • 添加基准测试以跟踪性能
  • 引入中间表示形式以更有效地执行指令
  • 为每个公共API添加文档

请参阅 任务板 了解当前进度。

该项目始于娱乐和深入了解Wasm。

安装

wain Crate尚未发布。请克隆此仓库,并通过 cargo build 构建项目。

最低支持的Rust版本是1.45.0。

$ cargo install wain
$ wain --help

如果您不想运行文本格式代码,则可以省略

# Only run binary format files
$ cargo install wain --no-default-features --features binary

用法

wain 命令

运行二进制格式Wasm源文件

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

运行文本格式Wasm源文件

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

如果没有参数,wain 命令从stdin检测二进制格式和文本格式并运行输入。

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

请参阅示例目录获取更多示例。

当前限制如下

  • 默认情况下,只有 int putchar(int)int getchar() 被实现为外部函数
  • wain一次只能运行一个模块。这意味着从其他模块导入东西还不工作
  • 许多扩展,如线程、WASI支持、SIMD支持等,尚未实现

作为库

wain由多个crate组成。

  • wain: 用于执行给定Wasm源代码的命令行工具
  • wain-ast: 抽象语法树定义。Wasm结构规范的实现。这个语法树对于二进制格式和文本格式都是通用的
  • wain-syntax-binary: Wasm二进制格式(.wasm文件)的解析器。Wasm二进制格式规范的实现。它将&[u8]值解析为wain_ast::Root抽象语法树
  • wain-syntax-text: Wasm文本格式(.wat文件)的解析器。Wasm文本格式规范的实现。它将&str值解析为wain_ast::Root抽象语法树
  • wain-validate: Wasm抽象语法树的验证器。Wasm验证规范的实现
  • wain-exec: 解析Wasm抽象语法树的执行器。Wasm执行规范的实现。它现在直接解析语法树,但将来会将其转换为中间表示形式以高效执行

wain-* crates作为WebAssembly的模块化实现,是库。它们可以解析、验证、执行WebAssembly代码。

以下是一个从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);
}

或者使用参数调用特定的导出函数

// ...(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),
}

默认情况下,在env模块中仅支持以下C函数作为外部函数

  • int putchar(int)(在wasm (func (param i32) (result i32))
  • int getchar(void) (在wasm (func (param) (result i32)))
  • void *memcpy(void *, void *, size_t) (在wasm (func (param i32 i32 i32) (result i32)))
  • void abort(void) (在wasm (func (param) (result)))

但是,您可以自己实现一个结构体,该结构体从Rust端定义外部函数,实现了wain_exec::Importer

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", &[]);

有关API的用法,工作示例可在examples/api/中找到。

未来工作

  • WASI支持
  • MVP支持之后的Wasm特性(线程、SIMD、多返回值等)
  • 与其他Wasm实现比较基准测试
  • 自托管的解释器。将wain编译成Wasm并自行运行
  • 将Wasm实现从Wasm v1升级到Wasm v2

工作原理

在此,我记录了每个解释阶段的要点。

解析

Sequence to parse Wasm

wain-syntax-binary根据二进制格式规范.wasm二进制文件解析为wain_ast::Root抽象语法树。Wasm二进制格式旨在通过基本的LL(1)解析器进行解析。因此,解析非常直接。解析器实现实现小于1000行。

相比之下,解析文本格式的实现更复杂。 wain-syntax-text根据文本格式规范.wat文本文件解析为wain_ast::Root抽象语法树。

  1. .wat文件解析为WAT语法树,该语法树是针对文本格式解析许多语法糖的。由于可以在.wat文件中放置多个模块,因此它可以解析为多个树。
  2. 将WAT语法树转换为通用的Wasm语法树(wain_ast::Root),解析标识符。标识符可能引用尚未定义的东西(向前引用),因此不能直接将.wat文件解析为通用的Wasm语法树。
  3. 根据规范从多个Wasm语法树中组合一个模块。

验证

验证通过遍历给定的 Wasm 语法树在 wain-validate 包中进行。符合 规范,以下内容将被验证

  • 在 Wasm 中,每个引用都是一个索引。它验证所有索引都没有超出范围
  • Wasm 设计用于静态检查栈操作。它通过模拟栈状态来验证指令序列
  • 由于多态指令 select,类型检查是尽力而为的。由于几乎所有指令都不是多态的,几乎所有类型检查都可以在验证中完成

符合规范,wain 验证 unreachable 指令之后的指令。例如,

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

i32.add 是无效的,因为它应该从栈中取出两个 i32 值,但至少有一个 i64 值在栈中。

执行

wain-exec 包解释一个符合 规范 的 Wasm 语法树。多亏了验证,运行时的检查是最小的(例如间接调用的函数签名)。

  1. 分配内存、表和全局变量。初始化栈
  2. 通过将值推入/弹出栈来解释语法树节点

目前 wain 直接解释 Wasm 语法树。我计划定义一个可以更快解释的中间表示

入口点是 'start 函数',它定义为

  1. start 节中设置的函数
  2. 在导出节中名为 _start 的导出函数

第 1 项是标准入口点,但 Clang 不生成 start 节。相反,它将 _start 函数作为入口点处理。wain 实现了这两个入口点(第 1 项优先)。

许可证

MIT 许可证

依赖关系