- JDK 23 用 GraalVM (最新)
- JDK 24 用 GraalVM (早期アクセス)
- JDK 21 用 GraalVM
- JDK 17 用 GraalVM
- アーカイブ
- 開発ビルド
- Truffle 言語実装フレームワーク
- Truffle ブランチインストルメンテーション
- 動的オブジェクトモデル
- 静的オブジェクトモデル
- インタープリターコードのホスト最適化
- 関数のインライン化に対する Truffle のアプローチ
- Truffle インタープリターのプロファイリング
- Truffle Interop 2.0
- 言語実装
- Truffle を使用した新しい言語の実装
- Java モジュールへの Truffle 言語とインストルメントの移行
- Truffle ネイティブ関数インターフェース
- Truffle インタープリターの最適化
- オプション
- オンスタック置換
- Truffle 文字列ガイド
- 特殊化ヒストグラム
- DSL 特殊化のテスト
- ポリグロット API ベースの TCK
- コンパイルキューに対する Truffle のアプローチ
- Truffle ライブラリガイド
- Truffle AOT の概要
- Truffle AOT コンパイル
- 補助エンジンキャッシュ
- Truffle 言語セーフポイントチュートリアル
- 単相化
- 分割アルゴリズム
- 単相化の使用例
- ポリモーフィック特殊化をランタイムに報告
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
は、受信側の型 Boolean
、Byte
、Short
、Integer
、Long
、Float
、Double
、Character
、および 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
の受信側タイプを特殊化する必要がありました。新しいインターオペレーションライブラリは、任意のインターオペレーション値で呼び出すことができます。デフォルトでは、インターオペレーションライブラリをエクスポートしない値の場合、isExecutable
はfalse
を返します。たとえば、ボックス化されたプリミティブ受信側値でライブラリを呼び出すことが有効になりました。 - 古いインターオペレーションで
@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
を追加します。その他はすべて自動的に行われます。