WASI

comrak

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

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) -&gt; 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 なんてのに触れてみるか。 資料は、

WASI tutorial

Wasmtime

ぐらいかな。

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 をためしてみる

WebAssembly のベンチマークとバイトコードと Lisp

やっぱり先客はおられた。みんなSO思うよな。

WebAssembly / spec が、仮想マシンのスペック表らしい。扱えるのは、i32,i64,f32,f64だけと言う潔さ、が売り? 後、文字列の扱い等はjavascriptに丸投げしてるとか。

これは、ビットコインの発掘に最適です。きった貴方のブラウザーでも人知れずに動いている亊でしょう。

interpreter/README.md あたりが、取っ掛かりになるかな。 proposals/simdなんてのも有るから、これはもう、全力でCPUパワーを食い潰せる仕様(提案)だ。あれ、GPUはまだサポートしてない? WebGPU なんてのが有るな。

WebAssembly Specification こういう手っ取り早いWEBも有った。

RustでWebAssemblyインタプリタ作った話

凄い方もおられるなあ。

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