動態物件模型

本指南示範如何開始使用 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);
    }
    // ...
}

為了建構這些物件的執行個體,您首先需要一個 Shape,您可以將其傳遞至 DynamicObject 建構函式。此形狀是使用 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);
    }
    //...
}

擴充物件配置 #

您可以使用額外的動態欄位來擴充預設的物件配置,方法是在子類別中加入類型為 Objectlong@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。盡可能減少每個快取函式庫執行個體看到的形狀和屬性索引鍵數量。當屬性索引鍵是靜態 (編譯最終) 時,請務必為每個屬性索引鍵使用個別的 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 出版物

與我們聯繫