tgmath.h の話

このプレゼンで紹介した tgmath.h の実装が素晴らしいという話なんですが、資料は口頭で説明する前提すぎて、イマイチ詳しくないので別に書いておこうかなぁと思います。これは C で型計算やってると言えると思うのでなかなかなもんなのです。

そもそも tgmath.h というヘッダが何かと言うと、 Type-generic math の略で、 C99 の PDF の 7.22 に書いてあります。ちなみにたぶん私はこの PDF を参照してます。

http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf

書いてあることは以下 4 点:

  1. tgmath.h を #include すると math.h と complex.h も #include されて type-generic なマクロを定義しまっせ
  2. sinf やら sinl みたいなのじゃなくて sin とかが呼ばれた時は float が引数なら sinf などと適切に dispatch されますよ
  3. 一つ以上の引数が long doubleなら long double バージョンが呼ばれます。一つ以上の引数が double か整数型なら double バージョンが呼ばれます。それ以外の場合 float バージョンが呼ばれます
  4. _Complex に対しては csin とかみたいに c prefix がついてるバージョンが呼ばれますよ。 cabs は例外的に fabs が generic version ですよ

実際に使ってみるとこんな感じ:

#include <stdio.h>
#include <tgmath.h>

int main() {
    float f;
    double d;
    _Complex c;
    printf("%f\n", sin(f));
    printf("%f\n", sin(d));
    printf("%f\n", sin(c));
}

これでできた tgmath.o に対して nm とかしてやると、

% nm tgmath.o
                 U csin
0000000000000000 T main
                 U printf
                 U sin
                 U sinf

などと、 sin, sinf, csin がそれぞれ参照されてることがわかります。 tgmath.h を #inlude せずに math.h と complex.h だけを #include した場合は sin だけしか参照されません。

で、使う方としてはまぁこれで十分なのですが、実装を考えると、 C にはオーバーロードとか無いわけで、あまり自明ではありません。

その時のいきさつは Joseph S. Myers さんの日記にあるようです。

http://www.advogato.org/person/jsm28/diary/1.html

これによると、最初は GCC 拡張の __typeof__ と statement expression でやっていたのだけど、整数型の扱いがおかしかったとのこと。でまぁどうすっかなーと gcc ML と相談して考えた結果、 GCC に __builtin_classify_type という拡張を入れてやろうということになったそうです。実際この拡張が入ってる GCC ではこれを使うようになっているようです。

しかし 個性的な人として有名な(オブラート) Ulrich Drepper さんは __typeof__ だけで頑張るコード書きましたよーというのがこの紹介したコードで。

 /* This is ugly but unless gcc gets appropriate builtins we have to do
   something like this.  Don't ask how it works.  */


 /* 1 if 'type' is a floating type, 0 if 'type' is an integer type.
   Allows for _Bool.  Expands to an integer constant expression.  */
#define __floating_type(type) (((type) 0.25) && ((type) 0.25 - 1))


 /* The tgmath real type for T, where E is 0 if T is an integer type and
   1 for a floating type.  */
#define __tgmath_real_type_sub(T, E) \
  __typeof__(*(0 ? (__typeof__(0 ? (double *)0 : (void *)(E)))0               \
                 : (__typeof__(0 ? (T *)0 : (void *)(!(E))))0))


 /* The tgmath real type of EXPR.  */
#define __tgmath_real_type(expr) \
  __tgmath_real_type_sub(__typeof__(expr), __floating_type(__typeof__(expr)))

というもの。このコードの __tgmath_real_type というマクロはどうやら、浮動小数型 T を渡したらその浮動小数型 T を、整数型を渡したら double をかえすような型に関しての関数になってるようです。

 /* 1 if 'type' is a floating type, 0 if 'type' is an integer type.
   Allows for _Bool.  Expands to an integer constant expression.  */
#define __floating_type(type) (((type) 0.25) && ((type) 0.25 - 1))

の __floating_type という型が浮動小数点型なら 1 を返して整数型なら 0 をかえすマクロはスライドに書いた通り、

(int)0.25 == 0
(_Bool)0.25 == 1
(float)0.25 == 0.25f
(double)0.25 == 0.25

を考えれば簡単に理解できるかと思います。

問題は次の __tgmath_real_type_sub というマクロで、さっきの C99 の PDF の 6.5.15.6 にある条件演算子の型についてのルールのうち、

  1. 片方が null pointer constant (要は (void*)0) であればもう一方の型を採用する
  2. 片方が void* でもう一方が pointer なら void* を採用する

の2つのルールをうまく活用しています。具体的に最初のの条件演算子を取り出して考えてやると、

0 ? (double *)0 : (void *)(E)

となっていて E は __floating_type の結果なので浮動小数なら 1 で整数型なら 0 。浮動小数の場合、右辺は (void *)(1) となって両辺ともに null pointer constant で無いので ((double*)0 は null pointer constant では無いことに注意) 、二つ目のルールが適用されて、この条件演算子全体の型は void* になります。整数の場合を考えると、右辺は (void*)(0) 、これは null pointer constant なので、 1 つ目のルールが適用されて、この条件演算子全体の型はもう一方の型である double* が採用されます。

二つ目の条件演算子はそれが ! によって逆になった格好です。

0 ? (T *)0 : (void *)(!(E))

ここで T は引数として与えた型なので、さっきと逆の動作になって、浮動小数が渡されるとその型が、整数型が渡されると void* がその型になるような条件演算子になっています。

この 2 つの条件演算子がまたまとめられて、

0 ? (一つ目の条件演算子の型)0 : (二つ目の条件演算子の型)0

