PURE手続とHPF指示文の関連に関しては、 High Performance Fortran Language Specification November 10, 1994 Version 1.1 の 4.3 節で定義されていますが、 HPF2.0の仕様書中には含まれておらず、 日本語訳が存在しないため、参考のため収録します。
PURE関数は、副作用がないことを保証するための、ある構文上の 制約に従う関数である。PURE関数の引用が、 プログラムの状態に及ぼす唯一の効果は、結果を返すことだけである。 すなわち、PURE関数の引用が、 引数や大域データの値、ポインタ結合、またはデータマッピングを 変更したり、外部入出力を実行したりすることはない。 PUREサブルーチンは、INTENT(OUT)およびINTENT(INOUT)引数の 値かつ/またはポインタ結合を変更する以外の副作用を持たない サブルーチンである。これらの特性は、 手続の新たな属性(PURE属性)によって、宣言される。
PURE手続(つまり関数またはサブルーチン)は、通常の手続と全く 同様に使用することができる。しかし、手続は、以下の文脈で使用される 場合、PUREでなければならない。
仕様の根拠
PURE関数は、副作用を持たないため、非決定性のような望ましからざる結果 をもたらすことなく、FORALL中で、並行的に起動することができる。 また、並行的な実行の効率的な実装を促進する、 (挙動に関する意味上の制約ではなく、)構文上の制約 が使われているのは、言語処理系によるチェックを可能にするためである。
HPF Journal of Developmentでは、スカラ引数を持つPURE手続 の要素別の起動を可能にすることも提案されている。
利用者定義の手続が、PUREでなければならない文脈で引用される場合、 それを引用する有効域において、引用仕様が明示的であって、 かつその引用仕様において、PURE属性が指定されていなければならない。 この属性は、 Fortran 90規格の 規則R1217(prefix)とR1220(subroutine-stmt)の拡張により、 function-stmtまたはsubroutine-stmt中で指定される。 規則R1216(function-stmt)は、変更されないが、 明確にするため、規則H409として、ここに再掲する。
H407 prefix is prefix-spec [ prefix-spec ] ... H408 prefix-spec is type-spec or RECURSIVE or PURE or extrinsic-prefix H409 function-stmt is [ prefix ] FUNCTION function-name function-stuff H410 function-stuff is ( [ dummy-arg-name-list ] ) [ RESULT ( result-name ) ] H411 subroutine-stmt is [ prefix ] SUBROUTINE subroutine-name subroutine-stuff H412 subroutine-stuff is [ ( [ dummy-arg-list ] ) ]
(extrinsic-prefix (規則H601)に関する議論は、6.2節を参照されたい。)
HPF組込み関数を含む組込み関数は、常にPUREであり、このことを明示的に宣言 する必要はない。組込み手続は、要素別(例えば、MVBITS)であれば PUREだが、さもなければPUREではない。 HPFライブラリ中の関数とサブルーチンは、PUREであると宣言されている。 文関数がPUREであるのは、そこで引用される全ての関数がPURE である場合、かつその場合に限る。
PURE属性を持つ手続は、以下の制約において「PURE手続」と呼ばれる。
以下の制約が、Fortran 90規格12.5.2.2節(function-subprogramの定義) の規則R1215に追加される。
利用者への助言
型宣言文、またはDATA文における局所変数の初期化は、 SAVE属性を意味するため、そのような初期化も許されない。
上記の制約は、 PURE関数が副作用を持たない(つまり、関数外で参照可能なデータを 定義しない)ことを保証するために、定められている。 そのため、前に説明したように、安全に並行的な引用が可能である。
仕様の根拠
上記の制約が、副作用を取り除くのに十分である理由を説明しておくことは 大切である。最初の制約(明示的なINTENT(IN)の要求)は、以下の規則によって 保証される挙動を宣言する。 それは、技術的に必要なわけではないが、利用者定義演算に対する明示的な 宣言の規則との整合性のために導入された。POINTER引数は、INTENT属性を持て ないことに注意されたい。POINTER引数もまた、 引数自身と、指示先の実体の両方に関して INTENT(IN)属性を持つかのように振る舞うことは、以下の制約により保証され る。
2番目の制約(SAVE変数の禁止)により、 PURE関数は、呼出間で、内部状態を保存しないことが保証される。 呼出間での内部状態の保存は、同じ手続の呼出間での副作用の 余地をつくる。
3番目の制約(大域変数と仮引数の使用に関する制限)により、 仮引数と大域変数が、関数により定義されないことを保証する。 仮ポインタや大域ポインタの場合、制約は、そのポインタ結合と 指示先の値の両方に適用されるため、 POINTER代入文、ALLOCATE文、DEALLOCATE文、またはNULLIFY文 により値や状態の変更を受けてはならない。ところで、 これらの制約は、局所変数と結果変数だけが、代入文やポインタ代入文に より確定できる、ということを意味する。
さらに、仮データ実体または大域データ実体は、 ポインタ代入文の指示先にはなれない。(つまり、それらは、局所ポインタや関数 結果に対するポインタ代入文の右辺として引用することはできない。) その場合、ポインタ経由で値が定義される可能性があるからである。 (そのような実体が、ポインタの指示先となることは許し、 そのようなポインタに対する代入を禁止する、という代替案もあるが、 それを許すような構文上の制約は、はるかに厳しいものとなるだろう。)
最後の点に関連して、いずれかのレベルの成分としてポインタ成分 を持つ構造型変数に対する 通常の(ポインタでない)代入文は、その変数のポインタ成分に対する ポインタ代入を引き起こしうることに注意されたい。 これは、組込み代入文の場合には確実である。 この場合、代入文の右辺の式は、左辺の変数と同じ型を持ち、 代入の結果、右辺の式の結果のポインタ成分が、 右辺の対応する成分にポインタ代入される。 (Fortran 90規格の7.5.1.5節を参照されたい。) しかし、そのような変数に対する利用者定義代入の場合には、 たとえ、右辺の式のデータ型にはポインタ成分が含まれなくても、 やはり同様のことが起こりうる。つまり、 利用者定義代入の左辺の変数のポインタ成分に対して、 式の結果の一部、または全てのポインタ代入が起こる可能性がある。 したがって、仮実体や大域実体は、 ポインタ成分を持つ構造型変数に対するいかなる代入文の 右辺としても引用することはできない。 なぜなら、それらの実体、またはその部分実体が、 ポインタ代入の指示先となり、 上記で述べた制約を犯す可能性があるからである。
(ところで、最後の2つの段落は、 ポインタ成分を持つ変数に対する ポインタ代入文または代入文の、右辺における唯一の実体として、 仮実体または大域実体を参照することだけを禁止している。 これらの状況における被演算子、実引数、添字式などとしての 参照には制約がない。)
最後に、仮データ実体または大域データ実体を、手続引用における INTENT(OUT)またはINTENT(INOUT)仮引数やポインタ属性を持つ 仮引数と結合する実引数として、使用することはできない。 なぜなら、その場合、手続引用により定義される可能性があるからである。 この制約は、他の制約と同様に、翻訳時のチェックが可能である。 なぜなら、PURE関数中で引用される任意の手続は、 引数を定義しない関数であるPURE関数か、 引用仕様中で引数のINTENT属性またはPOINTER属性を指定しなければならない PUREサブルーチンでなければならないからである。(以下を参照されたい。) ところで、この文脈では、仮ポインタと結合する実引数は、 定義されると仮定されている。なぜなら、Fortran 90規格では、 その授受特性を指定することができないからである。
4番目の制約(PURE手続だけを引用できる)により、 PURE関数から引用される全ての手続は、それら自身、 副作用がないことが保証される。ただし、 PUREサブルーチンの場合に、仮ポインタ、あるいはINTENT(OUT)またはINTENT(INTOU) 属性を持つ仮引数と結合する実引数を定義することを除く。 今説明したように、 大域実体または仮実体が、 副作用がないという要求に違反する可能性があるそのような引数 として引用されていないことは、チェック可能である。
制約5と制約6は、局所変数、仮引数、および結果変数の マッピングの明示的な宣言に関する制約である。 これらの制約の理由は、関数は並行的に起動される可能性があり、 そのそれぞれの起動は、特定のプロセッサの部分集合上で活 動状態になり、そのようなプロセッサの部分集合上にマップされた データ上で実行を行うかも知れないからである。 実際、最適化により、呼出元手続は、 FORALL中で最大限の並行的な実行を行ったり、 通信を削減する、といった理由により、 実引数や 結果変数のマッピングを自動的に変更するかもしれない。 そのような最適化は、他の引数、式中の他の項、代入文の左辺などを 考慮にいれて行われる。したがって、仮引数や結果変数は、 プロセッサ配列に関して場所を特定するようなマッピング指示文中に記述する ことはできない。(例えば、それらは大域変数やテンプレートに整列したり、 明示的に分散したり、INHERIT属性を持つことはできない。 これらのことにより、上記のように、呼出元が 実引数のマッピングを決定する自由が奪われるからである。) 仮引数や結果変数に対して指定できる唯一のマッピング情報は、 それらの間の整列だけである。 これは、 仮引数や結果変数が必要とする相対的なマッピングに関する有益な情報を 呼出元に与える。 同様の理由で、局所変数は、(直接、または他の局所変数と通じて) 仮引数または結果変数に整列できるが、任意のマッピングを持つことはできな い。
制約7と制約8により、PURE関数内で、再整列や再分散による副作用 が防止される。
最後から2番目の制約により、 並行的実行の文脈における外部入出力やファイル操作により、 その順序が非決定的になることが防止される。
最後の制約により、PAUSE文とSTOP文が禁止されている。 PAUSE文は、入力を要求するため、 入出力文と同様の理由で許されない。 STOP文により実行が停止するが、それはかなり大きな副作用となる。
実装者への助言
PURE関数中では、仮引数を指令的に整列させることができ、 したがって、関数引用時に再マッピングが起こる可能性があることに 注意されたい。 整列だけしか指定できないため、手続の起動に必要な データを保有していないプロセッサに対して、 データをマップすることにはならない。PURE関数は、分散された大域データを引用することはできるが、 定義することはできない、ということにも注意されたい。 これを、 共有メモリを持たないマシン上で実装することは非常に難しいかも知れない。 一つの可能な実装は、データを取得するために、 割り込み駆動型のメッセージを使用することである。 他の可能な実装としては、PURE手続中における全ての大域データ引用の可能性を、 手続間解析により検出することがある。 そのような高コストのアクセスパタンを指摘する 言語処理系からのフィードバックは、 性能に敏感な利用者にとって非常に重要である。
以下の制約が、Fortran 90規格12.5.2.3の規則R1219 (subroutine-subprogramの 定義)に追加される。
仕様の根拠
PUREサブルーチンに対する制約は、PURE関数に対するものと 同じ原則に基づいている。ただし、 INTENT(OUT)およびINTENT(INOUT)仮引数に対する副作用が 許されることを除く。 ポインタ仮引数は、常にINTENT(INOUT)であるとみなされる。PUREサブルーチンが定義されているのは、 PURE手続から安全にサブルーチンを引用するため、 およびforall-assignmentが利用者定義代入であることを許すためである。
さらに、最後の制約により、 PUREサブルーチンにおける選択戻りが禁止される。 これは、PURE関数では、明示的には禁止されない。 なぜなら、関数は選択戻りを持てないからである。 PUREサブルーチンからの選択戻りにより、 呼出元手続における制御の流れが変化する。 これは、PURE手続の性質にそぐわないと判断された。
仕様の根拠
この制約により、 PUREでない手続の引用を許すことにより、 手続のPUREである性質が損なわれるようなことがない ことが保証される。
! この文関数は、PUREである。なぜなら、他の関数を ! 引用していないからである。 REAL myexp myexp(x) = 1 + x + x*x/2.0 + x*x*x/6.0 FORALL ( i = 1:n ) a(i) = myexp( a(i+1) ) ... ! 組込み関数は、常にPUREである。 FORALL ( i = 1:n ) a(i,i) = log( abs( a(i,i) ) )forall-assignmentは、配列代入文であってもよいため、PURE関数は、 配列の結果を持ちうる。そのような関数は、 配列に対して行単位あるいは列単位 の演算を行う場合に、特に有用である。
INTERFACE PURE FUNCTION f(x) REAL, DIMENSION(3) :: f REAL, DIMENSION(3), INTENT(IN) :: x END FUNCTION f END INTERFACE REAL v (3,10,10) ... FORALL (i=1:10, j=1:10) v(:,i,j) = f(v(:,i,j))PURE関数がFORALL中で引用されたときの配列要素やその添字と結合する引数に 依存した、PURE手続中の分岐により、 制限された形のMIMD並列性が得られる。 これは、潜在的に同期のオーバヘッドを含む一連のマスクつきFORALL文やWHERE文 を使用する代りになることがある。 次の例では、これがどのように 記述できるかを示している。
REAL PURE FUNCTION f(x, i) REAL, INTENT(IN) :: x ! 配列要素と結合する INTEGER, INTENT(IN) :: i ! 配列添字と結合する IF (x > 0.0) THEN ! 値に依存した条件 f = x*x ELSE IF (i==1 .OR. i==n) THEN ! 添字に依存した条件 f = 0.0 ELSE f = x ENDIF END FUNCTION f ... REAL a(n) INTEGER i FORALL (i=1:n) a(i) = f( a(i),i)PURE手続には、(STOP文が使用できないことを除いて) 内部の制御の流れに関しては制約がないため 、PURE手続の利用によって、 さもなければFORALL内で入れ子になる より複雑な演算を隠蔽することができる。 例えば、以下のコード断片は、配列の各要素に対して繰り返し アルゴリズムを実行している。 入力が異なると、必要となる計算の量も異なることに注意されたい。 このような柔軟性を利用できないマシンもあるかも知れない。
PURE INTEGER FUNCTION iter(x) COMPLEX, INTENT(IN) :: x COMPLEX xtmp INTEGER i i = 0 xtmp = -x DO WHILE (ABS(xtmp).LT.2.0 .AND. i.LT.1000) xtmp = xtmp * xtmp -x i = i + 1 END DO iter = i END FUNCTION ... FORALL (i=1:n, j=1:m) ix(i,j) = iter(CMPLX(a+i*da,b+j*db))
仕様の根拠
PURE手続に対する制約により、副作用がないことが保証され、 そのため、配列の各「要素」に対して、並行的に起動できることが 保証される。 (ここで、「要素」は、それ自身配列を含むデータ構造であるかも知れない。)PURE手続に対する制約は、複雑に思えるかも知れないが、 プログラマが、それらに精通している必要はない。 プログラマの観点からすると、これらの制約は、以下のように要約 することができる。PURE手続は、 大域変数やINTENT(IN)仮引数に対して、 代入やポインタ代入を引き起こすと「考えられる」 いかなる処理も含んではならないし、入出力やSTOP処理も実行してはならない。 「考えられる」という言葉の使用に注意されたい。 それは、単にPURE手続が実際には副作用がない、 というだけでは十分ではない。 例えば、 大域変数に対する代入文を含んでいる関数は、たとえ、 それが、その関数のいかなる引用においても実行されない分岐中にある場合 でもPUREではない。 このような性質を持つ関数を除外することは、 厳しい翻訳時のチェックを利用するためには避けられない。翻訳時のチェックと、 柔軟性の選択において、HPF委員会は、チェックを強化する方を選択した。
ほとんどのライブラリ手続は、(ライブラリ手続本来の性質により) PURE手続に要求される制約に準拠しており、 したがって、PUREと宣言することができ、 FORALL文、FORALL構文および利用者定義PURE手続中で 引用可能であると期待される。 ほとんどのライブラリ手続が、大域データを参照しないことも 期待されている。それは、並行的な実行を抑制する場合があるからである。
PURE手続に対する制約は、副作用がないこと、 実行するプロセッサに対して独立であること、および 保存される内部状態がないことを、翻訳時にチェックする ために必要な事項に限定されている。 これらの制約に従うことで、 PURE手続の定義において、最大限の機能が保たれている。 これは、 FORALL中での関数引用ができる限り広い範囲で可能となり、 非常に一般的なライブラリ手続をPUREと分類できるように 定められている。
この柔軟性による弊害として、 PURE手続は、 FORALL中の並行的な実行の邪魔をし、最悪の場合禁止しうる ようなある種の機能を許している。 (つまり、そのような引用は、実装によっては逐次化 しなければならないかも知れない。) このような機能の中で、主要なものは、大域データ、特に分散された 大域データの引用と、 引数およびPURE関数の結果変数が、ポインタ又は リストや木構造のような再帰的データ構造を含んだ ポインタ成分を持つデータ構造であるかもしれない、という事 である。 プログラマは、そのような機能を使用する場合の潜在的な 性能上の不利益を認識しておくべきである。