Truffle Interop 2.0

このドキュメントは、ゲスト言語およびツール実装者を対象としています。続行する前に、まずTruffle ライブラリチュートリアルを読むことをお勧めします。

動機 #

Truffle バージョン 1.0 RC15 では、「Truffle ライブラリ」と呼ばれる新しい API が導入されました。Truffle ライブラリを使用すると、プロファイリング/キャッシュのサポートを使用してポリモーフィズムを使用できます。Interop 2.0 では、相互運用性プロトコルに Truffle ライブラリを使用する予定です。現在の相互運用性 API は成熟しており、十分にテストされており、すでに言語とツールによって採用されています。

現在の相互運用性 API が変更され、Interop 2.0 が導入された理由のリストを以下に示します。

  • フットプリント: 現在の interop API では、すべてのメッセージ送信は CallTarget を通過し、引数は Object[] にボックス化されます。これにより、現在の interop はインタープリター呼び出しで非効率になり、追加のメモリが必要になります。Truffle ライブラリは、引数の配列ボックス化や呼び出しターゲットを必要としない、単純なノードと型特殊化された呼び出しシグネチャを使用します。
  • キャッシュされていないディスパッチ: 現在の interop メッセージを、一時ノードを割り当てることなくスローパスから実行する方法はありません。Truffle ライブラリは、エクスポートされたすべてのメッセージのキャッシュされていないバージョンを自動的に生成します。これにより、一時データ構造を割り当てることなく、スローパス/ランタイムから interop メッセージを使用できます。
  • 複数のメッセージに対してディスパッチを再利用: 現在の interop では、エクスポートされたメッセージへのディスパッチは、送信されるメッセージごとに繰り返されます。複数のメッセージを送信する必要があり、受信側の型がポリモーフィックになると、これは悪いコードを生成します。Interop ライブラリのインスタンスは、入力値に対して特殊化できます。これにより、ユーザーはディスパッチを 1 回だけ実行し、ディスパッチを繰り返さずに複数のメッセージを呼び出すことができます。これにより、ポリモーフィックなケースでより効率的なコードが生成されます。
  • デフォルト実装のサポート: 現在の interop は、TruffleObject の実装にのみ使用できます。Truffle ライブラリは、任意の受信側の型で使用できます。たとえば、プリミティブ数値で isExecutable メッセージを呼び出すことができ、単に false を返します。
  • エラーの発生しやすさ: Truffle ライブラリが、受信側の型の混同や間違った型チェックの実装など、不可能にすることで回避しようとしているメッセージ解決に関するいくつかの一般的な問題がありました。Truffle ライブラリの新しいアサーション機能を使用すると、不変条件、事前条件、および事後条件を検証できるメッセージ固有のアサーションを指定できます。
  • ドキュメントの冗長性: 現在の interop は、Message 定数と ForeignAccess 静的アクセサーメソッドでメッセージを文書化しています。これにより、ほとんど冗長なドキュメントになります。Truffle interop では、ドキュメントの場所は 1 つだけであり、ライブラリクラスのインスタンスメソッドです。
  • 汎用性: Truffle ライブラリは、メモリ消費とインタープリターのパフォーマンスの点で十分に効率的になったため、言語表現の抽象化に使用できます。現在の interop API は、この問題のために現実的にそのように使用することはできませんでした。
  • プロトコルの問題を解決する: 現在の interop API には、interop 2.0 が解決しようとしているいくつかの設計上の問題があります (後述を参照)。

互換性 #

interop 1.0 から 2.0 への変更は、互換性のある方法で行われました。したがって、古い interop は引き続き機能し、採用は段階的に行うことができます。これは、ある言語がまだ古い interop API を使用して呼び出し、別の言語がすでに新しい interop API を採用している場合、互換性ブリッジが API をマップすることを意味します。これがどのように機能するかについて興味がある場合は、新しい interop 呼び出しから古い interop への DefaultTruffleObjectExports クラスを探してください。また、古い interop 呼び出しから新しい interop への LegacyToLibraryNode を探してください。互換性ブリッジを使用すると、パフォーマンスが低下する可能性があることに注意してください。そのため、言語はできるだけ早く移行する必要があります。

