Ada 2020 (19日目) - Globalその1

続き。

これまでのあらすじ

Ada 2020では複数のスレッドが同期なしで同じ変数にアクセスしていないことを検証できます。 具体的には各変数ごとに読み取り(in)と書き込み(out)のアクセスが衝突しているかがチェックされます。

さて、コンパイラが自動でチェックするにも限りがあります。 具体的には翻訳単位や可視性の壁があります。 これらの壁を越えるためにはパッケージの内側で何にアクセスしているかを外に向けて示す必要があります。

グローバル変数アクセスの表明

Globalアスペクト(グローバル変数)

Global アスペクトを通じてサブプログラムがアクセスしているグローバル変数を表明することができます。

package Pkg1 is
   G : Integer;
   function Get_G return Integer
     with Global => in G;
   procedure Set_G (Value : in Integer)
     with Global => out G;
   procedure Incr_G
     with Global => in out G;
   procedure Unrelated
     with Global => null;
end Pkg1;
package body Pkg1 is

   function Get_G return Integer is
   begin
      return G; -- in
   end Get_G;

   procedure Set_G (Value : in Integer) is
   begin
      G := Value; -- out
   end Set_G;

   procedure Incr_G is
   begin
      G := G + 1; -- in out
   end Incr_G;

   procedure Unrelated is
   begin
     null;
   end Unrelated;

end Pkg1;

割と特殊な文法です。

null はグローバル変数にアクセスしていないということです。 all と書きますと全てのグローバル変数にアクセスしていると見做されます。 どのグローバル変数にアクセスしているかがわからない場合が all ですね。

Global を指定しないときのデフォルトは、Pure なパッケージでは Global => null、それ以外では Global => in out all となります。

一般にグローバル変数にアクセスしていない関数をリエントラント(再入可能)と呼び他のスレッドのことを気にせず使える性質とされています。 Global アスペクトでは、リエントラントではないがこれとこれは同時に使っても大丈夫、みたいなことも表現できるわけですね。

それでは G を外からは見えない場所へ移動してみましょう。

package Pkg1 is
   function Get_G return Integer
     with Global => in private of Pkg1;
   procedure Set_G (Value : in Integer)
     with Global => out private of Pkg1;
   procedure Incr_G
     with Global => in out private of Pkg1;
   procedure Unrelated
     with Global => null;
private
   G : Integer;
end Pkg1;

Global => private of Pkg1Pkg1 にある隠されたグローバル変数にアクセスしている、Global => Pkg1Pkg1 にある全てのグローバル変数にアクセスしているという意味です。

なおこの Global アスペクトはSPARKでも使用されており全体を通じての整合性が検証されます。 そちらでは仮想のグローバル変数(状態)を宣言できたり状態間の関係を示せたりとより細かく制御できるよう拡張がなされています。

Ada 2020の範囲ではSPARKほど細かいことはできませんので、グローバル変数ではないがアクセスが競合していないかチェックしたいもの、例えばリンクするライブラリの内部状態やOSやCPUの持っている状態等はいずれかのパッケージに代表させればよいと思います。 細かく分けたいならそのための空パッケージを作ったりもありでしょうか。

スレッド間での衝突検証では同期なしにアクセスしている場合と同期を行ってアクセスしている場合を区別する必要があります。 きちんと同期を行っている場合は複数スレッドからアクセスされても問題ありませんが、同期なしにアクセスしているスレッドが混じると危険です。

同期を行っている場合は変数名の前に synchronized を書きます。 (また予約語を再利用している……。)

package Pkg2 is
   S : Integer;
   procedure Sync_Incr_S
     with Global => synchronized in out S;
end Pkg2;
package body Pkg2 is

   protected Prot is
      procedure Incr_S;
   end Prot;

   protected body Prot is

      procedure Incr_S is
      begin
         S := S + 1; -- in out
      end Incr_S;

   end Prot;

   procedure Sync_Incr_S is
   begin
      Prot.Incr_S;
   end Sync_Incr_S;

end Pkg2;

グローバル変数が複数ある場合は括弧で囲いaggregate式のように書きます。

package Pkg2 is
   X, Y : Integer;
   procedure Set_X_As_Y
     with Global => (out X, in Y);
end Pkg2;
package body Pkg2 is

   procedure Set_X_As_Y is
   begin
      X := Y;
   end Set_X_As_Y;

end Pkg2;

Nonblocking 同様に generic の引数として渡されたサブプログラムの Global を参照できます。

generic
   with function F (X : Integer) return Integer;
function G return Integer
  with
    Nonblocking => F'Nonblocking,
    Global      => F'Global;

function G return Integer is
begin
   return F (0);
end G;

複数まとめるときは & 演算子を使います。 (配列を真似ていると思われます。 ただし [ ] は使えません……。)

generic
   with function F1 (X : Integer) return Integer;
   with function F2 (X : Integer) return Integer;
function G return Integer
  with
    Nonblocking => F1'Nonblocking and F2'Nonblocking,
    Global      => F1'Global & F2'Global;

function G return Integer is
begin
   return F1 (F2 (0));
end G;

長くなりましたので今日はこの辺にして残りは後日とさせてください。 (例によって小出し。)

関連AI

  • AI12-0079-1 Global-in and global-out annotations

  • AI12-0302-1 Default Global aspect for language-defined units

  • AI12-0303-1 Some constants must be covered by Global aspects; extensibility

  • AI12-0310-1 Specifying private parts of packages in aspect Global

所感

Nonblocking と並んで標準ライブラリの定義のほとんどに with Global が付きまくってノイズです。