LoginSignup
46
30

More than 3 years have passed since last update.

WASI (WebAssembly System Interface)のランタイム5種を動かす

Last updated at Posted at 2019-12-21

はじめに

これは Node.js Advent Calendar 2019 の22日目の記事です。内容としてはNode.jsから遠いかもしれませんが、先日のJSConf.JP のLT発表のオマケとしてこちらに書かせていただきます。

WebAssembly と WASI

WebAssembly(WASM)は、ブラウザで実行できるバイナリーコードで、「同じコードを全てのマシンで高速、スケーラブル、安全に実行できる」ことを目指して作られています。その実行環境はブラウザを飛び出し、Node.jsでも直接利用できるようになりました。

現在のWASM自体は数値処理に特化していて、ファイルI/Oやユーザーインターフェイスについては直接利用はできません。ファイルやUIに関してはブラウザやNode.jsといった呼び出し元に処理を委ねることになります。

WASMをもっと色々な環境で利用するために、WebAssembly System Interface (WASI) という仕様が提案され、現在Bytecode Allianceという団体が活動しています。

WASIでは、POSIXのシステムコールに類似する、次の要素へのアクセスを提供します。

  • ファイル
  • ネットワーク
  • クロック
  • 乱数

Core APIの一覧

WASIのランタイム

すでに複数のWASIランタイムが実装されていて、利用することができます。それぞれ特徴を持ったものになっています。

また、Node.jsでもWASIをサポートする動きがでているようです。

先日のLT発表のときは wasmtime を利用していましたが、今回は他のランタイムも実行できるか試してみます。

前提環境

対象コード

こちらの記事「Node.js でつくる WASMコンパイラー - Extra1:WASIを使ってWASMを動かす」で作成した、fizzbuzzとフィボナッチ数列のJSコードをWASI対応のWASMにしたものを対象にします。内部ではWASIのAPIのfd_write()のみ利用しています。

実行環境

macOS Mojave 10.14.6 で環境構築、実行しました。(一部、macOSでの環境が作成できずに、Ubuntu 18.04を使っています)

WAT→WASMの変換

テキストフォーマットのWATから、バイナリフォーマットのWASMに変換するために、WebAssembly/wabt に含まれるwat2wasmを使いました。

インストール手順は次の通りです。cmakeが必要です。

$ git clone --recursive https://github.com/WebAssembly/wabt
$ cd wabt
$ mkdir build
$ cd build
$ cmake ..
$ cmake --build .

ビルド後、必要に応じてパスを通しておきます。

次のように変換すれば、テキスト形式からバイナリ形式のfizzbuzzz_wasi.wasmが生成されます。

$ wat2wasm fizzbuzz_wasi.wat

※上記の対象コードは、バイナリに変換済みのものを用意してありますので、それを使えばwat2wasmは不要です。

wasmtime の場合

wasmtimeの環境作成

wasmtimeをビルドするには、RustとCargoが必要です。私がビルドした時は、version 0.7.0 でした。

$ git clone --recurse-submodules https://github.com/bytecodealliance/wasmtime.git
$ cd wasmtime
$ cargo build --release
$ ./target/release/wasmtime --version
0.7.0

wasmtimeでの実行

wasmtimeは、テキスト形式のWATとバイナリ形式のWASMの両方を動かすことができます。

$ wasmtime fizbuzz_wasi.wasm
1
2
Fizz
4
Buzz
Fizz
... 省略 ...
98
Fizz
Buzz

ちなみに wasmtime 自身のサイズは8 MBです。

Lucet の場合

Lucetの環境作成

Lucetの環境をローカルに構築するには、Dockerが必要です。

$ git clone https://github.com/bytecodealliance/lucet.git
$ git submodule init 
$ git submodule update
$ source devenv_setenv.sh

devenv_setenv.sh では、未作成ならコンテナをビルドし、起動します。コンテナ自体はUbuntuで、起動するとsleepで待ち状態に入るようです。また devenv_setenv.sh では必要なツール類にパスも通してくれます。

$ docker ps
CONTAINER ID        IMAGE               COMMAND                 CREATED             STATUS              PORTS               NAMES
c2xxxxxxxx94        lucet:latest        "/bin/sleep 99999999"   16 hours ago        Up 16 hours                             lucet

lucetでの実行

直接wasmを実行するのではなく一度lucetc-wasiで変換してからlucet-wasiで実行します。

ホストOS上で実行
$ lucetc-wasi fizzbuzz_wasi.wasm -o fizzbuzz.so
$ lucet-wasi fizzbuzz.so

lucetc-wasi, lucet-wasi自体はホストOS上(今回はmacOS)で動くシェルスクリプトで、コンテナの内部の lucetc-wasi, lucet-wasi を実行しています。

  • ホストOS上の lucetc-wasi ... lucet/host/bin/lucetc-wasi (シェルスクリプト)
  • ホストOS上の lucet-wasi ... lucet/host/bin/lucet-wasi(シェルスクリプト)
  • コンテナ内の lucetc-wasi ... /opt/lucet/bin/lucetc-wasi (シェルクスリプト、最終的には同じディレクトリの lucetc を実行)
  • コンテナ内の lucet-wasi ... /opt/lucet/bin/lucet-wasi (実行モジュール)

ちなみにlucetコンテナ内の lucet-wasi 自身のサイズは8 MBです。

WebAssembly Micro Runtime(WAMR) の場合