Interop プロトコルの変更 #

Interop 2.0 には、多くのプロトコルの変更が付属しています。このセクションでは、これらの変更の根拠について説明します。詳細なリファレンスドキュメントについては、InteropLibrary Javadoc を参照してください。注: すべての非推奨 API は、Javadoc の @deprecated タグでマークされた移行パスについて説明しています。

IS_BOXED と UNBOX を明示的な型に置き換える #

IS_BOXED/UNBOX 設計にはいくつかの問題があります

  • 値が特定の型 (たとえば、String) であるかどうかを調べるには、最初に値をボックス化解除する必要があります。ボックス化解除はコストのかかる操作である可能性があり、単に値の型をチェックするだけで非効率なコードにつながります。
  • 古い API は、TruffleObject を実装しなかった値には使用できませんでした。したがって、プリミティブ数値の処理を TruffleObject のケースから分離する必要があり、既存のコードを再利用するために UNBOX 設計が必要になりました。Truffle ライブラリは、プリミティブな受信側の型をサポートします。
  • UNBOX の設計は、それが返すプリミティブ型の指定されたセットに依存しています。言語がプリミティブ型を直接参照するため、この方法で追加の新しい interop 型を導入するのは困難です。

InteropLibrary で次の新しいメッセージが置換として導入されました

boolean isBoolean(Object)
boolean asBoolean(Object)
boolean isString(Object)
String  asString(Object)
boolean isNumber(Object)
boolean fitsInByte(Object)
boolean fitsInShort(Object)
boolean fitsInInt(Object)
boolean fitsInLong(Object)
boolean fitsInFloat(Object)
boolean fitsInDouble(Object)
byte asByte(Object)
short asShort(Object)
int asInt(Object)
long asLong(Object)
float asFloat(Object)
double asDouble(Object)

InteropLibrary は、受信側の型 BooleanByteShortIntegerLongFloatDoubleCharacter、および String のデフォルト実装を指定します。この設計は、Java のプリミティブ型が直接使用されなくなったため、大きな数値やカスタムの String 抽象化などの新しい値をサポートするように拡張できます。特殊化でプリミティブ型を直接使用することは推奨されなくなりました。interop プリミティブ型のセットは将来変更される可能性があるためです。代わりに、常に interop ライブラリを使用して特定の型をチェックしてください。たとえば、instanceof Integer の代わりに fitsInInt を使用してください。

新しいメッセージを使用すると、次のように元の UNBOX メッセージをエミュレートできます

@Specialization(limit="5")
Object doUnbox(Object value, @CachedLibrary("value") InteropLibrary interop) {
    if (interop.isBoolean(value)) {
      return interop.asBoolean(value);
    } else if (interop.isString(value)) {
      return interop.asString(value);
    } else if (interop.isNumber(value)) {
      if (interop.fitsInByte(value)) {
        return interop.asByte(value);
      } else if (interop.fitsInShort(value)) {
        return interop.asShort(value);
      } else if (interop.fitsInInt(value)) {
        return interop.asInt(value);
      } else if (interop.fitsInLong(value)) {
        return interop.asLong(value);
      } else if (interop.fitsInFloat(value)) {
        return interop.asFloat(value);
      } else if (interop.fitsInDouble(value)) {
        return interop.asDouble(value);
      }
    }
    throw UnsupportedMessageException.create();
}

注: このようにすべてのプリミティブ型をボックス化解除することは推奨されません。代わりに、言語は実際に使用するプリミティブ型のみをボックス化解除する必要があります。理想的には、ボックス化解除操作は必要なく、interop ライブラリを直接使用して操作を実装します。たとえば、次のようにします

