Announcing GraalWasm — a WebAssembly engine in GraalVM

原文はこちら。
The original was written by Aleksandar Prokopec, Principal researcher at Oracle Labs.
https://medium.com/graalvm/announcing-graalwasm-a-webassembly-engine-in-graalvm-25cd0400a7f2

GraalVMで実装したWebAssemblyエンジンであるGraalWasmを発表します。現在GraalWasmはWebAssembly MVP (Minimum Viable Product、実用最小限の商品) 仕様を実装しており、Emscriptenのようなコンパイラバックエンドで生成された、バイナリフォーマットでWebAssemblyプログラムを実行できます。

GraalWasm
https://github.com/oracle/graal/tree/master/wasm

WebAssemblyのサポートにより、GraalVMでサポートされている言語とは別の言語セット全体で実行できるように拡張します。これにより、プログラミング言語実行のユニバーサルプラットフォーム化のために一歩進みだしました。この機能はGraalVMコミュニティからも強く要望されており、初期版を共有できることをうれしく思っています。

Awesome WebAssembly Languages
https://github.com/appcypher/awesome-wasm-langs
WebAssembly on GraalVM
https://github.com/oracle/graal/issues/1358

なお、GraalWasmは現時点でexperimental mode (実験モード) であり非常に早期の実装である点にご注意ください。現在テストスィートやベンチマークの拡張に取り組んでおり、将来のパフィーマンス改善ならびにWebAssembly extensionsの実装を予定しています。フィードバックならびに今トリビュートをお待ちしています。

Graal
https://github.com/oracle/graal

このエントリではWebAssemblyの概要、GraalVMでのGraalWasmの実装、GraalWasmの利用方法について説明します。

GraalWasmの研究の大部分は、最近のインターンであるErgys Donaのインターンシップの成功の結果です。テクノロジーの世界に直接影響を与える刺激的なインターンシッププロジェクトを探しているなら、GraalVMインターンシッププログラムをチェックしてください。

Ergys Dona
https://github.com/errikos/
GraalVM Internship Program
https://www.graalvm.org/community/internship/

A brief introduction to WebAssembly

WebAssemblyは、実行可能プログラムのポータブルなバイナリ命令形式であり、その主な目的は、Webページで高性能アプリケーションを有効にすることですが、それだけではなく、IoTデバイスなどの他の環境への埋め込みも可能にします。WebAssemblyは、抽象スタックベースの仮想マシンで実行するように設計されており、JavaScriptよりも速く解析できます。そのバイナリは、非常にコンパクトなコード表現のため、対応するJavaScriptプログラムよりも小さくなっています。

WebAssemblyはテキスト形式とバイナリ形式の2つの形式を規定しています。テキスト形式は人間が読めるように設計されています。以下は、Cで記述され、テキストWebAssemblyに変換された再帰的階乗関数の例です。

long fact(long n) {
if (n == 0)
return 1L;
else
return n * fact(n - 1);
}
view raw fact.c hosted with ❤ by GitHub

対応するWebAssemblyは以下のようになります。

get_local 0
i64.eqz
if (result i64)
i64.const 1
else
get_local 0
get_local 0
i64.const 1
i64.sub
call 0
i64.mul
end
view raw fact.wasm hosted with ❤ by GitHub

get_local 0命令は、最初の引数nを式スタックにプッシュします。 i64.eqz命令は、この引数をスタックからポップし、ゼロかどうかを確認し、1または0をスタックに戻します。if命令は、WebAssemblyの非常に興味深い側面を示しています。そのビットコード形式は半構造化されています。if命令の後には命令のブロックが続き、その後にオプションでelseを続けることができ、最後にif構造の終了を示す終了が続きます。したがって、完全に非構造化されたジャンプは不可能ですが、これによりWebAssemblyコードの理解ははるかに簡単になります。

ifブランチで何が起こるかを考えてみましょう。i64.const 1命令は、定数(デフォルトの再帰的ケース)をスタックにプッシュします。elseブランチでは、引数nがスタックに2回プッシュされ、i64.const 1命令とi64.sub命令で最上位の値が1つ減り、0の呼び出しでインデックス0の関数を呼び出します(今回の場合、fact関数)。その戻り値は、最初にスタックにプッシュされたnで乗算されます。スタックに残る最終値は、関数の戻り値です。

