C言語コンパイラ作成演習

京大の資料で、C言語コンパイラを作る実習、ってのがあったんだけど、これどのくらいの期間で
作らなきゃいけないんだろうか。大学の他の勉強もやりながら1ヶ月くらいで組む、といった所か。

う〜ん、うちの大学の情報系学科も、このくらいやらせられるんだろうなぁ。ついていけない
気がしてきた。

occ開発メモ

・関数コール時のスタック退避はStage3で


・最適化

  • 四則演算
  • ラベル統合
  • レジスタ流用、統合
  • 条件式変換省略
  • 条件無しラベルジャンプ後は次のラベルまで全消去
  • PALMEM系0x3F最適化(Stage2)

・仕様

  • プロトタイプ宣言の無い関数呼び出しはできない
  • 関数ポインタを普通のポインタに代入する事はできない
  • int *malloc(int size);
  • void free(int *mem);

・既知のバグ

  • 同名の引数とローカル変数を定義できる
  • 関数内での関数定義で、コードがごちゃまぜになるのでは?

・未実装

  • 式内でのポインタのアドレス参照
  • 配列
  • 配列型呼び出し

LLVMは万能の杖か?

LLVMでOSECPUを吐かせようとこの一ヶ月頑張ってきたのだけど、結論から言うと、諦める事にした。
LLVMでOSECPUを吐く事は技術的には可能だと思うから、やりたい人がいれば是非チャレンジしてもらいたい。
だけど、僕はLLVMでOSECPUを吐く事に対する価値を見出せなくなった。

言い訳がましいが、簡単に理由を書いておこう。

・ライブラリどうすんの?
C言語はまだ良い。C++って、継承や例外をサポートするためにコンパイラ側で沢山コード用意しなきゃ
いけないよね?それ、自力で書かなきゃいけないの?タルイ。C++はいいさ。僕も使うから。だけど僕が
使いもしない言語をサポートするためのコードを書くモチベなんてない。そしてそれはLLVMの長所を殺す
事になるよね。

LLVMデカイ
仮にLLVMでOSECPUが吐けたとしよう。しかし、それはWindowsMacといった他の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使わなければ実用上は問題ない・・・と思う。わからんけど。

アドレス演算

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のコードを見てみますかね。