@Specialization(guards = {
                "leftValues.fitsInLong(l)",
                "rightValues.fitsInLong(r)"}, limit="5")
long doAdd(Object l, Object r,
             @CachedLibrary("l") InteropLibrary leftValues,
             @CachedLibrary("r") InteropLibrary rightValues) {
       return leftValues.asLong(l) + rightValues.asLong(r);
}

配列要素とメンバー要素の明示的な名前空間 #

汎用の READ および WRITE メッセージは、もともと主に JavaScript のユースケースを念頭に置いて設計されました。より多くの言語で interop が採用されるにつれて、配列とオブジェクトメンバーの明示的な名前空間が必要であることが明らかになりました。時間の経過とともに、READ および WRITE の解釈は、数値で使用する場合は配列アクセスを、文字列で使用する場合はオブジェクトメンバーアクセスを表すように変更されました。HAS_SIZE メッセージは、値に配列要素が含まれているかどうか、および追加の保証 (たとえば、配列要素がインデックス 0 からサイズの間で反復可能であること) として再解釈されました。

言語間のより優れた interop を実現するには、明示的な Hash/Map/Dictionary エントリの名前空間が必要です。もともと、これには汎用の READ/WRITE 名前空間を再利用する予定でした。JavaScript の場合、辞書とメンバーの名前空間が同等であったため、これは可能でした。ただし、ほとんどの言語では、Map エントリをオブジェクトメンバーから分離しており、これによりあいまいなキーが発生します。ソース言語 (プロトコル実装者) がこの競合をどのように解決する必要があるかを知ることはできません。代わりに、明示的な名前空間を使用することで、ターゲット言語 (プロトコル呼び出し側) があいまいさを解決する方法を決定できるようにすることができます。たとえば、辞書要素とメンバー要素のどちらを優先するかを、ターゲット言語の操作で決定できるようになりました。

次の interop メッセージが変更されました

READ, WRITE, REMOVE, HAS_SIZE, GET_SIZE, HAS_KEYS, KEYS

InteropLibrary のメンバーおよび配列名前空間を分離した更新されたプロトコルは、次のようになります

オブジェクト名前空間

hasMembers(Object)
getMembers(Object, boolean)
readMember(Object, String)
writeMember(Object, String, Object)
removeMember(Object, String)
invokeMember(Object, String, Object...)

配列名前空間

hasArrayElements(Object)
readArrayElement(Object, long)
getArraySize(Object)
writeArrayElement(Object, long, Object)
removeArrayElement(Object, long)

配列アクセスメッセージは、UnknownIdentifierException をスローしなくなりました。代わりに、InvalidArrayIndexException をスローします。これは元の設計のバグであり、アクセスされた数値を UnknownIdentifierException の識別子文字列に変換する必要がありました。

KeyInfo を個別のメッセージに置き換える #

前のセクションでは、KEY_INFO メッセージについて言及しませんでした。KEY_INFO メッセージは、メンバーまたは配列要素のすべてのプロパティを照会するのに役立ちました。これは便利な小さな API でしたが、実装者がすべてのキー情報プロパティを返す必要があったため、非効率であることがよくありました。同時に、呼び出し側がすべてのキー情報プロパティを本当に必要とすることはまれです。Interop 2.0 では、この問題に対処するために、KEY_INFO メッセージを削除し、名前空間ごとに明示的なメッセージを導入しました。

オブジェクト名前空間

isMemberReadable(Object, String)
isMemberModifiable(Object, String)
isMemberInsertable(Object, String)
isMemberRemovable(Object, String)
isMemberInvocable(Object, String)
isMemberInternal(Object, String)
isMemberWritable(Object, String)
isMemberExisting(Object, String)
hasMemberReadSideEffects(Object, String)
hasMemberWriteSideEffects(Object, String)

配列名前空間

