WebAssemblyDev Basics/Keyword

WebAssemblyは、C/C++などで書かれたコードをWebページでも利用できるようにすることを念頭に置いたバイナリフォーマット。

» 2017年08月22日 05時00分 公開
[かわさきしんじInsider.NET編集部]
「Dev Basics/Keyword」のインデックス

連載目次

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


階乗を求めるコード(test.cファイル)

 なお、ここでは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を利用しているWebページ

 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のインスタンスを利用して何かを行う ……
});

WebAssemblyをJavaScriptコードから利用する流れ

 上のコード例では.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のインスタンスを使って行っている処理

 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をターゲットとしたコード生成が行われるようになったりと、その存在感は高まっている。

参考資料


「Dev Basics/Keyword」のインデックス

Dev Basics/Keyword

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。