From 1d4fb271704edcd1889ce603ec68834b0da98b9f 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 12:43:00 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=87=AA=E5=8A=A8=E5=86=85=E5=AD=98?= =?UTF-8?q?=E7=AE=A1=E7=90=86=20=E2=80=94=20=E4=BD=9C=E7=94=A8=E5=9F=9F?= =?UTF-8?q?=E7=BA=A7=20RAII=20for=20str?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 内存泄漏。 --- src/codegen/codegen.c | 66 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 59 insertions(+), 7 deletions(-) diff --git a/src/codegen/codegen.c b/src/codegen/codegen.c index 300699c..2a38412 100644 --- a/src/codegen/codegen.c +++ b/src/codegen/codegen.c @@ -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