Skip to content
broccolingualPublic

About

rust製のCコンパイラ

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

339 Commits

Folders and files

Repository files navigation

Lines of Code: 4904

モジュール構成

本プロジェクトは以下のモジュールで構成されています:

フロントエンド(字句解析・構文解析)

  • lexer.rs - 字句解析器.ソースコードをトークン列に変換
  • token.rs - トークン定義.キーワード,演算子,識別子などのトークン型を定義
  • ast.rs - 構文解析器.トークン列を解析してASTを生成
    • ast/decl.rs - 変数・関数・型の宣言をパース
    • ast/expr.rs - 式(演算子,関数呼び出しなど)をパース
    • ast/stmt.rs - 文(if, while, returnなど)をパース

中間表現(AST・型・シンボル)

  • node.rs - ASTノードの定義と型推論
    • node/kind.rs - ノードの種類(変数,二項演算,関数呼び出しなど)
    • node/operator.rs - 演算子の定義(単項演算子,二項演算子)
  • decl.rs - 宣言情報.Decl(変数・関数宣言)とMemberDecl(構造体メンバ宣言)を定義
  • types.rs - 型システム.int, char, ポインタ, 配列, 構造体などの型を管理
    • types/kind.rs - 型の種類を定義
    • types/spec.rs - 型指定子と修飾子(const, staticなど)を管理
    • types/table.rs - グローバル型テーブル.型をIDで管理し重複を防止
    • types/type_ref.rs - 型参照(TypeRef).型テーブルへの軽量な参照
  • func.rs - 関数定義の管理.パラメータ,ローカル変数,スタックサイズを扱う
  • symbol.rs - シンボル定義.変数・関数・列挙定数の情報を保持
    • symbol/table.rs - スコープ付きシンボルテーブル.名前解決とスコープ管理

バックエンド(コード生成)

  • x86.rs - x86-64アセンブリコード生成器
    • x86/expr.rs - 式のコード生成
    • x86/stmt.rs - 文のコード生成
    • x86/reg.rs - レジスタ管理
  • asm_builder.rs - アセンブリコードのビルダー.生成したアセンブリを文字列に整形し、簡易な最適化を実装

共通ユーティリティ

  • main.rs - エントリーポイント.コマンドライン引数(-d/--debug, -o/--optimize, -i/--input, -f/--file)を解析し,コンパイルパイプラインを実行
  • utils.rs - ユーティリティ.Span(ソースコード位置情報)とAlignUp(アライメント計算)を定義
  • errors.rs - エラー定義とエラーメッセージのフォーマット.ソースコード位置情報を含む詳細なエラー表示

コンパイルフロー

ソースコード
    ↓
[Lexer] 字句解析
    ↓
トークン列
    ↓
[AST] 構文解析 + 意味解析
    ↓
抽象構文木(型チェック済み)
    ↓
[Generator] コード生成
    ↓
[AsmBuilder] 最適化(オプション)
    ↓
x86-64アセンブリ

本コンパイラで利用する略語について

コード内で一貫して使用している略語の一覧です.C言語の文法用語に基づいています.

略語 正式名称 意味
decl declaration 宣言(変数宣言,関数宣言など)
spec specifier 指定子(型指定子,ストレージクラス指定子など)
qual qualifier 修飾子(const,volatileなど)
param parameter パラメータ,引数
func function 関数
ptr pointer ポインタ
abst abstract 抽象(abstract declarator)
expr expression 式
stmt statement 文

注意: declarator,initializerは文法上重要な概念のため省略していません.

実装状況

型システム

  • 基本的なデータ型(int, char, short, long, float, double, void)
  • ポインタ型
  • 配列(1次元、多次元)
  • 構造体(struct)
  • 自己参照構造体(リンクリスト、二分木など)
  • 構造体の前方宣言
  • 入れ子構造体
  • グローバル型テーブルによる型の統一管理
  • union型
  • enum型
  • typedef(型エイリアス)
  • 型キャスト(明示的な型変換)
  • unsigned型の完全なサポート
  • long long型

演算子

  • 算術演算子(+, -, *, /, %)
  • ビット演算子(&, |, ^, ~, <<, >>)
  • 比較演算子(==, !=, <, <=, >, >=)※ポインタとNULL(0)の比較も対応
  • 論理演算子(&&, ||, !)
  • 代入演算子(=, +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=)
  • インクリメント/デクリメント(++, --)前置・後置
  • 三項演算子(? :)
  • sizeof演算子
  • アドレス演算子(&)と参照外し演算子(*)
  • アロー演算子(->)とドット演算子(.)※連鎖的なアロー演算子も対応
  • 配列添字演算子([])
  • カンマ演算子(,)
  • 浮動小数点数の演算

