LoginSignup
1
1

GraalVM を使って WebAssembly を実行してみる

Last updated at Posted at 2024-02-23

Zenn にも書籍として公開しています.
読みやすい方で読み進めていただけたらと思います.
Zenn の方が最新版です.

この記事の目的

GraalVM ずっと触ってみたかったんですが,WebAsssembly 周りの実装が増えてきていることで万を持して動かしてみることにしました!

こちらの記事では,公式ドキュメントに従って GraalVM を動かしてみた結果と実装例,コメント等を残します.

こちらの記事を参考にして GraalVM を活用した Java プロジェクト開発 & WebAssembly 埋め込みの基礎を学ぶことができます.
Java が初めての方も含め,なるべく誰でも手を動かして動作を再現できるように記載しています.

GraalVM の基本機能紹介から行うので,WebAssembly 連携だけ気になる方は WebAssembly (Wasm) 連携する まで読み飛ばしてください.

GraalVM あるいは Java そのもののプロセスに興味ある方はゴメンナサイ...本記事ではほとんど扱いません.
(自分もまだチョットワカル程度でしかありません...ぜひコメントなどにてご指導ご鞭撻をお願いします)

GraalVM とは

GraalVM-logo-rabbit.png

GraalVM is a high-performance JDK distribution that compiles your Java applications ahead of time into standalone binaries. These binaries start instantly, provide peak performance with no warmup, and use fewer resources. You can use GraalVM just like any other Java Development Kit in your IDE.1

GraalVM は Java アプリケーションをコンパイル・実行する JDK 及び実行可能なバイナリファイルであるネイティブイメージの作成機能を提供します.

GraalVM には Oracle GraalVM と GraalVM Community Edition があります.

Oracle GraalVM

GraalVM Community Edition

GraalVM 関連の GitHub は oracle (Core,各言語に関する統合機能等) と graalvm (ツール,フレームワーク等) に分散しているので注意してください.

GraalVM では Maven2/Gradle3 プラグインも提供しており,従来の Java プロジェクト開発手順を崩すことなくアーティファクトを追加することができます.

また,ネイティブイメージを活用した Docker コンテナでのアプリケーション実行に関するドキュメントも準備されています.4

ネイティブイメージ

Native Image is a technology to compile Java code ahead-of-time to a binary – a native executable. A native executable includes only the code required at run time, that is the application classes, standard-library classes, the language runtime, and statically-linked native code from the JDK.5

GraalVM が提供するネイティブイメージの作成機能によって Java アプリケーションのポータビリティと実行パフォーマンスを向上させることが期待できます.
特にネイティブイメージ化したアプリケーションの実行には Java 実行環境が不要のため,再配布が非常に楽になりインフラストラクチャの保守に関するコスト削減も見込めます.

ネイティブイメージは GraalVM インストール後に追加された native-image コマンドを使って簡単に作成することができます.

Graal コンパイラ

The GraalVM compiler is a dynamic compiler written in Java that integrates with the HotSpot JVM. It has a focus on high performance and extensibility. In addition, it provides optimized performance for languages implemented with Truffle Framework-based languages running on the JVM.6

GraalVM はJIT コンパイラである Graal コンパイラを利用しており,Java HotSpot VM と統合されています.
Graal コンパイラは JVM Compiler Interface (JVMCI) を利用しており,特に C2 コンパイラの代替として機能しています.
JVM と JIT コンパイラについては,以下の記事で詳しく説明されています.

Graal コンパイラの中身をもっと詳しく知りたい方は以下をご参照ください.

Truffle フレームワーク

The Truffle language implementation framework (Truffle) is an open source library for building tools and programming languages implementations as interpreters for self-modifying Abstract Syntax Trees.7

Truffle フレームワークは GraalVM で利用されている言語実装用の AST インタプリタフレームワークです.
高速化の目的以外にも,独自のプロトコルを使った Java 以外の言語との相互連携を担っています.

現在は,

  • Javascript
  • Python
  • R
  • Ruby
  • LLVM
  • WebAssembly

との連携がサポートされています.

Java ソースコードの中から他言語で書かれた各ファイルの Embedding には Polyglot API を使用します.

