ottijp blog

swiftwasmで作ったwasmをwasmerで動かす

2021-02-07swiftswiftwasmwasmWebAssemblywasmer

先月1.0がリリースされたwasmerを使って,swiftwasmで作ったwasmを実行してみました.

swiftで作ったexecutableとの実行速度比較やwasmをpythonに組み込むことも試してみましたが,これらはちょっとうまくいきませんでした.

環境

  • macOS: 10.15 (Catalina)
  • swiftwasm: 5.3
  • wasmer: 1.0.1

swiftwasmのインストール

こちらに従ってインストールします.

Setup - Swift and WebAssembly

swiftwasmのツールチェインがインストールされるので,そのツールチェインを指定してswiftcできるようになります.

$ xcrun --toolchain swiftwasm swiftc --version
SwiftWasm Swift version 5.3 (swiftlang-5.3.1)
Target: x86_64-apple-darwin19.6.0

ただ,私の環境ではxcrunを使うとswiftc実行事にエラーが発生したので,PATHを通して実行するようにしました.

$ xcrun --toolchain swiftwasm swiftc -target wasm32-unknown-wasi -o main.wasm Main.swift
wasm-ld: error: cannot open crt1.o: No such file or directory
wasm-ld: error: unable to find library -ldl
wasm-ld: error: unable to find library -lc++
wasm-ld: error: unable to find library -lc++abi
wasm-ld: error: unable to find library -lm
wasm-ld: error: unable to find library -lwasi-emulated-mman
wasm-ld: error: unable to find library -lc
clang-10: error: linker command failed with exit code 1 (use -v to see invocation)
<unknown>:0: error: link command failed with exit code 1 (use -v to see invocation)

$ export PATH=/Library/Developer/Toolchains/swift-wasm-5.3.1-RELEASE.xctoolchain/usr/bin/:$PATH

$ swiftc -target wasm32-unknown-wasi -o main.wasm Main.swift

wasmerのインストール

こちらに従ってインストールします.

Getting Started - Wasmer Docs

$ curl https://get.wasmer.io -sSfL | sh
Welcome to the Wasmer bash installer!

               ww
               wwwww
        ww     wwwwww  w
        wwwww      wwwwwwwww
ww      wwwwww  w     wwwwwww
wwwww      wwwwwwwwww   wwwww
wwwwww  w      wwwwwww  wwwww
wwwwwwwwwwwwww   wwwww  wwwww
wwwwwwwwwwwwwww  wwwww  wwwww
wwwwwwwwwwwwwww  wwwww  wwwww
wwwwwwwwwwwwwww  wwwww  wwwww
wwwwwwwwwwwwwww  wwwww   wwww
wwwwwwwwwwwwwww  wwwww
   wwwwwwwwwwww   wwww
       wwwwwwww
           wwww

downloading: wasmer-darwin-amd64
Latest release: 1.0.1
Downloading archive from https://github.com/wasmerio/wasmer/releases/download/1.0.1/wasmer-darwin-amd64.tar.gz
######################################################################## 100.0%##O#- #
installing: /Users/otti/.wasmer
Updating bash profile /Users/otti/.zshrc
we've added the following to your /Users/otti/.zshrc
If you have a different profile please add the following:

# Wasmer
export WASMER_DIR="/Users/otti/.wasmer"
[ -s "$WASMER_DIR/wasmer.sh" ] && source "$WASMER_DIR/wasmer.sh"
check: wasmer 1.0.1 installed successfully ✓
wasmer & wapm will be available the next time you open the terminal.
If you want to have the commands available now please execute:

source /Users/otti/.wasmer/wasmer.sh

$ wasmer --version
wasmer 1.0.1

wasm化するプログラム

フィボナッチ数を計算するサンプルをよく見かけたのですが,同じだとつまらないので,前4項の和で決定されるテトラナッチ数を計算するプログラムを書きました.

Main.swift
@_cdecl("tetranacci")
func tetranacci(_ n: Int) -> Int {
    switch n {
    case 1, 2, 3: return 0
    case 4: return 1
    default: return [1, 2, 3, 4].reduce(0) {$0 + tetranacci(n - $1)}
    }
}

print(tetranacci(Int(CommandLine.arguments[1])!))

他のプログラミング言語に組み込むために1行目でtetranacci関数をexportしてますが,後述するようにうまくimportはできませんでした・・・.

wasm化

$ export PATH=/Library/Developer/Toolchains/swift-wasm-5.3.1-RELEASE.xctoolchain/usr/bin/:$PATH

$ swiftc -target wasm32-unknown-wasi -o main.wasm Main.swift

$ ls -lh main.wasm
-rwxr-xr-x@ 1 otti  staff   9.8M Feb  6 22:24 main.wasm*

$ file main.wasm
main.wasm: WebAssembly (wasm) binary module version 0x1 (MVP)

wasmerでの実行

wasmerで実行するには,できたwasmを渡してあげるだけです.(10番目のテトラナッチ数を計算しました.)

$ wasmer main.wasm 10
29

速度比較

wasmはネイティブ並みに早いということだったので,28番目のテトラナッチ数列を計算する実行で速度比較してみました. 28番目にしたのは,それくらいがちょうどよい実行時間だったからです.

