generated at
hcc

Cコンパイラの作り方


Haskell実装のCコンパイラ
学びたいこと
Haskellに慣れる
というか関数型言語に慣れる
Rytlは関数型言語なので
アセンブリ言語
最適化
サブプロジェクト

方針
9ccとtigerが軸


実行の仕方
haskellコードを編集後、実際にアセンブリを出力してから実行する場合
$ docker build -t hcc .
$ docker run -v "$PWD"/.:/home --rm -ti hcc
起動しておく
$ stack build
$ stack exec hcc-exe "(3 + 4) - 3 * 5 / 4;" > tmp.s
セミコロンは必須だよ!!!!
# gcc -g main.c tmp.s -o tmp
# ./tmp
# echo $?
gdbを使ってアセンブリのデバッグをする
上の # gcc -g main.c tmp.s -o tmp を実行するところまでは同じ
# gdb ./tmp でgdbを起動する
ここからさきはgdbの使い方
Haskell上でASTを確認する場合
src 内でghciを起動して、 :l Parser.hs で読み込む
run program "1+2" で実行するとASTが得られる
ちなみに :r でリロード
テスト
parserレベルのテスト
$ stack test
アセンブリレベルのテスト
$ stack build
ホスト側
$ cd test/tests
$ ./test.sh
ゲスト側
$ ./test.sh


DockerのHaskellのイメージがでかすぎるので、断念
↑これはtest.shの実行に影響する
つまり、test.shの中で $ runhaskell main.hs 42 > tmp.s のようなものが実行できない
なので、テストのやり方を自分で考えないといけない
具体的には、コード内で単体テストを行う

参考
HaskellとLLVMでJITコンパイラを作る



メモ
Parsecを使うことにした
『プログラミングHaskell』とかを読んで、再実装のノリがわかったから
ASTを作れるようになったので、ここからAsmに変換する
BNFの書き方
関数型言語で実装するか、手続き型で実装するかで、適切な表記法が変わる?
前者の場合再帰で
後者の場合クリーネ閉包 *


todo
型をもう少し分類したい
parserはもっと洗練できる
chainl1を理解しよう
Either Monadを導入しよう
右結合を解消しよう
gen関数をもっと洗練しよう
重複が多い
テストケースを増やす
エラー処理をしたい
Exceptモナドを使う
ハードコーディングしてるので修正
Parsecがエラーを吐いてもtmp.sに書き込まれるの意味ないのでやめたい
assignの左結合か右結合かみたいなところをチェック
リターンアドレス、ベースポインタの復習
★READMEへの追記
features
四則演算
local var
など
テストの仕方
description
C Compiler in Haskell with Parsec的な
genの方もテストしたい..
'3;'のようにただの数値の時おかしい
NG時のテスト
codeGenのリファクタ!!!!!!
codeGenのfor文のNop, 0, 1のときの処理

↓これの「最高のMonadチュートリアル」のところから読む


↓この辺、どっか別の場所に移す
CPUはスタックのてっぺん以外の値にもアクセスすることが出来る
ストアもロードも出来る
ロードする時
s
mov dst, [src]
srcのレジスタの値をアドレスとみなして値をロード
そのロードした値をdstに入れる
ex
raxに0x8000という数値が入っている場合を考える
s
mov rdi, [rax]
raxに入っているアドレス値、ここでは0x8000のアドレスに入っている値をrdiに入れる
ストアする時
ロードの逆
s
mov [dst], src
dstに入っている数値をアドレスとみなして、そこにsrcの値を入れる
pushやpopはこの構文のエイリアスみたいなものである
s
// この2つは同じ意味 pop rax mov rax, [rsp] add rsp, 8
s
// この2つは同じ意味 push rax sub rsp, 8 mov [rsp], rax



ステップ13: ブロック
ステップ14: 関数の呼び出しに対応する
ステップ15: 関数の定義に対応する
バイナリレベルのインターフェイス
コンピュータにおける整数の表現
符号なし整数
符号あり整数
符号拡張
符号の反転
ポインタと文字列リテラル
ステップ16: 単項&と単項*
ステップ17: 暗黙の変数定義を廃止して、intというキーワードを導入する
ステップ18: ポインタ型を導入する
ポインタを表す型を定義する
ポインタが指している値に代入する
ステップ19: ポインタの加算と減算を実装する
ステップ20: sizeof演算子
ステップ21: 配列を実装する
配列型を定義する
配列からポインタへの暗黙の型変換を実装する
ステップ22: 配列の添字を実装する
ステップ23: グローバル変数を実装する
ステップ24: 文字型を実装する
ステップ25: 文字列リテラルを実装する
ステップ26: 入力をファイルから読む
ステップ27: 行コメントとブロックコメント
ステップ28: テストをCで書き直す
プログラムの実行イメージと初期化式
実行ファイルの構造
データセグメントの内容
初期化式の文法
グローバル変数の初期化式
ローカル変数の初期化式
ステップ29以降
Cの型の構文
型を表す図
型を表す記法
Cの型の読み方
ネストしていない型の読み方
ネストしている型の読み方
練習問題
おわりに
付録1:x86-64命令セット チートシート
整数レジスタの一覧
メモリアクセス
関数呼び出し
条件分岐
条件代入
整数・論理演算
付録2:Gitによるバージョン管理
Gitを使ったワークフロー
コミットするときの注意点
Gitの内部構造
参考資料
索引