本記事の目玉である Webassembly を Java から実行する も Polyglot API を利用して実装しています.

Truffle についてもっと詳しく学びたい方は以下をご覧ください.

以下の記事は AST に関して非常にわかりやすく説明してくれています.(感謝!)

GraalWasm

GraalWasm is a WebAssembly engine implemented in GraalVM. It can interpret and compile WebAssembly programs in the binary format, or be embedded into other programs.8

GraalWasm は GraalVM における WebAssembly インタプリタ・実行環境を提供します.

Java ソースコードに WebAssembly を埋め込む場合は GraalWasm を Polyglot API から呼び出して利用します.

現在開発が進んでいる段階であり,今回注目したい機能です.

WebAssembly (Wasm) とは

image.png

WebAssembly (abbreviated Wasm) is a binary instruction format for a stack-based virtual machine. Wasm is designed as a portable compilation target for programming languages, enabling deployment on the web for client and server applications.9

WebAssembly (Wasm) はバイナリ形式のコードであり,様々な言語から変換できる Wasm ファイルと Javascript を併用することで,Web での処理をネイティブに近いパフォーマンスで実行することを意図して開発されています.

そのため,高速でかつポータビリティに優れながら,人間が内容を理解できるような可読性を担保することが重視されています.

可読性は WABT が提供する wasm2wat, wat2wasm などを使ってテキストフォーマットである .wat ファイルで確認するなどの手順で解決されています.

現在では Web での活用だけでなく,プラグインの埋め込みなどにも利用が広がっており,今後様々なサービスでの活用が広がることが期待されています.

(自身が直近で見たものだと Apache APISIX プラグイン 等が好例)

なお現在では C, C++, Rust などが Wasm 変換元として有力となっています.

動作確認環境

動作確認を行った環境情報を以下に示します.

名称 バージョン
Windows 11 22H2
Ubuntu (WSL2) 20.04.6 LTS
JDK graalvm-jdk-21.0.2+13.1
openjdk-21_linux-x64_bin.tar.gz (検証用)
GraalWasm graalwasm-23.1.1-linux-amd64
Maven 3.9.6
Emscripten git~3.1.53
WASI-SDK wasi-sdk-21.0-linux.tar.gz
WABT git~1.0.34-40-g1471dffe

GraalVM 環境準備手順

本記事では Oracle GraalVM を使います.

公式からバイナリを含む tar.gz を取得して展開,パスを通します.

# GraalVM
mkdir -p $HOME/installers/linux/graalvm && cd $_ && \
wget https://download.oracle.com/graalvm/21/archive/graalvm-jdk-21_linux-x64_bin.tar.gz &&
mkdir -p $HOME/extracts &&
tar -xvf graalvm-jdk-21_linux-x64_bin.tar.gz --directory $HOME/extracts
vim $HOME/.bashrc
# ダウンロードしたバージョンに合わせてください
> # Java
> export JAVA_HOME=$HOME/extracts/graalvm-jdk-21.0.2+13.1
> export CLASSPATH=$JAVA_HOME/lib
> export PATH=$PATH:$JAVA_HOME/bin
source $HOME/.bashrc
## 確認
java -version

# GraalWasm
cd $HOME/installers/linux/graalvm && \
wget https://gds.oracle.com/api/20220101/artifacts/069B4EC01C4D519AE0631718000AA34D/content -O graalwasm-23.1.1-linux-amd64.tar.gz &&
tar -xvf graalwasm-23.1.1-linux-amd64.tar.gz --directory $HOME/extracts
vim $HOME/.bashrc
> # GraalWasm
> export WASM_HOME=$HOME/extracts/graalwasm-23.1.1-linux-amd64
> export PATH=$PATH:$WASM_HOME/bin
source $HOME/.bashrc
## 確認
wasm --version

WebAssembly の動作検証のため,Emscripten,WASI-SDK と WABT も入れておきます.

