Truffle言語との相互運用性

Espressoを使用すると、他の「Truffle」言語(インタープリターがTruffleフレームワークで実装されている言語)をインターフェースして、ポリグロットプログラム、つまり複数の言語で記述されたプログラムを作成できます。

このガイドでは、他の言語で記述されたコードをロードする方法、言語間でオブジェクトをエクスポートおよびインポートする方法、外国語からEspressoオブジェクトを使用する方法、Espressoから外国オブジェクトを使用する方法、Javaアプリケーションに埋め込む方法について説明します。

混乱を避けるため、Javaが実行される異なるレイヤーを区別するために、ホストゲストという用語を使用します。Espressoはゲストレイヤーを指します。

ポリグロットオプションは、java -truffleランチャーに渡します。ネイティブ構成を使用している場合は、他の言語にアクセスするために--polyglotフラグを使用する必要があります。

外部オブジェクトは、Espressoに流入する際にゲストJava型を「占有」する必要があります。この型が外部オブジェクトにどのようにアタッチされるかは、実装の詳細です。

ポリグロット #

Espressoは、polyglot.jarで説明されているゲストJava Polyglot 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)メソッドは、拡張されたJavaキャストです。たとえば、targetClass.cast(obj)です。

  • 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/char はい*(ボックス化された型)
isString String いいえ(即時変換)
isException & Polyglot.isForeignObject ForeignException はい
hasArrayElements Object[] はい
isNull * はい
* Object はい

ポリグロットバインディングにアクセスできます

// guest java
Object foreignObject = Polyglot.importObject("foreign_object");

// typed imports
String userName = Polyglot.importObject("user_name", String.class);
int year = Polyglot.importObject("year", int.class);

// exports
Polyglot.exportObject("data", new double[]{56.77, 59.23, 55.67, 57.50, 64.44, 61.37);
Polyglot.exportObject("message", "Hello, Espresso!");

相互運用プロトコル #

Espressoは、相互運用プロトコルにアクセスするための明示的なゲスト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への埋め込み #

Espressoは、GraalVMの一部であるPolyglot 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を使用して、Truffleコンテキスト上のJavaのクラスパスを設定できます。
  • java.Properties.java.library.pathを使用して、Truffleコンテキスト上のJavaのネイティブライブラリパスを設定できます。
  • java.EnableAssertionstrueに設定してアサーションを有効にできます。
  • java.EnableSystemAssertionstrueに設定して、Java標準ライブラリのアサーションを有効にできます。
  • java.Verifynoneremove、またはallに設定して、バイトコード検証が実行されないか、ユーザーコードでのみ実行されるか、すべてのクラスで実行されるかを制御できます。
  • java.JDWPOptionsを、JDWP経由でのデバッグをセットアップして有効にするように設定できます。たとえば、transport=dt_socket,server=y,address=localhost:8000,suspend=yに設定できます。
  • java.Polyglottrueまたはfalseに設定して、com.oracle.truffle.espresso.polyglotパッケージからのポリグロット機能へのアクセスを許可または拒否できます。
  • java.PolyglotTypeConvertersを設定して、メタ修飾名を型コンバータークラスにマッピングする型変換関数を宣言できます。詳細については、以下の専用セクションを参照してください。
  • java.PolyglotInterfaceMappingsを、セミコロンで区切られた1対1のインターフェース型マッピングのリストに設定して、リストで宣言されたインターフェースを実装するホストオブジェクトに対してゲストプロキシを自動的に構築できます。詳細については、以下の専用セクションを参照してください。

*Espressoは、Javaソースの評価(.eval)をサポートしていません。

Javaでは、メソッドをオーバーロードできます。たとえば、複数のメソッドが異なるシグネチャで同じ名前を共有できます。あいまいさを解消するために、Espressoでは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 vice versa.
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"));

型コンバーターを使用してホストオブジェクトをゲスト型に変換する #

Espressoには、ホストオブジェクトから適切なゲスト型オブジェクトへの型変換を宣言するための組み込みサポートがあります。これは、上記のようにコンテキストビルダーオプションを介して行われます。主なアイデアは、ホストオブジェクトが埋め込まれたEspressoコンテキストに入るときにゲスト型チェックを実行することなく、ホストからゲストへのオブジェクトの透過的なフローを可能にすることです。具体的には、次のオプションを設定して、埋め込みコンテキストの型変換を制御できます。

java.PolyglotTypeConverters

このオプションは、java.PolyglotInterfaceMappingsよりも優先されます。したがって、専用の型変換関数が定義されている場合、他の自動インターフェースマッピングプロキシはEspressoによって生成されません。

注:宣言された型コンバーターは、polyglor.jarcom.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の部分は、Espressoに入るホストオブジェクトの完全修飾メタ名を宣言します。

java.PolyglotInterfaceMappings

埋め込まれたEspressoコンテキストに流入するホストオブジェクトに専用のjava.PolyglotTypeConvertersがない場合、自動インターフェース型マッピングが開始されます。java.PolyglotInterfaceMappingsは、ホストと埋め込みコンテキスト間のシームレスなインターフェース型共有を可能にします。

次の例は、このオプションを使用して、一般的なJDKコレクション型をインターフェースによって埋め込まれたEspressoコンテキストに渡せるようにする方法を示しています

// 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;";
}

マルチスレッド #

Espressoはマルチスレッド言語として設計されており、エコシステムの多くはスレッドが利用可能であることを期待しています。これは、スレッドをサポートしない他のTruffle言語と互換性がない可能性があるため、--java.MultiThreaded=falseオプションを使用して複数のスレッドの作成を無効にすることができます。

このオプションが有効になっている場合、ファイナライザーは実行されず、ReferenceQueue通知メカニズムも実行されません。これらの両方の機能では、新しいスレッドを開始する必要があります。弱く到達可能なオブジェクトのガベージコレクションは影響を受けないことに注意してください。

代わりに、参照処理は、シングルスレッド環境でのみ使用可能な特別なコマンドを介して手動でトリガーできます。

// Host Java
// Will trigger Reference processing and run finalizers
polyglot.eval("java", "<ProcessReferences>");

このコマンドは、任意のクリーナーコードおよびファイナライザーコードをトリガーする可能性があることに注意してください。したがって、これは、スタック上にできるだけ少ないゲストJavaフレームがある状態で実行するのが理想的です。

お問い合わせ