- 适用于 JDK 23 的 GraalVM(最新)
- 适用于 JDK 24 的 GraalVM(抢先体验)
- 适用于 JDK 21 的 GraalVM
- 适用于 JDK 17 的 GraalVM
- 存档
- 开发版本
- Truffle 语言实现框架
- Truffle 分支仪表
- 动态对象模型
- 静态对象模型
- 解释器代码的主机优化
- Truffle 的函数内联方法
- 分析 Truffle 解释器
- Truffle Interop 2.0
- 语言实现
- 使用 Truffle 实现新的语言
- Truffle 语言和工具迁移到 Java 模块
- Truffle 本地函数接口
- 优化 Truffle 解释器
- 选项
- 堆栈替换
- Truffle 字符串指南
- 专门化直方图
- 测试 DSL 专门化
- 基于 Polyglot API 的 TCK
- Truffle 的编译队列方法
- Truffle 库指南
- Truffle AOT 概述
- Truffle AOT 编译
- 辅助引擎缓存
- Truffle 语言安全点教程
- 单态化
- 拆分算法
- 单态化用例
- 向运行时报告多态专门化
静态对象模型
本指南演示了如何开始使用 StaticShape 和 StaticProperty API,这些 API 是在 GraalVM 21.3.0 中引入的。完整的文档可以在 Javadoc 中找到。
动机 #
静态对象模型提供抽象来表示对象的布局,这些对象的布局一旦定义,其属性的数量和类型就不会改变。它特别适合,但不仅限于,静态编程语言对象模型的实现。其 API 定义了对象布局 (StaticShape),执行属性访问 (StaticProperty) 以及分配静态对象 (DefaultStaticObjectFactory)。实现效率高,并在属性访问上执行安全检查,如果语言实现(例如,通过验证器)已经执行了这些检查,则可以禁用这些检查。
静态对象模型不提供构造来模拟属性的可见性,也不区分静态属性和实例属性。其 API 与 动态对象模型 的 API 不兼容,后者更适合动态语言。
入门 #
在这个第一个例子中,让我们假设
language
是我们正在实现的 TruffleLanguage 的实例。- 我们希望表示一个具有以下静态布局的对象
- 一个名为
property1
的int
属性。 - 一个名为
property2
的Object
属性,可以存储为最终字段。稍后我们将详细了解这意味着什么。
- 一个名为
以下是如何使用静态对象模型来表示此布局
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
。 - 第三个参数是一个布尔值,它定义了属性是否可以存储为最终字段。这给了编译器执行额外优化的机会。例如,对该属性的读取可能被常量折叠。重要的是要注意,静态对象模型不检查作为最终存储的属性是否只被分配了一次,以及它是否在读取之前被分配。这样做可能会导致程序的行为错误,由用户来确保这种情况不会发生。然后,我们通过调用
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();
在没有其他等效检查的情况下,强烈建议不要放松安全检查。如果关于静态对象形状正确性的假设是错误的,VM 很可能会崩溃。