- 适用于 JDK 24 的 GraalVM(最新)
- 适用于 JDK 25 的 GraalVM(早期访问)
- 适用于 JDK 21 的 GraalVM
- 适用于 JDK 17 的 GraalVM
- 存档
- 开发构建
- Truffle 语言实现框架
- Truffle 分支插桩
- 动态对象模型
- 静态对象模型
- 解释器代码的主机优化
- Truffle 函数内联方法
- 分析 Truffle 解释器
- Truffle 互操作 2.0
- 语言实现
- 使用 Truffle 实现新语言
- Truffle 语言和工具迁移到 Java 模块
- Truffle 原生函数接口
- 优化 Truffle 解释器
- 选项
- 栈上替换
- Truffle 字符串指南
- 特化直方图
- 测试 DSL 特化
- 基于多语言 API 的 TCK
- Truffle 编译队列方法
- Truffle 库指南
- Truffle AOT 概述
- Truffle AOT 编译
- 辅助引擎缓存
- Truffle 语言安全点教程
- 单态化
- 拆分算法
- 单态化用例
- 向运行时报告多态特化
静态对象模型
本指南演示了如何开始使用 GraalVM 21.3.0 中引入的 StaticShape 和 StaticProperty API。完整文档可在 Javadoc 中找到。
动机 #
静态对象模型提供了抽象来表示对象的布局,一旦定义,其属性的数量和类型就不会改变。它特别适用于(但不限于)静态编程语言对象模型的实现。其 API 定义了对象布局 (StaticShape)、执行属性访问 (StaticProperty) 和分配静态对象 (DefaultStaticObjectFactory)。该实现高效,并对属性访问执行安全检查,如果这些检查已由语言实现(例如由验证器)执行,则可以禁用它们。
静态对象模型不提供用于建模属性可见性的构造,也不区分静态属性和实例属性。它的 API 与 动态对象模型 的 API 不兼容,后者更适合动态语言。
入门 #
在本例中,假设
- language是我们正在实现的 TruffleLanguage 的一个实例。
- 我们希望表示具有以下静态布局的对象- 一个名为 property1的int属性。
- 一个名为 property2的Object属性,可以作为 final 字段存储。稍后我们将详细了解这意味着什么。
 
