动态对象模型

本指南演示了如何开始使用 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;
    }
}

进一步阅读 #

对象模型的高级描述已在 面向 Truffle 语言实现框架的对象存储模型 中发布。

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

与我们联系