その他

5.3.5. forループのためのコード生成

LLVMによるプログラミング言語の実装チュートリアル日本語訳
第5章 万華鏡: 言語の拡張: 制御フロー
第3.5節 forループのためのコード生成

コード生成の最初の部分は、とても簡単である。
ループ変数の初期値ための開始式を出力するだけである。(we just output the start expression for the loop value:)

Value *ForExprAST::Codegen() {
  // スコープ内の変数なしで、まず開始コードを出力する。
  Value *StartVal = Start->Codegen();
  if (StartVal == 0) return 0;

とりあえずこれは置いといて、次はループの本文を開始するためLLVMの基本ブロックを準備する。
第3.4節の例だと、ループ本文全体はひとつのブロックであるが、本文のコード自体は複数のブロックから構成されうる事に注意。
(例えば、本文がさらにif/then/elseやfor/in式を含む場合。)

// ループのヘッダのために新しい基本ブロックを作成する。
// 現在のブロックの後に挿入する。
Function *TheFunction = Builder.GetInsertBlock()->getParent();
BasicBlock *PreheaderBB = Builder.GetInsertBlock();
BasicBlock *LoopBB = BasicBlock::Create(getGlobalContext(), "loop", TheFunction);

// 現在のブロックからLoopBBへの明確なフォールスルー(br命令)を挿入する。 
Builder.CreateBr(LoopBB);

このコードは、if/then/elseのとき見たものと似ている。
phiノードを生成させる必要があるので、ループの中へフォールスルーするブロックを覚えておく。
これが終わったら、ループを開始する実際のブロックと、2つのブロックの間でフォールスルーするための無条件分岐(unconditional branch)を作成する。
訳注: フォールスルーというのは、あるブロックからその次のブロックへジャンプなしに単に処理を移す事を意味していると思われる。
以前も説明したとおり、LLVM IRでは単なるフォールスルーであってもブロックの最後にbr命令を置く必要がある。

// LoopBBの挿入を開始。
Builder.SetInsertPoint(LoopBB);

// Startのためのエントリによってphiノードを開始する。
PHINode *Variable = Builder.CreatePHI(Type::getDoubleTy(getGlobalContext()), 2, VarName.c_str());
Variable->addIncoming(StartVal, PreheaderBB);

これで、ループのためのプリヘッダ(preheader)が準備できたので、ループ本体のコード生成にとりかかる。
これにはまず、挿入点を移動し、ループが使用する変数のためのphiノードを生成する。
我々は初期値(となる式)を知ってるので、それをphiノードに追加するだけである。
最終的にphiは次のループ(backedge)のための2番目の値を得るだろうが、しかしまだそれは準備できない事に注意。(それは存在しないので。)

// ループの中で、その変数はphiノードと等しいと定義される。
// それが既存の変数を上書きしてしまったら、それをリストアしなければならないので、ここで保存する。
Value *OldVal = NamedValues[VarName];
NamedValues[VarName] = Variable;

// ループの本体を生成する。これや他の式は、現在の基本ブロックを変更する可能性がある。
// 本文で計算された結果は無視するが、エラーは逃さないという事に注意。
if (Body->Codegen() == 0)
  return 0;

この部分のコードはより面白い。
forループは、シンボルテーブルに新しい変数を取り込ませる。
つまりここに来て、シンボルテーブルには関数の引数のみならずループ変数も含まれるようになったということを意味する。
これを処理するには、ループの本体のコード生成をする前に、ループ変数をその名前で現在の値として追加する。
外のスコープに同じ名前の変数があってもよいという事に注意。
これをエラーとすることは簡単(すでにVarNameのエントリがあるか確認して、エラーを生成しnullを返すだけである)だが、我々は変数を覆い隠すことを許可するようにした。
これを正しく処理するために、潜在的に覆い隠される値をOldValとして保持しておく。(もし覆い隠される変数が無ければそれは単にnullになる。)

ループ変数がシンボルテーブルにセットされたら、本文のCodegenメソッドを再帰的に呼び出す。
これで、本文でループ変数を使用できるようになる。
ループ変数に対するどのような参照があっても、シンボルテーブルから適切に見つけられるだろう。

// ステップ値の生成。
Value *StepVal;
if (Step) {
  StepVal = Step->Codegen();
  if (StepVal == 0) return 0;
} else {
  // 指定されなければ1.0を使う。
  StepVal = ConstantFP::get(getGlobalContext(), APFloat(1.0));
}

Value *NextVar = Builder.CreateFAdd(Variable, StepVal, "nextvar");

これで本文のコードが生成されたので、ステップ値、もしくは指定が無ければ1.0を加算することによって次のループ変数の値を計算する。
NextVarは、ループの次の反復におけるループ変数の値である。

// 終了条件を計算する。
Value *EndCond = End->Codegen();
if (EndCond == 0) return EndCond;

// 0.0と比較することによって、条件を真偽値に変換する。
EndCond = Builder.CreateFCmpONE(EndCond,
                            ConstantFP::get(getGlobalContext(), APFloat(0.0)),
                                "loopcond");

最後に、ループを終了させるべきかどうかを決定するために、ループの終了条件を評価する。
これは、if/then/elseにおける条件の評価と酷似している。

// ループ後のブロックを生成し挿入する。
BasicBlock *LoopEndBB = Builder.GetInsertBlock();
BasicBlock *AfterBB = BasicBlock::Create(getGlobalContext(), "afterloop", TheFunction);

// LoopEndBBの末尾へ、条件分岐を挿入する。
Builder.CreateCondBr(EndCond, LoopBB, AfterBB);

// これ以降のコードは、AfterBBの後に挿入される。
Builder.SetInsertPoint(AfterBB);

あとはループの本文のコードの完成によって、制御フローを締めくくる必要があるだけである。
このコードはendブロックを記憶し(phiノードのために)、そしてループを終了するためのafterloopブロックを生成する。
終了条件の値を元に、ループをまだ続けるか、ループを終わらせるかを選択するための条件分岐を生成する。
この後のコードは、afterloopブロックの後に出力させたいので、挿入点をAfterBBにセットする。

  // 後方のため、phiノードへ新しいエントリを追加する。
  Variable->addIncoming(NextVar, LoopEndBB);

  // 覆い隠されていた変数を復元する。
  if (OldVal)
    NamedValues[VarName] = OldVal;
  else
    NamedValues.erase(VarName);

  // for式は常に0.0を返す。
  return Constant::getNullValue(Type::getDoubleTy(getGlobalContext()));
}

最後のコードは、いくつかの後片付けを処理する。
ここではNextVar値を持っているので、ループのphiノードへ次の値を追加することが出来る。
その後、forループの後はスコープ外となるので、シンボルテーブルからループ変数を取り除く。
最後に、forループは常に0.0を返すようコード生成するので、それがForExprAST::Codegenの戻り値となる。

これによって、このチュートリアルにおける”万華鏡への制御フローの追加”の章を完了する。
この章では、2つの制御フロー構造を追加した。
そして、それらを、フロントエンドの実装者に対して、特に知っていて欲しいLLLVMの性質について興味を起こさせるために使用した。
我々のサーガの次の章では、ちょっと逸脱して、我々の粗末で純真な言語にユーザー定義演算子を追加する。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です



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

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