# Emscripten
mkdir -p $HOME/codes/GitHub && cd $_ && \
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install latest
> Resolving SDK alias 'latest' to '3.1.53'
> Resolving SDK version '3.1.53' to 'sdk-releases-e5523d57a0e0dcf80f3b101bbc23613fcc3101aa-64bit'
> ...
./emsdk activate latest
> Resolving SDK alias 'latest' to '3.1.53'
> Resolving SDK version '3.1.53' to 'sdk-releases-e5523d57a0e0dcf80f3b101bbc23613fcc3101aa-64bit'
> ...
source ./emsdk_env.sh
## 確認
emcc -v

# WASI-SDK
mkdir -p $HOME/installers/linux/wasi && cd $_ && \
wget https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-21/wasi-sdk-21.0-linux.tar.gz
tar -xvf wasi-sdk-21.0-linux.tar.gz --directory $HOME/extracts
vim $HOME/.bashrc
> # WASI-SDK
> export WASI_HOME=$HOME/extracts/wasi-sdk-21.0
> export PATH=$PATH:$WASI_HOME/bin
source $HOME/.bashrc
## 確認
clang -v

# WABT
# 事前に cmake を取得しておく
sudo apt install cmake
cd $HOME/codes/GitHub && \
git clone --recursive https://github.com/WebAssembly/wabt && \
cd wabt && \
git submodule update --init && \
mkdir build && \
cd build && \
cmake .. && \
cmake --build .
vim $HOME/.bashrc
> # WABT
> export WABT_HOME=$HOME/codes/GitHub/wabt
> export PATH=$PATH:$WABT_HOME/build
source $HOME/.bashrc
## 確認
wat2wasm --help

ネイティブイメージ作成の基礎

ここでは,公式ドキュメントに従ってネイティブイメージの動作について確認します.

HelloWorld.java
public class HelloWorld {
    static class Greeter {
        static {
            System.out.println("Greeter is getting ready!");
        }
        
        public static void greet() {
          System.out.println("Hello, World!");
        }
    }

  public static void main(String[] args) {
    Greeter.greet();
  }
}

上記のサンプルに対して (普段通りに) javac -> java を行うと以下の結果が得られます.

javac HelloWorld.java
java -cp . HelloWorld
> Greeter is getting ready!
> Hello, World!

これに対してネイティブイメージを作成してみます.

# .class ファイル作成後
# ネイティブイメージ作成, helloworld ファイルが作成される
native-image HelloWorld
# 実行
./helloworld
> Greeter is getting ready!
> Hello, World!

特定のクラスの初期化をネイティブイメージのビルド中に行うこともできます.

# 実行
# Greeter is getting ready! はビルド中に表示される
native-image HelloWorld --initialize-at-build-time=HelloWorld\$Greeter
./helloworld
> Hello, World!

また,初期化段階においてパラメタを渡すこともできます.

Example.java
class Example {
    private static final String message;
    
    static {
        message = System.getProperty("message");
    }

    public static void main(String[] args) {
        System.out.println("Hello, World! My message is: " + message);
    }
}
javac Example.java
# 実行時にパラメタを渡す場合
java -Dmessage=hi Example
> Hello, World! My message is: hi

# 渡さないと null が返される
java -cp . Example
> Hello, World! My message is: null

# ネイティブイメージ作成, ビルド時にパラメタを渡す
native-image Example --initialize-at-build-time=Example -Dmessage=native
# 実行
./example 
> Hello, World! My message is: native

ネイティブイメージ作成時にどのようなことが行われているのか詳細が気になる場合は以下

初期化に関してより詳しく知りたい方は以下

ビルド時のメモリ最適化 (GC) が気になる方は以下をご参照ください.

WebAssembly (Wasm) 連携する

GraalVM での Wasm の活用方法は以下の 2 パターンに分かれます.

  • GraalWasm で Wasm を実行する
  • Wasm を Java から実行する

GraalWasm で Wasm を実行する

GraalVM JDK 21 以降では,スタンドアロンディストリビューションとして WebAssembly 実行環境 (GraalWasm) を提供しています.

例として,サンプルの C コード floyd.cEmscripten で WebAssembly に変換したのち,GraalWasm で実行します.

floyd.c
#include <stdio.h>

