wasm(WebAssembly)のランタイムについて、いろいろ調べてみました。
WebAssemblyは何度も扱ってきていますが、WATコード(WebAssembly Text)からコンパイルを試したことがありませんでした。まずは基本を押さえたいと思います。(参考:翔泳社「入門WebAssembly」)
環境) M2 Mac
myadd.wat
1 2 3 4 5 6 7 8 9 |
(module (func (export "myadd") (param $value.1 i32) (param $value.2 i32) (result i32) local.get $value.1 local.get $value.2 i32.add ) ) |
value 1,2をスタックに積んで、関数呼び出し。まさにアセンブラですね。
myadd.js
1 2 3 4 5 6 7 8 9 10 11 |
const fs = require('fs'); const bytes = fs.readFileSync(__dirname + '/myadd.wasm'); const v1 = parseInt(process.argv[2]); const v2 = parseInt(process.argv[3]); (async () => { const obj = await WebAssembly.instantiate(new Uint8Array(bytes)); let ret = obj.instance.exports.myadd(v1, v2); console.log("%d + %d = %d", v1, v2, ret); })(); |
% node -v
v18.12.1
% npm -v
8.19.2
% npm install -g wat-wasm
…
% wat2wasm myadd.wat
…
Contact Rick Battagline
Twitter: @battagline
https://wasmbook.com
v1.0.43
no memory
Writing to myadd.wasm
WASM File Saved!
% node myadd.js 1 2
1 + 2 = 3
wat2wasmで、バイトコードに、そしてそれをnode.jsから呼び出しました。
次は、node.jsではなく、wasmtimeというランタイムを使って実行してみます。(brew でインストール)
引用元)https://github.com/bytecodealliance/wasmtime/blob/main/docs/WASI-tutorial.md
demo.wat
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
(module ;; Import the required fd_write WASI function which will write the given io vectors to stdout ;; The function signature for fd_write is: ;; (File Descriptor, *iovs, iovs_len, nwritten) -> Returns number of bytes written (import "wasi_unstable" "fd_write" (func $fd_write (param i32 i32 i32 i32) (result i32))) (memory 1) (export "memory" (memory 0)) ;; Write 'hello world\n' to memory at an offset of 8 bytes ;; Note the trailing newline which is required for the text to appear (data (i32.const 8) "hello world\n") (func $main (export "_start") ;; Creating a new io vector within linear memory (i32.store (i32.const 0) (i32.const 8)) ;; iov.iov_base - This is a pointer to the start of the 'hello world\n' string (i32.store (i32.const 4) (i32.const 12)) ;; iov.iov_len - The length of the 'hello world\n' string (call $fd_write (i32.const 1) ;; file_descriptor - 1 for stdout (i32.const 0) ;; *iovs - The pointer to the iov array, which is stored at memory location 0 (i32.const 1) ;; iovs_len - We're printing 1 string stored in an iov - so one. (i32.const 20) ;; nwritten - A place in memory to store the number of bytes written ) drop ;; Discard the number of bytes written from the top of the stack ) ) |
% wasmtime -V
wasmtime-cli 10.0.1
% wasmtime demo.wat
hello world
% wat2wasm demo.wat
…
Contact Rick Battagline
Twitter: @battagline
https://wasmbook.com
v1.0.43
memory found
Writing to demo.wasm
WASM File Saved!
% wasmtime demo.wasm
hello world
先ほどのWATコードでは、実行はできますが標準出力に書かないためコードを変えています。
(watから直接実行もできるもよう)
ファイル確認
% file demo.wasm
demo.wasm: WebAssembly (wasm) binary module version 0x1 (MVP)
% file myadd.wasm
myadd.wasm: WebAssembly (wasm) binary module version 0x1 (MVP)
次は、Rustのコードからコンパイルするため、環境をdockerで構築しました。
引用元) https://qiita.com/toshikisugiyama/items/168cdbf834156b1f1e70
docker-compose.yml
1 2 3 4 5 6 7 8 |
services: rust: image: rust:latest volumes: - .:/projects working_dir: /projects environment: - USER=user |
% docker compose run –rm rust
このコマンドで一気に環境を作り、コンテナのシェルの状態になります。
root@f4b90a7fb5a9:/projects# cargo new hello_cargo
Created binary (application)hello_cargo
package
root@f4b90a7fb5a9:/projects# ls
docker-compose.yml hello_cargo
root@f4b90a7fb5a9:/projects# cd hello_cargo/
root@f4b90a7fb5a9:/projects/hello_cargo# ls
Cargo.toml src
root@f4b90a7fb5a9:/projects/hello_cargo# cargo run
Compiling hello_cargo v0.1.0 (/projects/hello_cargo)
Finished dev [unoptimized + debuginfo] target(s) in 1.07s
Runningtarget/debug/hello_cargo
Hello, world!
デフオルトプロジェクトの状態で実行できることを確認できました。(これはwasmではない)
hello_cargo/src/main.rs
1 2 3 |
fn main() { println!("Hello, world!"); } |
カレントフォルダとコンテナの/projectsが、マウントされているため、ファイルの編集は別コンソールで、できます。(コマンド実行だけコンテナからやればよいことにことに)
そして、この環境を使って、wasmtimeの例を動かしてみます。
引用元) https://wasmtime.dev/
root@f4b90a7fb5a9:/projects# rustup target add wasm32-wasi
info: downloading component ‘rust-std’ for ‘wasm32-wasi’
info: installing component ‘rust-std’ for ‘wasm32-wasi’
root@f4b90a7fb5a9:/projects# rustc hello.rs –target wasm32-wasi
root@f4b90a7fb5a9:/projects# wasmtime hello.wasm
bash: wasmtime: command not found
コンテナにはwasmtimeを入れていないので、カレントに戻り、
% wasmtime hello.wasm
Hello, world!
(hello.rsは、上記 hello_cargo/src/main.rs と全く同じ)
Rustコードをwasmにコンパイルして実行できることを確認しました。
そして次は、wasmをコンテナにして実行してみます。
引用元) https://developer.mamezou-tech.com/blogs/2023/01/25/using-wasm-on-docker/
% docker run -dp 8080:8080 –name=wasm-example –runtime=io.containerd.wasmedge.v1 –platform=wasi/wasm32 michaelirwin244/wasm-example
Unable to find image ‘michaelirwin244/wasm-example:latest’ locally
latest: Pulling from michaelirwin244/wasm-example
docker: operating system is not supported.
See ‘docker run –help’.
Use containerd for pulling and storing images -> on にします。
% docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
029a5376af41 michaelirwin244/wasm-example “/hello_world.wasm” 14 seconds ago Up 9 seconds 0.0.0.0:8080->8080/tcp wasm-example
% docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
michaelirwin244/wasm-example latest 1b0714a5af55 37 seconds ago 4.14MB
% curl localhost:8080
Hello world from Rust running with Wasm! Send POST data to /echo to have it echoed back to you%
% curl -X POST -d abcdefg localhost:8080
% curl -X POST -d abcdefg localhost:8080/echo
abcdefg%
うまく立ち上がり動作も確認できました。(POST /echo の場合はBodyをEcho)
でもこれではwasmかどうかわかりませんので、サーバの部分を単体で動かしてみます。
(引用元の情報を参考にダウンロード)
% git clone https://github.com/mikesir87/wasm-example.git
…
% cd wasm-example
コンテナでコマンド実行
root@bea36f5878ef:/projects/wasm-example# rustup target add wasm32-wasi
info: downloading component ‘rust-std’ for ‘wasm32-wasi’
info: installing component ‘rust-std’ for ‘wasm32-wasi’
root@bea36f5878ef:/projects/wasm-example# cargo build –target wasm32-wasi –release
カレントに戻りwasmtime実行
% wasmtime run –tcplisten 127.0.0.1:8080 target/wasm32-wasi/release/hello_world.wasm
Error: failed to run main moduletarget/wasm32-wasi/release/hello_world.wasm
Caused by:
0: failed to instantiate “target/wasm32-wasi/release/hello_world.wasm”
1: unknown import:wasi_snapshot_preview1::sock_setsockopt
has not been defined
うまく行かず。。。
Runtimeをwasmedgeに差し替えてチャレンジ。
% curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash
Using Python: /Users/z/.pyenv/versions/miniforge3-4.10.3-10/bin/python3
INFO – Compatible with current configuration
INFO – Running Uninstaller
WARNING – Uninstaller did not find previous installation
WARNING – SHELL variable not found. Using zsh as SHELL
INFO – shell configuration updated
INFO – Downloading WasmEdge
|============================================================|100.00 %INFO – Downloaded
INFO – Installing WasmEdge
INFO – WasmEdge Successfully installed
INFO – Run:
source /Users/z/.zshenv
% wasmedge target/wasm32-wasi/release/hello_world.wasm
Server is now running
これで、さほどのPOST /echo のcurlアクセスもうまくいきました。(dockerのwasm runtimeはwasmtimeと違うようです)
wasm-example/src/main.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
use hyper::service::{make_service_fn, service_fn}; use hyper::{Body, Method, Request, Response, StatusCode, Server}; use std::convert::Infallible; use std::net::SocketAddr; use std::result::Result; async fn handle_request(req: Request<Body>) -> Result<Response<Body>, anyhow::Error> { match (req.method(), req.uri().path()) { (&Method::GET, "/") => Ok(Response::new(Body::from( "Hello world from Rust running with Wasm! Send POST data to /echo to have it echoed back to you", ))), // Simply echo the body back to the client. (&Method::POST, "/echo") => Ok(Response::new(req.into_body())), // Return the 404 Not Found for other routes. _ => { let mut not_found = Response::default(); *not_found.status_mut() = StatusCode::NOT_FOUND; Ok(not_found) } } } #[tokio::main(flavor = "current_thread")] async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { let addr = SocketAddr::from(([0, 0, 0, 0], 8080)); let make_svc = make_service_fn(|_| { async move { Ok::<_, Infallible>(service_fn(move |req| { handle_request(req) })) } }); let server = Server::bind(&addr).serve(make_svc); println!("Server is now running"); if let Err(e) = server.await { eprintln!("server error: {}", e); } Ok(()) } |
ここまでwasmの動かし方について、いろいろ試してみました。前回Goを試しましたがいろいろ調べてみるとRustが非常に向いていると感じました。(まだ関数コールをやる必要がありますが、これは次の機会に・・)
下記サイトに、Goとwasmの関係について、とても参考になる記事があります。
「GoのWASMがライブラリではなくアプリケーションであること」
https://www.kabuku.co.jp/developers/annoying-go-wasm
RustはOSすらないベアメタル環境でもプログラムを動作させることができます。様々なところで実行できるwasmだけに、こういった成り立ちの違いが大きく影響するのではと感じました。