isArrayElementReadable(Object, long)
isArrayElementModifiable(Object, long)
isArrayElementInsertable(Object, long)
isArrayElementRemovable(Object, long)
isArrayElementWritable(Object, long)
isArrayElementExisting(Object, long)

注: 配列名前空間では、読み取りまたは書き込みの副作用の照会がサポートされなくなりました。これらのメッセージは再導入される可能性がありますが、現時点ではユースケースはありませんでした。また、配列名前空間では、呼び出しは許可されていません。

TO_NATIVE の戻り値の型の削除 #

TO_NATIVE メッセージは InteropLibrary で toNative に名前が変更され、値が返されなくなり、受信側でサポートされている場合は副作用としてネイティブ遷移を実行するようになりました。これにより、メッセージの呼び出し側はコードを簡素化できます。toNative 遷移が別の値を返す必要があったケースは見つかりませんでした。toNative のデフォルトの動作は、値を返さないように変更されました。

マイナーな変更 #

以下のメッセージはほとんど変更されていません。NEW メッセージは、isInstantiable との一貫性を保つために instantiate に名前が変更されました。

Message.IS_NULL         -> InteropLibrary.isNull
Message.EXECUTE         -> InteropLibrary.execute
Message.IS_INSTANTIABLE -> InteropLibrary.isInstantiable
Message.NEW             -> InteropLibrary.instantiate
Message.IS_EXECUTABLE   -> InteropLibrary.isExecutable
Message.EXECUTE         -> InteropLibrary.execute
Message.IS_POINTER      -> InteropLibrary.isPointer
Message.AS_POINTER      -> InteropLibrary.asPointer

より強力なアサーション #

移行の一環として、多くのアサーションが新たに導入されました。具体的な事前条件、事後条件、不変条件は Javadoc で説明されています。古いインターオペレーションノードとは異なり、キャッシュされたライブラリは AST の一部として採用された場合にのみ使用できます。

チェック例外/非チェック例外の廃止 #

Interop 2.0 では、InteropException.raise は非推奨になりました。可能ではありますが、チェック例外を非チェック例外として再スローすることはアンチパターンと見なされます。Truffle Libraries では、ターゲット言語ノードは呼び出し側の AST に直接挿入されるため、チェック例外をサポートしない制限のある CallTarget はもはや存在しません。Truffle DSL からのチェック例外の追加サポートとともに、raise メソッドを使用する必要はなくなるはずです。代わりに、すべてのインターオペレーション例外タイプに対して新しい create ファクトリメソッドが導入されました。

インターオペレーション例外は常にすぐにキャッチされ、再スローされることはないことを想定しているため、効率を向上させるために、インターオペレーション例外からスタックトレースを削除する予定です。これは互換性レイヤーを削除できるまで延期されました。

移行 #

インターオペレーションに Truffle Libraries を使用することにより、既存のほとんどのインターオペレーション API を非推奨にする必要がありました。以下の Interop 1.0 と Interop 2.0 の比較は、既存のインターオペレーションの使用法を移行するのに役立つように設計されています。

インターオペレーションメッセージの高速パス送信 #

これは、操作ノードに埋め込まれたインターオペレーションメッセージを送信する高速パスの方法です。これは、インターオペレーションメッセージを送信する最も一般的な方法です。

Interop 1.0

@ImportStatic({Message.class, ForeignAccess.class})
abstract static class ForeignExecuteNode extends Node {

    abstract Object execute(Object function, Object[] arguments);

    @Specialization(guards = "sendIsExecutable(isExecutableNode, function)")
    Object doDefault(TruffleObject function, Object[] arguments,
                    @Cached("IS_EXECUTABLE.createNode()") Node isExecutableNode,
                    @Cached("EXECUTE.createNode()") Node executeNode) {
        try {
            return ForeignAccess.sendExecute(executeNode, function, arguments);
        } catch (UnsupportedTypeException | ArityException | UnsupportedMessageException e) {
            // ... convert errors to guest language errors ...
        }
    }
}

