动态对象模型

本指南演示了如何开始使用 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 注解的 Objectlong 类型的字段声明,并使用 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;
    }
}

延伸阅读 #

对象模型的高层描述已在 An Object Storage Model for the Truffle Language Implementation Framework 中发布。

有关 Truffle 和 GraalVM 的更多演示文稿和出版物,请参阅 Truffle 出版物

联系我们