From adff2f35c42653385ff5e07a86a8edbcac5a6037 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Fri, 7 Aug 2026 22:36:16 +0800 Subject: [PATCH] =?UTF-8?q?fix(core):=20print=20=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E6=84=9F=E7=9F=A5=E6=A0=BC=E5=BC=8F=E5=8C=96=EF=BC=8C=E8=B7=A8?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E6=9C=AA=E5=AF=BC=E5=85=A5=20fast-fail?= =?UTF-8?q?=EF=BC=8C=E6=8A=A5=E9=94=99=E8=A1=8C=E5=8F=B7=E9=98=B2=E5=BE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- App/lib/core/Handles/HandlesBody.vp | 6 +- App/lib/core/Handles/HandlesExprCall.vp | 143 +++++++++++++++++++++++- App/lib/core/Handles/HandlesType.vp | 15 ++- 3 files changed, 159 insertions(+), 5 deletions(-) diff --git a/App/lib/core/Handles/HandlesBody.vp b/App/lib/core/Handles/HandlesBody.vp index d674a4f..66f5f1c 100644 --- a/App/lib/core/Handles/HandlesBody.vp +++ b/App/lib/core/Handles/HandlesBody.vp @@ -199,9 +199,11 @@ def translate_expr_stmt(trans: HT.Translator | t.CPtr, if func_name is None: return 0 - # printf / print 走特殊路径(print 映射到 printf) - if string.strcmp(func_name, "printf") == 0 or string.strcmp(func_name, "print") == 0: + # printf / print 走特殊路径(print 是类型感知格式化,不映射到 printf) + if string.strcmp(func_name, "printf") == 0: trans.ExprCallH.HandlePrintfCall(cl) + elif string.strcmp(func_name, "print") == 0: + trans.ExprCallH.HandlePrintCall(cl) else: # 通用函数调用(返回值丢弃) trans.ExprCallH.HandleCall(call_node) diff --git a/App/lib/core/Handles/HandlesExprCall.vp b/App/lib/core/Handles/HandlesExprCall.vp index e357b4e..0d93022 100644 --- a/App/lib/core/Handles/HandlesExprCall.vp +++ b/App/lib/core/Handles/HandlesExprCall.vp @@ -5541,6 +5541,22 @@ def translate_call(pool: memhub.MemBuddy | t.CPtr, if xmod_nm.id is not None: mod_sha1: str = _lookup_module_sha1(xmod_nm.id) if mod_sha1 is not None: + # fast-fail: 模块限定调用要求模块已被当前文件 import。 + # 全局 SHA1 存储包含所有被扫描的 includes,未导入的模块 + # (如 this.this() 但 import this 被注释)也会命中存储, + # 静默生成 @{sha1}.func 调用而非报错——违反"找不到 fast-fail"原则。 + is_xmod_imported: int = 1 + if trans is not None and trans._imported_modules is not None: + is_xmod_imported = HandlesImports.is_module_imported( + trans._imported_modules, xmod_nm.id) + if is_xmod_imported == 0: + err_ni: t.CChar | t.CPtr = pool.alloc(256) + if err_ni is not None: + viperlib.snprintf(err_ni, 256, + "跨模块调用 '%s.%s':模块 '%s' 未导入(请先 import %s)", + xmod_nm.id, func_name, xmod_nm.id, xmod_nm.id) + HandlesType.fatal_error(xmod_nm, err_ni) + HandlesType.fatal_error(xmod_nm, "跨模块调用:模块未导入") # 检查是否是 re-export 函数(如 ast._lexer_init 实际定义在 ast.lexer) reexport_src: str = HandlesImports.lookup_reexport(mod_sha1, func_name) if reexport_src is not None: @@ -5836,7 +5852,123 @@ def translate_printf_call(pool: memhub.MemBuddy | t.CPtr, # is_variadic=1: 生成带显式函数类型签名的 call(如 call i32 (i8*, ...) @printf), # 让 llc 识别为 variadic 调用,生成正确的 Win64 ABI 代码(浮点参数同时传 XMM 和整数寄存器) - llvmlite.build_call(builder, "printf", fmt_ptr, can, i32_ty, 1) + llvmlite.build_call(builder, "printf", fmt_ptr, can, i32_ty, 1) + + +# ============================================================ +# 翻译 print 调用 — 类型感知格式化 + 默认换行 +# +# 与 printf 不同,print 不需要用户提供格式串:每个参数按 LLVM +# 类型自动选择格式(%s/%d/%f/0x%llx),结尾追加 "\n"(对齐 Python +# print 默认 end="\n",以及 TPC HandlesExprBuiltin._HandlePrintLlvm)。 +# +# 类型分派规则: +# i8*(str/bytes) → %s +# 其他指针(struct*/int*) → 0x%llx(ptrtoint 到 i64) +# i1(bool) → %d(zext 到 i32) +# i8/i16/i32 → %d(sext 到 i32,C ABI 整数提升) +# i64 → %lld +# float/double → %f(float fpext 到 double,C ABI 提升) +# 无法识别的类型 → "(unknown)"(不传参) +# ============================================================ +def translate_print_call(pool: memhub.MemBuddy | t.CPtr, + builder: llvmlite.IRBuilder | t.CPtr, + mod: llvmlite.LLVMModule | t.CPtr, + cl: ast.Call | t.CPtr, + trans: HT.Translator | t.CPtr = None): + """翻译 print 调用(类型感知格式化 + 默认换行)""" + cargs: list[ast.AST | t.CPtr] | t.CPtr = cl.args + if cargs is None: + return + + FMT_BUF_SIZE: t.CSizeT = 256 + fmt_buf: bytes = stdlib.malloc(FMT_BUF_SIZE) + if fmt_buf is None: + return + fmt_buf[0] = '\0' + + i32_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Int32(pool) + i64_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Int64(pool) + dbl_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Double(pool) + + can: t.CSizeT = cargs.__len__() + arg_head: llvmlite.Value | t.CPtr = None + arg_tail: llvmlite.Value | t.CPtr = None + arg_count: int = 1 # 格式串本身是第 1 个 printf 参数 + + for ai in range(can): + arg: ast.AST | t.CPtr = cargs.get(ai) + if arg is None: + continue + arg_val: llvmlite.Value | t.CPtr = HandlesExpr.translate_value( + builder, pool, mod, arg, None, 0, trans) + if arg_val is None or arg_val.Ty is None: + # 参数翻译失败 → "(null)"(与 TPC 一致,不传参) + if string.strlen(fmt_buf) < FMT_BUF_SIZE - 16: + viperlib.snprintf(fmt_buf + string.strlen(fmt_buf), 16, "(null)") + continue + + token: t.CChar | t.CPtr = None + chain_arg: int = 1 # 1=该 token 消耗一个参数,0=纯文本 + arg_bits: int = HandlesExpr.get_llvm_type_bits(arg_val.Ty) + arg_fbits: int = HandlesExpr.get_llvm_float_bits(arg_val.Ty) + + if HandlesExpr.is_ptr_type(arg_val.Ty) != 0: + # i8*(str/bytes)→ %s;其他指针 → 0x%llx + pointee: llvmlite.LLVMType | t.CPtr = arg_val.Ty.Pointee + if pointee is not None and HandlesExpr.get_llvm_type_bits(pointee) == 8: + token = "%s" + else: + token = "0x%llx" + arg_val = llvmlite.build_ptrtoint(builder, arg_val, i64_ty) + elif arg_bits != 0: + if arg_bits == 1: + token = "%d" + arg_val = llvmlite.build_zext(builder, arg_val, i32_ty) + elif arg_bits == 8: + token = "%d" + arg_val = llvmlite.build_sext(builder, arg_val, i32_ty) + elif arg_bits == 16: + token = "%d" + arg_val = llvmlite.build_sext(builder, arg_val, i32_ty) + elif arg_bits == 32: + token = "%d" + else: + token = "%lld" + elif arg_fbits != 0: + token = "%f" + if arg_fbits < 64: + arg_val = llvmlite.build_fpext(builder, arg_val, dbl_ty) + else: + # 结构体值/数组/函数等 → "(unknown)",不传参 + token = "(unknown)" + chain_arg = 0 + + # 追加格式 token + if string.strlen(fmt_buf) < FMT_BUF_SIZE - 16: + viperlib.snprintf(fmt_buf + string.strlen(fmt_buf), 16, "%s", token) + # 链接参数 + if chain_arg != 0: + arg_val.Next = None + if arg_tail is None: + arg_head = arg_val + else: + arg_tail.Next = arg_val + arg_tail = arg_val + arg_count += 1 + + # 追加默认换行(Python print 默认 end="\n") + if string.strlen(fmt_buf) < FMT_BUF_SIZE - 2: + viperlib.snprintf(fmt_buf + string.strlen(fmt_buf), 2, "\n") + + fmt_ptr: llvmlite.Value | t.CPtr = HandlesExpr.create_global_string( + builder, pool, mod, fmt_buf, trans) + stdlib.free(fmt_buf) + if fmt_ptr is None: + return + fmt_ptr.Next = arg_head + # is_variadic=1: 带显式函数类型签名(C ABI 变参调用) + llvmlite.build_call(builder, "printf", fmt_ptr, arg_count, i32_ty, 1) # ============================================================ @@ -6550,6 +6682,15 @@ class ExprCallHandle(HandlesBase.Mixin): self.Trans.Pool, self.Trans._cur_builder, self.Trans.Module, cl, self.Trans) + # ============================================================ + # HandlePrintCall - 翻译 print 调用 + # ============================================================ + def HandlePrintCall(self, cl: ast.Call | t.CPtr): + """翻译 print 调用(类型感知格式化 + 默认换行)""" + translate_print_call( + self.Trans.Pool, self.Trans._cur_builder, self.Trans.Module, + cl, self.Trans) + # ============================================================ # NewExprCallHandle - 工厂函数 # ============================================================ diff --git a/App/lib/core/Handles/HandlesType.vp b/App/lib/core/Handles/HandlesType.vp index de37d59..0224440 100644 --- a/App/lib/core/Handles/HandlesType.vp +++ b/App/lib/core/Handles/HandlesType.vp @@ -836,9 +836,20 @@ def fatal_error(node: ast.AST | t.CPtr, msg: str, file_name: str = _g_current_file if file_name is None: file_name = "" - stdio.printf(' File "%s", line %d\n', file_name, lineno) + # 行号合理性防御:先读取该行源码,验证 node.lineno 是否落在当前文件内。 + # 若读不到(文件不存在或行号超出范围),说明文件/行号归属不可信 + # (如 AST 树被内存复用/串扰后跨文件报错),打印警告而非误导性行号。 + src_line: str = None + line_ok: int = 1 if lineno > 0 and string.strcmp(file_name, "") != 0: - src_line: str = _read_source_line(file_name, lineno) + src_line = _read_source_line(file_name, lineno) + if src_line is None: + line_ok = 0 + if line_ok == 0: + stdio.printf(' File "%s", line %d (!! 无法读取该行源码:文件不存在或行号超出范围,错误可能来自被复用的 AST 树,请以错误消息为准)\n', + file_name, lineno) + else: + stdio.printf(' File "%s", line %d\n', file_name, lineno) if src_line is not None: stdio.printf(" %s\n", src_line) # 列范围标记:在源代码行下方用 ^ 标记出错范围