2017.12.22

WebAssembly


こんにちは。次世代システム研究室のT.Kです。
今回は”WebAssembly”について、少し触れてみようと思います。
単語自体は以前から耳にしていましたが、先日『WebAssemblyが主要ブラウザでサポート』と言う記事を目にした事がキッカケです。
個人的にコンパイラ・アセンブラ・インタプリタなどの単語を聞くと少しテンションが上がります。

先日話題になったCoinhiveでもWebAssemblyが利用されていたようですね。

WebAssemblyとは


一言で言うと『ブラウザで低級なバイナリを実行できるようにする技術』になります。
WEB画面の処理には主にJavaScriptを用いるかと思いますが、WebAssemblyでも対応が可能になります。
利用するメリットとしては、フロント処理をC/C++等を用いて実装する事が出来、且つ処理速度の向上が見込める事になります。
WebAssemblyは前述の通り、実行ファイルは機械語(WebAssembly用バイナリコード)となる為、事前のコンパイルが必要になりますが、コンパイルを介する事で複数の言語に対応する事が可能となります。
現在対応している言語はC/C++やRUSTになりますが、メジャーな言語は今後対応されていく予定です。


各言語からWebAssemblyへの変換は、上記画像の様な流れで変換されていきます。
見ての通り、コンパイルプロセスが長く手間が掛かるは問題視されていますので、こちらに関しては今後改善されていくと思います。

ポイントとしては、(現状)WebAssemblyはJavaScriptの代わりに成るものではなく、JavaScriptの一部処理を肩代わりする事で全体の処理速度を向上させる仕組みであると言う事になります。

WebAssemblyの特徴


特徴としては主に下記になります。
  1. JavaScript以外の言語を用いてフロントエンド処理を記述・実行が可能
  2. LLVM(中間言語)を利用する事で複数言語への対応
  3. WebAssemblyの実行ファイルはバイトコードになり、ファイルサイズが小さく、ロード時間の短縮可能
  4. 実行はJavaScriptからだが、WebAssemblyからJavaScript処理の呼出しが可能

ブラウザ対応情報


WebAssemblyへの対応はMozilla、Google、Microsoft、Appleが標準フォーマットとして対応する事を2015年6月に発表し、
Chromeは2017年7月にリリースされた「Chrome 60」からWebAssemblyがデフォルトで有効となり、8月にFirefox、9月にSafari、10月にEdgeがWebAssemblyに対応しました。

参照サイト

コンパイル環境構築


環境はこちらのサイトを参考にMacBook Pro(macOS Sierra 10.12.6)上に構築しました。
ビルドにはcMakeのバージョンが3.4.3以上必要です。なぜか、私は3.4.0などを入れてしまい入れ直しましたが、基本最新版を入れましょう。
その他は手順通り進める事で下記バージョンでインストールされました
  1. clang version 6.0.0
  2. LLVM version 6.0.0svn
手順自体は複雑ではありませんが、結構構築時間は掛かります。
C/C++を最終的なバイトコードまで個々に変換する為、各コンパイラ環境を準備しています。

サンプルと同じソースを手順通りコンパイルしてみました。

1. C/C++(sample.c)
int c=0;
int count(){return c++;}

2. LLVM IR(sample.ll)
2行のコードがLLVMに変換すると結構行数が増えました。
; ModuleID = 'sample.c'
source_filename = "sample.c" 
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" 
target triple = "wasm32" 

@c = hidden global i32 0, align 4

; Function Attrs: noinline nounwind optnone
define hidden i32 @count() #0 {
entry:
  %0 = load i32, i32* @c, align 4
  %inc = add nsw i32 %0, 1
  store i32 %inc, i32* @c, align 4
  ret i32 %0
}

attributes #0 = { noinline nounwind optnone "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="generic" "unsafe-fp-math"="false" "use-soft-float"="false" }

!llvm.module.flags = !{!0}
!llvm.ident = !{!1}

!0 = !{i32 1, !"wchar_size", i32 4}
!1 = !{!"clang version 6.0.0 (http://llvm.org/git/clang.git 6f91ec10a74ef88d32422ddba2fb5a17a651df58) (http://llvm.org/git/llvm.git 6463d1106bef37b4073329cc2061064892752958)"}

