WebAssemblyを使ってみる(C/C++をWebAssemblyに変換してChromeで実行)

  • 72
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

WebAssemblyが動くブラウザが来た

2016/03/16
とうとうWebAssemblyが動くブラウザが出てきました。

今回はChrome Canary版で、実際にCで書いたコードをWebAssemblyに変換して実行して見るところまでやってみます。

なお環境はChromebookでビルド諸々をして、Windows上のChromeで動作確認しました。

実行に関してはブラウザを入れれば良いとして、ビルドに関してはLinuxやMac系のOSでだいたい同じ手順でできるはずです。

準備

コンパイルの手順

次のような流れでコンパイルします。

  • Cのコードをclangを用いてLLVM IRにコンパイル
  • LLVM IRをアセンブリを経由してWebAssemblyのテキスト形式までコンパイル
  • テキスト形式のWebAssemblyをバイナリにコンパイル

この3つでそれぞれ使うものを順番に導入していきます。

Windowsはこちらの WindowsでWebAssemblyの環境を整える でVisualStudioとCmakeを使ってビルドしてください。

LLVM+clang

とりあえず最新のLLVMとclangをソースからコンパイルします。
WORKDIRは適当に置き換えてください。しばらく使います。

LLVMのダウンロード
export WORKDIR=/home/chronos/user/workspace
mkdir -p $WORKDIR
cd $WORKDIR
git clone http://llvm.org/git/llvm.git
cd llvm
clangのダウンロード
cd $WORKDIR/llvm/tools/
git clone http://llvm.org/git/clang.git
compiler-rtのダウンロード

いらないかもだけどまぁ他のと違って小さめだしついでに入れておく。

cd $WORKDIR/llvm/projects/
git clone http://llvm.org/git/compiler-rt

ここまで準備できたらビルドです。長い時間がかかるので覚悟しておきましょう。

cd $WORKDIR
mkdir llvm_build
cd llvm_build
cmake -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX=/usr/local -DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=WebAssembly $WORKDIR/llvm 
make -j 8
sudo make install

インストールに成功すると、今回利用するclangllcが使えるようになります。

binaryenの導入

LLVMで出力したファイルをWebAssemblyに変換するツールをインストールします。

https://github.com/WebAssembly/binaryen

git clone https://github.com/WebAssembly/binaryen.git
cd binaryen
cmake . && make
sudo make install

インストールに成功すると、binaryen-shellwasm-aswasm-disasm2wasmwasm2asms2wasmwasm.jsなどのコマンドが使えるようになります。

sexpr-wasmの導入

本来は上だけで良いばずなのですが、最後のバイトコードへの変換に使うwasm-asが、ブラウザで読めないものになってしまうので、そこだけは別のWebAssembly周りのツール群を導入します。

cd $WORKDIR
git clone https://github.com/WebAssembly/sexpr-wasm-prototype.git
cd sexpr-wasm-prototype
make
sudo cp out/sexpr-wasm /usr/local/bin

とりあえず今回使うsexpr-wasmだけは使えるようにしておきます。

WebAssemblyの生成

実際にCのコードをWebAssemblyに変換します。
どこか適当な場所にディレクトリを作って、そこで作業をしてください。

Cのコードは以下です。

int c=0;
int count(){return c++;}

今回は呼び出すたびに1ずつ加算してその結果を返す関数のみです。

これをsample.cとした場合、次のようにコンパイルします。

clang -emit-llvm --target=wasm32 -S sample.c
llc sample.ll -march=wasm32
s2wasm sample.s > sample.wast
sexpr-wasm -o sample.wasm sample.wast

流れは以下のようになっています。

  • clangでC/C++→LLVM IRに変換
    • sample.c ==> sample.ll
  • llcでLLVM IRをアセンブリコードに変換
    • sample.ll ==> sample.s
  • s2wasmでアセンブリコードをWebAssemblyのテキストコードに変換(中身はS式)
    • sample.s ==> sample.wast
  • sexpr-wasmでWebAssemblyのテキストコードをバイトコードに変換
    • sample.wast ==> sample.wasm

一応生成物で重要なものだけ軽く目を通しておきます。

wast(WebAssemblyのテキスト形式:S式)の中身
(module
  (memory 1
    (segment 8 "\00\00\00\00")
  )
  (export "memory" memory)
  (export "count" $count)
  (func $count (result i32)
    (local $$0 i32)
    (i32.store offset=8
      (i32.const 0)
      (i32.add
        (set_local $$0
          (i32.load offset=8
            (i32.const 0)
          )
        )
        (i32.const 1)
      )
    )
    (return
      (get_local $$0)
    )
  )
)
;; METADATA: { "asmConsts": {},"staticBump": 11 }

count関数とグローバル変数をexportしているのは頭の片隅に入れておきます。

