cbefad339f81b8843af5aecd415c17dadb9245fe
TinyCC - A Tiny C Compiler in C#
参考 TCC(Tiny C Compiler)设计理念,使用 C# 语言开发的 C 语言编译器。编译器将 C 源代码直接编译为 x86/x64 本地机器码,而非 MSIL(Microsoft 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);
}
后续改进方向
- 完善语义分析(类型检查、符号表)
- 支持更多 C99 特性(结构体、指针、数组)
- 优化代码生成(寄存器分配、指令选择)
- 支持 PE 格式(Windows)
- 添加预处理器支持(#include, #define, 条件编译)
- 支持局部变量声明
技术栈
- 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,以分离elseLabel和endLabel位置 - 在
IrFunction记录中添加ParameterCount,用于正确的栈帧设置 - 在
X64CodeGenerator.Generate()中实现_start包装器,包含call main+sys_exit补丁 - 修复
LoadValue/StoreValue,通过模式匹配处理IrLocal和IrTemp - 添加
_funcOffsets字典和CallPatchInfo,用于修补函数间调用 - 在
GenerateFunction中实现参数寄存器到栈的保存(System V AMD64 ABI:rdi、rsi、rdx、rcx、r8、r9) - 添加调试日志到
/tmp/tinycc-debug/debug.log,记录标签、跳转、补丁和十六进制转储 - 修改
E2ETestRunner,在调试期间跳过清理 - 简单返回测试(
simple_return_zero、simple_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 而非跳过它) - 修复
while和for循环 IR 生成(GenerateWhileStatement、GenerateForStatement)
阻塞项
- 分支/标签偏移计算在标签与 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中的补丁逻辑 - 验证
while和for循环标签顺序(startLabel、condLabel、incLabel、endLabel) - 运行修正补丁后的
variable_assignment测试,验证 if/else 控制流 - 在分支稳定后扩展到
function_call和recursive_factorial测试
关键上下文
- 当前失败:
variable_assignment以退出码 1 退出(预期 0)。HEX 转储显示E9 01 00 00 00 90(jmp +1,落在 NOP 上,fallthrough 到下一个 if 语句而非 endLabel) - 补丁日志显示:
Jmp at 93 -> endif_2@99, rel=1但应跳过 NOP(1 字节)+ 标签对齐 - 标签冲突:当没有 else 分支时,
elseLabel和endLabel在同一偏移;通过插入IrNop修复,但补丁仍然差一 - 通过的测试:
simple_return_zero、simple_return_42工作(无分支/控制流) - 失败的测试:
arithmetic_add、control_flow_for_loop、control_flow_while_loop、function_call、conditional_branch、variable_assignment、recursive_factorial、local_variable_scope
相关文件
/workspace/src/TinyCC.Core/CodeGen/X64CodeGenerator.cs:代码生成器,包含标签补丁、_start包装器、栈帧设置/workspace/src/TinyCC.Core/IR/IrGenerator.cs:if/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
Languages
C#
100%