🏃

VS Code で .wasm(WASI) を実行する拡張機能を 2 つ作った

2023/12/25に公開

WASM WASI Core Extension により VS Code の拡張機能で .wasm (WASI)を扱いやすくなったのですが、実行するためには個別の拡張機能を作成する必要があります。

「それもちょっと面倒だよなぁ」ということで、WASM WASI Core Extension を利用しつつ任意の .wasm を実行できる拡張機能を作りました。

どのような拡張機能?

Wasmtime ランタイムのようなコマンドライン(ターミナル)で任意の .wasm を実行する拡張機能です。

図 1-1 wasmtime run の例

$ wasmtime run /path/to/file.wasm

図 1-2 今回の拡張機能の例

$ rw /path/to/file.wasm

上記のように似たような使い方になりますが、今回の拡張機能は「インストールから実行まで手軽に行える」ことが特徴と考えています(構成によっては Web 版の VS Code だけでも実行できます)。

Web Shell 用の拡張機能

今回作った拡張機能は 2 つあり、1 つは Web Shell 内に rw コマンドを配置します。これにより rw コマンドから .wasm を実行できるようになります。

インストール

以下の拡張機能をインストールすると利用可能になります。依存する拡張機能も自動的にインストールされます。

ただし、1 つ注意点があります。現状では Preview 版のみ公開しているので、基本的には Insiders 版の VS Code にのみインストールできます。

基本操作

以下は cowsay を実行しているスクリーンショットです。追加のランタイムを必要としないので Web 版 VS Code 単体でも利用可能となっています。

図 2-1 rw コマンドで cowsay.wasm を実行

Web版の VS Code で Web Shell のターミナルを開き、"rw" コマンドで "cowsay.wasm" を実行しているスクリーショット

.wasm の情報

また、 .wasm のインポートとエクスポート状況を表示する機能もあります。

図 2-2 chk1.wasm のインポートとエクスポート

$ rw --print-imports-exports -- chk1.wasm
import: wasi_snapshot_preview1 args_get function
import: wasi_snapshot_preview1 args_sizes_get function
import: wasi_snapshot_preview1 fd_read function
import: wasi_snapshot_preview1 fd_readdir function
import: wasi_snapshot_preview1 fd_write function
import: wasi_snapshot_preview1 path_open function
import: wasi_snapshot_preview1 environ_get function
import: wasi_snapshot_preview1 environ_sizes_get function
import: wasi_snapshot_preview1 fd_close function
import: wasi_snapshot_preview1 fd_prestat_get function
import: wasi_snapshot_preview1 fd_prestat_dir_name function
import: wasi_snapshot_preview1 proc_exit function
export: memory memory
export: _start function
export: __main_void function

マルチスレッド

WASM WASI Core Extension では wasi thread-spawn も利用できています。Rust の wasm32-wasi-preview1-threads ターゲットを使ってビルドしたものも少し試した限りでは動作しました。

以下、テスト用の .wasm を動かしているところです(0..10 のインデックスを 100ms 間隔で表示する)。

リスト 2-1 テスト用のソースコード

use std::thread;
use std::time::Duration;

fn main() {
    let mut handles = vec![];
    let workers = 2;
    for worker in 0..workers {
        handles.push(thread::spawn(move || {
            println!("start {}", worker);
            for i in 0..10 {
                println!("worker {} count {}", worker, i);
                thread::sleep(Duration::from_millis(100));
            }
            println!("end {}", worker);
        }));
    }
    for handle in handles {
        handle.join().unwrap();
    }
}

図 2-3 ビルドすると thread-spwan を利用している .wasm になる

/workspace $ rw --print-imports-exports -- workspace.wasm
import: env memory memory
import: wasi_snapshot_preview1 fd_write function
import: wasi_snapshot_preview1 poll_oneoff function
import: wasi_snapshot_preview1 environ_get function
import: wasi_snapshot_preview1 environ_sizes_get function
import: wasi_snapshot_preview1 clock_time_get function
import: wasi_snapshot_preview1 proc_exit function
import: wasi_snapshot_preview1 sched_yield function
import: wasi thread-spawn function
export: memory memory
export: _start function
export: __main_void function
export: wasi_thread_start function

図 2-4 実行し経過時間を表示

Web版の VS Code で Web Shell のターミナルを開き、"rw" コマンドでマルチスレッド検証用の `.wasm` を実行しているスクリーショット

スレッドを 2 つ動かしたところ、実行時間は 1382ms でした。いろいろオーバーヘッドがあるので若干遅いですが、2000ms を超えていないのでおそらくは並列に動作していると考えられます。

考慮点

Web Shell は実験的な実装であり一般的な機能が実装されていないので(パイプなどが使えない)、あまり凝ったことはできません。

また、どのような .wasm ファイルを実行できるわけでもありません。基本的には WASM WASI Core Extension が対応しているものに限られます。

たとえば、Wasmer のレジストリーの登録されているパッケージは、試した限りでは動作しない方が多い印象でした。

図 2-7 動作しないパッケージの例(wasi_unstable をインポートしている)

"rw" コマンドでインポートしている項目の一覧を表示しているスクリーショット

通常ターミナル(シェル)用の拡張機能

もう 1 つは通常のターミナルから利用できる拡張機能です。こちらは、拡張機能が .wasm を実行するサーバーとして動作します。

別途、クライアントツール(crw コマンド)をインストールすると通常のターミナルで.wasm を実行できるようになります。

Web Shell 用の拡張機能と比べるとパイプなども利用できるので扱いやすくはなっています。

図 3-1 crw コマンドで cowsay.wasm を実行

VS Code で通常ののターミナルを開き、"crw" コマンドで "cowsay.wasm" を実行している。また実行結果をパイプで他コマンドへ渡しているスクリーショット

しかし、下記のような問題点を含んでいます。

  • .wasm プロセスの stdio が安定しない

    • 現状、wasm プロセスの stdin を閉じる方法がなさそう
  • 遅い

  • クライアント - サーバー間の通信はデータを number[] に変換している

  • pty を考慮していない

  • クライアントツールは Linux 用しか用意していない

実行ファイルを 1 つ追加するだけで通常のターミナル内で .wasm を実行できるところは気に入っているのですが、現状ではちょっと厳しいかなというところです。

おわりに

任意の wasm を VS Code で実行する拡張機能を作ってみました。

正直なところ頻繁に使うものではないですが「自作ツールを .wasm としてちょっとだけ動かしてみたい」という場合、「気軽に試す選択肢になるかな」と考えています。

脚注
  1. devbox という名前で開発環境を構築するツールはいくつかありますが、それらとは別のツールです(たぶん)。 ↩︎

GitHubで編集を提案

Discussion