Files
tinycc/README.md
2026-05-30 07:59:28 +08:00

241 lines
9.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 头部
- 程序头部
- 代码段
## 使用方式
### 编译项目
```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 ABIrdi、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` 但应跳过 NOP1 字节)+ 标签对齐
- **标签冲突**:当没有 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`:运行时调试日志,包含标签偏移、补丁计算、十六进制转储