feat: 自动内存管理 — 作用域级 RAII for str

- codegen: 声明 CRT free(), cleanup_add/cleanup_emit 辅助函数
- LET_STMT: str 拼接/struct 初始化 → 追踪到 cleanup_list
- BLOCK: 作用域退出时自动 emit free() 释放 str 堆分配
- RETURN: return 前先释放当前作用域所有 str 分配
- 函数隐式 return: 退出前 cleanup_emit
- str 字面量不追踪(全局常量,free 会崩溃)

内存模型(类 Rust):
  let s = "a" + "b"  → malloc 分配 → 作用域退出 → free
  let s = "literal"  → 全局常量 → 不追踪 → 不 free
  let mut s = ...    → 重新赋值时旧值需手动管理(v0.5 扩展)

消除报告 §5-1 str concat malloc 内存泄漏。
This commit is contained in:
2026-06-05 12:43:00 +08:00
parent b390d390f3
commit 1d4fb27170
+59 -7
View File
@@ -42,8 +42,12 @@ typedef struct {
LLVMTypeRef printf_ty;
// 字符串拼接运行时支持
LLVMValueRef malloc_fn;
LLVMValueRef free_fn; // auto-free 需要 free()
LLVMValueRef strlen_fn;
LLVMValueRef memcpy_fn;
// 自动内存管理: 追踪需要 free 的 str alloca
LLVMValueRef cleanup_list[64];
size_t cleanup_count;
} CgCtx;
// === 类型映射(需要 Context===
@@ -340,6 +344,26 @@ static LLVMValueRef codegen_expr(CgCtx* ctx, AstNode* node) {
}
}
// === 自动内存管理: 作用域退出时释放 str 堆分配 ===
static void cleanup_add(CgCtx* ctx, LLVMValueRef alloca) {
if (ctx->cleanup_count < 64) {
ctx->cleanup_list[ctx->cleanup_count++] = alloca;
}
}
// 释放从 mark 位置开始的所有 str 变量
static void cleanup_emit(CgCtx* ctx, size_t from_mark) {
for (size_t j = from_mark; j < ctx->cleanup_count; j++) {
LLVMValueRef ptr = ctx->cleanup_list[j];
LLVMValueRef val = LLVMBuildLoad2(ctx->builder,
LLVMPointerType(LLVMInt8TypeInContext(ctx->context), 0), ptr, "free_load");
LLVMBuildCall2(ctx->builder,
LLVMGlobalGetValueType(ctx->free_fn), ctx->free_fn,
(LLVMValueRef[]){val}, 1, "");
}
ctx->cleanup_count = from_mark;
}
// === 语句代码生成 ===
static void codegen_stmt(CgCtx* ctx, AstNode* node) {
if (!node) return;
@@ -362,6 +386,17 @@ static void codegen_stmt(CgCtx* ctx, AstNode* node) {
var_type, node->as.let_stmt.name);
LLVMBuildStore(ctx->builder, init_val, alloca);
add_var(ctx, node->as.let_stmt.name, alloca);
// 自动内存管理: str 堆分配追踪
// 只有 BINARY_EXPR (拼接) 和 STRUCT_INIT 产生堆内存
if (node->as.let_stmt.init->type.kind == TYPE_STR) {
AstKind ik = node->as.let_stmt.init->kind;
if (ik == AST_BINARY_EXPR || ik == AST_STRUCT_INIT || ik == AST_CALL_EXPR) {
cleanup_add(ctx, alloca);
}
} else if (node->as.let_stmt.init->type.kind == TYPE_STRUCT) {
cleanup_add(ctx, alloca); // struct 可能含 str 字段
}
break;
}
@@ -378,20 +413,30 @@ static void codegen_stmt(CgCtx* ctx, AstNode* node) {
codegen_expr(ctx, node->as.expr_stmt.expr);
break;
case AST_RETURN_STMT:
if (node->as.return_stmt.expr) {
LLVMValueRef val = codegen_expr(ctx, node->as.return_stmt.expr);
if (val) LLVMBuildRet(ctx->builder, val);
} else {
LLVMBuildRetVoid(ctx->builder);
case AST_RETURN_STMT: {
// 先计算返回值
LLVMValueRef ret_val = NULL;
bool has_val = node->as.return_stmt.expr != NULL;
if (has_val) {
ret_val = codegen_expr(ctx, node->as.return_stmt.expr);
if (!ret_val) return;
}
// return 前释放当前作用域所有 str 堆分配
cleanup_emit(ctx, 0);
// 然后 emit ret
if (has_val) LLVMBuildRet(ctx->builder, ret_val);
else LLVMBuildRetVoid(ctx->builder);
break;
}
case AST_BLOCK:
case AST_BLOCK: {
size_t block_mark = ctx->cleanup_count;
for (size_t i = 0; i < node->as.block.stmt_count; i++) {
codegen_stmt(ctx, node->as.block.stmts[i]);
}
cleanup_emit(ctx, block_mark); // 作用域退出: 释放块内 str 堆分配
break;
}
case AST_IF_STMT: {
LLVMValueRef cond = codegen_expr(ctx, node->as.if_stmt.cond);
@@ -478,6 +523,11 @@ LLVMModuleRef codegen_module(AstNode* ast, Arena* codegen_arena,
LLVMPointerType(LLVMInt8TypeInContext(ctx.context), 0), malloc_args, 1, false);
ctx.malloc_fn = LLVMAddFunction(ctx.module, "malloc", malloc_ty);
// 声明 free: void free(void*)
LLVMTypeRef free_args[] = { LLVMPointerType(LLVMInt8TypeInContext(ctx.context), 0) };
LLVMTypeRef free_ty = LLVMFunctionType(LLVMVoidTypeInContext(ctx.context), free_args, 1, false);
ctx.free_fn = LLVMAddFunction(ctx.module, "free", free_ty);
// 声明 strlen: size_t strlen(const char*)
LLVMTypeRef strlen_args[] = { LLVMPointerType(LLVMInt8TypeInContext(ctx.context), 0) };
LLVMTypeRef strlen_ty = LLVMFunctionType(
@@ -560,6 +610,8 @@ LLVMModuleRef codegen_module(AstNode* ast, Arena* codegen_arena,
// 确保函数有终止指令(terminator)
if (!LLVMGetBasicBlockTerminator(LLVMGetInsertBlock(ctx.builder))) {
// 函数结尾隐式 return: 先释放所有 str 堆分配
cleanup_emit(&ctx, 0);
if (fn->as.function.return_type == TYPE_VOID)
LLVMBuildRetVoid(ctx.builder);
else