Closed18

興味のおもむくままにWASM/WASIらへん

乳牛乳牛

https://zenn.dev/link/comments/09280d40f6fa5a で考えていたけどあまりに脇道に逸れ過ぎなので別のスクラップブックにした。

気になること

  • WASIって何? (WASMとの差分)
  • Web用 WASMをパッケージングする上でEmscriptenとwasm-packの違い
  • 純粋JSとのパフォーマンス差をベンチマークしてみたい
乳牛乳牛

WASIって何? (WASMとの差分)

https://github.com/bytecodealliance/wasmtime/blob/main/docs/WASI-intro.md

  • WASIはBrowser, JS independent なWASM仕様 -> ランタイムのためのインターフェース?
  • BytecodeAllianceが中心となって策定している
  • WASI実装としては wasmtime, wasmer, lucetの3つがメジャーっぽい

結局、WASI/WASMの関係性は何なのか?

  • 一般的にWASMと呼ばれているものはブラウザ向けのWebAssemblyのこと
  • 対してWASIはCoreにWASI APIをのっけたもの
    • 正確にはInterfaceなのでWASI API仕様だけで、実装としてwasmerやwasmtimeなどがある
      • こちらもOSネイティブなランタイムにEmbeddedされている

つまり、ブラウザ向けのWASMとWASI向けのWASMには差がある。積集合に当たる部分はCoreで共通するが使えるcapabilityが異なる。

ブラウザ向けにコンパイルされた(アセンブルされたというべき?)wasmはWASIでは動かないかもしれない=依存するAPIが使えないかもしれない

理解の役に立った文献

メジャーなWASIランタイム

wasmtime

https://github.com/bytecodealliance/wasmtime

  • BytecodeAllienceのリファレンス実装
  • 実用性でいうと起動速度、実行速度ともに物足りなくなっている様子

wasmer

https://wasmer.io/

  • 単純なWASI実装を超えてエコシステム目指してるっぽい
    • NPMライクなWAPMとか
  • 商業感ある

lucet

https://www.fastly.com/blog/how-lucet-wasmtime-make-stronger-compiler-together

  • CDNで有名なFastly主管
  • Fastlyはエッジコンピューティングの有力な手段としてwasm推し
  • BytecodeAllianceのwasmtimeと密な連携してそう
  • ...と思ったら開発終了してwasmtimeと合流してる
    • もともとwasmtimeの派生でその成果をwasmtimeに還元してプロジェクトを終了したっぽい
乳牛乳牛

チュートリアルやってみた

https://github.com/bytecodealliance/wasmtime/blob/main/docs/WASI-tutorial.md#from-rust

リポジトリ
https://github.com/NewGyu/rust-wasm/tree/master/wasi-tutorial

wasmtime
$ wasmtime --dir=./tmp ./target/wasm32-wasi/debug/hello.wasm ./tmp/org.txt ./tmp/copy_by_wasmtime.txt
wasmer
$ wasmer --dir=./tmp ./target/wasm32-wasi/debug/hello.wasm ./tmp/org.txt ./tmp/copy_by_wasmer.txt

当然ながらどちらのランタイムでも同じwasmファイルが動作する

当たり前だけどWindowsに入れたwasmtimeでも動く

windows
PS C:\Users\NewGyu\tmp> wasmtime.exe --dir=. .\hello.wasm .\org.txt copy_by_win.txt

感想(サンドボックス)

なるほど、サンドボックスとして --dir, --mapdirでファイルアクセス範囲をホワイトリストで指定するようになっているのか。

The --dir= option instructs wasmtime to preopen a directory, and make it available to the program as a capability which can be used to open files inside that directory.

乳牛乳牛

wasmerの方が早い傾向(さすがパフォーマンスに注力している)

wasmer
$ time wasmer --dir=./tmp ./target/wasm32-wasi/debug/hello.wasm ./tmp/org.txt ./tmp/copy_by_wasmtime.txt

real    0m0.008s
user    0m0.000s
sys     0m0.008s
wasmtime
$ time wasmtime --dir=./tmp ./target/wasm32-wasi/debug/hello.wasm ./tmp/org.txt ./tmp/copy_by_wasmtime.txt

real    0m0.016s
user    0m0.017s
sys     0m0.000s
乳牛乳牛

Web向けのWASM

理解の役に立った文献

WASM with JavaScriptの仕様

JavaScrpit API

https://webassembly.github.io/spec/js-api/index.html

