WASM:有人支持多价WASM吗?

发布于 2025-02-12 07:02:56 字数 1569 浏览 0 评论 0 原文

将多个值从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
  )
)

我们直接输出1和2。

我可以使用 rick battagline的帮助功能来自wat(带有a 轻微修复以正确支持多值fall):

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)
  )
 )
)

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));

我可以看到 [1,2] 由WASM函数返回。了不起。

我希望能够用比WAT更高的语言来执行此操作。是否有任何高级语言产生多价值WASM?

Passing multiple values from WebAssembly to Javascript can be harder than it needs to be. Normally, I find that I have to:

  1. Setup memory shared between Javascript and WebAssembly.
  2. Do the Wasm work to produce the values.
  3. Store the values somewhere in the shared memory.
  4. Output a memory pointer to Javascript.
  5. Have Javascript retrieve the values from shared memory.

Multi-value is a feature of Wasm intended to make this easier, where multiple values can be passed directly from Wasm to JavaScript, eliminating the need to deal with pointers. The steps become:

  1. Do the Wasm work to produce the values.
  2. Output the values to Javascript.

For example:

(module
  (func $multResult (export "multResult")
    (result f64 f64)
    f64.const 1
    f64.const 2
  )
)

We directly output 1 and 2.

I can use Rick Battagline's helpful functions to compile Wasm from WAT (with a slight fix to properly support the multi-value flag):

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

Turning the resulting Wasm compilation back into WAT, we get:

(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)
  )
 )
)

That tuple.make command is the secret sauce that makes the function consumable directly in JavaScript. If I write this 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));

I can see that [1,2] is returned by the Wasm function. Terrific.

I want to be able to do this with higher-level languages than WAT. Do any higher-level languages produce multi-value Wasm?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

去了角落 2025-02-19 07:02:56

有两个:Rust和C. Rust似乎产生了最小的 .WASM 到目前为止。

tinygo不。 < 谷物不

对于Rust

以下示例假设您打算在浏览器中使用WebAssembly。为WASI编译(在操作系统中使用WebAssembly)略有不同;参见此评论。 WebAssembly的主要点是在浏览器中提供轻巧的快速代码,以及如何轻/如何取决于WebAssbly目标是否与源语言的优先级保持一致。下面的示例假设您希望您的WebAssembly最大光。 (请参阅在这里有关如何减少<<<的有用信息代码> .wasm 在更复杂的方案中大小。)

  1. 安装rust
  2. 安装节点。或更有可能,
  3. 安装二进制: npm install -G binaryen
  4. 使用 rustup target添加wastm32 intage 添加目标 wastm32 inscoup noundown 。 (WASI使用不同的目标。)
  5. 使用货物新创建一个项目,然后将此代码添加到 cargo.toml
[profile.release]
lto = true
opt-level = "z"
strip = "debuginfo"

[lib]
crate-type = ["cdylib"]
  1. 编写一个包含旨在用于在中使用的函数的库返回多个值的WebAssembly上下文。制作一个称为 lib.rs 的文件,然后添加以下内容:
#[no_mangle]
fn flip(a: u32, b: u32) -> (u32, u32) {
    (b, a)
}
  1. 在您打算在任何地方运行货物构建命令: Rustflags =”,应用以下环境变量。 -ctarget-feature =+Multivalue -clink-args = -zstack-size = 64000“ 。 (实现详细信息取决于您的操作系统和命令行计划。)
    标志的破坏:

    -ctarget-feature =+Multivalue :告诉Rust启用WASM多值功能

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

  2. 您要去想要发布版本,而不是调试版本。在上面的步骤5中, strip =“ debuginfo” 正在删除调试信息。此外,该程序的重​​点是在浏览器的上下文中运行,因此任何调试/测试都应通过将代码嵌入JavaScript中来完成。使用此命令来编译 .wasm 文件:货物构建 - 释放-target = wasm32-inknown-Ingnown

  3. 使用 wasm-opt < /code>从binareen到壁画的工具更多: wasm -opt -oz -o [输出文件名] .wasm [输入文件名] .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)))

非常干净。不是完美的,但就在附近。在仅239个字节时, .wasm 文件小于C中生成的文件。

对于C

您必须以特殊方式使用Emscripten编译器。 这仅在

(显然, 例如

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

nums echo(int x, int y)
{
    nums result = {x,y};
    return result;
}

。一个回声发送给它的功能。

它可以与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

标志的快速分解:

  1. -mmultivalue :告诉Clang启用多价值支持
  2. -XCLANG -TARGEN-ABI :告诉Clang到Clang定位应用程序二进制接口
  3. -xClang实验MV :启用更多多价值的东西?
  4. -oz :告诉Clang生产代码对大小
  5. -s standalone_wasm >:告诉emscripten不要产生通常会产生
  6. exported_functions =”的 javascript胶代码。 ['_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)
 )
)