となっているのが __tgmath_real_type_sub の全体像です。この条件演算子は両方 0 に対してキャストしているので、片方の型が void* なら null pointer constant ということになり、一つ目のルールによって、もう一方の型が全体の条件式の型となります。

で、浮動小数の場合は一つ目の条件演算子は void* 、二つ目は T* となっているので、 T* となり、整数の場合は double* と T* になっているので、無事当初の目的を果たす型関数ができました、ということになります。

ていうか tgmath.h って C99 作ってる人らはどう実装すればいいと思ってたんだろうか…ということで rationale をチラ見。

http://www.open-std.org/jtc1/sc22/wg14/www/C99RationaleV5.10.pdf

 The implementation might, as an extension, endow appropriate ones of
 the macros that this standard specifies only for real arguments with
 the ability to invoke the complex functions.

 This specification does not prescribe any particular implementation
 mechanism for generic macros. It could be implemented simply with
 built-in macros. The generic macro for sqrt, for example, could be
 implemented with

         #undef sqrt
         #define sqrt(x) _ _BUILTIN_GENERIC_sqrt(x)

 Generic macros are designed for a useful level of consistency with
 C++ overloaded math functions.

ということなので、それぞれの関数ごとに compiler の拡張作ってやればいいんじゃねーと考えてたっぽいですね。

C1X

なんか tgmath.h について調べてて、 GCC 拡張にある type generic macro をサポートする機能を C に入れてはどうすかという提案があることに気付きました。

http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1404.htm

でなんか C1X ってどういう感じなのかなーと眺めてみました。

http://en.wikipedia.org/wiki/C1X

http://www.open-std.org/JTC1/SC22/wg14/www/docs/n1425.pdf

ぱーと眺めると面白そうなのは、

  • 新キーワード _Thread_local に threads.h と stdatomic.h あたりのスレッドサポート系
  • 他の増えるキーワードは alignof と _Align と _Static_assert らしい。どれも字の如く的な機能っぽい。
  • uchar.h でさらば wchar_t
  • fmemopen とか asprintf とかが標準化 http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1337.pdf
  • bounds checker サポートを extension として。 stdio.h とかそのへんの標準関数が増えまくる (fopen_s とか strcpy_s とかそいうノリ) から extension らしい。 http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1225.pdf
  • Wikipedia によると gets 消えるそうなんでゴルファー死去。 getline とか getdelim がかわりに入るから、って話?

まぁとにもかくにも thread かなーという印象。

というか id:RiSK さんが色々書かれてるみたいだった。

http://d.hatena.ne.jp/RiSK/searchdiary?word=%2A%5BC1X%5D

あとは適当に documents とかをチラ見してた。

http://www.open-std.org/jtc1/sc22/wg14/www/documents

面白そうだったものを適当に

Block Proposal

http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1451.pdf

http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1457.pdf

要はラムダ。 Apple の人が提案してて GCD を持ってきた感じだと思う。

Floating-point to int/_Bool conversions: NaN を _Bool にキャストすると 1 なのか…

http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1391.htm

General support for type-generic macros

http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1441.htm

冒頭の type generic macro がどうこうの具体的なやつ。

#define cbrt(X) _Generic(X, long double: cbrtl, default: cbrt, float: cbrtf)(X)

とか書けたらいいんじゃねということらしい。

Memory Model Rationale

http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1411.htm

Boehm による C のメモリモデルの rationale 。基本は C++0x のやつらしい。

On The Removal of gets()

http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1420.htm

gets 消えるってことだけど、「いつも通り、コンパイラやらライブラリは標準を拡張していいわけだから、実際のところ legacy code サポートするために gets があるかもねー」的な感じっぽいので、まぁ実際 GCC から消えたりはしないんじゃないかね。

Vectorized types for C

http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1408.pdf

GCC とかにある vector 型。

#macro proposal

http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1410.pdf

#for とか #endfor とか #macro (プリプロセッサ時関数) とかを足そうとの話。いかにも通らなさそう。

Thread Unsafe Standard Functions

http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1371.htm

thread unsafe な関数に追記をしていこう的な話だと思う。

Support for Conditional Inclusion of Headers

http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1348.htm

ヘッダがあるか無いかの確認をプリプロセッサでできればいいね的な。

    #if include(<foo.h>)  // tries to #include <foo.h>
      // ...
    #else
      // ...
    #endif

とかできればいいでしょというような話らしい。いいと思うんだけど。

Encoding and Decoding Function Pointers

http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1332.pdf

クラッカーが関数ポインタテーブル書き換えて好きなところに飛ばすってのをよくやりやがるので、 encode_pointer とか作ってポインタを暗号化した状態でメモリに置いておけるようにしようぜって話。なるほどなぁ。

Unicode and Raw String Literals

http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1333.pdf

C++0x コンパチな raw string literal 入れようぜと。

Extensions to the C1X library to enhance security

http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1339.pdf

fopen のフラグに x を入れて exclusive な open ができるようにしようぜとかそういう話。ていうか x って glibc に既にあるんだなぁ。

感想

飽きてきたので中断。私としては C++0x と感想は同じで stacktrace をくれと言いたい。

次ながめる時のためにこの間のタイトルを読んだよとメモ。

http://www.open-std.org/jtc1/sc22/wg14/www/docs/Post%20Florence.htm

http://www.open-std.org/jtc1/sc22/wg14/www/docs/pre-delft.htm

なにかあれば下記メールアドレスへ。
shinichiro.hamaji _at_ gmail.com
shinichiro.h