int main() {
  int number = 1;
  int rows = 10;
  for (int i = 1; i <= rows; i++) {
    for (int j = 1; j <= i; j++) {
      printf("%d ", number);
      ++number;
    }
    printf(".\n");
  }
  return 0;
}
emcc -o floyd.wasm floyd.c
wasm --Builtins=wasi_snapshot_preview1 floyd.wasm
> 1 .
> 2 3 .
> 4 5 6 .
> 7 8 9 10 .
> 11 12 13 14 15 .
> 16 17 18 19 20 21 .
> 22 23 24 25 26 27 28 .
> 29 30 31 32 33 34 35 36 .
> 37 38 39 40 41 42 43 44 45 .
> 46 47 48 49 50 51 52 53 54 55 .

WASI_SDK など他のツールを利用してコンパイルしてからも GraalWasm で実行することができます.

clang -O3 -o floyd.wasm floyd.c
wasm --Builtins=wasi_snapshot_preview1 floyd.wasm
> 1 .
> 2 3 .
> ...

WebAssembly を Java から実行する

Truffle フレームワーク にてご紹介したように,GraalVM の Polyglot API を利用することで Java ソースコードの中に Wasm ファイルを埋め込んで実行することができます.

ここでは公式の Embedding サンプルを参考に

  • Maven プロジェクトを作成
  • 必要な依存関係を記述
  • Wasm ファイルの作成
  • アーティファクトの作成
  • ネイティブイメージの作成
  • ネイティブイメージの実行

を行います.

Apache Maven について詳しく学びたい場合は以下をご参照ください.

今回試実装したソースコードは GitHub に配置しています.

.wat から .wasm を作成するにあたっては,オンラインデモを使って記述していくとやりやすいかと思います.

GraalVM での他言語の Embedding に関してもう少し詳しく知りたい方は以下をご参照ください.

# Maven プロジェクトの作成
mvn archetype:generate
> ...
> Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): 2099:
> Choose org.apache.maven.archetypes:maven-archetype-quickstart version:
> 1: 1.0-alpha-1
> ...
> 8: 1.4
> Choose a number: 8:
> Define value for property 'groupId': com.caunus.sampleapp
> Define value for property 'artifactId': wasm-graal-training
> Define value for property 'version' 1.0-SNAPSHOT: :
> Define value for property 'package' com.caunus.sampleapp: :
> ...
> [INFO] ------------------------------------------------------------------------
> [INFO] BUILD SUCCESS
> [INFO] ------------------------------------------------------------------------

# pom.xml の確認,編集
cd wasm-graal-training && vim pom.xml

# サンプルアプリ用のファイルは削除しておく
rm ./src/main/java/com/caunus/sampleapp/App.java ./src/test/java/com/caunus/sampleapp/AppTest.java

# module-info.java, Main.java, SampleAppTest.java, assembly.xml 作成
vim ./src/main/java/module-info.java
vim ./src/main/java/com/caunus/sampleapp/Main.java
vim ./src/test/java/com/caunus/sampleapp/SampleAppTest.java
vim ./assembly.xml

# Wasm ファイルの作成, 1 から指定した数時までの和を計算する処理を作成した
mkdir -p ./src/main/resources/com/caunus/sampleapp
vim ./src/main/resources/com/caunus/sampleapp/sigma.wat
wat2wasm ./src/main/resources/com/caunus/sampleapp/sigma.wat -o ./src/main/resources/com/caunus/sampleapp/sigma.wasm

# 自分で jar を管理し,ネイティブイメージを作成する場合

## アーティファクト (jar) の作成
mvn package
## Maven で実行
mvn exec:exec

