技術

3.3. 式のコード生成

LLVMによるプログラミング言語の実装チュートリアル日本語訳
第3章 万華鏡: LLVM IRコードの生成
第3節 式のコード生成

式ノードにのためのLLVMコードの生成は、とても直接的に行える。
我々の4種類全ての式ノードについてのコードは、コメント入りで45行以下である。
まず数値リテラルから始める。

Value *NumberExprAST::Codegen() {
  return ConstantFP::get(getGlobalContext(), APFloat(Val));
}

LLVM IRでは、数値定数はConstantFPクラスによって表現される。
ConstantFPクラスは、その内部にAPFloatを持ち、その中で数値を保持する。
(APFloatには任意の精度の浮動小数点数の定数を保持する能力がある。)
このコードは基本的に、ConstantFPを生成し、返してるだけである。
LLVM IRでは、定数は、すべてお互いにユニークであり共有される点に注意。
なので、”new foo(…)”とか”foo::Create(…)”ではなく、”foo::get(…)”という表現をAPIは用いている。

Value *VariableExprAST::Codegen() {
  // 関数の中からこの変数を探す
  Value *V = NamedValues[Name];
  return V ? V : ErrorV("Unknown variable name");
}

変数への参照もまた、LLVMにおいては簡単である。
簡易版の万華鏡では、変数はすでにどこかで発行済みであり、その値が使用できる状態になっていると仮定している。
この段階では、NamedValuesマップの中に存在しうる値は、関数の引数だけである。
上のコードでは、与えられた名前がマップのなかに存在するか単純にチェックして、その値を返している。
(もし存在しなければ、不明な変数が参照されたことになる。)
後の章で、ループ帰納変数(loop induction variables)とローカル変数のサポートをシンボルテーブルに追加する。
訳注: ループ帰納変数はループ変数の事であるが、後の章で追加する最適化サポート絡み(帰納変数解析とか)で敢えて帰納という言葉が使われているんだと思う。

Value *BinaryExprAST::Codegen() {
  Value *L = LHS->Codegen();
  Value *R = RHS->Codegen();
  if (L == 0 || R == 0) return 0;

  switch (Op) {
  case '+': return Builder.CreateFAdd(L, R, "addtmp");
  case '-': return Builder.CreateFSub(L, R, "subtmp");
  case '*': return Builder.CreateFMul(L, R, "multmp");
  case '<':
    L = Builder.CreateFCmpULT(L, R, "cmptmp");
    // boolの0/1をdoubleの0.0/1.0に変換する。
    return Builder.CreateUIToFP(L, Type::getDoubleTy(getGlobalContext()),
                                "booltmp");
  default: return ErrorV("invalid binary operator");
  }
}

二項演算子についてはより興味深い。
基本的には、式の左手側のためのコードを再帰的に生成し、それから右手側を処理し、そして二項演算式(binary expression)の結果を計算する、というアイデアに基づいている。
このコードでは、正しいLLVM命令を生成するために、演算子を見てswitch文で単純に処理を切り替えている。

上の例では、LLVM Builderクラスが、その値を見はじめているところである。(the LLVM builder class is starting to show its value.)
IRBuilderは、直近で生成した命令をどこに挿入したかを知っているので、あなたがすべきことはどの命令を(例えばCreateFAdd)生成するのか、どのオペランド(上記のコードではLやRとなってる部分)を使用するのか、そして必須ではないがその名前を指定する事だけである。

LLVMの素晴らしい点は、あくまでもその名前はヒントに過ぎないという事である。
事実、上のコードによって複数の”addtmp”変数が生成された場合、LLVMは自動的にインクリメントされたユニークな通し番号をその接尾辞として追加する。
命令にローカル変数名を付けるかどうかは全く自由であるが、IRのダンプを読むときに楽になるだろう。

