WebAssembly:Dev Basics/Keyword
WebAssemblyは、C/C++などで書かれたコードをWebページでも利用できるようにすることを念頭に置いたバイナリフォーマット。
WebAssemblyは、Web(およびWeb以外の)プラットフォームを対象とした、サイズとロード時間の両面での効率性を高めるバイナリフォーマット。「wasm」とも呼ばれる。C/C++などで書かれたコードを、WebAssemblyをターゲットにコンパイルすることで、それらのコードをブラウザ内で実行できるようになる。W3CのWebAssembly Working Groupで標準化作業が進められている。
WebAssemblyの目標
今も述べたように、WebAssemblyはサイズとロード時間の面で効率的なバイナリフォーマットを定義するものであるが、その仕様策定と実装は段階を追って行われるようになっている。現在はWebAssemblyの初期API(MVP:Minimum Viable Product)については合意が形成され、各ブラウザベンダーでのブラウザへの実装作業が進められている。MVPは、asm.jsと同様な機能を標準化したものであり、C/C++を主要な対象としている。
その後の目標としては、スレッド機能、例外処理、SIMD、ガベージコレクション機能、ECMAScriptのモジュール機能との統合、C/C++以外の言語のサポートなどが挙げられている。また、既存のWebプラットフォームでのコードの実行、統合も目標として掲げられている。これには、JavaScriptコードとの相互運用、ブラウザが提供するJavaScript APIの利用などが含まれている。加えて、セキュアであること(サンドボックス化された実行環境でのコードの実行やSame Origin Policyの適用など)もWebAssemblyの目標の1つである。
WebAssemblyの利用箇所としては、コードの高速な実行が必要な部分。例えば、ブラウザ内での画像/映像編集、ゲーム、VRなどが考えられている。ブラウザ外でも、ゲーム配信サービス、サーバサイドアプリ、モバイルデバイス上でのJavaScriptコードとWebAssemblyコードを組み合わせての実行などが考えられている。これらの詳細については「Use Cases」ページを参照されたい。
なお、WebAssemblyはJavaScriptを置き換えるものではないことには注意しておこう。むしろ、WebAssemblyはJavaScriptを補完するものであり、C/C++をはじめとする言語で書かれた(高速性が求められる)コードをWebAssemblyコードとしてWebプラットフォーム上で実行し、JavaScriptからはそうしたコードを利用したり、HTML/CSS/JavaScriptを利用してそうしたコードに対するUIを構築するといった使い方が想定されている。
WebAssemblyを作成して、ブラウザで利用してみる
ここではCで記述した階乗を求めるコードを、WebAssemblyに変換し、それをWebブラウザ内で実行してみよう。
int fact1(int n) {
int result = 1;
for (int i = n; i > 1; i--)
result *= i;
return result;
}
int fact2(int n, int result) {
if (n == 0)
return result;
else if (n == 1)
return result;
else
return fact2(n - 1, n * result);
}
なお、ここではWebAssemblyへのコンパイルには、Emscriptenを利用した。Emscriptenのビルドについては「Getting Started」ページを参照してほしい。このページを参考にして、Emscriptenのビルドを行い、上のコードを「emcc test.c -O1 -s WASM=1 -s SIDE_MODULE=1 -o test.html」コマンドでコンパイルすることで、WebAssemblyを含んだtest.wasmファイルを生成した。emccコマンドのオプション「-s WASM=1」により、WebAssembly(wasm)コードが生成されるようになる。「-s SIDE_MODULE=1」はこれがEmscriptenのサイドモジュールであることを意味する。「-O1」オプションは最適化を行うオプションだ。
WebAssemblyコードを実際に利用しているのが、以下のHTMLファイルになる。
<!doctype html>
<html>
<head>
<meta charset='utf-8' />
<title>insider.net WebAssembly test</title>
</head>
<body>
<input type='text' id='num'input type='button' id='getFact' value='get Factorial' />
<p id="result1"></p>
<p id="result2"></p>
<script>
var importObject = {
env: {
memoryBase: 0,
memory: new WebAssembly.Memory({ initial: 256 }),
tableBase: 0,
table: new WebAssembly.Table({ initial: 0, element: 'anyfunc' })
}
};
fetch('test.wasm')
.then(response => response.arrayBuffer())
.then(bytes => WebAssembly.compile(bytes))
.then(module => WebAssembly.instantiate(module, importObject))
.then(instance => {
console.log(instance.exports);
let num = document.getElementById('num');
let btn = document.getElementById('getFact');
let result1 = document.getElementById('result1');
let result2 = document.getElementById('result2');
let fact1 = instance.exports._fact1;
let fact2 = instance.exports._fact2;
btn.addEventListener('click', () => {
result1.innerText =
`factorial of ${num.value} is ${fact1(num.value)}`;
result2.innerText =
`factorial of ${num.value} is ${fact2(num.value, 1)}`;
});
});
</script>
</body>
</html>
WebAssemblyはバイナリフォーマットであり、.wasmファイルに格納されているコードはコンパイル→モジュール生成→インスタンス生成という手順を踏むことで利用できるようになる。これを行っているのが、上のHTMLに含まれるコードのうちの次の部分である。
var importObject = {
env: {
…… WebAssemblyからインスタンスを生成するために必要な設定 ……
}
};
fetch('test.wasm')
.then(response => response.arrayBuffer())
.then(bytes => WebAssembly.compile(bytes))
.then(module => WebAssembly.instantiate(module, importObject))
.then(instance => {
…… WebAssemblyのインスタンスを利用して何かを行う ……
});
上のコード例では.wasmファイルをfetch APIで取得し、それをarrayBufferメソッドでバイト配列化してから、compileメソッドでコンパイルし、そこから得られたモジュールからinstantiateメソッドを使ってインスタンスを生成している。最後に、インスタンスを利用するという流れになっている。
instantiateメソッドで使用しているimportObjectオブジェクトには、WebAssembly.instantiateメソッドでインスタンスを生成するために必要な情報を記述する。上で記述しているenv.memoryプロパティなどについては「WebAssembly Dynamic Linking」ページなどを参照されたい。
実際に行っている処理は次のようになっていた。
console.log(instance.exports);
let num = document.getElementById('num');
let btn = document.getElementById('getFact');
let result1 = document.getElementById('result1');
let result2 = document.getElementById('result2');
let fact1 = instance.exports._fact1;
let fact2 = instance.exports._fact2;
btn.addEventListener('click', () => {
result1.innerText =
`factorial of ${num.value} is ${fact1(num.value)}`;
result2.innerText =
`factorial of ${num.value} is ${fact2(num.value, 1)}`;
});
WebAssemblyコードがエクスポートしているものはインスタンスのexportsプロパティから参照できる。最初の行では、fact1関数やfact2関数がエクスポートされているかを確認するために、コンソールにexportsプロパティの値を出力している。その後は、Webページに置かれたボタンなどの要素を取得している。
fact1変数とfact2変数には、このWebAssemblyがエクスポートしている2つの関数を代入している(ここでは「instance.exports._fact1」のように関数名に「_」が前置されている点に注意。これは以下の実行画面から分かるように実際に「_」が前置されているために付加した。コンパイラの処理によっては異なる名前となるかもしれない)。最後にボタンのclickイベントハンドラーでは、WebAssemblyがエクスポートしている関数を呼び出して、その結果をWebページに反映するようにしているだけだ。
実際にこれを実行した結果を以下に示す。
デベロッパーツールの[コンソール]タブにはWebAssemblyがエクスポートしている関数が表示されていることと、実際に階乗が計算され、その結果がWebページに表示されている点に注目しよう。
WebAssemblyは、C/C++などで書かれたコードをWebページでも利用できるようにすることを念頭に置いたバイナリフォーマットである。本稿では取り上げなかったが、Node.jsの新しいバージョンでもWebAssemblyがサポートされるようになったり、.NETの世界でもWebAssemblyをターゲットとしたコード生成が行われるようになったりと、その存在感は高まっている。
Copyright© Digital Advantage Corp. All Rights Reserved.