Interop 2.0

abstract static class ForeignExecuteNode extends Node {

    abstract Object execute(Object function, Object[] arguments);

    @Specialization(guards = "functions.isExecutable(function)", limit = "2")
    Object doDefault(Object function, Object[] arguments,
                    @CachedLibrary("function") InteropLibrary functions) {
        try {
            return functions.execute(function, arguments);
        } catch (UnsupportedTypeException | ArityException | UnsupportedMessageException e) {
            // ... convert errors to guest language errors ...
        }
    }
}

次の違いに注意してください

  • メッセージを呼び出すには、ForeignAccess の静的メソッドを呼び出す代わりに、TruffleLibrary のインスタンスメソッドを呼び出します。
  • 古いインターオペレーションでは、操作ごとに 1 つのノードを作成する必要がありました。新しいバージョンでは、特殊化されたインターオペレーションライブラリが 1 つだけ作成されます。
  • 古い API では、TruffleObject の受信側タイプを特殊化する必要がありました。新しいインターオペレーションライブラリは、任意のインターオペレーション値で呼び出すことができます。デフォルトでは、インターオペレーションライブラリをエクスポートしない値の場合、isExecutablefalse を返します。たとえば、ボックス化されたプリミティブ受信側値でライブラリを呼び出すことが有効になりました。
  • 古いインターオペレーションで @Cached を使用する代わりに、新しいインターオペレーションでは @CachedLibrary を使用します。
  • 新しい @CachedLibrary アノテーションは、ライブラリが特殊化される値を指定します。これにより、DSL はライブラリインスタンスをその値に特殊化できます。これにより、受信側値のディスパッチがすべてのメッセージ呼び出しに対して 1 回だけ実行されるようになります。古いインターオペレーションバージョンでは、ノードを値に特殊化できませんでした。したがって、ディスパッチはすべてのインターオペレーションメッセージ送信ごとに繰り返す必要がありました。
  • 特殊化されたライブラリインスタンスでは、特殊化メソッドの limit を指定する必要があります。この制限がオーバーフローした場合、プロファイリング/キャッシュを実行しないライブラリのキャッシュされていないバージョンが使用されます。古いインターオペレーション API は、インターオペレーションノードごとに 8 の定数特殊化制限を想定していました。
  • 新しいインターオペレーション API では、@CachedLibrary(limit="2") を代わりに指定することにより、ディスパッチされたバージョンのライブラリを使用できます。これにより、インターオペレーションライブラリは任意の値で使用できますが、古いインターオペレーション API のように、すべてのメッセージ呼び出しに対してインラインキャッシュが複製されるという欠点があります。したがって、可能な場合は常に特殊化されたライブラリを使用することをお勧めします。

インターオペレーションメッセージの低速パス送信 #

ノードのコンテキストなしで、ランタイムからインターオペレーションメッセージを呼び出す必要がある場合があります。

Interop 1.0

ForeignAccess.sendRead(Message.READ.createNode(), object, "property")

Interop 2.0

InteropLibrary.getFactory().getUncached().read(object, "property");

次の違いに注意してください

  • 古いインターフェースは、呼び出しごとにノードを割り当てていました。
  • 新しいライブラリは、呼び出しごとに割り当てやボックス化を必要としない、ライブラリのキャッシュされていないバージョンを使用します。
  • InteropLibrary.getFactory().getUncached(object) を使用すると、キャッシュされていない特殊化されたバージョンのライブラリをルックアップできます。これは、同じ受信側に複数のキャッシュされていないインターオペレーションメッセージを送信する必要がある場合に、繰り返しエクスポートをルックアップするのを回避するために使用できます。

カスタム高速パスインターオペレーションメッセージ送信 #

