自作Cコンパイラ

自作Cコンパイラでは、構造体を引数、または戻り値に取る関数をコンパイルできるようにしました。まずは構造体を引数として渡す場合です。AMD64のABI規則では、構造体のメンバの状態に応じてメンバごとにレジスタを介して渡したり、スタック(メモリ)を介して渡したりしていました。複雑すぎたため、独自のABI規則を作成することにしました。具体的には1byteを超える引数の場合すべてスタック(メモリ)経由で渡すというものです。下の図は、関数を呼び出し、呼び出された関数がrbpレジスタを退避させ、ローカル変数を格納するために必要なメモリの領域を確保するため、rspレジスタから即値を引いた後のメモリの状態を表したものです。rbpレジスタが指すアドレスから2byte下位のアドレスに1byteより大きな関数の実引数が格納されています。これをスタックに複製し、複製した値を仮引数の変数のメモリにコピーするようにしました。一度スタックを経由して仮引数に代入するようにすることで、比較的にプログラムの修正箇所が少なくてすみました。(最適化の観点からは見るとめちゃくちゃ無駄なのでしょうが…)

/*  1byteより大きな引数がある場合
 *	|          |
 *	+----------+ <-rsp
 *	|          |
 *	|          |
 *	+----------+ <-rbp
 *	| rbp      |
 *	+----------+
 *	| ret addr |
 *	+----------+
 *	| memory   |
 *	| argument |
 *	+----------+
 *	| memory   |
 *	| argument |
 *	|          |
 *	+----------+
 *	| return   | 
 *	| value    |
 *	|          |  
 *	+----------+  
 *	|          |
 */

次に構造体を戻り値とする関数の実装方法について説明します。AMD64のABIでは、レジスタで渡すことのできない戻り値の場合、関数の呼び出し側が戻り値の値を格納するためのメモリ領域を確保し、確保したメモリ領域の先頭アドレスをrdiレジスタを介して、関数を呼び出すようです(間違っていたらすみません。英語読むの苦手なので…)。しかしrdiレジスタは関数の第一引数を渡すのに使用しています。そのため、戻り値がメモリを介す必要がある場合、引数を渡すためのレジスタとしてrdiレジスタを使用してはいけません。しかしそんな場合分けするのは複雑なので、関数の引数同様、独自のABI規則を定義してあげることにしました。具体的には8byteよりも大きな戻り値の場合、本来はrdiレジスタで渡すアドレスの情報を、r12レジスタで渡すと決めました。8byte以下の値の場合は従来どおり、raxレジスタを介して値を渡すようにしました。下の図は呼び出された関数のreturn文が実行されている最中のメモリの状態を表しています。関数が呼ばれると、rbpレジスタをプッシュするタイミングで、r12レジスタもスタックに積むようにしました。return文が実行される直前のときはスタックトップにr12レジスタをプッシュした値が積まれています。return文が実行されると、return expression;のexpressionの結果の値(結果の値の大きさをsizeとする)がスタックトップに積まれています。そのためrsp+sizeでr12に格納されていた戻り値を格納するためのアドレスの値にアクセスすることが可能です。そのアドレスにスタックトップの値を複製することで、戻り値を呼び出し元関数に渡すことができます。

/*  1byteより大きな戻り値がある場合
 *	|          |
 *	+----------+ <-rsp
 *	| expr     |
 *	| (return  |
 *	|  value)  |
 *	+----------+ <-rsp+size
 *	| r12      | -+
 *	+----------+  |
 *	| rbp      |  |
 *	+----------+  |
 *	| ret addr |  |
 *	+----------+  |
 *	| memory   |  |
 *	| argument |  |
 *	+----------+  |
 *	| memory   |  |
 *	| argument |  |
 *	|          |  |
 *	+----------+ <+
 *	| return   | 
 *	| value    |
 *	|          |  
 *	+----------+  
 */

構造体を実装し終えた後、文字定数の拡張表記(いわゆるエスケープ文字)に対応させました。ダブルクオーテーションで囲まれた文字列定数の中にある\n\tなどはそのままアセンブリコードとして生成しても問題ないようです。なぜならアセンブリする時に、エスケープ処理をしてくれるからです。しかしシングルクオートで囲まれた文字はエスケープ文字を数値として変換する必要があります。多くの場合はこの法則で良いのですが、ダブルクオーテーションで囲まれていてもエスケープ文字を数値に置き換える必要がある場合が有ります。それはchar str[7] = "Hello\n";のような配列に文字列リテラルを代入するときです。先程の文は、次のように解釈されます。char str[7] = { 'H', 'e', 'l', 'l', 'o', '\n' }; そのため、エスケープ文字を数値に変換しておく必要があります。
細かい追加機能としてはグローバル配列の実装と、ダブルクオーテーションで囲まれている/**/などのコメントブロックを無視する機能の実装、sizeof(struct Type)などの構造体のサイズを計算できる機能の実装、空文を実装しました。
直近の目標として、セキュリティキャンプの講師のhsjoihsさんが作ったC言語で書かれたCコンパイラ、2kmccをコンパイルすることを目標として進めています。ちなみにプロトタイプ宣言を除いて1500行ぐらいある2kmccのプログラムの内、1000行ほどは正常にコンパイルできているみたいです。来週までには目標を達成したい!

英検準2級一次試験合格

先日受験していた英検準2級の一次試験に合格しました。まさか合格できると思っていなかったのでびっくりです。二次試験の練習をぼちぼちはじめているのですが、なかなか難しいです。頑張るしかない! —> 英検準2級の記事

やる気のでる曲

個人的にやる気スイッチを入れるために聞いている曲を紹介します。まじでback numberはいい曲が多いのでおすすめです。

  • スーパースターになったら @back number
  • 電車の窓から @back number
  • 兵、走る @B’z
  • サザンカ @SEKAI NO OWARI
  • プライド @高橋優
  • progress @スガシカオ

部活とサークルが併合

私はゆめくじらという情報系のサークルに所属しています。また私の学校にはコンピュータ部という部活もあります。本日、コンピュータ部とゆめくじらが来年併合することが会議で決定しました。私はゆめくじらを部活化したかったのですが、残念ながら予算が想像以上に貰えそうにない、ゆめくじらとコンピュータ部の活動内容が被る部分があるなどの理由から、併合が決定しました。今後は、ゆめくじらとコンピュータが協力して、情報系の部活を盛り上げていきたいと思います。

その他

高専祭まで2週間を切ろうとしています。そろそろ私達のクラスの出し物の準備をしなくては。。。一時はクラスの出し物に対していろいろあって、予算が出ないという話もありましたが、予算が出るみたいです。予算が出るということはクラスの出し物を準備しなくては行けないということでもあります。まだ何も注文していませんが、とりあえず、土曜日に注文するリストを作成し終えました。

今週は小テストがたくさんありました。結構しんどかったです。よく乗り切ったと思う。体育の実技テストもあった。

来週も頑張る!