0
0

Emscripten と WASI

Last updated at Posted at 2024-09-03

Emscripten は WASI に準拠した WebAssembly を出力できます。その挙動を確認します。

WASI

WASI は WebAssembly System Interface の略で、WebAssembly がシステムとやり取りするためのインターフェースです。WASI は WebAssembly がブラウザ以外の環境で動作するための仕様で、POSIX に似た API が提供されています。

WASI に準拠したバイナリが実行できる処理系がいくつかあります。

Emscripten

Emscripten で WASI に準拠した WebAssembly を出力する方法を確認します。

ハローワールドのソースを用意します。

hello.c
#include <stdio.h>

int main() {
    printf("Hello, World!\n");
    return 0;
}

Emscripten でオプションを何も付けずにソースだけ指定してコンパイルします。

emcc hello.c

a.out.wasm と a.out.js が出力されました。これは WASI に完全に準拠しているわけではなく、wasmtime や wasmer では動きません。

$ wasmtime a.out.wasm
Error: failed to run main module `a.out.wasm`

Caused by:
    0: failed to instantiate "a.out.wasm"
    1: unknown import: `env::_emscripten_memcpy_js` has not been defined
$ wasmer a.out.wasm
error: Instantiation failed
╰─▶ 1: Error while importing "env"."_emscripten_memcpy_js": unknown import. Expected Function(FunctionType { params: [I32, I32, I32], results: [] })

前回の記事 で見たように、_emscripten_memcpy_js はグルーコード (a.out.js) で定義される関数で、WASI に準拠していません。

WASI に準拠した WebAssembly を出力するには、-sPURE_WASI を付けてコンパイルします。

emcc -sPURE_WASI hello.c

これは wasmtime や wasmer で動作します。グルーコード経由で Node.js でも動きます。

$ wasmtime a.out.wasm
Hello, World!
$ wasmer a.out.wasm
Hello, World!
$ node a.out.js
Hello, World!

emcc に HTML を出力させれば、ブラウザでも動きます。

最低限のグルーコード

wasmtime や wasmer は WASI に準拠した関数を提供するため、a.out.wasm が動きます。

自分でグルーコードを書く場合、インポートされる proc_exitfd_write を実装する必要があります。

(async function () {
    const fs = require('fs');
    const fds = [null, process.stdout, process.stderr];
    const wasmBuffer = fs.readFileSync('a.out.wasm');
    const wasmModule = await WebAssembly.instantiate(wasmBuffer, {
        wasi_snapshot_preview1: {
            proc_exit: process.exit,
            fd_write,
        },
    });
    const buffer = wasmModule.instance.exports.memory.buffer;
    const view = new DataView(buffer);
    const HEAPU8 = new Uint8Array(buffer);

    wasmModule.instance.exports._start();

    function fd_write(fd, iov, iovcnt, pnum) {
        const buf = [];
        for (let i = 0; i < iovcnt; i++, iov += 8) {
            const ptr = view.getUint32(iov, true);
            const len = view.getUint32(iov + 4, true);
            for (let j = 0; j < len; j++) {
                buf.push(HEAPU8[ptr + j]);
            }
        }
        const decoder = new TextDecoder('utf-8');
        if (fds[fd]) fds[fd].write(decoder.decode(new Uint8Array(buf)));
        view.setUint32(pnum, buf.length, true);
        return 0;
    }
})();
実行結果
Hello, World!

単に main を抜けるだけでなく、明示的にプロセスを終了させる proc_exit が呼ばれます。

例外

process.exit は Node.js に依存しています。例外を投げることで JavaScript に制御を戻す方法があります。

変更箇所 1
            proc_exit: exitCode => { throw exitCode },
変更箇所 2
    try {
        wasmModule.instance.exports._start();
    } catch (e) {
        console.log("exit code:", e);
    }
実行結果
Hello, World!
exit code: 0

WASI の将来

WASI はまだ仕様がプレビューの段階で、POSIX の機能を完全に提供しているわけではありません。将来的には POSIX をカバーすることが目標のようです。

wasmer では独自に WASI を拡張した WASIX を策定しています。

これによって bash や curl などが WebAssembly で動作するようです。OS や CPU から独立した VM としての WebAssembly の可能性を示しています。

WASIX は独自規格のため今後どうなるかは未知数ですが、WASI が拡張されることで、発展的に解消されるかもしれません。

0
0
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
0
0