バイナリ形式は、テキスト形式の1対1のマッピングであり、解析とコンパイルを目的としており、コンパクトでありながら、解析と検証が効率的になるように設計されています。GraalWasmエンジンは、WebAssemblyプログラムを配布するデフォルトの方法として意図されているバイナリWebAssemblyファイルを実行します。

GraalWasm — implementing a WebAssembly engine inside GraalVM

この章では、GraalWasmの技術的な詳細を紹介します。GraalWasmの実装のために、効率的な部分評価エンジンを提供するGraalVMを使いました。GraalVMのTruffle APIを利用し、まずWebAssemblyバイナリのインタプリタを実装しました。

Introduction to SimpleLanguage
https://www.graalvm.org/docs/graalvm-as-a-platform/implement-language/
Truffle API (Package com.oracle.truffle.api)
https://www.graalvm.org/truffle/javadoc/com/oracle/truffle/api/package-summary.html

WebAssemblyの半構造化フォーマットを使うと、簡単にプログラムの制御フロー構造を回復でき、コードを格納するインメモリ構造をASTとして表現できました。ASTで表現されるプログラムのインタプリタは非常に簡単に作成できますが、ASTベースのデータ構造は検査と操作は間違いなく簡単ではあるものの、追加のメモリオーバーヘッドが発生するという欠点があります。それに対して、ビットコードベースのコード表現では基本命令のツリーノードごとにインスタンス化する必要はありません。このため、LLVM向けのSulongのような、ビットコードベースのGraalVMインタプリタは通常メモリフットプリントが小さいのです。

Sulong – high-performance LLVM bitcode interpreter
https://github.com/oracle/graal/tree/master/sulong

各WebAssemblyブロックには命令が含まれているので、GraalWasmは両インタプリタのアプローチのいいとこ取りができました。ASTはifやloopといったWebAssemblyの制御フロー命令に重ね合わせることができますが、各ブロックをWasmブロックノードと呼ばれる、1個のTruffle ASTノードで表現します。この方法では、各ブロックの個々の命令が別のノードオブジェクトを必要としないため、メモリフットプリントが削減されます。さらに、GraalWasmブロックノードは元の命令ストリームの一部をコピーせず、その代わりにWebAssemblyバイナリのバイト配列にポインタのみが含まれます。

Correspondence between textual WebAssembly, binary WebAssembly and a GraalWasm AST

このデータ構造上に実装されたインタプリタはASTベース、ビットコードベースの両インタプリタのハイブリッドです。上位の制御フローレベルで適切な基本ブロック間でディスパッチします。各基本ブロック内では、基本ブロックのオペコードを反復する解釈ループの中で解釈を実行します。この設計によりインタプリタが理解しやすくなり、部分評価が簡素化されました。

実行時には、インタプリタとプログラムはTruffleの部分評価エンジンに渡されます。これにより、インタプリタがプログラムに特化され、特殊化されたコードがGraalVMコンパイラに渡されます。最終的にターゲットプラットフォーム用の効率的なアセンブリコードが生成されます。

Installing and running GraalWasm from GraalVM

GraalWasmは、GraalVMのguツールを使用してGraalVM 19.3.0にインストールできます。GraalWasmの現在の開発バージョンは20.0.0-devであるため、デフォルトではguツールでGraalVM 19.3.0にGraalWasmをインストールできません。これを克服するために、–forceフラグを使用して、GraalVMコンポーネントインストール時のバージョンチェックをオーバーライドできます。

最初は最新のGraalWasmをダウンロードです。ダウンロードしたGraalVMのJDKのバージョンとプラットフォーム(LinuxとmacOSが事前ビルド済み)に対応するJARを選択します。例えば、JDK 8のGraalVMをLinuxでお使いの場合、wasm-installable-java8-linux-.jarをダウンロードしてください。なお、“nightly-timestamp”はnightly buildの一意の値です。

