241 lines
9.0 KiB
Markdown
241 lines
9.0 KiB
Markdown
# 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 头部
|
||
- 程序头部
|
||
- 代码段
|
||
|
||
## 使用方式
|
||
|
||
### 编译项目
|
||
|
||
```bash
|
||
export PATH="/usr/share/dotnet:$PATH"
|
||
dotnet build
|
||
```
|
||
|
||
### 运行编译器
|
||
|
||
```bash
|
||
# 编译 C 文件
|
||
dotnet run --project src/TinyCC.Cli -- test.c -o test_output
|
||
|
||
# 查看帮助
|
||
dotnet run --project src/TinyCC.Cli -- --help
|
||
```
|
||
|
||
### 运行测试
|
||
|
||
```bash
|
||
dotnet test
|
||
```
|
||
|
||
## 已实现功能
|
||
|
||
- 词法分析器:完整的 C 语言 token 识别
|
||
- 语法分析器:递归下降解析器,支持函数定义和表达式解析
|
||
- 中间表示:三地址码形式的 IR
|
||
- 代码生成器:x64 机器码生成
|
||
- ELF 文件生成:Linux x64 可执行文件
|
||
- 命令行接口:支持 `-o` 指定输出文件、`-h` 显示帮助
|
||
|
||
## 示例代码
|
||
|
||
```c
|
||
// 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 测试框架
|
||
|
||
## 架构设计
|
||
|
||
编译器采用传统的多遍编译架构:
|
||
|
||
```mermaid
|
||
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`:运行时调试日志,包含标签偏移、补丁计算、十六进制转储
|