SlideShare a Scribd company logo
Build Node.js – WASM/WASI tiny compiler
with Node.js
JSConf JP 2019 LT (2019.11.30)
@massie_g "Ma She[məʃ iː]"
Node.jsでつくる Node.js – WASM/WASI ミニミニコンパイラー
JSConf JP 2019 LT (2019.11.30)
@massie_g / がねこまさし
Are you interested in WASM ?
WASMに興味ありますよね?
• WASM = WebAssembly
• Then, how do you learn WASM?
• どうやって学びますか?
Learning WASM / WASMを学ぶ
• Read Spec. … I HATE BNF!
• 仕様を読む … BNFを読むと頭が痛くなる
Learning WASM / WASMを学ぶ
• Read Spec … I HATE BNF!
• Read code
• Use emscripten: hello_wold.c  wasm/wat … So huge code to read
• コードを読む
• Emscripten で変換: hello_wold.c  wasm/wat … 超巨大で読むの辛い
Learning WASM / WASMを学ぶ
• Read Spec … I HATE BNF!
• Read code
• Someone says, “You can write WASM by hand”
• … What are you talking about?
• 「手でWASM書けるよ」ていう人も
• … は?? 何言ってんの??
Learning WASM / WASMを学ぶ
• Read Spec … I HATE BNF!
• Read code
• “You can write WASM by hand” … What !? Crazy !?
• My suggestion: Build compiler to learn WASM !
• そうだ、コンパイラーを作って覚えよう!
The story so far / これまでの経緯
• I read the great book “Build Ruby with Ruby”
• I built Node.js tiny interpreter with Node.js
• I built Node.js tiny compiler with Node.js, using LLVM
• I made a talk in Nodefest Tokyo 2018
• Today: Node.js – WASM tiny compiler
• RubyでつくるRubyを読んだ
• Node.jsでつくるNode.jsミニインタープリター を作った
• Node.jsでつくるNode.jsミニコンパイラーをLLVMを使って作った
• 東京Node学園祭2018のセッションで話した
• https://www.slideshare.net/mganeko/nodejsnodejs-123707037
• 本日:Node.js でつくる ミニミニ Node.js-WASMコンパイラー
Structure of Node.js tiny compiler (2018)
Parser Simple AST
(abstract
syntax tree)
Code generator
Executable
binary
LLVM-IR
LLVM-IR
spec
esprima
Source
.js
Language spec:
Subset of JavaScript
llc
ld
Single Type:
32bit signed integer only
Structure of Node.js–WASM tiny compiler (2019)
Parser Simple AST
(abstract
syntax tree)
Code generator
WASM
spec
esprima
source
.js
Language spec:
Subset of JavaScript
wat2wasm
Re-use
.wat
(WebAssembly
Text format)
.wasm
(WebAssembly
binary format)
Web Browser
Node.js
read & execute
Just modify here
wat2wasm
• wat2wasm
• Convert WAT(text format) WASM(binary format)
• a tool of WebAssembly/wabt
• https://github.com/WebAssembly/wabt
• Install
• $ git clone --recursive https://github.com/WebAssembly/wabt
• $ cd wabt
• $ mkdir build
• $ cd build
• $ cmake ..
• $ cmake --build .
• Run
• $ wat2wasm src.wat -o dest.wasm
FYI/参考
Adapting to WASM is easy, (I thought)
WASM対応なんて楽勝!と思ってた
• … but, there are 2 big difference
• (1) machine architecture
• (2) Standard I/O Library
• … だがしかし、2つの大きな違い
• (1) レジスタマシン vs. スタックマシン
• (2) 標準ライブラリ
Difference (1): machine architecture
違い(1): レジスタマシンとスタックマシン
• LLVM … Register Machine /レジスタマシン
• Use register for data operation / データをレジスタに読み込んで処理
• LLVM, Lua VM, Erlang VM(BEAM), etc.
• WASM … Stack Machine / スタックマシン
• Use stack for data operation / データをスタックに積んで処理を行う
• WASM, Java VM(JVM), Ruby VM(YARV), etc.
Order of operation is different between LLVM  WASM
LLVM IR … Register Machine
CPU
register %r1
register %x
memory
load
point address
variable x
y = x + 1;
JavaScript
%r1 = load i32, i32* %x, align 4
%r2 = add i32 %r1, 1
store i32 %r2, i32* %y, align 4
LLVM-IR
FYI/参考
LLVM IR … Register Machine
CPU
ALU
register %r2
register %r1
memory
add
y = x + 1;
JavaScript
%r1 = load i32, i32* %x, align 4
%r2 = add i32 %r1, 1
store i32 %r2, i32* %y, align 4
LLVM-IR
1
FYI/参考
LLVM IR … Register Machine
CPU
register %r2 memory
point address
register %y
variable y
store
y = x + 1;
JavaScript
%r1 = load i32, i32* %x, align 4
%r2 = add i32 %r1, 1
store i32 %r2, i32* %y, align 4
LLVM-IR
FYI/参考
LLVM IR … Register Machine
CPU
ALU
register %r2
register %r1
register %x
memory
load
point address
register %y
variable x
variable y
store
add
y = x + 1;
JavaScript
%r1 = load i32, i32* %x, align 4
%r2 = add i32 %r1, 1
store i32 %r2, i32* %y, align 4
LLVM-IR
FYI/参考
WASM … Stack Machine
memory
variable x
y = x + 1;
JavaScript
get_local $x
i32.const 1
i32.add
set_local $y
WAT
value of x
1
1
ALU
FYI/参考
WASM … Stack Machine
ALU
memory
y = x + 1;
JavaScript
get_local $x
i32.const 1
i32.add
set_local $y
WAT
1
value of x
result (x+1)
result (x+1)
value of x
1
FYI/参考
WASM … Stack Machine
memory
variable y
y = x + 1;
JavaScript
get_local $x
i32.const 1
i32.add
set_local $y
WAT
result (x+1)
ALU
FYI/参考
WASM … Stack Machine
1
memory
variable x
variable y
y = x + 1;
JavaScript
get_local $x
i32.const 1
i32.add
set_local $y
WAT
value of x
result (x+1)
1
ALU
1
value of x
result (x+1)
FYI/参考
Difference (2) : Standard I/O Library
違い(2): 標準ライブラリ
• LLVM
• Link standard C library by linker (ld)
• easy to call puts(), printf()
• リンカーで C言語用のライブラリを一緒にリンク
• puts(), printf() が使えた
• WASM
• WASM has no standard I/O API/library
• Have to provide functions at outside, and import from WASM
• WASMとしての標準ライブラリ/APIは無し
• 呼び出し元で、インポート用の関数を提供
Ex) preparing functions at caller side
呼び出し元でインポート用関数の準備(例)
const imports = {
// --- print signed 32bit integer / 符号付32ビット整数を出力する関数 ---
imported_putn: function(arg) {
console.log(arg);
},
// --- print static string / 文字列を出力する関数 ---
imported_puts: function(offset) {
let str = '';
let arr = new Uint8Array(exported_string.buffer);
for (let i = offset; arr[i]; i++) {
str += String.fromCharCode(arr[i]);
}
console.log(str);
}
};
Exec WASM in Node.js
Node.jsからWASMの実行
const source = fs.readFileSync(filename); // read WASM / WASMファイルを読み込む
const typedArray = new Uint8Array(source);
let ret = null;
let exported_string = null;
WebAssembly.instantiate(typedArray,
{ imports: imports } // pass prepared functions / 先ほど用意したインポート用関数を渡す
).then(result => {
exported_string = result.instance.exports.exported_string;
ret = result.instance.exports.exported_main(); // call main of WASM / WASMのメイン関数を呼び出す
process.exit(ret);
}).catch(e => {
console.log(e);
});
Inside of WASM / WASMの中身
(module
;; ---- import print functions / 出力用関数をインポート ---
(func $putn (import "imports" "imported_putn") (param i32))
(func $puts (import "imports" "imported_puts") (param i32))
;; ---- static string area for puts()/ 出力用文字列の領域 ---
(memory $string_area 1) ;; string_area 64KiB
(export "exported_string" (memory $string_area))
;; --- export main/メインをエクスポート ---
(export "exported_main" (func $main))
(func $main (result i32)
(local $x i32)
(local $y i32)
i32.const 1 ;; let x = 1;
set_local $x
get_local $x ;; let y = x +1;
i32.const 1
i32.add
set_local $y
get_local $y ;; putn(y);
call $putn
i32.const 99 ;; return 99; (dummy)
return
)
)
Done! / できた
• I built Node.js-WASM tiny compiler
• able to run FizzBuzz, Fibonacci sequence
• https://github.com/mganeko/mini_node_wasm
• Node.js-WASM ミニミニコンパイラーができた
• FizzBuzzやフィボナッチ数列が動く
• https://github.com/mganeko/mini_node_wasm
WASI: Beyond Web
WASI でWebの外へ
• My Node.js-WASM tiny compiler is fun, but useless
• Convert .js to WASM, which was running on Node.js
• and run in Node.js again
• NEXT: Use WASM outside of Browser/Node.js
•  try WASI (WebAssembly system interface)
• 今回やったことは、楽しいけど意味はない
• もともとNode.jsで実行していた .js ファイルを、WASMに変換して
• またNode.js から実行
• ブラウザやNode.js以外の環境でWASMを動かしたい
• それなら、WASI (WebAssembly system interface) でしょ
WASI: WebAssembly System Interface
• WASI … a system interface to run WASM across all different OSs.
• https://hacks.mozilla.org/2019/03/standardizing-wasi-a-webassembly-system-interface/
• POSIX like API … file, network, clock, random
• Example
• wasmtime ... WASI reference build with Rust
• https://wasmtime.dev/
• lucet ... Made by Fastly, aim to run on CDN edge server
• https://www.fastly.com/blog/announcing-lucet-fastly-native-webassembly-compiler-runtime
• WebAssembly Micro Runtime ... Lightweight runtime for embedded device
• No JIT, interpreter only
• https://github.com/bytecodealliance/wasm-micro-runtime
• WASI … WASMをいろんなOS上で動かすためのシステムインターフェイス
• ファイル、ネットワーク、クロック、乱数など
• 例
• Wasmtime … Rustで作られたリファレンス実装
• Lucet … Fastlyが作っている、CDNエッジサーバー上でWASMを動かすための環境
• WebAssembly Micro Runtime ... 組み込みデバイスで動かすための軽量ランタイム
WASI runtime functions / ランタイム関数
• WASI Core API System calls
• https://github.com/CraneStation/wasmtime/blob/master/docs/WASI-api.md
• provide primitive functions
• __wasi_fd_write()
• No printf()/puts()
• プリミティブな関数が提供される様子
• __wasi_fd_write() を使う必要がある
• 文字列出力puts()や、フォーマット付き出力のprintf()に相当するもはない
__wasi_fd_write(fd, *iovs, iovs_len, *nwritten)
arguments
• fd … file descriptor / ファイルデスクリプタ
• 0 … stdin / 標準入力
• 1 … stdout / 標準出力
• 2 … stderr / 標準エラー出力
• *iovs … start address of iov group
• iov->*buf … start address of byte array
• Iov->buf_len … length of byte array
• Iovs_len … size of iov group
• *nwritten … bytes written to file
• pointer of 32bit integer to output the value
*buf:
start address of
byte array
H i !
buf_len:
length of bye array
3
*buf
buf_len
Write multiple byte arrays
複数のバイト列を出力可能
”Hi!”
Implement built-in function
組み込み関数の実装
• putn() … prepare built-in function for print 32bit signed integer
• need to convert integer to byte string with WASM code
• Steps in putn()
• Check sign, Calculate length in string
• Take 1 digit  covert to ASCII code (1  81)
• store ASCII string in in memory
• call __wasi_fd_write()
• Writing WASM code for putn() by hand … quite tough work (for me)
• putn() … 32ビット符号付整数を出力するための組み込み関数を準備
• WASMで 32ビット符号つき整数 → 文字列(バイト列)に変換
• putn() のステップ
• 符号の有無を判定、長さの算出
• 1桁ずつASCIIコードのバイトに変換
• 文字列をメモリに書き込む
• __wasi_fd_write() を呼び出す
• この処理を行うWASMコードを手書きするのはシンドイ
Implement built-in function
組み込み関数の実装
• putn() … prepare built-in function for print 32bit signed integer
• need to convert integer to byte string with WASM code
• Steps in putn()
• Check sign, Calculate length in string
• Take 1 digit  covert to ASCII code (1  81)
• store ASCII string in in memory
• call __wasi_fd_write()
• Writing WASM code for putn() by hand … quite tough work (for me)
• putn() … 32ビット符号付整数を出力するための組み込み関数を準備
• WASMで 32ビット符号つき整数 → 文字列(バイト列)に変換
• putn() のステップ
• 符号の有無を判定、長さの算出
• 1桁ずつASCIIコードのバイトに変換
• 文字列をメモリに書き込む
• __wasi_fd_write() を呼び出す
• この処理を行うWASMコードを手書きするのはシンドイ
Use compiler to
generate WAT code!
今回作った
コンパイラーで生成
Example of compiled code:
_calcLength(n)
function _calcLength(n) { // n >= 0
let restValue = n;
let len = 1;
while (restValue >= 10) {
restValue = restValue / 10;
len = len + 1;
}
return len;
}
(func $_calcLength (param $n i32) (result i32)
(local $restValue i32)
(local $len i32)
get_local $n
set_local $restValue
i32.const 1
set_local $len
loop ;; --begin of while loop--
get_local $restValue
i32.const 10
i32.ge_s
if
get_local $restValue
i32.const 10
i32.div_s
set_local $restValue
get_local $len
i32.const 1
i32.add
set_local $len
br 1 ;; --jump to head of while loop--
end ;; end of if-then
end ;; --end of while loop--
get_local $len
return
)
Source .js
Compiled .wat
Node.js-
WASM
compiler
Structure of Node.js–WASM/WASI tiny compiler
Parser Simple AST
(abstract
syntax tree)
Code generator
WASM
spec
esprima
Source
.js
Language spec:
Subset of JavaScript
wat2wasm
Re-use
WASI ready .wat
(WebAssembly
Text format)
WASI ready .wasm
(WebAssembly
binary format)
read & execute
modify for WASI
wasmtime
prepared .wat
for built-in function
wasmtime : WASI runtime
• wasmtime
• WASI runtime built with Rust
• https://github.com/CraneStation/wasmtime
• Install
• Need Rust、Cargo
• $ git clone --recurse-submodules https://github.com/CraneStation/wasmtime.git
• $ cd wasmtime
• $ cargo build --release
• Run
• $ wasmtime wasi_ready.wasm
or
• $ wasmtime wasi_ready.wat
FYI/参考
Conclusion / まとめ
• I learned (a piece of) WASM by building tiny compiler
• I found some difference between WASM   LLVM
• I try WASI
• My tiny compiler helped me
• Exciting! Let’s try WASM/WASI
• コンパイラーを作ってWASM(の一部)を理解した
• 予想に反して、LLVMとは思想が違った
• WASIが気になってたけど、雰囲気分かった
• WASI用の組み込み関数を書くのに、初めてコンパイラーが役立った
• みなさんも、WASM/WASIに触れてみよう!
About me
• Masashi Ganeko / @massie_g
• Please pronounce "Ma She[məʃ iː]"
• Staff of WebRTC Meetup Tokyo
• がねこまさし / @massie_g (まっしー)
• WebRTC Meetup Tokyo のスタッフ
• インフォコム(株)の技術調査チームのマネージャー
Thank You!
Node.jsでつくるNode.jsミニコンパイラ - もくじ
https://qiita.com/massie_g/items/3ba1ba5d55499ee84b0b
Node.jsでつくるNode.js - もくじ(インタープリター)
https://qiita.com/massie_g/items/3ee11c105b4458686bc1
Node.jsでつくるNode.js-WASMコンパイラ - もくじ
https://qiita.com/massie_g/items/c663095759a80c53c126

