6 个版本

0.19.0 2020 年 7 月 23 日
0.16.0-threadsafe.42020 年 5 月 19 日
0.13.0-threadsafe.12020 年 3 月 12 日

#1071WebAssembly

Download history 155/week @ 2024-03-12 181/week @ 2024-03-19 142/week @ 2024-03-26 199/week @ 2024-04-02 114/week @ 2024-04-09 181/week @ 2024-04-16 189/week @ 2024-04-23 165/week @ 2024-04-30 140/week @ 2024-05-07 151/week @ 2024-05-14 160/week @ 2024-05-21 156/week @ 2024-05-28 136/week @ 2024-06-04 126/week @ 2024-06-11 133/week @ 2024-06-18 125/week @ 2024-06-25

每月 538 次下载

Apache-2.0 WITH LLVM-exception

2MB
31K SLoC

Wasmtime 嵌入式 API

wasmtime crate 是 wasmtime WebAssembly 运行时的嵌入式 API。它旨在用于 Rust 项目,并提供与 WebAssembly 模块交互的高级 API。

如果您对在其他语言中嵌入 wasmtime 感兴趣,您可能需要查看 C 嵌入式 API


lib.rs:

Wasmtime 的嵌入式 API

此 crate 包含用于与 WebAssembly 模块交互的 API。例如,您可以编译模块、实例化它们、调用它们等。作为 WebAssembly 的嵌入器,您还可以通过创建主机定义的函数、内存、全局变量等,从主机提供 WebAssembly 模块的功能,这些功能 WebAssembly 不能完成(例如,在屏幕上打印)。

wasmtime 库从多个来源汲取灵感,包括 JS WebAssembly API 以及 提议的 C API。与所有其他 Rust 代码一样,只要你不在你自己的程序中使用 unsafe,就可以保证程序是安全的(没有未定义的行为或段错误)。使用 wasmtime,你可以轻松且方便地嵌入 WebAssembly 运行时,并确信 WebAssembly 是安全沙箱化的。

使用 Wasmtime 的示例如下:

use anyhow::Result;
use wasmtime::*;

fn main() -> Result<()> {
    // All wasm objects operate within the context of a "store"
    let store = Store::default();

    // Modules can be compiled through either the text or binary format
    let wat = r#"
        (module
            (import "" "" (func $host_hello (param i32)))

            (func (export "hello")
                i32.const 3
                call $host_hello)
        )
    "#;
    let module = Module::new(store.engine(), wat)?;

    // Host functions can be defined which take/return wasm values and
    // execute arbitrary code on the host.
    let host_hello = Func::wrap(&store, |param: i32| {
        println!("Got {} from WebAssembly", param);
    });

    // Instantiation of a module requires specifying its imports and then
    // afterwards we can fetch exports by name, as well as asserting the
    // type signature of the function with `get0`.
    let instance = Instance::new(&store, &module, &[host_hello.into()])?;
    let hello = instance
        .get_func("hello")
        .ok_or(anyhow::format_err!("failed to find `hello` function export"))?
        .get0::<()>()?;

    // And finally we can call the wasm as if it were a Rust function!
    hello()?;

    Ok(())
}

核心概念

在使用 wasmtime 库时,有一些核心类型和概念需要了解。

  • 引用计数 - 几乎这个 API 中的所有对象都是引用计数的。通常,当对象被 clone 时,你只是增加引用计数。例如,当你克隆一个 Instance 时,这是一个低成本的运算,它不会创建一个全新的实例。

  • Store - 所有的 WebAssembly 对象和主机值都将“连接”到一个存储中。一个 Store 不是线程安全的,这意味着它本身以及与之连接的所有对象都固定在单个线程上(这通过缺乏 SendSync 特性来自动发生)。同样,wasmtime 也没有垃圾收集器,所以任何在 Store 中创建的对象将不会在所有引用都消失之前被分配。有关更多信息,请参阅 Store 文档。

  • Module - 编译后的 WebAssembly 模块。这个结构表示内存中的 JIT 代码,实例化后即可执行。缓存 Module 的实例通常很重要,因为创建(编译)可能很昂贵。请注意,Module 可以安全地在线程之间共享。

  • Instance - 实例化的 WebAssembly 模块。实例是你可以从中获取 Func 的地方,例如调用。每个 Instance,就像所有其他 Store-连接的对象一样,不能在线程之间发送。

