ポリグロット・プログラミング
GraalVMでは、ユーザーは、Truffle言語実装フレームワーク(以降、Truffle)を使用して、ある言語から別の言語に値をシームレスに渡すポリグロット・アプリケーションを記述できます。
Truffleは、抽象構文ツリーを自己変更するためのインタプリタとしてプログラミング言語実装をビルドするためのJavaライブラリです。Truffleを使用して言語インタプリタを記述すると、言語のJust-in-TimeコンパイラとしてGraalVMコンパイラが自動的に使用されます。このフレームワークにアクセスできることにより、たとえば、RubyアプリケーションをJavaアプリケーションと同じJVMで実行できます。また、JVMベースのホスト言語とゲスト言語が、直接相互運用し、同じメモリー領域でデータを受け渡すこともできます。
Truffleを使用して実装された言語で他言語のポリグロット値を提供するために、いわゆるポリグロット相互運用性プロトコルが開発されました。この相互運用性プロトコルは、すべての言語が実装し、他言語のポリグロット値に使用する一連の標準化されたメッセージで構成されています。このプロトコルにより、GraalVMは、それぞれの言語が相互を認識していなくても、あらゆる組合せの言語間の相互運用性をサポートできます。詳細は、『High-Performance Cross-Language Interoperability in a Multi-Language Runtime』を参照してください。この項全体にわたって、GraalVMポリグロットAPIを使用して複数の言語を組み合せる方法を学習します。
ポリグロット・アプリケーションの実行
次に示す各例は、基本的なポリグロット・アプリケーションの使用を開始することを目的としています。開始言語の項を選択した後、ターゲット言語のタブを選択します。
始める前に、必ずGraalVMを設定してください。
下に示す各例は、次の方法で機能します:
- JVMでは、
--polyglot --jvm
を渡します。 - ネイティブ・ランチャでは、
--polyglot
(たとえば、js --polyglot
)を使用します。gu
でインストールされた言語にアクセスするには、イメージの再ビルドが必要になることがあります。 - ネイティブ実行可能ファイルを使用します(例:
native-image --language:js
)。
Javaをターゲット言語として使用し、Java配列以外のクラスにアクセスするネイティブ・ランチャおよびネイティブ実行可能ファイルについては、イメージを再コンパイルし、リフレクション構成ファイルを提供する必要があります。
ノート: LLVMをターゲット言語としてアプリケーションを開始するには、必ず、下に示すpolyglot.cファイルをプリコンパイルしてください。
JavaScript/Node.jsからの開始
ファイルpolyglot.js
を作成します:
// BEGIN-SNIPPET
var array = Polyglot.eval("R", "c(1,2,42,4)")
console.log(array[2]);
// END-SNIPPET
// BEGIN-SNIPPET
var array = Polyglot.eval("ruby", "[1,2,42,4]")
console.log(array[2]);
// END-SNIPPET
// BEGIN-SNIPPET
var array = Polyglot.eval("python", "[1,2,42,4]")
console.log(array[2]);
// END-SNIPPET
// BEGIN-SNIPPET
var array = new (Java.type("int[]"))(4);
array[2] = 42;
console.log(array[2])
// END-SNIPPET
// BEGIN-SNIPPET
var cpart = Polyglot.evalFile("llvm", "polyglot");
cpart.main()
// END-SNIPPET
次を実行します:
js --polyglot --jvm polyglot.js
42
node --polyglot --jvm polyglot.js
42
開始言語R
ファイルpolyglot.R
を作成します:
# BEGIN-SNIPPET
array <- eval.polyglot("js", "[1,2,42,4]")
print(array[3L])
# END-SNIPPET
# BEGIN-SNIPPET
array <- eval.polyglot("ruby", "[1,2,42,4]")
print(array[3L])
# END-SNIPPET
# BEGIN-SNIPPET
array <- eval.polyglot("python", "[1,2,42,4]")
print(array[3L])
# END-SNIPPET
# BEGIN-SNIPPET
array <- new("int[]", 4)
array[3L] <- 42
print(array[3L])
# END-SNIPPET
# BEGIN-SNIPPET
cpart <- eval.polyglot("llvm", path="polyglot")
cpart$main()
# END-SNIPPET
次を実行します:
Rscript --polyglot --jvm polyglot.R
[1] 42
開始言語Ruby
ファイルpolyglot.rb
を作成します:
# BEGIN-SNIPPET
array = Polyglot.eval('js', '[1,2,42,4]')
puts array[2]
# END-SNIPPET
# BEGIN-SNIPPET
array = Polyglot.eval('R', 'c(1L,2L,42L,4L)')
puts array[2]
# END-SNIPPET
# BEGIN-SNIPPET
array = Polyglot.eval('python', '[1,2,42,4]')
puts array[2]
# END-SNIPPET
# BEGIN-SNIPPET
array = Java.type('int[]').new(4)
array[2] = 42
print(array[2])
# END-SNIPPET
# BEGIN-SNIPPET
cpart = Polyglot.eval_file('llvm', 'polyglot')
cpart.main()
# END-SNIPPET
次を実行します:
ruby --polyglot --jvm polyglot.rb
42
開始言語Python
ファイルpolyglot.py
を作成します:
# BEGIN-SNIPPET
import polyglot
array = polyglot.eval(language="js", string="[1,2,42,4]")
print(array[2])
# END-SNIPPET
# BEGIN-SNIPPET
import polyglot
array = polyglot.eval(language="R", string="c(1L,2L,42L,4L)")
print(array[2])
# END-SNIPPET
# BEGIN-SNIPPET
import polyglot
array = polyglot.eval(language="ruby", string="[1,2,42,4]")
print(array[2])
# END-SNIPPET
# BEGIN-SNIPPET
import java
array = java.type("int[]")(4)
array[2] = 42
print(array[2])
# END-SNIPPET
# BEGIN-SNIPPET
import polyglot
cpart = polyglot.eval(language="llvm", path="polyglot")
cpart.main()
# END-SNIPPET
次を実行します:
graalpython --polyglot --jvm polyglot.py
42
開始言語Java
ファイルPolyglot.java
を作成します:
// BEGIN-SNIPPET
import org.graalvm.polyglot.*;
class Polyglot {
public static void main(String[] args) {
Context polyglot = Context.create();
Value array = polyglot.eval("js", "[1,2,42,4]");
int result = array.getArrayElement(2).asInt();
System.out.println(result);
}
}
// END-SNIPPET
// BEGIN-SNIPPET
import org.graalvm.polyglot.*;
class Polyglot {
public static void main(String[] args) {
Context polyglot = Context.newBuilder().
allowAllAccess(true).build();
Value array = polyglot.eval("R", "c(1,2,42,4)");
int result = array.getArrayElement(2).asInt();
System.out.println(result);
}
}
// END-SNIPPET
// BEGIN-SNIPPET
import org.graalvm.polyglot.*;
class Polyglot {
public static void main(String[] args) {
Context polyglot = Context.newBuilder().
allowAllAccess(true).build();
Value array = polyglot.eval("ruby", "[1,2,42,4]");
int result = array.getArrayElement(2).asInt();
System.out.println(result);
}
}
// END-SNIPPET
// BEGIN-SNIPPET
import org.graalvm.polyglot.*;
class Polyglot {
public static void main(String[] args) {
Context context = Context.newBuilder().allowIO(true).build();
Value array = context.eval("python", "[1,2,42,4]");
int result = array.getArrayElement(2).asInt();
System.out.println(result);
}
}
// END-SNIPPET
// BEGIN-SNIPPET
import java.io.*;
import org.graalvm.polyglot.*;
class Polyglot {
public static void main(String[] args) throws IOException {
Context polyglot = Context.newBuilder().
allowAllAccess(true).build();
File file = new File("polyglot");
Source source = Source.newBuilder("llvm", file).build();
Value cpart = polyglot.eval(source);
cpart.execute();
}
}
// END-SNIPPET
次を実行します:
javac Polyglot.java
java Polyglot
42
開始言語C
ファイルpolyglot.c
を作成します:
// BEGIN-SNIPPET
#include <stdio.h>
#include <graalvm/llvm/polyglot.h>
int main() {
void *array = polyglot_eval("js", "[1,2,42,4]");
int element = polyglot_as_i32(polyglot_get_array_element(array, 2));
printf("%d\n", element);
return element;
}
// END-SNIPPET
// BEGIN-SNIPPET
#include <stdio.h>
#include <graalvm/llvm/polyglot.h>
int main() {
void *array = polyglot_eval("R", "c(1,2,42,4)");
int element = polyglot_as_i32(polyglot_get_array_element(array, 2));
printf("%d\n", element);
return element;
}
// END-SNIPPET
// BEGIN-SNIPPET
#include <stdio.h>
#include <graalvm/llvm/polyglot.h>
int main() {
void *array = polyglot_eval("ruby", "[1,2,42,4]");
int element = polyglot_as_i32(polyglot_get_array_element(array, 2));
printf("%d\n", element);
return element;
}
// END-SNIPPET
// BEGIN-SNIPPET
#include <stdio.h>
#include <graalvm/llvm/polyglot.h>
int main() {
void *array = polyglot_eval("python", "[1,2,42,4]");
int element = polyglot_as_i32(polyglot_get_array_element(array, 2));
printf("%d\n", element);
return element;
}
// END-SNIPPET
// BEGIN-SNIPPET
#include <stdio.h>
#include <graalvm/llvm/polyglot.h>
int main() {
void *arrayType = polyglot_java_type("int[]");
void *array = polyglot_new_instance(arrayType, 4);
polyglot_set_array_element(array, 2, 42);
int element = polyglot_as_i32(polyglot_get_array_element(array, 2));
printf("%d\n", element);
return element;
}
// END-SNIPPET
Cのコード例は、clang
などのLLVMフロントエンドを使用してLLVMビットコードにコンパイルする必要があります。ユーザーは、ビルド済のLLVMツールチェーン・サポートをインストールすることにより、GraalVMに付属しているclang
を使用できます:
gu install llvm-toolchain
export LLVM_TOOLCHAIN=$(lli --print-toolchain-path)
次を実行します:
$LLVM_TOOLCHAIN/clang polyglot.c -lgraalvm-llvm -o polyglot
lli --polyglot polyglot
42
polyglotランチャ
ポリグロット・アプリケーションでは、多くの場合、アプリケーションのプライマリ言語を決定できません。そのため、polyglot
と呼ばれる試験段階の新しいランチャがGraalVMに追加されました。現時点では、このランチャは、プライマリ言語を選択しなくてもJavaScript、RubyおよびRのコードを実行します。polyglotランチャでは--polyglot
オプションは必要ありません。デフォルトで有効になっています。
前述の例を使用してポリグロット・アプリケーションを実行する方法を次に示します:
polyglot --jvm polyglot.js polyglot.R polyglot.rb
ポリグロット・シェルと呼ばれる、複数言語用の試験段階の基本シェルも用意しました。これは、Truffleフレームワークで実装された言語の双方向性をすばやくテストする場合に便利です。この起動方法は次のとおりです:
polyglot --jvm --shell
オプションの言語パックをすべてGraalVMコア・インストールにインストールした場合、ポリグロット・シェルは次のようになります:
GraalVM MultiLanguage Shell 21.2.0
Copyright (c) 2013-2021, Oracle and/or its affiliates
Java version 1.8|11
JavaScript version 21.2.0
Python version 3.8.5
R version 4.0.3
Ruby version 2.7.3
Usage:
Use Ctrl+n to switch language and Ctrl+d to exit.
Enter -usage to get a list of available commands.
js>
ノート: polyglot
ランチャおよびポリグロット・シェルは、GraalVMの試験段階の機能です。
ポリグロット・オプション
スループットまたは起動が向上するように言語エンジンを構成できます。
--engine.Mode=default
では、エンジンの実行モードを構成します。実行モードによって、ポリグロット・エンジンがレイテンシまたはスループットに関して自動的にチューニングされます。throughput
では、最大量のプロファイリング情報が収集され、最大数の最適化を使用してコンパイルされます。このモードでは、アプリケーションの起動が遅くなりますが、スループットが向上します。特に指定されていない場合、このモードではコンパイラ構成community
またはenterprise
が使用されます。default
では、バランスの取れたエンジン構成が使用されます。特に指定されていない場合、このモードではコンパイラ構成community
またはenterprise
が使用されます。latency
では、最小限のプロファイリング情報のみを収集してコンパイルを可能なかぎり高速化し、生成されるコードの最適化のレベルは低くなります。このモードでは、アプリケーションの起動が高速になりますが、スループットは最適になりません。特に指定されていない場合、このモードではコンパイラ構成economy
が使用されます。
言語ランチャのオプションの受渡し
それぞれの言語ランチャは、いわゆるポリグロット・オプションのセットで拡張されています。ポリグロット・オプションを使用すると、どの言語ランチャのユーザーも、GraalVMでサポートされている(Truffle言語実装フレームワークを使用して実装された)他の言語のオプションにアクセスできます。形式は--<languageID>.<property>=<value>
です。たとえば、R
ランチャは、--js.atomics=true
JavaScriptオプションもサポートします。
languageID
に使用できる値は次のとおりです:
js
: JavaScriptのオプションpython
: Pythonのオプションr
: Rのオプションruby
: Rubyのオプションllvm
: LLVMのオプション
使用可能なオプションを確認するには、--help:languages
を使用します。
ポリグロット・ツールのオプションも、--<toolID>.<property>=<value>
という形式で同様に機能します。
<toolID>
に使用できる値は次のとおりです:
inspect
: Chrome DevToolsを使用したデバッグを許可しますcpusampler
: CPU使用率に関するデータを収集しますcputracer
: CPU使用率に関するトレース情報を取得しますmemtracer
: メモリー使用量に関するトレース情報を取得します
使用可能なオプションを確認するには、--help:tools
を使用します。
プログラムによるオプションの受渡し
JavaポリグロットAPIを使用して、オプションをプログラムによって渡すこともできます。
OptionsTest.java
というファイルを作成します:
import org.graalvm.polyglot.*;
class OptionsTest {
public static void main(String[] args) {
Context polyglot = Context.newBuilder()
.allowExperimentalOptions(true)
.option("js.shared-array-buffer", "true")
.build();
// the use of shared array buffer requires the 'js.shared-array-buffer' option to be 'true'
polyglot.eval("js", "new SharedArrayBuffer(1024)");
}
}
次を実行します:
javac OptionsTest.java
java OptionsTest
ノート: ツール・オプションも同様に渡すことができます。コンテキストが作成された後にオプションを変更することはできません。
JVM引数を使用したオプションの受渡し
それぞれのポリグロット・オプションをJavaシステム・プロパティとして渡すこともできます。使用可能なオプションがそれぞれ、polyglot.
接頭辞が付いたシステム・プロパティに変換されます。たとえば、-Dpolyglot.js.strict=true
では、JVMで実行されるすべてのJavaScriptコードについて厳密解釈のデフォルト値が設定されます。プログラムによって設定されたオプションは、Javaシステム・プロパティよりも優先されます。言語については、使用可能な形式は-Dpolyglot.<languageID>.<property>=<value>
で、ツールについては-Dpolyglot.<toolID>.<property>=<value>
です。
SystemPropertiesTest.java
というファイルを作成します:
import org.graalvm.polyglot.*;
class SystemPropertiesTest {
public static void main(String[] args) {
Context polyglot = Context.newBuilder()
.allowExperimentalOptions(true)
.build();
// the use of shared array buffer requires the 'js.shared-array-buffer' option to be 'true'
polyglot.eval("js", "new SharedArrayBuffer(1024)");
}
}
次を実行します:
javac SystemPropertiesTest.java
java -Dpolyglot.js.strict=true SystemPropertiesTest
ノート: システム・プロパティは、ポリグロット・コンテキストの作成時に1回読み取られます。それ以降に変更しても影響はありません。