wasm(WebAssemblyのバイナリ)

00000000  00 61 73 6D 0A 00 00 00 0E 0A 73 69 67 6E 61 74 75 72 65 73 .asm......signatures
00000014  01 00 01 16 13 66 75 6E 63 74 69 6F 6E 5F 73 69 67 6E 61 74 .....function_signat
00000028  75 72 65 73 01 00 0A 06 6D 65 6D 6F 72 79 01 01 01 15 0C 65 ures....memory.....e
0000003C  78 70 6F 72 74 5F 74 61 62 6C 65 01 00 05 63 6F 75 6E 74 27 xport_table...count'
00000050  0F 66 75 6E 63 74 69 6F 6E 5F 62 6F 64 69 65 73 01 15 01 01 .function_bodies....
00000064  01 33 02 08 0A 00 40 0F 00 2A 02 08 0A 00 0A 01 14 0E 00 15 .3....@..*..........
00000078  0D 64 61 74 61 5F 73 65 67 6D 65 6E 74 73 01 08 04 00 00 00 .data_segments......
0000008C  00                                                          .

だいたいこんな感じのバイナリになっています。最初の4Byteで判別できるかと思います。

ブラウザの準備

Firefoxのナイトリービルド?でも実行できるようですが、今回はChromeで動かすので、Chrome Canaryをインストールします。

https://www.google.co.jp/chrome/browser/canary.html

起動後、以下URLをアドレスバーに入力し、詳細設定画面にあるWebAssemblyを有効にします。

chrome://flags/#enable-webassembly

もう一度再起動すると、WebAssemblyが使える状態になります。

HTMLとJavaScriptの用意

WebAssemblyのバイナリコードがsample.wasmとした時、次のようなHTMLで読み込みと実行が可能です。

<!doctype html>
<html lang="en-us">
<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
  <title>WebAssembly Test</title>
</head>
<body>

<input type="button" id="countup" value="CountUp?" />

<script type='text/javascript'>
  var xhr = new XMLHttpRequest();
  xhr.open('GET', 'sample.wasm', true);
  xhr.responseType = 'arraybuffer';
  xhr.onload = function() {
    var binary = xhr.response;
    var binarray = new Uint8Array( binary );
    var module = Wasm.instantiateModule( binarray, {} );
    console.log( module );
    document.getElementById( 'countup' ).addEventListener( 'click', function (){
      this.value = module.exports.count();
    }, false);
  };
  xhr.send(null);
</script>

</body>
</html>

ここでいくつかポイントが。

  • JavaScriptみたいに、scriptタグで読み込んでくれたり、みたいな情報は今のところないっぽい?
    • 今JavaSciptでファイルをダウンロードしているが、面倒でもこれが必要。
  • 基本処理は以下の流れ
    • 入手したバイナリデータ(binary:ArrayBuffer)を配列(binarray:Uint8Array)に変換
    • Wasm.instantiateModuleで解析
    • moduleには読み込んだWebAssemblyのコードなどが入っている。

console.logでmoduleを書き出しているので、ざっと出力を見ると以下の様な感じになっています。

module: Object {
    expots: Object {
        count: function,
        memory: ArrayBuffer,
    }
}

wastでmodule配下にあったものがそのまま出てる感じですね。

とりあえず、これでページにあるボタンを押すと、0から順番に1ずつクリックするたびに数値が増えていきます。

引数周りはまた扱いが別っぽいので、そのままでは動きません。これはまた調査する予定です。

その他

コンパイルめんどい

LLVM IRから直接wasmを出力したいので今回はLLVMから入れましたが、clangからだとemscriptenを用いた例が海外でよく見られました。

https://github.com/kripken/emscripten

C/C++との連携だけならこれを導入するのが良いようです。

コンパイルめんどいその2

一応c2wasmというコマンドを作りました。

手動でコンパイルできるように各種コマンドを用意していれば使えます。

https://github.com/HirokiMiyaoka/til/blob/master/WebAssembly/c2wasm

以下のように導入できます。

wget https://raw.githubusercontent.com/HirokiMiyaoka/til/master/WebAssembly/c2wasm
chmod a+x c2wasm
sudo cp c2wasm /usr/local/bin

以下のようにして使います。

c2wasm sample.c
c2wasm sample.c -o output.wasm

手順なんとかしたい

無駄にsファイル吐いてたりするので、何とかしてLLVM IRから直接wasmにしたい。

最後に

とりあえずHello, world!は無理でしたが、簡単な数値計算して、JavaScriptの世界に持ってくるくらいのことをやってみました。

今後ブラウザだけでなく、Electronも対応すれば、ゲームやアプリ面でさらなる活躍が期待できる技術なので、早いうちからいろいろと試してみたいところです。

次は引数の受け渡しや文字列の扱い周りに挑戦したいですね。