生成大量样板。重要位:

...
(export "echo" (func $0))
...
(func $0 (param $0 i32) (param $1 i32) (result i32 i32)
  (tuple.make
   (local.get $0)
   (local.get $1)
  )
 )

There are two: Rust and C. Rust seems to produce the smallest .wasm file so far.

TinyGo does not. AssemblyScript does not. Grain does not. SwiftWASM does not.

For Rust

The below example assumes that you intend to use WebAssembly in a browser. Compiling for WASI (using WebAssembly in an operating system) is slightly different; see this comment. The primary point of WebAssembly is to provide light, fast code in browsers, and how light/how fast depends on whether the WebAssembly goals align with the source language's priorities. The below example assumes that you want your WebAssembly maximally light. (See here for helpful information on how to reduce .wasm size in more complex scenarios.)

  1. Install Rust.
  2. Install Node. Or more likely, install NVM, and pick a version of Node compatible with whatever else you have going on.
  3. Install Binaryen: npm install -g binaryen
  4. Add the target wasm32-unknown-unknown using rustup target add wasm32-unknown-unknown. (WASI uses a different target.)
  5. Create a project using cargo new, and add this code to Cargo.toml:
[profile.release]
lto = true
opt-level = "z"
strip = "debuginfo"

[lib]
crate-type = ["cdylib"]
  1. Write a library that includes a function intended to be used in a WebAssembly context which returns multiple values. Make a file called lib.rs and add the following:
#[no_mangle]
fn flip(a: u32, b: u32) -> (u32, u32) {
    (b, a)
}
  1. Apply the following environment variable wherever you intend to run the cargo build command: RUSTFLAGS="-Ctarget-feature=+multivalue -Clink-args=-zstack-size=64000". (Implementation details depend on your operating system and command line program.)
    Rundown of the flags:

    -Ctarget-feature=+multivalue: tell Rust to enable the WASM multi-value feature

    -Clink-args=-zstack-size=64000: tell Rust to use one page of WASM linear memory. (Rust defaults to 16 pages of memory. For a very simple use case like the example, this is wasteful, so reset the memory to use the minimum setting. Browsers allocate 1 page per WASM thread anyway.)

  2. You're going to want a release version, not a debug version. In step 5 above, strip = "debuginfo" is stripping out the debug information. Besides, the point of the program is to run within the context of a browser, so any debugging/testing should be done by embedding the code within Javascript. Use this command to compile a .wasm file: cargo build --release --target=wasm32-unknown-unknown

  3. Use the wasm-opt tool from Binaryen to squash the code more: wasm-opt -Oz -o [output file name].wasm [input file name].wasm

Using this page to convert the .wasm into .wat, here is the entire program:

(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)))

Very clean. Not perfect, but just about. At just 239 bytes, the .wasm file is smaller than that produced in C.

For C

You have to use the Emscripten compiler in a special way. (This is apparently only otherwise mentioned anywhere in this Twitter entry.)

First, build the function in C. For example:

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

nums echo(int x, int y)
{
    nums result = {x,y};
    return result;
}

This defines a C struct containing two ints, then defines a function which echoes whatever is sent to it.

It can be compiled with Emscripten as follows:

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

Quick breakdown of the flags:

  1. -mmultivalue: tells Clang to enable multi-value support
  2. -Xclang -target-abi: tells Clang to target an application binary interface
  3. -Xclang experimental-mv: enables more multi-value stuff?
  4. -Oz: tells Clang to produce code aggressively optimized for size
  5. -s STANDALONE_WASM: tells Emscripten not to produce Javascript glue code that it normally produces
  6. -s EXPORTED_FUNCTIONS="['_echo']": tells Emscripten not to optimize away the 'echo' function if it finds no reference to it. This allows you to export the code to Javascript from WebAssembly.
  7. -Wl,--no-entry: tells Emscripten not to try to make a default entry in the WebAssembly
  8. hello.world.c: the input C file
  9. -o bob.wasm: output a file called 'bob.wasm'

This results in this code:

(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)
 )
)

That produces a lot of boilerplate. The important bits:

...
(export "echo" (func $0))
...
(func $0 (param $0 i32) (param $1 i32) (result i32 i32)
  (tuple.make
   (local.get $0)
   (local.get $1)
  )
 )
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文