fix: 技术债修复 — codegen malloc→arena + .codegraphignore
- codegen.c: VarEntry/FnEntry/ptypes 全部改用 arena_alloc,消除 malloc/free - codegen_module 新增 Arena* 参数,main.c 传入主 arena - 新增 .codegraphignore 排除 build/ 和 .codegraph/ - 基于 Codex 分析报告第7节技术债务
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
build/
|
||||
.codegraph/
|
||||
@@ -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 小时后。*
|
||||
+10
-6
@@ -3,7 +3,6 @@
|
||||
#include <llvm-c/Types.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
// === 内部状态 ===
|
||||
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);
|
||||
}
|
||||
|
||||
// 第二遍:生成函数体
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
#define CODEGEN_H
|
||||
|
||||
#include "ast.h"
|
||||
#include "arena.h"
|
||||
#include <llvm-c/Core.h>
|
||||
|
||||
// 生成 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
|
||||
|
||||
+1
-1
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user