GraalWasm Preview Release
https://github.com/graalvm/graalvm-ce-dev-builds/releases

続いて、以下のコマンドを実行してGraalWasmをインストールします。

$ graalvm-ce-java8-19.3.0/bin/gu install --force -L wasm-installable-java8-linux-<nightly-timestamp>.jar

gu ツールで新たなGraalVMコンポーネントをインストールすると、wasmランチャーを起動できます。

$ graalvm-ce-java8-19.3.0/bin/wasm

ランチャーはWebAssemblyモジュールが必要と言ってきます。

ERROR: Must specify the binary name.

以下のリポジトリにEmscriptenを使うCで事前コンパイルされたWebAssemblyモジュールがあります。

Example wasm binaries
https://github.com/axel22/wasm-examples

例えば、Floydの三角形を表示するWebAssemblyプログラムをダウンロードし、以下のように実行します。

https://github.com/axel22/wasm-examples/raw/master/floyd.wasm

$ graalvm/bin/wasm --PredefinedModules=env:emscripten floyd.wasm

ここで、–PredefinedModules=env:emscripten フラグをつけている点にご注意ください。このフラグは、envという名前で事前定義されたemscriptenモジュールにこのWebAssemblyバイナリをリンクするようGraalWasmに指示します。このモジュールには、EmscriptenツールチェーンがWebAssemblyモジュールとともに生成されるJavaScriptファイルに通常埋め込まれる、特定のシステム関数が含まれています。将来、WebAssembly System Interface (WSI) を事前定義済みモジュールとしてサポートする予定です。WSIはWebAssemblyプログラムをブラウザ外で実行するための標準的な方法になり、WebAssemblyツールチェーンのほとんどがサポートすることになるでしょう。

Building and testing GraalWasm

GraalWasm実装をGitHubのGraalVMリポジトリで見つけてください。GraalWasmをビルドして遊んでみたいとか、コントリビュートしたい、ということでしたら、以下の手順を踏んでください。

  1. ビルドツール mx をGitHubからダウンロードします、これを使ってすべてのGraalVMプロジェクトをビルドします。
    Command-line tool used for the development of Graal projects
    https://github.com/graalvm/mx
  2. GraalVMをGitHubからクローンします。
  3. JVMCIが利用可能な最新のJDKがあることを確認します。
    openjdk8-jvmci-builder
    https://github.com/graalvm/openjdk8-jvmci-builder/releases
  4. 環境変数 JAVA_HOME をJVMCIが利用可能なJDKに向くよう設定します。
  5. GraalVMのwasmディレクトリで以下のコマンドを実行します。
$ mx --dy /truffle,/compiler build

このコマンドはmxビルドツールを起動し、mxbuild/dists/jdkにあるwasm.jarファイルをビルドします。GraalWasmスィートからWebAssemblyテストを実行するためには、現時点ではWebAssemblyバイナリツールキットのダウンロードも必要です。テストスィートはこのツールキットを使いテキスト形式のWebAssemblyファイルをバイナリに変換します。

  1. WebAssemblyバイナリツールキットをダウンロードします。

    The WebAssembly Binary Toolkit
    https://github.com/WebAssembly/wabt
  2. WebAssemblyバイナリツールキットのルートディレクトリのPATHを環境変数 WABT_DIRに設定します。

WebAssemblyテストをいくつかの異なるテストスィートに編成します。以下のコマンドを実行すると全スィートからすべてのテストを実行します。

mx --dy /truffle,/compiler --jdk jvmci unittest \
  -Dwasmtest.watToWasmExecutable=$WABT_DIR \
  -Dwasmtest.testFilter="^.*\$" \
  WasmTestSuite

正規表現フラグ-Dwasmtest.testFilterを使って特定のテストを名前で選択できます。以下のコマンドは名前に if が含まれているテストをすべて実行します。

mx --dy /truffle,/compiler --jdk jvmci unittest \
  -Dwasmtest.watToWasmExecutable=$WABT_DIR \
  -Dwasmtest.testFilter="^.*if.*\$" \
  WasmTestSuite

以下の出力があるはずです。

