# 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`:运行时调试日志,包含标签偏移、补丁计算、十六进制转储