- 一个名为 
以下是如何使用静态对象模型来表示此布局
public class GettingStarted {
    public void simpleShape(TruffleLanguage<?> language) {
        StaticShape.Builder builder = StaticShape.newBuilder(language);
        StaticProperty p1 = new DefaultStaticProperty("property1");
        StaticProperty p2 = new DefaultStaticProperty("property2");
        builder.property(p1, int.class, false);
        builder.property(p2, Object.class, true);
        StaticShape<DefaultStaticObjectFactory> shape = builder.build();
        Object staticObject = shape.getFactory().create();
        ...
    }
}
我们首先创建一个 StaticShape.Builder 实例,并传入我们正在实现的语言的引用。然后,我们创建 DefaultStaticProperty 实例,它们表示我们希望添加到静态对象布局中的属性。作为参数传入的字符串 id 在构建器中必须是唯一的。创建属性后,我们将它们注册到构建器实例中
- 第一个参数是我们注册的 StaticProperty。
- 第二个参数是属性的类型。它可以是基本类型或 Object.class。
- 第三个参数是一个布尔值,定义该属性是否可以作为 final 字段存储。这为编译器提供了执行额外优化的机会。例如,对该属性的读取可能会进行常量折叠。需要注意的是,静态对象模型不检查作为 final 存储的属性是否只被赋值一次,以及在读取之前是否已赋值。这样做可能导致程序行为错误,这取决于用户来确保这种情况不会发生。然后我们通过调用 builder.build()创建一个新的静态形状。为了分配静态对象,我们从形状中检索 DefaultStaticObjectFactory,并调用其create()方法。
现在我们有了静态对象实例,让我们看看如何使用静态属性执行属性访问。扩展上面的例子
public class GettingStarted {
    public void simpleShape(TruffleLanguage<?> language) {
        ...
        p1.setInt(staticObject, 42);
        p2.setObject(staticObject, "42");
        assert p1.getInt(staticObject) == 42;
        assert p2.getObject(staticObject).equals("42");
    }
}
形状层次结构 #
通过声明一个新形状应该扩展现有形状,可以创建形状层次结构。这可以通过在创建子形状时将父形状作为参数传递给 StaticShape.Builder.build(StaticShape) 来完成。然后,可以使用父形状的属性来访问存储在子形状静态对象中的值。
在下面的示例中,我们创建一个与 上一节 讨论的父形状相同的形状,然后用一个隐藏父形状某个属性的子形状来扩展它。最后,我们演示了如何访问各种属性。
public class Subshapes {
    public void simpleSubShape(TruffleLanguage<?> language) {
        // Create a shape
        StaticShape.Builder b1 = StaticShape.newBuilder(language);
        StaticProperty s1p1 = new DefaultStaticProperty("property1");
        StaticProperty s1p2 = new DefaultStaticProperty("property2");
        b1.property(s1p1, int.class, false).property(s1p2, Object.class, true);
        StaticShape<DefaultStaticObjectFactory> s1 = b1.build();
        // Create a sub-shape
        StaticShape.Builder b2 = StaticShape.newBuilder(language);
        StaticProperty s2p1 = new DefaultStaticProperty("property1");
        b2.property(s2p1, int.class, false);
        StaticShape<DefaultStaticObjectFactory> s2 = b2.build(s1); // passing a shape as argument builds a sub-shape
        // Create a static object for the sub-shape
        Object o2 = s2.getFactory().create();
        // Perform property accesses
        s1p1.setInt(o2, 42);
        s1p2.setObject(o2, "42");
        s2p1.setInt(o2, 24);
        assert s1p1.getInt(o2) == 42;
        assert s1p2.getObject(o2).equals("42");
        assert s2p1.getInt(o2) == 24;    }
}
扩展自定义基类 #
为了减少内存占用,语言实现者可能希望静态对象扩展代表客户端级别对象的类。这很复杂,因为 StaticShape.getFactory() 必须返回一个分配静态对象的工厂类的实例。为了实现这一点,我们首先需要声明一个接口,该接口
- 为我们想要调用的静态对象超类的每个可见构造函数定义一个方法。
- 每个方法的参数必须与相应构造函数的参数匹配。
- 每个方法的返回类型必须可从静态对象超类分配。
例如,如果静态对象应该扩展这个类
public abstract class MyStaticObject {
    final String arg1;
    final Object arg2;
    public MyStaticObject(String arg1) {
        this(arg1, null);
    }
    public MyStaticObject(String arg1, Object arg2) {
        this.arg1 = arg1;
        this.arg2 = arg2;
    }
}
我们需要声明以下工厂接口
public interface MyStaticObjectFactory {
    MyStaticObject create(String arg1);
    MyStaticObject create(String arg1, Object arg2);
}
最后,这是如何分配自定义静态对象的方法
public void customStaticObject(TruffleLanguage<?> language) {
    StaticProperty property = new DefaultStaticProperty("arg1");
    StaticShape<MyStaticObjectFactory> shape = StaticShape.newBuilder(language).property(property, Object.class, false).build(MyStaticObject.class, MyStaticObjectFactory.class);
    MyStaticObject staticObject = shape.getFactory().create("arg1");
    property.setObject(staticObject, "42");
    assert staticObject.arg1.equals("arg1"); // fields of the custom super class are directly accessible
    assert property.getObject(staticObject).equals("42"); // static properties are accessible as usual
}
正如您从上面的示例中看到的,自定义父类的字段和方法可以直接访问,并且不会被静态对象的静态属性隐藏。
减少内存占用 #
阅读 Javadoc,您可能已经注意到 StaticShape 没有提供访问相关静态属性的 API。这在语言实现已经有存储此信息的方法时可以减少内存占用。例如,Java 语言的实现可能希望将静态形状存储在表示 Java 类的类中,并将静态属性存储在表示 Java 字段的类中。在这种情况下,表示 Java 类的类应该已经有一种方法来检索与其关联的 Java 字段,因此也就有了与形状关联的静态属性。为了进一步减少内存占用,语言实现者可能希望表示 Java 字段的类扩展 StaticProperty。
而不是将静态属性存储在表示字段的类中
class MyField {
    final StaticProperty p;
    MyField(StaticProperty p) {
        this.p = p;
    }
}
new MyField(new DefaultStaticProperty("property1"));
表示字段的类可以扩展 StaticProperty
class MyField extends StaticProperty {
    final Object name;
    MyField(Object name) {
        this.name = name;
    }
    @Override
    public String getId() {
        return name.toString(); // this string must be a unique identifier within a Builder
    }
}
new MyField("property1");
安全检查 #
在属性访问时,静态对象模型执行两种类型的安全检查
- 即 StaticProperty 方法与静态属性的类型匹配。
错误访问的示例
public void wrongMethod(TruffleLanguage<?> language) {
    StaticShape.Builder builder = StaticShape.newBuilder(language);
    StaticProperty property = new DefaultStaticProperty("property");
    Object staticObject = builder.property(property, int.class, false).build().getFactory().create();
    property.setObject(staticObject, "wrong access type"); // throws IllegalArgumentException
- 传递给访问器方法的对象与属性关联的构建器生成的形状或其子形状匹配。
错误访问的示例
public void wrongShape(TruffleLanguage<?> language) {
    StaticShape.Builder builder = StaticShape.newBuilder(language);
    StaticProperty property = new DefaultStaticProperty("property");;
    Object staticObject1 = builder.property(property, Object.class, false).build().getFactory().create();
    Object staticObject2 = StaticShape.newBuilder(language).build().getFactory().create();
    property.setObject(staticObject2, "wrong shape"); // throws IllegalArgumentException
}
虽然这些检查通常很有用,但如果语言实现已经执行了它们(例如使用验证器),它们可能显得冗余。第一种检查(关于属性类型)效率很高且无法禁用,而第二种检查(关于形状)计算成本较高,可以通过命令行参数禁用
--experimental-options --engine.RelaxStaticObjectSafetyChecks=true
或在创建 Context 时禁用
Context context = Context.newBuilder() //
                         .allowExperimentalOptions(true) //
                         .option("engine.RelaxStaticObjectSafetyChecks", "true") //
                         .build();
在没有其他等效检查的情况下,强烈不建议放松安全检查。如果对静态对象形状正确性的假设是错误的,则虚拟机很可能会崩溃。