Introduction
最近各所で盛り上がってきているWebAssembly。
ブラウザでJavaScriptと連携させて高速処理を実行したりするプログラムです。
(ブラウザ以外で実行環境としての活用も少しつづ進みつつある)
今回はWasmtimeを使ってWebAssemblyプログラムを動かしてみます。
Confirmation of terms
まずは基本的な用語の確認をしておきましょう。
WebAssembly(WASM)?
WebAssemblyは、ブラウザ上においてネイティブレベルのパフォーマンスで
動作することを目的に策定されたバイナリフォーマットです。
C/C++/Rustなどのプログラム言語をコンパイルしてWASMとして動かすことができます。
GoogleやMozzilaなど、いろいろなTech企業によって仕様策定が進められています。
現状ではブラウザ上で動かすことが多いですが、
最近はそれ以外の環境で動かすことも増えてきました。
ブラウザ以外の実行環境において、(OSリソースへアクセスするためなど)WebAssemblyの
インターフェイスを策定するのが、後述するWASIです。
WASI?
WASIとは、WebAssembly System Interfaceの略で、
WebAssemblyをブラウザ以外の環境で実行するためのシステムインターフェイスです。
このインターフェイスによって、WASMがOSのリソースにアクセスできます。
一般的にいうWASMがWebAssemblyのcore apiにJavascriptインターフェイスと
Web apiを加えたもので、WASIはcore apiにWASI APIを加えたもののようです。
- 参考:https://zenn.dev/newgyu/scraps/ffbce244b960e6
WASIの実装として、WasmerやWasmEdge、
本稿で紹介するWasmtimeなどがあります。
Wasmtime?
Wasmtimeとは、WebAssembly用の軽量&コンパクトなランタイムです。
Rustで実装されてます。
Wasmtimeは、規模に関係なくどんなアプリでも実行できるよう、
埋め込み可能なランタイムを目指して鋭意開発中とのことです。
ちなみに、ここではwasmtimeをつかってWASMを実行してます。
Environment
- MacBook Pro (13-inch, M1, 2020)
- OS : MacOS 11.3.1
- rust : 1.61.0
Setup
Wasmtimeはcurlを使ってインストールします。
% curl https://wasmtime.dev/install.sh -sSf | bash
Installing latest version of Wasmtime (v0.37.0)
Checking for existing Wasmtime installation
Fetching archive for macOS, version v0.37.0
https://github.com/bytecodealliance/wasmtime/releases/download/v0.37.0/wasmtime-v0.37.0-aarch64-macos.tar.xz
######################################################################## 100.0%
Creating directory layout
Extracting Wasmtime binaries
x wasmtime-v0.37.0-aarch64-macos/
x wasmtime-v0.37.0-aarch64-macos/wasmtime
x wasmtime-v0.37.0-aarch64-macos/LICENSE
x wasmtime-v0.37.0-aarch64-macos/README.md
Finished installation. Open a new terminal to start using Wasmtime!
wasmtimeコマンドが実行できればインストールOKです。
% wasmtime -V
wasmtime-cli 0.37.0
Try
では、WebAssemblyを動かしてみましょう。
ここではRustをつかってwasmファイルを作成します。
WebAssemblyのバイナリをRustでビルドするには普通に
RustがインストールされていればOKです。
(WebAssembly用のビルドターゲットは必要)
rustcでビルド
まずはシンプルにrustcでコンパイルしてみます。 main.rsを下記のように作成。
fn main() {
println!("Hello, world from rustc");
}
wasm32-wasiターゲットを追加して、rustcでコンパイル。
% rustup target add wasm32-wasi
% rustc main.rs --target wasm32-wasi
main.wasmが生成されているので、wasmtimeで実行。
% wasmtime main.wasm
Hello, world from rustc
Cargoでビルド
次はCargoでビルドしてみます。
% cargo new hello-wasmtime && cd hello-wasmtime
src/main.rsを修正。
fn main() {
println!("Hello, world from cargo");
}
target指定してビルド&実行。
% cargo build --target wasm32-wasi
% wasmtime target/wasm32-wasi/debug/hello-wasmtime.wasm
Hello, world from cargo
なお、これらのwasmファイルは別環境(Linuxとか)にもっていっても、
そのまま動かすことができます。
なんという Write once, run anywhere。
[Appendix] WASM on Deno
ここでは、Rustのコードをwasm
にコンパイルしてDenoで動かしています。
同じようにやってみましょう。
Denoをインストール
まずは↓のようにDenoのインストールをします。
% curl -fsSL https://deno.land/x/install/install.sh | sh
もしくは
% brew install deno
WebAssemblyバイナリの作成
wasmファイルを生成するためにRustプロジェクトを作成します。
新しいターゲットも追加する必要があるのでtarget addします。
% cargo new wasm-deno --lib
Created library `wasm-deno` package
#さっきのwasm32-wasiとは違うターゲット
% rustup target add wasm32-unknown-unknown
Cargo.tomlは下記のようにします。
WASMとjsのブリッジ用ツール、wasm-bindgenを追加しています。
[package]
name = "wasm-deno"
version = "0.1.0"
edition = "2021"
[dependencies]
wasm-bindgen = "0.2.80"
[lib]
name = "sayhello"
crate-type =["cdylib", "lib"]
[[bin]]
name = "mybin"
path = "src/main.rs"
スタンドアロンで実行する用にbinセクション、
Denoのライブラリとしてビルドする用にlibセクションを記述しています。
次に、src/lib.rsを下記のように記述。
Denoから呼び出される関数として定義します。
wasm_bindgenマクロがjsとrustのブリッジをしてくれます。
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn say_hello() -> String {
return "Hello Wasm on Deno".to_string();
}
動作確認用にmain.rsを記述してみます。
use sayhello::say_hello;
fn main() -> std::io::Result<()> {
let message = say_hello();
println!("{}", message);
Ok(())
}
say_hello関数は普通に実行可能です。
% cargo run --bin mybin
Compiling wasm-deno v0.1.0
Finished dev [unoptimized + debuginfo] target(s) in 0.26s
Running `target/debug/mybin`
Hello Wasm on Deno
wasmファイルを生成するため、libとtargetを指定してビルド実行します。
% cargo build --lib --target wasm32-unknown-unknown
ビルドすると、
target/wasm32-unknown-unknown/debug/に
sayhello.wasmが生成されます。
そして、wasm-bindgenでDeno用にwasmのブリッジファイルを生成。
% mkdir server
% wasm-bindgen --target deno ./target/wasm32-unknown-unknown/debug/sayhello.wasm --out-dir ./server
server/main.tsファイルで簡易httpサーバ機能を作成して、
say_hello関数を呼んでみましょう。
import { serve } from "https://deno.land/std@0.141.0/http/server.ts";
import { say_hello } from "./sayhello.js";
async function reqHandler(req: Request) {
let message = say_hello();
return new Response(message);
}
serve(reqHandler, { port: 5000 });
ちゃんとJavaScript経由で実行できています。
% deno run --allow-read --allow-net --allow-env ./server/main.ts
% curl http://localhost:5000
Hello Wasm on Deno
なお、lib.rsのuseとwasm_bindgenマクロをコメントアウトして、
さきほどと同じくwasm32-wasiをtargetとしてビルドすれば、
wasmtimeで実行可能です。