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フレームで実行することが理想的です。