動的オブジェクトモデル

このガイドでは、GraalVM 20.2.0で導入されたDynamicObjectDynamicObjectLibrary 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().layout(ExtendedObject.class).build();
        this.initialArrayShape = Shape.newBuilder().build();
    }

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

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

デフォルトのオブジェクトレイアウトは、サブクラスに`@DynamicField`アノテーション付きの`Object`または`long`型のフィールド宣言を追加し、`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);
    }
}

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

最適なキャッシングを確保するために、複数の独立した操作(`get`、`put`など)に同じキャッシュされた`DynamicObjectLibrary`を再利用しないでください。各キャッシュされたライブラリインスタンスによって認識される異なる形状とプロパティキーの数を最小限に抑えるようにしてください。プロパティキーが静的に(コンパイル最終的に)わかっている場合は、常にプロパティキーごとに個別の`DynamicObjectLibrary`を使用してください。複数のプロパティを連続して配置する場合は、ディスパッチされたライブラリ(`@CachedLibrary(limit=...)`)を使用してください。例えば

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

参考文献 #

オブジェクトモデルの概要は、Truffle言語実装フレームワークのオブジェクトストレージモデルで公開されています。

TruffleとGraalVMに関するその他のプレゼンテーションと出版物については、Truffleの出版物を参照してください。

お問い合わせ