C言語コンパイラ作成演習
京大の資料で、C言語コンパイラを作る実習、ってのがあったんだけど、これどのくらいの期間で
作らなきゃいけないんだろうか。大学の他の勉強もやりながら1ヶ月くらいで組む、といった所か。
う〜ん、うちの大学の情報系学科も、このくらいやらせられるんだろうなぁ。ついていけない
気がしてきた。
LLVMは万能の杖か?
LLVMでOSECPUを吐かせようとこの一ヶ月頑張ってきたのだけど、結論から言うと、諦める事にした。
LLVMでOSECPUを吐く事は技術的には可能だと思うから、やりたい人がいれば是非チャレンジしてもらいたい。
だけど、僕はLLVMでOSECPUを吐く事に対する価値を見出せなくなった。
言い訳がましいが、簡単に理由を書いておこう。
・ライブラリどうすんの?
C言語はまだ良い。C++って、継承や例外をサポートするためにコンパイラ側で沢山コード用意しなきゃ
いけないよね?それ、自力で書かなきゃいけないの?タルイ。C++はいいさ。僕も使うから。だけど僕が
使いもしない言語をサポートするためのコードを書くモチベなんてない。そしてそれはLLVMの長所を殺す
事になるよね。
・LLVMデカイ
仮にLLVMでOSECPUが吐けたとしよう。しかし、それはWindowsやMacといった他のOSの助けを借りて吐く物だ。
もちろん、OSECPUだってそうだろう。だけど、OSECPUはやろうと思えば自作OS上にすぐ移植できる。
(実際にやった。動いた)
だけど、LLVM自体をOSECPUアプリにするのは相当骨が折れると思う。規模がデカイから、修正しなきゃいけない
コードも膨大すぎるだろうね。
「別にLLVMをOSECPUアプリにする必要なんてない。そんな事よりいろんな言語でOSECPUアプリを書きたいんだ!」
という人はそれで結構。そういう人は是非LLVMにチャレンジして欲しい。だけど、僕はOSECPUを使って一つ
の世界を作りたい、自作OSのような、閉じた世界を作りたいわけ。セキュアな世界だから、エデンとでも
呼んでおこうか。だけど、そのためには、コンパイラくらいOSECPUアプリとして存在しなきゃいけないんだ。
そういう点ではLLVMは失格。
ここは強調しておきたい所なんだけど、僕がLLVMを諦めたのは、僕の考え方と合わなかったから。所謂、音楽性
の違いって奴かね?だから、技術的には、やっぱりLLVMでOSECPUアプリを吐くのは可能だと思う。
だから、もし今後これにチャレンジする人が出て来た時のために、ポインタを残しておこう。
・OSECPUではポインタとデータを完全に区別する。データレジスタにポインタを突っ込む、みたいな事は
許されない。LLVMはポインタとデータをごちゃまぜにして扱っちゃうんだけど、Blackfinというアーキテクチャ
向けのコードではポインタとデータを分けれてる。
・但しllvmのBlackfinのサポートは2.9で終了してる。3.0以降のコードはない。後、clangの3.x系の出力はLLVM
3.x系にしか対応してないし、clang 2.9の出力はllvm 2.9でしか動かない。
・llvm バックエンドについては http://jonathan2251.github.io/lbd/ が詳しい。これを読んである程度LLVMを
理解したら、 http://llvm.org/docs/WritingAnLLVMBackend.html にも目を通しておくと良い。
・OSECPU公式では今の所ポインタをデータ配列につっこむ事はできない。だけど、それだとスタックがどうしても
実装できない。いや、やり方はあるのかもしれないけど、Blackfinでさえまとめて全部同一スタックに突っ込んでる
から、参考資料がない。僕はOSECPUを拡張して、データもポインタも突っ込める汎用コンテナを作った。もちろん、
セキュリティチェックはきちんとやってて、ポインタを入れた場所からデータを読み出そうとするとエラーになる
ようにしてある。これがあればなんとかスタックが動きそうだ。
https://github.com/liva/osecpu/tree/llvm
これが汎用コンテナ実装済みの拡張OSECPU。そのうち消しちゃうかもしれないけど。
・汎用コンテナ使って実装できるのは良いけど、ポインタ使ってホイホイ隣に書き込めちゃうのはマズイよね。
int main() {
int i;
int *p = &i;
++p;
*p = 0x1234;
return 0;
}
配列じゃないのに、++pとかやめて、って感じ。
今の汎用コンテナの実装じゃあこれは防げない。
・ポインタは256bitから64bitにした方が良いよ。上位32bitがポインタ構造体へのアドレス、下位32bitがポインタ自身
が持つアドレス、とかして。LLVMで型が無いから。
・関数呼び出しにおけるレジスタ渡しについては、MIPSのコードを読むと良い。そもそもcpu0のコードってMIPSベース
なんだよね。だけどcpu0はスタックで渡してる。
・最新のcpu0のコードはLLVM側にパッチが当たってない。だからコンパイルができない。
lib/Target/Cpu0LLVMBackendTutorialExampleCode/3.3_src_files_modify/modify/src
を見ながら頑張ってパッチを当てよう。
・メモリ不足に気をつけて。
cygwin64bitでllvmをコンパイル
__deregister_frameが定義されてない参照です〜、みたいなエラーがでるんだよね。
ぐぐったら、こんなのを見つけた。
https://github.com/mono/llvm/pull/1/files
__deregister_frameと__register_frameを参照してる行を削除したらコンパイルは通った。
JIT使わなければ実用上は問題ない・・・と思う。わからんけど。
256ビットCPU
流石に無茶
アドレス演算
OSECPUはメモリアクセスに弱い。
だからOSECPUでスタックアクセスしようとするとこうなる。
.file "t3.bc"
.text
.globl _main
.align 4
.type _main,@function
_main: // @main
// BB#0:
LIMM(r1,-20); //imm32
PADD(p27,p27,r1);
LIMM(r0,0); //imm7
LIMM(r1,3); //imm7
LIMM(r2,16); //imm32
PADD(p0,p27,r2);
SMEM(r0,p0);
LIMM(r0,2); //imm7
LIMM(r2,12); //imm32
PADD(p0,p27,r2);
SMEM(r1,p0);
LIMM(r1,8); //imm32
PADD(p0,p27,r1);
SMEM(r0,p0);
LIMM(r0,12); //imm32
PADD(p0,p27,r0);
LMEM(r0,p0);
LIMM(r1,8); //imm32
PADD(p0,p27,r1);
LMEM(r1,p0);
ADD(r0,r0,r1);
LIMM(r1,4); //imm32
PADD(p0,p27,r1);
SMEM(r0,p0);
LIMM(r0,4); //imm32
PADD(p0,p27,r0);
LMEM(r0,p0);
LIMM(r1,20); //imm32
PADD(p27,p27,r1);
rts;
"`(0":
.size _main, "`(0"-_main
これは流石に酷い。
MIPSのコードを見てみますかね。