├── .travis.yml
├── Gemfile
├── Gemfile.lock
├── Guardfile
├── README.md
├── cahpv3.pdf
├── draft-cahpv3.asciidoc
├── draft-rv16kv2.asciidoc
├── draft-rv32kv1.asciidoc
├── main.asciidoc
└── ref.adoc
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: ruby
2 | branches:
3 | only:
4 | - master
5 | script:
6 | - git clone https://github.com/asciidoctor/asciidoctor.git
7 | - cd asciidoctor
8 | - bundle install
9 | - cd ..
10 | - pwd
11 | - mkdir build
12 | - asciidoctor/bin/asciidoctor main.asciidoc -o build/index.html
13 | - asciidoctor/bin/asciidoctor draft-cahpv3.asciidoc -o build/draft-cahpv3.html
14 | - asciidoctor/bin/asciidoctor draft-rv16kv2.asciidoc -o build/draft-rv16kv2.html
15 | - asciidoctor/bin/asciidoctor draft-rv32kv1.asciidoc -o build/draft-rv32kv1.html
16 | deploy:
17 | provider: pages:git
18 | edge: true
19 | local_dir: build/
20 | token:
21 | secure: "bkhKJHkgGqoleoSmsVK+ZTc+zf16P0sMlev1IFw4UzaU/PBXZgtXFIcZ1gntuyra+8jyzzeJal0HyYROVx7/TJbrmgmq4fDY469t+0r5onE5OEfklfLCIAdwUIfE3QZ9SQclYOzeVmN5Qdp/+H7I0hVqz7d2RwZcGid/RethywiVKxU+4/+sKYumn0330P1xZnuPTbtjIv5ohlWtVdWsTuj3jwKP7vOkSmIL1zxG5FLhFccEveSc7UYNqYH3OTnVQcJiHejG3VaBYzGD9td93lDEjvBMEfqLfxqX13U4F1pUfLoE6dreVmtjJ5mi+wzWMqFJlYtaG6efP74SAyGRjyxZ/gYYO5CIB/FX8yjd4urfiaKeWkC++HeNlrRG3Z+lFaCuc1Z6c6w1yudweG7Ah/bTsBTX2SxD2NNy7IiDMJxv0Yr5fKMvTFG6mO7swRAtc3kgdMyNd2kAQSYRSOVo2z61Pr64mrO6NhB0ySNOTaYWCU9aBiLkvXPeMhXnT2xa3e8kXYCNP0Khehy1BfMogEUWT3HGGxaiZCw7TG+tLkrZx6d9MkTm2J0KddMtku8GVW5aQ31Z0upKUJr0953fvfXnDPreyZgcY8V/mKIxfw0Ogf8LH7PSC/t43KcXgCaOfm55HYDm8HrQeHcE1ZIoHNxIwA5fVa0YtJdRVpAjLnQ="
22 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | gem "guard"
4 | gem "guard-shell"
5 | gem "asciidoctor"
6 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | asciidoctor (2.0.10)
5 | coderay (1.1.2)
6 | ffi (1.12.2)
7 | formatador (0.2.5)
8 | guard (2.16.2)
9 | formatador (>= 0.2.4)
10 | listen (>= 2.7, < 4.0)
11 | lumberjack (>= 1.0.12, < 2.0)
12 | nenv (~> 0.1)
13 | notiffany (~> 0.0)
14 | pry (>= 0.9.12)
15 | shellany (~> 0.0)
16 | thor (>= 0.18.1)
17 | guard-compat (1.2.1)
18 | guard-shell (0.7.1)
19 | guard (>= 2.0.0)
20 | guard-compat (~> 1.0)
21 | listen (3.2.1)
22 | rb-fsevent (~> 0.10, >= 0.10.3)
23 | rb-inotify (~> 0.9, >= 0.9.10)
24 | lumberjack (1.2.4)
25 | method_source (1.0.0)
26 | nenv (0.3.0)
27 | notiffany (0.1.3)
28 | nenv (~> 0.1)
29 | shellany (~> 0.0)
30 | pry (0.13.0)
31 | coderay (~> 1.1)
32 | method_source (~> 1.0)
33 | rb-fsevent (0.10.3)
34 | rb-inotify (0.10.1)
35 | ffi (~> 1.0)
36 | shellany (0.0.1)
37 | thor (1.0.1)
38 |
39 | PLATFORMS
40 | ruby
41 |
42 | DEPENDENCIES
43 | asciidoctor
44 | guard
45 | guard-shell
46 |
47 | BUNDLED WITH
48 | 1.17.2
49 |
--------------------------------------------------------------------------------
/Guardfile:
--------------------------------------------------------------------------------
1 | Bundler.require :default
2 |
3 | guard 'shell' do
4 | watch(/^main\.asciidoc$/) {|m|
5 | Asciidoctor.convert_file m[0], safe: :safe
6 | }
7 | end
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 手を動かせばできるLLVMバックエンド チュートリアル(WIP)
2 |
3 |
4 | [](https://travis-ci.org/ushitora-anqou/write-your-llvm-backend)
5 |
6 | 自作ISAのためのLLVMバックエンドをイテレーティブに作ります。鋭意執筆中。
7 |
8 | [ここからmasterが読めます。](https://ushitora-anqou.github.io/write-your-llvm-backend/)
9 |
10 | ## [2022/05/12 追記] LLVMバックエンドを作成予定の方へ
11 |
12 | このレポジトリにある内容はかなり古くなってしまっています。『[作って学ぶコンピュータアーキテクチャ —— LLVMとRISC-Vによる低レイヤプログラミングの基礎](https://www.amazon.co.jp/dp/4297129140/)』という書籍が発売予定らしいので、そちらを参照していただいたほうが良いかもしれません。
13 |
14 | ## 下書き
15 |
16 | この文章は、2019年度に艮 鮟鱇が作成したLLVMバックエンドの、自分用メモがベースになっています。
17 | このメモをブラッシュアップしてまともな文章として公開する予定でしたが、
18 | その作業が遅れているため、一旦メモのまま公開します。
19 |
20 | - [RV32Kv1](https://ushitora-anqou.github.io/write-your-llvm-backend/draft-rv32kv1.html)
21 | - [RV16Kv2](https://ushitora-anqou.github.io/write-your-llvm-backend/draft-rv16kv2.html)
22 | - [CAHPv3](https://ushitora-anqou.github.io/write-your-llvm-backend/draft-cahpv3.html)
23 |
24 | ## ビルド方法
25 |
26 | Asciidoctorのmasterを持ってきて`asciidoctor main.asciidoc`とかする。
27 |
28 | 文章を書くときに、ファイルを更新するたびにコンパイルしたい場合は
29 | 付属の`Gemfile`と`Guardfile`を使って`bundle exec guard`とかする。
30 | 要`bundle install`。Epiphany(GNU Web)を入れて生成されたHTMLを見ると、
31 | 更新時にリロードしてくれるので便利。詳細は[公式ドキュメント](https://asciidoctor.org/docs/editing-asciidoc-with-live-preview/)を参照。
32 |
33 | ## Author
34 |
35 | [艮 鮟鱇(うしとら あんこう/Ushitora Anqou)](https://anqou.net/)
36 |
37 | ## LICENSE
38 |
39 | 
This work is licensed under a Creative Commons Attribution 4.0 International License.
40 |
--------------------------------------------------------------------------------
/cahpv3.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ushitora-anqou/write-your-llvm-backend/c9444a879fc29aadc27544055cb90c1acd9e69b0/cahpv3.pdf
--------------------------------------------------------------------------------
/draft-cahpv3.asciidoc:
--------------------------------------------------------------------------------
1 | = [下書き] LLVMバックエンド開発文書 for CAHPv3
2 | 艮鮟鱇
3 | :toc: left
4 |
5 | == これはなに
6 |
7 | 2019年にCAHPv3という自作ISA用のLLVMバックエンドを作ったときの自分とメンバ用のメモ。
8 | メモなので当然読みにくい。これをブラッシュアップしてまともな文章にする予定だったが、
9 | その作業が遅れているので、一旦メモのまま公開する。内容について質問したい場合は
10 | Twitter https://twitter.com/ushitora_anqou[@ushitora_anqou]までリプライなどを貰えれば反応するかもしれない。
11 |
12 | この文章は、前に作ったRV16Kv2及びRV32Kv1用LLVMバックエンドで得た知識を前提にして書かれている。
13 | RV16Kv2のメモはhttps://ushitora-anqou.github.io/write-your-llvm-backend/draft-rv16kv2.html[draft-rv16kv2.adoc]を参照のこと。
14 | RV32Kv1のメモはlink:https://ushitora-anqou.github.io/write-your-llvm-backend/draft-rv32kv1.html[draft-rv32kv1.adoc]を参照のこと。
15 |
16 | ソースコードはlink:https://github.com/virtualsecureplatform/llvm-cahp[GitHub]にある。
17 |
18 | ブラッシュアップはlink:https://github.com/ushitora-anqou/write-your-llvm-backend[GitHub]にて行っている。
19 |
20 | このLLVMバックエンドの開発は、もともと2019年度未踏事業において
21 | link:https://github.com/virtualsecureplatform/kvsp[Virtual Secure Platform]を開発するために行われた。
22 |
23 |
24 | == 簡単使い方
25 |
26 | === ビルドする
27 |
28 | とりあえずビルドする。ビルドには
29 |
30 | * `cmake`
31 | * `ninja`
32 | * `clang`
33 | * `clang++`
34 | * `make`
35 | * `lld`
36 |
37 | が必要。
38 |
39 | これらを入れた後 `cmake` を次のように走らせる。
40 |
41 | $ cd /path/to/llvm-project
42 | $ mkdir build
43 | $ cd build
44 | $ cmake -G Ninja \
45 | -DLLVM_ENABLE_PROJECTS="lld;clang" \
46 | -DCMAKE_BUILD_TYPE="Release" \
47 | -DLLVM_BUILD_TESTS=True \
48 | -DCMAKE_C_COMPILER=clang \
49 | -DCMAKE_CXX_COMPILER=clang++ \
50 | -DLLVM_USE_LINKER=lld \
51 | -DLLVM_TARGETS_TO_BUILD="" \
52 | -DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD="CAHP" \
53 | ../llvm
54 | $ cmake --build .
55 |
56 | === アセンブラを使う
57 |
58 | アセンブラを起動する。アセンブラは `build/bin/llvm-mc` である。
59 |
60 | ....
61 | # オブジェクトファイルにアセンブル
62 | $ bin/llvm-mc -arch=cahp -filetype=obj foo.s | od -tx1z -Ax -v
63 |
64 | # コメント表示の機械語にアセンブル
65 | $ bin/llvm-mc -arch=cahp -show-encoding foo.s
66 |
67 | # オブジェクトファイルにアセンブルしたものを逆アセンブル
68 | $ bin/llvm-mc -filetype=obj -triple=cahp foo.s | bin/llvm-objdump -d -
69 | ....
70 |
71 | === コンパイラを起動する
72 |
73 | まずランタイムライブラリをビルドする必要がある。cahp-rtレポジトリを `git clone` し
74 | `CC=/path/to/bin/clang` をつけて `make` する。
75 |
76 | ....
77 | # cahp-rt レポジトリをcloneする。
78 | $ git clone git@github.com:ushitora-anqou/cahp-rt.git
79 |
80 | # cahp-rt をビルドする。 CC 環境変数で、先程ビルドしたclangを指定する。
81 | $ cd cahp-rt
82 | $ CC=/path/to/bin/clang make
83 | ....
84 |
85 | 以下のようなCプログラム `foo.c` を `clang` を用いてコンパイルする。
86 | コンパイル時に `--sysroot` オプションを用いて、先程ビルドしたcahp-rtのディレクトリを指定する。
87 | なおバイナリサイズを小さくしたい場合は `-Oz` オプションを指定するなどすればよい。
88 |
89 | ....
90 | $ cat foo.c
91 | int hoge;
92 |
93 | int main()
94 | {
95 | hoge = 42;
96 | return hoge;
97 | }
98 |
99 | $ bin/clang -target cahp foo.c -o foo.exe --sysroot=/path/to/cahp-rt
100 | ....
101 |
102 | `llvm-readelf` を用いて `.text` その他のサイズが分かる。
103 | これがROMサイズ( `0x200 = 512` )未満であることを確認する。
104 |
105 | 実行結果はRISC-Vのもの。TODO
106 |
107 | ....
108 | $ bin/llvm-readelf -S foo.exe
109 | There are 7 section headers, starting at offset 0x10f0:
110 |
111 | Section Headers:
112 | [Nr] Name Type Address Off Size ES Flg Lk Inf Al
113 | [ 0] NULL 00000000 000000 000000 00 0 0 0
114 | [ 1] .text PROGBITS 00000000 001000 00002e 00 AX 0 0 4
115 | [ 2] .bss NOBITS 00010000 00102e 000002 00 WA 0 0 2
116 | [ 3] .comment PROGBITS 00000000 00102e 000028 01 MS 0 0 1
117 | [ 4] .symtab SYMTAB 00000000 001058 000050 10 6 2 4
118 | [ 5] .shstrtab STRTAB 00000000 0010a8 00002f 00 0 0 1
119 | [ 6] .strtab STRTAB 00000000 0010d7 000018 00 0 0 1
120 | Key to Flags:
121 | W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
122 | I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
123 | O (extra OS processing required) o (OS specific), p (processor specific)
124 | ....
125 |
126 | `llvm-objdump` を用いて逆アセンブルを行うことができる。
127 |
128 | 実行結果はRISC-Vのもの。TODO
129 |
130 | ....
131 | $ bin/llvm-objdump -d foo.exe
132 |
133 | foo.exe: file format ELF32-cahp
134 |
135 | Disassembly of section .text:
136 | 0000000000000000 _start:
137 | 0: 00 73 06 00 jal 6
138 | 4: 00 52 fe ff j -2
139 |
140 | 0000000000000008 main:
141 | 8: c1 f2 addi sp, -4
142 | a: 21 80 swsp fp, 2(sp)
143 | c: 12 e0 mov fp, sp
144 | e: 42 f2 addi fp, 4
145 | 10: 08 78 00 00 li a0, 0
146 | 14: 82 92 fc ff sw a0, -4(fp)
147 | 18: 08 78 00 00 li a0, 0
148 | 1c: 88 b2 00 00 lw a0, 0(a0)
149 | 20: 12 a0 lwsp fp, 2(sp)
150 | 22: 41 f2 addi sp, 4
151 | 24: 00 40 jr ra
152 | ....
153 |
154 | `cahp-sim` を使ってシミュレーションを行う。
155 |
156 | 実行結果はRISC-Vのもの。TODO
157 |
158 | ....
159 | $ /path/to/cahp-sim/main foo.exe 20
160 | ROM: 0000 0073
161 | ROM: 0002 0600
162 | ROM: 0004 0052
163 | ROM: 0006 FEFF
164 | ROM: 0008 C1F2
165 | ROM: 000A 2180
166 | ROM: 000C 12E0
167 | ROM: 000E 42F2
168 | ROM: 0010 0878
169 | ROM: 0012 0000
170 | ROM: 0014 8292
171 | ROM: 0016 FCFF
172 | ROM: 0018 0878
173 | ROM: 001A 0000
174 | ROM: 001C 88B2
175 | ROM: 001E 0000
176 | ROM: 0020 12A0
177 | ROM: 0022 41F2
178 | ROM: 0024 0040
179 |
180 | RAM: 0000 2A00
181 |
182 | Inst:JAL PC <= 0x0002 Reg x0 <= 0x0004 PC <= 0x0008 FLAGS(SZCV) <= 0000
183 | Inst:ADDI Reg x1 <= 0x01FA PC <= 0x000A FLAGS(SZCV) <= 0000
184 | Inst:SWSP DataRam[0x01FC] <= 0x0000 DataRam[0x01FD] <= 0x0000 PC <= 0x000C FLAGS(SZCV) <= 0010
185 | Inst:MOV Reg x2 <= 0x01FA PC <= 0x000E FLAGS(SZCV) <= 0000
186 | Inst:ADDI Reg x2 <= 0x01FE PC <= 0x0010 FLAGS(SZCV) <= 0010
187 | Inst:LI PC <= 0x0012 Reg x8 <= 0x0000 PC <= 0x0014 FLAGS(SZCV) <= 0100
188 | Inst:SW PC <= 0x0016 DataRam[0x01FA] <= 0x0000 DataRam[0x01FB] <= 0x0000 PC <= 0x0018 FLAGS(SZCV) <= 0000
189 | Inst:LI PC <= 0x001A Reg x8 <= 0x0000 PC <= 0x001C FLAGS(SZCV) <= 0100
190 | Inst:LW PC <= 0x001E Reg x8 <= 0x002A PC <= 0x0020 FLAGS(SZCV) <= 0110
191 | Inst:LWSP Reg x2 <= 0x0000 PC <= 0x0022 FLAGS(SZCV) <= 0010
192 | Inst:ADDI Reg x1 <= 0x01FE PC <= 0x0024 FLAGS(SZCV) <= 0010
193 | Inst:JR PC <= 0x0004 FLAGS(SZCV) <= 0000
194 | Inst:J PC <= 0x0006 PC <= 0x0004 FLAGS(SZCV) <= 0000
195 | Inst:J PC <= 0x0006 PC <= 0x0004 FLAGS(SZCV) <= 0000
196 | Inst:J PC <= 0x0006 PC <= 0x0004 FLAGS(SZCV) <= 0000
197 | Inst:J PC <= 0x0006 PC <= 0x0004 FLAGS(SZCV) <= 0000
198 | Inst:J PC <= 0x0006 PC <= 0x0004 FLAGS(SZCV) <= 0000
199 | Inst:J PC <= 0x0006 PC <= 0x0004 FLAGS(SZCV) <= 0000
200 | Inst:J PC <= 0x0006 PC <= 0x0004 FLAGS(SZCV) <= 0000
201 | Inst:J PC <= 0x0006 PC <= 0x0004 FLAGS(SZCV) <= 0000
202 | x0=4 x1=510 x2=0 x3=0 x4=0 x5=0 x6=0 x7=0 x8=42 x9=0 x10=0 x11=0 x12=0 x13=0 x14=0 x15=0
203 | ....
204 |
205 | `x8=42` とあるので、正しく実行されていることが分かる。
206 |
207 | == 概要
208 |
209 | これを読めば自作アーキテクチャ(CAHPv3)の機械語を出力するLLVMバックエンドを作成することができる。
210 | VSP開発のバス係数を高める意義がある。
211 |
212 | この文書はAsciiDocを用いて記述されている。
213 | 記述方法についてはリファレンス<><>を参照のこと。
214 |
215 | もう3回目なので差分しかかかない。
216 |
217 | == ところで
218 |
219 | 一度もコンパイラを書いたことがない人は、この文書を読む前に
220 | 『低レイヤを知りたい人のためのCコンパイラ作成入門』<>などで一度
221 | フルスクラッチからコンパイラを書くことをおすすめします。
222 |
223 | また<>などを参考に、
224 | LLVMではなくGCCにバックエンドを追加することも検討してみてはいかがでしょうか。
225 | 意外とGCCのほうが楽かもしれませんよ?
226 |
227 | == 参考にすべき資料
228 |
229 | === Webページ
230 |
231 | * Writing an LLVM Backend<>
232 | ** 分かりにくく読みにくい。正直あんまり見ていないが、たまに眺めると有益な情報を見つけたりもする。
233 | * The LLVM Target-Independent Code Generator<>
234 | ** <>よりもよほど参考になる。LLVMバックエンドがどのようにLLVM IRをアセンブリに落とすかが明記されている。必読。
235 | * TableGenのLLVMのドキュメント<>
236 | ** 情報量が少ない。これを読むよりも各種バックエンドのTableGenファイルを読むほうが良い。
237 | * LLVM Language Reference Manual<>
238 | ** LLVM IRについての言語リファレンス。LLVM IRの仕様などを参照できる。必要に応じて読む。
239 | * Architecture & Platform Information for Compiler Writers<>
240 | ** LLVMで公式に実装されているバックエンドに関するISAの情報が集約されている。Lanaiの言語仕様へのリンクが貴重。
241 | * RISC-V support for LLVM projects<>
242 | ** **どちゃくそに参考になる**。以下の開発はこれに基づいて行う。
243 | ** LLVMにRISC-Vサポートを追加するパッチ群。バックエンドを開発するためのチュートリアルも兼ねているらしく `docs/` 及びそれと対応したpatchが参考になる。
244 | ** またこれについて、開発者が2018 LLVM Developers' Meetingで登壇したときの動画は<>より閲覧できる。スライドは<>より閲覧できる。
245 | ** そのときのCoding Labは<>より閲覧できる。
246 | * Create an LLVM Backend for the Cpu0 Architecture<>
247 | ** Cpu0という独自アーキテクチャのLLVMバックエンドを作成するチュートリアル。多少古いが、内容が網羅的で参考になる。英語が怪しい。
248 | * FPGA開発日記<>
249 | ** Cpu0の資料<>をもとに1からRISC-Vバックエンドを作成する過程がブログエントリとして公開されている。GitHubに実装も公開されている<>。
250 | * ELVMバックエンド<>
251 | ** 限られた命令でLLVM IRの機能を達成する例として貴重。でも意外とISAはリッチだったりする。
252 | ** 作成者のスライドも参考になる<>。
253 | * 2018年度東大CPU実験で開発されたLLVM Backend<>
254 | ** これについて書かれたAdCのエントリもある<>。
255 | * Tutorial: Building a backend in 24 hours<>
256 | ** LLVMバックエンドの大まかな動きについてざっとまとめたあと、 `ret` だけが定義された最低限のLLVMバックエンド ("stub backend") を構成している。
257 | ** Instruction Selection の説明にある *Does bunch of magic and crazy pattern-matching* が好き。
258 | * 2017 LLVM Developers’ Meeting: M. Braun "Welcome to the back-end: The LLVM machine representation"<>
259 | ** スライドも公開されている<>。
260 | ** 命令選択が終わったあとの中間表現であるLLVM MIR
261 | ( `MachineFunction` や `MachineInstr` など)や、それに対する操作の解説。
262 | RegStateやframe index・register scavengerなどの説明が貴重。
263 | * Howto: Implementing LLVM Integrated Assembler<>
264 | ** LLVM上でアセンブラを書くためのチュートリアル。アセンブラ単体に焦点を絞ったものは珍しい。
265 | * Building an LLVM Backend<>
266 | ** 対応するレポジトリが<>にある。
267 | * [LLVMdev] backend documentation<>
268 | ** llvm-devメーリングリストのバックエンドのよいドキュメントは無いかというスレッド。Cpu0とTriCoreが挙げられているが、深くまで記述したものは無いという回答。
269 | * TriCore Backend<>
270 | ** TriCoreというアーキテクチャ用のバックエンドを書いたという論文。スライドもある<>。ソースコードもGitHub上に上がっているが、どれが公式かわからないfootnote:[論文とスライドも怪しいものだが、著者が一致しているので多分正しいだろう。]。
271 | * Life of an instruction in LLVM<>
272 | ** Cコードからassemblyまでの流れを概観。
273 | * LLVM Backendの紹介<>
274 | ** 「コンパイラ勉強会」footnote:[これとは別の発表で「コンパイラ開発してない人生はFAKE」という名言が飛び出した勉強会<>。]での、LLVMバックエンドの大きな流れ(特に命令選択)について概観した日本語スライド。
275 |
276 | === 書籍
277 |
278 | * 『きつねさんでもわかるLLVM〜コンパイラを自作するためのガイドブック〜』<>
279 | ** 数少ない日本語資料。Passやバックエンドの各クラスについて説明している。<>と合わせて大まかな流れを掴むのに良い。
280 |
281 | なおLLVMについてGoogleで検索していると"LLVM Cookbook"なる謎の書籍(の電子コピー)が
282 | 見つかるが、内容はLLVM公式文書のパクリのようだ<>。
283 |
284 | === バックエンド
285 |
286 | * RISC-V<>
287 | ** パッチ群が開発ドキュメントとともに公開されている<>。以降の開発はこれをベースに行う。
288 | * Lanai<>
289 | ** Googleが開発した32bit RISCの謎アーキテクチャ。全く実用されていないが、バックエンドが単純に設計されておりコメントも豊富のためかなり参考になるfootnote:[LLVMバックエンドの開発を円滑にするためのアーキテクチャなのではと思うほどに分かりやすい。]。footnote:[後のSparcについて<>
290 | にて指摘されているように、商業的に成功しなかったアーキテクチャほどコードが単純で分かりやすい。]
291 | * Sparc
292 | ** <>でも説明に使われており、コメントが豊富。
293 | * x86
294 | ** みんな大好きx86。貴重なCISCの資料であり、かつ2オペランド方式を採用する場合に実装例を与えてくれる。あと `EFLAGS` の取り回しなども参考になるが、全体的にコードは読みにくい。ただLLVMの命名規則には従うため、他のバックエンドからある程度推論をして読むのが良い。
295 |
296 | == CAHPv3アーキテクチャ仕様
297 |
298 | https://docs.google.com/spreadsheets/d/1Q9JPoZLVyqJEC3p_LBYiPLMTjGZgVoNlu-Jx3CFrLNQ/edit?usp=sharing
299 |
300 | == LLVMをテストする
301 |
302 | `llvm-lit` を使用してLLVMをテストできる。
303 |
304 | ....
305 | $ bin/llvm-lit test -s # 全てのテストを実行する
306 | $ bin/llvm-lit -s --filter 'CAHP' test # CAHPを含むテストを実行する
307 | $ bin/llvm-lit -as --filter 'CAHP' test # テスト結果を詳細に表示する
308 | $ bin/llvm-lit -as --filter 'CAHP' --debug test # デバッグ情報を表示する
309 | ....
310 |
311 | == LLVMバックエンドの流れ
312 |
313 | `CAHP*` はオーバーライドできるメンバ関数を表す。
314 |
315 | ....
316 |
317 | LLVM IR code
318 |
319 | |
320 | |
321 | v
322 |
323 | SelectionDAG (SDNode); CAHPで扱えない型・操作を含む (not legal)。
324 |
325 | |
326 | | <-- CAHPTargetLowering::CAHPTargetLowering
327 | | <-- CAHPTargetLowering::Lower*
328 | v
329 |
330 | SelectionDAG (SDNode); CAHPで扱える型・操作のみを含む (legal)。
331 |
332 | |
333 | | <-- CAHPDAGToDAGISel, CAHPInstrInfo
334 | v
335 |
336 | SelectionDAG (MachineSDNode); ノードの命令は全てCAHPのもの。
337 |
338 | |
339 | | <-- CAHPInstrInfo; 命令スケジューリング
340 | v
341 |
342 | LLVM MIR (MachineInstr); スケジューリングされた命令列
343 |
344 | | (以下の流れは TargetPassConfig::addMachinePasses に記述されている)
345 | |
346 | | <-- CAHPTargetLowering::EmitInstrWithCustomInserter;
347 | | usesCustomInserter フラグが立っている ある MachineInstr の代わりに
348 | | 複数の MachineInstr を挿入したり MachineBasicBlock を追加したりする。
349 | |
350 | | <-- SSA上での最適化
351 | |
352 | | <-- レジスタ割り付け
353 | v
354 |
355 | LLVM MIR (MachineInstr); 物理レジスタのみを含む命令列(仮想レジスタを含まない)
356 |
357 | |
358 | | <-- CAHPInstrInfo::expandPostRAPseudo
359 | |
360 | | <-- CAHPFrameLowering::processFunctionBeforeFrameFinalized
361 | |
362 | | <-- スタックサイズの確定
363 | |
364 | | <-- CAHPFrameLowering::emitPrologue; 関数プロローグの挿入
365 | | <-- CAHPFrameLowering::emitEpilogue; 関数エピローグの挿入
366 | | <-- CAHPRegisterInfo::eliminateFrameIndex; frame indexの消去
367 | |
368 | | <-- llvm::scavengeFrameVirtualRegs;
369 | | frame lowering中に必要になった仮想レジスタをscavengeする
370 | v
371 |
372 | LLVM MIR (MachineInstr); frame index が削除された命令列
373 |
374 | |
375 | | <-- CAHPPassConfig::addPreEmitPass
376 | | <-- CAHPPassConfig::addPreEmitPass2
377 | |
378 | |
379 | | <-- CAHPAsmPrinter
380 | | <-- PseudoInstExpansion により指定された擬似命令展開の実行
381 | v
382 |
383 | MC (MCInst); アセンブリと等価な中間表現
384 | ....
385 |
386 | LLVM MIRについては<>に詳しい。
387 | 各フェーズでの `MachineInstr` をデバッグ出力させる場合は `llc` に `-print-machineinstrs` を
388 | 渡せば良い。
389 |
390 | == LLVMのソースコードを用意する
391 |
392 | LLVMのソースコードを取得する。今回の開発ではv9.0.0をベースとする。
393 | Git上でcahpブランチを作り、その上で開発する。
394 |
395 | ....
396 | $ git clone https://github.com/llvm/llvm-project.git
397 | $ cd llvm-project
398 | $ git switch llvmorg-9.0.0
399 | $ git checkout -b cahp
400 | ....
401 |
402 | == スケルトンバックエンドを追加する
403 |
404 | `isRISCV` などの関数が `Triple.h` に追加されていた。ただしLanaiのものは無かった。
405 | 無くとも問題ないと思われるので実装は省略。TODO
406 |
407 | ビルドする。RISC-Vはもはやexperimentalではない。
408 |
409 | ....
410 | $ cmake -G Ninja \
411 | -DLLVM_ENABLE_PROJECTS="clang;lld" \
412 | -DCMAKE_BUILD_TYPE="Debug" \
413 | -DBUILD_SHARED_LIBS=True \
414 | -DLLVM_USE_SPLIT_DWARF=True \
415 | -DLLVM_OPTIMIZED_TABLEGEN=True \
416 | -DLLVM_BUILD_TESTS=True \
417 | -DCMAKE_C_COMPILER=clang \
418 | -DCMAKE_CXX_COMPILER=clang++ \
419 | -DLLVM_USE_LINKER=lld \
420 | -DLLVM_TARGETS_TO_BUILD="X86;RISCV" \
421 | -DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD="CAHP" \
422 | ../llvm
423 | $ cmake --build .
424 | ....
425 |
426 | CAHPバックエンドが追加された。
427 |
428 | ....
429 | $ bin/llc --version
430 | LLVM (http://llvm.org/):
431 | LLVM version 9.0.0
432 | DEBUG build with assertions.
433 | Default target: x86_64-unknown-linux-gnu
434 | Host CPU: skylake
435 |
436 | Registered Targets:
437 | cahp - CAHP
438 | riscv32 - 32-bit RISC-V
439 | riscv64 - 64-bit RISC-V
440 | x86 - 32-bit X86: Pentium-Pro and above
441 | x86-64 - 64-bit X86: EM64T and AMD64
442 | ....
443 |
444 | == 簡易的なアセンブラを実装する
445 |
446 | やるだけ。メモリ演算以外は正しくエンコードされることを確認。
447 |
448 | invalid operandのエラーメッセージを正しく出す変更をここでしておく。
449 |
450 | == `CAHPInstPrinter` を実装する
451 |
452 | やるだけ。 `tablegen(LLVM CAHPGenAsmWriter.inc -gen-asm-writer)` を
453 | CMakeFilesに追加しなくても `-show-encoding` オプションは動いた。
454 |
455 | == テストを書く
456 |
457 | やるだけ。
458 |
459 | == メモリ演算を追加する
460 |
461 | やるだけ。やっぱり `AsmWriter` は必要だった。
462 |
463 | == 属性を指定する
464 |
465 | やるだけ。RISC-Vのlui/addi/xori/oriに
466 | `let isReMaterializable = 1, isAsCheapAsAMove = 1` がついていた。
467 | 要調査TODO.
468 |
469 | == ディスアセンブラを実装する
470 |
471 | やるだけ。24/16bit命令の判定がRV16Kよりも楽。
472 |
473 | == relocationとfixupに対応する
474 |
475 | relocationは何が必要なのか良くわからない。RISC-Vなどでは
476 | `R_RISCV_32` を定義しているが、32bitの即値を直接読み込める命令など
477 | 存在しないはずである。とりあえずfixupで対処し、関数呼び出しを実装する時点で
478 | 再び考えることにする。
479 |
480 | RV16Kのときとは異なり、CAHPは16bit即値を直接読み込むことはできない。
481 | 上位6bitと下位10bitを分けて読み込むことになるが、
482 | そのためには `%hi/%lo/%pcrel_hi/%pcrel_lo` の実装が必要である。
483 | これはRISC-Vを参考にして実装する。
484 |
485 | 即値の取り扱い方でだいぶ迷ったがおおよそ理解した。基本的にはRISC-Vに従う。
486 |
487 | まず `lui` は下位ビットをclearし、addiと補完して使用するほうが良い。
488 | こうすることで次のように `lw` などとの連携がとれる。
489 |
490 | ....
491 | lui a0, %hi(foo)
492 | lw a1, %lo(foo)(a0)
493 | ....
494 |
495 | ここで使用している `%hi` は `foo` の上位6bitという意味では**ない**。
496 | というのも `%lo` が使用されるのは符号つき即値フィールドのため
497 | 符号拡張が行われる。そのため `%lo` の10bit目が符号bitと見なされ、
498 | 不用意に負数になる可能性がある。そこでCAHP(と参照したRISC-V)では
499 | 「 `%lo` の10bit目が1の場合は `1 << 10` を足す」という動作を行う
500 | 必要がある。
501 |
502 | またCAHPv3に `auipc` は必要ない。当初関数ポインタを正しく扱うためには
503 | `auipc` が必要だと考えていたが、実際には次のようにすればよい。
504 |
505 | ....
506 | # 関数名を指定した関数呼び出し
507 | jal hoge
508 |
509 | # 関数ポインタを経由した関数呼び出し
510 | lui a0, %hi(hoge)
511 | addi a0, a0, %lo(hoge)
512 | jalr
513 | ....
514 |
515 | RISC-Vにおいて `auipc` を必要とするのは `j` や `jal` 命令などが32bit即値を
516 | とれないためである。CAHPでは `j` 及び `jal` が16bit即値を取れるため問題ない。
517 |
518 | とりあえず `%hi/%lo` を含まないfixupに対応した。
519 | relocation対応は後回し。
520 |
521 | == `%hi/%lo` に対応する
522 |
523 | アセンブリ中で使用できるmodifierである `%hi/%lo` に対応する。
524 | 例えば次のように動作する。
525 |
526 | ....
527 | lui a0, %hi(0xFFFF) # lui a0, 0
528 | addi a0, a0, %lo(0xFFFF) # addi a0, a0, 0x3FF
529 | ....
530 |
531 | 値の下位10bitが負数になる場合には `%hi` は単に上位6bitを返すのではなく、
532 | それに `1 << 10` を足した値を返すことに注意が必要である。
533 |
534 | 基本的な実装の流れは次のようになる。まず `CAHPMCExpr` を定義する。
535 | `CAHPMCExpr` は `MCExpr` をラップすると同時に、
536 | この式が `%lo/%hi` などのmodifierのうち、どれがついているか(あるいはついていないか)を
537 | `VariantKind` 列挙体として保持する。fixupの生成はこの `VariantKind` を目印に操作を行う。
538 |
539 | 次に `AsmParser` で `%` を読み込んだ場合に `parseOperandWithModifier` を呼出し、
540 | `CAHPMCExpr` を作成する。
541 |
542 | `isSImm10` などでは `CAHPMCExpr` が即値として現れることを想定する必要がある。
543 | この場合、まず i) 定数式として評価できるならばビット幅を確認し ii) そうでなければ
544 | そもそもその式がvalidであるかどうかとmodifierについて調べ( `classifySymbolRef` )、
545 | validかつ適切なmodifierであればtrueを返す。なおここでいう「適切なmodifier」とは、
546 | 例えば `isSImm10` に `%lo` が来ることは認められるが `%hi` は認められない、
547 | といったことを意味している。
548 |
549 | `getImmOpValue` にてfixupを作る際にも `CAHPMCExpr` を考慮する必要がある。
550 | fixupでは命令そのものを書き換える必要があるため、即値がどのようにバイト中に
551 | 配置されるかを知る必要がある。したがって同じbit幅でも格納方法が違う場合は
552 | 異なるfixupの種類としなければならない。RISC-Vでは実際このために `InstFormat` を
553 | 導入して対処しているが、幸いなことにCAHPではそのようなことがない。よかったね。
554 |
555 | `AsmBackend` で `%hi/lo` 用のfixupに対応する。
556 |
557 | ここまでで `lui/addi` はちゃんと動くようになった。
558 | 問題はその他の `ori` などで、例えば次のようなコードはエラーになってしまう。
559 |
560 | ori a0, a0, %lo(0xffff)
561 |
562 | 原因は `%lo(0xffff)` が `-1` となって符号なし即値でなくなってしまうためである。
563 | ではRISC-Vはどうしているかというと、なんと `ori` などビット演算にも符号付き即値を要求している。
564 | よくよく仕様を見てみるとこれらは符号付き即値を要求するのだ。これによって `xor a0, a0, -1` で
565 | `not` の代わりになるなどのメリットがあるらしい。なるほど。
566 |
567 | ということでISAを変更したfootnote:[どんどんCAHPがRISC-Vになっていく。]。
568 |
569 | `AsmParser` の `addExpr` で `CAHPMCExpr` について `evaluateAsConstant` をすることにより、
570 | これが定数式の場合はfixupを作ることなく `%lo/%hi` を評価できる。
571 |
572 | == コンパイラのスケルトンを作成する
573 |
574 | ここでいう「スケルトン」とはおおよそ「何もせずただreturnするだけ」の
575 | LLVM IRをコンパイルできる程度のものである。それでも関数のコンパイルなどが
576 | 必要になるため、変更量は多い。
577 |
578 | やるだけ。
579 |
580 | これによって次のような変換を実行できるようになる。
581 |
582 | ....
583 | $ cat foo.ll
584 | define void @foo() nounwind {
585 | ret void
586 | }
587 |
588 | $ bin/llc -mtriple=cahp -verify-machineinstrs < foo.ll
589 | .text
590 | .file ""
591 | .globl foo # -- Begin function foo
592 | .p2align 1
593 | .type foo,@function
594 | foo: # @foo
595 | # %bb.0:
596 | jr ra
597 | .Lfunc_end0:
598 | .size foo, .Lfunc_end0-foo
599 | # -- End function
600 |
601 | .section ".note.GNU-stack","",@progbits
602 | ....
603 |
604 | `ret` が `jr` に変換されていることが分かる。
605 |
606 | == 基本的な演算に対応する
607 |
608 | いわゆるALUで実行される演算に対応する。RV16Kまでは「スケルトン」に含めていたのだが
609 | やっぱり分けたほうが見通しが良いと思う。
610 |
611 | やるだけ。
612 |
613 | == 定数の実体化に対応する
614 |
615 | materialization<>に対応する。16bit整数は `lui` と `addi` を組み合わせて
616 | 読み込む必要があるため、TableGenファイルに `HI6` と `LO10Sext` という `SDNode` を
617 | 追記する。
618 |
619 | ついでに上位6bitで表現できる数値のときは `lui` のみを使用するという
620 | 最適化も取り込んでおく。RISC-Vではあとの方のコミット(3ff2022bb94)で
621 | ぬるっと実装されている。
622 |
623 | == メモリ演算に対応する
624 |
625 | やるだけ。 `copyPhysReg` の実装で `kill` フラグを立てているが、
626 | <>によればこれは不要であるので削除しておく。
627 |
628 | == relocationに対応する
629 |
630 | やるだけ。とりあえず `%hi/%lo` に対応するものと関数呼び出しに対応するものだけ。
631 |
632 | == 条件分岐に対応する
633 |
634 | CAHPはほとんどRISC-Vなのでfootnote:[要出典]、本家<>と
635 | 同様に `brcond` によるパターンマッチを行えば良い……と思いきや、そうではない。
636 | 後々 `setcc` に対応する際、RISC-Vでは `setcc` に対応する命令があるので問題ないが、
637 | CAHPではRV16Kのときと同様にこれをexpandしなければならない。
638 | これが問題で、愚直にやると `setcc` が `brcond` とセットのときにもexpandされてしまう。
639 | 回避方法があるのかもしれないfootnote:[一つは `custom` でよしなにする方法だが面倒。]が
640 | 分からないので `BR_CC` を採用する。
641 |
642 | `BR_CC` をまともに使っているバックエンドは少ない。BPFがその1つ。
643 | またBPFをもとに開発されたELVMもこれに従う。
644 | `CC_EQ` などの述語を作成し、これによってcond codeをパターンマッチする。
645 |
646 | == 関数呼び出しに対応する
647 |
648 | `PseudoCALL` を導入せずに初めから `Pat` を使用して `jalr` に置き換える。
649 | そのため `MO_RegisterMask` の対応が必要。なお `jal` による関数呼び出しにしようとすると
650 | エラーになるので一旦放置(TODO)。
651 |
652 | オブジェクトファイルを生成しようとすると(アセンブラを通そうとすると)次のような
653 | エラーがでた。
654 |
655 | LLVM ERROR: unable to write nop sequence of 1 bytes
656 |
657 | これは関数自体のアラインメントが2に設定されている( `.p2align 1` )に起因するようだ。
658 | `CAHPMCAsmInfo::CAHPMCAsmInfo` にて `HasFunctionAlignment = false;` とすることで回避したが、
659 | これは単に `.p2align` を表示させないだけなので正当ではない。
660 | `CAHPTargetLowering::CAHPTargetLowering` で次のように関数のアラインメントを設定する。
661 |
662 | setMinFunctionAlignment(0);
663 | setPrefFunctionAlignment(0);
664 |
665 | == 関数プロローグ・エピローグを実装する
666 |
667 | `llvm.frameaddress` と `llvm.returnaddress` が正常に動作するように、
668 | `ra` と `fp` は無条件に保存する。
669 |
670 | 半分以上のテストが動作しなくなるが、とりあえず放置する。
671 |
672 | == frame pointer eliminationを実装する
673 |
674 | テストを書き換えるのが面倒なので、さっさとframe pointer eliminationを実装してしまう。
675 |
676 | == `select` に対応する
677 |
678 | やるだけ。
679 |
680 | == `FrameIndex` をlowerする。
681 |
682 | `select` の前にやるべきだったかも。RV16Kのときのframe indexへの対応は3箇所に分かれているので、
683 | その全てを統合する。
684 |
685 | 途中混乱したが `Select` に `ISD::FrameIndex` が来た場合は単にaddiに還元してよい。
686 | ここで指定する即値は `0` である。後々具体的な即値が求まったときにビット幅に収まらない場合の処理は
687 | `eliminateFrameIndex` で行う。
688 |
689 | == 大きなスタックフレームに対応する
690 |
691 | RISC-Vの実装を参考にしつつ `RegState::Kill` を片っ端から消すfootnote:[これほんまに大丈夫なんやろな……]。
692 |
693 | == `SETCC` に対応する
694 |
695 | expandするだけ。
696 |
697 | == `ExternalSymbol` に対応する
698 |
699 | やるだけ。これで `frame.ll` が動くようになったfootnote:[後から見直したら、前から通るはずのテストっぽい。要確認。TODO]。
700 |
701 | == jump tableを無効化する
702 |
703 | やるだけ。ここでbrainfuckのLLVM IRコードがコンパイルできるようになった。
704 |
705 | == lldにバックエンドを追加する
706 |
707 | やるだけ。hi6/lo10に対応するのが面倒だったが、特別変わったところはない。
708 | RISC-Vのものを参考にして、アセンブリから作ったオブジェクトファイルをリンクした結果を
709 | 確認するテストを追加した。
710 |
711 | == 16bit命令を活用する
712 |
713 | === `CompressPat` の調査
714 |
715 | 現状24bit命令で表現されているところで、変換できる部分は16bit命令を使用するようにしたい。
716 | これにはRISC-V LLVMバックエンドにて導入された `CompressPat` の仕組みを利用する。
717 | `CompressPat` を導入したコミット(c1b0e66b586b6898ee73efe445fe6af4125bf998)
718 | <>を参考にする。
719 |
720 | `CompressPat` の仕組みは `utils/TableGen/RISCVCompressInstEmitter.cpp` にて実装されている。
721 | エントリポイントは `llvm::EmitCompressInst` で、ここから呼ばれる
722 | `RISCVCompressInstEmitter::run` が `RISCVCompressInstEmitter` クラスの関数を呼び出すことにより
723 | 処理が行われる。まず `CompressPat` を親に持つ定義( `def` )をすべて取り出し、
724 | これらを `evaluateCompressPat` に渡す。
725 | その後ファイルヘッダの出力、 `compressInst` 関数のソースコードの出力、
726 | `uncompressInst` 関数のソースコードの出力と続く。
727 |
728 | `CompressPat` 自体は次のように定義される。ここで `Input/Output` は入力・出力を表すDAGをとり、
729 | `Predicates` は `HasStdExtC` などの述語をとる。
730 |
731 | `evaluateCompressPat` では i) まずパターンとして記述された内容が正しいか否かを
732 | 判断し、正しければ ii) 変換元から変換先へのパターンを登録する。このときに対象のDAGを解析し、
733 | 「元のどのオペランドが先のどのオペランドに対応するか」という情報
734 | ( `SourceOperandMap/DestOperandMap` )を得る必要がある。
735 | なおそのあとに `PatReqFeatures` を構成しているが、これは `Predicates` を操作しているようだ。
736 |
737 | `emitCompressInstEmitter` では `CompressPatterns` を使用して
738 | `compressInst` 関数及び `uncompressInst` 関数の出力を行う。
739 | どちらの関数が出力されるかは `Compress` 引数によってきまる。
740 | この関数では、現在注目している `MachineInstr` を変換すべきか否か・変換するならば何に変換するべきか
741 | を決める巨大な `switch` 文を作成する。
742 | 各々の `case` は `if ( cond ) { code }` という形になっており、 `cond` の部分を `CondString` に、
743 | `code` の部分を `CodeString` に構築している。おおよそ `cond` の部分には
744 | 「変換元・変換後のオペランドのパターンが現在見ている `MachineInstr` と一致しているか」を
745 | 調べる条件式が入り、 `code` の部分には「現在見ている `MachineInstr` のオペランドを変換後の
746 | ものに置き換える」ためのコードが入る。
747 |
748 | `switch` 文の条件式には `MI.getOpcode()` を戻り値を使っている。1つのopcodeは
749 | 一般に複数のパターンにマッチする場合もあるfootnote:[ただしもちろんオペランドの
750 | 内容によって一意に定まる必要はある(多分;TODO)。]。そのようなケースは
751 | (C\++の `switch` が同名のラベルを複数個持てないという言語仕様により)一つにまとめる必要が
752 | ある。ここでは関数冒頭で `std::stable_sort` を呼び出したうえで、今見ているopcodeと
753 | 前に処理したopcodeが同じか否かによって判断している。なお `std::stable_sort` は安定ソートを
754 | 行うため、先に定義されたパターンがより早く試されることになる。
755 | その後 `Predicates` を満たしているか否かを判断するコードを出力する。
756 |
757 | それから変換元オペランドのパターンマッチに入る。まずtied operandの場合(2アドレス方式の
758 | 命令など)は、その結び付けられたオペランド同士が等しいかどうか確認する。
759 | その上で、いま見ているオペランドがパターンの変換元オペランドと等しいかどうかを確認する。
760 | ただし実際に確認できるのはfixed immediate( `OpData::Imm` )とfixed register( `OpData::Reg` )
761 | の場合のみである。つまり固定されていないレジスタや即値の場合( `OpData::Operand` )は
762 | 変換元オペランドに関するチェックのコードは生成されないfootnote:[これはおそらく、
763 | すでにその段階に到達する時点で通常のパターンマッチにおけるチェックが済んでいるからであると
764 | 推察されるが未確認。TODO]。
765 |
766 | ここから変換後オペランドのパターンマッチに入る。fixed registerの場合はすでにチェックが
767 | 終わっているため、置換のコードのみを出力する。それ以外の場合にはチェックのコードを出力する。
768 | なお即値チェックで使用されている `getMCOpPredicate` 関数は、
769 | `ValidateMCOperand` に渡すindexを返却する。このindexによってどのオペランドかを識別し、
770 | その型に設定された `MCOperandPredicate` の内容を出力する。
771 |
772 | 各即値型の( `simm12` など) `MCOperandPredicate` を見ると、定数値として計算できる場合は
773 | 計算した後にビット幅を確かめている一方で、bare symbolの場合(何のmodifierも付されておらず
774 | 単にシンボルがある場合)には無条件でチェックを通している。これは一見問題に見えるが、
775 | ここで入力されるアセンブリは全てcodegenによって生成され、かつopcodeによって
776 | 区別されたものである。したがって「変換元の命令の条件を満たしている」という
777 | 意味でwell-formedであって、例えば `simm12` にbare symbolが入力された場合に
778 | 対応する命令は `JAL` のみで `ADDI` などではない。したがって問題にならない。
779 | 逆に「他のシンボルを通す必要がないのか」という点は良くわからない。TODO
780 | footnote:[実際RISC-Vの `MCOperandPredicate` で使用される `isBareSymbolRef` を
781 | 全て `true` としてみたところ、圧縮命令に関するテストは全てパスしたように見えた。
782 | 一方で落ちるテストも2件あったことから、 `CompressPat` 以外でも `MCOperandPredicate` が
783 | 使用されていることが伺える。LLVMでは複数の箇所に記述されたプログラムの断片が
784 | 合わさってパターンマッチを行うため、全貌を把握することが難しいように思える。]
785 |
786 | === `CompressPat` をCAHPに導入する
787 |
788 | `utils/TableGen/RISCVCompressInstEmitter.cpp` をコピーして `CAHPCompressInstEmitter.cpp` を作る。
789 | `RVInst` を参照するところは `CAHPInst` を参照するように変更する。
790 | なお `Predicates` に関する処理はCAHPには不要だが面倒なので放置する。
791 | また `TableGen.cpp` を変更し `-gen-cahp-compress-inst-emitter` オプションを作成する。
792 |
793 | `CAHPAsmWriter` を作成し `int PassSubtarget = 1` とする必要があった。
794 | RISC-Vのパッチを参考にする。
795 |
796 | RISC-Vは `addi x1, x1, 10` のようなアセンブリが入力された場合にも `c.addi` に変換する。
797 | つまりアセンブラも `compressInst` を呼ぶが、CAHPではこのようなことは行わない。
798 | そのため `compressInst` を呼ぶのは `AsmPrinter` に限られfootnote:[ちょうど
799 | 疑似命令の展開と同じような操作である。]、また `uncompressInst` は全く呼ぶ必要がない。
800 |
801 | なおTableGenのコードを書き換えてビルドしようとするとエラーが発生する。
802 | これはLLVMのコードをビルドするために使用するTableGen( `NATIVE/bin/llvm-tblgen` )が
803 | 再コンパイルされないためである。これを解決するためにはフルビルドするか、
804 | フルビルドの際のビルドスケジュール( `cmake -nv` で得られるログ)を参考にして
805 | `NATIVE/bin/llvm-tblgen` を次のように再コンパイルする必要がある。
806 |
807 | cd /path/to/llvm-project/build/NATIVE && \
808 | /usr/bin/cmake --build /path/to/llvm-project/build/NATIVE --target llvm-tblgen --config Release
809 |
810 | テストが大幅に壊れるので修正する。RISC-Vの場合は圧縮命令を有効化するか否かを表す
811 | オプションが存在する( `-mattr=+c` )が、CAHPの場合は常時有効化されるため、
812 | 24bitの命令と16bit命令の両方をテストするには次のようにひと工夫必要である。
813 |
814 | define i16 @addi(i16 %a, i16 %b) nounwind {
815 | ; CAHP-LABEL: addi:
816 | ; CAHP: # %bb.0:
817 | ; CAHP-NEXT: addi a0, a1, 1
818 | ; CAHP-NEXT: jr ra
819 | %1 = add i16 %b, 1
820 | ret i16 %1
821 | }
822 | define i16 @addi2(i16 %a) nounwind {
823 | ; CAHP-LABEL: addi2:
824 | ; CAHP: # %bb.0:
825 | ; CAHP-NEXT: addi2 a0, 1
826 | ; CAHP-NEXT: jr ra
827 | %1 = add i16 %a, 1
828 | ret i16 %1
829 | }
830 |
831 | == ClangをCAHPに対応させる
832 |
833 | やるだけ。すでにlldがCAHPに対応しているので `ld.lld` を呼ぶようにしておく。
834 |
835 | == 分岐解析に対応する
836 |
837 | RISC-Vのパッチを参考にしながら分岐解析に対応する。やるだけ。
838 |
839 | == インラインアセンブリに対応する
840 |
841 | branch relaxationのテストを書くためにはインラインアセンブリに対応しておく
842 | 必要がある。忘れていた。
843 |
844 | == branch relaxationに対応する
845 |
846 | やるだけ。
847 |
848 | == 単体の `sext/zext/trunc` に対応する
849 |
850 | やるだけ。
851 |
852 | == fastccに対応する
853 |
854 | RISC-Vと同じようにcccと同様にしておく。やるだけ。
855 |
856 | == `jal` を活用する
857 |
858 | 現状のCAHPではROMのサイズに512B以下という制限があるため、
859 | 全ての関数呼び出しは `jal` によって解決できる。これを反映し、現在 `jalr` によって
860 | 行っている関数呼び出しを `jal` によって行いたい。
861 |
862 | `LowerCall` での `LowerGlobalAddress` と `LowerExternalSymbol` の呼び出しをやめ、
863 | `%hi/%lo` で包むことなく `TargetGlobalAddress/TagetExternalSymbol` に変換する。
864 | これで `LowerExternalSymbol` は不要になった。
865 |
866 | 次いでこれに対するパターンマッチをTableGenにて記述する。
867 | ここで `tglobaladdr` と `texternalsym` は `OtherVT` ではなく `i16` にマッチすることに
868 | 注意する。そのため `OtherVT` の11bit即値を表す `simm11_branch` と
869 | `i16` の11bit即値を表す `simm11` を分ける必要がある。 `js` は `simm11_branch` を
870 | とり `jsal` は `simm11` をとる。
871 |
872 | ここで気がついたが、実は `ExternalSymbol` を利用したテストは一つも存在しなかった。
873 | したがって上の `ExternalSymbol` に対する変更は正しいかどうか判断がつかない。
874 | 仕方がないので、このあとに行う乗算の導入で確認することにする。
875 |
876 | 以上の変更を加えて `-filetype=obj` を有効化すると `invalid fixup kind` の
877 | assertで落ちてしまう。直接の原因は `CAHPMCExpr::VK_CAHP_None` を持った
878 | `CAHPMCExpr` が `CAHPMCCodeEmitter::getImmOpValue` に渡されてしまうことである。
879 | デバッガを使って確認すると、この渡されてくる式の中身は `MCExpr::SymbolRef` であって、
880 | 関数名のシンボルが入っている。すなわちこれは本来 `MCSymbolRefExpr` として中身単体で
881 | 渡されてくるべきものであって `CAHPMCExpr` でラップしているのは余計なのだ。
882 |
883 | ではどこで余計にラップしているのか。ここで `CAHPMCExpr` を生成する箇所は二箇所あることに
884 | 注意する必要がある。一つは `AsmParser` で、ここはアセンブリが入力として
885 | 与えられたときに動くため、今回は関係がない。もう一つは `MachineInstr` から `MCInst` に変換する
886 | `llvm::LowerCAHPMachineInstrToMCInst` である。コード生成から直接オブジェクトファイルを
887 | 生成する際にはこれが使われる。これまでの実装では、ここから呼び出される
888 | `LowerSymbolOperand` で、対象がどのようなシンボルであっても `CAHPMCExpr::create` を
889 | 用いて `CAHPMCExpr` でラップしていた。これが原因である。
890 | RISC-Vを見習い、作成したい式が `CAHPMCExpr::VK_CAHP_None` 以外であるときのみに
891 | これを限定すれば解決した。
892 |
893 | == 乗算に対応する
894 |
895 | `__mulhi3/__musi3/` が適宜出力されるようにする。やるだけ。
896 |
897 | == 除算・剰余に対応する
898 |
899 | `__udivhi3/__udivsi3/__divhi3/__divsi3/__umodhi3/__modhi3`
900 | が適宜出力されるようにする。やるだけ。
901 |
902 | == `hlt` 疑似命令を追加する
903 |
904 | `js 0` のエイリアスとして `hlt` 疑似命令を追加する。やるだけ。
905 |
906 | == `crt0.o` と `cahp.lds` の導入
907 |
908 | スタートアップのためのオブジェクトファイル `crt0.o` と、
909 | リンカスクリプト `cahp.lds` を導入し、これが `sysroot` から読み込まれるように
910 | Clangの `CAHPToolChain` を改変する。なおこれらが `sysroot` にあるのは本来おかしいのだが、
911 | CAHPがベアメタル専用アーキテクチャのようになっている現状、
912 | これらのファイルをどこに置けばよいかは判然としない。TODO
913 |
914 | `cahp.lds` と、 `crt0.o` の元になる `crt0.s` は `cahp-rt` という
915 | レポジトリで管理することにする。このあたりはRV16Kと変わらない。
916 |
917 | == `--nmagic` の有効化
918 |
919 | セクションのページサイズでのアラインメントを無効化して、
920 | リンク後のバイナリサイズを小さくする。RV16Kのときには `--omagic` を使用していたが、
921 | これは `.text` に書き込み可フラグを立てるためにセキュリティ上問題がある。
922 | LLVM 9.0.0にてLLDに導入された `--nmagic` を使えばこの問題は発生しない。
923 |
924 | 実装はやるだけ。
925 |
926 | == libcの有効化
927 |
928 | `-nostdlib` や `-nodefaultlibs` が指定されない限りにおいて `-lc` を自動的に指定する。
929 | やるだけ。 `cahp-rt` と合わせて、これで掛け算や割り算を使用できるようになった。
930 |
931 | == `li a0, foo` をエラーにする
932 |
933 | `li` や `addi` などの即値オペランドには10bit符号付き即値が指定される。
934 | ここにシンボルが指定される場合、そのシンボルは `%lo(...)` という形をとる
935 | 必要がある。つまり何もmodifierが付与されていないシンボル(bare symbol)を
936 | 受理してはいけない。例えば次のような入力をエラーとする必要がある。
937 |
938 | li a0, foo
939 |
940 | RISC-Vでは<>にてこれに対応している。
941 | このコミットを参考にして修正する。
942 |
943 | `AsmParser` の `isSImm10` にてシンボルを扱う場合には i)
944 | そのシンボルに `%lo` が付されている、あるいは ii) bare symbolでかつ定数式である
945 | ときのみ `true` を返し符号付き10bit即値として認める。
946 | なお、条件分岐命令のオペランドも符号付き10bit即値を受け取るが、
947 | こちらはbare symbolでなければならない。そこで `simm10_branch` には
948 | `isBareSImm10` という新しい関数を参照させ、単にbare symbolであるか否かを
949 | 調べることにしておく。
950 |
951 | `%hi` についても `isSImm6` について同様の処理を行う。
952 |
953 | == `llvm-objdump` の調査
954 |
955 | `llvm-objdump -D hoge` として `.text` セクション以外でデコードできなくて死ぬ。
956 |
957 | llvm-objdump: /llvm-project/llvm/lib/Target/CAHP/Disassembler/CAHPDisassembler.cpp:73: DecodeStatus decodeUImmOperand(llvm::MCInst &, uint64_t, int64_t, const void *) [N = 4]: Assertion `isUInt(Imm) && "Invalid immediate"' failed.
958 |
959 | リリース版ビルドだと発生しない。謎。
960 |
961 | 24bit命令の10bit即値と4bit即値、及び16bit命令の6bit即値と4bit即値を、
962 | 同じ命令のクラスとしてTableGenにて記述していたことが原因だった。
963 | すなわち次のような `CAHPInst24I` クラスで10bit/4bit即値を受け取る命令の両方を
964 | 処理していたことが原因だった。
965 |
966 | class CAHPInst24I opcode, dag outs, dag ins, string opcodestr, string argstr>
967 | : CAHPInst24 {
968 | bits<4> rd;
969 | bits<4> rs1;
970 | bits<10> imm;
971 |
972 | let Inst{23-16} = imm{7-0};
973 | let Inst{15-12} = rs1;
974 | let Inst{11-8} = rd;
975 | let Inst{7-6} = imm{9-8};
976 | let Inst{5-0} = opcode;
977 | }
978 |
979 | このとき「まともな」ELFバイナリであれば、4bit即値を受け取る命令( `lsri` など)の
980 | 6-7ビット目と20-23ビット目には0が入っているため `imm` は正しく4bit即値となる。
981 | しかし実際にはこれらのビットはdon't careであり、0が入っているとは限らないうえ、
982 | 不正なバイナリであれば何が入っているかわからない。上の `llvm-objdump` を使った際には
983 | これらのビットが0ではなく、結果として4bitよりも大きい値が `imm` に入ってしまった。
984 |
985 | これを防ぐためには、4bit即値と10bit即値を受け取る命令のクラスを分ければ良い。
986 |
987 | ....
988 | // 24-bit I-instruction format for 10bit immediate
989 | class CAHPInst24I_10 opcode, dag outs, dag ins, string opcodestr, string argstr>
990 | : CAHPInst24 {
991 | bits<4> rd;
992 | bits<4> rs1;
993 | bits<10> imm;
994 |
995 | let Inst{23-16} = imm{7-0};
996 | let Inst{15-12} = rs1;
997 | let Inst{11-8} = rd;
998 | let Inst{7-6} = imm{9-8};
999 | let Inst{5-0} = opcode;
1000 | }
1001 |
1002 | // 24-bit I-instruction format for 4bit immediate
1003 | class CAHPInst24I_4 opcode, dag outs, dag ins, string opcodestr, string argstr>
1004 | : CAHPInst24 {
1005 | bits<4> rd;
1006 | bits<4> rs1;
1007 | bits<4> imm;
1008 |
1009 | let Inst{23-20} = 0;
1010 | let Inst{19-16} = imm{3-0};
1011 | let Inst{15-12} = rs1;
1012 | let Inst{11-8} = rd;
1013 | let Inst{7-6} = 0;
1014 | let Inst{5-0} = opcode;
1015 | }
1016 | ....
1017 |
1018 | 16bit命令の `CAHPInst16I` についても同様である。
1019 |
1020 | せっかくなので、回帰バグを防ぐためにテストを書く。
1021 | 不正なバイト列footnote:[ISA上はdont' careのbitが0でないだけで不正ではないが、
1022 | LLVMバックエンドとしてはこれらを0として扱うことにする。]に対して
1023 | 正しくunknownが出力されるかをチェックする。
1024 |
1025 | どこに書くのがLLVMとして正当なのかわからないが、
1026 | とりあえずllvm-objdumpのテストとして書くことにする。x86の
1027 | `disassemble-invalid-byte-sequences.test` を参考にする。
1028 | `yaml2obj` を使えばすきなELFバイナリを作ることができるので便利だ。
1029 |
1030 | == スタックを利用した引数渡し
1031 |
1032 | やるだけ。先達はなんとやら。
1033 |
1034 | == `byval` の対応
1035 |
1036 | やるだけ。 `byval` が絡むのは関数呼び出しの引数だけで、
1037 | 呼ばれる側や戻り値には関係がないことに注意。
1038 | 呼ばれる側はポインタが渡される場合と変わりなく、
1039 | 戻り値は `sret` として引数に組み込まれる。
1040 |
1041 | == 命令スケジューリングの実装
1042 |
1043 | cahp-emerald以降はスーパースカラに対応するらしいので、
1044 | LLVM側でもスーパスカラで効率的に動作するアセンブリを出力できるように
1045 | 調整する。具体的には命令スケジューリングの設定をする。
1046 | 残念ながらRISC-Vではこの設定は為されていないようだfootnote:[単一の
1047 | プロセッサをターゲットとしているわけではないからだろうか。]。
1048 | LanaiやSparc・ARMなどのバックエンドを参考にする。
1049 | また<>にも記述がある。
1050 | <>も参考になる。
1051 |
1052 | `include/llvm/Target/TargetSchedule.td` によると、
1053 | 命令スケジューリングにはいくつかの方法があり、
1054 | さらにこれらが有機的に構成されているようだ<>。
1055 |
1056 | <>によればinstruction itinerariesを利用する場合、
1057 | TableGenファイルに各命令が属する命令スケジュールを記述する。
1058 | スケジュール自体は `CAHPSchedule.td` に定義し、これを `CAHPInstrFormats.td`
1059 | や `CAHPInstrInfo.td` で使う。
1060 |
1061 | `FuncUnit` のインスタンスとして機能ユニットを定義し、
1062 | `InstrItinClass` のインスタンスとして命令スケジュールを定義する。
1063 | 各命令はいずれかの `InstrItinClass` に属する。
1064 |
1065 | どの `InstrItinClass` がどのように共有リソース(機能ユニット)を利用するかを
1066 | 記述するために `ProcessorItineraries` のインスタンスを定義する。
1067 | ここでは `InstrStage` を用いて、各命令がその処理を完了するまでに何サイクルかかり、
1068 | どの機能ユニットを使用するかを記述する。
1069 |
1070 | ある命令がどの `InstrItinClass` に属するかは `Instruction` クラスの
1071 | `Itinerary` 属性に `InstrItinClass` を入れておくことによって記述される。
1072 |
1073 | しかし上記のようなやり方は古いものとなっているようだ。(要確認; TODO
1074 | footnote:[LLVMのスケジューリング手法は一度大きく変わっている
1075 | <><><>。
1076 | `InstrItinClass` などを使用する方法が古いスケジューリング手法と新しいスケジューリング手法の
1077 | どちらに入るのか良くわからない。
1078 | ただし `TargetSchedule.td` にて `TargetItinerary.td` を `include` する箇所には
1079 | "Include legacy support for instruction itineraries."とコメントされているので、
1080 | 古いほうである可能性が高い。])
1081 | <><><>
1082 | <>を参考にし、 `SchedMachineModel` をベースとして実装する。
1083 | このとき参考になるのはAArch64で、特に `AArch64SchedA53.td` である<>。
1084 |
1085 | 次の4ステップで実装する<>。
1086 | まずi) `SchedWrite` と `SchedRead` を用いてtargetごとにoperand categoryを定義し、ii)
1087 | その後それらを実際の命令と結びつける。これは命令に `Sched` を継承させることで実現する。
1088 | `Sched` の引数にはオペランドに対応するoperand categoryを順に渡す。
1089 | 例えばADDならwrite, read, readのように並ぶことになる。
1090 |
1091 | 次にiii) sub-target毎に `SchedMachineModel` を用いてモデルを定義するfootnote:[ここでは、
1092 | 「基本的なISAが同じ(単一のtarget)で、それを実装するプロセッサ毎にスペックが異なる
1093 | (sub-target)」というコンセプトが採用されている。]。
1094 | ここで「一度にどれだけの命令を発行できるか」などを決める。
1095 | 最後にiv) `ProcResource` を用いてそのsub-targetがいくつの共有リソースを持っているか決め、
1096 | `WriteRes` を用いてそれらをoperand categoryと結びつける。同時に、その命令を実行するのに
1097 | 何サイクルかかるかを `Latency` として記述する。
1098 |
1099 | 以上で記述した情報を用いて、LLVM core(の `MachineScheduler` )は命令列をシミュレーションし、
1100 | ヒューリスティックを用いてよしなに命令をスケジュールしてくれるらしい。
1101 | ほかにも `ReadAdvance` を用いてフォワーディングを表現したりできるfootnote:[ただしこれは
1102 | `Latency` で表現できることが多いように思う。要検討;TODO]。
1103 | 詳しくは<>を参考のこと。
1104 |
1105 | `Latency` の単位が良くわからない。Cortex-A53のパイプライン図<>
1106 | と比較すると `AArch64SchedA53.td` の記述はfull latencyを4とするなど、
1107 | 明らかに間違っているように見える。
1108 | また `WriteRes` と `ReadAdvance` の両方でフォワーディングを考慮するのは二重でreduced
1109 | cycleをカウントしているようにも見える。わけが分からん。
1110 |
1111 | `include/llvm/MC/MCSchedule.h` を読む。Latencyの概念については
1112 | `struct MCSchedModel` のコメントが(多少)参考になる。
1113 |
1114 | ....
1115 | /// The abstract pipeline is built around the notion of an "issue point". This
1116 | /// is merely a reference point for counting machine cycles. The physical
1117 | /// machine will have pipeline stages that delay execution. The scheduler does
1118 | /// not model those delays because they are irrelevant as long as they are
1119 | /// consistent. Inaccuracies arise when instructions have different execution
1120 | /// delays relative to each other, in addition to their intrinsic latency. Those
1121 | /// special cases can be handled by TableGen constructs such as, ReadAdvance,
1122 | /// which reduces latency when reading data, and ResourceCycles, which consumes
1123 | /// a processor resource when writing data for a number of abstract
1124 | /// cycles.
1125 | ....
1126 |
1127 | TableGenコードのデバッグをする際には次のようにすればよいらしい<>。
1128 |
1129 | $ llvm-tblgen --debug-only=subtarget-emitter --print-records -I=/work/llvm.org/llvm/include/...
1130 |
1131 | これまでのプロセッサ(generic; スーパースカラなし)と
1132 | これからのプロセッサ(emerald; スーパースカラあり)を
1133 | 区別して扱うためにsubtargetを追加する。これによってARMのように
1134 | `-mcpu=generic` ・ `-mcpu=emerald` などとオプションとして
1135 | 指定できるようになる。
1136 |
1137 | コード上は `ProcessorModel` を新たに追加するだけである。
1138 | ARMでは `ProcA53` という `SubtargetFeature` を定義しているが、
1139 | 特別いじる属性などはないためこれは作成しない。
1140 | ただしこれだけでは `llc` のオプションとしては `-mcpu` が機能するが、
1141 | `clang` に渡すと `argument unused during compilation: '-mcpu=emerald'`
1142 | というエラーが出てしまう。
1143 |
1144 | これに対応するためには `clang` でのオプション解析を行う必要がある。
1145 | すなわち `Driver/ToolChains/CommonArgs.cpp` の `tools::getCPUName` をいじって
1146 | `Driver/ToolChains/Arch/CAHP.cpp` の `cahp::getCAHPTargetCPU` が呼ばれるようにする
1147 | foonote:[ちなみにtarget featuresをいじる場合は `Driver/ToolChains/Clang.cpp` の
1148 | `getTargetFeatures` をいじれば良いようだ。]。
1149 | さらにClangの `CAHPTargetInfo` をいじって `isValidCPUName` などを正しく実装する。
1150 | ARMだとClang側からLLVM coreのsupport関数を呼び出すなどして大変なことになっているが、
1151 | その本質はLanaiのバックエンドが分かりやすい。要するに `StringSwitch` を使って、
1152 | 引数の文字列がCPUの名前として正しいかどうかを振り分けているだけである。
1153 | この実装によって「 `-mcpu` が渡された場合にはその引数をcpu nameとして後の処理に回す」
1154 | 「渡されたcpu nameが正しいものであるかを判断し、正しければLLVM coreに渡す」という
1155 | 処理が実装でき、無事Clangでも `-mcpu` が使用できるようになる。
1156 |
1157 | 次のようにすれば `generic` の場合と `emerald` の場合の差を見ることができる。
1158 |
1159 | $ ls bf.c | while read line; do \
1160 | diff <(bin/clang -target cahp -mcpu=generic -c -S -o - -Oz $line) \
1161 | <(bin/clang -target cahp -mcpu=emerald -c -S -o - -Oz $line); done
1162 |
1163 | スケジューリングの詳細を知りたい場合は次のように `llc` を実行する。
1164 |
1165 | $ bin/llc -enable-misched -debug-only=machine-scheduler
1166 |
1167 | なお `clang` で間接的に `llc` を実行したい場合は `-mllvm` オプションにつなげれば良い
1168 | <>。(未確認;TODO)
1169 |
1170 | $ clang ... -mllvm -enable-misched -mllvm -enable-post-misched -mllvm -misched-postra
1171 |
1172 | ただこれらを見ても、なにをもってLLVMがスケジューリングしているのかは
1173 | そこまで自明ではないfooatnote:[`SU` は `SUnit` の略で、多分これはschedule unitの略で、
1174 | つまりスケジューリングの単位なので、各々の命令のことのようだ。]。
1175 | emeraldのエミュレーションで評価するのが一番適切である。TODO
1176 |
1177 | `CAHPSubtarget` にて `enableMachineScheduler` をオーバーライドし
1178 | `true` を返すようにしなければ新しいスケジューラである
1179 | MISchedulerを使用してくれないようだ<>footnote:[これによって
1180 | `llc` に `-enable-misched` オプションが渡る。]footnote:[これによってテストが壊れるので
1181 | 修正する。]。
1182 | また同様に `enablePostRAScheduler` から `true` を返すようにしなければ、
1183 | レジスタ割り付け後のスケジューリングは行ってくれないようだが、
1184 | こちらは実行時エラーが出てしまった。
1185 |
1186 | `ReadALU` のような `ReadSched` は、命令の `Sched` に指定するだけではエラーに
1187 | なってしまう。 `ReadAdvance` などで使用しなければいけない。逆に言えば、
1188 | 特別な属性を指定する必要が無いのであれば作る必要はない。
1189 | また複数の `ReadAdvance` を同じ `Read*` に対して定義することはできない。
1190 | この制限により「 `WriteALU` には `2` で `WriteLdSt` には `1` 」のようなことはできないようだ。
1191 |
1192 | emeraldを「正しく」モデル化しようとすると `WriteALU/WriteLdSt` のlatencyはともに3、
1193 | `ReadAdvance` で `WriteALU` は2, `WriteLdSt` は1とするのが筋のように思えるが、
1194 | 上記の理由からこれは不可能である。仕方がないので `WriteALU` のlatencyを1, `WriteLdSt` を2
1195 | とする。
1196 |
1197 | `enablePostRAScheduler` を `true` にしたい。 `enablePostRAScheduler` のコメントを読むと
1198 | `SchedMachineModel` にて `let PostRAScheduler = 1;` としろと
1199 | 書いてあるfootnote:[`PostRAScheduling` が原文ママだがこれは `PostRAScheduler` の誤植のようだ。]
1200 | のでそうするが、同じエラーが出る。
1201 |
1202 | clang-9: /llvm-project/llvm/lib/CodeGen/MachineBasicBlock.cpp:1494: MachineBasicBlock::livein_iterator llvm::MachineBasicBlock::livein_begin() const: Assertion `getParent()->getProperties().hasProperty( MachineFunctionProperties::Property::TracksLiveness) && "Liveness information is accurate"' failed.
1203 |
1204 | どうやら一部のbasic blockに `TracksLiveness` というフラグが立っていないことが原因のようだ。
1205 | このフラグについては `MachineFunctionProperties` のコメントに次のようにある。
1206 |
1207 | // TracksLiveness: True when tracking register liveness accurately.
1208 | // While this property is set, register liveness information in basic block
1209 | // live-in lists and machine instruction operands (e.g. kill flags, implicit
1210 | // defs) is accurate. This means it can be used to change the code in ways
1211 | // that affect the values in registers, for example by the register
1212 | // scavenger.
1213 | // When this property is clear, liveness is no longer reliable.
1214 |
1215 | AVRやWebAssemblyはこれを次のように明示的に立てている場所がある。
1216 |
1217 | MF.getProperties().set(MachineFunctionProperties::Property::TracksLiveness);
1218 |
1219 | とりあえず次のように `assert` をコメントアウトすれば動作した。
1220 |
1221 | MachineBasicBlock::livein_iterator MachineBasicBlock::livein_begin() const {
1222 | //assert(getParent()->getProperties().hasProperty(
1223 | // MachineFunctionProperties::Property::TracksLiveness) &&
1224 | // "Liveness information is accurate");
1225 | return LiveIns.begin();
1226 | }
1227 |
1228 | `BranchFolder::OptimizeFunction` で `MRI.invalidateLiveness()` が呼び出されることが
1229 | 原因のようだ。これを抑制するためには `TargetRegisterInfo::trackLivenessAfterRegAlloc` を
1230 | `CAHPRegisterInfo` でオーバーライドして `true` を返すようにすれば良い
1231 | footnote:[RISC-Vではbranch relaxationに対応させる際に実装されていた<>。]。
1232 | これで `let PostRAScheduler = 1;` とできるようになった。
1233 |
1234 | == 動的なスタック領域確保に対応する
1235 |
1236 | やるだけ。
1237 |
1238 | == `frameaddr/returnaddr` に対応する
1239 |
1240 | やるだけ。 `frameaddr-returnaddr.ll` の `test_frameaddress_3_alloca` は
1241 | 存在価値がよく分からなかったので削った。
1242 |
1243 | == emergency spillに対応する
1244 |
1245 | RV16Kのときには即値幅が小さすぎたために対応しなかったが、
1246 | 今回は `li` の即値幅が10bitあることもあり対応したほうがよさそうだ。
1247 | RISC-Vの実装にならい、スタックサイズが符号付き9bitに収まらない(256バイト以上)ときに
1248 | emergency spill slotをスタック上に用意し、どんなときでもレジスタを対比できるようにしておく。
1249 | emergency spillの実装そのものは<>を参考にすればよく、
1250 | それほど難しくない。
1251 |
1252 | むしろ同実装のテストケースが、その意味を理解するという点では難解である。
1253 | CAHPの場合は次のようなテストになった。
1254 |
1255 | %data = alloca [ 3000 x i16 ], align 2
1256 | %ptr = getelementptr inbounds [3000 x i16], [3000 x i16]* %data, i16 0, i16 1000
1257 | %1 = tail call { i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16 } asm sideeffect "nop", "=r,=r,=r,=r,=r,=r,=r,=r,=r,=r,=r,=r,=r"()
1258 | %asmresult0 = extractvalue { i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16 } %1, 0
1259 | %asmresult1 = extractvalue { i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16 } %1, 1
1260 | %asmresult2 = extractvalue { i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16 } %1, 2
1261 | %asmresult3 = extractvalue { i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16 } %1, 3
1262 | %asmresult4 = extractvalue { i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16 } %1, 4
1263 | %asmresult5 = extractvalue { i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16 } %1, 5
1264 | %asmresult6 = extractvalue { i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16 } %1, 6
1265 | %asmresult7 = extractvalue { i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16 } %1, 7
1266 | %asmresult8 = extractvalue { i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16 } %1, 8
1267 | %asmresult9 = extractvalue { i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16 } %1, 9
1268 | %asmresult10 = extractvalue { i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16 } %1, 10
1269 | %asmresult11 = extractvalue { i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16 } %1, 11
1270 | %asmresult12 = extractvalue { i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16 } %1, 12
1271 | store volatile i16 %a, i16* %ptr
1272 | tail call void asm sideeffect "nop", "r,r,r,r,r,r,r,r,r,r,r,r,r"(i16 %asmresult0, i16 %asmresult1, i16 %asmresult2, i16 %asmresult3, i16 %asmresult4, i16 %asmresult5, i16 %asmresult6, i16 %asmresult7, i16 %asmresult8, i16 %asmresult9, i16 %asmresult10, i16 %asmresult11, i16 %asmresult12)
1273 |
1274 | まず冒頭で大きくスタック上に領域を確保することにより、
1275 | emergency spill slotの確保を実行させると同時に、以降のレジスタのspillに
1276 | 複数命令(典型的には `lui` と `addi` )を要するようにする。
1277 | この領域は、以降のコードを最適化によって
1278 | 消されること無く確実に実行するためにも用いられるfootnote:[本当にそうかは未確認;TODO]。
1279 |
1280 | 次にインラインアセンブリを用いて `nop` を呼び出す。この `nop` は13個footnote:[この値は
1281 | 実験的に求めた。この値を超えるとレジスタ割り付けを行うことができない。
1282 | この値が理論的にどこから来ているのかは未確認;TODO]のレジスタに値を
1283 | 書き込む命令として扱う。これによって大量のレジスタを消費し、コード生成部にregister pressureを
1284 | かけることがこのテストの本質である。すなわちこの `nop` を実行する際には大量のレジスタのspillが
1285 | 発生しfootnote:[これは生成されるアセンブリを見れば明らかである。]、しかもそれらは
1286 | 一命令で行うことができない。したがってemergency spillが発生する。
1287 | 以降のコードは、この `nop` が最適化によって
1288 | 消されること無く確実に実行するためのコードであると推察されるfootnote:[本当にそうかは未確認
1289 | ;TODO]。
1290 |
1291 | == -----DONE LINE -----
1292 | == 末尾再帰の対応
1293 | == 落ち穂拾い
1294 | === `ROTL/ROTR/BSWAP/CTTZ/CTLZ/CTPOP` に対応する
1295 | === 32bitのシフトに対応する
1296 | === 間接ジャンプに対応する
1297 | === `BlockAddress` のlowerに対応する
1298 | == 可変長引数関数
1299 | == デバッグ情報の出力
1300 |
1301 | == `MachineBlockPlacement` のrollback<>
1302 |
1303 | これ違うっぽい。
1304 |
1305 | [bibliography]
1306 | == 参照
1307 |
1308 | - [[[github_riscv-llvm_docs_01,1]]] https://github.com/lowRISC/riscv-llvm/blob/master/docs/01-intro-and-building-llvm.mkd
1309 | - [[[llvm_getting-started,2]]] https://llvm.org/docs/GettingStarted.html
1310 | - [[[clang_gettings-started,3]]] https://clang.llvm.org/get_started.html
1311 | - [[[asciidoctor_user-manual,4]]] https://asciidoctor.org/docs/user-manual/
1312 | - [[[riscv,5]]] https://riscv.org/
1313 | - [[[riscv_specifications,6]]] https://riscv.org/specifications/
1314 | - [[[fox-llvm,7]]] 『きつねさんでもわかるLLVM〜コンパイラを自作するためのガイドブック〜』(柏木 餅子・風薬・矢上 栄一、株式会社インプレス、2013年)
1315 | - [[[github_riscv-llvm_docs_02,8]]] https://github.com/lowRISC/riscv-llvm/blob/master/docs/02-starting-the-backend.mkd
1316 | - [[[github_riscv-llvm_patch_02,9]]] https://github.com/lowRISC/riscv-llvm/blob/master/0002-RISCV-Recognise-riscv32-and-riscv64-in-triple-parsin.patch
1317 | - [[[github_riscv-llvm,10]]] https://github.com/lowRISC/riscv-llvm
1318 | - [[[youtube_llvm-backend-development-by-example,11]]] https://www.youtube.com/watch?v=AFaIP-dF-RA
1319 | - [[[msyksphinz_try-riscv64-llvm-backend,12]]] http://msyksphinz.hatenablog.com/entry/2019/01/02/040000_1
1320 | - [[[github_riscv-llvm_patch_03,13]]] https://github.com/lowRISC/riscv-llvm/blob/master/0003-RISCV-Add-RISC-V-ELF-defines.patch
1321 | - [[[github_riscv-llvm_patch_04,14]]] https://github.com/lowRISC/riscv-llvm/blob/master/0004-RISCV-Add-stub-backend.patch
1322 | - [[[github_riscv-llvm_patch_06,15]]] https://github.com/lowRISC/riscv-llvm/blob/master/0006-RISCV-Add-bare-bones-RISC-V-MCTargetDesc.patch
1323 | - [[[github_riscv-llvm_patch_10,16]]] https://github.com/lowRISC/riscv-llvm/blob/master/0010-RISCV-Add-support-for-disassembly.patch
1324 | - [[[llvm-writing_backend-operand_mapping,17]]] https://llvm.org/docs/WritingAnLLVMBackend.html#instruction-operand-mapping
1325 | - [[[llvm-writing_backend,18]]] https://llvm.org/docs/WritingAnLLVMBackend.html
1326 | - [[[github_riscv-llvm_patch_07,19]]] https://github.com/lowRISC/riscv-llvm/blob/master/0007-RISCV-Add-basic-RISCVAsmParser.patch
1327 | - [[[github_riscv-llvm_patch_08,20]]] https://github.com/lowRISC/riscv-llvm/blob/master/0008-RISCV-Add-RISCVInstPrinter-and-basic-MC-assembler-te.patch
1328 | - [[[llvm-tablegen,21]]] https://llvm.org/docs/TableGen/index.html
1329 | - [[[github_riscv-llvm_patch_09,22]]] https://github.com/lowRISC/riscv-llvm/blob/master/0009-RISCV-Add-support-for-all-RV32I-instructions.patch
1330 | - [[[llvm_dev_ml-tablegen_definition_question,23]]] http://lists.llvm.org/pipermail/llvm-dev/2015-December/093310.html
1331 | - [[[llvm_doxygen-twine,24]]] https://llvm.org/doxygen/classllvm_1_1Twine.html
1332 | - [[[llvm-tablegen-langref,25]]] https://llvm.org/docs/TableGen/LangRef.html
1333 | - [[[github_riscv-llvm_docs_05,26]]] https://github.com/lowRISC/riscv-llvm/blob/master/docs/05-disassembly.mkd
1334 | - [[[github_riscv-llvm_patch_11,27]]] https://github.com/lowRISC/riscv-llvm/blob/master/0011-RISCV-Add-common-fixups-and-relocations.patch
1335 | - [[[github_riscv-llvm_docs_06,28]]] https://github.com/lowRISC/riscv-llvm/blob/master/docs/06-relocations-and-fixups.mkd
1336 | - [[[github_riscv-llvm_patch_13,29]]] https://github.com/lowRISC/riscv-llvm/blob/master/0013-RISCV-Initial-codegen-support-for-ALU-operations.patch
1337 | - [[[speakerdeck-llvm_backend_development,30]]] https://speakerdeck.com/asb/llvm-backend-development-by-example-risc-v
1338 | - [[[llvm-code_generator,31]]] https://llvm.org/docs/CodeGenerator.html
1339 | - [[[llvm-code_generator-target_independent_code_gen_alg,32]]] https://llvm.org/docs/CodeGenerator.html#target-independent-code-generation-algorithms
1340 | - [[[llvm-code_generator-selectiondag_instruction_selection,33]]] https://llvm.org/docs/CodeGenerator.html#selectiondag-instruction-selection-process
1341 | - [[[github_riscv-llvm_patch_15,34]]] https://github.com/lowRISC/riscv-llvm/blob/master/0015-RISCV-Codegen-support-for-memory-operations.patch
1342 | - [[[cpu0,35]]] https://jonathan2251.github.io/lbd/
1343 | - [[[elvm-llvm_backend,36]]] https://github.com/shinh/llvm/tree/elvm
1344 | - [[[elvm-slide,37]]] http://shinh.skr.jp/slide/llel/000.html
1345 | - [[[github_riscv-llvm_patch_16,38]]] https://github.com/lowRISC/riscv-llvm/blob/master/0016-RISCV-Codegen-support-for-memory-operations-on-globa.patch
1346 | - [[[github_riscv-llvm_patch_17,39]]] https://github.com/lowRISC/riscv-llvm/blob/master/0017-RISCV-Codegen-for-conditional-branches.patch
1347 | - [[[todai_llvm_backend,40]]] https://github.com/cpu-experiment-2018-2/llvm/tree/master/lib/Target/ELMO
1348 | - [[[todai_llvm_backend-article,41]]] http://uenoku.hatenablog.com/entry/2018/12/25/044244
1349 | - [[[github_riscv-llvm_patch_18,42]]] https://github.com/lowRISC/riscv-llvm/blob/master/0018-RISCV-Support-for-function-calls.patch
1350 | - [[[llvm-langref,43]]] http://llvm.org/docs/LangRef.html
1351 | - [[[fpga_develop_diary,44]]] http://msyksphinz.hatenablog.com/
1352 | - [[[llvm-anton_korobeynikov_2012,45]]] https://llvm.org/devmtg/2012-04-12/Slides/Workshops/Anton_Korobeynikov.pdf
1353 | - [[[llvm-welcome_to_the_back_end_2017,46]]] https://www.youtube.com/watch?v=objxlZg01D0
1354 | - [[[ean10-howto-llvmas,47]]] https://www.embecosm.com/appnotes/ean10/ean10-howto-llvmas-1.0.html
1355 | - [[[lowrisc-devmtg18,48]]] https://www.lowrisc.org/llvm/devmtg18/
1356 | - [[[LLVMBackend_2015_03_26_v2,49]]] http://www.inf.ed.ac.uk/teaching/courses/ct/other/LLVMBackend-2015-03-26_v2.pdf
1357 | - [[[rui-compilerbook,50]]] https://www.sigbus.info/compilerbook
1358 | - [[[krister-writing_gcc_backend,51]]] https://kristerw.blogspot.com/2017/08/writing-gcc-backend_4.html
1359 | - [[[llvm-ml-129089,52]]] http://lists.llvm.org/pipermail/llvm-dev/2019-January/129089.html
1360 | - [[[llvm-langref-datalayout,53]]] https://llvm.org/docs/LangRef.html#langref-datalayout
1361 | - [[[github-frasercrmck_llvm_leg,54]]] https://github.com/frasercrmck/llvm-leg/tree/master/lib/Target/LEG
1362 | - [[[llvm_doxygen-InitMCRegisterInfo,55]]] https://llvm.org/doxygen/classllvm_1_1MCRegisterInfo.html#a989859615fcb74989b4f978c4d227a03
1363 | - [[[llvm-programmers_manual,56]]] http://llvm.org/docs/ProgrammersManual.html
1364 | - [[[llvm-writing_backend-calling_conventions,57]]] https://llvm.org/docs/WritingAnLLVMBackend.html#calling-conventions
1365 | - [[[riscv-calling,58]]] https://riscv.org/wp-content/uploads/2015/01/riscv-calling.pdf
1366 | - [[[llvm_dev_ml-how_to_debug_instruction_selection,59]]] http://lists.llvm.org/pipermail/llvm-dev/2017-August/116501.html
1367 | - [[[fpga_develop_diary-20190612040000,60]]] http://msyksphinz.hatenablog.com/entry/2019/06/12/040000
1368 | - [[[llvm_dev_ml-br_cc_questions,61]]] http://lists.llvm.org/pipermail/llvm-dev/2014-August/075303.html
1369 | - [[[llvm_dev_ml-multiple_result_instrs,62]]] https://groups.google.com/forum/#!topic/llvm-dev/8kPOj-_lbGk
1370 | - [[[stackoverflow-frame_lowering,63]]] https://stackoverflow.com/questions/32872946/what-is-stack-frame-lowering-in-llvm
1371 | - [[[llvm_dev_ml-selecting_frame_index,64]]] https://groups.google.com/d/msg/llvm-dev/QXwtqgau-jA/PwnHDF0gG_oJ
1372 | - [[[fpga_develop_diary-llvm,65]]] https://github.com/msyksphinz/llvm/tree/myriscvx/impl90/lib/Target/MYRISCVX
1373 | - [[[llvm-github_cd44ae,66]]] https://github.com/llvm/llvm-project/commit/cd44aee3da22f9a618f2e63c226bebf615fa8cf8
1374 | - [[[llvm_phabricator-d43752,67]]] https://reviews.llvm.org/D43752
1375 | - [[[llvm-compilerwriterinfo,68]]] https://llvm.org/docs/CompilerWriterInfo.html
1376 | - [[[wikipedia-The_Gleaners,69]]] https://en.wikipedia.org/wiki/The_Gleaners
1377 | - [[[github_riscv-llvm_patch_20,70]]] https://github.com/lowRISC/riscv-llvm/blob/master/0020-RISCV-Support-and-tests-for-a-variety-of-additional-.patch
1378 | - [[[llvm_phabricator-d47422,71]]] https://reviews.llvm.org/D47422
1379 | - [[[llvm-extendingllvm,72]]] https://llvm.org/docs/ExtendingLLVM.html
1380 | - [[[llvm_dev_ml-001264,73]]] http://lists.llvm.org/pipermail/llvm-dev/2004-June/001264.html
1381 | - [[[llvm_phabricator-d42958,74]]] https://reviews.llvm.org/D42958
1382 | - [[[compiler_rt,75]]] https://compiler-rt.llvm.org/
1383 | - [[[github-riscv_compiler_rt,76]]] https://github.com/andestech/riscv-compiler-rt
1384 | - [[[github_riscv-llvm_patch_27,77]]] https://github.com/lowRISC/riscv-llvm/blob/master/0027-RISCV-Support-stack-frames-and-offsets-up-to-32-bits.patch
1385 | - [[[llvm_phabricator-d44885,78]]] https://reviews.llvm.org/D44885
1386 | - [[[llvm_phabricator-d45859,79]]] https://reviews.llvm.org/D45859
1387 | - [[[llvm-langref-poison_value,80]]] http://llvm.org/docs/LangRef.html#poisonvalues
1388 | - [[[github-emscripten-issues-34,81]]] https://github.com/emscripten-core/emscripten/issues/34
1389 | - [[[switch_lowering_in_llvm,82]]] http://fileadmin.cs.lth.se/cs/education/edan75/part2.pdf
1390 | - [[[github-avr_llvm-issues-88,83]]] https://github.com/avr-llvm/llvm/issues/88
1391 | - [[[asciidoctor-quickref,84]]] https://asciidoctor.org/docs/asciidoc-syntax-quick-reference/
1392 | - [[[llvm_phabricator-d56351,85]]] https://reviews.llvm.org/D56351
1393 | - [[[hatenablog-rhysd-230119,86]]] https://rhysd.hatenablog.com/entry/2017/03/13/230119
1394 | - [[[llvm_dev_ml-115805,87]]] http://lists.llvm.org/pipermail/llvm-dev/2017-July/115805.html
1395 | - [[[github_riscv-llvm_patch_29,88]]] https://github.com/lowRISC/riscv-llvm/blob/master/0029-RISCV-Add-support-for-llvm.-frameaddress-returnaddre.patch
1396 | - [[[github-riscv_llvm-clang,89]]] https://github.com/lowRISC/riscv-llvm/tree/master/clang
1397 | - [[[github-elvm_clang,90]]] https://github.com/shinh/clang/tree/elvm
1398 | - [[[github_riscv-llvm_patch_22,91]]] https://github.com/lowRISC/riscv-llvm/blob/master/0022-RISCV-Support-lowering-FrameIndex.patch
1399 | - [[[llvm_dev_ml-087879,92]]] http://lists.llvm.org/pipermail/llvm-dev/2015-July/087879.html
1400 | - [[[stackoverflow-27467293,93]]] https://stackoverflow.com/questions/27467293/how-to-force-clang-use-llvm-assembler-instead-of-system
1401 | - [[[github-riscv_llvm-clang-03,94]]] https://github.com/lowRISC/riscv-llvm/blob/master/clang/0003-RISCV-Implement-clang-driver-for-the-baremetal-RISCV.patch
1402 | - [[[github_riscv-llvm_patch_25,95]]] https://github.com/lowRISC/riscv-llvm/blob/master/0025-RISCV-Add-custom-CC_RISCV-calling-convention-and-imp.patch
1403 | - [[[llvm_dev_ml-106187,96]]] http://lists.llvm.org/pipermail/llvm-dev/2016-October/106187.html
1404 | - [[[llvm_phabricator-d39322,97]]] https://reviews.llvm.org/D39322
1405 | - [[[cpu0-lld,98]]] http://jonathan2251.github.io/lbt/lld.html
1406 | - [[[youtube-how_to_add_a_new_target_to_lld,99]]] https://www.youtube.com/watch?v=FIXaeRU31Ww
1407 | - [[[llvm-smith_newlldtargetpdf,100]]] https://llvm.org/devmtg/2016-09/slides/Smith-NewLLDTarget.pdf
1408 | - [[[llvm-lld,101]]] https://lld.llvm.org/index.html
1409 | - [[[note-n9948f0cc3ed3,102]]] https://note.mu/ruiu/n/n9948f0cc3ed3
1410 | - [[[lanai-isa,103]]] https://docs.google.com/document/d/1jwAc-Rbw1Mn7Dbn2oEB3-0FQNOwqNPslZa-NDy8wGRo/pub
1411 | - [[[github-blog_os-issues-370,104]]] https://github.com/phil-opp/blog_os/issues/370
1412 | - [[[llvm_phabricator-d61688,105]]] https://reviews.llvm.org/D61688
1413 | - [[[man-xtensa_linux_gnu_ld,106]]] https://linux.die.net/man/1/xtensa-linux-gnu-ld
1414 | - [[[man-elf,107]]] https://linuxjm.osdn.jp/html/LDP_man-pages/man5/elf.5.html
1415 | - [[[llvm_phabricator-d45385,108]]] https://reviews.llvm.org/D45385
1416 | - [[[llvm_phabricator-d47882,109]]] https://reviews.llvm.org/D47882
1417 | - [[[llvm_dev_ml-128257,110]]] https://lists.llvm.org/pipermail/llvm-dev/2018-December/128257.html
1418 | - [[[github_riscv-llvm_patch_31,111]]] https://github.com/lowRISC/riscv-llvm/blob/master/0031-RISCV-Implement-support-for-the-BranchRelaxation-pas.patch
1419 | - [[[github_riscv-llvm_patch_30,112]]] https://github.com/lowRISC/riscv-llvm/blob/master/0030-RISCV-Implement-branch-analysis.patch
1420 | - [[[stackoverflow-5789806,113]]] https://stackoverflow.com/questions/5789806/meaning-of-and-in-c
1421 | - [[[compiler_study_report,114]]] https://proc-cpuinfo.fixstars.com/2018/11/compiler_study_report/
1422 | - [[[github-llvm-bcb36be8e3f5dced36710ba1a2e2206071ccc7ba,115]]] https://github.com/llvm/llvm-project/commit/bcb36be8e3f5dced36710ba1a2e2206071ccc7ba
1423 | - [[[llvm_dev_ml-059799,116]]] http://lists.llvm.org/pipermail/llvm-dev/2013-February/059799.html
1424 | - [[[tricore-llvm-slides,117]]] https://reup.dmcs.pl/wiki/images/7/7a/Tricore-llvm-slides.pdf
1425 | - [[[tricore-llvm,118]]] https://opus4.kobv.de/opus4-fau/files/1108/tricore_llvm.pdf
1426 | - [[[llvm_dev_ml-111697,119]]] http://lists.llvm.org/pipermail/llvm-dev/2017-April/111697.html
1427 | - [[[takayuki-no09,120]]] http://www.ertl.jp/~takayuki/readings/c/no09.html
1428 | - [[[hwenginner-linker,121]]] https://hwengineer.github.io/linker/
1429 | - [[[koikikukan-000300,122]]] http://www.koikikukan.com/archives/2017/04/05-000300.php
1430 | - [[[stackoverflow-57735654_34997577,123]]] https://stackoverflow.com/questions/34997577/linker-script-allocation-of-bss-section#comment57735654_34997577
1431 | - [[[redhat-ld_simple_example,124]]] https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/4/html/Using_ld_the_GNU_Linker/simple-example.html
1432 | - [[[llvm_phabricator-d45395,125]]] https://reviews.llvm.org/D45395
1433 | - [[[llvm_phabricator-d45395-398662,126]]] https://reviews.llvm.org/D45395#inline-398662
1434 | - [[[llvm-langref-inline_asm,127]]] http://llvm.org/docs/LangRef.html#inline-assembler-expressions
1435 | - [[[hazymoon-gcc_inline_asm,128]]] http://caspar.hazymoon.jp/OpenBSD/annex/gcc_inline_asm.html
1436 | - [[[github_riscv-llvm_patch_28,129]]] https://github.com/lowRISC/riscv-llvm/blob/master/0028-RISCV-Add-basic-support-for-inline-asm-constraints.patch
1437 | - [[[llvm-langref-inline_asm-asm_template_argument_modifier,130]]] http://llvm.org/docs/LangRef.html#asm-template-argument-modifiers
1438 | - [[[github-llvm-0715d35ed5ac2312951976bee2a0d2587f98f39f,131]]] https://github.com/llvm/llvm-project/commit/0715d35ed5ac2312951976bee2a0d2587f98f39f
1439 | - [[[github_riscv-llvm_patch_32,132]]] https://github.com/lowRISC/riscv-llvm/blob/master/0032-RISCV-Reserve-an-emergency-spill-slot-for-the-regist.patch
1440 | - [[[github_riscv-llvm_patch_26,133]]] https://github.com/lowRISC/riscv-llvm/blob/master/0026-RISCV-Support-for-varargs.patch
1441 | - [[[github-fracture-wiki-how-dagisel-works,134]]] https://github.com/draperlaboratory/fracture/wiki/How-TableGen%27s-DAGISel-Backend-Works
1442 | - [[[welcome_to_the_back_end-slides,135]]] http://llvm.org/devmtg/2017-10/slides/Braun-Welcome%20to%20the%20Back%20End.pdf
1443 | - [[[life_of_an_instruction,136]]] https://eli.thegreenplace.net/2012/11/24/life-of-an-instruction-in-llvm/
1444 | - [[[shinh-blog-010637,137]]] http://shinh.hatenablog.com/entry/2014/10/03/010637
1445 | - [[[llvm_backend_intro,138]]] https://www.slideshare.net/AkiraMaruoka/llvm-backend
1446 | - [[[amazon-llvm_cookbook-customer_review,139]]] https://www.amazon.co.jp/dp/178528598X#customer_review-R28L2NAL8T9M2H
1447 | - [[[llvm_dev_ml-117139,140]]] https://lists.llvm.org/pipermail/llvm-dev/2017-September/117139.html
1448 | - [[[github_riscv-llvm_patch_85,141]]] https://github.com/lowRISC/riscv-llvm/blob/master/0085-RISCV-Set-AllowRegisterRenaming-1.patch
1449 | - [[[llvm_dev_ml-135337,142]]] https://lists.llvm.org/pipermail/llvm-dev/2019-September/135337.html
1450 | - [[[wikipedia-weak_symbol,143]]] https://en.wikipedia.org/wiki/Weak_symbol
1451 | - [[[wikipedia-remat,144]]] https://en.wikipedia.org/wiki/Rematerialization
1452 | - [[[llvm_phabricator-d46182,145]]] https://reviews.llvm.org/D46182
1453 | - [[[nakata-compiler,146]]] 『コンパイラの構成と最適化(第2版)』(中田育男、朝倉書店、2009)
1454 | - [[[fpga_develop_diary-to_llvm9,147]]] http://msyksphinz.hatenablog.com/entry/2019/08/17/040000
1455 | - [[[llvm_phabricator-d60488,148]]] https://reviews.llvm.org/D60488
1456 | - [[[llvm_phabricator-rl364191,149]]] https://reviews.llvm.org/rL364191
1457 | - [[[llvm_phabricator-d64121,150]]] https://reviews.llvm.org/D64121
1458 | - [[[llvm-codingstandards,151]]] https://llvm.org/docs/CodingStandards.html
1459 | - [[[llvm_dev_ml-134921,152]]] https://lists.llvm.org/pipermail/llvm-dev/2019-September/134921.html
1460 | - [[[llvm_phabricator-d43256,153]]] https://reviews.llvm.org/D43256
1461 | - [[[llvm_dev_ml-114675,154]]] http://lists.llvm.org/pipermail/llvm-dev/2017-June/114675.html
1462 | - [[[llvm_phabricator-d42780,155]]] https://reviews.llvm.org/D42780
1463 | - [[[llvm_phabricator-d51732,156]]] https://reviews.llvm.org/D51732
1464 | - [[[llvm_devmtg-schedmachinemodel,157]]] http://llvm.org/devmtg/2014-10/Slides/Estes-MISchedulerTutorial.pdf
1465 | - [[[llvm_dev_ml-098535,158]]] https://lists.llvm.org/pipermail/llvm-dev/2016-April/098535.html
1466 | - [[[llvm_devmtg-writinggreatsched,159]]] https://www.youtube.com/watch?v=brpomKUynEA
1467 | - [[[anandtech-11441,160]]] https://www.anandtech.com/show/11441/dynamiq-and-arms-new-cpus-cortex-a75-a55/4
1468 | - [[[llvm_devmtg-larintrick,161]]] https://llvm.org/devmtg/2012-11/Larin-Trick-Scheduling.pdf
1469 | - [[[llvm-schedinorder,162]]] https://llvm.org/devmtg/2016-09/slides/Absar-SchedulingInOrder.pdf
1470 |
--------------------------------------------------------------------------------
/draft-rv32kv1.asciidoc:
--------------------------------------------------------------------------------
1 | = [下書き] LLVMバックエンド開発文書 for RV32Kv1
2 | 艮鮟鱇
3 |
4 | == これはなに
5 |
6 | 2019年にRV32Kv1という自作ISA用のLLVMバックエンドを作ったときの自分とメンバ用のメモ。
7 | メモなので当然読みにくい。これをブラッシュアップしてまともな文章にする予定だったが、
8 | その作業が遅れているので、一旦メモのまま公開する。内容について質問したい場合は
9 | Twitter https://twitter.com/ushitora_anqou[@ushitora_anqou]までリプライなどを貰えれば反応するかもしれない。
10 |
11 | ソースコードは未公開。
12 |
13 | ブラッシュアップはlink:https://github.com/ushitora-anqou/write-your-llvm-backend[GitHub]にて行っている。
14 |
15 | このLLVMバックエンドの開発は、もともと2019年度未踏事業において
16 | link:https://github.com/virtualsecureplatform/kvsp[Virtual Secure Platform]を開発するために行われた。
17 |
18 | == アセンブラ簡単使い方
19 |
20 | とりあえずビルドする。ビルドには
21 |
22 | * `cmake`
23 | * `ninja`
24 | * `clang`
25 | * `clang++`
26 |
27 | が必要。
28 |
29 | これらを入れた後 `cmake` を次のように走らせる。
30 | ....
31 | $ cmake -G Ninja \
32 | -DLLVM_ENABLE_PROJECTS=clang \
33 | -DCMAKE_BUILD_TYPE="Debug" \
34 | -DBUILD_SHARED_LIBS=True \
35 | -DLLVM_USE_SPLIT_DWARF=True \
36 | -DLLVM_OPTIMIZED_TABLEGEN=True \
37 | -DLLVM_BUILD_TESTS=True \
38 | -DCMAKE_C_COMPILER=clang \
39 | -DCMAKE_CXX_COMPILER=clang++ \
40 | -DLLVM_USE_LINKER=lld \
41 | -DLLVM_TARGETS_TO_BUILD="X86" \
42 | -DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD="RISCV;RV32K" \
43 | ../llvm
44 | $ cmake --build .
45 | ....
46 | その後アセンブラを起動する。アセンブラは `build/bin/llvm-mc` である。
47 | ....
48 | $ cat foo.s
49 | li x9, 3
50 | mv x11, x1
51 | sub x9, x10
52 | add x8, x1
53 | nop
54 |
55 | $ bin/llvm-mc -arch=rv32k -filetype=obj foo.s | od -tx1z -Ax -v
56 | 000000 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 >.ELF............<
57 | 000010 01 00 f5 00 01 00 00 00 00 00 00 00 00 00 00 00 >................<
58 | 000020 68 00 00 00 00 00 00 00 34 00 00 00 00 00 28 00 >h.......4.....(.<
59 | 000030 04 00 01 00 8d 44 86 85 89 8c 06 94 01 00 00 00 >.....D..........<
60 | 000040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 >................<
61 | 000050 00 2e 74 65 78 74 00 2e 73 74 72 74 61 62 00 2e >..text..strtab..<
62 | 000060 73 79 6d 74 61 62 00 00 00 00 00 00 00 00 00 00 >symtab..........<
63 | 000070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 >................<
64 | 000080 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 >................<
65 | 000090 07 00 00 00 03 00 00 00 00 00 00 00 00 00 00 00 >................<
66 | 0000a0 50 00 00 00 17 00 00 00 00 00 00 00 00 00 00 00 >P...............<
67 | 0000b0 01 00 00 00 00 00 00 00 01 00 00 00 01 00 00 00 >................<
68 | 0000c0 06 00 00 00 00 00 00 00 34 00 00 00 0a 00 00 00 >........4.......<
69 | 0000d0 00 00 00 00 00 00 00 00 04 00 00 00 00 00 00 00 >................<
70 | 0000e0 0f 00 00 00 02 00 00 00 00 00 00 00 00 00 00 00 >................<
71 | 0000f0 40 00 00 00 10 00 00 00 01 00 00 00 01 00 00 00 >@...............<
72 | 000100 04 00 00 00 10 00 00 00 >........<
73 | 000108
74 | ....
75 | 0x34から0x3dにある `8d 44 86 85 89 8c 06 94 01 00` が出力である。
76 |
77 | このような出力の他に `-show-encoding` を用いる方法もある。
78 | ....
79 | $ bin/llvm-mc -arch=rv32k -show-encoding foo.s
80 | .text
81 | li x9, 3 # encoding: [0x8d,0x44]
82 | mv x11, x1 # encoding: [0x86,0x85]
83 | sub x9, x10 # encoding: [0x89,0x8c]
84 | add x8, x1 # encoding: [0x06,0x94]
85 | nop # encoding: [0x01,0x00]
86 | ....
87 |
88 | == 概要
89 |
90 | これを読めば自作アーキテクチャ(RV32Kv1)の機械語を出力するLLVMバックエンドを作成することができる。
91 |
92 | この文書はAsciiDocを用いて記述されている。記述方法については<>を参照のこと。
93 |
94 | == 参考にすべき資料
95 |
96 | * Writing an LLVM Backend<>
97 | * 『きつねさんでもわかるLLVM〜コンパイラを自作するためのガイドブック〜』<>
98 | * RISC-V support for LLVM projects<>
99 | LLVMにRISC-Vサポートを追加するパッチ群。バックエンドを開発するためのチュートリアルも兼ねているらしく `docs/` 及びそれと対応したpatchが参考になる。
100 | またこれについて、開発者が2018 LLVM Developers' Meetingで登壇したときの動画は<>より閲覧できる。
101 | スライドは<>より閲覧できる。
102 | * TableGenのLLVMのドキュメント<>
103 | * The LLVM Target-Independent Code Generator<>
104 | * Create an LLVM Backend for the Cpu0 Architecture<>
105 | ** これをもとにLLVMバックエンドを開発しているブログ<>
106 | * ELVMバックエンド<>
107 | ** 限られた命令でLLVM IRの機能を達成する例として貴重。
108 | *** でも意外とISAはリッチだったりする。
109 | ** Hamajiさんのスライドも参考になる<>。
110 | * 2018年度東大CPU実験で開発されたLLVM Backend<>
111 | ** これについて書かれたAdCのエントリもある<>。
112 | * LLVM Language Reference Manual<>
113 | ** LLVM IRについての言語リファレンス。意外と実装すべき命令が少ないことが分かる。
114 |
115 | == RV32Kv1アーキテクチャ仕様
116 |
117 | RV32Kv1はRISC-V<>のISA<>であるRV32Cをベースとして設定する。
118 | RV32Cからそのまま借用する命令は下記のとおりである。
119 |
120 | * LWSP
121 | * SWSP
122 | * LW
123 | * SW
124 | * J
125 | * JAL
126 | * JR
127 | * JALR
128 | * BEQZ
129 | * BNEZ
130 | * LI
131 | * MV
132 | * ADD
133 | * SUB
134 | * NOP
135 |
136 | RV32Cに無く、新設する命令は下記のとおりである。
137 |
138 | * SLT
139 | ** 次に示すReservedのうち上の方を使う。
140 |
141 | image::img/rv32c_reserved_insts.png[]
142 |
143 | エンディアンにはリトルエンディアンを採用する。
144 |
145 | レジスタは次の通り。
146 |
147 | * x1/ra : return address register / ABI link register
148 | * x2/sp : ABI stack pointer
149 | * x8-x15 : general purpose register
150 |
151 | 分岐時のPC更新は `PC <- PC + d` とする( `PC <- PC + 1 + d` **でない**)。
152 |
153 | 関数呼び出し規約は次の通り。
154 |
155 | * x8-x15 : 引数0番目〜7番目
156 | * x8 : 戻り値
157 | * sp : callee-saved
158 | * x15 : frame register
159 |
160 | == マイルストーン
161 |
162 | * MachineCodeからMCLayerに変換するアセンブラ
163 | ** RV32Kアセンブリを受け取ってELFバイナリを出力する。
164 | ** ELFバイナリである必要は微塵もないが、既存のLLVMコードベースを利用するためにはこれが必要。
165 | *** 最終的には自作バイナリに出力するようにしたい。
166 | ** 実際に食わせるためにはELFバイナリを適当にstripする変換器を必要するかもしれない。
167 | ** というか同一ファイル内でシンボル解決を行うリンカもどきが必要になる。あと `main` もとい `_start` がバイナリの一番最初にないといけないなどありそう。
168 | ** 自作C標準ライブラリも必要かも。
169 |
170 | == LLVMのソースコードを用意する
171 |
172 | LLVMのソースコードを取得する。今回の開発ではv8.0.0をベースとする。
173 | Git上でrv32kブランチを作り、その上で開発する。
174 |
175 | ....
176 | $ git clone https://github.com/llvm/llvm-project.git
177 | $ cd llvm-project
178 | $ git checkout llvmorg-8.0.0
179 | $ git checkout -b rv32k
180 | ....
181 |
182 | == LLVM・Clangをビルドする
183 |
184 | <>, <>, <>を参考にしてLLVMとClangをNinjaを用いてビルドする。
185 | 以降の開発において参考にするため、x86とRISC VをLLVM・Clangの出力可能ターゲットとして指定する。
186 |
187 | なお、CPUがIntel Core i5 7200U、メモリが8GBのLet's note上でのビルドには1時間半程度要した。
188 |
189 | ....
190 | $ mkdir build
191 | $ cd build
192 | $ cmake -G Ninja \
193 | -DLLVM_ENABLE_PROJECTS=clang \
194 | -DCMAKE_BUILD_TYPE="Debug" \
195 | -DBUILD_SHARED_LIBS=True \
196 | -DLLVM_USE_SPLIT_DWARF=True \
197 | -DLLVM_OPTIMIZED_TABLEGEN=True \
198 | -DLLVM_BUILD_TESTS=True \
199 | -DCMAKE_C_COMPILER=clang \
200 | -DCMAKE_CXX_COMPILER=clang++ \
201 | -DLLVM_USE_LINKER=lld \
202 | -DLLVM_TARGETS_TO_BUILD="X86" \
203 | -DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD="RISCV" \
204 | ../llvm
205 | $ cmake --build . # OR ninja
206 | ....
207 |
208 | == LLVM・Clangを使う
209 |
210 | RV32バックエンドを使う例をいくつか掲げる<>。
211 |
212 | ....
213 | $ bin/clang -target riscv32-unknown-linux-gnu -S -o main.s main.c # main.cをRV32のアセンブリにコンパイル
214 | $ bin/clang -target riscv32-unknown-linux-gnu -emit-llvm -o main.bc -c main.c # main.cをRV32用のLLVM IRにコンパイル
215 | $ llvm-project/build/bin/llvm-dis main.bc -o - # LLVM IRをテキスト形式に変換
216 | ....
217 |
218 | == LLVMをテストする
219 |
220 | `llvm-lit` を使用してLLVMをテストできる。
221 |
222 | ....
223 | $ bin/llvm-lit test -s # 全てのテストを実行する
224 | $ bin/llvm-lit -s --filter 'RV32K' test # RV32Kを含むテストを実行する
225 | $ bin/llvm-lit -as --filter 'RV32K' test # テスト結果を詳細に表示する
226 | ....
227 |
228 | == スケルトンバックエンドを追加する
229 |
230 | <>を参考に、中身のないスケルトンのバックエンドをLLVMに追加する。
231 | これによって `llc` などの出力などにRV32Kが追加される。
232 |
233 | === RV32KをTripleに追加する
234 |
235 | 参照先<>のパッチのとおりに書き換える。
236 | 基本的に `riscv32` という文字列を検索し、都度 `rv32k` の項目を追加していけばよい。
237 | ただし、RISC-Vには32bit( `riscv32` )と64bit( `riscv64` )の変種があるため変換テーブルを用意する必要があるが、
238 | RV32Kにはその必要はない。 `UnknownArch` を返せば良い。
239 |
240 | コードを改変した後 `$ bin/llvm-lit -s --filter "Triple" test` でテストを行う。
241 |
242 | === RV32KのELF定義を追加する
243 |
244 | <>を参考にファイルを書き換える。
245 | 途中 `llvm-objdump.cpp` の書き換え該当箇所が見当たらない。とりあえず放置(TODO)。
246 |
247 | 出力するELF形式のテストは、出力すべきELFの細部が決まっていないため、
248 | とりあえず書かないでおく(TODO)footnote:[この変更は後で行ったため、後ろの記述に齟齬が発生する場合がある(TODO)。]。
249 |
250 | === バックエンドを追加する
251 |
252 | <>を参考にファイルを修正・追加する。
253 | ただし `RV32KTargetMachine::RV32KTargetMachine()` の初期化子で使用している
254 | `getEffectiveCodeModel()` がそのままではコンパイルエラーを出すため修正するfootnote:[同ファイル内にある同名のstatic関数を関数呼び出しの候補に入れていないようだ。原因不明。v8.0.0にて採用されているRISC-Vのコードを参考に、別の場所で定義されている同名関数を利用するよう修正した。]。
255 |
256 | ファイル中のライセンス条項などは、将来的にはApache 2.0 Licenseを明記する予定だが、
257 | とりあえず削除しておくfootnote:[LLVMのARMバックエンドはApache 2.0 Licenseに例外条項を
258 | 付与したライセンスになっていた。検討の余地あり。]。
259 |
260 | さて追加したバックエンドも含めてLLVMの再ビルドを行う。
261 | そのためには再び `cmake` を実行する必要がある:
262 | ....
263 | $ cmake -G Ninja \
264 | -DLLVM_ENABLE_PROJECTS=clang \
265 | -DCMAKE_BUILD_TYPE="Debug" \
266 | -DBUILD_SHARED_LIBS=True \
267 | -DLLVM_USE_SPLIT_DWARF=True \
268 | -DLLVM_OPTIMIZED_TABLEGEN=True \
269 | -DLLVM_BUILD_TESTS=True \
270 | -DCMAKE_C_COMPILER=clang \
271 | -DCMAKE_CXX_COMPILER=clang++ \
272 | -DLLVM_USE_LINKER=lld \
273 | -DLLVM_TARGETS_TO_BUILD="X86" \
274 | -DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD="RISCV;RV32K" \
275 | ../llvm
276 | $ cmake --build . # OR ninja
277 | ....
278 |
279 | 無事 `llc --version` にRV32Kが含まれるようになった:
280 | ....
281 | $ bin/llc --version
282 | LLVM (http://llvm.org/):
283 | LLVM version 8.0.0
284 | DEBUG build with assertions.
285 | Default target: x86_64-unknown-linux-gnu
286 | Host CPU: skylake
287 |
288 | Registered Targets:
289 | riscv32 - 32-bit RISC-V
290 | riscv64 - 64-bit RISC-V
291 | rv32k - RV32K
292 | x86 - 32-bit X86: Pentium-Pro and above
293 | x86-64 - 64-bit X86: EM64T and AMD64
294 | ....
295 |
296 | == 簡易的なアセンブラを実装する
297 |
298 | スケルトンバックエンドに実装を追加して、簡易アセンブラを構築する。
299 | わざわざ「簡易」と銘打っているのは、命令を
300 | `LI`, `MV`, `ADD`, `SUB`, `NOP` のみに限るためであるfootnote:[この方針はアセンブラを書き始めてから決めたため、以下の記述に不整合が生じている場合がある。いずれ修正する(TODO)。]。
301 | 残りの命令については後の章で実装する予定であるfootnote:[蓋し `RV32KInstrInfo.td` を書くのは見た目によらず難しい。ある程度進んでから戻ってくるほうが良いだろう。]。
302 |
303 | === TableGenファイルを追加する
304 |
305 | LLVMのDSLであるTableGenを使用し、RV32Kのレジスタや命令について記述する。
306 | 追加すべきTableGenファイルは次の通り:
307 |
308 | * `RV32KRegisterInfo.td`: レジスタ
309 | * `RV32KInstrFormats.td`: 命令形式
310 | * `RV32KInstrInfo.td`: 命令
311 | * `RV32K.td`: 全体
312 |
313 | `RISCV` ディレクトリ以下に `RISCVInstrFormatsC.td` や `RISCVInstrInfoC.td` などがあるので参考にする。
314 | `LWSP` はエンコードされると `SP` の情報を持たないが、表記上は `lwsp rd, 100(sp)` と書くため `SP` をとる必要がある。
315 | これのために `RegisterClass` として `SP` を定義している。ただし `SP` には `X2` のみが所属する。
316 | また、単純に全てのレジスタを `GPR` として1つの括りにしてしまうと、
317 | `sub` 命令のオペランドとして `x2` などが許容されてしまう。
318 | これを防ぐため、 `x8` から `x15` を束ねて `GPRC` という名の `RegisterClass` を作成し、これを `sub` 命令のオペランドの型として指定する。
319 |
320 | 即値を単純にとる命令はそのまま記述すればよいが、デコードした値を左シフトするような命令は注意が必要である。
321 | すなわち即値の `[7:2]` のみを生成コード中に持つような場合は `Instruction` 中に8bitの即値を宣言し、
322 | その `[7:2]` 部分が `Inst` と対応するという形をとる。
323 | なお、この点について `RISCVInstrFormatsC.td` に書いてある `RVInst16CJ` の実装が間違っている気がする。
324 | `offset` は12bitではなかろうか(TODO <>)。また `Bcz` も同様の問題があるように見える(TODO)。
325 |
326 | 文字列リテラル中の変数展開をTableGenはサポートしている。すなわち `"$rd, ${imm}(${rs})"` と書けばレジスタ `rd` や `rs`、 即値 `imm` の中身が展開される。
327 | なおここで変数名を `{}` で囲む**必要がある**。というのもTableGenでは `(` や `)`
328 | もオペランドの名前として認識されてしまうからだ。
329 | 実際 `$rd, $imm(${rs})` と書くと次のようなエラーになる。
330 | ....
331 | error: unable to find operand: 'imm('
332 | ....
333 | なおここでの表記はパーズの際に行うパターンマッチのパターンとしての役割と、
334 | アセンブリとして出力を行う際のパターンとしての役割があるようだ(TODO: ほんまか?)。
335 |
336 | //`ins` や `outs` で使用するレジスタ・即値の名前は `RV32KInstrFormats.td` でフィールドとして定義した名前と一致する必要があるようだ。//TODO: ほんまか?
337 | 2アドレス方式などで、読み込むレジスタと書き込むレジスタが一致する場合 `let Constraints = "$rd = $rd_wb";` などと書けばよい。
338 |
339 | 必要なTableGenファイルを追加した後、これらのTableGenファイルが正しいかどうか `llvm-tblgen` を用いて確認する:
340 | ....
341 | $ bin/llvm-tblgen -I ../llvm/lib/Target/RV32K/ -I ../llvm/include/ -I ../llvm/lib/Target/ ../llvm/lib/Target/RV32K/RV32K.td
342 | ....
343 | 初めて実行すると、おそらくたくさんエラーがでるので頑張って全て修正する。
344 | 変換が成功した場合、生成されたコードが、自分が得たいものになっているかどうかを確認する。
345 |
346 | === RV32K用の `MCTargetDesc` を追加する
347 |
348 | RV32KのELF形式オブジェクトファイルを出力できるようにする。そのために `LLVMInitializeRV32KTargetMC()` を実装する。
349 | `MCTargetDesc` サブディレクトリを作成し、その中に `RV32KMCTargetDesc.{cpp,h}` を作成する。
350 | 他のファイルに依存する部分はコメントアウトして、適当な `CMakeLists.txt` や `LLVMBuild.txt` を作成・編集すると、
351 | ビルドが通るようになる。そこで次に実装すべき場所を次のように特定する:
352 | ....
353 | $ bin/llvm-mc -arch=rv32k -filetype=obj foo.s
354 | llvm-mc: /home/anqou/workspace/llvm-project/llvm/tools/llvm-mc/llvm-mc.cpp:355: int main(int, char **): Assertion `MAI && "Unable to create target asm info!"' failed.
355 | Stack dump:
356 | 0. Program arguments: bin/llvm-mc -arch=rv32k -filetype=obj foo.s
357 | #0 0x00007ff0c8f93e26 llvm::sys::PrintStackTrace(llvm::raw_ostream&) /home/anqou/workspace/llvm-project/llvm/lib/Support/Unix/Signals.inc:495:11
358 | #1 0x00007ff0c8f94029 PrintStackTraceSignalHandler(void*) /home/anqou/workspace/llvm-project/llvm/lib/Support/Unix/Signals.inc:559:1
359 | #2 0x00007ff0c8f91eb3 llvm::sys::RunSignalHandlers() /home/anqou/workspace/llvm-project/llvm/lib/Support/Signals.cpp:68:5
360 | #3 0x00007ff0c8f947c4 SignalHandler(int) /home/anqou/workspace/llvm-project/llvm/lib/Support/Unix/Signals.inc:0:3
361 | #4 0x00007ff0c8af73c0 __restore_rt (/usr/lib/libpthread.so.0+0x123c0)
362 | #5 0x00007ff0c8628d7f __GI_raise (/usr/lib/libc.so.6+0x37d7f)
363 | #6 0x00007ff0c8613672 __GI_abort (/usr/lib/libc.so.6+0x22672)
364 | #7 0x00007ff0c8613548 _nl_load_domain.cold.0 (/usr/lib/libc.so.6+0x22548)
365 | #8 0x00007ff0c8621396 (/usr/lib/libc.so.6+0x30396)
366 | #9 0x00005588f8384783 main /home/anqou/workspace/llvm-project/llvm/tools/llvm-mc/llvm-mc.cpp:357:3
367 | #10 0x00007ff0c8615223 __libc_start_main (/usr/lib/libc.so.6+0x24223)
368 | #11 0x00005588f838402e _start (bin/llvm-mc+0x2402e)
369 | zsh: abort (core dumped) bin/llvm-mc -arch=rv32k -filetype=obj foo.s
370 | ....
371 | この場合 `AsmInfo` が次に実装すべき場所だと分かる。
372 |
373 | 具体的な編集方法はパッチ<>を参考にする。
374 |
375 | `RV32KAsmBackend.cpp` の作成時に多くのコンパイルエラーが出た。どうやらこのあたりの仕様変更があったようだ。
376 | 具体的には下記のとおりである。
377 |
378 | * `applyFixup` の引数変更。
379 | * `mayNeedRelaxation` の引数変更。
380 | * `writeNopData` の引数変更。
381 | * `MCAsmBackend::MCAsmBackend` の引数変更。
382 | * `createObjectWriter` の `createObjectTargetWriter` への名称・引数・戻り値変更。それに伴う `createRV32KELFObjectWriter` の引数・戻り値変更。
383 |
384 | 以上を実装して動かすとSEGVで落ちる。デバッガで追いかけると、どうやら `MCSubtargetInfo` の生成関数である `createRV32KMCSubtargetInfo`
385 | を実装しなければならないようだ。RISC Vの最新のソースコードを参考に実装する。
386 | 基本的にはTableGenが生成する `createRV32KMCSubtargetInfoImpl` をそのまま使えば良いが、これを使用するためには
387 | `CMakeLists.txt` に `tablegen(LLVM RISCVGenSubtargetInfo.inc -gen-subtarget)` を追加する必要があるため注意が必要だ。
388 | ちなみにRISC Vのパッチ群では、これらは<>で実装されている。なぜここで実装しなければならなくなったかは、よくわからない。
389 |
390 | 諸々実装すると、次のように出力される:
391 | ....
392 | $ bin/llvm-mc -arch=rv32k -filetype=obj foo.s
393 | bin/llvm-mc: error: this target does not support assembly parsing.
394 | ....
395 |
396 | なお `createRV32KMCCodeEmitter` を実装しなくてもこの表示が出るが、それでいいのかはよくわからない。
397 | とりあえずパッチ<>にある分は全て実装する。
398 |
399 | `createRV32KMCCodeEmitter` を実装する過程で、TableGenがどのように `InstrFormats` でのフィルード名と
400 | `InstrInfo` での `ins` と `outs` の名前を対応付けるのか調べた。例えば次のように `BEQZ` 命令をTableGenにて宣言したとする。
401 | ....
402 | class RVInstCB funct3, bits<2> op, dag outs, dag ins, string opcodestr, string argstr>
403 | : RVInst {
404 | bits<9> offset;
405 | bits<3> rs1;
406 |
407 | let Inst{15-13} = funct3;
408 | let Inst{12} = offset{8};
409 | let Inst{11-10} = offset{4-3};
410 | let Inst{9-7} = rs1;
411 | let Inst{6-5} = offset{7-6};
412 | let Inst{4-3} = offset{2-1};
413 | let Inst{2} = offset{5};
414 |
415 | let Opcode = op;
416 | }
417 | ....
418 | ....
419 | def BEQZ : RVInstCB<0b110, 0b01, (outs), (ins GPR:$rs1, simm9_lsb0:$imm),
420 | "beqz", "$rs1, $imm">;
421 | ....
422 | このときこのTableGenソースコードは期待通りに**動かない**(多分:TODO)。
423 | なぜなら `class RVInstCB` で指定した `offset` と `rs1` が、 `def` で指定した `$rs1` と `$imm` に対応しないからである。
424 | TableGenの実装footnote:[`utils/TableGen/CodeEmitterGen.cpp` などを参照。]や
425 | ドキュメント<>によると、これらを対応させる方法には2種類ある:
426 |
427 | . 両者で同じ名前を採用する。
428 | . 両者で宣言順序を揃えるfootnote:[関係するすべての `class` での宣言が対象になるようだが、それらがどの順に対応するのかは判然としない。]。
429 |
430 | 上の例では `RVInstCB` では `offset` が先に宣言されているにも関わらず、 `BEQZ` では `$rs1` を先に使用した。
431 | この結果 `$rs1` には `offset` と `rs1` の両方が結びつくことになるfootnote:[名前での対応により `rs1` が結びつき、順序による対応で `offset` が結びつく。]。
432 | なおこのような重複が発生してもTableGenは特に警告等を表示しないようだ。よくわからない
433 | footnote:[アセンブルではエラーが出ないが、ディスアセンブルで「オペランドが無い」というエラーが(添字チェックに引っかかるという形で)出る場合がある。]。
434 |
435 | `RV32KMCCodeEmitter::encodeInstruction` 内で使用している `support::endian::Writer(OS).write(Bits);` はそのままでは通らない。
436 | RISC Vを参考に `support::endian::write(OS, Bits, support::little);` としておく。
437 |
438 | === `RV32KAsmParser` を追加する
439 |
440 | アセンブリをパーズするための `RV32KAsmParser` を追加する。これによってアセンブリをオブジェクトファイルに直すことができるようになる。
441 | 新しく `AsmParser` ディレクトリを作成し、その中に `RV32KAsmParser.cpp` 及びそれに付随するファイルを作成する。
442 | 例によって、パッチ<>を参考に実装をすすめる。
443 |
444 | `RV32KAsmParser.cpp` に記述するコード量がそれなりにあるため少々面食らうが、
445 | 要するに `RV32KAsmParser` と `RV32KOperand` の2つのクラスを作成し、実装を行っている。
446 | パーズの本体は `RV32KAsmParser::ParseInstruction` である。これを起点に考えれば、それほど複雑な操作はしていない。
447 |
448 | 即値に関して `SImm6` を `SImm12` に直す必要がある。これらはTableGenが生成するコードから呼ばれるようだ。
449 |
450 | ところでRV32Kの命令には `add` などレジスタを5bitで指定する命令と、
451 | `sub` などレジスタを3bitで指定する命令の2種類がある。エンコードに際して、これらの区別のための特別な処理は必要ない。
452 | というのも、3bitでレジスタを指定する場合その添字の下位3bit以外が無視されるため、
453 | 結果的に正しいコードが出力されるfootnote:[LLVMのRISC V実装でもこの方式が採用されている。]。
454 | 例えば `x8` を指定すると、これに `1000` という添字が振られ、4bit目を無視することで `000` となるため、
455 | 3bitでのレジスタ指定方法として正しいものになる。
456 |
457 | そうこうしていると簡易的なアセンブラが完成する:
458 | ....
459 | $ cat foo.s
460 | li x9, 3
461 | mv x11, x1
462 | sub x9, x10
463 | add x8, x1
464 | nop
465 |
466 | $ bin/llvm-mc -arch=rv32k -filetype=obj foo.s | od -tx1z -Ax -v
467 | 000000 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 >.ELF............<
468 | 000010 01 00 f5 00 01 00 00 00 00 00 00 00 00 00 00 00 >................<
469 | 000020 68 00 00 00 00 00 00 00 34 00 00 00 00 00 28 00 >h.......4.....(.<
470 | 000030 04 00 01 00 8d 44 86 85 89 8c 06 94 01 00 00 00 >.....D..........<
471 | 000040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 >................<
472 | 000050 00 2e 74 65 78 74 00 2e 73 74 72 74 61 62 00 2e >..text..strtab..<
473 | 000060 73 79 6d 74 61 62 00 00 00 00 00 00 00 00 00 00 >symtab..........<
474 | 000070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 >................<
475 | 000080 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 >................<
476 | 000090 07 00 00 00 03 00 00 00 00 00 00 00 00 00 00 00 >................<
477 | 0000a0 50 00 00 00 17 00 00 00 00 00 00 00 00 00 00 00 >P...............<
478 | 0000b0 01 00 00 00 00 00 00 00 01 00 00 00 01 00 00 00 >................<
479 | 0000c0 06 00 00 00 00 00 00 00 34 00 00 00 0a 00 00 00 >........4.......<
480 | 0000d0 00 00 00 00 00 00 00 00 04 00 00 00 00 00 00 00 >................<
481 | 0000e0 0f 00 00 00 02 00 00 00 00 00 00 00 00 00 00 00 >................<
482 | 0000f0 40 00 00 00 10 00 00 00 01 00 00 00 01 00 00 00 >@...............<
483 | 000100 04 00 00 00 10 00 00 00 >........<
484 | 000108
485 | ....
486 | 0x34から0x3dにある `8d 44 86 85 89 8c 06 94 01 00` が出力であり、
487 | 正しく生成されていることが分かるfootnote:[白状すると、確認していない。次のステップでテストを書くため、そこで確認する。]。
488 | We made it!
489 |
490 | // ///// 以下は使用レジスタがx3-x10だと思っていたときの記述。ついでにコードも末尾に付けた。
491 | //そのため、3bitに収まらないレジスタを指定することはできないfootnote:[無理に指定すると4bit目が無視される。]。
492 | //これを仕様に従って訂正するfootnote:[なおRV32Cの場合、Integer Register Numberとして `x8` から `x15` が指定されている。これらは下位3bitをとればRVC Register Numberとして合法であるため、訂正の必要がない。]。
493 | //すなわち3bitでレジスタを指定する命令(現状では `SUB` のみ)では `x3` を0として順に添え字をふる。
494 | //
495 | //これを行うためには、アセンブリをコードに変換する際に、そのレジスタ番号を補正すればよい。
496 | //このようなレジスタオペランドエンコードのフックを行う関数を指定する場所として `RegisterOperand` の `EncoderMethod` がある。
497 | //そこで `X3` から `X10` を `GPRC` という `RegisterClass` とした上で、これを `RegisterOperand` で包み `ShiftedGPRC` とするfootnote:[なお、これを使用した具体例を見つけられていないため、この使い方が正しいかどうかはよくわからない(TODO)。]。
498 | //これの `EncoderMethod` として `RV32KEncodeShiftedGPRCRegisterOperand` という関数を指定する。
499 | //これは `RV32KMCCodeEmitter` クラスのメンバ関数として定義する。これによって任意の処理をフックすることができる。
500 | //ちなみにこれは最近入った新しい機能である。https://reviews.llvm.org/rL303044
501 | //
502 | //ビルドすると、次のような出力結果が得られる:
503 | //....
504 | //$ cat foo.s
505 | //li x3, 3
506 | //mv x3, x4
507 | //sub x9, x10
508 | //add x3, x4
509 | //nop
510 | //
511 | //$ bin/llvm-mc -arch=rv32k -filetype=obj foo.s | od -tx1z -Ax -v
512 | //000000 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 >.ELF............<
513 | //000010 01 00 f5 00 01 00 00 00 00 00 00 00 00 00 00 00 >................<
514 | //000020 68 00 00 00 00 00 00 00 34 00 00 00 00 00 28 00 >h.......4.....(.<
515 | //000030 04 00 01 00 8d 41 92 81 1d 8f 92 91 01 00 00 00 >.....A..........<
516 | //000040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 >................<
517 | //000050 00 2e 74 65 78 74 00 2e 73 74 72 74 61 62 00 2e >..text..strtab..<
518 | //000060 73 79 6d 74 61 62 00 00 00 00 00 00 00 00 00 00 >symtab..........<
519 | //000070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 >................<
520 | //000080 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 >................<
521 | //000090 07 00 00 00 03 00 00 00 00 00 00 00 00 00 00 00 >................<
522 | //0000a0 50 00 00 00 17 00 00 00 00 00 00 00 00 00 00 00 >P...............<
523 | //0000b0 01 00 00 00 00 00 00 00 01 00 00 00 01 00 00 00 >................<
524 | //0000c0 06 00 00 00 00 00 00 00 34 00 00 00 0a 00 00 00 >........4.......<
525 | //0000d0 00 00 00 00 00 00 00 00 04 00 00 00 00 00 00 00 >................<
526 | //0000e0 0f 00 00 00 02 00 00 00 00 00 00 00 00 00 00 00 >................<
527 | //0000f0 40 00 00 00 10 00 00 00 01 00 00 00 01 00 00 00 >@...............<
528 | //000100 04 00 00 00 10 00 00 00 >........<
529 | //000108
530 | //....
531 | //0x34から0x3cまでの `8d 41 92 81 1d 8f 92 91 01` が具体的なコードである。
532 | // //////
533 | //
534 | //
535 | //def GPRC : RegisterClass<"RV32K", [i32], 32, (add
536 | // X3, X4, X5, X6, X7, X8, X9, X10
537 | // )>;
538 | //
539 | //def ShiftedGPRC : RegisterOperand {
540 | // let EncoderMethod = "RV32KEncodeShiftedGPRCRegisterOperand";
541 | // //let DecoderMethod = "RV32KDecodeShiftedGPRCRegisterOperand";
542 | //}
543 | //
544 | //
545 | //
546 | //uint64_t
547 | //RV32KEncodeShiftedGPRCRegisterOperand(const MCInst &MI, unsigned no,
548 | // SmallVectorImpl &Fixups,
549 | // const MCSubtargetInfo &STI) const;
550 | //
551 | //uint64_t RV32KMCCodeEmitter::RV32KEncodeShiftedGPRCRegisterOperand(
552 | // const MCInst &MI, unsigned no, SmallVectorImpl &Fixups,
553 | // const MCSubtargetInfo &STI) const {
554 | // const MCOperand &MO = MI.getOperand(no);
555 | // if (MO.isReg()) {
556 | // uint64_t op = Ctx.getRegisterInfo()->getEncodingValue(MO.getReg());
557 | // assert(3 <= op && op <= 10 && "op should belong to GPRC.");
558 | // return op - 3;
559 | // }
560 | //
561 | // llvm_unreachable("Unhandled expression!");
562 | // return 0;
563 | //}
564 |
565 | == 簡易アセンブラのテストを書く
566 |
567 | 適当な入力に対してアセンブラが正しい出力を行うかを確認しつつ
568 | 新たなバグの発生を抑止するために、LLVMのフレームワークを用いてテストを書く。
569 |
570 | === `RV32KInstPrinter` を実装する
571 |
572 | テストを書くために、まずRV32Kのためのinstruction printerを作成する。
573 | これは内部表現である `MCInst` から文字列表現であるアセンブリに変換するための機構である。
574 | この後で作成するテストでは、アセンブリを `MCInst` に変換した上で、
575 | それをアセンブリに逆変換したものがもとのアセンブリと同じであるか否かでテストを行う。
576 |
577 | まず例によって `InstPrinter` ディレクトリを作成した後、適切に `CMakeLists.txt` や
578 | `LLVMBuild.txt` を作成・編集する。
579 | また `RV32KInstPrinter` を作成するための関数を `LLVMInitializeRV32KTargetMC`
580 | にて登録する必要がある。
581 |
582 | その後 `InstPrinter/RV32KInstPrinter.{cpp,h}` を作成する。
583 | いつものようにこれはパッチ<>を参考にして行う。
584 |
585 | `RV32KInstPrinter::printRegName` の実装で用いる `getRegisterName` の第二引数に
586 | 何も渡さなければAltNameを出力に使用する。第二引数に `RV32K::NoRegAltName` を渡すことで、
587 | レジスタを `X0`, `X1`, ... と表示することができる。
588 |
589 | 上記をビルドした後、
590 | 実際にアセンブリを `MCInst` に変換し、さらにそれをアセンブリに戻すには、
591 | `llvm-mc` を用いる:
592 | ....
593 | $ bin/llvm-mc -arch=rv32k -show-encoding foo.s
594 | .text
595 | li x9, 3 # encoding: [0x8d,0x44]
596 | mv x11, x1 # encoding: [0x86,0x85]
597 | sub x9, x10 # encoding: [0x89,0x8c]
598 | add x8, x1 # encoding: [0x06,0x94]
599 | nop # encoding: [0x01,0x00]
600 | ....
601 | `-show-encoding` を指定することよって当該アセンブリがどのような機械語に
602 | 翻訳されるか確認することができる。テストではこの機械語の正誤も確認する。
603 |
604 | === テストを書く
605 |
606 | 簡易アセンブラが正しく動作していることを保証するためにテストを書く。
607 | ここでは「適当な入力を与えたときに正しく解釈し正しい機械語を生成するか」を確認する。
608 |
609 | 前節と同様にパッチ<>を参考に記述する。
610 | まず `test/MC/RISCV` ディレクトリを作成する。
611 | その中に `rv32k-valid.s` と `rv32k-invalid.s` を作成し、
612 | 前者で正しいアセンブリが適切に処理されるか、
613 | 後者で誤ったアセンブリに正しくエラーを出力するかを確認する。
614 |
615 | 記述後 `llvm-lit` を用いてテストを行う:
616 | ....
617 | $ bin/llvm-lit -as --filter 'RV32K' test
618 | PASS: LLVM :: MC/RV32K/rv32k-valid.s (1 of 2)
619 | Script:
620 | --
621 | : 'RUN: at line 1'; /home/anqou/workspace/llvm-project/build/bin/llvm-mc /data/anqou/workspace/llvm-project/llvm/test/MC/RV32K/rv32k-valid.s -triple=rv32k -show-encoding | /home/anqou/workspace/llvm-project/build/bin/FileCheck -check-prefixes=CHECK,CHECK-INST /data/anqou/workspace/llvm-project/llvm/test/MC/RV32K/rv32k-valid.s
622 | --
623 | Exit Code: 0
624 |
625 |
626 | ********************
627 | PASS: LLVM :: MC/RV32K/rv32k-invalid.s (2 of 2)
628 | Script:
629 | --
630 | : 'RUN: at line 1'; not /home/anqou/workspace/llvm-project/build/bin/llvm-mc -triple rv32k < /data/anqou/workspace/llvm-project/llvm/test/MC/RV32K/rv32k-invalid.s 2>&1 | /home/anqou/workspace/llvm-project/build/bin/FileCheck /data/anqou/workspace/llvm-project/llvm/test/MC/RV32K/rv32k-invalid.s
631 | --
632 | Exit Code: 0
633 |
634 |
635 | ********************
636 | Testing Time: 0.11s
637 | Expected Passes : 2
638 | ....
639 | テストに通っていることが分かる。YATTA!
640 |
641 | == アセンブラに残りの命令を追加する
642 |
643 | 簡易アセンブラに残りの命令を実装する。「簡易」を取り払うということである。
644 | 参考にするRISC Vのパッチは<>である。
645 |
646 | === `lw` 命令を追加する
647 |
648 | `lw` 命令は7bit符号なし即値をとる。この即値の下位2ビットは0であることが要求される。
649 | この即値はアセンブリ中でもLLVM内部でも7bitのまま保持される。
650 |
651 | `ParserMatchClass` は `AsmParser` がどのようにそのオペランドを読めばよいか指定する。
652 | TableGenで直接指定できない性質は `EncoderMethod` などを用いてフックし、C\++側から指定する<>。
653 |
654 | `uimm8_lsb00` の `let EncoderMethod = "getImmOpValue";` は一見不思議に見えるが正常で、
655 | ここでえられた即値は `lw` の `imm` になり、そちらで整形される。
656 | [[RVInst16CJ-offset]]だが `simm12_lsb0` は `getImmOpValueAsr` が指定されるので `offset` は12bitではなく11bitになっている。
657 |
658 | `uimm8_lsb00` をつくると `RV32KOperand::isUImm7Lsb00` が無いと言われるので追加する必要がある。
659 | この `UImm7Lsb00` という名前は `ImmAsmOperand` の `Name` に従って作られているようだ。
660 |
661 | `generateImmOutOfRangeError` の実装に使われているおもしろデータ構造 `Twine` は
662 | 文字列の連結を二分木で持つことで高速に行うことができるらしい<>。
663 |
664 | メモリ表記のアセンブリをパーズするためには `RV32KAsmParser` を変更する必要がある。
665 | この処理のエントリポイントは `parseOperand` で、ここから `parseMemOpBaseReg` を呼び出す。
666 |
667 | `li x11, x12, x13` というアセンブリを流し込むと `invalid operand for instruction` を期待
668 | しているにもかかわらず `immediate must be an integer in the range [-32, 31]` と出てしまう。
669 | `LW` を追加する前はそのメッセージが出ていたのでいろいろ調査したが原因不明。
670 | `git stash` してもとのコードをテストしてみると、実際には
671 | ....
672 | $ bin/llvm-mc -arch=rv32k -show-encoding test.s
673 | .text
674 | test.s:1:14: error: invalid operand for instruction
675 | li x11, x12, x13
676 | ^
677 | ....
678 | となっている。すなわちエラーが発生している位置がずれている。もとからおかしかったのだ。
679 | おそらくこれは将来的に解決されるだろうという判断のもと保留(TODO)。
680 |
681 | テストを忘れずに追加してコミット。
682 |
683 | ところで `getImmOpValue` はオペランドにある即値をそのまま返す関数であり、
684 | 即値の `EncoderMethod` にフックとして使用する。
685 | しかしこれは `simm6` の実装時には不要であり、
686 | 実際 `let EncoderMethod = "getImmOpValue";` をコメントアウトしてもテストには通る。
687 | <>では `getImmOpValueAsr1` のみが実装されている。
688 | RISC Vの最終的なコードで `getImmOpValue` は、即値として使えるような複雑な命令を解釈している。
689 |
690 | === `sw` 命令を追加
691 |
692 | `lw` 命令を追加する際に諸々を整えたので、やるだけ。
693 |
694 | === `lwsp` 命令を追加
695 |
696 | tdファイルに `lwsp` を追加してビルドしようとすると次のようなエラーが出た。
697 | ....
698 | error: Invalid bit range for value
699 | let Inst{3-2} = imm{7-6};
700 | ^
701 | ....
702 | どうやら `imm` が6bit即値として扱われているようだ。原因を探すと `RVInstCI` 内で `imm` を
703 | 6bitと定義していたことが原因だったようだ。RISC V側での実装に合わせ、これを10bitに変更する。
704 | 同様に `RVInstCI` である `li` の定義も変更し、即値については各々の `def` で適切に処理するようにした。
705 |
706 | `sp` を用いたテストを書くと、出力時にこれが `x2` に変換されてしまうためにエラーになってしまう。
707 | `RegisterInfo.td` の `RegAltNameIndices` の定義を変更しAltNameで出力をまかなえるようにした上で、
708 | `RV32KInstPrinter::printRegName` の実装を改変した。
709 |
710 | === `swsp` 命令を追加
711 |
712 | tdファイルに `swsp` を追加するだけ。
713 |
714 | === `j` 命令を追加
715 |
716 | RISC Vの仕様上 `c.j` 命令は12bitの即値フィールドを持つ。
717 | しかしLLVMのRISC Vバックエンド実装では `RVInst16CJ` は11bitの即値を持つ。
718 | これは末尾1bitが必ず0であるために( `lsb0` )この1bitを捨てることができるためである。
719 | しかしこの実装は `lw` などがとる即値をそのままのビット数でデータを持つことと一貫性がないように
720 | 見受けられる。そこでRV32K実装では `j` 命令などもすべてそのままのビット数でデータを持つことにする。
721 | したがって `simm12_lsb0` などに指定する `EncoderMethod` は `getImmOpValueAsr1` ではなく
722 | `simm6_lsb00` と同様の `getImmOpValue` となる。
723 |
724 | === `jal` ・ `jr` ・ `jalr` ・ `beqz` ・ `benz` 命令を追加
725 |
726 | やるだけ。
727 |
728 | === 属性footnote:[これを属性と呼んでいいかどうかよくわからないが、わかりやすいし呼びやすいのでとりあえずこれで。内部的にはrecordと書かれることが多いようだ。]を指定する
729 |
730 | `isBranch` やら `isTerminator` やら `hasSideEffects` やらを命令毎にちゃんと設定する。
731 | これはアセンブラでは意味をなさないが、コンパイラ部分を作り始めると重要になるのだろう。多分(TODO: ほんまか?)。
732 |
733 | ところでどのような属性フィールドがあるのかはリファレンスを読んでも判然としない。
734 | TableGenのソースコードを読みに行くと `llvm/utils/TableGen/CodeGenInstruction.h` にて
735 | 属性のための大量のフラグが定義されているが、各々がどのような目的で使用されるかは書いていない。
736 | `llvm/include/llvm/CodeGen.h` に `mayLoad` や `isTerminator` ・ `isBarrier` などの一部分のフラグについて説明がある一方、
737 | `hasSideEffects` などのフラグについては説明がない。
738 |
739 | `hasSideEffects` は `llvm/lib/Target/RISCV/RISCVInstrInfoC.td` の中で `C_UNIMP` と `C_EBREAK` でのみ `1` に設定されている。
740 | 特殊な事象が起こらない限り `0` にしておいて良さそうだ。
741 |
742 | `class` や `def` の中に `let field = value;` と書くのと、外に `let field = value in ...` ないし `let field = value in { ... }`
743 | と書くのは同じ効果を持つが、外に書くと複数の `class` ・ `def` にまとめて効果を持つという点においてのみ異なる<>。
744 |
745 | `class` の段階で `hasSideEffects` などについて列挙するのは、あまりよいスタイルと思えない。
746 | というのもRV32CなどのISAでは、エンコーディングフォーマットが同じでも全く違う意味をもつ命令を
747 | 意味することが往々にしてあるからだ。また我々の開発のように後々ISAを変更することが半ば確定している状況において、
748 | `class` と複数の `def` におけるフラグの整合を保ちつつ変更するのは骨が折れる。
749 | それなら `class` はビットパターンのみを扱うものとし、
750 | `def` にその意味的な部分(フラグの上げ下げ)を書くほうが、後々の拡張性を考えるとよいと思うfootnote:[ただしこれは命令数が少ない場合に限るのかもしれない。]。
751 |
752 | === レジスタ指定の `GPR` と `GPRC` を使い分ける
753 |
754 | `RV32KRegisterInfo.td` において `GPRC` は `X8` から `X15` までの汎用レジスタを指し、
755 | `GPR` は `X1` と `X2` を含むすべてのレジスタを指すように定義されている。
756 | RV32Kの命令のうち3bit幅でレジスタ番号を指定する命令には `GPRC` を用い、
757 | 5bit幅でレジスタ番号を指定する命令には `GPR` を用いるようにすることで、
758 | LLVMに適切なレジスタを教えることができる。
759 |
760 | この別はディスアセンブラを開発する段になって重要になる。というのも、バイナリ中にレジスタ番号として `1` と出てきた場合、
761 | このオペランドが `GPR` か `GPRC` かによって指すレジスタが `x1` または `x9` と異なるからである。
762 |
763 | == ディスアセンブラを実装する
764 |
765 | アセンブラが一通り実装できたので、今度はディスアセンブラを実装する。
766 | これによって `llvm-objdump -d` を使用することができるようになる。
767 | このディスアセンブラのテストには、すでに記述した `rv32k-valid.s` などのテストを使用できる。
768 | すなわちアセンブラによってアセンブリを機械語に直し、さらにディスアセンブラによって機械語をアセンブリに直した結果が、
769 | 元のアセンブリと一致するかどうかをみればよいfootnote:[<>にはfull round trip testingと表現されている。]。
770 |
771 | 参考にするRISC Vのパッチは<>である。
772 |
773 | === `RV32KDisassembler` を追加する
774 |
775 | `*_lsb0` や `*_lsb00` の取り回しがよくわからなくなったので整理footnote:[整理というより半分推測だが。]する。
776 | TableGenで指定するビットによって、即値のどの部分をどのように命令コード中に配置するかを決定することができる。
777 | これによって `RV32KCodeEmitter::getImmOpValueAsr1` などの `EncoderMethod` に頼る必要がなくなる。
778 | 同様にディスアセンブラについても `RV32KDisassembler::decodeSImmOperandAndLsl1` などの `DecoderMethod` に
779 | 頼る必要がなくなる。言い換えれば、これらのフック関数が受け取る即値は、TableGenで指定したビット単位の
780 | エンコード・デコードをすでに受けた値になるfootnote:[バイナリに直接触れるのはTableGenが出力するコードなので、当然といえば当然だが。]。
781 | `RV32KAsmParser::isUImm12` などが呼び出す `getConstantImm` が返す即値も同様である。
782 |
783 | ナイーブに実装すると `lwsp` や `swsp` が入ったバイナリをディスアセンブルしようとしたときに
784 | エラーがでる。これは例えば次のようにして確認することができる。
785 | ....
786 | $ cat test.s
787 | lwsp x11, 0(sp)
788 |
789 | $ bin/llvm-mc -filetype=obj -triple=rv32k < test.s | bin/llvm-objdump -d -
790 | ....
791 | 原因は `lwsp` や `swsp` がアセンブリ上はspというオペランドをとるにも関わらず、
792 | バイナリにはその情報が埋め込まれないためである。このためディスアセンブル時に
793 | オペランドが一つ足りない状態になり、配列の添字チェックに引っかかってしまう。
794 |
795 | これを修正するためには `lwsp` や `swsp` に含まれる即値のDecoderが呼ばれたときをフックし、
796 | `sp` のオペランドが必要ならばこれを補えばよいfootnote:[この実装手法はRISC Vのそれによる。かなりad-hocだと感じるが、他の方法が分からないのでとりあえず真似る。]。
797 | この関数を `addImplySP` という名前で実装する。ここで即値をオペランドに追加するために呼ぶ
798 | `Inst.addOperand` と `addImplySP` の呼び出しの順序に注意が必要である。
799 | すなわち `LWSP` を `RV32KInstrInfo.td` で定義したときのオペランドの順序で呼ばなければ
800 | `lwsp x11, sp(0)` のようなおかしなアセンブリが生成されてしまう。
801 |
802 | ちなみにエンコード方式にコンフリクトがある場合はビルド時に教えてくれる。
803 | ....
804 | Decoding Conflict:
805 | 111...........01
806 | 111.............
807 | ................
808 | BNEZ 111___________01
809 | BNEZhoge 111___________01
810 | ....
811 |
812 | これを防ぐためには、もちろん異なるエンコード方式を指定すればよいのだが、
813 | 他にディスアセンブル時に命令を無効化する方法としてTableGenファイルで
814 | `isPseudo = 1` を指定して疑似命令にしたり
815 | `isCodeGen = 1` を指定してコード生成時にのみ効力を持つ
816 | 命令にすることなどができる。
817 |
818 | == relocationとfixupに対応する
819 |
820 | ワンパスでは決められない値についてあとから補うための機構であるfixupと、
821 | コンパイル時には決定できない値に対してリンカにその処理を任せるためのrelocationについて
822 | 対応する。参考にするパッチは<>。
823 |
824 | 必要な作業は大きく分けて次の通り。
825 | * Fixupの種類とその内容を定義する。
826 | * Fixupを適用する関数を定義する。
827 | * アセンブラがFixupを生成するように改変する。
828 | * Fixupが解決されないまま最後まで残る場合は、これをrelocationに変換する。
829 |
830 | === Fixupを定義する
831 |
832 | `RV32KFixupKinds.h` を新規に作成し `enum Fixups` を定義する。
833 | RV32Kv1では `beqz` ・ `bnez` がとる8bitの即値と `j` ・ `jal` がとる11bitの即値のために
834 | 必要なFixupを定義する。
835 | なお `enum` の最初のフィールドには `FirstTargetFixupKind` を設定し、
836 | 最後のフィールドは `NumTargetFixupKinds` として、定義した `enum` のフィールドの個数を設定する。
837 |
838 | Fixupの種類からその情報を返すのは `RV32KAsmBackend::getFixupKindInfo` が行う。
839 | ここでの `offset` の値は、Fixupのために得られた即値を何ビット左シフトするかを意味し、
840 | `bits` は TODO を意味している。そこで、即値のフィールドが命令中で2つに分かれている命令のためのFixup
841 | である `fixup_rv32k_branch` では `offset` と `bits` を各々 `0` と `16` にしておく
842 | footnote:[これはRISC Vの実装を真似ている。]。
843 |
844 | === Fixupを適用する関数を定義する
845 |
846 | 要するに `RV32KAsmBackend::applyFixup` の実装である。補助関数として `adjustFixupValue` も実装する
847 | footnote:[ところでRISC Vの `fixup_riscv_rvc_{jump,branch}` の実装では即値の幅のチェックを行っていない。
848 | なぜかはよくわからない。あと `fixup_riscv_jal` のコメントが間違っている気がする。]
849 |
850 | === アセンブラにFixupを生成させる
851 |
852 | `AsmParser` と `CodeEmitter` を書き換え、必要なときにアセンブラにFixupを生成させるようにする。
853 |
854 | さてRISC Vでは `%hi` や `%lo` などが使えるために、これらを評価するための機構として `RISCVMCExpr` を導入している。
855 | 具体的には `RISCVAsmParser::parseImmediate` でトークンに `AsmToken::Percent` が現れた場合に
856 | `RISCVAsmParser::parseOperandWithModifier` を呼び出し、この中でこれらをパーズして `RISCVMCExpr` を生成している。
857 |
858 | しかしRV32Kではこれらの `%` から始まる特殊な即値footnote:[内部的にはoperand with modifiersと呼ばれているようだ。]
859 | に対応せずfootnote:[とりあえずはね。]、あくまで分岐命令の即値におけるラベルのFixupのみが行えれば十分である。
860 | footnote:[なおRISC Vの `classifySymbolRef` を見るとシンボルの引き算ができるように見えるが、実際に試してみると `getImmOpValue` で
861 | `Unhandled expression` が出て落ちてしまった。よくわからない。即値としては認識される( `isSImm12` などが `true` を返す)が、
862 | Fixupとして受領されない( `getImmOpValue` でエラーになる)気がする。TODO]。
863 |
864 | そこでまず `isSImm9Lsb0` などにシンボルが来た場合には `true` を返すようにする。
865 | これはすなわち、即値を指定するべきところにラベル名が来た場合は `true` とするということである
866 | footnote:[ここで `isImm` を呼び出す必要があることに注意が必要である。「即値」と聞くと整数値のみを受け取ると誤解しがちだが、
867 | 実際には `RV32KOperand` の `ImmOp` は `MCExpr` をその値として持っている。したがって、シンボルなどを含めた
868 | 即値を求めるための演算そのものが対象になっている。すなわち `isImm` が `true` であることを確認することで、
869 | ラベル名が来るべきところにレジスタ名などが来ないことを担保しているのである。
870 | またより形式的には、これは `RV32KOperand` が持つ共用体の中身が `ImmOp` となっていることを保証している。]。
871 | その次に `getImmOpValue` を変更し、即値を書くべき場所にシンボルが来ている場合にはFixupを生成するようにする。
872 | このとき `fixup_rv32k_branch` と `fixup_rv32k_jump` のいずれを発行するかは、
873 | オペランドの種類で `switch-case` して判断している。
874 | これは良くないコーディングスタイルであり、実際<>ではこれを避けるために
875 | 種々のInstFormatに手を加えるとはっきり書いてあるのだが、ここでは作業の単純さを重視して
876 | ハードコーディングすることにする。
877 |
878 | それから `RV32KAsmParser::parseImmediate` を変更し `AsmToken::Identifier` が来たときには
879 | `MCSymbolRefExpr` を生成するようにしておく。この変更は<>に含まれていたものだが、
880 | 取り込み忘れていた。
881 |
882 | === Fixupからrelocationへの変換部を実装する
883 |
884 | `RV32KELFObjectWriter::getRelocType` を実装すればよいのだが、実のところRV32Kにおけるrelocationはほとんど想定おらず、
885 | 仕様も決まっていない。そこでここでは実装を省略する。
886 |
887 | === Fixupのためのテストを書く
888 |
889 | `.space` を使って適当に間隔を開けながら命令を並べ、正しく動作しているかを確かめる。
890 |
891 | これでアセンブラ部分は終了。We made it!
892 |
893 | == コンパイラのスケルトンを作成する
894 |
895 | 空の関数とALUオペレーションをサポートできるような最小のバックエンドを作成する。
896 |
897 | * `RV32KAsmPrinter` と `RV32KMCInstLower`
898 | ** `MachineInstr` を `MCInst` に変換する。
899 | ** `llvm::LowerRV32KMachineInstrToMCInst` で `MCInst` のオペコード・オペランドを設定する
900 | * `RV32KInstrInfo`
901 | ** TableGenによる自動生成。命令を表す `enum` の定義。
902 | * `RV32KRegisterInfo`
903 | ** ほとんどTableGenによる自動生成。
904 | ** `getReservedRegs` で変数に割り付ける**べきでない**レジスタを指定する。
905 | ** `getFrameRegister` はframe indicesに用いられるレジスタを指定する(?:TODO)
906 | * `RV32KISelDAGToDAG`
907 | ** `SelectionDAG` ノードに対して適切なRV32K命令(多分 `MachineInstr` )を見繕う。
908 | ** `RV32KDAGToDAGISel::Select` がエントリポイント
909 | *** ただしこれはTableGenが生成した `SelectCode` 関数を呼んでいるだけ。
910 | * `RV32KISelLowering`
911 | ** LLVM IRを `SelectionDAG` に変換する。
912 | ** この処理はほとんどターゲット非依存である。
913 | *** フックを設定して挙動を変化させる。
914 | * `RV32KInstrInfo.td`
915 | ** ここに「 `SelectionDAG` をどのようにRV32Kの命令セット(多分 `MachineInstr` )に変換するか」が書かれる。
916 | ** `def : Pat<(hoge), (piyo)>;` という表記で「 `hoge` に `SelectionDAG` がパターンマッチしたとき `piyo` に変換する」を意味する。
917 | *** ここで `piyo` に記入するのは `ins` のオペランドのみである。 `outs` は「戻り値」となる。(TODO;詳細は?)
918 | ** どのような `SelectionDAG` にマッチできるかは `include/llvm/Target/TargetSelectionDAG.td` に情報がある。
919 | ** マッチは命令( `add` のような)のみならず「オペランドが即値であるか」などにも適用できる。ただしこの場合その即値の定義が `ImmLeaf` を継承していることが必要。
920 | * 関数呼び出し規約やCallee-savedなレジスタについてもここで指定する。
921 | * `update_llc_test_checks.py` を使用することで、LLVM IRで書かれた関数から生成されるアセンブリのテストを自動的に生成することができる。
922 |
923 | おおよそ次のような過程をたどるようだ<>。
924 |
925 | ....
926 | LLVM IR
927 | |
928 | | ISelLowering (関数呼び出し規約などSelectionDAGでは扱えない要素を消す)
929 | v
930 | SelectionDAG (仮想的/物理的なレジスタによる表現)
931 | |
932 | | ISelDAGToDAG
933 | v
934 | DAG (MachineInstrのリスト)(命令の順序を決める)
935 | |
936 | |
937 | v
938 | (ここでSSA-basedの最適化・レジスタ割り付け・プロローグエピローグ挿入を行う)
939 | |
940 | | MCInstLower
941 | v
942 | MCInst
943 | ....
944 |
945 | === `utils/UpdateTestChecks/asm.py` を変更する
946 |
947 | `update_llc_test_checks.py` を使用するために必要な変更らしい。RISCVのための記述を参考にしながら追記すれば良い。
948 |
949 | === `RV32KTargetLowering` を実装する
950 |
951 | とりあえずLLVM IRに近いところから実装していくことにする。 `RV32KTargetLowering` はLLVM IRを `SelectionDAG` に変換するためのクラスである。
952 | `RV32KISelLowering.{h,cpp}` で定義される。
953 |
954 | まずこのクラスのコンストラクタで、ターゲットの仕様を指定する。
955 |
956 | 次いでメンバ関数の `LowerFormalArguments` で関数呼び出し時の引数の扱いについて記述する。
957 | ここでは引数はすべてレジスタ経由で渡されることにする。
958 | その場合、使用するレジスタクラスを指定して仮想レジスタを作成し、引数に割り付けて追加する。
959 |
960 | 最後に `LowerReturn` で、関数呼び出しから戻るときの戻り値の扱いについて記述する。
961 | 戻り値を解析し、そのすべてをレジスタに詰め込み(複数個あることを前提?)、最後に `RET_FLAG` を出力している
962 | footnote:[ここでの `RET_FLAG` は `RV32KDAGToDAGISel` にて `JR X1` に変換される疑似命令である。
963 | <>では直接 `jr` 命令を参照していたが、それよりも「関数からのリターンである」
964 | という意味的情報を付加できるというメリットがありそうだ(TODO;ほんまか?)]。
965 | ここの処理は全体的によくわからない(TODO;特に `Chain`, `Flag`, `RetOps` がどのような働きをしているのか判然としない)。
966 | `Chain` を通じて `SelectionDAG` がじゅじゅつなぎになっている?
967 |
968 | 引数が渡ってくるレジスタを `CopyFromReg` でくるむことで、その(仮想的/物理的)レジスタがこの `SelectionDAG` の
969 | 外側で定義されたものであることを示している。
970 |
971 | なおRV32Kは下位(sub)に位置づけられるべきターゲットが存在しないが、
972 | それでもターゲットの情報を保持するために `RV32KSubTarget` を定義することが必要である。
973 | `CPUName` は `RV32K.td` や `RV32KMCTargetDesc` と合わせて `generic-rv32k` としておく。
974 | Lanaiの実装を見ると `generic` だけで統一しても良いようだ(TODO)。
975 |
976 | `setBooleanContents(ZeroOrOneBooleanContent);` はターゲットが1/0でtrue/falseを判定することを設定する。
977 |
978 | さて上記のように関数呼び出し時の処理を記述するためには、
979 | そもそも関数呼び出し規約の定義を行う必要がある。これは `RV32KCallingConv.td` にて行う。
980 | 関数呼び出し時の引数・戻り値を処理するためのレジスタを指定する。またcallee-savedなレジスタも指定する。
981 |
982 | ここで戻り値を返すためのレジスタを複数指定することができるようだ。
983 | これはおそらくLLVM IRが複数の戻り値を扱うことができることの単純な反映であろうし(TODO;ほんまか?)、
984 | また例えばRISC VのCalling conventionではa0とa1が戻り値を返すためのレジスタとして指定されているためでも
985 | あると思う。
986 |
987 | なおここで `sp` である `x2` をcallee-savedとして指定する必要はない。
988 | というのもこの処理は関数プロローグ・エピローグで行うことに(将来的に)なるからだ
989 | footnote:[<>のコミットメッセージを参考のこと。]。
990 |
991 | === `RV32KDAGToDAGISel` を実装する
992 |
993 | `SelectionDAG` をRV32Kコードに変換するためのクラスを実装する。 `RV32KISelDAGToDAG.cpp` で実装される。
994 | このクラスのエントリポイントは `Select` であるが、これ自体はTableGenが生成する関数である `SelectCode` を
995 | 呼び出すだけである。そこでこの処理の本体は `RV32KInstrInfo.td` に記述されることになる。
996 |
997 | RISCVのCompressedの実装を見ると `Pat` を継承しない方法で処理をしていた。よくわからない(TODO)。
998 |
999 | ALU操作のみにとりあえず対応するため、現状対応するのは `ADD` と `SUB` 、それから便宜上必要になる
1000 | `PseudoRET` の3つのみである。
1001 |
1002 | === `RV32KFrameLowering` を実装する
1003 |
1004 | 関数のプロローグとエピローグを出力するためのクラスを実装する。
1005 | これらではスタックフレームサイズの調整を行う。
1006 | とはいいつつもこれらはまだ実装の必要がないため、ただのプレースホルダになっている。
1007 |
1008 | スタックがアドレス負方向に伸びることを `TargetFrameLowering` のコンストラクタの第一引数に
1009 | `StackGrowDown` を渡すことで表現している。
1010 |
1011 | === `RegisterInfo` を実装する
1012 |
1013 | まず `RV32KRegisterInfo.td` を変更し、レジスタを割付優先度順に並び替える。
1014 |
1015 | 続いて `RV32KRegisterInfo` クラスを実装する。
1016 | `getReservedRegs` で通常のレジスタ割り付けでは使用しないレジスタを指定する。
1017 |
1018 | === `RV32KAsmPrinter` と `RV32KMCInstLower` を実装する
1019 |
1020 | <>と同様の実装をすればよい。
1021 | 本体の処理は `LowerRV32KMachineInstrToMCInst` である。
1022 | これは `MachineInstr` を `MCInst` に変換している。
1023 |
1024 | === その他
1025 |
1026 | ビルドに必要なファイルなどを実装する。
1027 |
1028 | `RV32KInstrInfo` に `let guessInstructionProperties = 0;` という文を追加している。
1029 | 命令の属性( `hasSideEffects` など)を推論するためのオプションのようだ(TODO)。
1030 |
1031 | `RV32KGenInstrInfo.inc` を読み込むため `RV32KInstrInfo.{h,cpp}` を追加する。
1032 |
1033 | `RV32KTarget>achine.{h,cpp}` を変更する。ここでは `RV32KSubtarget` のインスタンスを
1034 | 得るための `getSubtargetImpl` を実装すると同時に、実行パスに
1035 | `RV32KDagToDAGISel` を挿入するために `RV32KPassConfig` を実装する
1036 | footnote:[他の変換は `RV32KSubtarget` 経由で呼び出される。この変換のみが異なるようだ。]。
1037 |
1038 | === テストを行う
1039 |
1040 | `CodeGen` のためのテストを作成する。これはLLVM IRを入力し、出力されるアセンブリが正しいかどうかを判定するものである。
1041 |
1042 | テストの枠組みに頼らず、手でテストを行うためには次のようにする。
1043 | ....
1044 | $ cat test.ll
1045 | define i32 @sub(i32 %a, i32 %b) nounwind {
1046 | ; RV32K-LABEL: sub:
1047 | ; RV32K: # %bb.0:
1048 | ; RV32K-NEXT: sub x8, x9
1049 | ; RV32K-NEXT: jr x1
1050 | %1 = sub i32 %a, %b
1051 | ret i32 %1
1052 | }
1053 |
1054 | $ bin/llc -mtriple=rv32k -verify-machineinstrs < test.ll
1055 | .text
1056 | .file ""
1057 | .globl sub # -- Begin function sub
1058 | .p2align 3
1059 | .type sub,@function
1060 | sub: # @sub
1061 | # %bb.0:
1062 | sub x8, x9
1063 | jr x1
1064 | .Lfunc_end0:
1065 | .size sub, .Lfunc_end0-sub
1066 | # -- End function
1067 |
1068 | .section ".note.GNU-stack","",@progbits
1069 | ....
1070 | ついに我々はLLVM IRからコード生成を行うその第一歩を踏み出せたようだ。Here we go!
1071 | footnote:[言い換えればここからが本番ということで、ここまでは長い長い前座だったのだ。
1072 | それでも一つ終わったことは良いことだ。祝杯にコーヒーを入れよう。]
1073 |
1074 | === `SelectionDAG` とはなにか
1075 |
1076 | <>を参考にまとめる。
1077 |
1078 | * `SelectionDAG` は `SDNode` をノードとする有向非巡回グラフである。
1079 | ** `SDNode` は大抵opcodeとオペランドを持つ。
1080 | ** `include/llvm/CodeGen/ISDOpcodes.h` を見るとどのようなものがあるか分かる。
1081 | * `SelectionDAG` は2つの値を持つ:データフローとコントロールフロー
1082 | ** データフローは単に値がノードになっているだけ。
1083 | ** "`chain`" ノードによって副作用を持つ操作(load, store, call, returnなど)の順序が決まる。
1084 | *** 副作用を持つ操作はすべてchainを入力として受け取り、新たなchainを出力しなければならない。
1085 | **** 伝統的にchainの入力は0番目のオペランドになっており、出力は最後の値になっている。ただしinstruction selectionが起こった後はそうとも限らない。
1086 | * "`legal`" なDAGは、そのターゲットがサポートしている操作・型のみで構成されている。
1087 |
1088 | `SelectionDAG` を用いたinstruction selectionは `SelectionDAG` を最適化・正規化することで行われる
1089 | <>。
1090 | この過程は `-view-dag-combine1-dags` などのオプションを `llc` に与えることでグラフとして見ることができる。
1091 | ....
1092 | $ bin/llc -mtriple=rv32k -view-dag-combine1-dags < test.ll
1093 | # DOTファイルビューワがあればそれが起動する。ない場合は次のようにしてSVGに変換する。
1094 | $ dot -Tsvg /tmp/dag.sub-86df29.dot -o dot.svg
1095 | ....
1096 | image::img/simple_sub_combine1.svg[]
1097 | それっぽい。
1098 |
1099 | == 定数に対応する
1100 |
1101 | 定数をレジスタに読み込むためのパターンを `RV32KInstrInfo.td` に追加する。
1102 | すなわち `simm6` が来たら `li` を呼ぶようにすれば良い。ただしここで `li` のオペランドには、
1103 | 渡ってきた即値のみを渡せばよいことに注意が必要である。すなわちレジスタを指定する必要はない。
1104 | これは `Pat` の右側に書くDAGは `(ins)` のオペランドのみを記載すればよいからである。
1105 |
1106 | なお `simm6` を `Pat` でも使用できるように `ImmLeaf` を `simm6` の継承先に追加する必要がある。
1107 |
1108 | == メモリ操作に対応する
1109 |
1110 | ロードとストアのための `Pat` を追加する。offsetが0の場合と非ゼロの場合で別の `def` が必要なことに注意。
1111 | `lw` を `load` に対応付け `sw` を `store` に対応付ければ、最低限の実装が整う。
1112 | RISC Vではさらに `sextloadi8` を `LB` に対応させるなどしているが、
1113 | RV32Kv1ではこれらの命令が無い。 `lw` と他の命令を合わせれば実現できるかもしれないが、
1114 | その実装方法がよくわからない。 `setOperationAction` で `Custom` 指定とかすればできそうだが詳細不明。(TODO)
1115 | footnote:[その後RV32Kv2以降でHW側で実装と合意。]
1116 |
1117 | [[implement-copyPhysReg]]
1118 | なお参考にするコミット<>のコミットメッセージには `copyPhysReg` を
1119 | 実装する必要があると書いてあるが、実際に実装してみるとこれは必要ない。
1120 | この関数はどうやらレジスタの中身を移動させる命令を生成するための関数のようで、
1121 | 使用するレジスタが多くなると使われるようだ。必要になるまで遅延することにする。
1122 |
1123 | 続くコミット<>はグローバル変数を扱うためのコミットのようだ。
1124 | グローバル変数を `load` する場合、まずその読み込むべきアドレスの値をレジスタに作る必要があるが、
1125 | 即値ロード命令が限定されているため、即値のアドレスの上位ビットと下位ビットを分けて読み込む必要がある。
1126 | そのためにコミットでは `%hi` と `%lo` を使用しているが、現状我々のバックエンドにはこれらが実装されていない。
1127 | そこで一旦このコミットは飛ばすことにするfootnote:[そもそもRV32Kv1の枠組みでは32bit即値を読み込むことが
1128 | 困難であるという事情もある。]。
1129 |
1130 | == 条件分岐に対応する
1131 |
1132 | 条件分岐に<>を参考にして対応する。
1133 | ISDには条件分岐として `BRCOND` と `BR_CC` の2つがある。
1134 | `BRCOND` は条件分岐のみを行うのに対し、 `BR_CC` は2ノード間の比較と条件分岐をともに行う。
1135 | ここでは、よりパターンマッチが容易な `BRCOND` にのみ対応することにし、
1136 | `BR_CC` は `setOPerationAction` を用いて `Expand` する
1137 | footnote:[おそらく `BRCOND` に書き換えるということ。どう書き換えるかがどこで定義されているかはよくわからない。TODO]。
1138 |
1139 | === `setlt` と `setgt` に対応する
1140 |
1141 | TableGenを用いて `brcond` に対するパターンマッチを記述する。
1142 |
1143 | RV32Kv1では `BEQZ` と `BNEZ` のみが定義されているfootnote:[RV32Cではこれらは `BEQ` ・ `BNE` 命令から置換する形で使用される。]。
1144 | そこで `setlt` と `setgt` が自然に定義できる。すなわち `$a < $b` という比較なら `SLT $a, $b` 後 `BNEZ $a, hoge` とする。
1145 |
1146 | 分岐命令のパターンマッチでは `bb` というクラスがよく登場する。( `bb:$imm9` )。これはおそらくbasic blockで、
1147 | 分岐の際に用いるプリミティブな型のようだ。パターンマッチの後半で正しい型を指定することで、実際の型を指定できる気がする。TODO
1148 | これと絡む即値の型は `Operand` の代わりに `Operand` を継承する必要があるようだ。
1149 | なおbasic blockを処理するために `MachineOperand::MO_MachineBasicBlock` についての場合分けを
1150 | `LowerRV32KMachineOperandToMCOperand` に追加する必要がある。
1151 |
1152 | LLVM IRをテキスト形式で書く際 `brcond` という命令は直接は存在せず `br` 命令を介してこれを使うことになる。
1153 | その際 `false` 部の処理を行うため `br` 命令も発行されることに注意が必要である。
1154 | すなわち `brcond` のためのパターンマッチのみならず `br` のためのパターンマッチも潜在的に不可欠である。
1155 | これを解決するために
1156 |
1157 | def : Pat<(br bb:$imm12), (J simm12_lsb0:$imm12)>;
1158 |
1159 | としても良いように見えるし、実際動く。
1160 | しかしRISC Vの実装では `PseudoBR` を定義して解消しているため、とりあえずこれを採用してみる
1161 | footnote:[要するにちゃんとした理由がないのだが、そのうち同様の実装を必要とする
1162 | Pseudo命令が出てくるだろうという予測はある。]。
1163 |
1164 | このPseudo命令を定義するためには `RV32KAsmPrinter::lowerOperand` を
1165 | <>を参考にして実装する必要がある。
1166 |
1167 | RV32Kv1の現在の定義では `SLT` のみが存在するため、ナイーブに定義できるのは `setlt` のみである。
1168 | これを使用するようにSelectionDAGを構成するためには次のようなLLVM IRを入力する。
1169 | ....
1170 | define i32 @foo(i32 %a, i32 *%b) {
1171 | %val3 = load volatile i32, i32* %b
1172 | %tst3 = icmp slt i32 %val3, %a
1173 | br i1 %tst3, label %end2, label %end
1174 | end:
1175 | ret i32 1
1176 | end2:
1177 | ret i32 0
1178 | }
1179 | ....
1180 | ここで
1181 | ....
1182 | br i1 %tst3, label %end2, label %end
1183 | ....
1184 | とすると途中で `setge` に変換されてしまうため注意が必要である。
1185 |
1186 | image::img/brcond_with_br.svg[]
1187 |
1188 | 引数を逆転させることで `setgt` についてもパターンマッチ可能である。
1189 |
1190 | なお `setlt` などを挟まず `brcond` が単体で現れる場合についてもパターンマッチが可能だが、
1191 | これについてLLVM IRでテストを書くと `and` のSelectionDAGが現れてしまうためRV32Kv1では
1192 | コンパイルできない。仕方がないのでテストはなしにしておく。
1193 |
1194 | === `seteq` と `setne` に対応する
1195 |
1196 | `sub` と `beqz` を組み合わせることで `seteq` を実現可能である。
1197 | また `sub` と `bnez` を組み合わせることで `setne` を実現可能である。
1198 | しかし `sub` は左辺を破壊する命令のため、前後の命令との兼ね合いによってはレジスタの値を別のレジスタに移したり、
1199 | スタックに保存する必要があるfootnote:[実はこの時点ではこの需要は微妙なところだが、まあそのうち必要になることに疑いはない。]。そこで<>で示唆したように
1200 | <>を参考にして `RV32KInstrInfo::copyPhysReg` を実装し、
1201 | さらに<>を参考にして `RV32KInstrInfo::storeRegToStackSlot` と
1202 | `RV32KInstrInfo::loadRegFromStackSlot` を実装する。
1203 | またこれに必要な `RV32KRegisterInfo::eliminateFrameIndex` も実装する。
1204 | この関数は `MachineInstr` にオペランドとして含まれる `FrameIndex` を
1205 | `FrameReg` と `Offset` に変換するための関数である。
1206 | `storeRegToStackSlot` などではオペランドとして `FrameIndex` を指定し、
1207 | 即値は `0` にしておく。そのうえで `eliminateFrameIndex` を呼び出し、
1208 | 正しいオペランドに変換しているfootnote:[と思う。裏はあんまり取っていない。TODO]。
1209 | ここでRV32Kv1の `lw` と `sw` が符号**なし**即値をとることが問題になる。
1210 | すなわち `Offset` が負数になる場合に対処できない。
1211 | この問題はどうすることもできないので、とりあえず放置する。
1212 | また `FrameReg` はRV32Kv1でははっきりと決まっていいないがとりあえず `X15` にしておく。
1213 |
1214 | ここまでで次のようなLLVM IRが
1215 | ....
1216 | define void @foo(i32 %a, i32 *%b) {
1217 | %val1 = load volatile i32, i32* %b
1218 | %tst1 = icmp slt i32 %val1, %a
1219 | br i1 %tst1, label %end, label %test2
1220 |
1221 | test2:
1222 | %val2 = load volatile i32, i32* %b
1223 | %tst2 = icmp sgt i32 %val2, %a
1224 | br i1 %tst2, label %end, label %test3
1225 |
1226 | test3:
1227 | %val3 = load volatile i32, i32* %b
1228 | %tst3 = icmp eq i32 %val3, %a
1229 | br i1 %tst3, label %end, label %test4
1230 |
1231 | test4:
1232 | %val4 = load volatile i32, i32* %b
1233 | %tst4 = icmp ne i32 %val4, %a
1234 | br i1 %tst4, label %end, label %test5
1235 |
1236 | test5:
1237 | %val5 = load volatile i32, i32* %b
1238 | br label %end
1239 |
1240 | end:
1241 | ret void
1242 | }
1243 | ....
1244 | 次のようなRV32Kv1コードに変換される。
1245 | ....
1246 | .globl foo # -- Begin function foo
1247 | .p2align 3
1248 | .type foo,@function
1249 | foo: # @foo
1250 | # %bb.0:
1251 | lw x10, 0(x9)
1252 | slt x10, x8
1253 | bnez x10, .LBB0_5
1254 | j .LBB0_1
1255 | .LBB0_1: # %test2
1256 | lw x10, 0(x9)
1257 | mv x11, x8
1258 | slt x11, x10
1259 | bnez x11, .LBB0_5
1260 | j .LBB0_2
1261 | .LBB0_2: # %test3
1262 | lw x10, 0(x9)
1263 | sub x10, x8
1264 | beqz x10, .LBB0_5
1265 | j .LBB0_3
1266 | .LBB0_3: # %test4
1267 | lw x10, 0(x9)
1268 | sub x10, x8
1269 | bnez x10, .LBB0_5
1270 | j .LBB0_4
1271 | .LBB0_4: # %test5
1272 | lw x8, 0(x9)
1273 | .LBB0_5: # %end
1274 | jr x1
1275 | .Lfunc_end0:
1276 | .size foo, .Lfunc_end0-foo
1277 | # -- End function
1278 |
1279 | .section ".note.GNU-stack","",@progbits
1280 | ....
1281 |
1282 | == 関数呼び出しに対応する
1283 |
1284 | 関数呼び出しをサポートするためにはi) `PseudoCALL` を実装しii) `RV32KTargetLowering::LowerCall`
1285 | を実装すれば良いようだ。前者は `JAL` に展開され、
1286 | 後者はLLVM IRから `SelectionDAG` への変換を担う。
1287 |
1288 | LLVM IRの `call` は、まず `lui` と `addi` で関数のアドレスを取得した後、
1289 | `jalr` でその場所で飛ぶようなアセンブリに変換される。ただLLVM 9の `clang` でアセンブリを出力
1290 | させると `call` 命令を使うものが出力される。RISC Vのソースコードを見ると次のようにある。
1291 | ....
1292 | // PseudoCALL is a pseudo instruction which will eventually expand to auipc
1293 | // and jalr while encoding. This is desirable, as an auipc+jalr pair with
1294 | // R_RISCV_CALL and R_RISCV_RELAX relocations can be be relaxed by the linker
1295 | // if the offset fits in a signed 21-bit immediate.
1296 | // Define AsmString to print "call" when compile with -S flag.
1297 | // Define isCodeGenOnly = 0 to support parsing assembly "call" instruction.
1298 | ....
1299 | アセンブリ出力時のみ `call` を使うようだ。
1300 |
1301 | 関数のアドレスはグローバルなので、そのアドレスを取得するコードを `lowerCall` 中に
1302 | 記述する必要がある。パッチ<>では `lowerGlobalAddress` を読んでいるが、
1303 | これは実装していない。そこでこの処理を応急処置的に書くことにする。
1304 | 現状のRV32Kv1ではすべての関数呼び出しのアドレスはFxiupで解決される(relocationに回らない)
1305 | と前提しているので、そのとおりに実装する。
1306 |
1307 | `LowerCall` ではおおよそi) 引数を解析してii) `CALLSEQ_START` ノードを出力しiii)
1308 | 引数を処理するためのDAG( `CopyToReg` )をはさみiii) `CALL` ノードを出力しiv)
1309 | `CALLSEQ_END` ノードを出力し v) 返ってきた値を `CopyFromReg` ノードを出力することで
1310 | 取り出している、ようだ。他にも様々な処理が挟まっているが、よくわからない。TODO
1311 |
1312 | `CopyFromReg` は物理レジスタ(physical register)**から**仮想レジスタ(virtual register)に
1313 | 値をコピーするようなDAGを生成し、逆に `CopyToReg` は物理レジスタ**へ**仮想レジスタの中の
1314 | 値をコピーするようなDAGを生成する。一般のレジスタ割り付けが行われる以前であっても、
1315 | 例えば関数の引数・戻り値のように割り付けるべき物理レジスタが決まる場合がある。
1316 | この区別をこのDAGは行っているようだ。
1317 | したがって `LowerCall` では `CopyToReg` を使って引数を詰め込み、
1318 | 関数を呼び、それが終わったら `CopyFromReg` を使って戻り値を取り出しているということになる。
1319 |
1320 | さて `PseudoCALL` はパッチ<>では `jalr` に展開される。
1321 | 一見 `jal` に展開すれば良いように思えるし、実際RISC Vの実装ではそうなっているのだが、
1322 | そうするためには `PseudoCALL` の `ins` に指定するクラスを作成する必要がある。
1323 | RISC Vでは `bare_symbol` が、Lanaiでは `CallTarget` がそれに当たる。
1324 | 複雑で良くわからないのでとりあえずここは `jalr` に展開することにする。
1325 |
1326 | `let Defs = [X1]` を `PseudoCALL` にかぶせているのは、おそらく `X1` が `jalr` によって
1327 | 書き換えられることを意味している。 `jalr` が `X1` を `outs` に含めていないのは、
1328 | おそらく `jalr` が `X1` を出力するというわけではないからだと思うがわからない。TODO
1329 |
1330 | `getCallPreservedMask` を定義する必要がある。Callee-savedなレジスタに関する情報を
1331 | 渡すための関数のようだ。(おそらく;TODO)TableGenが再生する `CSR_RegMask` をそのまま返せば良い。
1332 |
1333 | `CallSeqStart` と `CallSeqEnd` は `ADJCALLSTACKDOWN` ・ `ADJCALLSTACKUP` に結びつける必要がある。
1334 | これらが `let Defs = [X2], Uses = [X2]` で囲まれているのは(おそらく) `X2` を書き換えつつ、
1335 | かつその(過去の?;TODO)値を使用するから。
1336 |
1337 | すると次のようなエラーが出る。
1338 |
1339 | ....
1340 | Can't store this register to stack slot
1341 | UNREACHABLE executed at /home/anqou/ano/secure_vm/llvm-project/llvm/lib/Target/RV32K/RV32KInstrInfo.cpp:55!
1342 | ....
1343 |
1344 | そこで `ADJCALLSTACKDOWN` らを `RV32KGenInstrInfo` のコンストラクタに渡すようにすると、
1345 | また次のようなエラーが出る。
1346 |
1347 | ....
1348 | Call Frame Pseudo Instructions do not exist on this target!
1349 | UNREACHABLE executed at /home/anqou/ano/secure_vm/llvm-project/llvm/include/llvm/CodeGen/TargetFrameLowering.h:299!
1350 | ....
1351 |
1352 | そこで `eliminateCallFramePseudoInstr` を実装する。
1353 | この関数は関数呼び出しのための疑似命令を具体的な命令に置き換えるための関数のようだが、
1354 | ここではその疑似命令を削除するに留める。
1355 |
1356 | するとまた初めのエラーが再燃した。
1357 |
1358 | ....
1359 | Can't store this register to stack slot
1360 | UNREACHABLE executed at /home/anqou/ano/secure_vm/llvm-project/llvm/lib/Target/RV32K/RV32KInstrInfo.cpp:56!
1361 | ....
1362 |
1363 | よくよく見てみると、当該ソースコードは次のようになっている。
1364 |
1365 | ....
1366 | void RV32KInstrInfo::storeRegToStackSlot(MachineBasicBlock &MBB,
1367 | MachineBasicBlock::iterator I,
1368 | unsigned SrcReg, bool IsKill, int FI,
1369 | const TargetRegisterClass *RC,
1370 | const TargetRegisterInfo *TRI) const {
1371 | DebugLoc DL;
1372 | if (I != MBB.end())
1373 | DL = I->getDebugLoc();
1374 |
1375 | if (RV32K::GPRCRegClass.hasSubClassEq(RC))
1376 | BuildMI(MBB, I, DL, get(RV32K::SW))
1377 | .addReg(SrcReg, getKillRegState(IsKill))
1378 | .addFrameIndex(FI)
1379 | .addImm(0);
1380 | else
1381 | llvm_unreachable("Can't store this register to stack slot");
1382 | }
1383 | ....
1384 |
1385 | レジスタをスタックに対比するMIを生成するためのコードである。
1386 | ここで `sw` 命令が `GPRC` レジスタのみをとることが災いし `x1` をスタックに積むことができない。
1387 | したがって**RV32Kv1の枠組みでは関数呼び出しを行えないことが判明した**。
1388 |
1389 | RV32Kv1のためのLLVMバックエンド作成はここで(突然)中止であるfootnote:[作りながら、このISAでは
1390 | 限界がありそうだと感じていたが、しかしLLVMがハングするまでここで中止になることはわからなかった。]
1391 | footnote:[あとから気づいたが、直接スタックに積むのではなく一旦 `GPRC` のレジスタに `mv` してから
1392 | スタックに積めばこの問題を回避できたのかもしれない。ただここで「どのレジスタに一旦退避するか」を
1393 | どう求めればよいかは良くわからない。]。
1394 |
1395 | [bibliography]
1396 | == 参照
1397 |
1398 | - [[[github_riscv-llvm_docs_01,1]]] https://github.com/lowRISC/riscv-llvm/blob/master/docs/01-intro-and-building-llvm.mkd
1399 | - [[[llvm_getting-started,2]]] https://llvm.org/docs/GettingStarted.html
1400 | - [[[clang_gettings-started,3]]] https://clang.llvm.org/get_started.html
1401 | - [[[asciidoctor_user-manual,4]]] https://asciidoctor.org/docs/user-manual/
1402 | - [[[riscv,5]]] https://riscv.org/
1403 | - [[[riscv_specifications,6]]] https://riscv.org/specifications/
1404 | - [[[fox-llvm,7]]] 『きつねさんでもわかるLLVM〜コンパイラを自作するためのガイドブック〜』(柏木 餅子・風薬・矢上 栄一、株式会社インプレス、2013年)
1405 | - [[[github_riscv-llvm_docs_02,8]]] https://github.com/lowRISC/riscv-llvm/blob/master/docs/02-starting-the-backend.mkd
1406 | - [[[github_riscv-llvm_patch_0002,9]]] https://github.com/lowRISC/riscv-llvm/blob/master/0002-RISCV-Recognise-riscv32-and-riscv64-in-triple-parsin.patch
1407 | - [[[github_riscv-llvm,10]]] https://github.com/lowRISC/riscv-llvm
1408 | - [[[youtube_llvm-backend-development-by-example,11]]] https://www.youtube.com/watch?v=AFaIP-dF-RA
1409 | - [[[msyksphinz_try-riscv64-llvm-backend,12]]] http://msyksphinz.hatenablog.com/entry/2019/01/02/040000_1
1410 | - [[[github_riscv-llvm_patch_03,13]]] https://github.com/lowRISC/riscv-llvm/blob/master/0003-RISCV-Add-RISC-V-ELF-defines.patch
1411 | - [[[github_riscv-llvm_patch_04,14]]] https://github.com/lowRISC/riscv-llvm/blob/master/0004-RISCV-Add-stub-backend.patch
1412 | - [[[github_riscv-llvm_patch_06,15]]] https://github.com/lowRISC/riscv-llvm/blob/master/0006-RISCV-Add-bare-bones-RISC-V-MCTargetDesc.patch
1413 | - [[[github_riscv-llvm_patch_10,16]]] https://github.com/lowRISC/riscv-llvm/blob/master/0010-RISCV-Add-support-for-disassembly.patch
1414 | - [[[llvm-writing_backend-operand_mapping,17]]] https://llvm.org/docs/WritingAnLLVMBackend.html#instruction-operand-mapping
1415 | - [[[llvm-writing_backend,18]]] https://llvm.org/docs/WritingAnLLVMBackend.html
1416 | - [[[github_riscv-llvm_patch_07,19]]] https://github.com/lowRISC/riscv-llvm/blob/master/0007-RISCV-Add-basic-RISCVAsmParser.patch
1417 | - [[[github_riscv-llvm_patch_08,20]]] https://github.com/lowRISC/riscv-llvm/blob/master/0008-RISCV-Add-RISCVInstPrinter-and-basic-MC-assembler-te.patch
1418 | - [[[llvm-tablegen,21]]] https://llvm.org/docs/TableGen/index.html
1419 | - [[[github_riscv-llvm_patch_09,22]]] https://github.com/lowRISC/riscv-llvm/blob/master/0009-RISCV-Add-support-for-all-RV32I-instructions.patch
1420 | - [[[llvm_dev_ml-tablegen_definition_question,23]]] http://lists.llvm.org/pipermail/llvm-dev/2015-December/093310.html
1421 | - [[[llvm_doxygen-twine,24]]] https://llvm.org/doxygen/classllvm_1_1Twine.html
1422 | - [[[llvm-tablegen-langref,25]]] https://llvm.org/docs/TableGen/LangRef.html
1423 | - [[[github_riscv-llvm_docs_05,26]]] https://github.com/lowRISC/riscv-llvm/blob/master/docs/05-disassembly.mkd
1424 | - [[[github_riscv-llvm_patch_11,27]]] https://github.com/lowRISC/riscv-llvm/blob/master/0011-RISCV-Add-common-fixups-and-relocations.patch
1425 | - [[[github_riscv-llvm_docs_06,28]]] https://github.com/lowRISC/riscv-llvm/blob/master/docs/06-relocations-and-fixups.mkd
1426 | - [[[github_riscv-llvm_patch_13,29]]] https://github.com/lowRISC/riscv-llvm/blob/master/0013-RISCV-Initial-codegen-support-for-ALU-operations.patch
1427 | - [[[speakerdeck-llvm_backend_development,30]]] https://speakerdeck.com/asb/llvm-backend-development-by-example-risc-v
1428 | - [[[llvm-code_generator,31]]] https://llvm.org/docs/CodeGenerator.html
1429 | - [[[llvm-code_generator-target_independent_code_gen_alg,32]]] https://llvm.org/docs/CodeGenerator.html#target-independent-code-generation-algorithms
1430 | - [[[llvm-code_generator-selectiondag_instruction_selection,33]]] https://llvm.org/docs/CodeGenerator.html#selectiondag-instruction-selection-process
1431 | - [[[github_riscv-llvm_patch_15,34]]] https://github.com/lowRISC/riscv-llvm/blob/master/0015-RISCV-Codegen-support-for-memory-operations.patch
1432 | - [[[cpu0,35]]] https://jonathan2251.github.io/lbd/
1433 | - [[[elvm-llvm_backend,36]]] https://github.com/shinh/llvm/tree/elvm
1434 | - [[[elvm-slide,37]]] http://shinh.skr.jp/slide/llel/000.html
1435 | - [[[github_riscv-llvm_patch_16,38]]] https://github.com/lowRISC/riscv-llvm/blob/master/0016-RISCV-Codegen-support-for-memory-operations-on-globa.patch
1436 | - [[[github_riscv-llvm_patch_17,39]]] https://github.com/lowRISC/riscv-llvm/blob/master/0017-RISCV-Codegen-for-conditional-branches.patch
1437 | - [[[todai_llvm_backend,40]]] https://github.com/cpu-experiment-2018-2/llvm/tree/master/lib/Target/ELMO
1438 | - [[[todai_llvm_backend-article,41]]] http://uenoku.hatenablog.com/entry/2018/12/25/044244
1439 | - [[[github_riscv-llvm_patch_18,42]]] https://github.com/lowRISC/riscv-llvm/blob/master/0018-RISCV-Support-for-function-calls.patch
1440 | - [[[llvm-langref,43]]] http://llvm.org/docs/LangRef.html
1441 | - [[[fpga_develop_diary,44]]] http://msyksphinz.hatenablog.com/
1442 |
--------------------------------------------------------------------------------
/main.asciidoc:
--------------------------------------------------------------------------------
1 | = 手を動かせばできるLLVMバックエンド チュートリアル
2 | 艮 鮟鱇
3 | :toc: left
4 | :icons: font
5 | :stem: latexmath
6 |
7 | == FIXME
8 |
9 | AsciiDocのコメントを用いて文中にFIXMEを仕込む。
10 | その他のFIXME(全般的なものなど)をここにリストにする。
11 |
12 | * だ・である調をです・ます調に変える。
13 | * 実際にやってみる。
14 | ** 現状過去の作業ログを切り貼りしながら書いているので通してちゃんと動くかは良くわからない。
15 | * LLVM 12.0.0に対応させる
16 |
17 | == この文書について
18 |
19 | この文書は https://asciidoctor.org/[Asciidoctor]を用いて執筆されています。
20 | 記述方法は https://asciidoctor.org/docs/user-manual/[Asciidoctor User Manual]を
21 | 参考にしてください。
22 |
23 | この文書はGitによって管理されています。
24 | https://github.com/ushitora-anqou/write-your-llvm-backend[リポジトリはGitHubにて
25 | 公開しています]。同じリポジトリで、この文書を書くにあたってベースとしている
26 | メモ(
27 | https://ushitora-anqou.github.io/write-your-llvm-backend/draft-rv32kv1.html[RV32Kv1]・
28 | https://ushitora-anqou.github.io/write-your-llvm-backend/draft-rv16kv2.html[RV16Kv2]・
29 | https://ushitora-anqou.github.io/write-your-llvm-backend/draft-cahpv3.html[CAHPv3])を読むことができます。
30 |
31 | この文書に(おおよそ)則って開発されたLLVMバックエンドのソースコードを
32 | https://github.com/virtualsecureplatform/llvm-cahp[GitHubリポジトリにて公開しています]。
33 |
34 | この作品は、クリエイティブ・コモンズの 表示 4.0 国際 ライセンスで提供されています。ライセンスの写しをご覧になるには、 http://creativecommons.org/licenses/by/4.0/ をご覧頂くか、Creative Commons, PO Box 1866, Mountain View, CA 94042, USA までお手紙をお送りくださいfootnote:[この
35 | 段落はクリエイティブ・コモンズより引用。]。
36 |
37 | 本文書の内容は筆者が独自に調査したものです。
38 | **疑う余地なく誤りが含まれます**。誤りに気づかれた方はGitHubリポジトリなどを通じて
39 | ご連絡ください。なお誤っていそうな部分についてはAsciidoctorのコメント機能を用いて
40 | コメントを残しています。 `FIXME` というキーワードでソースコードの全文検索をしてください。
41 |
42 | == LLVMバックエンド概略
43 |
44 | 本書ではRISC-V風味の独自ISAを例にLLVMバックエンドを開発します。
45 |
46 | 使用するLLVMのバージョンはv12.0.0です。
47 |
48 | // FIXME: 人がLLVMバックエンドを書きたくなるような文章をここに書く。
49 |
50 | == ところで
51 |
52 | 一度もコンパイラを書いたことがない人は、この文書を読む前に
53 | 『低レイヤを知りたい人のためのCコンパイラ作成入門』<>などで一度
54 | フルスクラッチからコンパイラを書くことをおすすめします。
55 |
56 | また<>などを参考に、
57 | LLVMではなくGCCにバックエンドを追加することも検討してみてはいかがでしょうか。
58 |
59 | == 参考にすべき文献
60 |
61 | LLVMバックエンドを開発する際に参考にできる書籍やWebサイトを以下に一覧します。
62 | なおこの文書では、RISC-Vバックエンド及びそれに関する技術資料を**大いに**参考しています。
63 |
64 | === Webページ
65 |
66 | * Writing an LLVM Backend<>
67 | ** 分かりにくく読みにくい。正直あんまり見ていないが、たまに眺めると有益な情報を見つけたりもする。
68 | * The LLVM Target-Independent Code Generator<>
69 | ** <>よりもよほど参考になる。LLVMバックエンドがどのようにLLVM IRをアセンブリに落とすかが明記されている。必読。
70 | * TableGenのLLVMのドキュメント<>
71 | ** 情報量が少ない。これを読むよりも各種バックエンドのTableGenファイルを読むほうが良い。
72 | * LLVM Language Reference Manual<>
73 | ** LLVM IRについての言語リファレンス。LLVM IRの仕様などを参照できる。必要に応じて読む。
74 | * Architecture & Platform Information for Compiler Writers<>
75 | ** LLVMで公式に実装されているバックエンドに関するISAの情報が集約されている。Lanaiの言語仕様へのリンクが貴重。
76 | * RISC-V support for LLVM projects<>
77 | ** **どちゃくそに参考になる**。以下の開発はこれに基づいて行う。
78 | ** LLVMにRISC-Vサポートを追加するパッチ群。バックエンドを開発するためのチュートリアルも兼ねているらしく `docs/` 及びそれと対応したpatchが参考になる。
79 | ** またこれについて、開発者が2018 LLVM Developers' Meetingで登壇したときの動画は<>より閲覧できる。スライドは<>より閲覧できる。
80 | ** そのときのCoding Labは<>より閲覧できる。
81 | * Create an LLVM Backend for the Cpu0 Architecture<>
82 | ** Cpu0という独自アーキテクチャのLLVMバックエンドを作成するチュートリアル。多少古いが、内容が網羅的で参考になる。英語が怪しい。
83 | * FPGA開発日記<>
84 | ** Cpu0の資料<>をもとに1からRISC-Vバックエンドを作成する過程がブログ<>として公開されている。GitHubに実装も公開されている<>。
85 | * ELVMバックエンド<>
86 | ** 限られた命令でLLVM IRの機能を達成する例として貴重。でも意外とISAはリッチだったりする。
87 | ** 作成者のスライドも参考になる<>。
88 | * 2018年度東大CPU実験で開発されたLLVM Backend<>
89 | ** これについて書かれたAdCのエントリもある<>。
90 | * Tutorial: Building a backend in 24 hours<>
91 | ** LLVMバックエンドの大まかな動きについてざっとまとめたあと、 `ret` だけが定義された最低限のLLVMバックエンド ("stub backend") を構成している。
92 | ** Instruction Selection の説明にある *Does bunch of magic and crazy pattern-matching* が好き。
93 | * 2017 LLVM Developers’ Meeting: M. Braun "Welcome to the back-end: The LLVM machine representation"<>
94 | ** スライドも公開されている<>。
95 | ** 命令選択が終わったあとの中間表現であるLLVM MIR
96 | ( `MachineFunction` や `MachineInstr` など)や、それに対する操作の解説。
97 | RegStateやframe index・register scavengerなどの説明が貴重。
98 | * Howto: Implementing LLVM Integrated Assembler<>
99 | ** LLVM上でアセンブラを書くためのチュートリアル。アセンブラ単体に焦点を絞ったものは珍しい。
100 | * Building an LLVM Backend<>
101 | ** 対応するレポジトリが<>にある。
102 | * [LLVMdev] backend documentation<>
103 | ** llvm-devメーリングリストのバックエンドのよいドキュメントは無いかというスレッド。Cpu0とTriCoreが挙げられているが、深くまで記述したものは無いという回答。
104 | * TriCore Backend<>
105 | ** TriCoreというアーキテクチャ用のバックエンドを書いたという論文。スライドもある<>。ソースコードもGitHub上に上がっているが、どれが公式かわからないfootnote:[論文とスライドも怪しいものだが、著者が一致しているので多分正しいだろう。]。
106 | * Life of an instruction in LLVM<>
107 | ** Cコードからassemblyまでの流れを概観。
108 | * LLVM Backendの紹介<>
109 | ** 「コンパイラ勉強会」footnote:[これとは別の発表で「コンパイラ開発してない人生はFAKE」という名言が飛び出した勉強会<>。]での、LLVMバックエンドの大きな流れ(特に命令選択)について概観した日本語スライド。
110 | * llvm-2003f 処理の流れの例<>
111 | ** 2003f用のLLVMバックエンドの処理について説明したページ。
112 |
113 | === 書籍
114 |
115 | * 『きつねさんでもわかるLLVM〜コンパイラを自作するためのガイドブック〜』<>
116 | ** 数少ない日本語資料。Passやバックエンドの各クラスについて説明している。<>と合わせて大まかな流れを掴むのに良い。
117 | ** ただし書籍中で作成されているバックエンドは機能が制限されており、またコードベースも多少古い。
118 |
119 | なおLLVMについてGoogleで検索していると"LLVM Cookbook"なる謎の書籍(の電子コピー)が
120 | 見つかるが、内容はLLVM公式文書のパクリのようだ<>。
121 |
122 | === バックエンド
123 |
124 | * RISC-V<>
125 | ** パッチ群が開発ドキュメントとともに公開されている<>。以降の開発はこれをベースに行う。
126 | * Lanai<>
127 | ** Googleが開発した32bit RISCの謎アーキテクチャ。全く実用されていないが、バックエンドが単純に設計されておりコメントも豊富のためかなり参考になるfootnote:[LLVMバックエンドの開発を円滑にするためのアーキテクチャなのではと思うほどに分かりやすい。]footnote:[後のSparcについて<>
128 | にて指摘されているように、商業的に成功しなかったバックエンドほどコードが単純で分かりやすい。]。
129 | * Sparc
130 | ** <>でも説明に使われており、コメントが豊富。
131 | * M68k<>
132 | ** 最近(2021年)追加された<>バックエンド。新し目のLLVMにバックエンドを追加する例として貴重。
133 | * C-SKY<>
134 | ** 最近(2020年)追加された<>バックエンド。新し目のLLVMにバックエンドを追加する例として貴重。
135 | * x86
136 | ** みんな大好きx86。貴重なCISCの資料であり、かつ2オペランド方式を採用する場合に実装例を与えてくれる。あと `EFLAGS` の取り回しなども参考になるが、全体的にコードは読みにくい。ただLLVMの命名規則には従うため、他のバックエンドからある程度推論をして読むのが良い。
137 |
138 | == ISAの仕様を決める
139 |
140 | 本書で使用するISAであるCAHPv4について説明します。
141 |
142 | https://docs.google.com/spreadsheets/d/1lPOTQqDV3XkFyt1o2gx18-NXOeuleu_OAJ0xNvIlQG0/edit?usp=sharing[命令の一覧]
143 |
144 | // FIXME: 詳細を書く
145 |
146 | == スケルトンバックエンドを追加する
147 |
148 | https://github.com/virtualsecureplatform/llvm-cahp/commit/47ed98f09cd52a746c7ba2a0404758d3c1e047bc[47ed98f09cd52a746c7ba2a0404758d3c1e047bc]
149 |
150 | CAHPv4のためのビルドを行うために、中身のないバックエンド(スケルトンバックエンド)を
151 | LLVMに追加します。
152 |
153 | === LLVMのコードを取得する
154 |
155 | まずLLVMのソースコードをGitを用いて取得します。
156 | 前述したように、今回の開発ではLLVM v12.0.0をベースとします。
157 | そこでタグ `llvmorg-12.0.0` から独自実装のためのブランチ `cahpv4` を生成し、
158 | 以降の開発はこのブランチ上で行うことにします。
159 |
160 | $ git clone https://github.com/llvm/llvm-project.git
161 | $ cd llvm-project
162 | $ git checkout llvmorg-12.0.0
163 | $ git switch -c cahpv4
164 |
165 | === CAHPv4をTripleに追加する
166 |
167 | <><>を参考にして
168 | CAHPv4をLLVMに認識させます。LLVMではコンパイル先のターゲットをTripleという単位で
169 | 管理しています。そのTripleの一つとしてCAHPv4を追加します。
170 |
171 | `llvm/include/llvm/ADT/Triple.h` や `llvm/lib/Support/Triple.cpp` などの
172 | ファイルにTripleが列挙されているため、そこにCAHPv4を追加します。
173 | また `llvm/unittests/ADT/TripleTest.cpp` にTripleが正しく認識されているかをチェックする
174 | テストを書きます。
175 |
176 | === CAHPv4のELFフォーマットを定義する
177 |
178 | <>を参考にして、CAHPv4のためのELFフォーマットを定義します。
179 | 具体的にはCAHPv4のマシンを表す識別コードや再配置情報などを記述し、
180 | ELFファイルの出力が動作するようにします。
181 | ただし独自ISAではそのような情報が決まっていないため、適当にでっちあげます。
182 |
183 | === バックエンドを追加する
184 |
185 | <>を参考に `llvm/lib/Target` ディレクトリ内に
186 | `CAHPV4` ディレクトリを作成し、最低限必要なファイルを用意します。
187 |
188 | まずビルドのために `CMakeLists.txt` を用意します。
189 | またCAHPv4に関する情報を提供するために
190 | `CAHPV4TargetInfo.cpp` や `CAHPV4TargetMachine.cpp` などを記述します。
191 |
192 | `CAHPV4TargetMachine.cpp` ではdata layoutを文字列で指定します。
193 | 詳細はLLVM IRの言語仕様<>を参考してください。
194 | // FIXME: ここで指定するdata layoutが結局の所どの程度影響力を持つのかは良くわからない。
195 | // ツール間でのターゲットの識別程度にしか使ってなさそう。要確認。
196 |
197 | 以上で必要最小限のファイルを用意することができました。
198 |
199 | [NOTE]
200 | ====
201 |
202 | LLVM 11までは、ビルドのために `CMakeLists.txt` の他に `LLVMBuild.txt` が必要でしたが、
203 | 外部ツールへの依存を避けるために `CMakeLists.txt` のみで全てを賄うようになりました<>。
204 |
205 | ====
206 |
207 | == LLVMをビルドする
208 |
209 | LLVMは巨大なプロジェクトで、ビルドするだけでも一苦労です。
210 | 以下では継続的な開発のために、高速にLLVMをデバッグビルドする手法を紹介します。
211 | <>・<>・<>を
212 | 参考にしています。
213 |
214 | ビルドの際には以下のソフトウェアが必要になります。
215 |
216 | * `cmake`
217 | * `ninja`
218 | * `clang`
219 | * `clang++`
220 | * `lld`
221 |
222 | ビルドを行うための設定をCMakeを用いて行います。
223 | 大量のオプションはビルドを早くするためのものです<>。
224 |
225 | $ mkdir build
226 | $ cd build
227 | $ cmake -G Ninja \
228 | -DLLVM_ENABLE_PROJECTS="clang;lld" \
229 | -DCMAKE_BUILD_TYPE="Debug" \
230 | -DBUILD_SHARED_LIBS=True \
231 | -DLLVM_USE_SPLIT_DWARF=True \
232 | -DLLVM_OPTIMIZED_TABLEGEN=True \
233 | -DLLVM_BUILD_TESTS=True \
234 | -DCMAKE_C_COMPILER=clang \
235 | -DCMAKE_CXX_COMPILER=clang++ \
236 | -DLLVM_USE_LINKER=lld \
237 | -DLLVM_TARGETS_TO_BUILD="" \
238 | -DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD="CAHPV4" \
239 | ../llvm
240 |
241 | Ninjaを用いてビルドを行います。直接Ninjaを実行しても構いません( `$ ninja` )が、
242 | CMakeを用いて間接的に実行することもできます。
243 |
244 | $ cmake --build .
245 |
246 | 手元の環境(CPUはIntel Core i7-8700で6コア12スレッド、RAMは16GB)では
247 | 30分弱でビルドが完了しました。
248 | また別の環境(CPUはIntel Core i5-7200Uで2コア4スレッド、RAMは8GB)では
249 | 1時間半程度かかりました。以上から類推すると、
250 | stem:[n]コアのCPUを使用する場合およそstem:[\frac{180}{n}]分程度かかるようです。
251 |
252 | ビルドが終了すると `bin/` ディレクトリ以下にコンパイルされたバイナリが生成されます。
253 | 例えば次のようにして、CAHPv4バックエンドが含まれていることを確認できます。
254 |
255 | ....
256 | $ bin/llc --version
257 | LLVM (http://llvm.org/):
258 | LLVM version 12.0.0
259 | DEBUG build with assertions.
260 | Default target: x86_64-unknown-linux-gnu
261 | Host CPU: skylake
262 |
263 | Registered Targets:
264 | cahpv4 - CAHPV4
265 | ....
266 |
267 | [NOTE]
268 | ====
269 | ここでは開発用にデバッグビルドを行いました。
270 | 一方で、他人に配布する場合などはリリースビルドを行います。
271 | その際は次のようにCMakeのオプションを指定します。
272 |
273 | // FIXME: LLVM_BUILD_TESTS=False で良い気がする。要確認。
274 |
275 | $ cmake -G Ninja \
276 | -DLLVM_ENABLE_PROJECTS="lld;clang" \
277 | -DCMAKE_BUILD_TYPE="Release" \
278 | -DLLVM_BUILD_TESTS=True \
279 | -DCMAKE_C_COMPILER=clang \
280 | -DCMAKE_CXX_COMPILER=clang++ \
281 | -DLLVM_USE_LINKER=lld \
282 | -DLLVM_TARGETS_TO_BUILD="" \
283 | -DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD="CAHPV4" \
284 | ../llvm
285 |
286 | ====
287 |
288 | == LLVMをテストする
289 |
290 | `llvm-lit` を使用してLLVMをテストできます。
291 | 例えば先程追加したテストを実行するには次のようにします。
292 |
293 | $ bin/llvm-lit -s --filter "Triple" test # Tripleに関するテストを実行する。
294 |
295 | 他にも次のように実行できます。
296 |
297 | $ bin/llvm-lit test -s # 全てのテストを実行する。
298 | $ bin/llvm-lit -s --filter 'CAHPV4' test # CAHPV4を含むテストを実行する。
299 | $ bin/llvm-lit -as --filter 'CAHPV4' test # テスト結果を詳細に表示する。
300 | $ bin/llvm-lit -as --filter 'CAHPV4' --debug test # デバッグ情報を表示する。
301 |
302 | == アセンブラを作る
303 |
304 | https://github.com/virtualsecureplatform/llvm-cahp/commit/1093d7862020d0e98195c47e7f00294415803d0c[1093d7862020d0e98195c47e7f00294415803d0c]
305 |
306 | この章ではLLVMバックエンドの一部としてアセンブラを実装します。
307 | 具体的にはLLVMのMCLayerを実装し、アセンブリからオブジェクトファイルへの変換を可能にします。
308 | 一度にアセンブラ全体を作るのは難しいため、まずレジスタのみを使用する演算命令に絞って実装し、
309 | 続いて即値を使う命令を実装し、その後メモリを使用する命令やジャンプ命令をカバーします。
310 |
311 | === TableGenファイルを追加する
312 |
313 | LLVM coreは基本的に{cpp}によって記述されています。一方で、多くの箇所で共通する処理などは
314 | 独自のDSL(ドメイン固有言語)であるTableGenを用いて記述し `llvm-tblgen` という
315 | ソフトウェアを用いてこれを{cpp}コードに変換しています。
316 | こうすることによって記述量を減らし、ヒューマンエラーを少なくするという考え方
317 | のようです<>。
318 |
319 | LLVMバックエンドでは、アーキテクチャが持つレジスタや命令などの情報をTableGenによって
320 | 記述します。大まかに言って、TableGenで書ける場所はTableGenによって書き、
321 | 対応できない部分を{cpp}で直に書くというのがLLVM coreの方針のようです。
322 | // FIXME: 単なる印象。ほんまか?
323 | ここでは、簡単なアセンブラを実装するために最低限必要なTableGenファイルを追加します。
324 | 内訳は次のとおりです。
325 |
326 | * `CAHPV4.td`: 下のTableGenファイルをincludeし、その他もろもろを定義。
327 | * `CAHPV4RegisterInfo.td`: レジスタを定義。
328 | * `CAHPV4InstrFormats.td`: 命令形式を定義。
329 | * `CAHPV4InstrInfo.td`: 命令を定義。
330 |
331 | 順に説明します。 `CAHPV4.td` がTableGenファイル全体をまとめているTableGenファイルで、
332 | 内部では `include` を使って他のファイルを読み込んでいます。
333 |
334 | include "llvm/Target/Target.td"
335 |
336 | include "CAHPV4RegisterInfo.td"
337 | include "CAHPV4InstrInfo.td"
338 |
339 | また同時に、今回想定するプロセッサを表す `ProcessorModel` や、
340 | 現在実装しているターゲットの `CAHPV4` について定義しています。
341 | // FIXME: ここの定義が具体的にC++コードにどう反映されるかの確認が必要。
342 | // まぁこう書いておけば問題ないという認識でもとりあえず良い気もするけど……。
343 |
344 | `CAHPV4RegisterInfo.td` ではCAHPV4に存在するレジスタを定義します。
345 | まず `Register` を継承して `class CAHPV4Reg` を作り、これに基本的なレジスタの性質をもたせます。
346 | ついで `class CAHPV4Reg` の実体として `X0` から `X31` を作成します。
347 | `alt` にはレジスタの別名を指定します。
348 | // FIXME: ABIRegAltName がどういう役割を果たしてるのか要検証。
349 | // 多分 `getRegisterName` の第二引数に何も渡さなかったときにAltNameを表示
350 | // させるのに必要なんだと思うけど、裏をとってない。
351 | 最後に、レジスタをまとめて `RegisterClass` である `GPR`
352 | (General Purpose Register; 汎用レジスタの意)を定義します。
353 | ここでレジスタを並べる順番が先であるほどレジスタ割り付けで割り付けられやすいため、
354 | caller-savedなもの(使ってもspill outが起こりにくいもの)を先に並べておきます。
355 | 並べる際には `X0, X1, ...` と並べてもよいのですが、連番の場合は `sequence` を使うことができます。
356 | `RegisterClass` のテンプレート引数には、他に、名前空間・レジスタの型・
357 | メモリ上に保存されるときのアラインメントを指定します。
358 |
359 | def GPR : RegisterClass<"CAHPV4", [i16], 16, (add
360 | (sequence "X%u", 10, 17), // X10, X11, ..., X17, と明示的に書いても同じ意味
361 | (sequence "X%u", 5, 7),
362 | (sequence "X%u", 28, 31),
363 | (sequence "X%u", 8, 9),
364 | (sequence "X%u", 18, 27),
365 | (sequence "X%u", 0, 4)
366 | )>;
367 |
368 | このあと命令を定義する際にはこの `RegisterClass` 単位で指定するため、
369 | 特定のレジスタしか取らないような命令では、その都度新しい `RegisterClass` を作る必要があります。
370 | // FIXME: GPRX0とGPRNoX0の説明
371 | // FIXME: 多分SPはCAHPv4では使わないので以下の段落は要らない。
372 | //`GPR` と同様に `SP` という `RegisterClass` も作成し、 `X1` 、
373 | //つまりスタックポインタを表すレジスタのみを追加しておきます。
374 | //この `RegisterClass` を命令のオペランドに指定することで
375 | //`lwsp` や `swsp` などの「スタックポインタのみを取る命令」を表現することができます。
376 |
377 | 命令は `CAHPV4InstrFormats.td` と `CAHPV4InstrInfo.td` に分けて記述します。
378 | `CAHPV4InstrFormats.td` ではおおよその命令の「形」を定義しておき、
379 | `CAHPV4InstrInfo.td` でそれを具体化します。言葉で言ってもわかりにくいので、コードで見ます。
380 | 例えば加算命令は次のように定義されます。
381 | まずCAHPV4の命令全体に共通する事項を `class CAHPV4Inst` として定義します。
382 |
383 | ....
384 | class CAHPV4Inst pattern = []>
385 | : Instruction {
386 | let Namespace = "CAHPV4";
387 |
388 | dag OutOperandList = outs;
389 | dag InOperandList = ins;
390 |
391 | let AsmString = opcodestr # "\t" # argstr;
392 |
393 | // Matching patterns used when converting SelectionDAG into MachineDAG.
394 | let Pattern = pattern;
395 |
396 | let Size = 4;
397 | bits<32> Inst;
398 | }
399 | ....
400 |
401 | 次いで、R形式(オペランドにレジスタを3つとる)命令を
402 | `class CAHPV4InstR` として定義します。 `class CAHPV4Inst` を継承します。
403 |
404 | ....
405 | // R-instruction format
406 | class CAHPV4InstR opcode, dag outs, dag ins, string opcodestr, string argstr>
407 | : CAHPV4Inst {
408 | bits<5> rd;
409 | bits<5> rs1;
410 | bits<5> rs2;
411 |
412 | let Inst{31-26} = 0;
413 | let Inst{25-21} = rs2;
414 | let Inst{20-16} = rd;
415 | let Inst{15-11} = rs1;
416 | let Inst{10-0} = opcode;
417 | }
418 | ....
419 |
420 | 最後にこれを使って加算命令 `ADD` を定義します。
421 |
422 | ....
423 | def ADD : CAHPV4InstR<0b00000001000, (outs GPR:$rd), (ins GPR:$rs1, GPR:$rs2),
424 | "add", "$rd, $rs1, $rs2">;
425 | ....
426 |
427 | // FIXME: don't careを指定する方法は無いように見えるので、とりあえず0にしておく
428 | // これは出力の際にはそれほど問題ではないが、
429 | // objdumpなどでは正しいバイナリ列を不正なものとみなす恐れがあるため
430 | // 問題になりうる。解決方法を探す必要あり。
431 | // let Inst{31-26} = ?; はいけるけど意味が違いそう。0b00?001みたいなのは駄目。
432 |
433 | 上記の継承による構造を展開すると、結局 `class Instruction` を使って
434 | 次のような定義を行ったことになります。
435 | // FIXME: 要確認。
436 |
437 | ....
438 | def ADD : Instruction {
439 | let Namespace = "CAHPV4";
440 |
441 | let Pattern = [];
442 |
443 | let Size = 4; // 命令長は8bit * 4 = 32bit
444 | bits<32> Inst;
445 |
446 | bits<5> rd; // オペランドrdは5bit
447 | bits<5> rs1; // オペランドrs1は5bit
448 | bits<5> rs2; // オペランドrs2は5bit
449 |
450 | // 命令のエンコーディングは次の通り。
451 | let Inst{31-26} = 0; // 26〜31bit目は0
452 | let Inst{25-21} = rs2; // 21〜25bit目はrs2
453 | let Inst{20-16} = rd; // 16〜20bit目はrd
454 | let Inst{15-11} = rs1; // 11〜15bit目はrs1
455 | let Inst{10-0} = 0b00000001000; // 0〜10bit目はADDのopcode
456 |
457 | // 出力はレジスタクラスGPRのrdに入る。
458 | dag OutOperandList = (outs GPR:$rd);
459 | // 入力はレジスタクラスGPRのrs1とrs2に入る。
460 | dag InOperandList = (ins GPR:$rs1, GPR:$rs2);
461 |
462 | // アセンブリ上では「add rd, rs1, rs2」という形で与えられる。
463 | let AsmString = "add\t$rd, $rs1, $rs2";
464 | }
465 | ....
466 |
467 | `Inst` フィールドにエンコーディングを設定することで、
468 | TableGenにエンコードの処理を移譲することができますfootnote:[一方でx86など
469 | 複雑なエンコーディングを行うISAの場合は `Inst` フィールドを使用せず、
470 | 自前で変換を行っている。]。
471 |
472 | [NOTE]
473 | ====
474 | (CAHPv3の) `add2` のような2オペランドの命令を記述する場合、上の方法では問題があります。
475 | // FIXME: RISC-VのC命令を例に出す
476 | というのも `add2` の第一オペランドは入力であると同時に出力先でもあるためです。
477 | // FIXME: 要検証:outsとinsに同じレジスタを指定した場合はエラーになる?
478 | このような場合は次のように `Constraints` フィールドにその旨を記述します。
479 |
480 | let Constraints = "$rd = $rd_w" in {
481 | def ADD2 : CAHPInst16R<0b10000000, (outs GPR:$rd_w), (ins GPR:$rd, GPR:$rs),
482 | "add2", "$rd, $rs">;
483 | }
484 |
485 | ====
486 |
487 | [NOTE]
488 | ====
489 | TableGenでは `let` で囲むレコードが一つの場合は括弧 `{ }` は必要ありません。
490 | また `let` で外からフィールドを上書きするのと、 `def` の中身に記載するのとで意味は
491 | 変わりません。すなわち、上のコードは次の2通りと意味は異なりません<>。
492 | // FIXME: CAHPv3の例をやめてCAHPv4の例か、RISC-Vなどの例にする
493 | // FIXME: 要検証:本当に意味が変わらないか
494 |
495 | let Constraints = "$rd = $rd_w" in
496 | def ADD2 : CAHPInst16R<0b10000000, (outs GPR:$rd_w), (ins GPR:$rd, GPR:$rs),
497 | "add2", "$rd, $rs">;
498 |
499 | def ADD2 : CAHPInst16R<0b10000000, (outs GPR:$rd_w), (ins GPR:$rd, GPR:$rs),
500 | "add2", "$rd, $rs"> {
501 | let Constraints = "$rd = $rd_w";
502 | }
503 |
504 | ====
505 |
506 | 必要なTableGenファイルを追加した後、
507 | これらのTableGenファイルが正しいかどうか `llvm-tblgen` を用いて確認します。
508 | // FIXME: 要検証:ここで表示されるのは継承を展開したものになっているはず。
509 | // どのへんをみて「正しい」と判断するのか。
510 |
511 | $ bin/llvm-tblgen -I ../llvm/lib/Target/CAHPV4/ -I ../llvm/include/ -I ../llvm/lib/Target/ ../llvm/lib/Target/CAHPV4/CAHPV4.td
512 |
513 | // FIXME: 要確認:キーワードfieldがつく場合とつかない場合で意味が異なるか。
514 | // 観測範囲で言うと多分変わらない。
515 |
516 |
517 | // FIXME: この段落はもう少し冒頭に持ってきたい
518 | なおTableGenファイル中で使用した `RegisterClass` や `Instruction` などは
519 | `llvm/include/llvm/Target/Target.td` にてコメントとともに定義されています。
520 | フィールドの値が何を意味するかわからない場合に参照してください。
521 |
522 | === `MCTargetDesc` を追加する
523 |
524 | アセンブラ本体の{cpp}コードを作成します。ここでは、
525 | アセンブリのエンコードからバイナリ生成部分を担当する `MCTargetDesc` ディレクトリを追加し、
526 | 必要なファイルを揃えます。複数のクラスを定義しますが、それらは全て
527 | `MCTargetDesc/CAHPV4MCTargetDesc.cpp` にある `LLVMInitializeCAHPV4TargetMC`
528 | 関数でLLVM coreに登録されます。
529 |
530 | 定義するクラスは次のとおりです。
531 |
532 | * `CAHPV4MCAsmInfo`
533 | * `CAHPV4MCInstrInfo`
534 | * `CAHPV4MCRegisterInfo`
535 | * `CAHPV4MCSubtargetInfo`
536 | * `CAHPV4MCCodeEmitter`
537 | * `CAHPV4AsmBackend`
538 | * `CAHPV4ELFObjectWriter`
539 |
540 | 順に説明します。
541 |
542 | `CAHPV4MCAsmInfo` にはアセンブリがどのように表記されるかを主に記述します。
543 | // FIXME: 要確認:とllvm::MCAsmInfoのコメントにも書いてあるんだけど、
544 | // の割にCalleeSaveStackSlotSizeとかCodePointerSizeとか指定してて
545 | // どういうこっちゃとなる。
546 | `MCTargetDesc/CAHPV4MCAsmInfo.{h,cpp}` に記述します。
547 |
548 | `CAHPV4MCInstrInfo` は先程記述したTableGenファイルから、
549 | TableGenによって `InitCAHPV4MCInstrInfo` 関数として自動的に生成されます。
550 | `CAHPV4MCTargetDesc.cpp` 内でこれを呼び出して作成します。
551 |
552 | `CAHPV4MCRegisterInfo` も同様に自動的に生成されます。
553 | `InitCAHPV4MCRegisterInfo` 関数を呼び出します。なおこの関数の第二引数には
554 | 関数の戻りアドレスが入るレジスタを指定しますfootnote:[内部で
555 | `llvm::MCRegisterInfo::InitMCRegisterInfo` <>
556 | を呼び出していることからわかります。]。
557 | CAHPV4では `CAHPV4::X1` を渡すことになります。
558 | // FIXME: 要確認:return addressをスタックに積むx86では `eip` を(x86_64では `rip` を)返している。なぜかは良くわからない。
559 |
560 | `CAHPV4MCSubtargetInfo` も同様に自動生成されます。
561 | `createCAHPV4MCSubtargetInfoImpl` を呼び出します。この関数の第二引数には
562 | `CAHPV4.td` で `ProcessorModel` として定義したCPUの名前を指定します。
563 |
564 | `CAHPV4MCCodeEmitter` はアセンブリのエンコード作業を行います。
565 | `MCTargetDesc/CAHPV4MCCodeEmitter.cpp` に記述します。
566 | 主要なエンコード処理はTableGenによって自動生成された
567 | `getBinaryCodeForInstr` を `CAHPV4MCCodeEmitter::encodeInstruction`
568 | から呼び出すことによって行われます。
569 | この関数は `CAHPV4GenMCCodeEmitter.inc` というファイルに定義されるため、
570 | これを `MCTargetDesc/CAHPV4MCCodeEmitter.cpp` 末尾で `#include` しておきます。
571 |
572 | `CAHPV4AsmBackend` にはオブジェクトファイルを作成する際に必要な
573 | fixupの操作( `applyFixup` )や指定バイト数分の無効命令を書き出す処理( `writeNopData` )
574 | などを記述します。 `MCTargetDesc/CAHPV4AsmBackend.cpp` に記述します。
575 | fixupについては後ほど実装するためここではスタブにしておきます。
576 |
577 | `CAHPV4ELFObjectWriter` にはELFファイル(の特にヘッダ)を作成する際に必要な情報を記載します。
578 | このクラスは `LLVMInitializeCAHPV4TargetMC` ではなく
579 | `CAHPV4AsmBackend` の `createObjectTargetWriter` メンバ関数として紐付けられます。
580 | 親クラス `MCELFObjectTargetWriter` のコンストラクタに、
581 | CAHPv4マシンを表す `ELF::EM_CAHPV4` と、 `.rel` ではなく `.rela` を使用する旨を示す
582 | `true` を渡しておきますfootnote:[CAHPv4マシンの仕様などはこの世に存在しないので、
583 | これらは勝手に決めたものです。]。
584 | // FIXME: .rel と .rela の説明をする。原則これは歴史的事情で決まっているものなので
585 | // どっちでもいい、みたいな話がLLDのコメントだったかELFの仕様書だったかに
586 | // 書いてあった気がする。覚えてない。
587 | また `getRelocType` メンバ関数はどのような再配置を行うかを見繕うためのものですが、
588 | ここではスタブにしておきます。
589 |
590 | 上記を実装してビルドします。一度使ってみましょう。
591 | まずテスト用の入力アセンブリファイルを用意します。
592 |
593 | $ cat foo.s
594 | add x1, x2, x3
595 | add x16, x17, x18
596 | sub x1, x2, x3
597 | xor x1, x2, x3
598 | or x1, x2, x3
599 |
600 | LLVMのアセンブラを単体で使う場合は `llvm-mc` というコマンドを使用します。
601 | 次のようにすると `foo.s` をオブジェクトファイルに変換できます。
602 |
603 | $ bin/llvm-mc -arch=cahp -filetype=obj foo.s
604 | bin/llvm-mc: error: this target does not support assembly parsing.
605 |
606 | このようなエラーメッセージが出れば成功ですfootnote:[失敗した場合は
607 | assertなどで異常終了し、スタックトレースなどが表示されます。]。
608 | このエラーメッセージはCAHPV4ターゲットがアセンブリのパーズ(構文解析)に対応していない
609 | ことを意味しています。これは次の節で実装します。
610 |
611 | [NOTE]
612 | ====
613 | // FIXME: 古くなっていないか確認
614 | // FIXME: 要修正:RV32Kv1のメモからそのまま引っ張ってきたのでめちゃくちゃ。
615 | RISC-Vの拡張C命令には `add` などレジスタを5bitで指定する命令と、
616 | `sub` などレジスタを3bitで指定する命令の2種類があります。
617 | LLVM RISC-Vバックエンドを見ると、
618 | エンコードに際してこれらの区別のための特別な処理は行っていません。
619 | というのも、3bitでレジスタを指定する場合その添字の下位3bit以外が無視されるため、
620 | 結果的に正しいコードが出力されるのです。
621 | 例えば `x8` を指定すると、これに `1000` という添字が振られ、
622 | 4bit目を無視することで `000` となるため、
623 | 3bitでのレジスタ指定方法として正しいものになります。
624 |
625 | 独自ISAなどで、このような手法が取れないレジスタの並びを使用する場合は、
626 | アセンブリをコードに変換する際にそのレジスタのエンコーディングを補正します。
627 | このようなレジスタオペランドエンコードのフックを行う関数を指定する場所として
628 | `RegisterOperand` の `EncoderMethod` があります。
629 | 例えば `sub` で `X3` から `X10` を0〜7というエンコードで用いたい場合、
630 | `X3` から `X10` を `GPRC` という `RegisterClass` とした上で、
631 | これを `RegisterOperand` で包み `ShiftedGPRC` とします。
632 | これの `EncoderMethod` として `RV32KEncodeShiftedGPRCRegisterOperand` という関数を指定します。
633 | これは `RV32KMCCodeEmitter` クラスのメンバ関数として定義する。
634 | これによって任意の処理をフックすることができる。https://reviews.llvm.org/rL303044
635 |
636 | ....
637 | def GPRC : RegisterClass<"RV32K", [i32], 32, (add
638 | X3, X4, X5, X6, X7, X8, X9, X10
639 | )>;
640 |
641 | def ShiftedGPRC : RegisterOperand {
642 | let EncoderMethod = "RV32KEncodeShiftedGPRCRegisterOperand";
643 | //let DecoderMethod = "RV32KDecodeShiftedGPRCRegisterOperand";
644 | }
645 | ....
646 |
647 | ....
648 | uint64_t
649 | RV32KEncodeShiftedGPRCRegisterOperand(const MCInst &MI, unsigned no,
650 | SmallVectorImpl &Fixups,
651 | const MCSubtargetInfo &STI) const;
652 |
653 | uint64_t RV32KMCCodeEmitter::RV32KEncodeShiftedGPRCRegisterOperand(
654 | const MCInst &MI, unsigned no, SmallVectorImpl &Fixups,
655 | const MCSubtargetInfo &STI) const {
656 | const MCOperand &MO = MI.getOperand(no);
657 | if (MO.isReg()) {
658 | uint64_t op = Ctx.getRegisterInfo()->getEncodingValue(MO.getReg());
659 | assert(3 <= op && op <= 10 && "op should belong to GPRC.");
660 | return op - 3;
661 | }
662 |
663 | llvm_unreachable("Unhandled expression!");
664 | return 0;
665 | }
666 | ....
667 |
668 | ====
669 |
670 | === `CAHPV4AsmParser` を追加する
671 |
672 | アセンブリのパーズは `CAHPV4AsmParser` クラスが取り仕切ります。
673 | 新しく `AsmParser` ディレクトリを作成し、その中に `CAHPV4AsmParser.cpp` を作成して
674 | パーズ処理を記述します。<>を参考にします。
675 |
676 | `CAHPV4AsmParser::ParseInstruction` がパーズ処理のエントリポイントです。
677 | `CAHPV4AsmParser::parseOperand` や `CAHPV4AsmParser::parseRegister` ・
678 | `CAHPV4AsmParser::parseImmediate` を適宜用いながら、
679 | アセンブリのトークンを切り出し `Operands` に詰め込みますfootnote:[なお以下では
680 | しばらくの間、命令を表す `add` などの文字列そのものも「オペランド」として扱います。]。
681 |
682 | この際にオペランドを表すクラスとして `CAHPV4Operand` を定義・使用しています。
683 | 現在はオペランドとして現れうるのはレジスタと命令などのトークンなので、
684 | その旨を記述します。即値のオペランドなどは後ほど対応します。
685 | なおラベルなどの識別子がオペランドに来るアセンブリには
686 | まだ対応していませんが、後ほど対応する際にはトークンではなく
687 | 即値として対応することになります。
688 |
689 | 切り出されたオペランドのリストを命令としてLLVMに認識させるのは `MatchAndEmitInstruction` で
690 | 行います。具体的には、先程の `Operands` を読み込んで `MCInst` に変換します。
691 | ただし実際の処理の殆どはTableGenによって自動生成された `MatchInstructionImpl` によって
692 | 行われます。実際に書く必要があるのはこの関数が失敗した場合のエラーメッセージ等です。
693 |
694 | `CAHPV4AsmParser` を実装するとアセンブラが完成します。使ってみましょう。
695 |
696 | ....
697 | # 先ほどと同じfoo.sを入力として使用します。
698 | $ cat foo.s
699 | add x1, x2, x3
700 | add x16, x17, x18
701 | sub x1, x2, x3
702 | xor x1, x2, x3
703 | or x1, x2, x3
704 |
705 | $ bin/llvm-mc -arch=cahpv4 -filetype=obj foo.s | od -tx1z -Ax -v
706 | 000000 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 >.ELF............<
707 | 000010 01 00 f6 00 01 00 00 00 00 00 00 00 00 00 00 00 >................<
708 | 000020 70 00 00 00 00 00 00 00 34 00 00 00 00 00 28 00 >p.......4.....(.<
709 | 000030 04 00 01 00 08 10 61 00 08 88 50 02 18 10 61 00 >......a...P...a.<
710 | 000040 28 10 61 00 38 10 61 00 00 00 00 00 00 00 00 00 >(.a.8.a.........<
711 | 000050 00 00 00 00 00 00 00 00 00 2e 74 65 78 74 00 2e >..........text..<
712 | 000060 73 74 72 74 61 62 00 2e 73 79 6d 74 61 62 00 00 >strtab..symtab..<
713 | 000070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 >................<
714 | 000080 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 >................<
715 | 000090 00 00 00 00 00 00 00 00 07 00 00 00 03 00 00 00 >................<
716 | 0000a0 00 00 00 00 00 00 00 00 58 00 00 00 17 00 00 00 >........X.......<
717 | 0000b0 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 >................<
718 | 0000c0 01 00 00 00 01 00 00 00 06 00 00 00 00 00 00 00 >................<
719 | 0000d0 34 00 00 00 14 00 00 00 00 00 00 00 00 00 00 00 >4...............<
720 | 0000e0 04 00 00 00 00 00 00 00 0f 00 00 00 02 00 00 00 >................<
721 | 0000f0 00 00 00 00 00 00 00 00 48 00 00 00 10 00 00 00 >........H.......<
722 | 000100 01 00 00 00 01 00 00 00 04 00 00 00 10 00 00 00 >................<
723 | 000110
724 | ....
725 |
726 | 0x34から0x3dにある `08 10 61 00 ...` が出力であり、
727 | 正しく生成されていることが分かりますfootnote:[このアセンブリはcahpv4-simのテストと
728 | 同じものになっているので、cahpv4-simのテストと比較することで正しいかを(ある程度)判断できます。]。
729 |
730 | === `CAHPV4InstPrinter` を実装する
731 |
732 | https://github.com/virtualsecureplatform/llvm-cahp/commit/bc9465e32f41842ea6cc5796f166fe594635c830[bc9465e32f41842ea6cc5796f166fe594635c830]
733 |
734 | 次の節では、上記までで作成したアセンブラのテストを記述します。
735 | その際、アセンブリを `MCInst` に変換した上でそれをアセンブリに逆変換したものが、
736 | もとのアセンブリと同じであるか否かをチェックします。
737 | このテストを行うためには `MCInst` からアセンブリを得るための仕組みが必要です。
738 | この節ではこれを行う `CAHPV4InstPrinter` クラスを実装します。
739 | <>を参考にします。
740 |
741 | `MCTargetDesc/CAHPV4InstPrinter.{cpp,h}` を作成します。
742 | 命令印字処理の本体は `CAHPV4InstPrinter::printInst` ですが、
743 | そのほとんどの処理は `CAHPV4InstPrinter::printInstruction` というTableGenが生成する
744 | メンバ関数により実行されます。 `CAHPV4InstPrinter::printRegName` はレジスタ名を
745 | 出力する関数で `CAHPV4InstPrinter::printOperand` から呼ばれますが、
746 | これも `CAHPV4InstPrinter::getRegisterName` という自動生成された
747 | メンバ関数に処理を移譲します。この `CAHPV4InstPrinter::getRegisterName` の第二引数に
748 | `CAHPV4::ABIRegAltName` を渡すと、TableGenで定義したAltNameが出力に使用されますfootnote:[この場合
749 | `AltNames` が指定されていないレジスタ(条件分岐のためのフラグなど)があるとエラーとなります。
750 | アセンブリ中に表示され得ないレジスタにもダミーの名前をつける必要があります。]。
751 | // FIXME: 要調査:x86のEFLAGSの名前取っ払ったらエラーになるのか?
752 | `CAHPV4::NoRegAltName` を渡すと本来の名前(CAHPV4では `x0` 〜 `x32` )が使用されます。
753 |
754 | `CAHPV4InstPrinter` クラスは `MCTargetDesc/CAHPV4MCTargetDesc.cpp` にて作成・登録されます。
755 |
756 | 節の冒頭で説明した「アセンブリを `MCInst` に変換した上でそれをアセンブリに逆変換」は
757 | `llvm-mc` の `-show-encoding` オプションを用いて行うことができます。
758 | `-show-encoding` を指定することよって当該アセンブリがどのような機械語に
759 | 翻訳されるか確認することができます。
760 | // FIXME: 要修正:RV32KのものなのでCAHPV4ではない。
761 |
762 | ....
763 | # 前の節と同じfoo.sを使用
764 | $ bin/llvm-mc -arch=cahpv4 -show-encoding foo.s
765 | .text
766 | add x1, x2, x3 # encoding: [0x08,0x10,0x61,0x00]
767 | add x16, x17, x18 # encoding: [0x08,0x88,0x50,0x02]
768 | sub x1, x2, x3 # encoding: [0x18,0x10,0x61,0x00]
769 | xor x1, x2, x3 # encoding: [0x28,0x10,0x61,0x00]
770 | or x1, x2, x3 # encoding: [0x38,0x10,0x61,0x00]
771 | ....
772 |
773 | [NOTE]
774 | ====
775 | `InstPrinter` は、以前はそれ専用のディレクトリを作成してコードを記述していましたが、
776 | バックエンドによっては不都合があるということで `MCTargetDesc` ディレクトリに配置する形に
777 | 変更されました<>。
778 |
779 | ====
780 |
781 | === テストを書く
782 |
783 | https://github.com/virtualsecureplatform/llvm-cahp/commit/964d687f03455d613cb718bc36f62479c2ff2193[964d687f03455d613cb718bc36f62479c2ff2193]
784 |
785 | 前節で動作させた `-show-encoding` オプションを用いて、
786 | アセンブラが正しく動作していることを確認するためのテストを記述します。
787 | 前節と同様にパッチ<>を参考にします。
788 |
789 | まず `test/MC/CAHPV4` ディレクトリを作成し、その中に `valid.s` と `invalid.s` を
790 | 作成します。前者で正しいアセンブリが適切に処理されるか、
791 | 後者で誤ったアセンブリに正しくエラーを出力するかを確認します。
792 |
793 | 記述後 `llvm-lit` を用いてテストを行います。
794 |
795 | ....
796 | $ bin/llvm-lit -as --filter "CAHPV4" test
797 | PASS: LLVM :: MC/CAHPV4/valid.s (1 of 2)
798 | Script:
799 | --
800 | : 'RUN: at line 1'; /home/anqou/ano/secure_vm/llvm-project/build/bin/llvm-mc /home/anqou/workspace/anqura/ano/secure_vm/llvm-project/llvm/test/MC/CAHPV4/valid.s -triple=cahpv4 -show-encoding | /home/anqou/ano/secure_vm/llvm-project/build/bin/FileCheck --allow-unused-prefixes=false -check-prefixes=CHECK,CHECK-INST /home/anqou/workspace/anqura/ano/secure_vm/llvm-project/llvm/test/MC/CAHPV4/valid.s
801 | --
802 | Exit Code: 0
803 |
804 |
805 | ********************
806 | PASS: LLVM :: MC/CAHPV4/invalid.s (2 of 2)
807 | Script:
808 | --
809 | : 'RUN: at line 1'; not /home/anqou/ano/secure_vm/llvm-project/build/bin/llvm-mc -triple cahpv4 < /home/anqou/workspace/anqura/ano/secure_vm/llvm-project/llvm/test/MC/CAHPV4/invalid.s 2>&1 | /home/anqou/ano/secure_vm/llvm-project/build/bin/FileCheck --allow-unused-prefixes=false /home/anqou/workspace/anqura/ano/secure_vm/llvm-project/llvm/test/MC/CAHPV4/invalid.s
810 | --
811 | Exit Code: 0
812 |
813 |
814 | ********************
815 |
816 | Testing Time: 0.03s
817 | Excluded: 41952
818 | Passed : 2
819 | ....
820 |
821 | === 即値を扱う命令を追加する
822 |
823 | https://github.com/virtualsecureplatform/llvm-cahp/commit/b12b28fc5a9e544fef657f45ffafb900cdf5e4e0[b12b28fc5a9e544fef657f45ffafb900cdf5e4e0]
824 |
825 | 続いて即値を用いる命令を追加します。例として `addi` を取り上げます。
826 | `addi` は11bit符号付き即値をオペランドに取ります。まずこれを定義します。
827 |
828 | ....
829 | class ImmAsmOperand : AsmOperandClass {
830 | let Name = prefix # "Imm" # width # suffix;
831 | let RenderMethod = "addImmOperands";
832 | let DiagnosticType = "Invalid" # Name;
833 | }
834 |
835 | class SImmAsmOperand
836 | : ImmAsmOperand<"S", width, suffix> {
837 | }
838 |
839 | def simm11 : Operand {
840 | let ParserMatchClass = SImmAsmOperand<11>;
841 | }
842 | ....
843 |
844 | 続いて命令の「形」を定義します。
845 |
846 | ....
847 | class CAHPV4InstIS11 opcode, dag outs, dag ins, string opcodestr, string argstr>
848 | : CAHPV4Inst {
849 | bits<5> rd;
850 | bits<5> rs1;
851 | bits<11> imm;
852 |
853 | let Inst{31-21} = imm;
854 | let Inst{20-16} = rd;
855 | let Inst{15-11} = rs1;
856 | let Inst{10-0} = opcode;
857 | }
858 | ....
859 |
860 | 最後に、これを用いて `addi` を定義します。
861 |
862 | def ADDI : CAHPV4InstIS11<0b01000001000, (outs GPR:$rd), (ins GPR:$rs1, simm11:$imm),
863 | "addi", "$rd, $rs1, $imm">;
864 |
865 | `add` の際には `GPR` とした第三オペランドが `simm11` となっています。
866 | これによって、この部分に符号付き11bit即値が来ることを指定しています。
867 |
868 | TableGenにて定義・使用した即値を正しく認識するために `isSImm11` などの
869 | メンバ関数を定義する必要があります。これらの関数は後述の `MatchInstructionImpl` 内で
870 | 使用されます。
871 |
872 | // FIXME: 16-bit immediateをsignedとするかunsignedとするか。とりあえずsignedとしているが、
873 | // それはそれとして0xFFFFって書きたい
874 |
875 | == !!!ここより下はまだ書き直されていません!!!
876 |
877 | === メモリ演算を追加する
878 |
879 | https://github.com/virtualsecureplatform/llvm-cahp/commit/43145f861dc729756a8a85df13a7257248e98169[43145f861dc729756a8a85df13a7257248e98169]
880 |
881 | 前節までで、レジスタのみを使用する命令に対応しました。この節ではメモリを使用する
882 | 命令に対応します。具体的にはメモリから1ワード(2バイト)読み込む `lw` と
883 | 1ワード書き込む `sw` 、及びその1バイト版である `lb/lbu/sb` 、
884 | 更にスタックへの読み書きに特化した `lwsp/swsp` を追加します。
885 |
886 | まずTableGenにこれらの命令を定義します。
887 | CAHPアセンブリ中ではメモリは即値とレジスタの組み合わせで表現されます。
888 | // FIXME: 要調査:こういう「メモリ番地の指定方法」を一般に何ていうんだっけ……
889 | 例えば `x8` に入っている値に `4` 足した番地から1ワード読み込んで `x9` に入れる場合は
890 | `lw x9, 4(x8)` と書きます。これを正しく表示するために `AsmString` にはこのように書きます。
891 |
892 | def LW : CAHPInst24MLoad <0b010101, (outs GPR:$rd), (ins GPR:$rs, simm11_lsb0:$imm),
893 | "lw", "$rd, ${imm}(${rs})">
894 |
895 | ここで `${imm}` と括弧でくくっているのは、単に `$imm(` とかくと `imm(` という識別子として
896 | 認識されてしまうためです。
897 |
898 | 即値のうち、下位1bitが0になるものは `_lsb0` というサフィックスを名前につけ区別しておきます。
899 | `uimm7_lsb0` と `simm11_lsb0` がそれに当たります。
900 | 後々、{cpp}コードにてこの制限が守られているかをチェックします。
901 |
902 | 次いでこれらのアセンブリをパーズできるように `CAHPAsmParser` に手を加えます。
903 | `CAHPAsmParser::parseMemOpBaseReg` メンバ関数を定義してメモリ指定のアセンブリである
904 | `即値(レジスタ)` という形を読み込めるようにし、これを `CAHPAsmParser::parseOperand` から
905 | 呼び出します。
906 |
907 | 最後にテストを書きます。
908 |
909 | === フィールドを詳細に指定する
910 |
911 | https://github.com/virtualsecureplatform/llvm-cahp/commit/1963e0288a450c3785723861c7c5d5c7280186fc[1963e0288a450c3785723861c7c5d5c7280186fc]
912 |
913 | 各命令がどのような特性を持つかをTableGenで指定します。
914 | この情報はコード生成の際に使用されます。
915 | これらのフィールドは `llvm/include/llvm/Target/Target.td`
916 | にてコメントとともに定義されています。
917 |
918 | 以下に主要なフィールドについて説明します。
919 | // FIXME: 要修正:DefsとかisCommutableとかhasSideEffectsが非直感的。
920 |
921 | // FIXME: 要修正:もうちょっと詳しく書く。
922 |
923 | === ディスアセンブラを実装する
924 |
925 | https://github.com/virtualsecureplatform/llvm-cahp/commit/01fdfc0e1a5281527e339913ee08cb0da9d75f46[01fdfc0e1a5281527e339913ee08cb0da9d75f46]
926 |
927 | <>を参考にしてディスアセンブラを実装します。
928 | `Disassembler` ディレクトリを作成して `Disassembler/CAHPDisassembler.cpp`
929 | を追加・記述します。
930 |
931 | ディスアセンブラの本体は `CAHPDisassembler::getInstruction` です。
932 | ディスアセンブルの処理のほとんどはTableGenが生成する `decodeInstruction` 関数によって
933 | 行われます。CAHPでは24bitの命令と16bitの命令が混在するため、
934 | バイナリ列を解析してどちらの命令かを判断し、 `decodeInstruction` の第一引数に
935 | 渡すテーブルを選びます。
936 |
937 | レジスタのディスアセンブルは `DecodeGPRRegisterClass` にて行います。
938 |
939 | 即値のディスアセンブルは `decodeUImmOperand` と `decodeSImmOperand` にて
940 | 行います。これらの関数は `CAHPInstrInfo.td` にて 即値オペランドの `DecoderMethod` として
941 | 指定します。
942 |
943 | ナイーブに実装すると `lwsp` や `swsp` が入ったバイナリをディスアセンブルしようとしたときに
944 | エラーがでる。これは例えば次のようにして確認することができる。
945 | // FIXME: 要修正:RV32K用になっているのでCAHPに修正。
946 | ....
947 | $ cat test.s
948 | lwsp x11, 0(sp)
949 |
950 | $ bin/llvm-mc -filetype=obj -triple=rv32k < test.s | bin/llvm-objdump -d -
951 | ....
952 | 原因は `lwsp` や `swsp` がアセンブリ上はspというオペランドをとるにも関わらず、
953 | バイナリにはその情報が埋め込まれないためである。このためディスアセンブル時に
954 | オペランドが一つ足りない状態になり、配列の添字チェックに引っかかってしまう。
955 |
956 | これを修正するためには `lwsp` や `swsp` に含まれる即値のDecoderが呼ばれたときをフックし、
957 | `sp` のオペランドが必要ならばこれを補えばよいfootnote:[この実装手法はRISC Vのそれによる。かなりad-hocだと感じるが、他の方法が分からないのでとりあえず真似る。]。
958 | この関数を `addImplySP` という名前で実装する。ここで即値をオペランドに追加するために呼ぶ
959 | `Inst.addOperand` と `addImplySP` の呼び出しの順序に注意が必要である。
960 | すなわち `LWSP` を `CAHPInstrInfo.td` で定義したときのオペランドの順序で呼ばなければ
961 | `lwsp x11, sp(0)` のようなおかしなアセンブリが生成されてしまう。
962 |
963 | [NOTE]
964 | ====
965 | ちなみにエンコード方式にコンフリクトがある場合はビルド時に教えてくれる。
966 |
967 | ....
968 | Decoding Conflict:
969 | 111...........01
970 | 111.............
971 | ................
972 | BNEZ 111___________01
973 | BNEZhoge 111___________01
974 | ....
975 | // FIXME: 要修正:BNEZはRV32Kv1のもの
976 |
977 | これを防ぐためには、もちろん異なるエンコード方式を指定すればよいのだが、
978 | 他にディスアセンブル時に命令を無効化する方法としてTableGenファイルで
979 | `isPseudo = 1` を指定して疑似命令にしたり
980 | `isCodeGen = 1` を指定してコード生成時にのみ効力を持つ
981 | 命令にすることなどができる。
982 |
983 | ====
984 |
985 | === relocationとfixupに対応する
986 |
987 | https://github.com/virtualsecureplatform/llvm-cahp/commit/a03e70e9157510937ca522f14ca0c64c61d47ca7[a03e70e9157510937ca522f14ca0c64c61d47ca7]
988 |
989 | ワンパスでは決められない値についてあとから補うための機構であるfixupと、
990 | コンパイル時には決定できない値に対してリンカにその処理を任せるためのrelocationについて
991 | 対応する。参考にするパッチは<>。
992 |
993 | 必要な作業は大きく分けて次の通り。
994 | * Fixupの種類とその内容を定義する。
995 | * Fixupを適用する関数を定義する。
996 | * アセンブラがFixupを生成するように改変する。
997 | * Fixupが解決されないまま最後まで残る場合は、これをrelocationに変換する。
998 |
999 | === `%hi` と `%lo` に対応する
1000 | === `li a0, foo` をエラーにする
1001 | === llvm-objdump の調査
1002 | === `hlt` 疑似命令を追加する
1003 |
1004 | == コード生成部を作る
1005 |
1006 | === コンパイラのスケルトンを作成する
1007 | === 基本的な演算に対応する
1008 | === 定数の実体化に対応する
1009 | === メモリ演算に対応する
1010 | === relocationに対応する
1011 | === 条件分岐に対応する
1012 | === 関数呼び出しに対応する
1013 | === 関数プロローグ・エピローグを実装する
1014 | === frame pointer eliminationを実装する
1015 | === `select` に対応する
1016 | === `FrameIndex` をlowerする。
1017 | === 大きなスタックフレームに対応する
1018 | === `SETCC` に対応する
1019 | === `ExternalSymbol` に対応する
1020 | === jump tableを無効化する
1021 | === インラインアセンブリに対応する
1022 | === fastccに対応する
1023 |
1024 | == Cコンパイラに仕立てる
1025 |
1026 | === LLDにCAHPバックエンドを追加する
1027 | === ClangをCAHPに対応させる
1028 | === `crt0.o` と `cahp.lds` の導入
1029 | === `--nmagic` の有効化
1030 | === libcの有効化
1031 |
1032 | == まともなコードを生成する
1033 |
1034 | === 分岐解析に対応する
1035 | === branch relaxationに対応する
1036 | === 16bit命令を活用する
1037 | === `jal` を活用する
1038 | === 命令スケジューリングを設定する
1039 | === 末尾再帰に対応する
1040 |
1041 | == 落ち穂拾い
1042 |
1043 | === スタックを利用した引数渡し
1044 | === `byval` の対応
1045 | === 動的なスタック領域確保に対応する
1046 | === emergency spillに対応する
1047 | === 可変長引数関数に対応する
1048 | === 単体の `sext/zext/trunc` に対応する
1049 | === 乗算に対応する
1050 | === 除算・剰余に対応する
1051 | === `frameaddr/returnaddr` に対応する
1052 | === `ROTL/ROTR/BSWAP/CTTZ/CTLZ/CTPOP` に対応する
1053 | === 32bitのシフトに対応する
1054 | === 間接ジャンプに対応する
1055 | === `BlockAddress` のlowerに対応する
1056 |
1057 | include::ref.adoc[]
1058 |
--------------------------------------------------------------------------------
/ref.adoc:
--------------------------------------------------------------------------------
1 | [bibliography]
2 | == 参考文献
3 |
4 | - [[[github_riscv-llvm_docs_01,1]]] https://github.com/lowRISC/riscv-llvm/blob/master/docs/01-intro-and-building-llvm.mkd
5 | - [[[llvm_getting-started,2]]] https://llvm.org/docs/GettingStarted.html
6 | - [[[clang_gettings-started,3]]] https://clang.llvm.org/get_started.html
7 | - [[[asciidoctor_user-manual,4]]] https://asciidoctor.org/docs/user-manual/
8 | - [[[riscv,5]]] https://riscv.org/
9 | - [[[riscv_specifications,6]]] https://riscv.org/specifications/
10 | - [[[fox-llvm,7]]] 『きつねさんでもわかるLLVM〜コンパイラを自作するためのガイドブック〜』(柏木 餅子・風薬・矢上 栄一、株式会社インプレス、2013年)
11 | - [[[github_riscv-llvm_docs_02,8]]] https://github.com/lowRISC/riscv-llvm/blob/master/docs/02-starting-the-backend.mkd
12 | - [[[github_riscv-llvm_patch_02,9]]] https://github.com/lowRISC/riscv-llvm/blob/master/0002-RISCV-Recognise-riscv32-and-riscv64-in-triple-parsin.patch
13 | - [[[github_riscv-llvm,10]]] https://github.com/lowRISC/riscv-llvm
14 | - [[[youtube_llvm-backend-development-by-example,11]]] https://www.youtube.com/watch?v=AFaIP-dF-RA
15 | - [[[msyksphinz_try-riscv64-llvm-backend,12]]] http://msyksphinz.hatenablog.com/entry/2019/01/02/040000_1
16 | - [[[github_riscv-llvm_patch_03,13]]] https://github.com/lowRISC/riscv-llvm/blob/master/0003-RISCV-Add-RISC-V-ELF-defines.patch
17 | - [[[github_riscv-llvm_patch_04,14]]] https://github.com/lowRISC/riscv-llvm/blob/master/0004-RISCV-Add-stub-backend.patch
18 | - [[[github_riscv-llvm_patch_06,15]]] https://github.com/lowRISC/riscv-llvm/blob/master/0006-RISCV-Add-bare-bones-RISC-V-MCTargetDesc.patch
19 | - [[[github_riscv-llvm_patch_10,16]]] https://github.com/lowRISC/riscv-llvm/blob/master/0010-RISCV-Add-support-for-disassembly.patch
20 | - [[[llvm-writing_backend-operand_mapping,17]]] https://llvm.org/docs/WritingAnLLVMBackend.html#instruction-operand-mapping
21 | - [[[llvm-writing_backend,18]]] https://llvm.org/docs/WritingAnLLVMBackend.html
22 | - [[[github_riscv-llvm_patch_07,19]]] https://github.com/lowRISC/riscv-llvm/blob/master/0007-RISCV-Add-basic-RISCVAsmParser.patch
23 | - [[[github_riscv-llvm_patch_08,20]]] https://github.com/lowRISC/riscv-llvm/blob/master/0008-RISCV-Add-RISCVInstPrinter-and-basic-MC-assembler-te.patch
24 | - [[[llvm-tablegen,21]]] https://llvm.org/docs/TableGen/index.html
25 | - [[[github_riscv-llvm_patch_09,22]]] https://github.com/lowRISC/riscv-llvm/blob/master/0009-RISCV-Add-support-for-all-RV32I-instructions.patch
26 | - [[[llvm_dev_ml-tablegen_definition_question,23]]] http://lists.llvm.org/pipermail/llvm-dev/2015-December/093310.html
27 | - [[[llvm_doxygen-twine,24]]] https://llvm.org/doxygen/classllvm_1_1Twine.html
28 | - [[[llvm-tablegen-langref,25]]] https://llvm.org/docs/TableGen/LangRef.html
29 | - [[[github_riscv-llvm_docs_05,26]]] https://github.com/lowRISC/riscv-llvm/blob/master/docs/05-disassembly.mkd
30 | - [[[github_riscv-llvm_patch_11,27]]] https://github.com/lowRISC/riscv-llvm/blob/master/0011-RISCV-Add-common-fixups-and-relocations.patch
31 | - [[[github_riscv-llvm_docs_06,28]]] https://github.com/lowRISC/riscv-llvm/blob/master/docs/06-relocations-and-fixups.mkd
32 | - [[[github_riscv-llvm_patch_13,29]]] https://github.com/lowRISC/riscv-llvm/blob/master/0013-RISCV-Initial-codegen-support-for-ALU-operations.patch
33 | - [[[speakerdeck-llvm_backend_development,30]]] https://speakerdeck.com/asb/llvm-backend-development-by-example-risc-v
34 | - [[[llvm-code_generator,31]]] https://llvm.org/docs/CodeGenerator.html
35 | - [[[llvm-code_generator-target_independent_code_gen_alg,32]]] https://llvm.org/docs/CodeGenerator.html#target-independent-code-generation-algorithms
36 | - [[[llvm-code_generator-selectiondag_instruction_selection,33]]] https://llvm.org/docs/CodeGenerator.html#selectiondag-instruction-selection-process
37 | - [[[github_riscv-llvm_patch_15,34]]] https://github.com/lowRISC/riscv-llvm/blob/master/0015-RISCV-Codegen-support-for-memory-operations.patch
38 | - [[[cpu0,35]]] https://jonathan2251.github.io/lbd/
39 | - [[[elvm-llvm_backend,36]]] https://github.com/shinh/llvm/tree/elvm
40 | - [[[elvm-slide,37]]] http://shinh.skr.jp/slide/llel/000.html
41 | - [[[github_riscv-llvm_patch_16,38]]] https://github.com/lowRISC/riscv-llvm/blob/master/0016-RISCV-Codegen-support-for-memory-operations-on-globa.patch
42 | - [[[github_riscv-llvm_patch_17,39]]] https://github.com/lowRISC/riscv-llvm/blob/master/0017-RISCV-Codegen-for-conditional-branches.patch
43 | - [[[todai_llvm_backend,40]]] https://github.com/cpu-experiment-2018-2/llvm/tree/master/lib/Target/ELMO
44 | - [[[todai_llvm_backend-article,41]]] http://uenoku.hatenablog.com/entry/2018/12/25/044244
45 | - [[[github_riscv-llvm_patch_18,42]]] https://github.com/lowRISC/riscv-llvm/blob/master/0018-RISCV-Support-for-function-calls.patch
46 | - [[[llvm-langref,43]]] http://llvm.org/docs/LangRef.html
47 | - [[[fpga_develop_diary,44]]] http://msyksphinz.hatenablog.com/
48 | - [[[llvm-anton_korobeynikov_2012,45]]] https://llvm.org/devmtg/2012-04-12/Slides/Workshops/Anton_Korobeynikov.pdf
49 | - [[[llvm-welcome_to_the_back_end_2017,46]]] https://www.youtube.com/watch?v=objxlZg01D0
50 | - [[[ean10-howto-llvmas,47]]] https://www.embecosm.com/appnotes/ean10/ean10-howto-llvmas-1.0.html
51 | - [[[lowrisc-devmtg18,48]]] https://www.lowrisc.org/llvm/devmtg18/
52 | - [[[LLVMBackend_2015_03_26_v2,49]]] http://www.inf.ed.ac.uk/teaching/courses/ct/other/LLVMBackend-2015-03-26_v2.pdf
53 | - [[[rui-compilerbook,50]]] https://www.sigbus.info/compilerbook
54 | - [[[krister-writing_gcc_backend,51]]] https://kristerw.blogspot.com/2017/08/writing-gcc-backend_4.html
55 | - [[[llvm-ml-129089,52]]] http://lists.llvm.org/pipermail/llvm-dev/2019-January/129089.html
56 | - [[[llvm-langref-datalayout,53]]] https://llvm.org/docs/LangRef.html#langref-datalayout
57 | - [[[github-frasercrmck_llvm_leg,54]]] https://github.com/frasercrmck/llvm-leg/tree/master/lib/Target/LEG
58 | - [[[llvm_doxygen-InitMCRegisterInfo,55]]] https://llvm.org/doxygen/classllvm_1_1MCRegisterInfo.html#a989859615fcb74989b4f978c4d227a03
59 | - [[[llvm-programmers_manual,56]]] http://llvm.org/docs/ProgrammersManual.html
60 | - [[[llvm-writing_backend-calling_conventions,57]]] https://llvm.org/docs/WritingAnLLVMBackend.html#calling-conventions
61 | - [[[riscv-calling,58]]] https://riscv.org/wp-content/uploads/2015/01/riscv-calling.pdf
62 | - [[[llvm_dev_ml-how_to_debug_instruction_selection,59]]] http://lists.llvm.org/pipermail/llvm-dev/2017-August/116501.html
63 | - [[[fpga_develop_diary-20190612040000,60]]] http://msyksphinz.hatenablog.com/entry/2019/06/12/040000
64 | - [[[llvm_dev_ml-br_cc_questions,61]]] http://lists.llvm.org/pipermail/llvm-dev/2014-August/075303.html
65 | - [[[llvm_dev_ml-multiple_result_instrs,62]]] https://groups.google.com/forum/#!topic/llvm-dev/8kPOj-_lbGk
66 | - [[[stackoverflow-frame_lowering,63]]] https://stackoverflow.com/questions/32872946/what-is-stack-frame-lowering-in-llvm
67 | - [[[llvm_dev_ml-selecting_frame_index,64]]] https://groups.google.com/d/msg/llvm-dev/QXwtqgau-jA/PwnHDF0gG_oJ
68 | - [[[fpga_develop_diary-llvm,65]]] https://github.com/msyksphinz/llvm/tree/myriscvx/impl90/lib/Target/MYRISCVX
69 | - [[[llvm-github_cd44ae,66]]] https://github.com/llvm/llvm-project/commit/cd44aee3da22f9a618f2e63c226bebf615fa8cf8
70 | - [[[llvm_phabricator-d43752,67]]] https://reviews.llvm.org/D43752
71 | - [[[llvm-compilerwriterinfo,68]]] https://llvm.org/docs/CompilerWriterInfo.html
72 | - [[[wikipedia-The_Gleaners,69]]] https://en.wikipedia.org/wiki/The_Gleaners
73 | - [[[github_riscv-llvm_patch_20,70]]] https://github.com/lowRISC/riscv-llvm/blob/master/0020-RISCV-Support-and-tests-for-a-variety-of-additional-.patch
74 | - [[[llvm_phabricator-d47422,71]]] https://reviews.llvm.org/D47422
75 | - [[[llvm-extendingllvm,72]]] https://llvm.org/docs/ExtendingLLVM.html
76 | - [[[llvm_dev_ml-001264,73]]] http://lists.llvm.org/pipermail/llvm-dev/2004-June/001264.html
77 | - [[[llvm_phabricator-d42958,74]]] https://reviews.llvm.org/D42958
78 | - [[[compiler_rt,75]]] https://compiler-rt.llvm.org/
79 | - [[[github-riscv_compiler_rt,76]]] https://github.com/andestech/riscv-compiler-rt
80 | - [[[github_riscv-llvm_patch_27,77]]] https://github.com/lowRISC/riscv-llvm/blob/master/0027-RISCV-Support-stack-frames-and-offsets-up-to-32-bits.patch
81 | - [[[llvm_phabricator-d44885,78]]] https://reviews.llvm.org/D44885
82 | - [[[llvm_phabricator-d45859,79]]] https://reviews.llvm.org/D45859
83 | - [[[llvm-langref-poison_value,80]]] http://llvm.org/docs/LangRef.html#poisonvalues
84 | - [[[github-emscripten-issues-34,81]]] https://github.com/emscripten-core/emscripten/issues/34
85 | - [[[switch_lowering_in_llvm,82]]] http://fileadmin.cs.lth.se/cs/education/edan75/part2.pdf
86 | - [[[github-avr_llvm-issues-88,83]]] https://github.com/avr-llvm/llvm/issues/88
87 | - [[[asciidoctor-quickref,84]]] https://asciidoctor.org/docs/asciidoc-syntax-quick-reference/
88 | - [[[llvm_phabricator-d56351,85]]] https://reviews.llvm.org/D56351
89 | - [[[hatenablog-rhysd-230119,86]]] https://rhysd.hatenablog.com/entry/2017/03/13/230119
90 | - [[[llvm_dev_ml-115805,87]]] http://lists.llvm.org/pipermail/llvm-dev/2017-July/115805.html
91 | - [[[github_riscv-llvm_patch_29,88]]] https://github.com/lowRISC/riscv-llvm/blob/master/0029-RISCV-Add-support-for-llvm.-frameaddress-returnaddre.patch
92 | - [[[github-riscv_llvm-clang,89]]] https://github.com/lowRISC/riscv-llvm/tree/master/clang
93 | - [[[github-elvm_clang,90]]] https://github.com/shinh/clang/tree/elvm
94 | - [[[github_riscv-llvm_patch_22,91]]] https://github.com/lowRISC/riscv-llvm/blob/master/0022-RISCV-Support-lowering-FrameIndex.patch
95 | - [[[llvm_dev_ml-087879,92]]] http://lists.llvm.org/pipermail/llvm-dev/2015-July/087879.html
96 | - [[[stackoverflow-27467293,93]]] https://stackoverflow.com/questions/27467293/how-to-force-clang-use-llvm-assembler-instead-of-system
97 | - [[[github-riscv_llvm-clang-03,94]]] https://github.com/lowRISC/riscv-llvm/blob/master/clang/0003-RISCV-Implement-clang-driver-for-the-baremetal-RISCV.patch
98 | - [[[github_riscv-llvm_patch_25,95]]] https://github.com/lowRISC/riscv-llvm/blob/master/0025-RISCV-Add-custom-CC_RISCV-calling-convention-and-imp.patch
99 | - [[[llvm_dev_ml-106187,96]]] http://lists.llvm.org/pipermail/llvm-dev/2016-October/106187.html
100 | - [[[llvm_phabricator-d39322,97]]] https://reviews.llvm.org/D39322
101 | - [[[cpu0-lld,98]]] http://jonathan2251.github.io/lbt/lld.html
102 | - [[[youtube-how_to_add_a_new_target_to_lld,99]]] https://www.youtube.com/watch?v=FIXaeRU31Ww
103 | - [[[llvm-smith_newlldtargetpdf,100]]] https://llvm.org/devmtg/2016-09/slides/Smith-NewLLDTarget.pdf
104 | - [[[llvm-lld,101]]] https://lld.llvm.org/index.html
105 | - [[[note-n9948f0cc3ed3,102]]] https://note.mu/ruiu/n/n9948f0cc3ed3
106 | - [[[lanai-isa,103]]] https://docs.google.com/document/d/1jwAc-Rbw1Mn7Dbn2oEB3-0FQNOwqNPslZa-NDy8wGRo/pub
107 | - [[[github-blog_os-issues-370,104]]] https://github.com/phil-opp/blog_os/issues/370
108 | - [[[llvm_phabricator-d61688,105]]] https://reviews.llvm.org/D61688
109 | - [[[man-xtensa_linux_gnu_ld,106]]] https://linux.die.net/man/1/xtensa-linux-gnu-ld
110 | - [[[man-elf,107]]] https://linuxjm.osdn.jp/html/LDP_man-pages/man5/elf.5.html
111 | - [[[llvm_phabricator-d45385,108]]] https://reviews.llvm.org/D45385
112 | - [[[llvm_phabricator-d47882,109]]] https://reviews.llvm.org/D47882
113 | - [[[llvm_dev_ml-128257,110]]] https://lists.llvm.org/pipermail/llvm-dev/2018-December/128257.html
114 | - [[[github_riscv-llvm_patch_31,111]]] https://github.com/lowRISC/riscv-llvm/blob/master/0031-RISCV-Implement-support-for-the-BranchRelaxation-pas.patch
115 | - [[[github_riscv-llvm_patch_30,112]]] https://github.com/lowRISC/riscv-llvm/blob/master/0030-RISCV-Implement-branch-analysis.patch
116 | - [[[stackoverflow-5789806,113]]] https://stackoverflow.com/questions/5789806/meaning-of-and-in-c
117 | - [[[compiler_study_report,114]]] https://proc-cpuinfo.fixstars.com/2018/11/compiler_study_report/
118 | - [[[github-llvm-bcb36be8e3f5dced36710ba1a2e2206071ccc7ba,115]]] https://github.com/llvm/llvm-project/commit/bcb36be8e3f5dced36710ba1a2e2206071ccc7ba
119 | - [[[llvm_dev_ml-059799,116]]] http://lists.llvm.org/pipermail/llvm-dev/2013-February/059799.html
120 | - [[[tricore-llvm-slides,117]]] https://reup.dmcs.pl/wiki/images/7/7a/Tricore-llvm-slides.pdf
121 | - [[[tricore-llvm,118]]] https://opus4.kobv.de/opus4-fau/files/1108/tricore_llvm.pdf
122 | - [[[llvm_dev_ml-111697,119]]] http://lists.llvm.org/pipermail/llvm-dev/2017-April/111697.html
123 | - [[[takayuki-no09,120]]] http://www.ertl.jp/~takayuki/readings/c/no09.html
124 | - [[[hwenginner-linker,121]]] https://hwengineer.github.io/linker/
125 | - [[[koikikukan-000300,122]]] http://www.koikikukan.com/archives/2017/04/05-000300.php
126 | - [[[stackoverflow-57735654_34997577,123]]] https://stackoverflow.com/questions/34997577/linker-script-allocation-of-bss-section#comment57735654_34997577
127 | - [[[redhat-ld_simple_example,124]]] https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/4/html/Using_ld_the_GNU_Linker/simple-example.html
128 | - [[[llvm_phabricator-d45395,125]]] https://reviews.llvm.org/D45395
129 | - [[[llvm_phabricator-d45395-398662,126]]] https://reviews.llvm.org/D45395#inline-398662
130 | - [[[llvm-langref-inline_asm,127]]] http://llvm.org/docs/LangRef.html#inline-assembler-expressions
131 | - [[[hazymoon-gcc_inline_asm,128]]] http://caspar.hazymoon.jp/OpenBSD/annex/gcc_inline_asm.html
132 | - [[[github_riscv-llvm_patch_28,129]]] https://github.com/lowRISC/riscv-llvm/blob/master/0028-RISCV-Add-basic-support-for-inline-asm-constraints.patch
133 | - [[[llvm-langref-inline_asm-asm_template_argument_modifier,130]]] http://llvm.org/docs/LangRef.html#asm-template-argument-modifiers
134 | - [[[github-llvm-0715d35ed5ac2312951976bee2a0d2587f98f39f,131]]] https://github.com/llvm/llvm-project/commit/0715d35ed5ac2312951976bee2a0d2587f98f39f
135 | - [[[github_riscv-llvm_patch_32,132]]] https://github.com/lowRISC/riscv-llvm/blob/master/0032-RISCV-Reserve-an-emergency-spill-slot-for-the-regist.patch
136 | - [[[github_riscv-llvm_patch_26,133]]] https://github.com/lowRISC/riscv-llvm/blob/master/0026-RISCV-Support-for-varargs.patch
137 | - [[[github-fracture-wiki-how-dagisel-works,134]]] https://github.com/draperlaboratory/fracture/wiki/How-TableGen%27s-DAGISel-Backend-Works
138 | - [[[welcome_to_the_back_end-slides,135]]] http://llvm.org/devmtg/2017-10/slides/Braun-Welcome%20to%20the%20Back%20End.pdf
139 | - [[[life_of_an_instruction,136]]] https://eli.thegreenplace.net/2012/11/24/life-of-an-instruction-in-llvm/
140 | - [[[shinh-blog-010637,137]]] http://shinh.hatenablog.com/entry/2014/10/03/010637
141 | - [[[llvm_backend_intro,138]]] https://www.slideshare.net/AkiraMaruoka/llvm-backend
142 | - [[[amazon-llvm_cookbook-customer_review,139]]] https://www.amazon.co.jp/dp/178528598X#customer_review-R28L2NAL8T9M2H
143 | - [[[llvm_dev_ml-117139,140]]] https://lists.llvm.org/pipermail/llvm-dev/2017-September/117139.html
144 | - [[[github_riscv-llvm_patch_85,141]]] https://github.com/lowRISC/riscv-llvm/blob/master/0085-RISCV-Set-AllowRegisterRenaming-1.patch
145 | - [[[llvm_dev_ml-135337,142]]] https://lists.llvm.org/pipermail/llvm-dev/2019-September/135337.html
146 | - [[[wikipedia-weak_symbol,143]]] https://en.wikipedia.org/wiki/Weak_symbol
147 | - [[[wikipedia-remat,144]]] https://en.wikipedia.org/wiki/Rematerialization
148 | - [[[llvm_phabricator-d46182,145]]] https://reviews.llvm.org/D46182
149 | - [[[nakata-compiler,146]]] 『コンパイラの構成と最適化(第2版)』(中田育男、朝倉書店、2009)
150 | - [[[fpga_develop_diary-to_llvm9,147]]] http://msyksphinz.hatenablog.com/entry/2019/08/17/040000
151 | - [[[llvm_phabricator-d60488,148]]] https://reviews.llvm.org/D60488
152 | - [[[llvm_phabricator-rl364191,149]]] https://reviews.llvm.org/rL364191
153 | - [[[llvm_phabricator-d64121,150]]] https://reviews.llvm.org/D64121
154 | - [[[llvm-codingstandards,151]]] https://llvm.org/docs/CodingStandards.html
155 | - [[[llvm_dev_ml-134921,152]]] https://lists.llvm.org/pipermail/llvm-dev/2019-September/134921.html
156 | - [[[llvm_phabricator-d43256,153]]] https://reviews.llvm.org/D43256
157 | - [[[llvm_dev_ml-114675,154]]] http://lists.llvm.org/pipermail/llvm-dev/2017-June/114675.html
158 | - [[[llvm_phabricator-d42780,155]]] https://reviews.llvm.org/D42780
159 | - [[[llvm_phabricator-d51732,156]]] https://reviews.llvm.org/D51732
160 | - [[[llvm_devmtg-schedmachinemodel,157]]] http://llvm.org/devmtg/2014-10/Slides/Estes-MISchedulerTutorial.pdf
161 | - [[[llvm_dev_ml-098535,158]]] https://lists.llvm.org/pipermail/llvm-dev/2016-April/098535.html
162 | - [[[llvm_devmtg-writinggreatsched,159]]] https://www.youtube.com/watch?v=brpomKUynEA
163 | - [[[anandtech-11441,160]]] https://www.anandtech.com/show/11441/dynamiq-and-arms-new-cpus-cortex-a75-a55/4
164 | - [[[llvm_devmtg-larintrick,161]]] https://llvm.org/devmtg/2012-11/Larin-Trick-Scheduling.pdf
165 | - [[[llvm-schedinorder,162]]] https://llvm.org/devmtg/2016-09/slides/Absar-SchedulingInOrder.pdf
166 | - [[[llvm_2003f,163]]] http://na2co3.exp.jp/llvm-2003f-fib-trace.html
167 | - [[[msyksphinz_github_io-llvm,164]]] https://msyksphinz.github.io/github_pages/llvm/
168 | - [[[llvm_phabricator-d86505,165]]] https://reviews.llvm.org/D86505
169 | - [[[llvm_phabricator-d86269,166]]] https://reviews.llvm.org/D86269
170 | - [[[github_csky,167]]] https://github.com/c-sky
171 | - [[[llvm_phabricator-d69103,168]]] https://reviews.llvm.org/D69103
172 | - [[[llvm_phabricator-rG9218ff50f93085d0a16a974db28ca8f14bc66f64,169]]] https://reviews.llvm.org/rG9218ff50f93085d0a16a974db28ca8f14bc66f64
173 | - [[[llvm_phabricator-d95315,170]]] https://reviews.llvm.org/D95315
174 | - [[[github_llvm_00ecf67045231743ef58950f3d3f4fbe450b8e0a,171]]] https://github.com/llvm/llvm-project/commit/00ecf67045231743ef58950f3d3f4fbe450b8e0a
175 |
--------------------------------------------------------------------------------