WASI
comrak
git log を眺めると日本人とおぼしき名前の方が出て来る。でも、投稿日時が Date: Mon Dec 6 22:29:50 2021 +1100 とか、時差が +1000 とかになってるんだよな。 どのエリアに住んでおられるのだろう? ちょっとしたプロファイリングを楽しんでます。
苦行の末、やっと動いた。木箱が92個なんて大杉。
[sakae@fb /tmp/comrak]$ cargo r -- README.md | w3m -T text/html
実際に cargo vendor して、集った個数を数えてみたら、107個になってた。デフォで、1個は既に存在してた。ドッカー用みたい。
これだけ有ると、cargo tree しても、訳わかめになりそうだな。多分、Cargo.tomlに列挙されてるのが、重要と思える。
相関をCargo.tomlと、取ってみる。簡単な構造のやつって亊で、前回やったmrkdown.rsで
vbox$ cargo tree markdown v0.3.0 (/tmp/markdown.rs) ├── lazy_static v1.4.0 ├── pipeline v0.5.0 └── regex v1.5.4 ├── aho-corasick v0.7.18 │ └── memchr v2.4.1 ├── memchr v2.4.1 └── regex-syntax v0.6.25 [dev-dependencies] └── difference v0.4.1 └── getopts v0.2.21 └── unicode-width v0.1.9
[dependencies] lazy_static = "1.4.0" pipeline = "0.5" regex = "1" [dev-dependencies] difference = "0.4"
相関係数1。これ以上は無いと言う、強い正の相関が得られました。これで、見るべきREADME.mdをショートカット出来るな。使うのは、markdown.rs(自家制版)です。
Markdown to HTML Converter
UTCからの時差が8時間って亊は台湾の方かな。名前もそれっぽい。
[sakae@fb /tmp/markdown2html-converter]$ cargo r -- README.md
3332バイトのREADME.mdから965111バイトのREADME.htmlが生成された。密圧縮されたCSSだかJAVASCRIPTだかが詰っていた。
p}</style><script>/*! Highlight.js v11.0.1 (git: 1cf31f015d) (c) 2006-2021 Ivan Sagalaev and other contributors License: BSD-3-Clause */
もしやと思って、
# My first code ```rust fn add(x: i32, y: i32) -> i32 { x + y } ``` Is it ok? [MIT](https://choosealicense.com/licenses/mit/)
例のテストデータを喰わせると、出来上がったhtmlの最後の方が下のようになってた。
e></head><body><article class="markdown-body"><h1>My first code</h1> <pre><code class="language-rust">fn add(x: i32, y: i32) -> i32 { x + y } </code></pre> <p>Is it ok?<br/> <a href="https://choosealicense.com/licenses/mit/">MIT</a></p>
ちゃんと、rustってのが取り込まれている。これをfirefoxで閲覧すると、色付きコードが出て来た。ちゃんとコードが何の言語か認識して表示用のjavascriptを取り込んでいるんでな。 ご苦労なこっちゃ。w3mを使ってる人には、関係ない亊ですけど。
w3m
そうそう、この間からよく出て来るw3mて何者?
w3m is a pager/text-based WWW browser. It is similar to Lynx, but it has several features Lynx doesn't have: * w3m can render tables * w3m can render frames (by converting frames into tables) * w3m can display documents from standard input * w3m is small Available flavors: image - Support displaying inline images when run from an X11 terminal emulator.
Lynxなんて懐しい名前だ。こやつ日本語を表示出来無くて困っていたのよ。そこに颯爽と登場してきたのが、山形大学に在学中の方が制作中のw3mだった。
日本語OKは勿論、テーブルが表示出来るのが嬉しかった。そして、更に嬉しいのが、不純なJavascriptを毅然と無視してくれる所。そんな物、知りませんと潔い。この態度は、伝統になってて、誰も手を出していない。
詳しい使いかたは、 w3m HomePage を参照。キーボードだけで操作出来る。マウスはいらないぞ。
WASI
思いっきり、新しいのって亊で、 WebAssembly なんてのに触れてみるか。 資料は、
ぐらいかな。
WebAssembly by Go こんなのも有るけど、本命は、 {Rust} WebAssemblyを用いたウェブフロントエンド開発入門 だな。資料豊富で、良い道標になる。余力が有ったら、Go だね。
hello-wasm なんてサンプルが公開されてるので、叩き台にもってこい。
環境は下記に従って用意する。例によってリナ限定の感じがするな。まあ、Debian(32bit)でも、用意出来たんで、文句は言うまい。
$ rustup target add wasm32-unknown-unknown $ cargo install wasm-pack
sakae@deb:/tmp/hello-wasm$ wasm-pack build --target web [INFO]: Checking for the Wasm target... [INFO]: Compiling to Wasm... Compiling proc-macro2 v1.0.36 Compiling unicode-xid v0.2.2 : Compiling hello-wasm v0.1.0 (/tmp/hello-wasm) Finished release [optimized] target(s) in 1m 43s [INFO]: Installing wasm-bindgen... [INFO]: Optimizing wasm binaries with `wasm-opt`... Error: Exec format error (os error 8) To disable `wasm-opt`, add `wasm-opt = false` to your package metadata in your `Cargo.toml`. sakae@deb:/tmp/hello-wasm$ server Serving HTTP on 0.0.0.0 port 8080 (http://0.0.0.0:8080/) ... 127.0.0.1 - - [07/Mar/2022 06:06:11] "GET / HTTP/1.1" 200 - 127.0.0.1 - - [07/Mar/2022 06:06:12] "GET /pkg/hello_wasm.js HTTP/1.1" 200 - 127.0.0.1 - - [07/Mar/2022 06:06:12] "GET /pkg/hello_wasm_bg.wasm HTTP/1.1" 200 - 127.0.0.1 - - [07/Mar/2022 06:06:12] code 404, message File not found 127.0.0.1 - - [07/Mar/2022 06:06:12] "GET /favicon.ico HTTP/1.1" 404 -
serverは、こんな亊ぐらいにしか使い道が無いPython製です。
alias server='python3 -m http.server 8080'
sakae@deb:/tmp/hello-wasm$ cargo tree hello-wasm v0.1.0 (/tmp/hello-wasm) ├── wasm-bindgen v0.2.79 │ ├── cfg-if v1.0.0 │ └── wasm-bindgen-macro v0.2.79 (proc-macro) │ ├── quote v1.0.15 │ │ └── proc-macro2 v1.0.36 │ │ └── unicode-xid v0.2.2 │ └── wasm-bindgen-macro-support v0.2.79 │ ├── proc-macro2 v1.0.36 (*) │ ├── quote v1.0.15 (*) │ ├── syn v1.0.86 │ │ ├── proc-macro2 v1.0.36 (*) │ │ ├── quote v1.0.15 (*) │ │ └── unicode-xid v0.2.2 │ ├── wasm-bindgen-backend v0.2.79 │ │ ├── bumpalo v3.9.1 │ │ ├── lazy_static v1.4.0 │ │ ├── log v0.4.14 │ │ │ └── cfg-if v1.0.0 │ │ ├── proc-macro2 v1.0.36 (*) │ │ ├── quote v1.0.15 (*) │ │ ├── syn v1.0.86 (*) │ │ └── wasm-bindgen-shared v0.2.79 │ └── wasm-bindgen-shared v0.2.79 └── web-sys v0.3.56 ├── js-sys v0.3.56 │ └── wasm-bindgen v0.2.79 (*) └── wasm-bindgen v0.2.79 (*)
なんか色々な物を組合せている。これもそれも、npmと組合せて使う亊を想定してるんだな。 LIFEゲームを題材にWebAssemblyの使いかたを解説した資料が、公開されてる。 The Rust Wasm Book これは見ておく価値があるな。
hello-wasmをコンパイルすると、pkgの中身が
sakae@pen:/tmp/hello-wasm$ ls pkg hello_wasm_bg.wasm hello_wasm.d.ts package.json hello_wasm_bg.wasm.d.ts hello_wasm.js README.md
こんな風に出来上がるけど、本当に必要なやつは、ブラウザー内に鎮座してる仮想コンピュータ用のバイナリーコード hello_wasm_bg.wasm
と、それをブラウザーにロードする為のローダーである hello_wasm.js
だけだ。
残り物は、多分npmと連動させる時に、必要なのだろう。 細かい亊は、 The wasm-pack Book を見ればいいんだな。
真剣にやるには、大掛かり過ぎるな。
wasmtime
ブラウザーを持っていない人用に、ブラウザー内の仮想マシンをシュミレートする環境が用意されます。ってのは、半分嘘で、32bitの場合は、ソースからコンパイルしれ。
README.mdの紹介事例
Example If you’ve got the [Rust compiler installed](https://www.rust-lang.org/tools/ install) then you can take some Rust source code: ======== fn main() { println!("Hello, world!"); } -------- and compile/run it with: ======== $ rustup target add wasm32-wasi $ rustc hello.rs --target wasm32-wasi $ wasmtime hello.wasm Hello, world! --------
仮想マシン用のコンパイル環境を用意。そして例題をコンパイル。出来上がったhello.wasmを、シュミレータで実行。ただそれだけって嘆くなかれ。 https://docs.wasmtime.dev/ なんてのがあるぞ。
ちら見したら、Markdown Parser を作ってみせますでって。いい題材を選んでくれているね。
RISC-Vは、あこがれの石だけど、規模が大きすぎる。その点、ブラウザーに内蔵されるぐらい小さい石なら、お手頃だろう。
頑張ってソースからwasmtimeを入れた人はexample/の特典が有る。 上で出て来た、hello-wasmの中に、ひっそりと
#[wasm_bindgen] pub fn fibonacci(n: u32) -> u32 { match n { 0 => 0, 1 => 1, n => fibonacci(n - 1) + fibonacci(n - 2), } }
こんなコードが紛れている。これが
sakae@pen:/tmp/examples$ cat fuel.wat (module (func $fibonacci (param $n i32) (result i32) (if (i32.lt_s (local.get $n) (i32.const 2)) (return (local.get $n)) ) (i32.add (call $fibonacci (i32.sub (local.get $n) (i32.const 1))) (call $fibonacci (i32.sub (local.get $n) (i32.const 2))) ) ) (export "fibonacci" (func $fibonacci)) )
こういう楽しいコードで表されているの。ブラウザーに忍び込ませておく言語として、初代ネットスケープの連中はschemeを欲した。けど、一般公開する言語として、括弧だらけの言語はいかがなものかと言う亊になり、新たにJavascriptが発明された。
今、やっと括弧だらけの言語が復権したね。よかったよかった。
で、少々煩わしいのは、これを呼び出す方法。ブラウザーだと上でみたようにローダーとしてjavascriptが使われていた。けど、wasmtimeの環境では、そうもいかない。
sakae@pen:/tmp/examples$ cat fuel.rs //! Example of limiting a WebAssembly function's runtime using "fuel consumption". // You can execute this example with `cargo run --example fuel` use anyhow::Result; use wasmtime::*; fn main() -> Result<()> { let mut config = Config::new(); config.consume_fuel(true); let engine = Engine::new(&config)?; let mut store = Store::new(&engine, ()); store.add_fuel(10_000)?; let module = Module::from_file(store.engine(), "examples/fuel.wat")?; let instance = Instance::new(&mut store, &module, &[])?; let fibonacci = instance.get_typed_func::<i32, i32, _>(&mut store, "fibonacci")?; for n in 1.. { let fuel_before = store.fuel_consumed().unwrap(); let output = match fibonacci.call(&mut store, n) { Ok(v) => v, Err(_) => { println!("Exhausted fuel computing fib({})", n); break; } }; let fuel_consumed = store.fuel_consumed().unwrap() - fuel_before; println!("fib({}) = {} [consumed {} fuel]", n, output, fuel_consumed); store.add_fuel(fuel_consumed)?; } Ok(()) }
こんなローダーを使うとな。
そして、色々と遊びたいなら、 wabt も入れておけとな。
sakae@pen:/tmp/hello-wasm/pkg$ wasm2wat hello_wasm_bg.wasm >aa.wat sakae@pen:/tmp/hello-wasm/pkg$ lv aa.wat (module (type (;0;) (func)) (type (;1;) (func (result i32))) (type (;2;) (func (param i32))) (type (;3;) (func (param i32) (result i32))) (type (;4;) (func (param i32) (result i64))) : (func $dlmalloc::dlmalloc::Dlmalloc::malloc::h60f614cc9308e3a0 (type 6) (param i32 i32) (result i32) (local i32 i32 i32 i32 i32 i32 i32 i32 i64) block ;; label = @1 block ;; label = @2 block ;; label = @3 local.get 1 i32.const 245 :
WebAssembly
WebAssembly のベンチマークとバイトコードと Lisp
やっぱり先客はおられた。みんなSO思うよな。
WebAssembly / spec が、仮想マシンのスペック表らしい。扱えるのは、i32,i64,f32,f64だけと言う潔さ、が売り? 後、文字列の扱い等はjavascriptに丸投げしてるとか。
これは、ビットコインの発掘に最適です。きった貴方のブラウザーでも人知れずに動いている亊でしょう。
interpreter/README.md あたりが、取っ掛かりになるかな。 proposals/simdなんてのも有るから、これはもう、全力でCPUパワーを食い潰せる仕様(提案)だ。あれ、GPUはまだサポートしてない? WebGPU なんてのが有るな。
WebAssembly Specification こういう手っ取り早いWEBも有った。
凄い方もおられるなあ。
pub fn fib(n: u32) -> u32 { match n { 0 => 0, 1 => 1, n => fib(n - 1) + fib(n - 2), } } fn main() { println!("{}", fib(12)); }
sakae@deb:/tmp/t$ rustc main.rs --target wasm32-wasi sakae@deb:/tmp/t$ file main.wasm main.wasm: WebAssembly (wasm) binary module version 0x1 (MVP) sakae@deb:/tmp/t$ wasmtime main.wasm thread 'main' panicked at 'host machine is not a supported target: "unsupported architecture"', crates/cranelift/src/builder.rs:48:48 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
32bit環境では、動かない。腐ったwasmtimeを作っちゃったかな。
sakae@deb:/tmp/t$ wasm2wat main.wasm -o aa.wat sakae@deb:/tmp/t$ wc aa.wat 25930 53347 709378 aa.wat
心を入れ替えて64bit環境。wasimtimeも、curl https://wasmtime.dev/install.sh -sSf | bash で、簡単インストール。
sakae@pen:/tmp/t$ cargo b --target wasm32-wasi Compiling t v0.1.0 (/tmp/t) Finished dev [unoptimized + debuginfo] target(s) in 0.48s sakae@pen:/tmp/t$ file target/wasm32-wasi/debug/t.wasm target/wasm32-wasi/debug/t.wasm: WebAssembly (wasm) binary module version 0x1 (MVP) sakae@pen:/tmp/t$ wasmtime target/wasm32-wasi/debug/t.wasm 144
ちゃんと動いているようなので、220210にやったmeas!()で、実行時間を計測。
sakae@pen:/tmp/t$ cargo b --release --target wasm32-wasi Compiling t v0.1.0 (/tmp/t) Finished release [optimized] target(s) in 0.52s sakae@pen:/tmp/t$ wasmtime target/wasm32-wasi/release/t.wasm fib(40) Elapsed: 0.731 102334155
対戦相手は、インテルのamd64な石です。
sakae@pen:/tmp/t$ cargo b --release Compiling t v0.1.0 (/tmp/t) Finished release [optimized] target(s) in 0.32s sakae@pen:/tmp/t$ target/release/t fib(40) Elapsed: 0.432 102334155
一応gaucheでも計測する。
gosh> (time (fib 40)) ;(time (fib 40)) ; real 11.018 ; user 11.010 ; sys 0.000 102334155
最後はここに帰って來るって亊で、C言語でも(-O2)。
sakae@pen:/tmp/t$ time ./a.out 102334155 real 0m0.248s user 0m0.236s sys 0m0.012s
そして、これがリアルなブラウザーの結果(fib 40)。
firefox chrome edge WebAssembly 2.672 1.4594 1.5995 JavaScript 3.333 1.9909 2.016
Windows 10に入っているブラウザーです。サーバーは、debian(64)なマシン。上の方で参照してる、WebAssembly のベンチマークを使わして頂きました。 エッジは毛嫌いしてたけど、開発ツールは、表示フォントが大きくて眼にやさしいね。
tools for WebAssembly
新しいCPUの開発環境として、下記が提供されてた。言わばgauche内蔵の仮想マシンを外部に取出し、それ用のコマンドを提供してるって亊。
spectest-interp wasm-decompile wasm-opcodecnt wast2json wasm2c wasm-interp wasm-strip wat2wasm wasm2wat wasm-objdump wasm-validate wat-desugar
目星いコマンドを試してみる。最初に、名前から想像が付くコマンド。これを見て、チューニングをするんだな。
sakae@pen:/tmp/t/target/wasm32-wasi/release$ wasm-opcodecnt t.wasm Total opcodes: 26906 Opcode counts: local.get: 6382 i32.const: 4693 i32.add: 1768 : Opcode counts with immediates: i32.add: 1768 end: 1483 local.get 2: 1127 block: 1113 :
これもすぐ分るな。逆アセンブラーだ。
sakae@pen:/tmp/t/target/wasm32-wasi/release$ wasm-decompile t.wasm : function start_command_export() { wasm_call_ctors(); start(); wasm_call_dtors(); } function main_command_export(a:int, b:int):int { wasm_call_ctors(); let t0 = main(a, b); wasm_call_dtors(); return t0; }
こちらが、本物の逆アセンブラーだな。
sakae@pen:/tmp/t/target/wasm32-wasi/release$ wasm-objdump -d t.wasm : 00ee31 func[246] <_start.command_export>: 00ee32: 10 05 | call 5 <__wasm_call_ctors> 00ee34: 10 06 | call 6 <_start> 00ee36: 10 9a 01 | call 154 <__wasm_call_dtors> 00ee39: 0b | end 00ee3b func[247] <main.command_export>: 00ee3c: 10 05 | call 5 <__wasm_call_ctors> 00ee3e: 20 00 | local.get 0 00ee40: 20 01 | local.get 1 00ee42: 10 0f | call 15 <main> 00ee44: 10 9a 01 | call 154 <__wasm_call_dtors> 00ee47: 0b | end
そして、ヘッダー情報。
sakae@pen:/tmp/t/target/wasm32-wasi/release$ wasm-objdump -h t.wasm t.wasm: file format wasm 0x1 Sections: Type start=0x0000000b end=0x00000099 (size=0x0000008e) count: 21 Import start=0x0000009c end=0x0000015a (size=0x000000be) count: 5 Function start=0x0000015d end=0x00000252 (size=0x000000f5) count: 243 :
次は、どう使えばよいかピンとこないやつ。
usage: wasm-interp [options] filename [arg]... read a file in the wasm binary format, and run in it a stack-based interpreter. examples: # parse binary file test.wasm, and type-check it $ wasm-interp test.wasm # parse test.wasm and run all its exported functions $ wasm-interp test.wasm --run-all-exports # parse test.wasm, run the exported functions and trace the output $ wasm-interp test.wasm --run-all-exports --trace # parse test.wasm and run all its exported functions, setting the # value stack size to 100 elements $ wasm-interp test.wasm -V 100 --run-all-exports
これはS式のフォーマッターか。emacsのlispモードで何とかなりそうな予感がするけどな。 いや、最近の若者はemacsなんて知らないだろうから、あえてこんなコマンドが提供されてるのか。
usage: wat-desugar [options] filename read a file in the wasm s-expression format and format it. examples: # write output to stdout $ wat-desugar test.wat # write output to test2.wat $ wat-desugar test.wat -o test2.wat # generate names for indexed variables $ wat-desugar --generate-names test.wat
etc
楽しい亊が一杯あるなあ。短歌なんてあれを思い出すよ。