Truffle言語との相互運用性
Java on Truffleを使用すると、他のTruffle言語(Truffleフレームワークでインタプリタが実装される言語)をインタフェースして、ポリグロット・プログラム(複数の言語で記述されたプログラム)を作成できます。
このガイドでは、外部言語で記述されたコードをロードする方法、言語間でオブジェクトをエクスポートおよびインポートする方法、外部言語からJava on Truffleオブジェクトを使用する方法、Java on Truffleから外部オブジェクトを使用する方法およびホストJavaへの埋込み方法について説明します。
混乱を避けるために、ホストとゲストという用語を使用して、Javaが実行される異なるレイヤーを区別します。Java on Truffleは、ゲスト・レイヤーを指します。
ポリグロット・オプションはjava -truffle
ランチャに渡します。ネイティブ構成を使用する場合、他の言語にアクセスするには、--polyglot
フラグを使用することが必要になります。
Java on Truffleで使用される外部オブジェクトはゲストJava型に存在する必要があります。この型が外部オブジェクトにどのようにアタッチされるかは、実装の詳細になります。
ポリグロット
Java on Truffleは、polyglot.jar
として記述されているゲストJavaポリグロットAPIを提供します。このJARはゲストJavaコンテキストで自動的に注入されますが、--java.Polyglot=false
を使用して除外できます。
Polyglot
クラスをインポートして、他のゲスト言語と対話できます:
// guest java
import com.oracle.truffle.espresso.polyglot.Polyglot;
int two = Polyglot.eval(int.class, "js", "1+1");
オブジェクトが外部かどうかを判別できます:
// guest java
Object foreign = Polyglot.eval("js", "[2, 0, 2, 1]");
Object local = new int[]{2, 0, 2, 1};
System.out.println(Polyglot.isForeignObject(foreign)); // prints true
System.out.println(Polyglot.isForeignObject(local)); // prints false
外部オブジェクトをゲストJava型にキャストできます:
// guest java
Object foreignArray = Polyglot.eval("js", "['a string', 42, 3.14159, null]");
Object[] objects = Polyglot.cast(Object[].class, foreignArray);
assert objects.length == 4;
String elem0 = Polyglot.cast(String.class, objects[0]); // eager conversion
Integer elem1 = Polyglot.cast(Integer.class, objects[1]); // preserves identity
int elem1_ = Polyglot.cast(int.class, objects[1]); // eager conversion
double elem2 = Polyglot.cast(double.class, objects[2]); // eager conversion
Object elem3 = objects[3];
assert elem3 == null;
Polyglot.cast(targetClass, obj)
メソッドは、targetClass.cast(obj)
などの拡張Javaキャストです:
- Javaキャストは成功します ⇒
Polyglot.cast
が成功します。 - Javaキャストは成功せず、
Polyglot.cast
は外部オブジェクトを再入力でき、たとえばInteger
にキャストするには、外部オブジェクトがfitsInInt
である必要があります。 Polyglot.cast
が失敗した場合、Class#cast
と同様のClassCastException
をスローします。
Polyglot.cast
は、次に概要を示すように、一般的な相互運用の種類からJava型への自然なマッピングをサポートします:
相互運用の種類 | 使用可能な型 | アイデンティティの保持 |
---|---|---|
isBoolean | Boolean/boolean | はい*(ボックス化型) |
fitsInByte | Byte/byte | はい*(ボックス化型) |
fitsInShort | Short/short | はい*(ボックス化型) |
fitsInInt | Integer/int | はい*(ボックス化型) |
fitsInLong | Long/long | はい*(ボックス化型) |
fitsInFloat | Float/float | はい*(ボックス化型) |
fitsInDouble | Double/double | はい*(ボックス化型) |
isString & 1-character | Character/char | はい*(ボックス化型) |
isString | String | いいえ(即時変換) |
isException & Polyglot.isForeignObject | ForeignException | はい |
hasArrayElements | Object[] | はい |
isNull | * | はい |
* | Object | はい |
ポリグロット・バインディングにアクセスできます:
// guest java
Object foreignObject = Polyglot.importObject("foreign_object");
// Also typed imports
String userName = Polyglot.importObject("user_name", String.class);
int year = Polyglot.importObject("year", int.class);
// And exports
Polyglot.exportObject("data", new double[]{56.77, 59.23, 55.67, 57.50, 64.44, 61.37);
Polyglot.exportObject("message", "Hello, Espresso!");
相互運用プロトコル
Java on Truffleは、相互運用プロトコルにアクセスするための明示的なゲストAPIを提供します。これには、相互運用プロトコル・メッセージを模倣するメソッドが含まれます。このAPIは、ゲストJavaオブジェクトでも使用できます。
// guest java
import com.oracle.truffle.espresso.polyglot.Interop;
Object foreignArray = Polyglot.eval("js", "[2, 0, 2, 1]");
System.out.println(Interop.hasArrayElements(foreignArray)); // prints true
System.out.println(Interop.getArraySize(foreignArray)); // prints 4
Object elem0 = Interop.readArrayElement(foreignArray, 0);
System.out.println(Interop.fitsInInt(elem0)); // prints true
System.out.println(Interop.asInt(elem0)); // prints 2
ホストJavaへの埋込み
Java on Truffleは、GraalVMの一部であるポリグロットAPIを介して埋め込まれます。
// host java
import org.graalvm.polyglot.*;
class Embedding {
public static void main(String[] args) {
Context polyglot = Context.newBuilder().allowAllAccess(true).build();
// Class loading is exposed through language bindings, with class
// names using the same format as Class#forName(String).
Value intArray = polyglot.getBindings("java").getMember("[I");
Value objectArray = polyglot.getBindings("java").getMember("[Ljava.lang.Object;")
Value java_lang_Math = polyglot.getBindings("java").getMember("java.lang.Math");
double sqrt2 = java_lang_Math.invokeMember("sqrt", 2).asDouble();
double pi = java_lang_Math.getMember("PI").asDouble();
System.out.println(sqrt2);
System.out.println(pi);
}
}
contextBuilder.option(key, value)
を使用して、数多くの有用なコンテキスト・オプションを設定できます:
- Javaプロパティは、目的の値への設定
java.Properties.property.name
によって追加できます(この場合はproperty.name
を設定します)。 java.Properties.java.class.path
を使用すると、Java on Truffleコンテキストのクラスパスを設定できます。java.Properties.java.library.path
を使用すると、Java on Truffleコンテキストのネイティブ・ライブラリ・パスを設定できます。java.EnableAssertions
をtrue
に設定すると、アサーションを有効にすることができます。java.EnableSystemAssertions
をtrue
に設定すると、Java標準ライブラリでアサーションを有効にすることができます。java.Verify
をnone
、remove
またはall
に設定して、バイトコード検証が行われないか、ユーザー・コードでのみ行われるか、またはすべてのクラスで行われるかを制御できます。java.JDWPOptions
は、JDWPによるデバッグを設定して有効にするように設定できます。たとえば、transport=dt_socket,server=y,address=localhost:8000,suspend=y
に設定できます。java.Polyglot
をtrue
またはfalse
に設定して、com.oracle.truffle.espresso.polyglot
パッケージのポリグロット機能へのアクセスを許可または拒否できます。java.PolyglotTypeConverters
を設定して、メタ修飾名を型コンバータ・クラスにマップする型変換関数を宣言できます。詳細は、次の専用の項を参照してください。java.PolyglotInterfaceMappings
にセミコロンで区切られた1対1のインタフェース型マッピングのリストを設定すると、リスト内で宣言されたインタフェースを実装するホスト・オブジェクトのゲスト・プロキシが自動的に構築されます。詳細は、次の専用の項を参照してください。
*Java on Truffleは、Javaソースの評価(.eval
)をサポートしていません。
Javaでは、メソッドをオーバーロードでき、たとえば複数のメソッドが異なるシグネチャで同じ名前を共有できます。あいまいさを解消するために、Java on TruffleではmethodName/methodDescriptor
形式でメソッド・ディスクリプタを指定できます:
// host java
Value java_lang_String = polyglot.getBindings("java").getMember("java.lang.String");
// String#valueOf(int)
String valueOf = String.format("%s/%s", "valueOf", "(I)Ljava/lang/String;");
Value fortyTwo = java_lang_String.invokeMember(valueOf, 42);
assert "42".equals(fortyTwo.asString());
Class<?>インスタンスと静的クラス・アクセサ(Klass):
静的クラス・アクセサを使用すると、(public)静的フィールドにアクセスし、(public)静的メソッドをコールできます。
// Class loading through language bindings return the static class accessor.
Value java_lang_Number = polyglot.getBindings("java").getMember("java.lang.Number");
Value java_lang_Class = polyglot.getBindings("java").getMember("java.lang.Class");
// Class#forName(String) returns the Class<Integer> instance.
Value integer_class = java_lang_Class.invokeMember("forName", "java.lang.Integer");
// Static class accessor to Class<?> instance and viceversa.
assert integer_class.equals(java_lang_Integer.getMember("class"));
assert java_lang_Integer.equals(integer_class.getMember("static"));
// Get Integer super class.
assert java_lang_Number.equals(java_lang_Integer.getMember("super"));
型コンバータを使用したホスト・オブジェクトのゲストの型への変換
バージョン22.3.0以降、Java on Truffleには、ホスト・オブジェクトから適切なゲスト型オブジェクトへの型変換を宣言するためのサポートが組み込まれています。これは、前述のコンテキスト・ビルダー・オプションを介して実行されます。主な考え方は、ホスト・オブジェクトが埋込みJava on Truffleコンテキストに入るときに、ゲストの型チェックを実行することなく、ホストからゲストへのオブジェクトの透過的なフローを可能にすることです。具体的には、次のオプションを設定して、埋込みコンテキストの型変換を制御できます:
java.PolyglotTypeConverters
このオプションはjava.PolyglotInterfaceMappings
より優先されるため、専用型コンバータ関数が定義されている場合、その他の自動インタフェース・マッピング・プロキシはJava on Truffleによって生成されません。
ノート: 宣言される型コンバータは、polyglor.jar
のcom.oracle.truffle.espresso.polyglot
パッケージにあるGuestTypeConversion
インタフェースを実装する必要があります。
package com.oracle.truffle.espresso.polyglot;
public interface GuestTypeConversion<T> {
T toGuest(Object polyglotInstance);
}
宣言される型コンバータごとに、次のような1つのオプション・コールを使用します:
// host java
Context polyglot = Context.newBuilder().allowAllAccess(true).
option("java.PolyglotTypeConverters.java.math.BigDecimal", "guest.context.path.BigDecimalConverter").
build();
...
// guest java
package guest.context.path;
import com.oracle.truffle.espresso.polyglot.GuestTypeConversion;
import com.oracle.truffle.espresso.polyglot.Interop;
import com.oracle.truffle.espresso.polyglot.InteropException;
import java.math.BigDecimal;
public class BigDecimalConverter implements GuestTypeConversion<BigDecimal> {
@Override
@SuppressWarnings("unchecked")
public BigDecimal toGuest(Object polyglotInstance) {
try {
return new BigDecimal(Interop.asString(Interop.invokeMember(polyglotInstance, "toString")));
} catch (InteropException e) {
throw new ClassCastException("polyglot instance cannot be cast to java.math.BigDecimal");
}
}
}
オプションのjava.math.Bigdecimal
部分は、Java on Truffleに入るホスト・オブジェクトの完全修飾メタ名を宣言します。
java.PolyglotInterfaceMappings
埋込みJava on Truffleコンテキストにフローするホスト・オブジェクトに専用のjava.PolyglotTypeConverters
がない場合、自動インタフェース型マッピングが機能します。java.PolyglotInterfaceMappings
を使用すると、ホストと埋込みコンテキストの間でシームレスなインタフェース型共有が可能になります。
次の例は、このオプションを使用して、埋込みJava on Truffleコンテキストにインタフェースごとに共通のJDKコレクション・タイプを渡すことができるようにする方法を示しています:
// host java
builder.option("java.PolyglotInterfaceMappings", getInterfaceMappings());
private static String getInterfaceMappings(){
return "java.lang.Iterable;"+
"java.util.Collection;"+
"java.util.List;"+
"java.util.Set;"+
"java.util.Map;"+
"java.util.Iterator;"+
"java.util.Spliterator;";
}
マルチスレッド
Java on Truffleはマルチスレッド言語として設計され、エコシステムの多くが、スレッドを使用できることを想定しています。このことは、スレッドをサポートしていない他のTruffle言語と互換性がない可能性があるため、オプション--java.MultiThreaded=false
を使用して複数のスレッドの作成を無効にできます。
このオプションを有効にすると、ファイナライザは実行されず、ReferenceQueue
通知メカニズムも実行されません。これらの機能には、どちらも新しいスレッドの起動が必要になります。到達可能性が低いオブジェクトのガベージ・コレクションは引き続き影響を受けないことに注意してください。
かわりに、参照処理は特殊なコマンドを使用して手動でトリガーでき、シングルスレッド環境でのみ使用できます。
// Host Java
// Will trigger Reference processing and run finalizers
polyglot.eval("java", "<ProcessReferences>");
このコマンドは、任意のクリーナおよびファイナライザ・コードをトリガーする可能性があります。そのため、これは、スタック上でできるだけ少ないゲストjavaフレームで実行することが理想的です。