一.What?
WebAssembly or wasm is a new portable, size- and load-time-efficient format suitable for compilation to the web.
移植可能で、体积小く読み込みが高速な(バイナリ)形式で、Web へのコンパイルに適しています
主な目標はWeb 環境でハイパフォーマンスアプリケーションをサポートすることです。しかし、設計上は Web 特性に依存せず、Web 特性向けの機能も提供せず、他の環境でも使用できます
簡単に理解すると、コンパイル目標形式を 1 つ定義し、この形式をサポートするあらゆる環境でネイティブに近い実行性能を得られるようにしたものです。native モジュールの拡張を許可するようなもので、性能が苛酷なシーンでは、他のより適切な言語(例えば C++)で実装し、事前に WebAssembly 形式にコンパイルすれば、native に匹敵する性能体験を得られます
その設計目標は 2 方面に分かれます:
-
高速で、安全で、移植可能なセマンティクス
-
高速:ネイティブコードに近い性能で実行し、すべての現代ハードウェアに共通する機能を利用
-
安全:コードは検証され、メモリ安全なサンドボックス環境で実行され、データ破損やセキュリティ違反を防止
-
定義良好:合法的なプログラムとその動作を十分かつ正確に定義し、非公式と公式の両方を推論しやすい方法で
-
ハードウェア独立:すべての現代アーキテクチャ、デスクトップまたはモバイルデバイス、および組み込みシステムでコンパイル可能
-
言語独立:特定の言語、プログラミングモデル、またはオブジェクトモデルに偏らない
-
プラットフォーム独立:ブラウザに埋め込んだり、stand-alone VM として実行したり、他の環境に統合したり可能
-
オープン:プログラムがシンプルで普遍的な方法で環境と対話可能
-
効率的で、移植可能な表現
-
小型:典型的なテキストまたはネイティブコード形式よりも体积が小さいバイナリ形式で、高速転送可能
-
モジュール化:プログラムはより小さな部分に分割でき、個別に転送、キャッシュ、使用可能
-
高效:単一パス(走査)で高速にデコード、検証、コンパイル可能。リアルタイム(JIT)または事前(AOT)コンパイルと同等
-
ストリーミング:すべてのデータを取得する前に、できるだけ早くデコード、検証、コンパイルを開始可能
-
並列化:デコード、検証、コンパイルを複数の独立した並列タスクに分割可能
-
移植可能:現代ハードウェアで広くサポートされていないアーキテクチャを仮定しない
主流ブラウザ(Chrome、Edge、Firefox、および WebKit)によって標準化プロセスが共同で推進されています:
WebAssembly is currently being designed as an open standard by a W3C Community Group that includes representatives from all major browsers.
P.S. このことはブラウザベンダーが主導で行っています(彼ら 4 つが一緒に事を起こしており、非常に期待できます)。ついでに開放標準を確立しています(Web 環境だけでなく)。動力源は JS ランタイム性能をさらに向上させたいことで、V8 に JIT を導入した後、性能をさらに向上させるのはもはや不可能です。JS 言語特性方面的制限(例えば解釈型、弱タイプ)に直面しているからです。Web 能力がますます強大になり、クライアント JS がますます重くなり、JS 実行性能をさらに向上させる需要仍然存在するため、WebAssembly の釜底抽薪が生まれました
二.wasm と wast
WebAssembly が 1 つのバイナリ形式を定義していることは知っています。この形式がwasmです。例えば:
0061 736d 0100 0000 0187 8080 8000 0160
027f 7f01 7f03 8280 8080 0001 0004 8480
8080 0001 7000 0005 8380 8080 0001 0001
0681 8080 8000 0007 9080 8080 0002 066d
656d 6f72 7902 0003 6763 6400 000a ab80
8080 0001 a580 8080 0001 017f 0240 2000
450d 0003 4020 0120 0022 026f 2100 2002
2101 2000 0d00 0b20 020f 0b20 010b
この 16 進数文字列に対応する C コードは:
// 辗转相除法求最大公约数
int gcd(int m, int n) {
if (m == 0) return n;
return gcd(n % m, m);
}
wasm の可読性は 0 です。この問題を緩和するために、可読性がもう少し良いテキスト形式を定義しました。wastと呼びます:
(module
(table 0 anyfunc)
(memory $0 1)
(export "memory" (memory $0))
(export "gcd" (func $gcd))
(func $gcd (; 0 ;) (param $0 i32) (param $1 i32) (result i32)
(local $2 i32)
(block $label$0
(br_if $label$0
(i32.eqz
(get_local $0)
)
)
(loop $label$1
(set_local $0
(i32.rem_s
(get_local $1)
(tee_local $2
(get_local $0)
)
)
)
(set_local $1
(get_local $2)
)
(br_if $label$1
(get_local $0)
)
)
(return
(get_local $2)
)
)
(get_local $1)
)
)
括弧は少し Lisp 風ですが、少なくとも読めます。例えば:
// 导出了两个东西,分别叫 `memory`和`gcd`
(export "memory" (memory $0))
(export "gcd" (func $gcd))
// 函数签名,接受 2 个 int32 类型参数,返回 int32 类型值
(func $gcd (; 0 ;) (param $0 i32) (param $1 i32) (result i32)
// 函数体...就不猜了
P.S.wast と wasm は相互変換可能です。詳細は WABT: The WebAssembly Binary Toolkit を参照
また、ブラウザの Source パネルで別のテキスト命令を見ることができます:
func (param i32 i32) (result i32)
(local i32)
block
get_local 0
i32.eqz
br_if 0
loop
get_local 1
get_local 0
tee_local 2
i32.rem_s
set_local 0
get_local 2
set_local 1
get_local 0
br_if 0
end
get_local 2
return
end
get_local 1
end
wast とよく似ていますが、名前があるかどうかは分かりません。または wast に属するのでしょうか?これはブラウザが wasm から変換したものです
三.试玩環境
環境要件:
-
C/C++ コンパイル環境 Emscripten
-
WebAssembly をサポートするブラウザ(最新の Chrome はデフォルトでサポート)
オンライン環境
無傷の试玩環境があります:WebAssembly Explorer
COMPILEしてDOWNLOAD すれば wasm が得られ、非常に便利です
注意:デフォルトは C++ 環境です。C を使いたい場合は、左側で C99 または C89 を選択してください。否则関数名が壊れて編成されます。例えば C++11 の wast:
(module
(table 0 anyfunc)
(memory $0 1)
(export "memory" (memory $0))
(export "_Z3gcdii" (func $_Z3gcdii))
(func $_Z3gcdii (; 0 ;) (param $0 i32) (param $1 i32) (result i32)
(local $2 i32)
(block $label$0
(br_if $label$0
(i32.eqz
(get_local $0)
)
)
(loop $label$1
(set_local $0
(i32.rem_s
(get_local $1)
(tee_local $2
(get_local $0)
)
)
)
(set_local $1
(get_local $2)
)
(br_if $label$1
(get_local $0)
)
)
(return
(get_local $2)
)
)
(get_local $1)
)
)
関数名が _Z3gcdii に編成されています。名前空間などのものが怪しいと推測します。C++ はあまり詳しくないので、素直に C を使います
P.S.C/C++ 以外でも、他の言語でも WebAssembly を试玩できます。例えば Rust
ローカル環境
-
プラットフォーム SDK をダウンロード
-
インストール手順 に従う
問題なければ、これでインストール完了です。emcc -v で試してみてください:
INFO:root:(Emscripten: Running sanity checks)
emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 1.37.22
clang version 4.0.0 (emscripten 1.37.22 : 1.37.22)
Target: x86_64-pc-windows-msvc
Thread model: posix
InstalledDir: D:\emsdk-portable-64bit\clang\e1.37.22_64bit
INFO:root:(Emscripten: Running sanity checks)
Windows 環境では DLL 缺失(MSVCP140.dll)のエラーに遭遇する可能性があります。手動で 必要な C++ 環境 をインストールできます。詳細は MSVCP140.dll not found · Issue #5605 · kripken/emscripten を参照
その後、1 つ編成して試せます(以前の C コードをファイル gcd.c として保存):
emcc ./c/gcd.c -Os -s WASM=1 -s SIDE_MODULE=1 -s BINARYEN_ASYNC_COMPILATION=0 -o ./output/gcd.wasm
P.S. より多くの用法は Emscripten Tutorial を参照
得られた gcd.wasm の内容は以下の通り:
0061 736d 0100 0000 000c 0664 796c 696e
6b80 80c0 0200 010a 0260 027f 7f01 7f60
0000 0241 0403 656e 760a 6d65 6d6f 7279
4261 7365 037f 0003 656e 7606 6d65 6d6f
7279 0200 8002 0365 6e76 0574 6162 6c65
0170 0000 0365 6e76 0974 6162 6c65 4261
7365 037f 0003 0403 0001 0106 0b02 7f01
4100 0b7f 0141 000b 072b 0312 5f5f 706f
7374 5f69 6e73 7461 6e74 6961 7465 0002
0b72 756e 506f 7374 5365 7473 0001 045f
6763 6400 0009 0100 0a40 0327 0101 7f20
0004 4003 4020 0120 006f 2202 0440 2000
2101 2002 2100 0c01 0b0b 0520 0121 000b
2000 0b03 0001 0b12 0023 0024 0223 0241
8080 c002 6a24 0310 010b
注意:メソッド名はデフォルトでアンダースコア(_)プレフィックスが追加されます。本例ではエクスポートされたメソッド名は _gcd です。詳細は Interacting with code を参照:
The keys passed into mergeInto generate functions that are prefixed by _. In other words my_func: function() {}, becomes function _my_func() {}, as all C methods in emscripten have a _ prefix. Keys starting with $ have the $ stripped and no underscore added.
JS でモジュールインターフェースを使用する際はアンダースコアを追加する必要があります(それを削除できる設定項目があるかどうかは分かりません)
四.试玩
WebAssembly.compile(new Uint8Array(`
0061 736d 0100 0000 0187 8080 8000 0160
027f 7f01 7f03 8280 8080 0001 0004 8480
8080 0001 7000 0005 8380 8080 0001 0001
0681 8080 8000 0007 9080 8080 0002 066d
656d 6f72 7902 0003 6763 6400 000a ab80
8080 0001 a580 8080 0001 017f 0240 2000
450d 0003 4020 0120 0022 026f 2100 2002
2101 2000 0d00 0b20 020f 0b20 010b
`.match(/\S{2}/g).map(s => parseInt(s, 16))
)).then(module => {
const instance = new WebAssembly.Instance(module);
console.log(instance.exports);
const { gcd } = instance.exports;
console.log('gcd(328, 648)', gcd(328, 648));
});
16 進数文字列はオンライン试玩からで、最初の wasm サンプル内容と一致します。これらを Chrome の Console に貼り付けて実行すればよいです。すべて正常であれば、エラーが発生します:
VM40:1 Uncaught (in promise) CompileError: WasmCompile: Wasm code generation disallowed in this context
これはデフォルトの CSP(コンテンツセキュリティポリシー)制限によるものです。簡単に解決できます。シークレットモード(Ctrl/CMD + Shift + N)を開くだけです
出力が得られます:
{memory: Memory, gcd: ?}
gcd(328, 648) 8
1 行目は WebAssembly をロードして得たモジュールのエクスポート内容で、メモリオブジェク��と gcd メソッドを含みます。2 行目の出力はハイパフォーマンスモジュールを呼び出して計算した最大公約数です
WebAssembly.compile などの関連 API は以下を参照できます:
また、ローカルコンパイルで得たバージョンは imports env を要求します(しかも関数名にアンダースコア _ プレフィックスが追加されます):
WebAssembly.compile(new Uint8Array(`
0061 736d 0100 0000 000c 0664 796c 696e
6b80 80c0 0200 010a 0260 027f 7f01 7f60
0000 0241 0403 656e 760a 6d65 6d6f 7279
4261 7365 037f 0003 656e 7606 6d65 6d6f
7279 0200 8002 0365 6e76 0574 6162 6c65
0170 0000 0365 6e76 0974 6162 6c65 4261
7365 037f 0003 0403 0001 0106 0b02 7f01
4100 0b7f 0141 000b 072b 0312 5f5f 706f
7374 5f69 6e73 7461 6e74 6961 7465 0002
0b72 756e 506f 7374 5365 7473 0001 045f
6763 6400 0009 0100 0a40 0327 0101 7f20
0004 4003 4020 0120 006f 2202 0440 2000
2101 2002 2100 0c01 0b0b 0520 0121 000b
2000 0b03 0001 0b12 0023 0024 0223 0241
8080 c002 6a24 0310 010b
`.match(/\S{2}/g).map(s => parseInt(s, 16))
)).then(module => {
let imports = {
env: {
memoryBase: 0,
memory: new WebAssembly.Memory({ initial: 256 }),
tableBase: 0,
table: new WebAssembly.Table({ initial: 0, element: 'anyfunc' })
}
};
const instance = new WebAssembly.Instance(module, imports);
console.log(instance.exports);
// 注意下划线前缀
const { _gcd } = instance.exports;
console.log('gcd(328, 648)', _gcd(328, 648));
});
類似の出力が得られます:
{__post_instantiate: ?, runPostSets: ?, _gcd: ?}
gcd(328, 648) 8
Emscripten がデフォルトでいくつかの重要でないものを追加したのでしょう。機能的には簡易版と同等です
五.長所短所および应用场景
長所
- コード体积が非常に小さい
300k 程度(圧縮後)の JavaScript ロジックを WebAssembly で書き直すと、体积はわずか 90k 程度になります
しかし、WebAssembly を使用するには 50k-100k の JavaScript ライブラリをインフラストラクチャとして導入する必要があります
- セキュリティが少し向上
ソースコードに対応する WebAssembly テキスト命令は依然として全く隠されていませんが、リバースコストが少し高くなりました
- 性能向上
理論上 WebAssembly は native に近い実行性能を持ちます。解釈環節をスキップし、ファイル体积も転送面で有利だからです
もちろん、前提はビジネスコード量が非常に大きく、極致性能を要求するシーンです。benchmark などの重複実行シーンでは、JIT は AOT よりあまり遅くありません
短所
現在 能力有限 です:
-
数種類の基本データ型のみサポート(i32 / i64 / f32 / f64 / i8 / i16)
-
DOM および他の Web API に直接アクセス不可
-
GC を制御不可
应用场景
WebAssembly はブラウザのために標準的な実行可能バイナリ形式を定義しました。これにより、より多くの開発者が統一されたコンパイルメカニズムを通じて参加でき、繁栄した Web エコシステムを共同構築できます。ビジョンは美好的ですが、いくつかの実際問題に直面しています
まず WebAssembly の初衷は「Web 環境でハイパフォーマンスアプリケーションをサポートすること」です。性能ボトルネックを突破するために、可能な应用场景は:
-
動画デコード
-
画像処理
-
3D/WebVR/AR 可視化
-
レンダリングエンジン
-
物理エンジン
-
圧縮/暗号化アルゴリズム
-
...など演算量が比較的多いシーン
もちろん、これらのサポートは将来ブラウザに内置される可能性もあり、「拡張プラグイン」などの方式で行う必要はありません。しかし WebAssembly の真の意義は、高性能「native」モジュールを自行拡張できる能力を提供したことです。ブラウザが提供し、互換性が許容可能になるまで待つには相当長い時間がかかる可能性があります。しかし、この能力があれば、市場主流ブラウザが某个ネイティブ特性をサポートするのを苦しく待つ必要はなく、自分で手を動かして搞定できます。しかも互換性差異は存在しません。逆に、人気のあるコミュニティモジュールが湧現し、徐々にブラウザネイティブサポートとして吸収され、エコシステムが Web 環境にフィードバックされます
コメントはまだありません