More than 5 years have passed since last update.
WebAssembly事始め(Chrome57記念)
10
Last updated at Posted at 2017-03-15
1 / 27Page 1 of 27
Ref
- WebAssembly.org
- Can I use webassembly ?
- WebAssembly - MDN
- Binaryen
- WebAssemblyを使ってみる(C/C++をWebAssemblyに変換してChromeで実行)
Preface
-
Chrome 57でWebAssemblyがデフォルト有効になった
-
とりあえず現状のAPIについてはすぐ使える状態になってる
-
試してみたし
-
社内勉強会での発表内容を展開
WebAssembly?
WebAssembly.org
👁 webassembly-org.png
- 主にシステムプログラミング言語からコンパイルできて、ブラウザ上で実行できる(ようになる予定の)バイナリ形式
- 現段階では、バイナリをJS APIでmoduleとして読み込み、exportしている関数をJSから使うというインターフェイス
- 実行自体はJSコードとは全く違う系で行われ、一般にJSより速い
- なぜWebAssemblyは速いか
- ざっくりいうと、JSコードの実行に必要なParse/Compile/OptimizeといったフェイズがWebAssemblyでは大規模に省略できるため
- サーバサイドでのビルド時にそれらのフェイズを通過済みであり、相当機械語に近い形式になっている
- 今のところGCもない
Let's Try
Preparation
- llvm/clang
- ターゲットアーキにWebAssemblyを含む
clangとllcがインストールされる - 30分くらいかかる
- ターゲットアーキにWebAssemblyを含む
$ WORKDIR=$(pwd)
$ git clone http://llvm.org/git/llvm.git
$ git clone http://llvm.org/git/clang.git llvm/tools/clang
$ git clone http://llvm.org/git/compiler-rt llvm/projects/compiler-rt
$ 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
- binaryen
-
s2wasmとwasm-asがインストールされる
-
-
sexpr-wasm-prototypeでwasmへのコンパイルを行わないと動かない、という記事が結構見つかる
が、2017/03時点ではwasm-asが生成するバイナリで(少なくともFirefox/Chromeなら)動くようになった模様
$ git clone https://github.com/WebAssembly/binaryen.git
$ cd binaryen
$ cmake . && make
$ sudo make install
Build sequence
C source (.c)
-> LLVM-IR (.ll)
-> Assembly (.s)
-> WebAssembly text (.wast)
-> WebAssembly (.wasm)
(追記) dcodeIO/webassembly
- 記事公開少し後に出てきたやつ
-
npmからインストール可能で、ほぼ前項の経路をたどるビルド環境を簡単に構築できる - Cソースからwasmバイナリを作って実行してみたい人には今はこちらがオススメ
Example code in C
int fib(n1, n2, i, max) {
if (i == max) return n1;
return fib(n2, n1 + n2, i + 1, max);
}
int fib_to(max) {
return fib(0, 1, 0, max);
}
Example code in JavaScript
function fib(n1, n2, i, max) {
if (i == max) return n1;
return fib(n2, n1 + n2, i + 1, max);
}
function fib_to(max) {
return fib(0, 1, 0, max);
}
Build
$ cd src/
$ clang -S -emit-llvm -Oz --target=wasm32 fib.c
$ llc fib.ll -march=wasm32
$ s2wasm -s 100000 fib.s > fib.wast
$ wasm-as fib.wast > fib.wasm
-
s2wasmを単に呼ぶと"memory access out of bounds"例外で実行時に止まる。
-sオプションでメモリ領域を適当に確保できる 参考
Execution
<script>タグでソースを指定してembedするような便利な経路は現状ない。
- XHRでバイナリファイル取得
- バイナリを配列に変換し、
WebAssembly.instantiate()で実行可能なInstanceに変換 - Public関数がexportされるので、好きに使う
With fetch
fetch('./fib.wasm').then(response =>
response.arrayBuffer()
).then(bytes =>
WebAssembly.instantiate(bytes, {})
).then(result =>
registerHandler('wasm', result.instance.exports.fib_to) // 別で定義
)
Demo
👁 スクリーンショット 2017-03-15 17.39.11.png
- 指定されたフィボナッチ数を計算して
performance.now()の差分で計測 - 50,000回の平均
- CベースWASMが大体10倍くらい速い
- まだwasm32(32bit-integer)であるため、単にやるとF_47でオーバーフローする
- 余談)JSの方もF_79で誤差が出る。巨大整数取り扱い時の問題らしい。
- 正)14472334024676221
- 誤)14472334024676220
- Fib100
Portability
- macOS sierra
- Chrome 57 👁 :white_check_mark:
- JS: 0.000761ms, WASM: 0.000073ms
- Firefox 52 👁 :white_check_mark:
- JS: 0.003311ms, WASM: 0.000219ms
- Opera 43 👁 :x:
- JS: 0.001642ms
-
#enable-webassemblyを有効にすると、WebAssemblyオブジェクトは使える -
WebAssembly.instantiate()APIが未実装 -
WebAssembly.compile()APIはあるが、wasm-asが吐いたバイナリをデコードできない模様 - こちらは
sexpr-wasm-prototypeを使ったバイナリなら読めるかも
- Safari 10 👁 :x:
- JS: 0.003420ms
- Chrome 57 👁 :white_check_mark:
- Windows 10
- Chrome 57 👁 :white_check_mark:
- Firefox 52 👁 :no_entry:
- 試してない。多分できるのでは
- Edge 👁 :x:
- JS版は実行できはしたが引くほど遅かった。末尾再帰最適化に未対応だかららしい
- IE 11 👁 :x:
- 唯一ES6記法を知らない情けないやつ
- Chrome 57 👁 :white_check_mark:
- Android
- Chrome 57 (Beta) 👁 :white_check_mark:
- JS: 0.002986ms, WASM: 0.000373ms
- Chrome 57 (Beta) 👁 :white_check_mark:
- iOS
- Chrome 57 👁 :x:
- JS: 0.003792ms
- WASMはバージョン的には合っているが動かないようだった
- iOS版ブラウザアプリのエンジンは今のところWebKit(Safariと同じ)が強制されるため
- Chrome 57 👁 :x:
Deploy
- 単にローカルでコンパイルしてバイナリを適当にサーブ
- wasmの容量は、今回のExample Codeだと最適化しても若干JSソースより大きい
- JSは350B
- Wasmは429B
Future
- wasmまでのコンパイル経路が確立していて、ツールも揃っている言語は少ない
-
Rustでもできるようではある
-
rustupを使ってwasm32をターゲットアーキとして追加 -
cargoにオプションを付けてビルド、もしくはrustc --emit=llvm-irして云々
-
- golangはTracking Issueだけ立っている
- elixir-lang-core MLの過去ログ見てたらトピックはあった
Impression
- LLVM関連の環境インストールがむしろヘビー
- ひとたびコンパイル経路が確立すれば意外とすんなり動く
- ひまな人はCで爆速フロントエンドロジックを書いてみては
Appendix
LLVM-IR
; ModuleID = 'fib.c'
source_filename = "fib.c"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32"
; Function Attrs: minsize nounwind optsize readnone
define hidden i32 @fib(i32 %n1, i32 %n2, i32 %i, i32 %max) local_unnamed_addr #0 {
entry:
br label %tailrecurse
tailrecurse: ; preds = %if.end, %entry
%n1.tr = phi i32 [ %n1, %entry ], [ %n2.tr, %if.end ]
%n2.tr = phi i32 [ %n2, %entry ], [ %add, %if.end ]
%i.tr = phi i32 [ %i, %entry ], [ %add1, %if.end ]
%cmp = icmp eq i32 %i.tr, %max
br i1 %cmp, label %return, label %if.end
if.end: ; preds = %tailrecurse
%add = add nsw i32 %n2.tr, %n1.tr
%add1 = add nsw i32 %i.tr, 1
br label %tailrecurse
return: ; preds = %tailrecurse
ret i32 %n1.tr
}
; Function Attrs: minsize norecurse nounwind optsize readnone
define hidden i32 @fib_to(i32 %max) local_unnamed_addr #1 {
entry:
br label %tailrecurse.i
tailrecurse.i: ; preds = %if.end.i, %entry
%n1.tr.i = phi i32 [ 0, %entry ], [ %n2.tr.i, %if.end.i ]
%n2.tr.i = phi i32 [ 1, %entry ], [ %add.i, %if.end.i ]
%i.tr.i = phi i32 [ 0, %entry ], [ %add1.i, %if.end.i ]
%cmp.i = icmp eq i32 %i.tr.i, %max
br i1 %cmp.i, label %fib.exit, label %if.end.i
if.end.i: ; preds = %tailrecurse.i
%add.i = add nsw i32 %n2.tr.i, %n1.tr.i
%add1.i = add nuw nsw i32 %i.tr.i, 1
br label %tailrecurse.i
fib.exit: ; preds = %tailrecurse.i
ret i32 %n1.tr.i
}
attributes #0 = { minsize nounwind optsize readnone "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" }
attributes #1 = { minsize norecurse nounwind optsize readnone "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.ident = !{!0}
!0 = !{!"clang version 5.0.0 (http://llvm.org/git/clang.git e3a2454ea8263759d2ac667d3e086bb15269e10e) (http://llvm.org/git/llvm.git cd2a5b62d109d6864f2c566efab8e1dfb93f0550)"}
Assembly
.sファイルではあるものの、通常のアセンブリではない模様
.text.file"fib.ll".hiddenfib.globlfib.typefib,@functionfib:# @fib.parami32,i32,i32,i32.resulti32# BB#0: # %entryi32.sub$3=,$3,$2.LBB0_1:# %tailrecurse# =>This Inner Loop Header: Depth=1blockloop# label1:i32.eqz$push1=,$3br_if1,$pop1# 1: down to label0# BB#2: # %if.end# in Loop: Header=BB0_1 Depth=1i32.const$push0=,-1i32.add$3=,$3,$pop0i32.add$2=,$1,$0copy_local$0=,$1copy_local$1=,$2br0# 0: up to label1.LBB0_3:# %returnend_loopend_block# label0:copy_local$push2=,$0# fallthrough-return: $pop2.endfunc.Lfunc_end0:.sizefib,.Lfunc_end0-fib.hiddenfib_to.globlfib_to.typefib_to,@functionfib_to:# @fib_to.parami32.resulti32.locali32,i32,i32# BB#0: # %entryi32.const$3=,1i32.const$2=,0.LBB1_1:# %tailrecurse.i# =>This Inner Loop Header: Depth=1blockloop# label3:i32.eqz$push1=,$0br_if1,$pop1# 1: down to label2# BB#2: # %if.end.i# in Loop: Header=BB1_1 Depth=1i32.const$push0=,-1i32.add$0=,$0,$pop0i32.add$1=,$3,$2copy_local$2=,$3copy_local$3=,$1br0# 0: up to label3.LBB1_3:# %fib.exitend_loopend_block# label2:copy_local$push2=,$2# fallthrough-return: $pop2.endfunc.Lfunc_end1:.sizefib_to,.Lfunc_end1-fib_to.ident"clang version 5.0.0 (http://llvm.org/git/clang.git e3a2454ea8263759d2ac667d3e086bb15269e10e) (http://llvm.org/git/llvm.git cd2a5b62d109d6864f2c566efab8e1dfb93f0550)"WASM-Text
(module
(table 0 anyfunc)
(memory $0 2)
(data (i32.const 4) "\b0\86\01\00")
(export "memory" (memory $0))
(export "fib" (func $fib))
(export "fib_to" (func $fib_to))
(func $fib (param $0 i32) (param $1 i32) (param $2 i32) (param $3 i32) (result i32)
(set_local $3
(i32.sub
(get_local $3)
(get_local $2)
)
)
(block $label$0
(loop $label$1
(br_if $label$0
(i32.eqz
(get_local $3)
)
)
(set_local $3
(i32.add
(get_local $3)
(i32.const -1)
)
)
(set_local $2
(i32.add
(get_local $1)
(get_local $0)
)
)
(set_local $0
(get_local $1)
)
(set_local $1
(get_local $2)
)
(br $label$1)
)
)
(get_local $0)
)
(func $fib_to (param $0 i32) (result i32)
(local $1 i32)
(local $2 i32)
(local $3 i32)
(set_local $3
(i32.const 1)
)
(set_local $2
(i32.const 0)
)
(block $label$0
(loop $label$1
(br_if $label$0
(i32.eqz
(get_local $0)
)
)
(set_local $0
(i32.add
(get_local $0)
(i32.const -1)
)
)
(set_local $1
(i32.add
(get_local $3)
(get_local $2)
)
)
(set_local $2
(get_local $3)
)
(set_local $3
(get_local $1)
)
(br $label$1)
)
)
(get_local $2)
)
)
Register as a new user and use Qiita more conveniently
- You get articles that match your needs
- You can efficiently read back useful information
- You can use dark theme