3. アセンブリコード(sample.s)
個人的にはLLVMよりもこちらの方がみやすいですね
        .text
        .file   "sample.c" 
        .hidden count                   # -- Begin function count
        .globl  count
        .type   count,@function
count:                                  # @count
        .result         i32
        .local          i32
# %bb.0:                                # %entry
        i32.const       $push0=, 0
        i32.const       $push3=, 0
        i32.load        $push5=, c($pop3)
        tee_local       $push4=, $0=, $pop5
        i32.const       $push1=, 1
        i32.add         $push2=, $pop4, $pop1
        i32.store       c($pop0), $pop2
        copy_local      $push6=, $0
                                        # fallthrough-return: $pop6
        .endfunc
.Lfunc_end0:
        .size   count, .Lfunc_end0-count
                                        # -- End function
        .hidden c                       # @c
        .type   c,@object
        .bss
        .globl  c
        .p2align        2
c:
        .int32  0                       # 0x0
        .size   c, 4

        .ident  "clang version 6.0.0 (http://llvm.org/git/clang.git 6f91ec10a74ef88d32422ddba2fb5a17a651df58) (http://llvm.org/git/llvm.git 6463d1106bef37b4073329cc2061064892752958)" 


4. WebAssemblyのテキストコード(sample.wast)
(module
 (table 0 anyfunc)
 (memory $0 1)
 (data (i32.const 12) "\00\00\00\00")
 (export "memory" (memory $0))
 (export "count" (func $count))
 (func $count (; 0 ; ) (result i32)
  (local $0 i32)
  (i32.store offset=12
   (i32.const 0)
   (i32.add
    (tee_local $0
     (i32.load offset=12
      (i32.const 0)
     )
    )
    (i32.const 1)
   )
  )
  (get_local $0)
 )
)

5. WebAssemblyのバイトコードに変換(sample.wasm)
※バイトコードは割愛します。
上記レベルのコードであれば、コンパイル自体は一瞬で完了します。

※後から見つけましたがThe WebAssembly Binary Toolkitを利用すれば、一発で環境構築出来るようです。

簡易実行方法


ChromeのDevTools.Consoleで簡易的に動作確認をする事が可能です。
codeに読み込んでいるのは、WebAssemblyモジュールのバイナリコードになります。

code = [
	 0x00,0x61,0x73,0x6D,0x01,0x00,0x00,0x00
	,0x01,0x87,0x80,0x80,0x80,0x00,0x01,0x60
	,0x02,0x7F,0x7F,0x01,0x7F,0x03,0x82,0x80
	,0x80,0x80,0x00,0x01,0x00,0x07,0x87,0x80
	,0x80,0x80,0x00,0x01,0x03,0x61,0x64,0x64
	,0x00,0x00,0x0A,0x8D,0x80,0x80,0x80,0x00
	,0x01,0x87,0x80,0x80,0x80,0x00,0x00,0x20
	,0x00,0x20,0x01,0x6A,0x0B];
module = new WebAssembly.Module(Uint8Array.from(code))
instance = new WebAssembly.Instance(module)
instance.exports.add(3, 5)

実行すると下記のように8と言う数値が出力されるかと思います。


まとめ


今回は触りの部分だけでしたが、引続き注目の技術になりそうな予感を感じました。
実サービスに投入する環境は着々と整って来ているので、Knowledge確保の為にテスト導入を検討しても良いのではないかと思っています。
普段使い慣れているChromeでしれっとWebAssemblyが実行出来た事は新鮮でした。

動作確認の為に毎回コンパイルが必要な事は開発者として気になるところですが、工夫次第である程度改善出来そうです。
一部C/C++コードでLLVMへの変換時にエラーが見られましたので、現時点では全ての機能には対応していないかも知れませんが、現在手間となっているコンパイル周りを含めて、近々改善される事を期待しています。

次世代システム研究室では、最新技術に興味のある方を募集しています。
他にもアプリケーション開発者の方、次世代システム研究室にご興味を持って頂ける方がいらっしゃいましたら、ぜひ募集職種一覧からご応募をお願いします。