例外

gistのはてなダイアリー埋め込みテストも兼ねて。

LLVMでの例外の扱いは基本的にgccと同じようです。libgccを使って投げた例外を受けることができます。
gccの例外をゼロから実装するにはぱーそなりてぃふぁんくしょんというとても面倒な関数を書かないといけませんので、手を抜いてlibstdc++(g++のランタイム)とlibgnat(GNATのランタイム)を使って例外を投げてみました。


↑とほぼ等価なC++コード

#include <cstdio>
int main()
{
  try {
    throw 0;
  } catch(int) {
    std::puts("int");
  } catch(float) {
    std::puts("float");
  }
  return 0;
}


↑とほぼ等価なAdaコード

with GNAT.IO;
procedure main is
begin
   raise Program_Error with "Message";
exception
   when Program_Error => GNAT.IO.Put_Line ("Program_Error");
   when Constraint_Error => GNAT.IO.Put_Line ("Constraint_Error");
end main;

try ... catchの...の部分をinvokeで包みます。invokeにはひとつの関数呼び出ししか書けませんが、そこはLLVMですので一度しか使われていない関数は必ずインライン展開されるはず。
例外発生時はinvokeのunwindのところに書いたラベルに飛んできますが、ここで面白いのが、llvm.eh.selectorという「関数」呼び出しがdwarf2の例外テーブルに化けるところ。
それはいいのですが、複数種類の例外をハンドリングするとき一旦共通の例外ハンドラに飛んでから例外の種類毎に分岐する形になってるような?gccコンパイルしたときは直接例外の種類ごとのハンドラに飛んでませんでしたっけ……記憶違いでしょうか。
llvm.eh.selectorの返値は序数ではないので、ここみたいに定数値と比較してはダメです。平気で1を飛ばして3とか4が返ってきますのでllvm.eh.typeid.forの返してくる値と比較しましょう。こいつは名前に反してtypeidでもなんでもなくて、単にllvm.eh.selectorに渡した例外の種類を表すi8*とllvm.eh.selectorの返値との対応を取ってくれるだけです。(例外オブジェクトに対応するハンドラを探すのはぱーそなりてぃふぁんくしょん。つまりLLVMは「例外の種類を表すi8*」をユーザー定義マジックナンバーとして扱ってるだけみたい)
あとまあコードでは例外ハンドラを更にinvokeで包んで、確実に__cxa_end_catchだか__gnat_end_handlerだかが呼ばれるようにしてますが、これは単にlibstdc++やlibgnatが例外ハンドラの最後でこれらの関数を呼ばないといけない作りになっているというだけで(ついでにgccの出力に似せただけで)、必須ではないです。