yohhoyの日記

技術的メモをしていきたい日記

reproducible/unsequenced属性

プログラミング言語Cの次期C2x(C23)言語仕様に追加される属性(→id:yohhoy:20200505reproducible, unsequencedに関するメモ。関数呼び出しに対する最適化ヒント。

// C2x
int calc(int x, int y) [[unsequenced]];

int a = /*...*/;
int b = calc(a) * 2;
/* 任意の処理 */
int c = calc(a) * 4;
// calc関数に付与されたunsequenced属性により、
// コンパイラによる下記の最適化が許可される。
// int b = calc(a) * 2;
// int c = b * 2;
// /* 任意の処理 */

まとめ:

  • 関数または関数ポインタに対して付与する属性。
  • reproducible属性=副作用を持たない(effectless)+連続呼出しは同一結果(idempotent)
  • unsequenced属性=reproducible属性+可変状態を持たない(stateless)+引数以外に依存しない(independent)
  • 関数動作セマンティクスが指定属性に反する場合は未定義動作(undefined behavior)となる。
    • Cコンパイラは関数実装が指定属性を満たすことを検査しなくてもよいが、できる限り診断メッセージを出すことが推奨される(recommended practice)。
  • GCC拡張属性__attribute__((pure))reproducibleGCC拡張属性__attribute__((const))unsequencedと概ね近似できる。
  • 注意:unsequenced属性の関数は再入可能(reentrant)や並行実行(executed concurrently)に対する安全性を直接意味しない。*1

例えば標準ヘッダ<math.h>提供の数学関数群はエラー発生時にerrnoへ値を書込む可能性があるため、一般にreproducibleunsequencedいずれでもない。実引数に対して制約条件を保証できるのであれば、プログラマの責任で同属性を付与した関数再宣言を行ってもよい。提案文書N2956よりコード例を引用。*2 *3

// C2x
#include <math.h>
#include <fenv.h>

inline double distance (double const x[static 2]) [[reproducible]] {
  #pragma FP_CONTRACT OFF
  #pragma FENV_ROUND FE_TONEAREST
  // We assert that sqrt will not be called with invalid arguments
  // and the result only depends on the argument value.
  extern typeof(sqrt) [[unsequenced]] sqrt;
  return sqrt(x[0]*x[0] + x[1]*x[1]);
}

double g (double y[static 1], double const x[static 2]) {
  // We assert that distance will not see different states of the floating
  // point environment.
  extern double distance (double const x[static 2]) [[unsequenced]];
  y[0] = distance(x);
  ...
  return distance(x); // replacement by y[0] is valid
}

C2x WD N3047 6.7.12.7/p3, p5-8より一部引用。

3 The main purpose of the function type properties and attributes defined in this clause is to provide the translator with information about the access of objects by a function such that certain properties of function calls can be deduced; the properties distinguish read operations (stateless and independent) and write operations (effectless, idempotent and reproducible) or a combination of both (unsequenced). (snip)

5 A function definition f is stateless if any definition of an object of static or thread storage duration in f or in a function that is called by f is const but not volatile qualified.
6 (snip) A function pointer value f is independent if for any object X that is observed by some call to f through an lvalue that is not based on a parameter of the call, then all accesses to X in all calls to f during the same program execution observe the same value; otherwise if the access is based on a pointer parameter, there shall be a unique such pointer parameter P such that any access to X shall be to an lvalue that is based on P. A function definition is independent if the derived function pointer value is independent.
7 A store operation to an object X that is sequenced during a function call such that both synchronize is said to be observable if X is not local to the call, if the lifetime of X ends after the call, if the stored value is different from the value observed by the call, if any, and if it is the last value written before the termination of the call. An evaluation of a function call is effectless if any store operation that is sequenced during the call is the modification of an object that synchronizes with the call; (snip)
8 An evaluation E is idempotent if a second evaluation of E can be sequenced immediately after the original one without changing the resulting value, if any, or the observable state of the execution. A function definition is idempotent if the derived function pointer value is idempotent.
9 A function is reproducible if it is effectless and idempotent; it is unsequenced if it is stateless, effectless, idempotent and independent.

関連URL

*1:unsequencedを満たすには関数終了時点で観測可能な変化がなければよく、関数は(呼出し元がポインタ経由で渡した)静的記憶域期間(static storage duration)変数に対して読取/書込を行う可能性がある。...そんな用途にstatic変数を使うんじゃねぇ(`ェ´)

*2:関数引数リスト中の T arg[static N] はC99で導入された構文。T 型のポインタ型仮引数 arg が少なくとも N 要素を持つデータ領域を指すことを表明する。id:yohhoy:20170503 も参照のこと。

*3:typeof はC2xで導入される型情報を取り出す演算子(→id:yohhoy:20220912)。typeof(sqrt) はC標準ライブラリ関数 sqrt の型情報つまり「戻り値doubleかつ引数リスト(dubule)をもつ関数型」を表す型指定子であり、該当文は extern double sqrt(double) [[unsequenced]]; 関数宣言となる。