Truffle DSL を使用できず、ノードを手動で記述する必要がある場合があります。どちらの API も、それを行うことができます

Interop 1.0


final class ForeignExecuteNode extends Node {

    @Child private Node isExecutableNode = Message.IS_EXECUTABLE.createNode();
    @Child private Node executeNode = Message.EXECUTE.createNode();

    Object execute(Object function, Object[] arguments) {
        if (function instanceof TruffleObject) {
            TruffleObject tFunction = (TruffleObject) function;
            if (ForeignAccess.sendIsExecutable(isExecutableNode, tFunction)) {
                try {
                    return ForeignAccess.sendExecute(executeNode, tFunction, arguments);
                } catch (UnsupportedTypeException | ArityException | UnsupportedMessageException e) {
                    // TODO handle errors
                }
            }
        }
        // throw user error
    }
}

Interop 2.0

static final class ForeignExecuteNode extends Node {

    @Child private InteropLibrary functions = InteropLibrary.getFactory().createDispatched(5);

    Object execute(Object function, Object[] arguments) {
        if (functions.isExecutable(function)) {
            try {
                return functions.execute(function, arguments);
            } catch (UnsupportedTypeException | ArityException | UnsupportedMessageException e) {
                // handle errors
                return null;
            }
        }
        // throw user error
    }
}

次の違いに注意してください

  • 新しいインターオペレーションは、InteropLibrary.getFactory() を介してアクセスできる LibraryFactory<InteropLibrary> を介してノードを作成します。古いインターオペレーションは、Message インスタンスを介してディスパッチノードを作成します。
  • 新しいインターオペレーションライブラリには、ディスパッチ制限を指定できます。古いインターオペレーション API は、常に 8 の定数制限を想定していました。
  • 新しいインターオペレーションでは、TruffleObject 型を確認する必要はありません。Truffle Libraries は任意の受信側タイプで使用できるためです。非関数値の場合、isExecutable は単に false を返します。

インターオペレーションメッセージの実装/エクスポート #

インターオペレーションライブラリメッセージを実装/エクスポートするには、次の例を参照してください。

Interop 1.0

@MessageResolution(receiverType = KeysArray.class)
final class KeysArray implements TruffleObject {

    private final String[] keys;

    KeysArray(String[] keys) {
        this.keys = keys;
    }

    @Resolve(message = "HAS_SIZE")
    abstract static class HasSize extends Node {

        public Object access(KeysArray receiver) {
            return true;
        }
    }

    @Resolve(message = "GET_SIZE")
    abstract static class GetSize extends Node {

        public Object access(KeysArray receiver) {
            return receiver.keys.length;
        }
    }

    @Resolve(message = "READ")
    abstract static class Read extends Node {

        public Object access(KeysArray receiver, int index) {
            try {
                return receiver.keys[index];
            } catch (IndexOutOfBoundsException e) {
                CompilerDirectives.transferToInterpreter();
                throw UnknownIdentifierException.raise(String.valueOf(index));
            }
        }
    }

    @Override
    public ForeignAccess getForeignAccess() {
        return KeysArrayForeign.ACCESS;
    }

    static boolean isInstance(TruffleObject array) {
        return array instanceof KeysArray;
    }
}

Interop 2.0

@ExportLibrary(InteropLibrary.class)
final class KeysArray implements TruffleObject {

    private final String[] keys;

    KeysArray(String[] keys) {
        this.keys = keys;
    }

    @ExportMessage
    boolean hasArrayElements() {
        return true;
    }

    @ExportMessage
    boolean isArrayElementReadable(long index) {
        return index >= 0 && index < keys.length;
    }

    @ExportMessage
    long getArraySize() {
        return keys.length;
    }

    @ExportMessage
    Object readArrayElement(long index) throws InvalidArrayIndexException {
        if (!isArrayElementReadable(index) {
            throw InvalidArrayIndexException.create(index);
        }
        return keys[(int) index];
    }
}

次の違いに注意してください