-------------------------------------------------------------------
Running: BranchBlockSuite (4/16 tests - you have enabled filters)
-------------------------------------------------------------------
Using runtime: org.graalvm.compiler.truffle.runtime.hotspot.java.HotSpotTruffleRuntime@7b1d7fff
😍😍😍😍                                
Finished running: BranchBlockSuite
🍀 4/4 Wasm tests passed.

-------------------------------------------------------------------
Running: IfThenElseSuite (4 tests)
-------------------------------------------------------------------
Using runtime: org.graalvm.compiler.truffle.runtime.hotspot.java.HotSpotTruffleRuntime@7b1d7fff
😍😍😍😍                                
Finished running: IfThenElseSuite
🍀 4/4 Wasm tests passed

生のWebAssemblyテストに加え、GraalWasmはCベースのテストスィートが付属していますが、これはデフォルトビルドには含まれていません。それは外部の依存関係を追加で必要とするからです。Cベースのテストスィートを実行するには、Emscripten SDK(現在Emscripten 1.38.45)をご自身のシステムにインストールしてください。インストール手順はドキュメントをご覧ください。

Emscripten SDK
https://github.com/emscripten-core/emsdk
Download and install
https://emscripten.org/docs/getting_started/downloads.html

Emscripten SDKのインストールが完了したら、SDKのfastcomp/emscripten/ディレクトリを指すよう、EMCC_DIRという環境変数を設定します。

この設定がすめば、以下のコマンドを実行するとデフォルトではないターゲットをビルドできるようになります。

mx --dy /truffle,/compiler build --all

以下のように追加のCのテストケースを実行できます。

mx --dy /truffle,/compiler --jdk jvmci unittest \
  -Dwasmtest.watToWasmExecutable=$WABT_DIR \
  -Dwasmtest.testFilter="^.*\$" \
  CSuite

Embedding GraalWasm into a Java program

GraalWasmは、GraalVMの他のすべての言語実装と同様に、GraalVMのPolyglot APIでアクセスできます。これにより、GraalWasmエンジンをカスタムJavaプログラムに埋め込むことができます。以下は、GraalWasmを使用してJavaアプリケーションからWebAssemblyプログラムを実行する方法の最小限の例です。

Reference for Polyglot Applications
https://www.graalvm.org/docs/reference-manual/polyglot/

main.cで42を返すだけの単純なC言語のプログラムがあり、このプログラムをmain.wasmというWebAssemblyバイナリに変換したと仮定します。CやRustなどの言語をWebAssemblyに変換する既存のコンパイラを使用して、このようなバイナリを生成できますし、オンラインのWebAssembly Studioを使用してCプログラムを翻訳することもできます。

WebAssembly Studio
https://webassembly.studio/

#define WASM_EXPORT __attribute__((visibility("default")))
WASM_EXPORT
int main() {
return 42;
}
view raw main-42.c hosted with ❤ by GitHub

WebAssembly studioはこのC言語のプログラムをWebAssemblyバイナリに変換します。テキスト形式では以下のようになります。

(module
(type $t0 (func))
(type $t1 (func (result i32)))
(func $__wasm_call_ctors (type $t0))
(func $main (export "main") (type $t1) (result i32)
i32.const 42)
(table $T0 1 1 anyfunc)
(memory $memory (export "memory") 0)
(global $g0 (mut i32) (i32.const 66560))
(global $__heap_base (export "__heap_base") i32 (i32.const 66560))
(global $__data_end (export "__data_end") i32 (i32.const 1024)))
view raw main.wat hosted with ❤ by GitHub

WebAssemblyバイナリファイルの main.wasm (WebAssembly studioからダウンロードもしくはEmscriptenなどを使って生成できます)はGraalWasmで実行できます。

以下の例では、GraalWasmテストスイートにWasmExampleTestと呼ばれる新しい単体テストを作成します。最初に、wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/WasmExampleTest.java という新しいファイルを作成します。 このファイルでは、WebAssemblyバイナリをバイト配列に読み取り、WebAssembly言語のPolyglot Contextオブジェクトを作成します。識別子wasmは、GraalVMのWebAssemblyプログラムを参照するために使用されることに注意してください。続いて、main.wasmバイナリ用のSource.Builderを作成し、buildを呼び出して解析可能なSourceオブジェクトを作成します。