## jar 実行
java -cp \
./target/wasm-graal-training-1.0-SNAPSHOT.jar:\
$HOME/.m2/repository/org/graalvm/polyglot/polyglot/23.1.2/polyglot-23.1.2.jar:\
$HOME/.m2/repository/org/graalvm/sdk/collections/23.1.2/collections-23.1.2.jar:\
$HOME/.m2/repository/org/graalvm/sdk/nativeimage/23.1.2/nativeimage-23.1.2.jar:\
$HOME/.m2/repository/org/graalvm/sdk/word/23.1.2/word-23.1.2.jar:\
$HOME/.m2/repository/org/graalvm/wasm/wasm-language/23.1.2/wasm-language-23.1.2.jar:\
$HOME/.m2/repository/org/graalvm/truffle/truffle-api/23.1.2/truffle-api-23.1.2.jar:\
$HOME/.m2/repository/org/graalvm/truffle/truffle-runtime/23.1.2/truffle-runtime-23.1.2.jar:\
$HOME/.m2/repository/org/graalvm/truffle/truffle-enterprise/23.1.2/truffle-enterprise-23.1.2.jar:\
$HOME/.m2/repository/org/graalvm/truffle/truffle-compiler/23.1.2/truffle-compiler-23.1.2.jar:\
$HOME/.m2/repository/org/graalvm/sdk/jniutils/23.1.2/jniutils-23.1.2.jar:\
$HOME/.m2/repository/org/graalvm/sdk/nativebridge/23.1.2/nativebridge-23.1.2.jar:\
$HOME/.m2/repository/junit/junit/4.13.2/junit-4.13.2.jar:\
$HOME/.m2/repository/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar \
com.caunus.sampleapp.Main
> Initializing language wasm
> wasm: sigma(10) = 55

## ネイティブイメージの作成
native-image -cp \
./target/wasm-graal-training-1.0-SNAPSHOT.jar:\
$HOME/.m2/repository/org/graalvm/polyglot/polyglot/23.1.2/polyglot-23.1.2.jar:\
$HOME/.m2/repository/org/graalvm/sdk/collections/23.1.2/collections-23.1.2.jar:\
$HOME/.m2/repository/org/graalvm/sdk/nativeimage/23.1.2/nativeimage-23.1.2.jar:\
$HOME/.m2/repository/org/graalvm/sdk/word/23.1.2/word-23.1.2.jar:\
$HOME/.m2/repository/org/graalvm/wasm/wasm-language/23.1.2/wasm-language-23.1.2.jar:\
$HOME/.m2/repository/org/graalvm/truffle/truffle-api/23.1.2/truffle-api-23.1.2.jar:\
$HOME/.m2/repository/org/graalvm/truffle/truffle-runtime/23.1.2/truffle-runtime-23.1.2.jar:\
$HOME/.m2/repository/org/graalvm/truffle/truffle-enterprise/23.1.2/truffle-enterprise-23.1.2.jar:\
$HOME/.m2/repository/org/graalvm/truffle/truffle-compiler/23.1.2/truffle-compiler-23.1.2.jar:\
$HOME/.m2/repository/org/graalvm/sdk/jniutils/23.1.2/jniutils-23.1.2.jar:\
$HOME/.m2/repository/org/graalvm/sdk/nativebridge/23.1.2/nativebridge-23.1.2.jar:\
$HOME/.m2/repository/junit/junit/4.13.2/junit-4.13.2.jar:\
$HOME/.m2/repository/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar \
com.caunus.sampleapp.Main
## ネイティブイメージの実行, カレントディレクトリに作成される
./com.caunus.sampleapp.main
> Initializing language wasm
> wasm: sigma(10) = 55
mv com.caunus.sampleapp.main ./target

# Maven でネイティブイメージの作成まで行う場合

## アーティファクト (jar) & ネイティブイメージの作成
mvn -Pnative package

## ネイティブイメージの実行, target に作成される
./target/wasm-graal-training
> Initializing language wasm
> wasm: sigma(10) = 55
pom.xml
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.caunus.sampleapp</groupId>
  <artifactId>wasm-graal-training</artifactId>
  <version>1.0-SNAPSHOT</version>

  <name>wasm-graal-training</name>
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <maven.compiler.release>21</maven.compiler.release>
+    <graalvm.version>23.1.2</graalvm.version>
  </properties>

  <dependencies>