こちらは組み込みデバイスを想定して、JITコンパイラーを除外してインタープリターのみ実装しているそうです。

WAMRの環境作成

ドキュメントによると下記手順でMac用にビルドできるとのことですが、makeでエラーが出てしまいました。

macOS用のビルド手順
$ git clone https://github.com/bytecodealliance/wasm-micro-runtime.git
$ cd wasm-micro-runtime/
$ cd core/iwasm/products/darwin/
$ mkdir build
$ cd build
$ cmake ..
$ make

そのため、今回は例外的にUbuntu 18.04でビルドして試しました。cmakeが必要です。

Ubuntu用のビルド手順
$ sudo apt install lib32gcc-5-dev g++-multilib
$ sudo apt install build-essential
$ sudo apt install cmake
$
$ git clone https://github.com/bytecodealliance/wasm-micro-runtime.git
$ cd wasm-micro-runtime/
$ cd core/iwasm/products/linux/
$ mkdir build
$ cd build
$ cmake ..

WAMRでの実行

先ほどのbuildディレクトリにiwasmという実行モジュールが出来ているので、それを使います。

$ ./iwasm fizzbuzz_wasi.wasm

ちなみにiwasm自身のサイズは226 KBでした。こちらはUbuntuなので他のmacOSの物と比較はできませんが、wasmtimeと一桁違うということは相当小さいですね。

WASMERの場合

名前が紛らわしいですが、WASMERというランタイムもあります。wapmというパッケージマネージャーを備えていたり、PHPやRubyといったスクリプト言語からWASMを実行するためのインタフェイスを提供していることが特徴です。

WASMERの環境作成

スクリプトを実行することで、セットアップができます。各種プラットフォーム向けのビルド済みバイナリが用意されているようです。ARM64向けのバイナリもあり、AWS a1.medium でも実行することができました。

$ curl https://get.wasmer.io -sSfL | sh
$ source ~/.wasmer/wasmer.sh

WASMERでの実行

先ほどのsource実行すると、wasmerにパスが通っているので、そのまま実行できます。

$ wasmer fizzbuzz_wasi.wasm

wasmer自身のサイズは32 MBです。こちらはwasmtimeと比べて一桁大きいです。

Node.js の場合

タイムリーなことに、2019/12/3にリリースされたNode.js v13.3.0で、WASIが試験的にサポートされました。

Node.js v13.3での実行

wasiモジュールを利用し、WASMの実行環境として wasi.wasiImport を渡します。

run_wasi.js
// node --experimental-wasi-unstable-preview0 run_wasi.js your_wasi.wasm

'use strict'

const fs = require('fs');
const filename = process.argv[2]; // 対象とするwasmファイル名
console.warn('Loading wasm/wasi file: ' + filename);

const { WASI } = require('wasi');
const wasi = new WASI({
  args: process.argv,
  env: process.env,
  preopens: {
    //'/sandbox': '/some/real/path/that/wasm/can/access'
  }
});
const importObject = { wasi_unstable: wasi.wasiImport };

(async () => {
  const wasm = await WebAssembly.compile(fs.readFileSync(filename));
  const instance = await WebAssembly.instantiate(wasm, importObject);

  wasi.start(instance);
})();

wasi.wasiImportには、次のようにWASIのAPIの定義が含まれています。今回のサンプルで呼び出しているfd_write()も存在しています。

wasi.wasiImportの内容(抜粋)
WASI {
  args_get: [Function: bound args_get],
  args_sizes_get: [Function: bound args_sizes_get],
  clock_res_get: [Function: bound clock_res_get],
  clock_time_get: [Function: bound clock_time_get],
  environ_get: [Function: bound environ_get],
  environ_sizes_get: [Function: bound environ_sizes_get],
  fd_advise: [Function: bound fd_advise],
  fd_allocate: [Function: bound fd_allocate],
  fd_close: [Function: bound fd_close],
  ... 省略 ...
  fd_write: [Function: bound fd_write],
  path_create_directory: [Function: bound path_create_directory],
  ... 省略 ...
  proc_exit: [Function: bound proc_exit],
  proc_raise: [Function: bound proc_raise],
  random_get: [Function: bound random_get],
  sched_yield: [Function: bound sched_yield],
  sock_recv: [Function: bound sock_recv],
  sock_send: [Function: bound sock_send],
  sock_shutdown: [Function: bound sock_shutdown]
}

まだ試験的なサポートなので、WASIの実行にはnode起動時に --experimental-wasi-unstable-preview0 オプションが必要です。

$ node --experimental-wasi-unstable-preview0 run_wasi.js fizzbuzz_wasi.wasm

参考までにnode自体のサイズは67 MBでした。WASIの実行環境としては最重量級です。

まとめ

WASMを色々なOS上で実行するためのWASIのランタイムも徐々に増えてきまいた。今回WASI向けに生成した同一のWASMファイルが、5種のランタイム上で同じように動作することが確認できました。
使っているAPIがfd_write()だけなので、完全に互換性が確認されたわけではありませんが、同一バイナリを複数環境で動かすというWASM/WASIの理念が実装されていることが分かりました。

ランタイムのサイズだけでなく、実行速度や実行中のメモリ使用量なども調べたいところですが、今回はそこまでやれませんでした。まだまだ改良が進むと予想されるので、定期的に比較すると面白そうです。

46
30
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
46
30