More Related Content

Build Node.js-WASM/WASI Tiny compiler with Node.js

  • 1. Build Node.js – WASM/WASI tiny compiler with Node.js JSConf JP 2019 LT (2019.11.30) @massie_g "Ma She[məʃ iː]" Node.jsでつくる Node.js – WASM/WASI ミニミニコンパイラー JSConf JP 2019 LT (2019.11.30) @massie_g / がねこまさし
  • 2. Are you interested in WASM ? WASMに興味ありますよね? • WASM = WebAssembly • Then, how do you learn WASM? • どうやって学びますか?
  • 3. Learning WASM / WASMを学ぶ • Read Spec. … I HATE BNF! • 仕様を読む … BNFを読むと頭が痛くなる
  • 4. Learning WASM / WASMを学ぶ • Read Spec … I HATE BNF! • Read code • Use emscripten: hello_wold.c  wasm/wat … So huge code to read • コードを読む • Emscripten で変換: hello_wold.c  wasm/wat … 超巨大で読むの���い
  • 5. Learning WASM / WASMを学ぶ • Read Spec … I HATE BNF! • Read code • Someone says, “You can write WASM by hand” • … What are you talking about? • 「手でWASM書けるよ」ていう人も • … は?? 何言ってんの??
  • 6. Learning WASM / WASMを学ぶ • Read Spec … I HATE BNF! • Read code • “You can write WASM by hand” … What !? Crazy !? • My suggestion: Build compiler to learn WASM ! • そうだ、コンパイラーを作って覚えよう!
  • 7. The story so far / これまでの経緯 • I read the great book “Build Ruby with Ruby” • I built Node.js tiny interpreter with Node.js • I built Node.js tiny compiler with Node.js, using LLVM • I made a talk in Nodefest Tokyo 2018 • Today: Node.js – WASM tiny compiler • RubyでつくるRubyを読んだ • Node.jsでつくるNode.jsミニインタープリター を作った • Node.jsでつくるNode.jsミニコンパイラーをLLVMを使って作った • 東京Node学園祭2018のセッションで話した • https://www.slideshare.net/mganeko/nodejsnodejs-123707037 • 本日:Node.js でつくる ミニミニ Node.js-WASMコンパイラー
  • 8. Structure of Node.js tiny compiler (2018) Parser Simple AST (abstract syntax tree) Code generator Executable binary LLVM-IR LLVM-IR spec esprima Source .js Language spec: Subset of JavaScript llc ld Single Type: 32bit signed integer only
  • 9. Structure of Node.js–WASM tiny compiler (2019) Parser Simple AST (abstract syntax tree) Code generator WASM spec esprima source .js Language spec: Subset of JavaScript wat2wasm Re-use .wat (WebAssembly Text format) .wasm (WebAssembly binary format) Web Browser Node.js read & execute Just modify here
  • 10. wat2wasm • wat2wasm • Convert WAT(text format) WASM(binary format) • a tool of WebAssembly/wabt • https://github.com/WebAssembly/wabt • Install • $ git clone --recursive https://github.com/WebAssembly/wabt • $ cd wabt • $ mkdir build • $ cd build • $ cmake .. • $ cmake --build . • Run • $ wat2wasm src.wat -o dest.wasm FYI/参考
  • 11. Adapting to WASM is easy, (I thought) WASM対応なんて楽勝!と思ってた • … but, there are 2 big difference • (1) machine architecture • (2) Standard I/O Library • … だがしかし、2つの大きな違い • (1) レジスタマシン vs. スタックマシン • (2) 標準ライブラリ
  • 12. Difference (1): machine architecture 違い(1): レジスタマシンとスタックマシン • LLVM … Register Machine /レジスタマシン • Use register for data operation / データをレジスタに読み込んで処理 • LLVM, Lua VM, Erlang VM(BEAM), etc. • WASM … Stack Machine / スタックマシン • Use stack for data operation / データをスタックに積んで処理を行う • WASM, Java VM(JVM), Ruby VM(YARV), etc. Order of operation is different between LLVM  WASM
  • 13. LLVM IR … Register Machine CPU register %r1 register %x memory load point address variable x y = x + 1; JavaScript %r1 = load i32, i32* %x, align 4 %r2 = add i32 %r1, 1 store i32 %r2, i32* %y, align 4 LLVM-IR FYI/参考
  • 14. LLVM IR … Register Machine CPU ALU register %r2 register %r1 memory add y = x + 1; JavaScript %r1 = load i32, i32* %x, align 4 %r2 = add i32 %r1, 1 store i32 %r2, i32* %y, align 4 LLVM-IR 1 FYI/参考
  • 15. LLVM IR … Register Machine CPU register %r2 memory point address register %y variable y store y = x + 1; JavaScript %r1 = load i32, i32* %x, align 4 %r2 = add i32 %r1, 1 store i32 %r2, i32* %y, align 4 LLVM-IR FYI/参考
  • 16. LLVM IR … Register Machine CPU ALU register %r2 register %r1 register %x memory load point address register %y variable x variable y store add y = x + 1; JavaScript %r1 = load i32, i32* %x, align 4 %r2 = add i32 %r1, 1 store i32 %r2, i32* %y, align 4 LLVM-IR FYI/参考
  • 17. WASM … Stack Machine memory variable x y = x + 1; JavaScript get_local $x i32.const 1 i32.add set_local $y WAT value of x 1 1 ALU FYI/参考
  • 18. WASM … Stack Machine ALU memory y = x + 1; JavaScript get_local $x i32.const 1 i32.add set_local $y WAT 1 value of x result (x+1) result (x+1) value of x 1 FYI/参考
  • 19. WASM … Stack Machine memory variable y y = x + 1; JavaScript get_local $x i32.const 1 i32.add set_local $y WAT result (x+1) ALU FYI/参考
  • 20. WASM … Stack Machine 1 memory variable x variable y y = x + 1; JavaScript get_local $x i32.const 1 i32.add set_local $y WAT value of x result (x+1) 1 ALU 1 value of x result (x+1) FYI/参考
  • 21. Difference (2) : Standard I/O Library 違い(2): 標準ライブラリ • LLVM • Link standard C library by linker (ld) • easy to call puts(), printf() • リンカーで C言語用のライブラリを一緒にリンク • puts(), printf() が使えた • WASM • WASM has no standard I/O API/library • Have to provide functions at outside, and import from WASM • WASMとしての標準ライブラリ/APIは無し • 呼び出し元で、インポート用の関数を提供
  • 22. Ex) preparing functions at caller side 呼び出し元でインポート用関数の準備(例) const imports = { // --- print signed 32bit integer / 符号付32ビット整数を出力する関数 --- imported_putn: function(arg) { console.log(arg); }, // --- print static string / 文字列を出力する関数 --- imported_puts: function(offset) { let str = ''; let arr = new Uint8Array(exported_string.buffer); for (let i = offset; arr[i]; i++) { str += String.fromCharCode(arr[i]); } console.log(str); } };
  • 23. Exec WASM in Node.js Node.jsからWASMの実行 const source = fs.readFileSync(filename); // read WASM / WASMファイルを読み込む const typedArray = new Uint8Array(source); let ret = null; let exported_string = null; WebAssembly.instantiate(typedArray, { imports: imports } // pass prepared functions / 先ほど用意したインポート用関数を渡す ).then(result => { exported_string = result.instance.exports.exported_string; ret = result.instance.exports.exported_main(); // call main of WASM / WASMのメイン関数を呼び出す process.exit(ret); }).catch(e => { console.log(e); });
  • 24. Inside of WASM / WASMの中身 (module ;; ---- import print functions / 出力用関数をインポート --- (func $putn (import "imports" "imported_putn") (param i32)) (func $puts (import "imports" "imported_puts") (param i32)) ;; ---- static string area for puts()/ 出力用文字列の領域 --- (memory $string_area 1) ;; string_area 64KiB (export "exported_string" (memory $string_area)) ;; --- export main/メインをエクスポート --- (export "exported_main" (func $main)) (func $main (result i32) (local $x i32) (local $y i32) i32.const 1 ;; let x = 1; set_local $x get_local $x ;; let y = x +1; i32.const 1 i32.add set_local $y get_local $y ;; putn(y); call $putn i32.const 99 ;; return 99; (dummy) return ) )
  • 25. Done! / できた • I built Node.js-WASM tiny compiler • able to run FizzBuzz, Fibonacci sequence • https://github.com/mganeko/mini_node_wasm • Node.js-WASM ミニミニコンパイラーができた • FizzBuzzやフィボナッチ数列が動く • https://github.com/mganeko/mini_node_wasm
  • 26. WASI: Beyond Web WASI でWebの外へ • My Node.js-WASM tiny compiler is fun, but useless • Convert .js to WASM, which was running on Node.js • and run in Node.js again • NEXT: Use WASM outside of Browser/Node.js •  try WASI (WebAssembly system interface) • 今回やったことは、楽しいけど意味はない • もともとNode.jsで実行していた .js ファイルを、WASMに変換して • またNode.js から実行 • ブラウザやNode.js以外の環境でWASMを動かしたい • それなら、WASI (WebAssembly system interface) でしょ
  • 27. WASI: WebAssembly System Interface • WASI … a system interface to run WASM across all different OSs. • https://hacks.mozilla.org/2019/03/standardizing-wasi-a-webassembly-system-interface/ • POSIX like API … file, network, clock, random • Example • wasmtime ... WASI reference build with Rust • https://wasmtime.dev/ • lucet ... Made by Fastly, aim to run on CDN edge server • https://www.fastly.com/blog/announcing-lucet-fastly-native-webassembly-compiler-runtime • WebAssembly Micro Runtime ... Lightweight runtime for embedded device • No JIT, interpreter only • https://github.com/bytecodealliance/wasm-micro-runtime • WASI … WASMをいろんなOS上で動かすためのシステムインターフェイス • ファイル、ネットワーク、クロック、乱数など • 例 • Wasmtime … Rustで作られたリファレンス実装 • Lucet … Fastlyが作っている、CDNエッジサーバー上でWASMを動かすための環境 • WebAssembly Micro Runtime ... 組み込みデバイスで動かすための軽量ランタイム
  • 28. WASI runtime functions / ランタイム関数 • WASI Core API System calls • https://github.com/CraneStation/wasmtime/blob/master/docs/WASI-api.md • provide primitive functions • __wasi_fd_write() • No printf()/puts() • プリミティブな関数が提供される様子 • __wasi_fd_write() を使う必要がある • 文字列出力puts()や、フォーマット付き出力のprintf()に相当するもはない
  • 29. __wasi_fd_write(fd, *iovs, iovs_len, *nwritten) arguments • fd … file descriptor / ファイルデスクリプタ • 0 … stdin / 標準入力 • 1 … stdout / 標準出力 • 2 … stderr / 標準エラー出力 • *iovs … start address of iov group • iov->*buf … start address of byte array • Iov->buf_len … length of byte array • Iovs_len … size of iov group • *nwritten … bytes written to file • pointer of 32bit integer to output the value *buf: start address of byte array H i ! buf_len: length of bye array 3 *buf buf_len Write multiple byte arrays 複数のバイト列を出力可能 ”Hi!”
  • 30. Implement built-in function 組み込み関数の実装 • putn() … prepare built-in function for print 32bit signed integer • need to convert integer to byte string with WASM code • Steps in putn() • Check sign, Calculate length in string • Take 1 digit  covert to ASCII code (1  81) • store ASCII string in in memory • call __wasi_fd_write() • Writing WASM code for putn() by hand … quite tough work (for me) • putn() … 32ビット符号付整数を出力するための組み込み関数を準備 • WASMで 32ビット符号つき整数 → 文字列(バイト列)に変換 • putn() のステップ • 符号の有無を判定、長さの算出 • 1桁ずつASCIIコードのバイトに変換 • 文字列をメモリに書き込む • __wasi_fd_write() を呼び出す • この処理を行うWASMコードを手書きするのはシンドイ
  • 31. Implement built-in function 組み込み関数の実装 • putn() … prepare built-in function for print 32bit signed integer • need to convert integer to byte string with WASM code • Steps in putn() • Check sign, Calculate length in string • Take 1 digit  covert to ASCII code (1  81) • store ASCII string in in memory • call __wasi_fd_write() • Writing WASM code for putn() by hand … quite tough work (for me) • putn() … 32ビット符号付整数を出力するための組み込み関数を準備 • WASMで 32ビット符号つき整数 → 文字列(バイト列)に変換 • putn() のステップ • 符号の有無を判定、長さの算出 • 1桁ずつASCIIコードのバイトに変換 • 文字列をメモリに書き込む • __wasi_fd_write() を呼び出す • この処理を行うWASMコードを手書きするのはシンドイ Use compiler to generate WAT code! 今回作った コンパイラーで生成
  • 32. Example of compiled code: _calcLength(n) function _calcLength(n) { // n >= 0 let restValue = n; let len = 1; while (restValue >= 10) { restValue = restValue / 10; len = len + 1; } return len; } (func $_calcLength (param $n i32) (result i32) (local $restValue i32) (local $len i32) get_local $n set_local $restValue i32.const 1 set_local $len loop ;; --begin of while loop-- get_local $restValue i32.const 10 i32.ge_s if get_local $restValue i32.const 10 i32.div_s set_local $restValue get_local $len i32.const 1 i32.add set_local $len br 1 ;; --jump to head of while loop-- end ;; end of if-then end ;; --end of while loop-- get_local $len return ) Source .js Compiled .wat Node.js- WASM compiler
  • 33. Structure of Node.js–WASM/WASI tiny compiler Parser Simple AST (abstract syntax tree) Code generator WASM spec esprima Source .js Language spec: Subset of JavaScript wat2wasm Re-use WASI ready .wat (WebAssembly Text format) WASI ready .wasm (WebAssembly binary format) read & execute modify for WASI wasmtime prepared .wat for built-in function
  • 34. wasmtime : WASI runtime • wasmtime • WASI runtime built with Rust • https://github.com/CraneStation/wasmtime • Install • Need Rust、Cargo • $ git clone --recurse-submodules https://github.com/CraneStation/wasmtime.git • $ cd wasmtime • $ cargo build --release • Run • $ wasmtime wasi_ready.wasm or • $ wasmtime wasi_ready.wat FYI/参考
  • 35. Conclusion / まとめ • I learned (a piece of) WASM by building tiny compiler • I found some difference between WASM   LLVM • I try WASI • My tiny compiler helped me • Exciting! Let’s try WASM/WASI • コンパイラーを作ってWASM(の一部)を理解した • 予想に反して、LLVMとは思想が違った • WASIが気になってたけど、雰囲気分かった • WASI用の組み込み関数を書くのに、初めてコンパイラーが役立った • みなさんも、WASM/WASIに触れてみよう!
  • 36. About me • Masashi Ganeko / @massie_g • Please pronounce "Ma She[məʃ iː]" • Staff of WebRTC Meetup Tokyo • がねこまさし / @massie_g (まっしー) • WebRTC Meetup Tokyo のスタッフ • インフォコム(株)の技術調査チームのマネージャー
  • 37. Thank You! Node.jsでつくるNode.jsミニコンパイラ - もくじ https://qiita.com/massie_g/items/3ba1ba5d55499ee84b0b Node.jsでつくるNode.js - もくじ(インタープリター) https://qiita.com/massie_g/items/3ee11c105b4458686bc1 Node.jsでつくるNode.js-WASMコンパイラ - もくじ https://qiita.com/massie_g/items/c663095759a80c53c126

Editor's Notes

  1. Hi everyone. I’d like to talk about “Build Node.js – WASM/WASI tiny compiler, with Node.js” (I am massie.)
  2. At first, are you interested in WebAssembly? Please raise your hand who are intrested? Thank you. Great, almost half of you! Then how do you learn WASM?
  3. First choice is reading spec, but I don’t like BNF
  4. Second choice is, reading wasm code. For example I can use emscripten to convert C code to WASM and read it. But generated code is so huge to read.
  5. Someone says, “you can write WASM by hand”. But it sounds crazy for me.
  6. So, my suggestion is “Build compiler to learn WASM!”. Don’t you think so?
  7. Why I am talking about compiler? Because, I read a great book “build Ruby with ruby”, and built a tiny compiler using LLVM last year. So, I am talking about WASM compiler today.
  8. This is a structure of my tiny compiler. It support very small subset of JavaScript. It has only single type. 32 bit signed integer. I use esprima module for parse. I write a code generator for LLVM-IR, intermediate representation. Then I use LLVM tools to generate executable binary.
  9. To build WASM compiler, I re-use parse and modify generator to generate WebAssmebly Text format. After that, I use wat2wasm to convert to WebAssembly binary format, then execute in Web Browser or Node.js
  10. Wet2wasm is a tool of WebAssembly/wabt
  11. Adapting my compiler from LLVM to WASM is easy, I thought. But it wasn’t. There are 2 big differences. Machine architecture , and Standard I/O library ※ LLVM との違いをなぜ出しているのか?
  12. LLVM is register machine. It use register for date operation. On the other hand, WASM is stack machine. Push data to a stack before data operation. So order of operation is quite different.
  13. Sorry, I don’t have a enough time, so skipping a couple of pages
  14. Second difference is Standard I/O library. In LLVM, I link standard C library by linker, so it is easy to call puts() or printf(). On the other hand, WASM has no standard I/O API or Library.
  15. I have to prepare functions at caller side, outside of WASM. In this example, I provide 2 functions. Printing 32bit integer, and printing static string
  16. I pass my function when executing wasm code like this.
  17. Inside of WASM, I import function like left side. And call it like right side.
  18. Now my tiny compiler is done! I can compile and run FizzBuzzz and Fibonacci sequence.
  19. My tiny WASM compiler is fun, but useless. Because it convert JS file to WASM, which was running on Node.js. And run in Node.js again. There is no additional value. So I want to run my wasm at outside of Browser or Node.js. I heard about WASI (WebAssembly system interface), so I want to try it next.
  20. What is wasi? WASM is a fast, scalable, secure way to run the same code across all machines. WASI is a system interface to run WASM across all different OSs. There are runtimes for CDN edge server or embedded devices. Luchet is a runtime to run WASM on CDN edge WebAssembly Micro Runtime is a lightweight runtime for embedded devices.
  21. WASI runtime functions are listed in this page. It provides primitive functions There is no printf(). I have to use fd_write() instead.
  22. It is possible to write multiple byte arrays at once with fd_write(). To use it, I have to pass a set of buffers. It is a pointer of of pointer. Using fd_write() is quite complicated.
  23. I want to provide simple built-in function putn(), for printing 32bit integer, instead of calling fd_write() directly. In this function, I need to convert integer value to byte string with WASM code. There are some steps in putn() Writing these steps in WASM code by hand is quite a tough work for me.
  24. So I decide to use my compiler to generate WAT code for some part of built-in function.
  25. This is an example of one step. Take integer value and calculate length in string. Left side is source JS code. And right side is generated WAT code by my compiler.
  26. I prepare .wat file for built-in function, and use it for generating WASI ready WAT code. Then, I use “wasmtime” for run WASI ready wasm. Done!
  27. Wasmtime is a WASI runtime built with Rust.
  28. Conclusion. I learn (a piece of) WASM by building tiny compiler Also, I try WASI. It was exiting! So, I want to say “Let’s try WASM and WASI” everyone.
  29. That’s all.
  30. Thank you!