+    <!--polyglot runtime-->
+    <dependency>
+      <groupId>org.graalvm.polyglot</groupId>
+      <artifactId>polyglot</artifactId>
+      <version>${graalvm.version}</version>
+      <type>jar</type>
+    </dependency>
+    <!--graalwasm-->
+    <dependency>
+      <groupId>org.graalvm.polyglot</groupId>
+      <artifactId>wasm</artifactId>
+      <version>${graalvm.version}</version>
+      <type>pom</type>
+    </dependency>
+    <!--truffle tools-->
+    <dependency>
+      <groupId>org.graalvm.polyglot</groupId>
+      <artifactId>tools</artifactId>
+      <version>${graalvm.version}</version>
+      <type>pom</type>
+    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <pluginManagement>
      <plugins>
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.1.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
-          <version>3.8.0</version>
+          <version>3.11.0</version>
+          <configuration>
+            <source>21</source>
+            <target>21</target>
+          </configuration>
        </plugin>
        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
-          <version>2.22.1</version>
+          <version>3.1.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-jar-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-install-plugin</artifactId>
          <version>2.5.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-deploy-plugin</artifactId>
          <version>2.8.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-site-plugin</artifactId>
          <version>3.7.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-project-info-reports-plugin</artifactId>
          <version>3.0.0</version>
        </plugin>

+        <!--exec goal-->
+        <plugin>
+          <groupId>org.codehaus.mojo</groupId>
+          <artifactId>exec-maven-plugin</artifactId>
+          <version>3.1.0</version>
+          <executions>
+            <execution>
+              <goals>
+                <goal>exec</goal>
+              </goals>
+            </execution>
+            <execution>
+              <id>no-runtime-compilation</id>
+              <goals>
+                <goal>exec</goal>
+              </goals>
+              <configuration>
+                <executable>${java.home}/bin/java</executable>
+                <arguments>
+                  <argument>--module-path</argument>
+                  <modulepath />
+                  <argument>-m</argument>
+                  <argument>sampleapp/com.caunus.sampleapp.Main</argument>
+                </arguments>
+              </configuration>
+            </execution>
+          </executions>
+          <configuration>
+            <executable>${java.home}/bin/java</executable>
+            <arguments>
+              <argument>--module-path</argument>
+              <modulepath />
+              <argument>-m</argument>
+              <argument>sampleapp/com.caunus.sampleapp.Main</argument>
+            </arguments>
+          </configuration>
+        </plugin>
+        <!--maven jlink-->
+        <plugin>
+          <artifactId>maven-jlink-plugin</artifactId>
+          <version>3.1.0</version>
+          <extensions>true</extensions>
+          <configuration>
+            <ignoreSigningInformation>true</ignoreSigningInformation>
+          </configuration>
+        </plugin>
      </plugins>
    </pluginManagement>
  </build>

+  <!--mvn -Pnative package : native-image with maven-->
+  <profiles>
+    <profile>
+      <id>native</id>
+      <build>
+        <plugins>
+          <plugin>
+            <groupId>org.graalvm.buildtools</groupId>
+            <artifactId>native-maven-plugin</artifactId>
+            <version>0.9.28</version>
+            <extensions>true</extensions>
+            <executions>
+              <execution>
+                <id>build-native</id>
+                <goals>
+                  <goal>compile-no-fork</goal>
+                </goals>
+                <phase>package</phase>
+              </execution>
+            </executions>
+            <configuration>
+              <imageName>${project.artifactId}</imageName>
+              <mainClass>com.caunus.sampleapp.Main</mainClass>
+              <buildArgs>
+                <buildArg>--no-fallback</buildArg>
+                <buildArg>-J-Xmx20g</buildArg>
+              </buildArgs>
+            </configuration>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>

+    <!--when running on a different JDK than GraalVM-->
+    <profile>
+      <id>not-graalvm-jdk</id>
+      <activation>
+        <file>
+          <missing>${java.home}/lib/graalvm</missing>
+        </file>
+      </activation>
+
+      <build>
+        <plugins>
+          ... (abbreviation)
+        </plugins>
+      </build>
+    </profile>

+    <!--maven assembly-->
+    <profile>
+      <id>assembly</id>
+      <build>
+        <plugins>
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-assembly-plugin</artifactId>
+            <version>3.6.0</version>
+            <executions>
+              <execution>
+                <phase>package</phase>
+                <goals>
+                  <goal>single</goal>
+                </goals>
+                <configuration>
+                  <archive>
+                    <manifest>
+                      <mainClass>com.caunus.sampleapp.Main</mainClass>
+                    </manifest>
+                  </archive>
+                  <descriptors>
+                    <descriptor>assembly.xml</descriptor>
+                  </descriptors>
+                </configuration>
+              </execution>
+            </executions>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>