wasmtime 库中还有其他重要的类型,但熟悉上述类型至关重要!务必浏览 API 文档,以了解这个库提供的其他功能。

示例架构

为了更好地理解 Wasmtime 类型之间的交互,让我们以一个高级的示例来说明你如何使用 WebAssembly。在我们的用例中,假设我们有一个 Web 服务器,我们想在每次请求上运行一些自定义 WebAssembly。但是,为了确保请求彼此隔离,我们将为每个请求创建一个新的 Instance

当服务器启动时,我们首先创建一个 Engine(可能还需要调整 Config 设置),并在服务器运行期间作为唯一引擎使用。

接下来,我们可以编译我们的 WebAssembly。您可以通过 Module API 的 Module::new 接口创建一个模块。这将生成 JIT 代码并执行昂贵的编译任务。

在完成这些设置后,服务器将像往常一样启动并准备接收请求。当收到请求时,您将使用 StoreStore::new 指向原始 Engine 创建一个存储。然后,使用之前创建的 Module 调用 Instance::new 来实例化请求的模块。这两个操作都旨在尽可能降低成本。

有了 Instance,您就可以调用各种导出项并与 WebAssembly 模块交互。一旦请求完成,StoreInstance 以及所有其他加载的项都会被丢弃,并且一切都会被释放。请注意,创建一个针对每个请求的 Store 是至关重要的,以确保内存使用不会意外增加,因为无限期地保持 Store 存活。

高级链接

通常,WebAssembly 模块并非完全隔离。它们可能需要引用大量主机功能、WASI 或甚至其他多个 wasm 模块。为了帮助处理所有这些内容,这个 crate 提供了一个 Linker 类型,它作为实例化模块的抽象层。该 Linker 类型还可以透明地处理由 WASI 定义的命令和反应器。

WASI

wasmtime crate 不原生支持 WASI,但您可以使用 wasmtime-wasi crate 来实现这一目的。使用 wasmtime-wasi,您可以创建一个“wasi 实例”,然后将其所有项添加到 Linker 中,然后可以使用它来实例化使用 WASI 的 Module

示例

除了下面的示例外,请务必查看在线嵌入文档 在线嵌入文档 以及在线示例列表 在线示例列表

使用 WASI 的示例看起来像

use wasmtime_wasi::{Wasi, WasiCtx};

let store = Store::default();
let mut linker = Linker::new(&store);

// Create an instance of `Wasi` which contains a `WasiCtx`. Note that
// `WasiCtx` provides a number of ways to configure what the target program
// will have access to.
let wasi = Wasi::new(&store, WasiCtx::new(std::env::args())?);
wasi.add_to_linker(&mut linker)?;

// Instantiate our module with the imports we've created, and run it.
let module = Module::from_file(store.engine(), "foo.wasm")?;
let instance = linker.instantiate(&module)?;
// ...

从wasm模块中读取字符串的示例

use std::str;

let store = Store::default();
let log_str = Func::wrap(&store, |caller: Caller<'_>, ptr: i32, len: i32| {
    let mem = match caller.get_export("memory") {
        Some(Extern::Memory(mem)) => mem,
        _ => return Err(Trap::new("failed to find host memory")),
    };

    // We're reading raw wasm memory here so we need `unsafe`. Note
    // though that this should be safe because we don't reenter wasm
    // while we're reading wasm memory, nor should we clash with
    // any other memory accessors (assuming they're well-behaved
    // too).
    unsafe {
        let data = mem.data_unchecked()
            .get(ptr as u32 as usize..)
            .and_then(|arr| arr.get(..len as u32 as usize));
        let string = match data {
            Some(data) => match str::from_utf8(data) {
                Ok(s) => s,
                Err(_) => return Err(Trap::new("invalid utf-8")),
            },
            None => return Err(Trap::new("pointer/length out of bounds")),
        };
        assert_eq!(string, "Hello, world!");
        println!("{}", string);
    }
    Ok(())
});
let module = Module::new(
    store.engine(),
    r#"
        (module
            (import "" "" (func $log_str (param i32 i32)))
            (func (export "foo")
                i32.const 4   ;; ptr
                i32.const 13  ;; len
                call $log_str)
            (memory (export "memory") 1)
            (data (i32.const 4) "Hello, world!"))
    "#,
)?;
let instance = Instance::new(&store, &module, &[log_str.into()])?;
let foo = instance.get_func("foo").unwrap().get0::<()>()?;
foo()?;

依赖项

~21-30MB
~639K SLoC