宣言と初期化

  • 変数宣言と初期化
  • 関数定義
  • 関数呼び出し
  • 配列初期化子(1次元)
  • ポインタ配列の初期化
  • サイズ省略配列の初期化(int a[] = {...})
  • グローバル変数
  • グローバル変数の初期化
  • 型修飾子の構文解析(const, volatile, restrict)
  • ストレージクラス指定子の構文解析(auto, extern, register, static, typedef)
  • 関数指定子の構文解析(inline)
  • 可変長引数(...)の構文解析
  • 関数プロトタイプ宣言のシンボルテーブル登録
  • 多次元配列の初期化子(int a[2][3] = {{1,2,3}, {4,5,6}})
  • 構造体の初期化子(struct Point p = {1, 2})
  • 初期化子の末尾カンマ対応(int a[] = {1, 2, 3,})
  • 抽象宣言子(abstract declarator)
  • 型修飾子の意味解析と実装(const, volatile, restrict)
  • ストレージクラス指定子の実装(static, externの完全なサポート)
  • 関数指定子の実装(inline)
  • 代入時の型チェックの強化
  • グローバル変数の定数式評価

制御構文

  • if文、else文
  • while文
  • for文
  • do-while文
  • break文、continue文
  • return文
  • goto文とラベル
  • switch文
  • case文、default文

スコープと変数

  • ブロックスコープ
  • ローカル変数
  • グローバル変数

リテラル

  • 文字列リテラル
  • 数値リテラル(10進数、8進数、16進数)
  • 文字列リテラルの連結("Hello" "World")

その他の機能

  • 構造体のメンバアクセス
  • 構造体のメンバオフセット計算
  • 構造体の値渡し・値返し
  • 標準ライブラリ関数のサポート(現状は外部関数を呼び出す機能のみ)

プリプロセッサ

  • #include
  • #define
  • #ifdef, #ifndef, #endif
  • #if, #elif, #else
  • マクロ展開
  • 条件付きコンパイル

最適化とエラー処理

  • 最適化(冗長なpush/pop命令の削除、--optimizeフラグで有効化)
  • ソースコード位置情報付きエラーメッセージ
  • 警告メッセージ
  • エラーメッセージの更なる改善

雑記

アセンブリとアーキテクチャ

  • Intel記法とAT&T記法でのレジスタのsrc,dstの違いに注意.
  • スタックはLIFOであることに注意.特に関数呼び出し.
  • レジスタのバイト数によってレジスタの名称が異なるので注意.
  • 32ビットのレジスタを呼び出した場合のみ上位が0クリアされることに注意.
  • 関数呼び出しは,ABIに基づいた16バイトアラインメントが必要.
  • 関数が呼び出された後にスタックポインタを下げて領域を確保する際に16の倍数にしておくと,16バイトアラインメントが維持される.
  • なるべくpushでスタックに結果を一時退避させるのではなく,レジスタに直接飛ばす.スタックリークの発生要因になり得る.

コンパイラ設計

  • 加算・減算にはポインタの計算が含まれることに注意.
  • ASTの作成と同時に意味解析をやるので,BNF通りにパースすることが難しい場合がある.あまりBNFにこだわらないこと.
  • BNFが左再帰の場合は無限ループに陥らないよう関数に工夫が必要.
  • トークナイザは比較的実装が容易なので,インクリメンタルに開発するのではなく最初から全部作っても大丈夫そう.
  • 型はグローバルテーブルで一元管理し,TypeRefでID参照することで重複を防ぐ.
  • 自己参照構造体は,構造体定義開始時に不完全型として登録し,メンバパース後に完全型に更新することで実現.
  • 入れ子構造体は再帰下降パーサーの関数スタックフレームによって自然に処理される.

Rust実装のコツ

  • Rustの場合,連結リストを作ると記述が冗長になるので,Vecで代用できる部分はなるべくVecで代用する.
  • 最初から網羅的に実装しようとすると破綻する.諦めが肝心.

開発ワークフロー

  • デバッグモード(--debug)でシンボルテーブルや関数情報を確認できる.
  • 最適化(--optimize)はデフォルトで有効.無効化する場合は--optimize=falseを指定.
  • 冗長なpush/pop命令は最適化で自動削除される.

gdbを利用したデバッグ

gdb ./bin/tmp
(gdb) b 11 # アセンブリ上の確認したい行を指定し,ブレイクポイントを打つ
(gdb) run # ブレイクポイントまで実行
(gdb) info registers # レジスタの状態を確認

リンク集

About

rust製のCコンパイラ

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages