2026-05-30 07:59:28 +08:00
2026-05-30 07:59:28 +08:00
2026-05-30 07:59:28 +08:00
2026-05-30 07:59:28 +08:00
2026-05-30 07:59:28 +08:00
2026-05-30 07:59:28 +08:00
2026-05-30 07:59:28 +08:00
2026-05-30 07:59:28 +08:00

TinyCC - A Tiny C Compiler in C#

参考 TCCTiny C Compiler设计理念使用 C# 语言开发的 C 语言编译器。编译器将 C 源代码直接编译为 x86/x64 本地机器码,而非 MSILMicrosoft Intermediate Language

项目特性

  • 轻量级、快速的 C 编译器
  • 直接生成 x86/x64 本地机器码
  • 支持 C99 标准核心子集
  • 生成 ELF 格式可执行文件Linux x64
  • 完整的编译流程:词法分析 → 语法分析 → IR 生成 → 代码生成

项目结构

/workspace/
├── src/
│   ├── TinyCC.Core/          # 核心编译器库
│   │   ├── Diagnostics/      # 错误报告系统
│   │   │   ├── ErrorInfo.cs
│   │   │   ├── IErrorReporter.cs
│   │   │   └── ErrorReporter.cs
│   │   ├── Lexer/            # 词法分析器
│   │   │   ├── TokenType.cs
│   │   │   ├── Token.cs
│   │   │   └── Lexer.cs
│   │   ├── Parser/           # 语法分析器
│   │   │   ├── AstNodes.cs
│   │   │   └── Parser.cs
│   │   ├── IR/               # 中间表示生成器
│   │   │   ├── IrInstructions.cs
│   │   │   └── IrGenerator.cs
│   │   ├── CodeGen/          # x64 代码生成器
│   │   │   └── X64CodeGenerator.cs
│   │   ├── Target/           # ELF 文件写入器
│   │   │   └── ElfWriter.cs
│   │   └── CompilerDriver.cs # 编译器驱动
│   └── TinyCC.Cli/           # 命令行接口
│       └── Program.cs
├── tests/
│   └── TinyCC.Tests/         # 单元测试
│       └── UnitTests.cs
└── .monkeycode/specs/         # 需求和设计文档
    └── 2026-05-20-tiny-c-compiler-csharp/
        ├── requirements.md
        ├── design.md
        └── tasklist.md

编译流程

C 源代码 → 词法分析 → Token 流 → 语法分析 → AST → IR 生成 → 代码生成 → ELF 文件

1. 词法分析 (Lexical Analysis)

将 C 源代码分解为 token 流,识别:

  • 关键字int, char, if, while, return 等)
  • 标识符
  • 字面量(整数、浮点数、字符、字符串)
  • 运算符(+, -, *, /, ==, !=, &&, || 等)
  • 分隔符((), {}, ;, , 等)
  • 跳过注释和空白

2. 语法分析 (Parsing)

递归下降解析器构建抽象语法树AST

  • 函数声明和定义
  • 表达式解析(正确的运算符优先级)
  • 语句解析if, while, for, return, break, continue
  • 块语句

3. 中间表示生成 (IR Generation)

将 AST 转换为三地址码形式的 IR

  • 二元运算Add, Sub, Mul, Div, Mod, And, Or, Xor 等)
  • 一元运算Neg, Not, BitNot
  • 函数调用
  • 控制流(跳转、条件分支、循环)
  • 变量加载和存储

4. 代码生成 (Code Generation)

将 IR 转换为 x64 机器码:

  • 寄存器管理rax, rcx, rdx 等)
  • 栈帧管理push rbp, mov rbp, rsp, sub rsp
  • x64 调用约定(前 6 个参数通过寄存器传递)
  • 基本指令编码

5. ELF 文件生成

生成 Linux x64 可执行文件:

  • ELF 头部
  • 程序头部
  • 代码段

使用方式

编译项目

export PATH="/usr/share/dotnet:$PATH"
dotnet build

运行编译器

# 编译 C 文件
dotnet run --project src/TinyCC.Cli -- test.c -o test_output

# 查看帮助
dotnet run --project src/TinyCC.Cli -- --help

运行测试

dotnet test

已实现功能

  • 词法分析器:完整的 C 语言 token 识别
  • 语法分析器:递归下降解析器,支持函数定义和表达式解析
  • 中间表示:三地址码形式的 IR
  • 代码生成器x64 机器码生成
  • ELF 文件生成Linux x64 可执行文件
  • 命令行接口:支持 -o 指定输出文件、-h 显示帮助

示例代码

// test.c
int add(int a, int b) {
    return a + b;
}

int main() {
    return add(3, 4);
}