import org.junit.Test;
import static org.junit.Assert.*;
import org.graalvm.polyglot.*;
import org.graalvm.polyglot.io.ByteSequence;
import java.io.IOException;
public class WasmPolyglotTest {
@Test
public void test() throws IOException {
Context.Builder contextBuilder = Context.newBuilder("wasm");
Source.Builder sourceBuilder = Source.newBuilder("wasm",
ByteSequence.create(binary),
"main");
Source source = sourceBuilder.build();
Context context = contextBuilder.build();
context.eval(source);
Value mainFunction = context.getBindings("wasm").getMember("main");
Value result = mainFunction.execute();
assertEquals(42, result.asInt());
}
}
view raw wasm-context.java hosted with ❤ by GitHub

WebAssemblyプログラムに対応するSourceオブジェクトを解析、検証するために、Contextのevalメソッドを呼び出します。

context.eval(source);

WebAssemblyバイナリが仕様に準拠する場合、evalは例外を送出せずに終了します。また、evalはそれぞれのWebAssemblyバイナリの関数を解析中に利用されたPolyglotコンテキストに挿入します。これらの関数を それぞれの名前を使いWebAssemblyバイナリから検索できます。

続いてmain関数へのハンドルを取得し、実行します。

Value mainFunction = context.getBindings("wasm").getMember("main");
Object result = mainFunction.execute();

完了時にresultオブジェクトはJavaのIntegerに、値が42になります。以下のコマンドでテストできます。

mx --dy /compiler,/truffle --jdk jvmci unittest WasmExampleTest

Future plans

GraalWasm実装のソースコードは現在、メインのGraalVMリポジトリ内のGitHubにあり、20.xリリースで改善する予定です。

GraalWasm
https://github.com/oracle/graal/tree/master/wasm

GraalWasmの背後にある動機の1つは、GraalVMのnode.js実装でサポートされるAPIセットを拡張することです。 WebAssemblyサポートの追加により、WebAssemblyバイナリをロードするV8準拠のAPI機能を実装できるようになります。

Graal Node.js Implementation
https://github.com/graalvm/graaljs/tree/master/graal-nodejs

直近のタスクは、WebAssembly System Interface (WASI) の実装です。これは、Webコンテキストの外部でWebAssemblyプログラムを実行するために必要です。WASIは、ファイルAPI、ネットワークソケット、クロックなど、さまざまなOS機能へのアクセスを抽象化するAPIのセットです。GraalWasmの一部としてWASIをサポートする予定です。

WebAssembly System Interface (WASI)
https://wasi.dev/

もちろん、パフォーマンスの改善にも注力します。いくつかのCマイクロベンチマークでの最初の実験とパフォーマンスチューニングにより、GraalWasmは、最高の最適化レベルでコンパイルされたネイティブGCCバイナリと比較して、現時点では約0.5〜0.75倍のピークパフォーマンスにすぎません。最初の結果としては良好ですが、やるべきことはまだたくさんあります。GraalWasmをGCCのピークパフォーマンスに近づける以外にも、次のステップではより大規模にして本格的なベンチマークでパフォーマンスチューニングを行うことを検討しています。

その他、GraalWasmのデバッグサポートを改善し、GraalVMの他の部分と統合することを検討しています。特に、一部のコンパイラがWebAssemblyバイナリに埋め込むシンボルおよびソースマップ情報の抽出に取り組む予定です。GraalVMツールを使用して、コードの場所と素のメモリレイアウトを元のソースコードのコンストラクトにマップできるようにすることを目標としています。

GraalVMでWebAssemblyに関する最新情報を投稿し続けますので、お楽しみに。フィードバックまたは機能のリクエストがある場合は、GithubリポジトリでIssueを作成するか、Twitterで@graalvmまでご連絡ください。

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト /  変更 )

Google フォト

Google アカウントを使ってコメントしています。 ログアウト /  変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト /  変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト /  変更 )

%s と連携中