LLVMの命令には、厳格なルールが強制されている。
例えば、加算命令の左側と右側オペランドは、同じ型(type)でなければならない。
そして、戻り値の型は、オペランドの型と合致してなければならない。
万華鏡では全ての値がdoubleなので、加算や減算、乗算のコードはとてもシンプルになる。
訳注: LLVM IRにおける加算命令は以下のような形をとる。

<result> = add <ty> <op1>, <op2>

op1, op2が左側、右側オペランドで、tyが型、resultがその結果である。
原文は、op1とop2の型が同じで、さらに2つのオペランドとresultが同じ型でなければならないという事を言ってるんだと思う。
LLVMの命令のリファレンスは以下のページにある。
http://llvm.org/docs/LangRef.html

一方、LLVMでは、fcmp命令は常に’i1’型(1bit整数)の値を返すと定められている。
万華鏡では、これを0.0と1.0という値で扱いたい。
このような動作をさせるために、fcmp命令をuitofp命令と一緒に使っている。
この命令は、入力された整数を符合なし整数として扱い、浮動小数点数へ変換する。
もし、uitofpでは無くsitofp命令を使ったとしたら、万華鏡の”<"演算子は、入力値に応じて0.0か-1.0の値を返してしまうだろう。 訳注: fcmp命令は以下の形をとる。

<result> = fcmp <cond> <ty> <op1>, <op2>

condには、演算子を表すキーワードを指定する。
例えば”小なりイコール”なら、”ole”を指定する、という風にキーワードが決まっている。
あとは、addと同じだが、結果が1bit整数で返ってくる点に注意。
uitofpとsitofpは以下の形をとる。

<result> = uitofp <ty> <value> to <ty2>
<result> = sitofp <ty> <value> to <ty2>

どちらも整数を浮動小数点数に変換する命令だが、uitofpは入力を符合なし整数として、sitofpは入力を符号付き整数として扱う。

Value *CallExprAST::Codegen() {
  // グローバルモジュールテーブルから名前を探す。
  Function *CalleeF = TheModule->getFunction(Callee);
  if (CalleeF == 0)
    return ErrorV("Unknown function referenced");

  // 引数の数のチェック。
  if (CalleeF->arg_size() != Args.size())
    return ErrorV("Incorrect # arguments passed");

  std::vector<Value*> ArgsV;
  for (unsigned i = 0, e = Args.size(); i != e; ++i) {
    ArgsV.push_back(Args[i]->Codegen());
    if (ArgsV.back() == 0) return 0;
  }

  return Builder.CreateCall(CalleeF, ArgsV, "calltmp");
}

関数呼び出しのためのコード生成は、LLVMを用いるととても素直に書ける。
上のコードではまず、関数名を解決するためにLLVMモジュールのシンボルテーブルから関数名を探す。
LLVMモジュールは、JITコンパイルされた全ての関数を保持しているコンテナである事を思い出そう。
各関数に、ユーザが指定するものと同じ名前を与えることによって、関数名を解決するためにLLVMのシンボルテーブルを使用することが出来る。

呼び出す関数を取得できたら、その関数に渡される各引数に対して再帰的にコード生成を行い、LLVMのcall命令を生成する。
LLVMはデフォルトで、ネイティブなCの呼出規約を使用していることに注意。
なのでcall命令を使って”sin”や”cos”のような標準ライブラリ関数を直接呼び出せる。
訳注: call命令は以下の形をとる。

<result> = [tail] call [cconv] [ret attrs] <ty> [<fnty>*] <fnptrval>(<function args>) [fn attrs]

tyが戻り値の型、function argsが引数のリストになるみたい。

これで、今のところ万華鏡が持つ4種類の基本的な式についての処理は完了した。
これにさらに何か追加するのを尻込みする必要はない。
例えば、LLVM言語リファレンスを見ると、我々のシンプルなフレームワークにとても簡単に追加出来るような、面白い命令がいくつか見つかるだろう。

コメントを残す

メールアドレスが公開されることはありません。



※画像をクリックして別の画像を表示

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください