Wasm:有人支持多值 WASM 吗?

Jos*_*ski 5 c rust webassembly

将多个值从 WebAssembly 传递到 Javascript 可能比实际需要的更困难。通常,我发现我必须:

  1. 设置 Javascript 和 WebAssembly 之间共享的内存。
  2. 执行 Wasm 工作来产生价值观。
  3. 将值存储在共享内存中的某个位置。
  4. 输出一个指向 Javascript 的内存指针。
  5. 让 Javascript 从共享内存中检索值。

多值是 Wasm 的一项功能,旨在使这一过程变得更容易,其中多个值可以直接从 Wasm 传递到 JavaScript,从而无需处理指针。步骤变为:

  1. 执行 Wasm 工作来产生价值观。
  2. 将值输出到 Javascript。

例如:

(module
  (func $multResult (export "multResult")
    (result f64 f64)
    f64.const 1
    f64.const 2
  )
)
Run Code Online (Sandbox Code Playgroud)

我们直接输出1和2。

我可以使用Rick Battagline 的有用函数从 WAT 编译 Wasm(稍作修复以正确支持多值标志):

node ./bin/watwasm bugrepro.wat -o newoutput.wasm -O3 --multi-value

将生成的 Wasm 编译转回 WAT,我们得到:

(module
 (type $none_=>_f64_f64 (func (result f64 f64)))
 (export "multResult" (func $0))
 (func $0 (result f64 f64)
  (tuple.make
   (f64.const 1)
   (f64.const 2)
  )
 )
)
Run Code Online (Sandbox Code Playgroud)

该tuple.make命令是使函数可以直接在 JavaScript 中使用的秘密武器。如果我写这个 JavaScript:

const fs = require('fs');

const wasmBytes = fs.readFileSync('./newoutput.wasm');

WebAssembly.instantiate(wasmBytes)
    .then(obj => obj.instance.exports)
    .then(exported => exported.multResult())
    .then(res => console.log(res));
Run Code Online (Sandbox Code Playgroud)

我可以看到它[1,2]是由 Wasm 函数返回的。了不起。

我希望能够使用比 WAT 更高级的语言来做到这一点。是否有任何高级语言可以产生多值 Wasm?

Jos*_*ski 6

有两种:Rust 和 C。Rust 似乎生成.wasm迄今为止最小的文件。

TinyGo 没有。AssemblyScript 没有。谷物没有。SwiftWASM 没有。

对于生锈

下面的示例假设您打算在浏览器中使用 WebAssembly。WASI 编译(在操作系统中使用 WebAssembly)略有不同;看到这个评论。WebAssembly 的主要目的是在浏览器中提供轻量、快速的代码,其轻量/速度取决于 WebAssembly 目标是否与源语言的优先级一致。下面的示例假设您希望 WebAssembly 尽可能轻。(有关如何在更复杂的情况下减小大小的有用信息,请参阅此处.wasm。)

  1. 安装 Rust。
  2. 安装节点。或者更有可能的是,安装 NVM,并选择与您正在进行的任何其他操作兼容的 Node 版本。
  3. 安装二进制:npm install -g binaryen
  4. wasm32-unknown-unknown使用添加目标rustup target add wasm32-unknown-unknown。(WASI 使用不同的目标。)
  5. 使用 创建一个项目cargo new,并将此代码添加到Cargo.toml:
[profile.release]
lto = true
opt-level = "z"
strip = "debuginfo"

[lib]
crate-type = ["cdylib"]
Run Code Online (Sandbox Code Playgroud)
  1. 编写一个库,其中包含一个要在 WebAssembly 上下文中使用的函数,该函数返回多个值。创建一个名为的文件lib.rs并添加以下内容:
#[no_mangle]
fn flip(a: u32, b: u32) -> (u32, u32) {
    (b, a)
}
Run Code Online (Sandbox Code Playgroud)
  1. 无论您打算在何处运行命令,都应用以下环境变量cargo build:RUSTFLAGS="-Ctarget-feature=+multivalue -Clink-args=-zstack-size=64000"。(实现细节取决于您的操作系统和命令行程序。)标志概述:

    -Ctarget-feature=+multivalue:告诉 Rust 启用 WASM 多值功能

    -Clink-args=-zstack-size=64000:告诉 Rust 使用一页 WASM 线性内存。(Rust 默认为 16 页内存。对于像示例这样的非常简单的用例,这是浪费的,因此重置内存以使用最小设置。无论如何,浏览器为每个 WASM 线程分配 1 页。)

  2. 您将需要发布版本,而不是调试版本。在上面的步骤 5 中,strip = "debuginfo"正在剥离调试信息。此外,程序的重点是在浏览器上下文中运行,因此任何调试/测试都应该通过将代码嵌入 Javascript 中来完成。使用此命令编译.wasm文件:cargo build --release --target=wasm32-unknown-unknown

  3. 使用wasm-optBinaryen 的工具进一步压缩代码:wasm-opt -Oz -o [output file name].wasm [input file name].wasm

