include "llvm/Target/Target.td"+
From 19cd6e079b56ccef05299270a6e0df24adbd60a4 Mon Sep 17 00:00:00 2001
From: "Ushitora Anqou (via Travis CI)" だ・である調をです・ます調に変える。 実際にやってみる。 現状過去の作業ログを切り貼りしながら書いているので通してちゃんと動くかは良くわからない。 ついでにLLVM v10.0.0に対応させる。
+
+
この作品は、クリエイティブ・コモンズの 表示 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などの説明が貴重。
数少ない日本語資料。Passやバックエンドの各クラスについて説明している。[31]と合わせて大まかな流れを掴むのに良い。
ただし書籍中で作成されているバックエンドは機能が制限されており、またコードベースも多少古い。
+この章ではLLVMバックエンドの一部としてアセンブラを実装します。 具体的にはLLVMのMCLayerを実装し、アセンブリからオブジェクトファイルへの変換を可能にします。 一度にアセンブラ全体を作るのは難しいため、まずレジスタのみを使用する演算命令に絞って実装し、 @@ -1125,12 +1144,25 @@ $ bin/llvm-lit -as --filter 'CAHP' --debug test # デバッグ情報を表示す
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
: レジスタを定義。
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
を命令のオペランドに指定することで
+lwsp
や swsp
などの「スタックポインタのみを取る命令」を表現することができます。
命令は CAHPInstrFormats.td
と CAHPInstrInfo.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
を追加する