対象 実行速度(sec)
swift executable(最適化なし) 6.9
swift executable(最適化有り) 0.4
wasmer JIT (LLVM) 35.0
wasmer JIT (cranelift) 7.5
wasmer JIT (singlepass) 8.8
wasmer 事前コンパイル (LLVM) 3.7
wasmer 事前コンパイル (cranelift) 7.0
wasmer 事前コンパイル (singlepass) 8.9

wasmerを使った場合,LLVMの事前コンパイルが一番速かったです. ただし,事前コンパイルで確かに速くなるけど,最適化オプション付きのexecutableには全く歯が立たない,という感じでした. (もしかすると,なにかやり方が間違ってるのかもしれません.)

また,事前コンパイルはJITエンジンを使った場合はうまくいくのですが,nativeエンジンを使った場合はcraneliftでしかうまくいきませんでした. (上記表はJITエンジンでの事前コンパイル.) コンパイラそれぞれで,以下のような状況でした.

  • LLVMはコンパイルコマンドが終わらない
  • craneliftは同じくらいの実行速度
  • singlepassはコンパイルできるが,実行時エラー

create-exeでexecutableを作ることも試してみましたが,これもnativeエンジンを使った事前コンパイルと同様でした.

それぞれ次のようなコマンドを実行しました.

# swift executable(最適化なし)
$ xcrun --toolchain swift swiftc Main.swift
$ time ./Main 28

# swift executable(最適化有り)
$ xcrun --toolchain swift swiftc -O Main.swift
$ time ./Main 28

# wasmer JIT (LLVM)
$ time wasmer run --llvm main.wasm 28

# wasmer JIT (cranelift)
$ time wasmer run --cranelift main.wasm 28

# wasmer JIT (singlepass)
$ time wasmer run --singlepass main.wasm 28

# wasmer 事前コンパイル (JIT+LLVM)
$ wasmer compile main.wasm -o main.wjit --jit --llvm
$ time wasmer main.wjit 28

# wasmer 事前コンパイル (JIT+cranelift)
$ wasmer compile main.wasm -o main-cranelift.wjit --jit --cranelift
$ time wasmer main-cranelift.wjit 28

# wasmer 事前コンパイル (JIT+singlepass)
$ wasmer compile main.wasm -o main-singlepass.wjit --jit --singlepass
$ time wasmer main-singlepass.wjit 28

# wasmer 事前コンパイル (native+LLVM)
$ wasmer compile main.wasm -o main.dylib --native --llvm
# (どれだけ待ってもコマンドが終わらない)

# wasmer 事前コンパイル (native+cranelift)
$ wasmer compile main.wasm -o main-cranelift.dylib --native --cranelift
$ time wasmer main-cranelift.dylib 28
# (表には載せていないがJIT+craneliftと同程度の実行時間)

# wasmer 事前コンパイル (native+singlepass)
$ wasmer compile main.wasm -o main-singlepass.dylib --native --singlepass
$ time wasmer main-singlepass.dylib 28
error: failed to run `main-singlepass.dylib`
│   1: WASI execution failed
│   2: failed to run WASI `_start` function
│   3: RuntimeError: out of bounds memory access
╰─> 4: heap_get_oob

wasmの他言語への組み込み

作ったwasmをpythonで読み込んで使おうと試してみましたが,wasi_snapshot_preview1のインポートが解決できないエラーが発生し,正常に実行できませんでした.

main.py
from wasmer import engine, Store, Module, Instance
from wasmer_compiler_cranelift import Compiler

store = Store(engine.JIT(Compiler))
module = Module(store, open('main.wasm', 'rb').read())
instance = Instance(module)
result = instance.exports.tetranacci(28)

print(result)
$ pip install wasmer

$ pip install wasmer_compiler_cranelift

$ python main.py
Traceback (most recent call last):
  File "main.py", line 6, in <module>
    instance = Instance(module)
RuntimeError: Error while importing "wasi_snapshot_preview1"."proc_exit": unknown import. Expected Function(FunctionType { params: [I32], results: [] })

swiftwasmが出力するwasmがおかしいのか,pipのwasmerがおかしいのか,原因はよくわかりませんでした・・・.

2021-02-12追記

作者の方からリプライしてもらい,wasi.Environmentを使ってインポートオブジェクトを構築することで,エントリポイントから実行することはできるようになりました.

main.py
from wasmer import engine, wasi, Store, Module, Instance
from wasmer_compiler_cranelift import Compiler

store = Store(engine.JIT(Compiler))
module = Module(store, open('main.wasm', 'rb').read())

wasi_version = wasi.get_version(module, strict=True)
wasi_env = \
        wasi.StateBuilder('wasi_test_program'). \
        argument('10'). \
        finalize()

import_object = wasi_env.generate_import_object(store, wasi_version)
instance = Instance(module, import_object)
instance.exports._start()
$ python main.py
29

ただ,instance.exports.tetranacciを参照しようとするとLookupError: Exporttetranaccidoes not exist.というエラーが出るので,エクスポートした関数が使えることまでは確認できませんでした.

refs


ottijp
Satoshi SAKAO (@ottijp)

都内でアプリケーションエンジニアをしています

...