class ImmAsmOperand<string prefix, int width, string suffix> : AsmOperandClass { + let Name = prefix # "Imm" # width # suffix; + let RenderMethod = "addImmOperands"; + let DiagnosticType = "Invalid" # Name; +}+
diff --git a/index.html b/index.html index fc340aa..33b178f 100644 --- a/index.html +++ b/index.html @@ -581,7 +581,9 @@ body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-b
この文書はAsciiDocを用いて執筆されています。
+この文書はhttps://asciidoctor.org/[Asciidoctor]を用いて執筆されています。 +記述方法はhttps://asciidoctor.org/docs/user-manual/[Asciidoctor User Manual]を +参考にしてください。
この文書はGitによって管理されています。 @@ -598,7 +600,8 @@ body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-b
本文書の内容は筆者が独自に調査したものです。 疑う余地なく誤りが含まれます。誤りに気づかれた方はGitHubリポジトリなどを通じて -ご連絡ください。
+ご連絡ください。なお誤っていそうな部分についてはAsciidoctorのコメント機能を用いて +コメントを残しています。FIXME
というキーワードでソースコードの全文検索をしてください。
LLVM coreは基本的にC によって記述されています。一方で、多くの箇所で共通する処理などは -独自のDSL(ドメイン固有言語)であるTableGenを用いて記述し `llvm-tblgen` という -ソフトウェアを用いてこれをC コードに変換しています。 +
LLVM coreは基本的にC++によって記述されています。一方で、多くの箇所で共通する処理などは
+独自のDSL(ドメイン固有言語)であるTableGenを用いて記述し llvm-tblgen
という
+ソフトウェアを用いてこれをC++コードに変換しています。
こうすることによって記述量を減らし、ヒューマンエラーを少なくするという考え方
のようです[21]。
LLVMバックエンドでは、アーキテクチャが持つレジスタや命令などの情報をTableGenによって 記述します。大まかに言って、TableGenで書ける場所はTableGenによって書き、 -対応できない部分をC++ で直に書くというのがLLVM coreの方針のようです。 +対応できない部分をC++で直に書くというのがLLVM coreの方針のようです。 ここでは、簡単なアセンブラを実装するために最低限必要なTableGenファイルを追加します。 内訳は次のとおりです。
Inst
フィールドにエンコーディングを設定することで、
+TableGenにエンコードの処理を移譲することができます[6]。
続いて即値を用いる命令を見ます。例として addi
を取り上げます。
+addi
は8bit符号付き即値をオペランドに取ります。まずこれを定義します。
class ImmAsmOperand<string prefix, int width, string suffix> : AsmOperandClass { + let Name = prefix # "Imm" # width # suffix; + let RenderMethod = "addImmOperands"; + let DiagnosticType = "Invalid" # Name; +}+
class SImmAsmOperand<int width, string suffix = ""> + : ImmAsmOperand<"S", width, suffix> { +}+
def simm8 : Operand<i16> { + let ParserMatchClass = SImmAsmOperand<8>; +}+
続いて命令の「形」を定義します。 addi
は24bit I形式です。
class CAHPInst24I<bits<8> opcode, dag outs, dag ins, string opcodestr, string argstr> +: CAHPInst24<outs, ins, opcodestr, argstr> { + bits<4> rd; + bits<4> rs1; + bits<8> imm; + + let Inst{23-16} = imm; + let Inst{15-12} = rs1; + let Inst{11-8} = rd; + let Inst{7-0} = opcode; +}+
最後に、これを用いて addi
を定義します。
def ADDI : CAHPInst24I<0b11000011, (outs GPR:$rd), (ins GPR:$rs1, simm8:$imm), + "addi", "$rd, $rs1, $imm">;+
add
の際には GPR
とした第三オペランドが simm8
となっています。
+これによって、この部分に符号付き8bit即値が来ることを指定しています。
即値のうち、下位1bitが0になるものは _lsb0
というサフィックスを名前につけ区別しておきます。
+uimm7_lsb0
と simm11_lsb0
がそれに当たります。
+後々、C++コードにてこの制限が守られているかをチェックします。
add2
のような2オペランドの命令を記述する場合、上の方法では問題があります。
+というのも add2
の第一オペランドは入力であると同時に出力先でもあるためです。
+このような場合は次のように Constraints
フィールドにその旨を記述します。
let Constraints = "$rd = $rd_w" in { + def ADD2 : CAHPInst16R<0b10000000, (outs GPR:$rd_w), (ins GPR:$rd, GPR:$rs), + "add2", "$rd, $rs">; +}+
なおTableGenでは let
で囲むレコードが一つの場合は括弧 { }
は必要ありません。
+また let
で外からフィールドを上書きするのと、 def
の中身に記載するのとで意味は
+変わりません。すなわち、上のコードは次の2通りと意味は異なりません[21]。
let Constraints = "$rd = $rd_w" in +def ADD2 : CAHPInst16R<0b10000000, (outs GPR:$rd_w), (ins GPR:$rd, GPR:$rs), + "add2", "$rd, $rs">;+
def ADD2 : CAHPInst16R<0b10000000, (outs GPR:$rd_w), (ins GPR:$rd, GPR:$rs), + "add2", "$rd, $rs"> { + let Constraints = "$rd = $rd_w"; +}+
必要なTableGenファイルを追加した後、
+これらのTableGenファイルが正しいかどうか llvm-tblgen
を用いて確認します。
$ bin/llvm-tblgen -I ../llvm/lib/Target/CAHP/ -I ../llvm/include/ -I ../llvm/lib/Target/ ../llvm/lib/Target/CAHP/CAHP.td+
MCTargetDesc
を追加するアセンブラ本体のC++コードを作成します。ここでは、
+アセンブリのエンコードからバイナリ生成部分を担当する MCTargetDesc
ディレクトリを追加し、
+必要なファイルを揃えます。複数のクラスを定義しますが、それらは全て
+MCTargetDesc/CAHPMCTargetDesc.cpp
にある LLVMInitializeCAHPTargetMC
+関数でLLVM coreに登録されます。
定義するクラスは次のとおりです。
+CAHPMCAsmInfo
CAHPMCInstrInfo
CAHPMCRegisterInfo
CAHPMCSubtargetInfo
CAHPMCCodeEmitter
CAHPAsmBackend
CAHPELFObjectWriter
順に説明します。
+CAHPMCAsmInfo
にはアセンブリがどのように表記されるかを主に記述します。
+MCTargetDesc/CAHPMCAsmInfo.{h,cpp}
に記述します。
CAHPMCInstrInfo
は先程記述したTableGenファイルから、
+TableGenによって InitCAHPMCInstrInfo
関数として自動的に生成されます。
+CAHPMCTargetDesc.cpp
内でこれを呼び出して作成します。
CAHPMCRegisterInfo
も同様に自動的に生成されます。
+InitCAHPMCRegisterInfo
関数を呼び出します。なおこの関数の第二引数には
+関数の戻りアドレスが入るレジスタを指定します[7]。
+CAHPではx0を表す CAHP::X0
を渡すことになります。
CAHPMCSubtargetInfo
も同様に自動生成されます。
+createCAHPMCSubtargetInfoImpl
を呼び出します。この関数の第二引数には
+CAHP.td
で ProcessorModel
として定義したCPUの名前を指定します。
CAHPMCCodeEmitter
はアセンブリのエンコード作業を行います。
+MCTargetDesc/CAHPMCCodeEmitter.cpp
に記述します。
+主要なエンコード処理はTableGenによって自動生成された
+getBinaryCodeForInstr
を CAHPMCCodeEmitter::encodeInstruction
+から呼び出すことによって行われます。
+この関数は CAHPGenMCCodeEmitter.inc
というファイルに定義されるため、
+これを MCTargetDesc/CAHPMCCodeEmitter.cpp
末尾で #include
しておきます。
CAHPAsmBackend
にはオブジェクトファイルを作成する際に必要な
+fixupの操作や指定バイト数分の無効命令を書き出す処理などを記述します。
+MCTargetDesc/CAHPAsmBackend.cpp
に記述します。
+fixupについては後ほど実装するためここではスタブにしておきます。
CAHPELFObjectWriter
にはELFファイル(の特にヘッダ)を作成する際に必要な情報を記載します。
+このクラスは LLVMInitializeCAHPTargetMC
ではなく
+CAHPAsmBackend
の createObjectTargetWriter
メンバ関数として紐付けられます。
+親クラス MCELFObjectTargetWriter
のコンストラクタに、
+CAHPマシンを表す ELF::EM_CAHP
と、 .rel
ではなく .rela
を使用する旨を示す
+true
を渡しておきます[8]。
+また getRelocType
メンバ関数はどのような再配置を行うかを見繕うためのものですが、
+ここではスタブにしておきます。
上記を実装してビルドします。一度使ってみましょう。
+LLVMのアセンブラを単体で使う場合は llvm-mc
というコマンドを使用します。
+次のようにすると foo.s
というアセンブリファイルをオブジェクトファイルに
+変換できます。
$ bin/llvm-mc -arch=cahp -filetype=obj foo.s +bin/llvm-mc: error: this target does not support assembly parsing.+
このようなエラーメッセージが出れば成功です[9]。 +続いてアセンブリをパーズする部分を開発します。
+CAHPAsmParser
を追加するアセンブリのパーズは CAHPAsmParser
が取り仕切っています。