静的オブジェクトモデル

このガイドでは、GraalVM 21.3.0 で導入された StaticShape および StaticProperty API の使用を開始する方法を示します。完全なドキュメントは、Javadoc にあります。

動機 #

静的オブジェクトモデルは、一度定義されると、プロパティの数と型を変更しないオブジェクトのレイアウトを表現するための抽象化を提供します。静的プログラミング言語のオブジェクトモデルの実装に特に適していますが、これに限定されません。その API は、オブジェクトレイアウト(StaticShape)を定義し、プロパティアクセス(StaticProperty)を実行し、静的オブジェクト(DefaultStaticObjectFactory)を割り当てます。実装は効率的で、プロパティアクセスに対する安全チェックを実行します。これらのチェックは、言語実装(例えば、検証ツール)によって既に実行されている場合は無効にできます。

静的オブジェクトモデルは、プロパティの可視性をモデル化する構成要素を提供せず、静的プロパティとインスタンスプロパティを区別しません。その API は、動的言語により適した 動的オブジェクトモデル の API と互換性がありません。

はじめに #

最初の例では、次のことを想定しましょう。

  1. language は、実装している TruffleLanguage のインスタンスです。
  2. 次の静的レイアウトを持つオブジェクトを表現したいとします。
    • property1 という名前の int プロパティ。
    • final フィールドとして格納できる 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 インスタンスを作成します。引数として渡される String ID は、ビルダー内で一意である必要があります。プロパティを作成した後、ビルダーインスタンスに登録します。

  • 最初の引数は、登録する StaticProperty です。
  • 2 番目の引数は、プロパティの型です。プリミティブクラスまたは Object.class にすることができます。
  • 3 番目の引数は、プロパティを 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) に渡すことによって行われます。親シェイプのプロパティを使用して、子シェイプの静的オブジェクトに格納されている値にアクセスできます。

次の例では、前のセクションで説明したものと同じ親シェイプを作成し、親シェイプのプロパティの 1 つを隠す子シェイプで拡張します。最後に、さまざまなプロパティにアクセスする方法を示します。

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 種類の安全チェックを実行します。

  1. 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
  1. アクセサーメソッドに渡されるオブジェクトが、プロパティが関連付けられているビルダーによって生成されたシェイプ、またはその子シェイプのいずれかと一致すること。

不正なアクセスの例

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 = Context.newBuilder() //
                         .allowExperimentalOptions(true) //
                         .option("engine.RelaxStaticObjectSafetyChecks", "true") //
                         .build();

他の同等のチェックがない場合は、安全チェックを緩和することは強く推奨されません。静的オブジェクトのシェイプの正確性に関する仮定が間違っている場合、VM がクラッシュする可能性があります。

お問い合わせ