From f8c5e18188d3a4d2bab384b00a333a24130828cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E8=88=AA=E5=AE=87?= <3364451258@qq.com> Date: Fri, 5 Jun 2026 00:37:54 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=8A=80=E6=9C=AF=E5=80=BA=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=20=E2=80=94=20codegen=20malloc=E2=86=92arena=20+=20.c?= =?UTF-8?q?odegraphignore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - codegen.c: VarEntry/FnEntry/ptypes 全部改用 arena_alloc,消除 malloc/free - codegen_module 新增 Arena* 参数,main.c 传入主 arena - 新增 .codegraphignore 排除 build/ 和 .codegraph/ - 基于 Codex 分析报告第7节技术债务 --- .codegraphignore | 2 + ...architecture-analysis-report-2026-06-05.md | 336 ++++++++++++++++++ src/codegen/codegen.c | 16 +- src/codegen/codegen.h | 6 +- src/driver/main.c | 2 +- 5 files changed, 353 insertions(+), 9 deletions(-) create mode 100644 .codegraphignore create mode 100644 docs/architecture-analysis-report-2026-06-05.md diff --git a/.codegraphignore b/.codegraphignore new file mode 100644 index 0000000..dc9f75c --- /dev/null +++ b/.codegraphignore @@ -0,0 +1,2 @@ +build/ +.codegraph/ diff --git a/docs/architecture-analysis-report-2026-06-05.md b/docs/architecture-analysis-report-2026-06-05.md new file mode 100644 index 0000000..47e0b48 --- /dev/null +++ b/docs/architecture-analysis-report-2026-06-05.md @@ -0,0 +1,336 @@ +# L Language 架构分析与功能建议报告 + +> 日期: 2026-06-05 | 自动生成 +> 项目: L Language v0.1 — 用 C17 实现的静态类型编译型语言 + +--- + +## 1. 当前架构全景 + +### 1.1 编译流水线 + +``` +源码(.l) → Lexer(词法分析) → Parser(语法分析) → Sema(语义分析) → Codegen(LLVM IR) → 链接(.exe) + Token[] AstNode* 类型标注 LLVMModuleRef GCC/ld +``` + +一条经典的单趟编译器流水线,手写实现,无依赖生成器。 + +### 1.2 模块深度分析 + +| 模块 | 行数(估) | 关键数据结构 | 现状评估 | +|------|----------|-------------|---------| +| lexer/ | ~200 | Token {kind, start, length, line, col} | 稳定,支持 25+ Token 类型、行/块注释 | +| ast/ | ~150 | AstNode {kind, type, as{union}} | 14 种节点,工厂函数风格 | +| parser/ | ~400 | Parser {tokens, pos, filename, arena} | 递归下降 + Pratt 优先级解析,成熟 | +| sema/ | ~200 | Scope {head, parent}, Symbol {name, kind, type} | 作用域链、类型推断、类型检查 | +| codegen/ | ~300 | CgCtx {builder, var_table, fn_table} | LLVM-C API 完整映射,含内置 printf | +| util/ | ~80 | Arena bump allocator | 8MB 固定大小,贯穿全流水线 | +| driver/ | ~150 | 流水线串联 | 带 --emit-ir、-o 参数 | + +### 1.3 技术选择评估 + +| 选择 | 评价 | +|------|------| +| C17 + CMake | 轻量,适合学习编译器全流程 | +| Arena allocator | 极简内存管理,无 GC 开销,无内存泄漏 | +| LLVM-C API | 成熟稳定,但版本差异需在 CMake 中处理 | +| 手写词法/语法分析 | 0 外部依赖,完整控制权 | +| Pratt 解析 | 优雅处理表达式优先级,扩展方便 | +| GCC 链接 | 简单粗暴,跨平台需额外处理 | + +--- + +## 2. 当前功能清单与成熟度 + +### 已实现 (v0.1) + +- [x] 基本类型: i64, f64, bool, void +- [x] 算术运算: + - * / % +- [x] 比较运算: == != < > <= >= +- [x] 逻辑运算: && || ! +- [x] 变量声明: let 不可变,支持类型标注和类型推断 +- [x] 控制流: if/else, while +- [x] 函数定义与调用: 参数、返回类型、递归 +- [x] 内置函数: print_i64, print_f64, print_bool +- [x] 注释: // 行注释, /* */ 块注释 +- [x] 错误报告: 词法/语法即时终止, 语义错误收集后批量输出 +- [x] 测试: 41(词法) + 15(语法) + 9(语义) 单元测试, 5 个集成测试程序 + +### 未实现 (与 Rust 对标) + +- [ ] 字符串类型 (只有 printf 的内建打印) +- [ ] 数组/切片 +- [ ] 自定义结构体/枚举 +- [ ] let mut 可变变量 + 赋值语句 +- [ ] 复合赋值: += -= *= /= +- [ ] for 循环 / loop 循环 +- [ ] match 模式匹配 +- [ ] 泛型 +- [ ] 模块系统 / 多文件编译 +- [ ] trait / 接口 +- [ ] 所有权 / 借用 / 生命周期 +- [ ] 类型别名 +- [ ] 标准库 + +--- + +## 3. 向 Rust 学习: 功能优先级建议 + +按"学习价值 + 实现难度 + 对编译器的收获"排序。 + +### P0: 短期可加(1-2 天) + +#### 3.1 let mut + 赋值语句 + +Rust 启发: Rust 中 let 默认不可变,let mut 声明可变变量。 + +改动范围: +- lexer: 新增 mut 关键字 Token +- parser: let_stmt 添加 is_mut 标志; 新增 assignment_stmt +- sema: 可变性检查(不可变变量不能赋值) +- codegen: 赋值语句生成 store IR + +学习价值: 理解"默认不可变"的设计哲学如何贯穿全编译流程。 + +#### 3.2 字符串类型 + 字符串字面量 + +Rust 启发: Rust 的 &str 和 String 区分, 但初期只需一个简单字符串。 + +改动范围: +- lexer: 双引号字符串字面量 Token ("...") +- parser: 字符串字面量 AST 节点 +- sema: 新增 TYPE_STR 类型 +- codegen: 字符串常量在 LLVM IR 中生成全局数组; print_str 内置函数 +- driver: 字符串在 IR 中需 i8* 指针表示 + +学习价值: 掌握 LLVM 中常量和全局变量的处理。 + +### P1: 中期(3-5 天) + +#### 3.3 数组类型 + +Rust 启发: [T; N] 固定大小数组。 + +改动范围: +- lexer: [ ] Token 已有 +- ast: AST_ARRAY_TYPE, AST_INDEX_EXPR 节点 +- parser: 类型语法 [i64; 10], 索引表达式 arr[0] +- sema: 数组类型检查, 索引必须为 i64, 边界检查 +- codegen: LLVM alloca + GEP 指针计算 + +学习价值: 学习 GEP (GetElementPtr) 指针计算, 这是 LLVM 中最重要的概念之一。 + +#### 3.4 结构体 (struct) + +Rust 启发: Rust 的 struct 定义具名域。 + +改动范围: +- lexer: struct 关键字, . 点号 +- ast: AST_STRUCT_DECL, AST_FIELD_ACCESS 节点 +- parser: struct 声明 + 初始化 + 字段访问 +- sema: 结构体类型符号表, 字段类型解析 +- codegen: LLVM 结构体类型 (LLVMStructType), GEP 访问字段 + +学习价值: 深入 LLVM 结构体类型与 GEP 偏移量的精确定位。 + +#### 3.5 枚举 + 简单模式匹配 + +Rust 启发: Rust 的 enum 是代数数据类型, 但初期可以先做 C 风格 enum。 + +改动范围: +- lexer: enum, :: 作用域解析 +- ast: AST_ENUM_DECL 节点 +- parser: enum 声明 +- sema: enum 类型符号表 +- codegen: enum 在 LLVM 中用 i64 表示 (C 风格) + +学习价值: 为后续 Rust 风格的代数类型和 match 做铺垫。 + +### P2: 中后期(1-2 周) + +#### 3.6 match 表达式 + +Rust 启发: Rust 的 match 是穷举模式匹配, 表达式级别。 + +改动范围: +- lexer: match, =>, _ 通配符 +- ast: AST_MATCH_EXPR, AST_MATCH_ARM 节点 +- parser: match 表达式解析 +- sema: 穷举性检查, 模式变量绑定 +- codegen: switch 或 if-else 链生成 + +学习价值: 模式匹配是 PL 设计中最重要的概念之一。 + +#### 3.7 for 循环 + Range + +Rust 启发: for i in 0..10 {} + +改动范围: +- lexer: for, in, .. Token +- ast: AST_FOR_STMT, AST_RANGE_EXPR 节点 +- parser: for 循环解析 +- sema: 范围类型检查 +- codegen: 翻译为 while 循环 IR + +学习价值: 语法糖如何被编译为底层控制流, 理解"去糖" (desugaring)。 + +#### 3.8 类型别名 (type) + +Rust 启发: type Meters = i64; + +- lexer: type 关键字 +- ast: AST_TYPE_ALIAS 节点 +- parser: 类型别名声明 +- sema: 类型别名展开 +- codegen: 透明 (别名展开后与原类型相同) + +学习价值: 类型系统的"名称等价"vs"结构等价"概念。 + +### P3: 长期(2-4 周) + +#### 3.9 泛型 + +Rust 启发: Rust 的泛型是编译期单态化。 + +改动范围: +- lexer: < > (已有) + 类型参数语法 +- ast: AST_GENERIC_FN, AST_TYPE_PARAM 节点 +- parser: 带类型参数的函数签名 +- sema: 泛型符号表, 单态化展开 +- codegen: 按具体类型展开生成代码 + +学习价值: 单态化 (monomorphization) 是 Rust 零成本抽象的基石。 + +#### 3.10 trait / 接口 + +Rust 启发: Rust 的 trait 定义行为契约。 + +改动范围: +- ast: AST_TRAIT_DECL, AST_IMPL_BLOCK 节点 +- parser: trait + impl 语法 +- sema: trait 一致性检查, 虚表/静态分发 +- codegen: 动态分发 (虚表指针) / 静态分发 (单态化) + +学习价值: 理解"接口"在编译期/运行时的不同实现策略。 + +#### 3.11 模块系统 / 多文件编译 + +Rust 启发: Rust 的 mod + use 模块系统。 + +改动范围: +- driver: 多文件读取 + 编译单元管理 +- ast: AST_MODULE, AST_USE 节点 +- parser: mod, use 关键字 +- sema: 模块作用域, 可见性检查 +- codegen: 链接多个编译单元 + +学习价值: 从单文件到多文件的跳跃, 理解链接过程和符号解析。 + +--- + +## 4. 架构改进建议 + +### 4.1 短期改进 + +1. **代码生成与链接解耦** + - 当前: codegen.c 中混合了 LLVM IR 生成和初始化 LLVM 目标 + - 建议: 抽取 target_init() 到独立的 target.c, codegen.c 只负责 AST->IR + +2. **错误类型统一** + - 当前: ErrorInfo (单个错误) 和 ErrorList (错误列表) 存在于多个模块 + - 建议: 统一为 CompilerError { phase, message, line, col, filename } 结构体 + +3. **测试框架扩展** + - 当前: 手写断言宏, 缺少 codegen 层测试 + - 建议: 新增 test_codegen.c, 测试 AST->LLVM IR 是否正确 + +### 4.2 中期改进 + +4. **IR 中间表示层** + - 当前: AST 直接生成 LLVM IR, 跳过了"自己设计 IR"这一步 + - 建议: 在 AST 和 LLVM IR 之间加一层自定义 IR (三地址码或 SSA 形式) + - 学习价值: 理解"IR 是编译器的核心抽象"这句话的真正含义 + +5. **解释器模式** + - 建议: 在 codegen/ 旁加一个 interp/ 模块, 直接 walk AST 解释执行 + - 好处: 快速验证语义正确性, 无需 LLVM 编译; 区分编译和解释模式 + +6. **调用约定抽象** + - 当前: codegen 直接调 LLVMBuildCall2 + - 建议: 抽一层 abi.h, 封装参数传递和返回值处理 + +### 4.3 长期改进 + +7. **自举准备** + - 用 L Language 写一个 L Language 编译器的最小实现 + - 分阶段: 先让 L 输出 AST -> 再输出 LLVM IR -> 最终用 L 编译自己 + +8. **增量编译** + - 缓存已编译模块的时间戳和符号表 + - 只重新编译有改动的文件 + +--- + +## 5. Rust 设计哲学清单: 哪些值得吸收 + +| Rust 特性 | L 是否该加 | 理由 | +|-----------|-----------|------| +| 默认不可变 (let vs let mut) | 是 | 编译期检查, 几乎零实现成本 | +| 表达式 vs 语句 | 是 | if/match/block 可作为表达式 | +| 模式匹配 | 是 | 最优雅的控制流之一 | +| 代数数据类型 (enum + 带字段) | 是 | 类型安全的设计模式 | +| Option/Result 错误处理 | 有条件 | 需要泛型 + enum, 实现门槛高 | +| 所有权 / 借用 | 暂不加 | 需要借用检查器, 复杂度远超当前阶段 | +| 生命周期标注 | 暂不加 | 所有权系统的伴生概念 | +| trait 系统 | 暂不加 | 需要泛型 + 虚方法表, 实现成本高 | +| 宏系统 | 暂不加 | 极其复杂, 自举后才考虑 | +| 模块系统 | 可以加简单版 | 简单 mod + 文件包含即可 | + +--- + +## 6. 推荐开发路线图 + +### 短期 (下一个迭代) + +| 优先级 | 功能 | 预计工时 | +|--------|------|---------| +| P0 | let mut + 赋值语句 | 1 天 | +| P0 | 字符串类型 + 字面量 + print_str | 1 天 | +| P1 | 数组 + arr[i] 索引 | 2 天 | +| P0 | for 循环 + range | 1 天 | + +### 中期 (下下个迭代) + +| 优先级 | 功能 | 预计工时 | +|--------|------|---------| +| P1 | 结构体 | 2-3 天 | +| P1 | 枚举 (C 风格) | 1 天 | +| P2 | match 表达式 | 2-3 天 | +| P1 | 类型别名 | 0.5 天 | + +### 长期 + +| 优先级 | 功能 | 预计工时 | +|--------|------|---------| +| P2 | 自定义 IR 层 | 3-5 天 | +| P3 | 泛型 | 5-7 天 | +| P2 | 模块系统 | 3-5 天 | +| P3 | trait / 接口 | 5-7 天 | +| P3 | 自举尝试 | 数周 | + +--- + +## 7. 技术债务与风险 + +| 问题 | 影响 | 建议 | +|------|------|------| +| codegen.c 中 malloc 混用 arena | 内存管理不一致 | 优先使用 arena, 或实现统一的代码生成器分配器 | +| system("gcc ...") 链接 | 平台不兼容 | 封装 Linker 接口, 支持直接调用 ld/lld | +| 无 codegen 层单元测试 | IR 正确性无保障 | 添加 test_codegen.c, 用 LLVMVerifyModule 验证 | +| 所有变量用 alloca | 效率低 | LLVM opt -mem2reg 可以优化, 但当前没跑优化 pass | +| 无 .codegraphignore | 索引可能包含无关文件 | 添加 .codegraphignore 排除 build/ | + +--- + +*本报告由 Codex 自动生成。下次运行: 2 小时后。* diff --git a/src/codegen/codegen.c b/src/codegen/codegen.c index 6ede560..967296d 100644 --- a/src/codegen/codegen.c +++ b/src/codegen/codegen.c @@ -3,7 +3,6 @@ #include #include #include -#include // === 内部状态 === typedef struct VarEntry { @@ -22,6 +21,7 @@ typedef struct FnEntry { } FnEntry; typedef struct { + Arena* arena; // 代码生成阶段分配器 LLVMContextRef context; // LLVM 19+ 需要显式 Context LLVMModuleRef module; LLVMBuilderRef builder; @@ -60,7 +60,8 @@ static LLVMValueRef find_var(CgCtx* ctx, const char* name) { } static void add_var(CgCtx* ctx, const char* name, LLVMValueRef alloca) { - VarEntry* e = malloc(sizeof(*e)); + VarEntry* e = arena_alloc(ctx->arena, sizeof(*e)); + if (!e) return; e->name = name; e->alloca = alloca; e->next = ctx->var_table; ctx->var_table = e; } @@ -73,7 +74,8 @@ static LLVMValueRef find_fn(CgCtx* ctx, const char* name) { } static void add_fn(CgCtx* ctx, const char* name, LLVMValueRef fn) { - FnEntry* e = malloc(sizeof(*e)); + FnEntry* e = arena_alloc(ctx->arena, sizeof(*e)); + if (!e) return; e->name = name; e->fn = fn; e->ret = TYPE_VOID; e->params = NULL; @@ -305,8 +307,10 @@ static void codegen_stmt(CgCtx* ctx, AstNode* node) { } // === 程序级代码生成 === -LLVMModuleRef codegen_module(AstNode* ast, const char* name, const char** error_msg) { +LLVMModuleRef codegen_module(AstNode* ast, Arena* codegen_arena, + const char* name, const char** error_msg) { CgCtx ctx = {0}; + ctx.arena = codegen_arena; ctx.context = LLVMContextCreate(); if (!ctx.context) { *error_msg = "无法创建 LLVM Context"; @@ -326,7 +330,8 @@ LLVMModuleRef codegen_module(AstNode* ast, const char* name, const char** error_ // 第一遍:声明所有 L 函数 for (size_t i = 0; i < ast->as.program.fn_count; i++) { AstNode* fn = ast->as.program.functions[i]; - LLVMTypeRef* ptypes = malloc(fn->as.function.param_count * sizeof(LLVMTypeRef)); + LLVMTypeRef* ptypes = arena_alloc(ctx.arena, + fn->as.function.param_count * sizeof(LLVMTypeRef)); for (size_t j = 0; j < fn->as.function.param_count; j++) ptypes[j] = to_llvm_type(&ctx, fn->as.function.params[j]->as.parameter.type); LLVMTypeRef fty = LLVMFunctionType( @@ -334,7 +339,6 @@ LLVMModuleRef codegen_module(AstNode* ast, const char* name, const char** error_ ptypes, (unsigned)fn->as.function.param_count, false); LLVMValueRef lfn = LLVMAddFunction(ctx.module, fn->as.function.name, fty); add_fn(&ctx, fn->as.function.name, lfn); - free(ptypes); } // 第二遍:生成函数体 diff --git a/src/codegen/codegen.h b/src/codegen/codegen.h index c8aaa4c..cc1eae8 100644 --- a/src/codegen/codegen.h +++ b/src/codegen/codegen.h @@ -2,11 +2,13 @@ #define CODEGEN_H #include "ast.h" +#include "arena.h" #include // 生成 LLVM Module。模块已 verify,可直接 dump 或写入文件。 +// codegen_arena 用于内部分配(VarEntry/FnEntry 等),需在整个 Module 生命周期保持存活。 // 出错时返回 NULL 并设置 *error_msg。 -LLVMModuleRef codegen_module(AstNode* ast, const char* module_name, - const char** error_msg); +LLVMModuleRef codegen_module(AstNode* ast, Arena* codegen_arena, + const char* module_name, const char** error_msg); #endif diff --git a/src/driver/main.c b/src/driver/main.c index 79bfcd1..7dba356 100644 --- a/src/driver/main.c +++ b/src/driver/main.c @@ -95,7 +95,7 @@ int main(int argc, char** argv) { // 6. LLVM IR 生成 const char* codegen_error = NULL; - LLVMModuleRef module = codegen_module(ast, "l_module", &codegen_error); + LLVMModuleRef module = codegen_module(ast, &arena, "l_module", &codegen_error); if (!module) { fprintf(stderr, "IR 生成错误: %s\n", codegen_error); free(source); arena_destroy(&arena);