6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

ベアメタルWASIなバイナリを作って動かしてみる

Posted at

前置き

現在だとWASIでプログラムを書くときはwasi-libcここからダウンロードしてきて静的リンクして使うことが一般的と思われます。

ただまあ基本ホスト環境にないものなので(そのうち各distroでパッケージ用意してくれるようになるかもしれませんが)、毎回毎回ダウンロードしてくるのもだるいなあという気がします。
もちろんlibcの上にあるからこそ移植が簡単になっているとかその上でプログラムが書きやすいとかあったりするので結論からいうとlibcを使わないことによるメリットはほとんどない(サイズを小さくできるかもしれないくらい?)ですが、ちょろっとwasmtime/wasmer試すのにもう少し手軽にならないかな?ということでベアメタル路線(これをベアメタルという言い方をしていいのか疑問がありますが)を試してみます。

libcを経由せずにホスト環境のAPIを触る方法を考えてみましょう。
もちろんlibcはそういうふうに中でやっているわけなので、コードをみてみると__wasilibc_real.cというファイルがあります。
これをみるとwasm-import-modulewasm-import-nameを与えてやればホスト環境にアクセスできそうです。wasmtimeやwasmerはこれらのAPIを露出させてやることでwasmバイナリからでも標準入出力を扱えるようになっているのでしょう。
wasmtimeの実装を見ると、たとえばfd_writeはゲストから受け取ったメモリに対してランタイム側で書き込み権限があるかどうかをチェックして実際にホスト環境への書き込みが行われる、というような感じになっているようです。

wasm-import-moduleについて少しみてみましょう。wasi_snaphot_preview1という値が指定されていますが、これは現在WASIのAPIはまだpreview段階であることを意味します。ただしsnapshotの定義をみるにここで破壊的変更が行われる心配はしなくても大丈夫そうです(正式な仕様策定が実装されたらpreview版がごっそり消されてしまう、などはありそうですが)。

というわけで、wasm-import-moduleおよびwasm-import-nameを言語機能として呼び出せるような言語であればベアメタルなWASI環境で動くwasmバイナリが作れそうなことがわかりました。ここではD言語(LDC)を使ってみます。

環境

$ ldc2 --version| head -2
LDC - the LLVM D compiler (1.26.0):
  based on DMD v2.096.1 and LLVM 11.0.1
$ wasmtime --version
wasmtime 0.28.0

以下実装

LDCでは@llvmAttrを使ってAttributesを指定できます。
というわけで実装してみたのですが、やはりというべきかlibcを使わない場合と比較してだいぶ冗長になってしまいました。

/**
 based on https://github.com/WebAssembly/wasi-libc/blob/2b7e73ae7ac0bad6391d89cc3274a28412243389/libc-bottom-half/sources/__wasilibc_real.c
 */
import ldc.attributes;

extern (C):
nothrow:
@nogc:
@system:

alias __wasi_fd_t = int;
alias __wasi_size_t = size_t;
alias __wasi_errno_t = ushort;
alias __wasi_exitcode_t = uint;

struct __wasi_ciovec_t
{
    const(ubyte)* buf;
    __wasi_size_t buf_len;
}
static assert(__wasi_ciovec_t.sizeof == 8);
static assert(__wasi_ciovec_t.alignof == 4);
static assert(__wasi_ciovec_t.buf.offsetof == 0);
static assert(__wasi_ciovec_t.buf_len.offsetof == 4);

@llvmAttr("wasm-import-module", "wasi_snapshot_preview1")
@llvmAttr("wasm-import-name", "fd_write")
extern int __imported_wasi_snapshot_preview1_fd_write(int arg0, int arg1, int arg2, int arg3) @trusted;

__wasi_errno_t __wasi_fd_write(__wasi_fd_t fd, const(__wasi_ciovec_t)* iovs, size_t iovs_len, __wasi_size_t* retptr0) @trusted
{
    int ret = __imported_wasi_snapshot_preview1_fd_write(cast(int) fd, cast(int) iovs, cast(int) iovs_len, cast(int) retptr0);
    return cast(ushort) ret;
}

void _start() @trusted
{
    const(ubyte)* s = cast(const(ubyte)*) "Hello, World!\n".ptr;
    int fd = 1;
    __wasi_ciovec_t iovs = { s, 14 };
    __wasi_size_t retp;
    __wasi_fd_write(fd, &iovs, 1, &retp);
}

ビルド・実行

WASI向けにビルドする必要があるので当然クロスコンパイルが必要になります。
D言語(LDC)であれば以下のように簡単にクロスコンパイルすることができます。

$ ldc2 -mtriple=wasm32-unknown-wasi \
 -betterC \
 -fvisibility=hidden \
 -defaultlib= \
 -of=app.wasm \
 app.d

実行はwasmtimeを使っています。

$ wasmtime app.wasm
Hello, World!

結論

というわけでわりと手軽にベアメタルなWASI対応バイナリは作れるんだな、というのがわかりました。
メリットはほとんどないですが、たとえば複数のwasm-import-moduleでAPIを使い分けたいような場合には便利になるかもしれません。

6
1
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
6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?