  • @MessageResolution の代わりに @ExportLibrary を使用します。
  • どちらのバージョンも TruffleObject を実装する必要があります。新しいインターオペレーション API では、互換性の理由から TruffleObject タイプのみが必要です。
  • @Resolve の代わりに、@ExportMessage アノテーションを使用します。後者のアノテーションは、メソッド名からメッセージの名前を推測できます。複数のライブラリがエクスポートされている場合など、メソッド名が曖昧な場合は、名前とライブラリを明示的に指定できます。
  • エクスポート/解決にクラスを指定する必要はありません。ただし、エクスポートに複数の特殊化が必要な場合は、それでも指定できます。詳細については、Truffle Library のチュートリアルを参照してください。
  • 例外はチェック例外としてスローされるようになりました。
  • getForeignAccess() を実装する必要はなくなりました。実装では、受信側タイプの実装が自動的に検出されます。
  • isInstance を実装する必要はなくなりました。実装は、クラス署名から派生するようになりました。受信側タイプが final として宣言されている場合、チェックはより効率的になる可能性があることに注意してください。非 final 受信側タイプの場合は、エクスポートされたメソッドを final として指定することをお勧めします。

DynamicObject との統合 #

古いインターオペレーションでは、ObjectType.getForeignAccessFactory() を介して外部アクセスファクトリを指定できました。このメソッドは非推奨になり、新しいメソッド ObjectType.dispatch() が導入されました。外部アクセスファクトリの代わりに、ディスパッチメソッドは、明示的な受信側で InteropLibrary をエクスポートするクラスを返す必要があります

Interop 1.0

public final class SLObjectType extends ObjectType {

    public static final ObjectType SINGLETON = new SLObjectType();

    private SLObjectType() {
    }

    public static boolean isInstance(TruffleObject obj) {
        return SLContext.isSLObject(obj);
    }

    @Override
    public ForeignAccess getForeignAccessFactory(DynamicObject obj) {
        return SLObjectMessageResolutionForeign.ACCESS;
    }
}

@MessageResolution(receiverType = SLObjectType.class)
public class SLObjectMessageResolution {

    @Resolve(message = "WRITE")
    public abstract static class SLForeignWriteNode extends Node {...}

    @Resolve(message = "READ")
    public abstract static class SLForeignReadNode extends Node {...}
    ...

Interop 2.0

@ExportLibrary(value = InteropLibrary.class, receiverType = DynamicObject.class)
public final class SLObjectType extends ObjectType {

    public static final ObjectType SINGLETON = new SLObjectType();

    private SLObjectType() {
    }

    @Override
    public Class<?> dispatch() {
        return SLObjectType.class;
    }

    @ExportMessage
    static boolean hasMembers(DynamicObject receiver) {
        return true;
    }

    @ExportMessage
    static boolean removeMember(DynamicObject receiver, String member) throws UnknownIdentifierException {...}
    // other exports omitted
 }

次の違いに注意してください

  • オブジェクトタイプをエクスポートクラスとして再利用できます。
  • isInstance メソッドを指定する必要はなくなりました。
  • 新しいインターオペレーションでは、DynamicObject に受信側タイプを指定する必要があります。

インターオペレーションの拡張 #

Truffle で実装された言語はインターオペレーションを拡張する必要はめったにありませんが、独自の言語固有のプロトコルを拡張する必要がある場合があります

Interop 1.0

  • FooBar という名前の新しい KnownMessage サブクラスを追加します。
  • ForeignAccess に新しいメソッド sendFooBar を追加します。
  • ForeignAccess.Factory に新しいメソッド createFooBar を追加します。
  • インターオペレーションアノテーションプロセッサを修正して、createFooBar のコードを生成します。

Interop 2.0

  • InteropLibrary に新しいメソッド fooBar を追加します。その他はすべて自動的に行われます。

お問い合わせ