動的オブジェクト・モデル

このガイドでは、GraalVM 20.2.0で導入されたDynamicObject APIおよびDynamicObjectLibrary APIの使用を開始する方法について説明します。詳細なドキュメントはJavadocを参照してください。

使用する意義

動的言語を実装する場合、ユーザー定義オブジェクト/クラスのオブジェクト・レイアウトは静的に推測できないことが多く、動的に追加されたメンバーおよび型の変更に対応する必要があります。このような場合は、動的オブジェクトAPIを使用して、オブジェクト・レイアウトを処理し、オブジェクトをそのシェイプ、つまりプロパティや値の型によって分類します。これにより、アクセス・ノードは、出現したシェイプをキャッシュし、負荷のかかるチェックを実行して、オブジェクト・プロパティにより効率的にアクセスできます。

スタート・ガイド

ゲスト言語には、DynamicObjectを拡張してTruffleObjectを実装する、すべての言語オブジェクトに共通のベース・クラスが必要です。たとえば:

@ExportLibrary(InteropLibrary.class)
public class BasicObject extends DynamicObject implements TruffleObject {

    public BasicObject(Shape shape) {
        super(shape);
    }

    @ExportMessage
    boolean hasLanguage() {
        return true;
    }
    // ...
}

このクラスで一般的なInteropLibraryメッセージをエクスポートすることも理にかなっています。

これにより、組込みオブジェクト・クラスは、このベース・クラスを拡張して、追加メッセージおよび通常どおり追加のJavaフィールドおよびメソッドをエクスポートできます:

@ExportLibrary(InteropLibrary.class)
public class Array extends BasicObject {

    private final Object[] elements;

    public Array(Shape shape, Object[] elements) {
        super(shape);
        this.elements = elements;
    }

    @ExportMessage
    boolean hasArrayElements() {
        return true;
    }

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

動的オブジェクト・メンバーには、DynamicObjectLibraryを使用してアクセスできます。これは、Truffle DSLの@CachedLibrary注釈と、DynamicObjectLibrary.getFactory() + getUncached()create(DynamicObject)およびcreateDispatched(int)を使用して取得できます。これを使用してInteropLibraryメッセージを実装する方法の例を次に示します:

@ExportLibrary(InteropLibrary.class)
public class SimpleObject extends BasicObject {

    public UserObject(Shape shape) {
        super(shape);
    }

    @ExportMessage
    boolean hasMembers() {
        return true;
    }

    @ExportMessage
    Object readMember(String name,
                    @CachedLibrary("this") DynamicObjectLibrary objectLibrary)
                    throws UnknownIdentifierException {
        Object result = objectLibrary.getOrDefault(this, name, null);
        if (result == null) {
            /* Property does not exist. */
            throw UnknownIdentifierException.create(name);
        }
        return result;
    }

    @ExportMessage
    void writeMember(String name, Object value,
                    @CachedLibrary("this") DynamicObjectLibrary objectLibrary) {
        objectLibrary.put(this, name, value);
    }

    @ExportMessage
    boolean isMemberReadable(String member,
                    @CachedLibrary("this") DynamicObjectLibrary objectLibrary) {
        return objectLibrary.containsKey(this, member);
    }
    // ...
}

これらのオブジェクトのインスタンスを構築するには、まずDynamicObjectコンストラクタに渡すことができるShapeが必要です。このシェイプは、Shape.newBuilder().build()を使用して作成されます。返されるシェイプは、オブジェクトの初期シェイプを記述し、新しいシェイプ・ツリーのルートを形成します。DynamicObjectLibrary#putを使用して新しいプロパティを追加すると、オブジェクトはこのシェイプ・ツリーの他のシェイプに変更されます。

ノート: シェイプはルート・シェイプごとに内部的にキャッシュされるため、同じ初期シェイプを再利用する必要があります。初期シェイプは、同じエンジンのコンテキスト間で共有できるように、TruffleLanguageインスタンスに格納することをお薦めします。シングルトン(null値など)を除いて、静的シェイプは避けてください。

たとえば:

@TruffleLanguage.Registration(...)
public final class MyLanguage extends TruffleLanguage<MyContext> {

    private final Shape initialObjectShape;
    private final Shape initialArrayShape;

    public MyLanguage() {
        this.initialObjectShape = Shape.newBuilder(ExtendedObject.class).build();
        this.initialArrayShape = Shape.newBuilder().build();
    }

    public createObject() {
        return new MyObject(initialObjectShape);
    }
    //...
}

拡張オブジェクト・レイアウト

動的オブジェクト・モデルに渡す追加の動的フィールドを使用してデフォルトのオブジェクト・レイアウトを拡張するには、サブクラスにObject型またはlong型の@DynamicField注釈付きフィールド宣言を追加し、Shape.newBuilder().layout(ExtendedObject.class).build();を使用してレイアウト・クラスを指定します。このクラスとそのスーパークラスで宣言された動的フィールドは、動的オブジェクト・プロパティを格納するために自動的に使用され、この予約済領域に収まるプロパティに高速にアクセスできるようになります。ノート: 動的フィールドに直接アクセスしないでください。そのためには、常にDynamicObjectLibraryを使用してください。

@ExportLibrary(InteropLibrary.class)
public class ExtendedObject extends SimpleObject {

    @DynamicField private Object _obj0;
    @DynamicField private Object _obj1;
    @DynamicField private Object _obj2;
    @DynamicField private long _long0;
    @DynamicField private long _long1;
    @DynamicField private long _long2;

    public ExtendedObject(Shape shape) {
        super(shape);
    }
}

キャッシングに関する考慮事項

最適なキャッシングを実現するには、複数の独立した操作(getputなど)に、キャッシュされた同じDynamicObjectLibraryを再利用しないようにしてください。キャッシュされた各ライブラリ・インスタンスに表示される様々なシェイプおよびプロパティ・キーの数を最小限に抑えるようにしてください。プロパティ・キーが静的に既知の場合(compilation-final)は、常にプロパティ・キーごとに個別のDynamicObjectLibraryを使用します。複数のプロパティを連続して配置する場合は、ディスパッチ済ライブラリ(@CachedLibrary(limit=...))を使用します。たとえば:

public abstract class MakePairNode extends BinaryExpressionNode {
    @Specialization
    Object makePair(Object left, Object right,
                    @CachedLanguage MyLanguage language,
                    @CachedLibrary(limit = "3") putLeft,
                    @CachedLibrary(limit = "3") putRight) {
        MyObject obj = language.createObject();
        putLeft.put(obj, "left", left);
        putRight.put(obj, "right", right);
        return obj;
    }
}

その他の情報

オブジェクト・モデルの概要は、『An Object Storage Model for the Truffle Language Implementation Framework』に記載されています。

TruffleおよびGraalVMに関するその他のプレゼンテーションおよび資料は、Truffleの資料を参照してください。