From 19cd6e079b56ccef05299270a6e0df24adbd60a4 Mon Sep 17 00:00:00 2001 From: "Ushitora Anqou (via Travis CI)" Date: Mon, 30 Mar 2020 14:43:39 +0000 Subject: [PATCH] Deploy ushitora-anqou/write-your-llvm-backend to github.com/ushitora-anqou/write-your-llvm-backend.git:gh-pages --- index.html | 198 +++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 184 insertions(+), 14 deletions(-) diff --git a/index.html b/index.html index 5f4b636..fc340aa 100644 --- a/index.html +++ b/index.html @@ -560,6 +560,19 @@ body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-b
  • だ・である調をです・ます調に変える。

  • +
  • +

    実際にやってみる。

    +
    +
      +
    • +

      現状過去の作業ログを切り貼りしながら書いているので通してちゃんと動くかは良くわからない。

      +
    • +
    • +

      ついでにLLVM v10.0.0に対応させる。

      +
    • +
    +
    +
  • @@ -580,7 +593,7 @@ body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-b GitHubリポジトリにて公開しています

    -

    この作品は、クリエイティブ・コモンズの 表示 4.0 国際 ライセンスで提供されています。ライセンスの写しをご覧になるには、 http://creativecommons.org/licenses/by/4.0/ をご覧頂くか、Creative Commons, PO Box 1866, Mountain View, CA 94042, USA までお手紙をお送りください。

    +

    この作品は、クリエイティブ・コモンズの 表示 4.0 国際 ライセンスで提供されています。ライセンスの写しをご覧になるには、 http://creativecommons.org/licenses/by/4.0/ をご覧頂くか、Creative Commons, PO Box 1866, Mountain View, CA 94042, USA までお手紙をお送りください[1]

    本文書の内容は筆者が独自に調査したものです。 @@ -800,7 +813,7 @@ RegStateやframe index・register scavengerなどの説明が貴重。

    • -

      TriCoreというアーキテクチャ用のバックエンドを書いたという論文。スライドもある[117]。ソースコードもGitHub上に上がっているが、どれが公式かわからない[1]

      +

      TriCoreというアーキテクチャ用のバックエンドを書いたという論文。スライドもある[117]。ソースコードもGitHub上に上がっているが、どれが公式かわからない[2]

    @@ -820,7 +833,7 @@ RegStateやframe index・register scavengerなどの説明が貴重。

    • -

      「コンパイラ勉強会」[2]での、LLVMバックエンドの大きな流れ(特に命令選択)について概観した日本語スライド。

      +

      「コンパイラ勉強会」[3]での、LLVMバックエンドの大きな流れ(特に命令選択)について概観した日本語スライド。

    @@ -839,6 +852,9 @@ RegStateやframe index・register scavengerなどの説明が貴重。

  • 数少ない日本語資料。Passやバックエンドの各クラスについて説明している。[31]と合わせて大まかな流れを掴むのに良い。

  • +
  • +

    ただし書籍中で作成されているバックエンドは機能が制限されており、またコードベースも多少古い。

    +
  • @@ -868,7 +884,7 @@ RegStateやframe index・register scavengerなどの説明が貴重。

    @@ -1117,6 +1133,9 @@ $ bin/llvm-lit -as --filter 'CAHP' --debug test # デバッグ情報を表示す

    アセンブラを作る

    +

    この章ではLLVMバックエンドの一部としてアセンブラを実装します。 具体的にはLLVMのMCLayerを実装し、アセンブリからオブジェクトファイルへの変換を可能にします。 一度にアセンブラ全体を作るのは難しいため、まずレジスタのみを使用する演算命令に絞って実装し、 @@ -1125,12 +1144,25 @@ $ bin/llvm-lit -as --filter 'CAHP' --debug test # デバッグ情報を表示す

    TableGenファイルを追加する

    -

    LLVMのDSLであるTableGenを使用してCAHPのレジスタや命令について記述します。 -追加スべきTableGenファイルはおおよそ次のとおりです。

    +

    LLVM coreは基本的にC によって記述されています。一方で、多くの箇所で共通する処理などは +独自のDSL(ドメイン固有言語)であるTableGenを用いて記述し `llvm-tblgen` という +ソフトウェアを用いてこれをC コードに変換しています。 +こうすることによって記述量を減らし、ヒューマンエラーを少なくするという考え方 +のようです[21]

    +
    +
    +

    LLVMバックエンドでは、アーキテクチャが持つレジスタや命令などの情報をTableGenによって +記述します。大まかに言って、TableGenで書ける場所はTableGenによって書き、 +対応できない部分をC++ で直に書くというのがLLVM coreの方針のようです。 +ここでは、簡単なアセンブラを実装するために最低限必要なTableGenファイルを追加します。 +内訳は次のとおりです。

    • +

      CAHP.td: 下のTableGenファイルをincludeし、その他もろもろを定義。

      +
    • +
    • CAHPRegisterInfo.td: レジスタを定義。

    • @@ -1139,11 +1171,146 @@ $ bin/llvm-lit -as --filter 'CAHP' --debug test # デバッグ情報を表示す
    • CAHPInstrInfo.td: 命令を定義。

    • -
    • -

      CAHP.td: 全体に関することを定義。

      -
    +
    +

    順に説明します。 CAHP.td がTableGenファイル全体をまとめているTableGenファイルで、 +内部では include を使って他のファイルを読み込んでいます。

    +
    +
    +
    +
    include "llvm/Target/Target.td"
    +
    +
    +
    +
    +
    include "CAHPRegisterInfo.td"
    +include "CAHPInstrInfo.td"
    +
    +
    +
    +

    また同時に、今回想定するプロセッサを表す ProcessorModel や、 +現在実装しているターゲットの CAHP について定義しています。

    +
    +
    +

    CAHPRegisterInfo.td ではCAHPに存在するレジスタを定義します。 +まず Register を継承して class CAHPReg を作り、これに基本的なレジスタの性質をもたせます。 +ついで class CAHPReg の実体として X0 から X15 を作成します。 +alt にはレジスタの別名を指定します。 +最後に、レジスタをまとめて RegisterClass である GPR +(General Purpose Register; 汎用レジスタの意)を定義します。 +このあと命令を定義する際にはこの RegisterClass 単位で指定します。 +ここでレジスタを並べる順番が先であるほどレジスタ割り付けで割り付けられやすいため、 +caller-savedなもの(使ってもspill outが起こりにくいもの)を先に並べておきます。

    +
    +
    +

    GPR と同様に SP という RegisterClass も作成し、 X1 、 +つまりスタックポインタを表すレジスタのみを追加しておきます。 +この RegisterClass を命令のオペランドに指定することで +lwspswsp などの「スタックポインタのみを取る命令」を表現することができます。

    +
    +
    +

    命令は CAHPInstrFormats.tdCAHPInstrInfo.td に分けて記述します。 +CAHPInstrFormats.td ではおおよその命令の「形」を定義しておき、 +CAHPInstrInfo.td でそれを具体化します。言葉で言ってもわかりにくいので、コードで見ます。 +例えば24bit長の加算命令は次のように定義されます。 +まずCAHPの命令全体に共通する事項を class CAHPInst として定義します。

    +
    +
    +
    +
    class CAHPInst<dag outs, dag ins, string opcodestr, string argstr, list<dag> pattern = []>
    +: Instruction {
    +  let Namespace = "CAHP";
    +
    +  dag OutOperandList = outs;
    +  dag InOperandList = ins;
    +
    +  let AsmString = opcodestr # "\t" # argstr;
    +
    +  // Matching patterns used when converting SelectionDAG into MachineDAG.
    +  let Pattern = pattern;
    +}
    +
    +
    +
    +

    次に、CAHPの24bit命令に共通する事項を class CAHPInst を継承した +class CAHP24Inst として定義します。

    +
    +
    +
    +
    // 24-bit instruction format.
    +class CAHPInst24<dag outs, dag ins, string opcodestr, string argstr, list<dag> pattern = []>
    +: CAHPInst<outs, ins, opcodestr, argstr, pattern> {
    +  let Size = 3;
    +  bits<24> Inst;
    +}
    +
    +
    +
    +

    さらに、24bit長加算命令の「形」である24bit R形式(オペランドにレジスタを3つとる)を +class CAHPInst24R として定義します。 class CAHPInst24 を継承します。

    +
    +
    +
    +
    // 24-bit R-instruction format.
    +class CAHPInst24R<bits<8> opcode, dag outs, dag ins, string opcodestr, string argstr>
    +: CAHPInst24<outs, ins, opcodestr, argstr> {
    +  bits<4> rd;
    +  bits<4> rs1;
    +  bits<4> rs2;
    +
    +  let Inst{23-20} = 0;
    +  let Inst{19-16} = rs2;
    +  let Inst{15-12} = rs1;
    +  let Inst{11-8} = rd;
    +  let Inst{7-0} = opcode;
    +}
    +
    +
    +
    +

    最後にこれを使って加算命令 ADD を定義します。

    +
    +
    +
    +
    def ADD : CAHPInst24R<0b00000001, (outs GPR:$rd), (ins GPR:$rs1, GPR:$rs2),
    +                      "add", "$rd, $rs1, $rs2">;
    +
    +
    +
    +

    上記の継承による構造を展開すると、結局 class Instruction を使って +次のような定義を行ったことになります。

    +
    +
    +
    +
    def ADD : Instruction {
    +  let Namespace = "CAHP";
    +
    +  let Pattern = [];
    +
    +  let Size = 3; // 命令長は8bit * 3 = 24bit
    +  bits<24> Inst;
    +
    +  bits<4> rd;  // オペランドrdは4bit
    +  bits<4> rs1; // オペランドrs1は4bit
    +  bits<4> rs2; // オペランドrs2は4bit
    +
    +  // 命令のエンコーディングは次の通り。
    +  let Inst{23-20} = 0;
    +  let Inst{19-16} = rs2;
    +  let Inst{15-12} = rs1;
    +  let Inst{11-8} = rd;
    +  let Inst{7-0} = 0b00000001;
    +
    +  // 出力はレジスタクラスGPRのrdに入る。
    +  dag OutOperandList = (outs GPR:$rd);
    +  // 入力はレジスタクラスGPRのrs1とrs2に入る。
    +  dag InOperandList = (ins GPR:$rs1, GPR:$rs2);
    +
    +  // アセンブリ上では「add rd, rs1, rs2」という形で与えられる。
    +  let AsmString = "add\t$rd, $rs1, $rs2";
    +}
    +
    +

    MCTargetDesc を追加する

    @@ -1878,21 +2045,24 @@ $ bin/llvm-lit -as --filter 'CAHP' --debug test # デバッグ情報を表示す

    -1. 論文とスライドも怪しいものだが、著者が一致しているので多分正しいだろう。 +1. この 段落はクリエイティブ・コモンズより引用。
    -2. これとは別の発表で「コンパイラ開発してない人生はFAKE」という名言が飛び出した勉強会[114]。 +2. 論文とスライドも怪しいものだが、著者が一致しているので多分正しいだろう。
    -3. LLVMバックエンドの開発を円滑にするためのアーキテクチャなのではと思うほどに分かりやすい。 +3. これとは別の発表で「コンパイラ開発してない人生はFAKE」という名言が飛び出した勉強会[114]
    -4. 後のSparcについて[116] にて指摘されているように、商業的に成功しなかったバックエンドほどコードが単純で分かりやすい。 +4. LLVMバックエンドの開発を円滑にするためのアーキテクチャなのではと思うほどに分かりやすい。 +
    +
    +5. 後のSparcについて[116] にて指摘されているように、商業的に成功しなかったバックエンドほどコードが単純で分かりやすい。