+    <!--maven shade-->
+    <profile>
+      <id>shade</id>
+      <build>
+        <plugins>
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-shade-plugin</artifactId>
+            <version>3.5.1</version>
+            <executions>
+              <execution>
+                <phase>package</phase>
+                <goals>
+                  <goal>shade</goal>
+                </goals>
+              </execution>
+            </executions>
+            <configuration>
+              <transformers>
+                <transformer
+                  implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
+                <transformer
+                  implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
+                  <mainClass>com.caunus.sampleapp.Main</mainClass>
+                </transformer>
+              </transformers>
+              <filters>
+                <filter>
+                  <artifact>*:*:*:*</artifact>
+                  <excludes>
+                    <exclude>META-INF/*.SF</exclude>
+                    <exclude>META-INF/*.DSA</exclude>
+                    <exclude>META-INF/*.RSA</exclude>
+                  </excludes>
+                </filter>
+              </filters>
+            </configuration>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
  </profiles>
</project>
Main.java
package com.caunus.sampleapp;

import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Source;
import org.graalvm.polyglot.Value;

import java.io.IOException;
import java.nio.file.Files;
import java.util.Set;

public class Main {

    public static void main(String[] args) throws IOException {
        try (Context context = Context.newBuilder().allowAllAccess(true).build()) {
            Set<String> languages = context.getEngine().getLanguages().keySet();
            for (String id : languages) {
                System.out.println("Initializing language " + id);
                context.eval(Source.newBuilder("wasm", Main.class.getResource("sigma.wasm")).build());
                Value sigma = context.getBindings("wasm").getMember("main").getMember("sigma");
                System.out.println("wasm: sigma(10) = " + sigma.execute(10L));
            }
        }
    }
}
sigma.wat
(module
  (func $sigma (export "sigma") (param i64) (result i64)
    local.get 0
    i64.const 2
    i64.lt_s
    if (result i64)
      i64.const 1
    else
      local.get 0
      local.get 0
      i64.const 1
      i64.sub
      call $sigma
      i64.add
    end))
  • exec-maven-plugin を有効化して exec ゴールを実行しています.exec ゴールを Maven で実行することで,ビルドライフサイクルに合わせて Wasm 等で組み込んだ何かしらの処理を実行させることもできます.
  • 自分で jar を使って処理を行いたい場合のクラスパスは target に作成された modulepath を参考に指定すると良いです.
  • native-maven-plugin をプロファイルに設定しておくことで mvn コマンドでのネイティブイメージ作成を実現しています.
  • ランタイムの JDK が GraalVM ではなかった場合のプロファイルも設定しています.(後述)

上記より Wasm を埋め込んだ Java ソースコードのコンパイル,アーティファクト (jar) の作成からネイティブイメージの作成,実行まで確認できたと思います.

ポータビリティはどんな感じ?

ここで,Java 環境 (ランタイム) に GraalVM の JDK を使わず (素の) OpenJDK を利用していた場合はどうなるのでしょうか?

pom.xml には GraalVM JDK を使っていない場合に備えて,GraalVM が準備しているコンパイルプラグインをプロファイルに設定してたため,コンパイルは問題になりません.

一方で (別途準備しない限り) 環境で native-image コマンドは使えないので,ネイティブイメージの作成は行えません.

それでは mvn package でコンパイル・作成した jar は実行できるのか試してみましょう.

実際に動かしてみると Wasm で実装した処理は動きますが,警告も同時に出力されます.

java -cp \
./target/wasm-graal-training-1.0-SNAPSHOT.jar:\
$HOME/.m2/repository/org/graalvm/polyglot/polyglot/23.1.2/polyglot-23.1.2.jar:\
$HOME/.m2/repository/org/graalvm/sdk/collections/23.1.2/collections-23.1.2.jar:\
$HOME/.m2/repository/org/graalvm/sdk/nativeimage/23.1.2/nativeimage-23.1.2.jar:\
$HOME/.m2/repository/org/graalvm/sdk/word/23.1.2/word-23.1.2.jar:\
$HOME/.m2/repository/org/graalvm/wasm/wasm-language/23.1.2/wasm-language-23.1.2.jar:\
$HOME/.m2/repository/org/graalvm/truffle/truffle-api/23.1.2/truffle-api-23.1.2.jar:\
$HOME/.m2/repository/org/graalvm/truffle/truffle-runtime/23.1.2/truffle-runtime-23.1.2.jar:\
$HOME/.m2/repository/org/graalvm/truffle/truffle-enterprise/23.1.2/truffle-enterprise-23.1.2.jar:\
$HOME/.m2/repository/org/graalvm/truffle/truffle-compiler/23.1.2/truffle-compiler-23.1.2.jar:\
$HOME/.m2/repository/org/graalvm/sdk/jniutils/23.1.2/jniutils-23.1.2.jar:\
$HOME/.m2/repository/org/graalvm/sdk/nativebridge/23.1.2/nativebridge-23.1.2.jar:\
$HOME/.m2/repository/junit/junit/4.13.2/junit-4.13.2.jar:\
$HOME/.m2/repository/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar \
com.caunus.sampleapp.Main
> [To redirect Truffle log output to a file use one of the following options:
> * '--log.file=<path>' if the option is passed using a guest language launcher.
> * '-Dpolyglot.log.file=<path>' if the option is passed using the host Java launcher.
> * Configure logging using the polyglot embedding API.]
> [engine] WARNING: The polyglot engine uses a fallback runtime that does not support runtime compilation to native code.
> Execution without runtime compilation will negatively impact the guest application performance.
> The following cause was found: JVMCI is not enabled for this JVM. Enable JVMCI using -XX:+EnableJVMCI.
> For more information see: https://www.graalvm.org/latest/reference-manual/embed-languages/#runtime-optimization-support.
> To disable this warning use the '--engine.WarnInterpreterOnly=false' option or the '-Dpolyglot.engine.WarnInterpreterOnly=false' system property.
> Initializing language wasm
> wasm: sigma(10) = 55

出力された公式ドキュメントのリンクを改めて確認すると,実行パフォーマンスへの悪影響を与える可能性があることが説明されています.

Java ランタイム ランタイムの最適化レベル
Oracle GraalVM コンパイル時に追加で最適化処理を行うことで問題なく利用できるようになっている
GraalVM Community Edition 最高効率
Oracle JDK -XX:+EnableJVMCI オプションをつけることで最適化を図らないといけない
パフォーマンスに悪影響がある可能性がある
OpenJDK -XX:+EnableJVMCI オプションをつけることで最適化を図らないといけない
パフォーマンスに悪影響がある可能性がある
JVMCI capability のない JDK 最適化する方法はない

よって jar を取り扱いたいなら Java の実行環境は GraalVM の JDK を利用しておくと良いかと思います.

一方,GraalVM の JDK を利用した環境で mvn -Pnative package をして作成したネイティブイメージが配布された場合はどうでしょうか?

こちらは警告を表示することはなく正常に動きます.
改めてネイティブイメージの強力さを実感します.

builder-runner.png

最後に

本記事では,GraalVM の基本機能を確認するとともに,GraalWasm はどうやって使うのかを確認しました.
また,ネイティブイメージのポータビリティ性能についても確認を行いました.

GraalVM を利用している Java アプリケーションにおいて,今後特に注目していきたいトピックかと思います.

是非ご参考になればと思います.

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

  2. https://graalvm.github.io/native-build-tools/latest/maven-plugin.html

  3. https://graalvm.github.io/native-build-tools/latest/gradle-plugin.html

  4. https://www.graalvm.org/latest/reference-manual/native-image/guides/containerise-native-executable-and-run-in-docker-container/

  5. https://www.graalvm.org/latest/reference-manual/native-image/

  6. https://github.com/oracle/graal/tree/master/compiler

  7. https://www.graalvm.org/latest/graalvm-as-a-platform/language-implementation-framework/

  8. https://github.com/oracle/graal/blob/master/wasm/README.md

  9. https://webassembly.org/

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