読者です 読者をやめる 読者になる 読者になる

テストステ論

高テス協会会長が, テストステロンに関する情報をお届けします.

macro-of-inlineの紹介

すいませんね. Erlang VMの解説のように役立つ記事が書けなくて!!!!

概要

macro-of-inlineという, インライン関数をコードレベルで等価なマクロに変換してインライン関数を模擬するというソフトウェアを開発しているので状況を報告します.

背景

Elixirという超絶メタプログラミング言語に嵌ってることから分かるように, 私はコード書換が好きです. と言っても, 仕事やトップレベルのOSSコンパイラ最適化をがっつりやれるほど専門レベルに精通してるわけではないですし, そこまでのめりこむモチベーションもないですが, エレガントにコードを書換えること自体には興味があります. 日立での最後の仕事も, クエリの書換に関わってました.

世の中, gccLLVMだとすさまじい最適化技術を勝手にやってくれて, プログラマはきれいなコードを書くことだけに専念出来る領域が99.9%だと思いますが, 残り0.1%は苦しみしかない世界です. 通称うんコンパイラは, 「おいこれは学生の演習か?」というレベルのコードを吐きます.

今回注目したのは, 関数のインライン化です. うんコンパイラは, 関数のインライン化をサポートしません. コンパイラが正しく動けばそれでよし. (そしてそんなモチベーションで作ったものが正しく動くわけはない) 「最適化?知らねえよ. プログラマの努力が足りないに決まってる. はぁ?神であるコンパイラ技術者様のやることに文句でもあるのか?アプリケーションエンジニアくんw」と言われてるような気がします. うぜえ

関数のインライン化というのが最適化の中でどれほど難しいかは分からないですが, コンパイラが持っている情報を考えれば, 破滅的に難しいとも思えない. ネグレクトだ!許せない!そこで, pycparserというライブラリを使い, 関数をマクロに書換えることでインライン化と同等の効果を出すというプリプロセッサを作ることにしました. Githubやウェブで探してみましたが, 見つかりませんでした. 製品はあまり調べていませんが, ざっと調べた限りでは見つかりませんでした. たぶんですが, 関数のインライン化の実装は簡単で, 関数のインライン化もサポートされていないコンパイラというのは, うんコンパイラ中のうんコンパイラだから, そんなプログラムの需要はないんでしょう. 大体, 本来inlineは「インライン化すべきどうかもコンパイラが判断する」という意味ですから, ただインライン化することくらいは序の口と想像がつきます. というものの, 機能を絞ればそんなに難しくならなそうなので, 面白そうだしやってみました.

実装の方針

Cのマクロが危険であるというのは, 言葉ではよく知られていることですが, Cのマクロが本当に危険になるまで突っ込んで使っている人はたぶんあんまりいないでしょう. Cのマクロは結構危険です. 素人のみなさんは使わないでください. その理由の一つは, Hygiene Problemとして Hygienic macro - Wikipedia, the free encyclopedia に書いてあります. Cのマクロは, 単に使うと, マクロの外の変数を参照してしまうことを防げないのです. Hygienic Macroは, ElixirやRustのようなクールな言語では実装されています. Elixirでは, マクロの外にアクセスするためには, そのことをわざわざ示す必要があります.

もう一つ大きな問題は, マクロにすると返り値を返すのが難しくなるということです. 例えば, 関数の途中で抜けるガードを想像してください. とりあえず最初の実装では, 返り値がvoidの関数のみをサポートすることにします. 返り値を返されると, どういう罠があるかは読み切れていませんが, 直感としては, マクロ化が容易になるところまでASTを書換えていくことでどうにか出来ると思います(gotoを使うなど).

結果

今回は, マクロに変換するところまでは至らず, 関数内の変数名を適切に書き換えるという実装を行いました. 変数名は, 衝突しないことを祈って乱数で生成することにしています. 絶対に衝突しないようにすることも可能ですが, 本質ではないので省きました. 桁数を増やせば現実的には衝突しなくなるでしょう(4桁でも524ですから現実的にはほとんど衝突しません). プロトタイピングでは本質のみに絞って実装して, 瑣末なことは無視するのが良いです. 企業のR&Dにいましたから, こういう考え方に慣れてます.

以下は, でたらめなテスト用の関数の変換前と変換後です. この変換を見れば, どういうコードかというのは容易に想像出来ると思います. Pythonで200行程度, 1日で一気に駆け抜けました. macro-of-inlineに興味があるという人は, Twitterか何かでコンタクトとってきてください. コードを公開します. マクロ化まではまだまだ結構大変なのと, 一つの関数について変換するコードを書いただけですから, まだまだ先は遠いのですが, たぶん出来ると思います.

よくみるとバグってるなこれ・・・

変換前:

void fun(int x, char *y, int (*f)(int), void (*g)(char c), struct T *t, int ys[3])
{
  int z = *y;
  int *pz = &z;
  int xs[3];
  x = z;
  while (x)
  {
    int x;
    x = 0;
    x = 0;
    do
    {
      x += x;
    }
    while (x);
  }

  int alpha;
  if (*y)
  {
    t->x = f(*y);
  }
  else
  {
    g(t->x);
  }

  do
  {
    struct T t;
    t.x = 1;
  }
  while (0);
}

変換後:

void fun(int x, char *y, int (*f)(int), void (*g)(char c), struct T *t, int ys[3])
{
  int qcnH = *WyYW;
  int *Frcm = &qcnH;
  int npKN[3];
  xNnx = qcnH;
  while (xNnx)
  {
    int buij;
    buij = 0;
    buij = 0;
    do
    {
      buij += buij;
    }
    while (buij);
  }

  int hpLy;
  if (*WyYW)
  {
    BvJB->x = cFcI(*WyYW);
  }
  else
  {
    ISMw(BvJB->x);
  }

  do
  {
    struct T zfTS;
    zfTS.x = 1;
  }
  while (0);
}

(追記)

修正した. 渡されてきた関数ポインタは不変なので, renameする必要がない.

inline void fun(int x, char *y, int (*f)(int), void (*g)(char c), struct T *t, int ys[3])
{
  int fVlu = *OChM;
  int *tbsd = &fVlu;
  int PXIR[3];
  yytA = fVlu;
  while (yytA)
  {
    int wzHg;
    wzHg = 0;
    wzHg = 0;
    do
    {
      wzHg += wzHg;
    }
    while (wzHg);
  }

  int uVge;
  if (*OChM)
  {
    owYB->x = f(*OChM);
  }
  else
  {
    g(owYB->x);
  }

  do
  {
    struct T PAWi;
    PAWi.x = 1;
  }
  while (0);
}
広告を非表示にする