WebAssemblyを試してみた
メモ.WebAssembly については WebAssembly のコンセプト - WebAssembly | MDN を参考にするとよさそう.
さらに追記
$ nvm install 8.0.0 $ nvm use 8.0.0 $ node -v v8.0.0 $ node > WebAssembly {}
新しくリリースされた v8.0.0 の Node.js ではデフォルトで使えるようになってるみたい.
追記のところで main 関数がとか言っていたけど Emscripten を使えばその辺りもいい感じしてもらえるっぽい.
見直してみると,色々と勘違いしているところもあるけれど記録としてそのままに.
追記
main 関数があっても動かせた.と言うか足りない関数は補ってねという感じなのかな.
楓 software: wasm に clang を使った場合に C 内で関数呼び出しする場合に出るエラー
(index):25 Uncaught (in promise) LinkError: WebAssembly.Instance(): Import #0 module="env" function="printf" error: function import requires a callable at fetch.then.then.then.module (http://localhost/:25:24) at <anonymous>
$ s2wasm main.s -o main.wast --allocate-stack 1024
const loadWebAssembly = ( filename, imports ) => { return fetch( filename ) .then( response => response.arrayBuffer() ) .then( buffer => WebAssembly.compile( buffer ) ) .then( module => { imports = imports || {} imports.env = imports.env || {} imports.env.printf = arg => console.log( arg ) if( !imports.env.memory ) imports.env.memory = new WebAssembly.Memory( { initial: 256 } ) if( !imports.env.table ) imports.env.table = new WebAssembly.Table( { initial: 18, element: 'anyfunc' } ) return new WebAssembly.Instance( module, imports ) } ) } loadWebAssembly( 'main.wasm' ).then( instance => { const exports = instance.exports exports.main() } )
Google Chrome Canary や Node.js で WebAssembly を読み込むための関数を作った(よくわかっていないのでものによっては動かないかも)
WebAssembly/loadWebAssembly.js at master · calmery/WebAssembly · GitHub
環境構築
Mac OS X 10.11.6
Google Chrome Canary 59.0.3030.0 canary (64-bit)
Firefox Nightly 54.0a1 (2017-03-05) (64 bit)
Node.js v8.0.0-pre
おまじない.
$ pyenv global 2.7.12
The LLVM Compiler Infrastructure Project
$ cd $ mkdir workspace $ cd workspace $ git clone http://llvm.org/git/llvm.git $ cd llvm/tools $ git clone http://llvm.org/git/clang.git $ cd ../projects $ git clone http://llvm.org/git/compiler-rt $ cd ~/workspace $ mkdir llvm_build $ cd llvm_build $ cmake -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX=/usr/local -DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=WebAssembly ../llvm $ make -j 8 $ sudo make install
GitHub - WebAssembly/binaryen: Compiler infrastructure and toolchain library for WebAssembly, in C++
$ cd ~/workspace $ git clone https://github.com/WebAssembly/binaryen.git $ cd binaryen $ cmake . && make $ sudo make install
GitHub - WebAssembly/wabt: The WebAssembly Binary Toolkit
$ cd /usr/local/src $ sudo git clone --recursive https://github.com/WebAssembly/wabt $ cd wabt $ sudo make gcc-debug-no-tests $ cd $ echo "export PATH=$PATH:/usr/local/src/wabt/out/gcc/Debug/no-tests/" >> .bash_profile
Node.js は v8.0.0-pre でないと Google Chrome Canary や Firefox Nightly で動く WebAssembly が動かない.そのため v8/node か nodejs/node を使う.Nightly builds の v8.0.0-nightly20170307efaab8fccf も試したが動かなかった.
GitHub - nodejs/node: Node.js JavaScript runtime
$ git clone https://github.com/nodejs/node.git $ cd node $ git checkout -b canary origin/canary $ ./configure $ make -j4 if [ ! -r node -o ! -L node ]; then ln -fs out/Release/node node; fi $ ./out/Release/node -v v8.0.0-pre $ ./out/Release/node --expose-wasm > WebAssembly {}
GitHub - v8/node: Node.js JavaScript runtime
$ git clone https://github.com/v8/node.git $ cd node $ git checkout -b vee-eight-lkgr origin/vee-eight-lkgr $ ./configure $ make -j4 if [ ! -r node -o ! -L node ]; then ln -fs out/Release/node node; fi $ ./out/Release/node -v v8.0.0-pre $ ./out/Release/node --expose-wasm > WebAssembly {}
WebAssembly に変換する
C や C++ で書かれたコードを WebAssembly に変換する.
extern "C" { int fib( int n ){ switch( n ){ case 0: return 0; case 1: return 1; default: return fib( n - 2 ) + fib( n - 1 ); } } }
C や C++ のコードをまず LLVM bitecode に変換し,次にアセンブリに変換する.その後 S 式に変換しバイナリに変換する.
$ clang++ -v clang version 5.0.0 (http://llvm.org/git/clang.git fc2d9054c86c9b8acbc98b06f1b9d8aa0e5d40f9) (http://llvm.org/git/llvm.git 58580c59aefa59fb793a72dd5abeb6a60340a317) $ clang++ main.cpp -emit-llvm --target=wasm32 -Oz -c -o main.bc $ llc -version LLVM (http://llvm.org/): LLVM version 5.0.0svn ... $ llc main.bc -march=wasm32 -filetype=asm -o main.s $ s2wasm main.s > main.wast $ sexpr-wasm main.wast -o main.wasm
追記
sexpr-wasm ではなく wast2wasm を使うようにする.
$ wast2wasm main.wast -o main.wasm
動かしてみる
GitHub - 1984weed/webassembly-sample を参考にした.
const loadWebAssembly = ( filename, imports ) => { return fetch(filename) .then( response => response.arrayBuffer() ) .then( buffer => WebAssembly.compile( buffer ) ) .then( module => { imports = imports || {} imports.env = imports.env || {} imports.env.memoryBase = imports.env.memoryBase || 0 imports.env.tableBase = imports.env.tableBase || 0 if( !imports.env.memory ) imports.env.memory = new WebAssembly.Memory( { initial: 256 } ) if( !imports.env.table ) imports.env.table = new WebAssembly.Table( { initial: 0, element: 'anyfunc' } ) return new WebAssembly.Instance( module, imports ) } ) } loadWebAssembly( 'main.wasm' ).then( instance => { const exports = instance.exports console.log( exports ) // => Object {memory: W…y.Memory, fib: function} } )
Node.js はオプションで --expose-wasm をつける必要がある.
// Node.js v8.0.0-pre // node --expose-wasm const fs = require( 'fs' ) const buffer = fs.readFileSync( './main.wasm' ) const arrayBuffer = new Uint8Array( buffer ).buffer WebAssembly.compile( arrayBuffer ).then( module => { let imports = {} imports.env = {} imports.env.memoryBase = 0 imports.env.tableBase = 0 if( !imports.env.memory ) imports.env.memory = new WebAssembly.Memory( { initial: 256 } ) if( !imports.env.table ) imports.env.table = new WebAssembly.Table( { initial: 0, element: 'anyfunc' } ) const instance = new WebAssembly.Instance( module, imports ) console.log( instance.exports ) } )
// Node.js v7.7.1 Promise { <rejected> Error: WebAssembly.compile(): Wasm decoding failedResult = expected version 0c 00 00 00, found 01 00 00 00 @+4
ちなみに C++ では名前に修飾がついてしまうので extern "C" で囲う必要がある.
console.log( instance.exports ) // => Object {memory: W…y.Memory, _Z3fibi: function}
> WebAssembly.Instance( module ).exports { _Z3fibi: [Function: 0], memory: Memory {} }
サンプルなどを見ていると WebAssembly ではなく Wasm を使っているものがある.だが Google Chrome Canary や Firefox Nightly では Wasm がない.
// Replace Wasm.instantiateModule() // => ReferenceError: WASM is not defined WebAssembly.Instance()
Node.js v7.2.1
Getting Started With WebAssembly in Node.js | www.thecodebarbarian.com
この方法は v.7.7.1 では動かなかった.sexpr-wasm demo も s2wasm で作られた wast がそのままでは使えない.書き換えれば使えはしたが v7.7.1 で動かなかった.
Emscripten で変換する
Developer’s Guide - WebAssembly を参考にして導入する.
Main — Emscripten 1.37.22 documentation
latest ではなく incoming を使う.
$ git clone https://github.com/juj/emsdk.git $ cd emsdk $ ./emsdk install sdk-incoming-64bit binaryen-master-64bit $ ./emsdk activate sdk-incoming-64bit binaryen-master-64bit $ source ./emsdk_env.sh $ emcc INFO:root:(Emscripten: Running sanity checks) WARNING:root:no input files
$ mkdir hello $ cd hello $ echo '#include <stdio.h>' > hello.c $ echo 'int main(int argc, char ** argv) {' >> hello.c $ echo 'printf("Hello, world!\n");' >> hello.c $ echo '}' >> hello.c $ emcc hello.c -s WASM=1 -o hello.html $ ls hello.c hello.html hello.js hello.wasm $ node -v v8.0.0-pre $ node --expose-wasm hello.js trying binaryen method: native-wasm asynchronously preparing wasm binaryen method succeeded. run() called, but dependencies remain, so not running Hello, world!
ここで生成された hello.wasm は動かしてみるのように読み込むことはできなかった.
> WebAssembly.compile( arrayBuffer ).then( ... Promise { <pending> } (node:92075) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): LinkError: WebAssembly.Instance(): Import #0 module="env" function="DYNAMICTOP_PTR" error: global import must be a number (node:92075) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
どうやら main 関数があると動かないみたい.出力されたファイルをそのまま実行すると動きはする.Google Chrome Canary や Firefox Nightly も出力された hello.html では動くのだが動かしてみるのように読み込むと動かない.以下のようにオプションを指定すると動いた.だがこちらも main 関数があると動かない.この辺りはもうちょっと調べたい.(追記 未確認だけど main 関数というよりは存在しない関数を呼び出しているからっぽい.ここでは main 関数内で呼び出している printf 関数のせいかも)
$ emcc hello.c -Os -s WASM=1 -s SIDE_MODULE=1 -s 'BINARYEN_METHOD="native-wasm"' -o hello.wasm $ ls main.cpp main.wasm $ node --expose-wasm > const fs = require( 'fs' ) > const buffer = fs.readFileSync( 'hello.wasm' ) > const arrayBuffer = new Uint8Array( buffer ).buffer ...
動かしてみる のコードを使用して動かした結果はこうなった.
emcc hello.c -s WASM=1 -o hello.html
main 関数があってもなくても読み込めない.
emcc hello.c -Os -s WASM=1 -s SIDE_MODULE=1 -s 'BINARYEN_METHOD="native-wasm"' -o hello.wasm
main 関数がなければ Google Chrome Canary,Firefox Nightly,Node.js v8.0.0-pre で動かせた.
最適化の -Os を抜くと読み込めなかったりとよくわからない.
Rust から変換する
Compiling Rust to WebAssembly Guide – Hacker Noon
結局は Emscripten と同じみたい.
fn main(){ println!( "Hello World" ); }
$ rustc --target=wasm32-unknown-emscripten hello.rs -o hello.html $ node -v v8.0.0-pre $ node --expose-wasm hello.js trying binaryen method: native-wasm asynchronously preparing wasm binaryen method succeeded. run() called, but dependencies remain, so not running Hello World
まとめ
Emscripten を使えば良さそうだけどよくわからなかった.調べる.
参考
MacOSXでWebAssemblyを触ってみた - Qiita
node.js - Convert a binary NodeJS Buffer to JavaScript ArrayBuffer - Stack Overflow
WebAssembly - JavaScript | MDN
Developer’s Guide - WebAssembly
ゼロから始めるWebAssembly - Qiita
GitHub - 1984weed/webassembly-sample
WebAssemblyを試してみた - Qiita
WebAssemblyを使ってみる(C/C++をWebAssemblyに変換してChromeで実行) - Qiita
WebAssembly を使って自作言語をブラウザで動かしてみよう - はやくプログラムになりたい
WebAssemblyを使ってみる(C/C++をWebAssemblyに変換してChromeで実行) - Qiita
MacOSXでWebAssemblyを触ってみた - Qiita
2016/05/11時点でWebAssembly関連の情報を整理してみた - Qiita