JVMでWebAssemblyにコンパイルしたRustのコードを動かす

Chicoryを使うとJVM上でWebAssemblyを動かせるということで、RustからWebAssemblyにコンパイルしたコードを動かしてみます。

このときはRustをLLVMビットコードにしてGraalVMで動かしていましたね。
GraalVMでRust動かしたりレイトレをネイティブコンパイルしたり - きしだのHatena

Rustからwasmを作成

まずはRustのプロジェクトを作成。

>cargo new --lib hello-wasm
    Creating library `hello-wasm` package

lib.rsというファイルが作成されてadd関数が定義されているので、no_mangleをつけてu64をi32にします。

#[no_mangle]
pub fn add(left: i32, right: i32) -> i32 {
    left + right
}

そしたらビルド

hello-wasm>cargo build --target=wasm32-unknown-unknown --release
   Compiling hello-wasm v0.1.0 (C:\Users\naoki\Desktop\hello-wasm)
    Finished `release` profile [optimized] target(s) in 0.44s

ブラウザで試す

とりあえずブラウザで動かしてみます。こんなJS入りHTMLを書く。

<html>
<head>
 <title>Hello wasm</title>
 <script>
   const wasm = "./target/wasm32-unknown-unknown/release/hello_wasm.wasm"
   fetch(wasm)
    .then(res => res.arrayBuffer())
    .then(b => WebAssembly.instantiate(b, {}))
    .then(res => {
      alert(res.instance.exports.add(2, 3))
    })
  </script>
</head>
</html>

さて、fetchでwasmを読み込んでいるけど、これはファイルシステム経由での読み込みができないので、Webサーバーが必要です。

ここでJava 18で導入されたjwebserver。 https://openjdk.org/jeps/408

ただ、Windowsで起動すると文字化けるです。

hello-wasm>jwebserver
繝・ヵ繧ゥ繝ォ繝医〒繝ォ繝シ繝励ヰ繝・け縺ォ繝舌う繝ウ繝峨@縺セ縺吶ゅ☆縺ケ縺ヲ縺ョ繧、繝ウ繧ソ繝輔ぉ繝シ繧ケ縺ァ"-b 0.0.0.0"縺セ縺溘・"-b ::"繧剃スソ逕ィ縺励∪縺吶・
C:\Users\naoki\Desktop\hello-wasm縺翫h縺ウ繧オ繝悶ョ繧」繝ャ繧ッ繝医Μ繧・27.0.0.1繝昴・繝・000縺ァ菴ソ逕ィ縺励∪縺・URL http://127.0.0.1:8000/

別にコンソールなので文字化けさせておけばいいのだけど、気になるのでchcpコマンドでUTF-8にしておきましょう。

hello-wasm>chcp 65001

気を取り直してjwebserverの起動。

hello-wasm>jwebserver
デフォルトでループバックにバインドします。すべてのインタフェースで"-b 0.0.0.0"または"-b ::"を使用します。
C:\Users\naoki\Desktop\hello-wasmおよびサブディレクトリを127.0.0.1ポート8000で使用します
URL http://127.0.0.1:8000/

http://localhost:8000/hello.htmlにアクセスすると、なんかWebAssemblyが動いてるっぽい。

Chicoryの導入

では、JVMでWebAssemblyを動かすChicoryを導入します。
https://github.com/dylibso/chicory

readmeに従ってdependencyを追加します。

<dependency>
  <groupId>com.dylibso.chicory</groupId>
  <artifactId>runtime</artifactId>
  <version>0.0.12</version>
</dependency>

Javaコードで動かす

では、試しに動かしてみましょう。targetの下の方にhello_wasm.wasmができているので、src/main/resourcesにコピーしておきます。

そして次のコードを。ちなみに、現状のChicoryのreadmeに書いてあるParser.parseを使ったサンプルは、まだ配布バイナリに反映されていません。古い書き方で書く必要があります。

import com.dylibso.chicory.wasm.types.Value;
import com.dylibso.chicory.runtime.Module;
import java.io.IOException;

public class CallWasm {
    public static void main(String[] args) throws IOException {
        var wasm = CallWasm.class.getClassLoader().getResourceAsStream("hello_wasm.wasm");
        var module = Module.builder(wasm).build();
        var instance = module.instantiate();
        var add = instance.export("add");
        
        var result = add.apply(Value.i32(3), Value.i32(2));
        System.out.println(result[0].asInt());
    }
}

実行すると5が表示されて、なにか動いてそう。

まとめ

この記事ではSQLiteをwasmにコンパイルして動かすということもやってます。Cで書かれたコードをJavaで動かしやすくなりそうで、なかなかよさそうですね。
https://www.infoq.com/articles/sqlite-java-integration-webassembly/

ただ、パフォーマンスが必要ならおとなしくネイティブバイナリ使いましょうということになると思うので、テスト用とかですかね。