- 適用於 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 解譯器
- 選項
- 堆疊上置換 (On-Stack Replacement)
- Truffle 字串指南
- 特化長條圖
- 測試 DSL 特化
- 基於 Polyglot API 的 TCK
- Truffle 編譯佇列方法
- Truffle 程式庫指南
- Truffle AOT 概觀
- Truffle AOT 編譯
- 輔助引擎快取
- Truffle 語言安全點教學
- 單態化 (Monomorphization)
- 分割演算法
- 單態化使用案例
- 向執行階段回報多型特化
靜態物件模型
本指南示範如何開始使用 GraalVM 21.3.0 引入的 StaticShape 和 StaticProperty API。完整文件可以在 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();
強烈建議在沒有其他等效檢查的情況下放寬安全性檢查。如果靜態物件形狀的正確性假設錯誤,虛擬機器可能會崩潰。