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
楽しい亊が一杯あるなあ。短歌なんてあれを思い出すよ。