静的オブジェクト・モデル
このガイドでは、GraalVM 21.3.0で導入されたStaticShape APIおよび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です。
- 2番目の引数は、プロパティのタイプです。プリミティブ・クラスまたは
Object.class
を指定できます。 - 3番目の引数は、プロパティを最終フィールドとして格納できるかどうかを定義するブール値です。これにより、コンパイラはさらに最適化を実行できます。たとえば、このプロパティへの読取りは、定数畳込みされている可能性があります。静的オブジェクト・モデルでは、最終フィールドとして格納されたプロパティが複数回割り当てられていないかどうか、および読取り前に割り当てられているかどうかはチェックされないことに注意してください。これを行うと、プログラムの誤動作につながる可能性があり、この動作を行えないようにするのがユーザーの責任です。次に、
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");
安全性チェック
プロパティ・アクセスでは、静的オブジェクト・モデルは次の2つのタイプの安全性チェックを実行します:
- 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
}
多くの場合、これらのチェックは役に立ちますが、言語実装でベリファイアを使用するなど、これらのチェックがすでに実行されている場合は冗長になることがあります。最初のタイプの(プロパティ・タイプに関する)チェックは非常に効率的であり、無効にすることはできませんが、2番目のタイプの(シェイプに関する)チェックは計算コストが高く、コマンドライン引数によって無効にできます:
--experimental-options --engine.RelaxStaticObjectSafetyChecks=true
または、コンテキストを作成する場合:
Context context = Context.newBuilder() //
.allowExperimentalOptions(true) //
.option("engine.RelaxStaticObjectSafetyChecks", "true") //
.build();
他の同等のチェックがない場合、安全性チェックを緩和しないことを強くお薦めします。静的オブジェクトのシェイプの正確さに関する仮定が間違っていると、VMがクラッシュする可能性があります。