JavaScript API: defines JavaScript classes and objects for accessing WebAssembly from within JavaScript, including methods for validation, compilation, instantiation, and classes for representing and manipulating imports and exports as JavaScript objects.

JavaScript <---> WASMのつなぎの仕様

  • WASMの中にJSのfunctionをどうやってimportするか
  • JS側からWASMのModuleをどうinstantiateするか
  • その他にもJSObjectをどうやってWASMの中で扱うかとかそんなことが書かれている(たぶん)

Web API

https://webassembly.github.io/spec/web-api/index.html

Web API: defines extensions to the JavaScript API made available specifically in web browsers, in particular, an interface for streaming compilation and instantiation from origin-bound Response types.

ブラウザがWASMを組み込むための仕様っぽい

Web向けWASMをビルドするツール

基本的にはRustでコードを書くスタンスで調べる

wasm-pack

https://rustwasm.github.io/wasm-pack/book/

  • Rustからブラウザ向けのWASMにするツール
  • RustコードにアノテーションをつけることでJSとのグルーコードを吐いてくれる
    • グルーコードを吐く点では後述のEmscriptenとやっていることは近い用に思えるが、暗黙ではなく明示的にアノテートするところが違いなのかな

Emscripten

https://emscripten.org/

Emscripten is a complete compiler toolchain to WebAssembly, using LLVM, with a special focus on speed, size, and the Web platform.
APIs
Emscripten converts OpenGL into WebGL, and has support for familiar APIs like SDL, pthreads, and POSIX, as well as Web APIs and JavaScript.

  • 基本C/C++をWASMにするツール
    • LLVM使える言語なら何でもできそうではある
  • ブラウザーで動かすために色々便宜を計らってくれるっぽい(JS APIに置き換えたり)
  • Rust勢はEmscriptenから離れていっている様子

WebAssembly Script

https://www.assemblyscript.org/

Designed for WebAssembly
AssemblyScript targets WebAssembly's feature set specifically, giving developers low-level control over their code.

Familiar TypeScript syntax
Its similarity with TypeScript makes it easy to compile to WebAssembly without learning a new language.

WASMにコンパイルするためのTypeScriptライクな専用言語らしい。とりあえずはあまり興味ない。

乳牛乳牛

wasm-pack

チュートリアル

https://developer.mozilla.org/ja/docs/WebAssembly/Rust_to_wasm

npmパッケージとして扱うパターン

https://github.com/NewGyu/rust-wasm/tree/webpack-ver/hello-wasm

正直webpackが必要になってめんどい。手順は以下。

  1. wasm-pack build でビルドすると以下のようにnpmパッケージ前提の一式が作られる。
$ ls -1 ./pkg/
hello_wasm_bg.js
hello_wasm_bg.wasm
hello_wasm_bg.wasm.d.ts
hello_wasm.d.ts
hello_wasm.js
package.json
README.md

$ cat ./pkg/package.json 
{
"name": "hello-wasm",
"version": "0.1.0",
"files": [
  "hello_wasm_bg.wasm",
  "hello_wasm.js",
  "hello_wasm_bg.js",
  "hello_wasm.d.ts"
],
"module": "hello_wasm.js",
"types": "hello_wasm.d.ts",
"sideEffects": false
}
  1. 別途testsite用にwebpackの構成をする
$ ls -1 ./testsite/
index.html
index.js
package.json
package-lock.json
webpack.config.js
  1. 上記のwasmの入ったhello-wasm npmパッケージをtestsiteに取り込む