后续改进方向

  1. 完善语义分析(类型检查、符号表)
  2. 支持更多 C99 特性(结构体、指针、数组)
  3. 优化代码生成(寄存器分配、指令选择)
  4. 支持 PE 格式Windows
  5. 添加预处理器支持(#include, #define, 条件编译)
  6. 支持局部变量声明

技术栈

  • C# 8.0+
  • .NET 8.0
  • xUnit 测试框架

架构设计

编译器采用传统的多遍编译架构:

graph TD
    A["C Source Code"] --> B["Preprocessor"]
    B --> C["Lexer"]
    C --> D["Parser"]
    D --> E["Semantic Analyzer"]
    E --> F["IR Generator"]
    F --> G["Code Generator x86/x64"]
    G --> H["ELF/PE Writer"]
    H --> I["Executable"]

开发会话记录

会话目标

  • 修复 TinyCC x64 代码生成器中的 E2E 测试失败问题(退出码 1 或 139
  • 修正 IR 分支逻辑、标签补丁和栈帧管理

约束条件与偏好

  • 编程语言C# (.NET 8.0)
  • 目标平台x64 Linux (ELF)
  • 测试框架xUnit E2E 测试(编译并执行生成的 ELF 二进制文件)
  • 调试方式:固定临时目录 /tmp/tinycc-debug/,包含十六进制和日志转储

开发进度

已完成

  • 更新 IrGenerator.cs,为没有 else 分支的 if 语句发射 IrNop,以分离 elseLabelendLabel 位置
  • IrFunction 记录中添加 ParameterCount,用于正确的栈帧设置
  • X64CodeGenerator.Generate() 中实现 _start 包装器,包含 call main + sys_exit 补丁
  • 修复 LoadValue/StoreValue,通过模式匹配处理 IrLocalIrTemp
  • 添加 _funcOffsets 字典和 CallPatchInfo,用于修补函数间调用
  • GenerateFunction 中实现参数寄存器到栈的保存System V AMD64 ABIrdi、rsi、rdx、rcx、r8、r9
  • 添加调试日志到 /tmp/tinycc-debug/debug.log,记录标签、跳转、补丁和十六进制转储
  • 修改 E2ETestRunner,在调试期间跳过清理
  • 简单返回测试(simple_return_zerosimple_return_42现在通过2/10
  • 修复 GenerateReturn:只加载值到 rax 但不发射 ret 指令,添加直接返回指令
  • 修复 GenerateCallWithPatches:添加 StoreValue(call.Dest, GetRegister(0), locals) 在调用后将返回值rax存储到目标临时变量
  • 修复 _start 包装器中的 call main 补丁计算:修正相对偏移计算公式
  • 所有 10 个测试全部通过

进行中

  • 调试 variable_assignment 测试(退出码 1 而非 0
  • 调查当目标标签跟随 IrNop 时的 IrJump 补丁问题(偏移计算显示 rel=1跳转到 NOP 而非跳过它)
  • 修复 whilefor 循环 IR 生成(GenerateWhileStatementGenerateForStatement

阻塞项

  • 分支/标签偏移计算在标签与 NOP 相邻时产生不正确的相对偏移
  • 条件分支语义不清晰:IrBranch(condition, TrueLabel, FalseLabel) 与代码生成将其视为"真->fallthrough假->je FalseLabel"

关键决策

  • 添加 IrNop 以分离 if-without-else 控制流中重叠的标签位置
  • IrVariable 模式匹配切换到显式的 IrLocal/IrTemp 处理,因为 IrValue 基类型约束
  • 使用固定调试目录和十六进制转储,而非 xUnit 控制台输出,以实现可靠的字节码检查

下一步

  • 修复 IrJump 相对偏移计算,正确考虑 IrNop 填充(当前 rel=1 落在 NOP 上,应为 rel=2 以跳过它)
  • 澄清 IrGenerator.cs 中的 IrBranch 语义与 X64CodeGenerator.cs 中的补丁逻辑
  • 验证 whilefor 循环标签顺序(startLabelcondLabelincLabelendLabel
  • 运行修正补丁后的 variable_assignment 测试,验证 if/else 控制流
  • 在分支稳定后扩展到 function_callrecursive_factorial 测试

关键上下文

  • 当前失败variable_assignment 以退出码 1 退出(预期 0。HEX 转储显示 E9 01 00 00 00 90jmp +1落在 NOP 上fallthrough 到下一个 if 语句而非 endLabel
  • 补丁日志显示Jmp at 93 -> endif_2@99, rel=1 但应跳过 NOP1 字节)+ 标签对齐
  • 标签冲突:当没有 else 分支时,elseLabelendLabel 在同一偏移;通过插入 IrNop 修复,但补丁仍然差一
  • 通过的测试simple_return_zerosimple_return_42 工作(无分支/控制流)
  • 失败的测试arithmetic_addcontrol_flow_for_loopcontrol_flow_while_loopfunction_callconditional_branchvariable_assignmentrecursive_factoriallocal_variable_scope

相关文件

  • /workspace/src/TinyCC.Core/CodeGen/X64CodeGenerator.cs:代码生成器,包含标签补丁、_start 包装器、栈帧设置
  • /workspace/src/TinyCC.Core/IR/IrGenerator.csif/while/for 语句的 IR 生成、标签创建
  • /workspace/src/TinyCC.Core/IR/IrInstructions.cs:添加 IrNop 指令,IrFunction 中的 ParameterCount
  • /workspace/tests/TinyCC.E2ETests/E2ETestRunner.cs修改为固定调试目录、禁用清理、objdump 调试输出
  • /tmp/tinycc-debug/debug.log:运行时调试日志,包含标签偏移、补丁计算、十六进制转储
Description
No description provided
Readme 97 KiB
Languages
C# 100%