WasmtimeでWebAssemblyを動かしてみる

2022.05.31

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

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の実装として、WasmerWasmEdge
本稿で紹介する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で実行可能です。

References