$ cat ./testsite/package.json 
{
"name": "testsite",
"dependencies": {
  "hello-wasm": "file:../pkg"
},
  1. そいつをimportして使う
import * as wasm from "hello-wasm";

wasm.greet("WebAssembly!");

ポイント

  • WASM入りのnpmパッケージを配布する想定になっている
    • node.js向け?
  • ブラウザJSにするためにWebpackを前提としている
  • wasmモジュールを読み込むところはWebpackの機能

wasm-pack build --target web でビルドするパターン

日本語ページには載ってなくて、英語版にひっそり書いてあった…。こっちのほうがええやん。
https://developer.mozilla.org/en-US/docs/WebAssembly/Rust_to_wasm#building_the_package

というわけでこれも試した。
https://github.com/NewGyu/rust-wasm/tree/target-web-ver/hello-wasm

  1. wasm-pack build --target webとするとpkgディレクトリに色々作られる
$ ls -1 pkg/
hello_wasm_bg.wasm          ...コンパイルされたWASM
hello_wasm_bg.wasm.d.ts
hello_wasm.d.ts
hello_wasm.js               ...つなぎのJSコード
package.json
README.md
  1. 上記のうちhello_wasm.jsにinit関数が定義されるのが大きな違い
    init関数の中でwasmファイルのロードとinstantiateが行われている
  2. HTML中から扱うのはとてもシンプル
<script type="module">
    import init, {greet} from "./pkg/hello_wasm.js";
    init()
      .then(() => {
        greet("WebAssembly")
      });
</script>

ポイント

  • --target webをつけよう
  • hello_wasm.jsがすべてを覆い隠してjs関数のように扱うことができる!
  • いずれにしてもrustcのwasm32-unknown-unknownターゲットとしてコンパイルされる=WASIと共通のwasm-coreに準じたものになる。

Rust + WASM

https://rustwasm.github.io/docs/book/reference/tools.html

乳牛乳牛

Emscripten

やろうと思ったけどインストールするものが多いので面倒になってやめた。

乳牛乳牛

Emscripten vs wasm-pack メモ

https://docs.wasmtime.dev/wasm-rust.html

  • wasm32-unknown-unknown - this target, like the WASI one, is focused on producing single *.wasm binaries. The standard library, however, is largely stubbed out since the "unknown" part of the target means libstd can't assume anything. This means that while binaries will likely work in wasmtime, common conveniences like println! or panic! won't work.
  • wasm32-unknown-emscripten - this target is intended to work in a web browser and produces a *.wasm file coupled with a *.js file, and it is not compatible with wasmtime.

前者は純粋なwasm(おそらくwasm coreの範囲)でのビルドになるのでポータビリティがある。(WASIランタイムでも動くし、ブラウザ上でも動く)。しかし、libstd相当の部分、例えばprintf!, panic!のようなものは動かない。(別の手段が必要になる)

後者(emscripten)でビルドされたものはprintf!, panic!みたいなシステムライブラリをうまいことJSに依存するようにしてあるので便利だけど、ブラウザを前提としているのでWASIランタイム上では動かない。(たぶんimport されたobjectにメッチャ依存するようになってる)

https://users.rust-lang.org/t/wasm-unknown-vs-emscripten/22997/3

wasm32-unknown-emscripten

This target uses emscripten to provide the standard library. emscripten tries to provide things like TCP sockets/file io/multithreading/opengl so that you can compile your existing code to wasm and have it ‘just work’. People compile their SDL2 applications, and emscripten will handle making DOM elements etc. that given you your gl canvas.

wasm32-unknown-unknown

This target is much more “bare bones”. There is no attempt to emulate a PC - most system calls like IO will just panic (todo check this). However, the compiled wasm modules tend to be smaller since they don’t need all that emulation, and we don’t have to build and install an enormous C project. We do get an allocator (wee_alloc I believe) so we can use all our favorite heap allocated data structures (Vec, HashMap, …). The awesome wasm-bindgen project provides interop with javascript, and then libraries like web-sys/js-sys provide rust functions that map to ECMAScript and Web API functions, so you can do anything like create HTML nodes.

  • Emscriptenは(ブラウザ想定なら)既存のプログラムがほとんどそのまま動く...けれどサイズが大きい

vs

  • 「素」のwasmだけなのでコンピューティング以外(IOなど)は何もできないけれど小さい
  • 必要ならwasm-bindgenを組み合わせて

という構造で、Emscriptenのモノリシックだった部分を分割して行く方向性か。

乳牛乳牛

WASM vs JS パフォーマンス対決

一番これがやりたかった。題材としては純粋な計算量勝負のアルゴリズムとしてハノイの塔 O(2n)をチョイス。

https://github.com/NewGyu/rust-wasm/tree/master/bench-hanoi

手元で動かす場合はwebpack-dev-serverでlocalhost:8080だが、gh-pagesにもしてある。
https://newgyu.github.io/rust-wasm/bench-hanoi/

結果としてはこんな感じで思ったよりも速くない…。 v8が優秀なのか?
計測結果

  • プロセッサ AMD Ryzen 7 3700X 8-Core Processor、3593 Mhz、8 個のコア、16 個のロジカル プロセッサ
  • メモリ 32GB
  • Windows11上のWSL2 Ubuntu20.04上のVSCode Remote Container上で計測

Node.js vs WASIだとWASIのほうが15%くらい早くて、同じ展開を期待していたのだが・・・
(´・ω・`)

乳牛乳牛

パフォーマンス対決をしようと思ってハマったのは

  • ChromeのdevtoolsでWASM関数を実行するとやたらと遅い
  • なぜかperformance profilerをONにすると解消してしまう
  • Firefoxはなんの問題もない


という謎事象。

Rustのdiscordで聞いてみたら「devtoolsを閉じた状態で計測してみ?」というアドバイスをもらって確認したところ上記の「やや速い」という結果が得られた。

Twitterでも教えていただけたのはdevtoolsのバグなんじゃないか説。
https://twitter.com/tkihira/status/1521736039735635968
https://twitter.com/tkihira/status/1522791656944472065

乳牛乳牛

http://nmi.jp/2022-05-14-Dynamically-created-WebAssembly

Chrome において、DevTools をただ開くと wasm の実行が極めて遅くなる現象が確認されております。これはバグではなくて仕様なのですが、結構複雑な仕様で、

DevTools を開くと TurboFan の最適化がキャンセルされて遅くなる
しかし Profile タブで Profile を取るときには再度 TurboFan の最適化が適用される
という挙動になっております。Chrome では、ただ DevTools を開いているだけで wasm の実行がかなり遅くなります。大変気づきにくいトリガーなので、wasm 系の開発をされる時にはお気をつけください。

このまとめ超ありがたい。

乳牛乳牛

rust + wasm-packで外部ライブラリのリンク

ソフトウェア作ってるとどうしても外部ライブラリが必要になってくる。他のライブラリに依存するようなWASMをビルドしたい場合どうしたものか。

パターン別に考えてみる。

  1. Cargoのdependencyでcrateにリンク
  2. *.soにDynamic Linking
  3. *.aにStatic Linking
  4. 別のwasmにDynamic Linking
  5. 別のwasmとCombine(Static Linking)

結論としては以下のいずれかしか方法がない。

    1. Cargoのdependencyでcrateにリンク
    1. *.aにStatic Linking

下記についてはwasm仕様として検討はされているようだがまだexperimentalな機能(emscripten固有?)として捉えたほうが良さそう。

    1. 別のwasmにDynamic Linking
乳牛乳牛

1.Cargoのdependencyでcrateにリンク

依存するcrateはCargoがソースコードをダウンロードしてきて*.rlibとしてコンパイルされる。
(.rlibは c/c++における.aと同じでスタティックリンクを意図した形式のRust版)

その後にリンカーによってそれらがスタティックリンクされて一つのwasmにまとめられる。
(このあたりはwasm-pack buildの内部挙動で良くは分かっていないが)

つまりは依存ライブラリがRust製のcrateなら特に気にすることなくwasm-packできる。

乳牛乳牛

2. *.soにDynamic Linking

結論から言うと無理。
wasmはwasm runtime上で動くわけでruntimeは色々な環境で動く。
Webブラウザ上のwasm runtimeから*.soにアクセスできるわけが無い。そもそもwasm runtimeは隔離性を重視しているのでruntimeの下回りのOSに直接アクセスと言うのは無い。

乳牛乳牛

4. 別のwasmにDynamic Linking

Emscriptenだとメインモジュール、サイドモジュールという考え方があってメインのwasmから別のwasmを動的にリンクできるように見える。

WebAssembly公式のGitHubでは以下のように述べられているのでまだexperimentalな機能と考えて良さそう。

Note: This ABI is still a work in progress. There is no stable ABI yet.

乳牛乳牛

5.別のwasmとCombine(Static Linking)

コンパイル済みのwasmファイルを取り込む(static linkingのようなイメージ)事ができないかとおもったが、noと言われているのでだめなんだろうな。
https://groups.google.com/g/emscripten-discuss/c/1iuJUSEq1Dg

you are asking about linking a SIDE_MODULE into the MAIN_MODULE ahead of time, its not something that is supported no.

過去にはwasm-mergeというツールがあった??
https://github.com/WebAssembly/binaryen/issues/2174

補足:この考え方はComponent ModelではComposeという概念で実現される様子。
https://component-model.bytecodealliance.org/creating-and-consuming/composing.html

What is composition?
When you compose components, you wire up the imports of one "primary" component to the exports of one or more other "dependency" components, creating a new component.

このスクラップは2022/09/30にクローズされました