使用此页面将 转换.wasm为.wat,这是整个程序:

(module
  (type $t0 (func (param i32 i32) (result i32 i32)))
  (func $flip (export "flip") (type $t0) (param $p0 i32) (param $p1 i32) (result i32 i32)
    (local.get $p1)
    (local.get $p0))
  (memory $memory (export "memory") 1)
  (global $__data_end (export "__data_end") i32 (i32.const 64000))
  (global $__heap_base (export "__heap_base") i32 (i32.const 64000)))
Run Code Online (Sandbox Code Playgroud)

很干净。并不完美,但也差不多。该文件只有 239 字节,.wasm比用 C 生成的文件要小。

对于C

您必须以特殊方式使用 Emscripten 编译器。(这显然只是在此 Twitter 条目中的其他地方提到的。)

首先,用 C 语言构建函数。例如:

typedef struct _nums
{
    int x;
    int y;
} nums;

nums echo(int x, int y)
{
    nums result = {x,y};
    return result;
}
Run Code Online (Sandbox Code Playgroud)

这定义了一个struct包含两个ints 的 C,然后定义了一个函数,该函数回显发送给它的任何内容。

可以使用 Emscripten 进行编译,如下所示:

emcc -mmultivalue -Xclang -target-abi -Xclang experimental-mv -Oz -s STANDALONE_WASM -s EXPORTED_FUNCTIONS="['_echo']" -Wl,--no-entry hello.world.c -o bob.wasm
Run Code Online (Sandbox Code Playgroud)

标志的快速分解:

  1. -mmultivalue:告诉 Clang 启用多值支持
  2. -Xclang -target-abi:告诉 Clang 以应用程序二进制接口为目标
  3. -Xclang experimental-mv:启用更多多值内容?
  4. -Oz:告诉 Clang 生成针对大小进行积极优化的代码
  5. -s STANDALONE_WASM: 告诉 Emscripten 不要生成它通常生成的 Javascript 粘合代码
  6. -s EXPORTED_FUNCTIONS="['_echo']":告诉 Emscripten 如果没有找到对“echo”函数的引用,就不要优化它。这允许您将代码从 WebAssembly 导出到 Javascript。
  7. -Wl,--no-entry:告诉 Emscripten 不要尝试在 WebAssembly 中创建默认条目
  8. hello.world.c:输入C文件
  9. -o bob.wasm:输出一个名为“bob.wasm”的文件

这会产生以下代码:

(module
 (type $none_=>_i32 (func (result i32)))
 (type $i32_=>_none (func (param i32)))
 (type $i32_=>_i32 (func (param i32) (result i32)))
 (type $i32_i32_=>_i32_i32 (func (param i32 i32) (result i32 i32)))
 (memory $0 256 256)
 (table $0 1 1 funcref)
 (global $global$0 (mut i32) (i32.const 5243920))
 (export "memory" (memory $0))
 (export "echo" (func $0))
 (export "__indirect_function_table" (table $0))
 (export "__errno_location" (func $4))
 (export "stackSave" (func $1))
 (export "stackRestore" (func $2))
 (export "stackAlloc" (func $3))
 (func $0 (param $0 i32) (param $1 i32) (result i32 i32)
  (tuple.make
   (local.get $0)
   (local.get $1)
  )
 )
 (func $1 (result i32)
  (global.get $global$0)
 )
 (func $2 (param $0 i32)
  (global.set $global$0
   (local.get $0)
  )
 )
 (func $3 (param $0 i32) (result i32)
  (global.set $global$0
   (local.tee $0
    (i32.and
     (i32.sub
      (global.get $global$0)
      (local.get $0)
     )
     (i32.const -16)
    )
   )
  )
  (local.get $0)
 )
 (func $4 (result i32)
  (i32.const 1024)
 )
)
Run Code Online (Sandbox Code Playgroud)

这会产生大量的样板文件。重要的部分:

...
(export "echo" (func $0))
...
(func $0 (param $0 i32) (param $1 i32) (result i32 i32)
  (tuple.make
   (local.get $0)
   (local.get $1)
  )
 )
Run Code Online (Sandbox Code Playgroud)