From a5ceb3d4eb9b01890d2ddc83663a2457cc2667b1 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Mon, 21 Sep 2026 16:17:19 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20JIT=20REPL?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- App/lib/Phase/BuildPipeline.vp | 53 +- App/lib/Phase/Phase1/Phase1Main.vp | 2 +- App/lib/Phase/Phase1/Phase1Scan.vp | 22 +- App/lib/Phase/Phase2/__init__.vp | 7 +- App/lib/Projectrans/Config.vp | 11 + App/lib/Stub/StubGen/Converter.vp | 171 +- App/lib/Stub/StubGen/VpiReader.vp | 1178 +++++++- App/lib/Stub/StubMerger/StubMergerStore.vp | 77 + App/lib/core/Gen/AssignGenMixin.vp | 234 +- App/lib/core/Gen/AsyncGenMixin.vp | 16 +- App/lib/core/Gen/Call/CallGenBase.vp | 24 +- App/lib/core/Gen/Call/CallGenCBuiltin.vp | 162 +- App/lib/core/Gen/Call/CallGenCall.vp | 10 +- App/lib/core/Gen/Call/CallGenCtor.vp | 33 +- App/lib/core/Gen/Call/CallGenDispatch.vp | 60 +- App/lib/core/Gen/Call/CallGenFd.vp | 2 +- App/lib/core/Gen/Call/CallGenLeafKit.vp | 139 +- App/lib/core/Gen/Call/CallGenLib.vp | 6 +- App/lib/core/Gen/Call/CallGenMixin.vp | 90 +- App/lib/core/Gen/Call/CallGenProp.vp | 2 +- App/lib/core/Gen/Call/CallGenVa.vp | 10 +- App/lib/core/Gen/Class/ClassGenBase.vp | 28 +- App/lib/core/Gen/Class/ClassGenDef.vp | 50 +- App/lib/core/Gen/Class/ClassGenEnum.vp | 22 +- App/lib/core/Gen/Class/ClassGenGeneric.vp | 75 +- App/lib/core/Gen/Class/ClassGenVTable.vp | 14 +- App/lib/core/Gen/Expr/ExprGenAttr.vp | 144 +- App/lib/core/Gen/Expr/ExprGenCls.vp | 10 +- App/lib/core/Gen/Expr/ExprGenConst.vp | 2 +- App/lib/core/Gen/Expr/ExprGenFstr.vp | 2 +- App/lib/core/Gen/Expr/ExprGenName.vp | 59 +- App/lib/core/Gen/Expr/ExprGenOps.vp | 4 +- App/lib/core/Gen/Func/FuncGenBase.vp | 14 +- App/lib/core/Gen/Func/FuncGenDef.vp | 39 +- App/lib/core/Gen/Func/FuncGenMixin.vp | 38 +- App/lib/core/Gen/GenApi.vp | 13 +- App/lib/core/Gen/GenTypes.vp | 220 ++ App/lib/core/Gen/ImportsGenMixin.vp | 12 +- App/lib/core/Gen/MatchGenMixin.vp | 56 +- App/lib/core/Gen/Stmt/StmtGenCifMixin.vp | 52 +- App/lib/core/Gen/Stmt/StmtGenDeclMixin.vp | 36 +- App/lib/core/Gen/Stmt/StmtGenMixin.vp | 4 +- App/lib/core/Gen/StructGenMixin.vp | 25 +- App/lib/core/Gen/Translator.vp | 26 +- App/lib/core/Gen/TranslatorGenMixin.vp | 141 +- App/lib/core/Gen/Type/TypeGenCDef.vp | 138 +- App/lib/core/Gen/Type/TypeGenCType.vp | 101 +- App/lib/core/Gen/Type/TypeGenErr.vp | 58 +- App/lib/core/Gen/Type/TypeGenResolve.vp | 150 +- App/lib/core/Gen/VarGenMixin.vp | 1197 ++++++++ App/lib/core/Repl.vp | 2980 ++++++++++++++++++++ App/lib/core/VLogger.vp | 66 +- App/main.vp | 76 +- README.md | 2 + project.vpj | 1 + repl.vpj | 41 + 56 files changed, 7621 insertions(+), 584 deletions(-) create mode 100644 App/lib/core/Repl.vp create mode 100644 repl.vpj diff --git a/App/lib/Phase/BuildPipeline.vp b/App/lib/Phase/BuildPipeline.vp index 033d9f9..52a0cf8 100644 --- a/App/lib/Phase/BuildPipeline.vp +++ b/App/lib/Phase/BuildPipeline.vp @@ -115,7 +115,10 @@ def TranslateFileGetTrans(mb: rawptr[memhub.MemBuddy], file_path: str, pyi_buf: bytes = stdlib.malloc(PYI_BUF_SIZE) if pyi_buf is not None: pyi_buf[0] = 0 - pyi_pos: sizet = StubConverter._GeneratePyiFromAst(mb, tree, None, pyi_buf, PYI_BUF_SIZE) + # rel_path 传 file_path(本文件绝对路径):vpi 生成期间 fatal_error + # 的报错归因需指向本源文件(Phase A 预注册/翻译时 file_path 已可用), + # 且绝对路径可被 fatal_error 的 _read_source_line 直接打开打印源码行。 + pyi_pos: sizet = StubConverter._GeneratePyiFromAst(mb, tree, file_path, pyi_buf, PYI_BUF_SIZE) if pyi_pos > 0: # 写入 {sha1}.vpi 到 temp_dir(切片路径) td_len_vpi: sizet = len(temp_dir) @@ -709,6 +712,37 @@ def collect_obj_files(includes_binary_dir: str, out_buf: bytes, out_size: sizet) return out_pos +def _append_host_export_implib(linker_flags: str, implib_path: str) -> str: + """宿主导出模式:为链接参数追加 `--out-implib=<绝对路径>`(REPL 片段导入宿主用) + + 触发条件 = linker_flags 显式含 "--export-all-symbols"(工程级开关,宿主的 + project.vpj 里声明)。此时 exe 会带导出表,REPL 片段 DLL 需要一份 import lib + 才能把 IR 里的跨模块引用(形如 `<16位hex>.<函数名>`,见 .text.ll 的 + `define i32 @".utf8_is_valid"`)解析到宿主的导出符号。 + + 为什么路径由本层计算而不写进 project.vpj:Config 对 linker.flags 既不做 + %VAR% 展开、也不按 project_dir 解析(Config.vpj:180-230 的 _join_flags_array + 只做拼接;:570-632 只重写 -T 后的脚本路径;env 展开只发生在 static_libs/ + dynamic_libs/includes/build_dir 等字段),而 out-implib 的值是相对 + clang 进程 cwd 解析的 ⇒ 只有本层能从 output_dir + linker_output 算出绝对路径。 + + 未开启工程的 linker_flags 不含该标志 ⇒ 原样返回,链接命令逐字节不变。 + """ + if linker_flags is None or implib_path is None: + return linker_flags + if string.strstr(linker_flags, "--export-all-symbols") is None: + return linker_flags + extra: sizet = string.strlen(" -Wl,--out-implib=") + string.strlen(implib_path) + 1 + buf: str = _mbuddy.alloc(string.strlen(linker_flags) + extra) + if buf is None: + VLogger.warning("宿主导出:import lib 参数缓冲分配失败(片段将无法导入宿主符号)", "link") + return linker_flags + viperlib.snprintf(buf, string.strlen(linker_flags) + extra, + "%s -Wl,--out-implib=%s", linker_flags, implib_path) + VLogger.info(f"宿主导出:产出 import lib {implib_path}", "link") + return buf + + def link_obj_to_exe(output_dir: str, module_name: str, linker_cmd: str, linker_flags: str, linker_output: str, includes_binary_dir: str, linker_static_libs: str = None, linker_dynamic_libs: str = None) -> int: """调用 clang++ 链接 .obj 为 .exe @@ -734,6 +768,16 @@ def link_obj_to_exe(output_dir: str, module_name: str, linker_cmd: str, linker_f if obj_path is None: return 1 + # 宿主 exe 导出(工程 linker.flags 含 --export-all-symbols 时)同步产出 import lib: + # {output_dir}/{linker_output}.dll.a —— REPL 片段链接时把它加进命令行即可导入 + # 宿主(同一进程/同一份 includes 代码与数据)。未开启的工程不受影响。 + if linker_output is not None: + implib_len: sizet = string.strlen(output_dir) + string.strlen(linker_output) + 8 + implib_path: str = _mbuddy.alloc(implib_len) + if implib_path is not None: + viperlib.snprintf(implib_path, implib_len, "%s/%s.dll.a", output_dir, linker_output) + linker_flags = _append_host_export_implib(linker_flags, implib_path) + # 收集 includes.binary 的 .obj 文件路径 EXTRA_BUF_SIZE: sizet = 32768 extra_objs: bytes = _mbuddy.alloc(EXTRA_BUF_SIZE) @@ -988,6 +1032,13 @@ def link_objs_to_exe(obj_paths: str, obj_paths_len: sizet, # 构造命令: clang++ obj_paths extra_objs -o linker_output linker_flags # ASan 运行库同样不再由 TPV 注入(在 link_obj_to_exe 里已有说明): # 由 project.vpj 声明,--asan 下由 main.vp 校验。 + # 宿主导出:linker_output 是完整输出路径 ⇒ import lib 取名 .dll.a + if linker_output is not None: + implib_len2: sizet = string.strlen(linker_output) + 8 + implib_path2: str = _mbuddy.alloc(implib_len2) + if implib_path2 is not None: + viperlib.snprintf(implib_path2, implib_len2, "%s.dll.a", linker_output) + linker_flags = _append_host_export_implib(linker_flags, implib_path2) use_flags: str = linker_flags static_libs2: str = linker_static_libs if static_libs2 is None: diff --git a/App/lib/Phase/Phase1/Phase1Main.vp b/App/lib/Phase/Phase1/Phase1Main.vp index e13097d..87f7927 100644 --- a/App/lib/Phase/Phase1/Phase1Main.vp +++ b/App/lib/Phase/Phase1/Phase1Main.vp @@ -1007,7 +1007,7 @@ def RunPhase1(mb: rawptr[memhub.MemBuddy], includes_dir: str, temp_dir: str, pyi_buf: bytes = stdlib.malloc(Phase1.PYI_BUF_SIZE) if pyi_buf is not None: pyi_pos: sizet = StubConverter._GeneratePyiFromAst( - mb, tree, entry.RelPath, pyi_buf, Phase1.PYI_BUF_SIZE) + mb, tree, entry.Path, pyi_buf, Phase1.PYI_BUF_SIZE) if pyi_pos > 0: pf_1b: rawptr[fileio.File] = fileio.File(vpi_path_1b, fileio.MODE.W) if not pf_1b.closed: diff --git a/App/lib/Phase/Phase1/Phase1Scan.vp b/App/lib/Phase/Phase1/Phase1Scan.vp index 656553e..fe6b54a 100644 --- a/App/lib/Phase/Phase1/Phase1Scan.vp +++ b/App/lib/Phase/Phase1/Phase1Scan.vp @@ -133,7 +133,7 @@ def _ScanClassInheritance(mb: rawptr[memhub.MemBuddy], continue # 遍历 AST 顶层节点,收集 ClassDef - ch: rawptr[list[rawptr[ast.AST]]] = tree.children + ch: list[ast.AST*]* = tree.children if ch is not None: cn: sizet = len(ch) for ci in range(cn): @@ -305,7 +305,7 @@ def _PreRegisterAllGenericTemplates(mb: rawptr[memhub.MemBuddy], stdlib.free(src_buf_pg) continue # 遍历顶层 ClassDef,注册泛型模板(type_params 非 None) - ch_pg: rawptr[list[rawptr[ast.AST]]] = tree_pg.children + ch_pg: list[ast.AST*]* = tree_pg.children if ch_pg is not None: cn_pg: sizet = len(ch_pg) for ci_pg in range(cn_pg): @@ -405,13 +405,13 @@ def _ScanTreeForContainers(node: rawptr[ast.AST], elif k_s == ast.ASTKind.Call: cl_s2: rawptr[ast.Call] = rawptr[ast.Call](node) _ScanTreeForContainers(cl_s2.func, need_list_box, need_dict_box, depth + 1) - cl_args_s: rawptr[list[rawptr[ast.AST]]] = cl_s2.args + cl_args_s: list[ast.AST*]* = cl_s2.args if cl_args_s is not None: na_s: sizet = len(cl_args_s) i_a2: sizet for i_a2 in range(na_s): _ScanTreeForContainers(cl_args_s.get(i_a2), need_list_box, need_dict_box, depth + 1) - cl_kws_s: rawptr[list[rawptr[ast.AST]]] = cl_s2.keywords + cl_kws_s: list[ast.AST*]* = cl_s2.keywords if cl_kws_s is not None: nk_s: sizet = len(cl_kws_s) i_k2: sizet @@ -422,7 +422,7 @@ def _ScanTreeForContainers(node: rawptr[ast.AST], elif k_s == ast.ASTKind.Assign: asn_s: rawptr[ast.Assign] = rawptr[ast.Assign](node) _ScanTreeForContainers(asn_s.value, need_list_box, need_dict_box, depth + 1) - tgts_s: rawptr[list[rawptr[ast.AST]]] = asn_s.targets + tgts_s: list[ast.AST*]* = asn_s.targets if tgts_s is not None: nt_s: sizet = len(tgts_s) i_t2: sizet @@ -437,7 +437,7 @@ def _ScanTreeForContainers(node: rawptr[ast.AST], _ScanTreeForContainers(fn_s.returns, need_list_box, need_dict_box, depth + 1) fn_args_s: rawptr[ast.Arguments] = rawptr[ast.Arguments](fn_s.args) if fn_args_s is not None: - fa_s: rawptr[list[rawptr[ast.AST]]] = fn_args_s.args + fa_s: list[ast.AST*]* = fn_args_s.args if fa_s is not None: nf_s: sizet = len(fa_s) i_f2: sizet @@ -447,7 +447,7 @@ def _ScanTreeForContainers(node: rawptr[ast.AST], _ScanTreeForContainers(arg_s.annotation, need_list_box, need_dict_box, depth + 1) elif k_s == ast.ASTKind.ClassDef: cd_s: rawptr[ast.ClassDef] = rawptr[ast.ClassDef](node) - bases_s: rawptr[list[rawptr[ast.AST]]] = cd_s.bases + bases_s: list[ast.AST*]* = cd_s.bases if bases_s is not None: nb_s: sizet = len(bases_s) i_b2: sizet @@ -458,7 +458,7 @@ def _ScanTreeForContainers(node: rawptr[ast.AST], _ScanTreeForContainers(at_s.value, need_list_box, need_dict_box, depth + 1) elif k_s == ast.ASTKind.Tuple: tu_s: rawptr[ast.Tuple] = rawptr[ast.Tuple](node) - elts_s: rawptr[list[rawptr[ast.AST]]] = tu_s.elts + elts_s: list[ast.AST*]* = tu_s.elts if elts_s is not None: ne_s: sizet = len(elts_s) i_e2: sizet @@ -476,7 +476,7 @@ def _ScanTreeForContainers(node: rawptr[ast.AST], elif k_s == ast.ASTKind.Compare: cp_s: rawptr[ast.Compare] = rawptr[ast.Compare](node) _ScanTreeForContainers(cp_s.left, need_list_box, need_dict_box, depth + 1) - cps_s: rawptr[list[rawptr[ast.AST]]] = cp_s.comparators + cps_s: list[ast.AST*]* = cp_s.comparators if cps_s is not None: nc_s: sizet = len(cps_s) i_c2: sizet @@ -484,7 +484,7 @@ def _ScanTreeForContainers(node: rawptr[ast.AST], _ScanTreeForContainers(cps_s.get(i_c2), need_list_box, need_dict_box, depth + 1) elif k_s == ast.ASTKind.BoolOp: bo_s: rawptr[ast.BoolOp] = rawptr[ast.BoolOp](node) - bvs_s: rawptr[list[rawptr[ast.AST]]] = bo_s.values + bvs_s: list[ast.AST*]* = bo_s.values if bvs_s is not None: nv_s: sizet = len(bvs_s) i_v2: sizet @@ -498,7 +498,7 @@ def _ScanTreeForContainers(node: rawptr[ast.AST], # need_dict_box[0] = 1 # 递归语句块子节点 if node.children is not None: - ch_s: rawptr[list[rawptr[ast.AST]]] = node.children + ch_s: list[ast.AST*]* = node.children n_s: sizet = len(ch_s) ci_s: sizet for ci_s in range(n_s): diff --git a/App/lib/Phase/Phase2/__init__.vp b/App/lib/Phase/Phase2/__init__.vp index 90d635d..794f41d 100644 --- a/App/lib/Phase/Phase2/__init__.vp +++ b/App/lib/Phase/Phase2/__init__.vp @@ -304,7 +304,7 @@ def _PreRegisterAppGenericTemplates(mb: rawptr[memhub.MemBuddy], stdlib.free(src_buf_pa) continue # 遍历顶层 ClassDef,注册泛型模板(type_params 非 None) - ch_pa: rawptr[list[rawptr[ast.AST]]] = tree_pa.children + ch_pa: list[ast.AST*]* = tree_pa.children if ch_pa is not None: cn_pa: sizet = len(ch_pa) for ci_pa in range(cn_pa): @@ -765,7 +765,8 @@ def RunMultiFileProject(mb: rawptr[memhub.MemBuddy], continue stdlib.free(vpi_path_vp2) # 读取源文件 → 解析 AST → 生成 vpi - # (rel_path 传 None:与 TranslateFileGetTrans 后续覆盖写的内容一致) + # (rel_path 传 ent_vp2.Path:vpi 内容不依赖它——与 TranslateFileGetTrans + # 后续覆盖写的内容一致;此处仅用于 fatal_error 的报错归因,指向本源文件) f_vp2: rawptr[fileio.File] = fileio.File(ent_vp2.Path, fileio.MODE.R) if f_vp2.closed: vpi_failed_p2 += 1 @@ -812,7 +813,7 @@ def RunMultiFileProject(mb: rawptr[memhub.MemBuddy], pyi_buf_vp2: bytes = stdlib.malloc(PYI_BUF_SIZE_P2) if pyi_buf_vp2 is not None: pyi_pos_vp2: sizet = StubConverter._GeneratePyiFromAst( - mb, tree_vp2, None, pyi_buf_vp2, PYI_BUF_SIZE_P2) + mb, tree_vp2, ent_vp2.Path, pyi_buf_vp2, PYI_BUF_SIZE_P2) if pyi_pos_vp2 > 0: pyi_path_vp2: str = StubMergerCollect._sliced_path(temp_dir, td_len_vpa, ent_vp2.Sha1, "vpi") if pyi_path_vp2 is not None: diff --git a/App/lib/Projectrans/Config.vp b/App/lib/Projectrans/Config.vp index bb0b330..436b7fa 100644 --- a/App/lib/Projectrans/Config.vp +++ b/App/lib/Projectrans/Config.vp @@ -241,9 +241,20 @@ def _join_path(rel: str, project_dir: str) -> str: 拼接结果经 _normalize_path 规范化:./Test/../includes → ./includes, 消除 .. 段,保证路径显示干净且相对寻址正确。 + + 含 %VAR% 的路径(如 repl.vpj 的 build_dir = %temp%/viperepl/.tpv_build) + 先经 fileio 路径层展开为实路径:TempDir/OutputDir 等由 BuildDir 派生, + 且后续消费者不止文件打开(ensure_dir 直接调 CreateDirectoryA、 + collect_obj_files 调 FindFirstFileA、命令串走 CreateProcessA), + 故展开必须发生在**配置路径设置层**而不是逐个调用点。 """ if rel is None: return None + # 环境变量展开:仅在含 '%' 时生效(fileio.ExpandEnvironmentPath 自带 + # "失败/无 '%' 返回 None" 契约),展开失败时保持原串不变 + ep: str = fileio.ExpandEnvironmentPath(rel) + if ep is not None: + rel = ep # 绝对路径(以 / 或 \ 开头)保持不变 if rel[0] == '/' or rel[0] == '\\': return rel diff --git a/App/lib/Stub/StubGen/Converter.vp b/App/lib/Stub/StubGen/Converter.vp index e9cb3b2..d7e4177 100644 --- a/App/lib/Stub/StubGen/Converter.vp +++ b/App/lib/Stub/StubGen/Converter.vp @@ -202,11 +202,41 @@ def _PyiIndent(buf: bytes, buf_size: sizet, pos: sizet, return pos +# ============================================================ +# _PyiBinOpOpText - 输出二元运算符两侧的分隔文本 +# +# 与 TPC StubGen/_GetTypeString 的 BinOp 运算符表逐字节一致(" | " / " + " / +# " - " / " * " / " / " / " % " / " & " / " >> ")。支持集必须两侧相同,否则 +# 同一份源码写出的 .vpi 注解文本不同——旧实现恒写 " | ",使 +# zhuff.vp `t.CArray[zhuff_decode_node, 2 * ZHUFF_MAX_CODES]` 写成 +# `2 | ZHUFF_MAX_CODES`(读回端按位或算成 15,正确值 30 → 数组长度错)。 +# 表外运算符 fail-fast:静默写错运算符会生成错误的类型/长度文本, +# 让下游按错误结果解析(与 TPC 落到未知节点 fail-fast 同语义)。 +# ============================================================ +def _PyiBinOpOpText(buf: bytes, buf_size: sizet, pos: sizet, + op: int, node: rawptr[ast.AST]) -> sizet: + """按运算符种类输出 " OP " 文本,表外运算符 fail-fast""" + if op == ast.OpKind.BitOr: return _PyiEmit(buf, buf_size, pos, " | ") + if op == ast.OpKind.Add: return _PyiEmit(buf, buf_size, pos, " + ") + if op == ast.OpKind.Sub: return _PyiEmit(buf, buf_size, pos, " - ") + if op == ast.OpKind.Mult: return _PyiEmit(buf, buf_size, pos, " * ") + if op == ast.OpKind.Div: return _PyiEmit(buf, buf_size, pos, " / ") + if op == ast.OpKind.Mod: return _PyiEmit(buf, buf_size, pos, " % ") + if op == ast.OpKind.BitAnd: return _PyiEmit(buf, buf_size, pos, " & ") + if op == ast.OpKind.RShift: return _PyiEmit(buf, buf_size, pos, " >> ") + GenApi.fatal_error(node, "vpi 类型注解节点类型未知", "TypeError") + return pos # 不可达(fatal_error 已退出) + + # ============================================================ # _PyiTypeStr - 递归生成类型注解字符串 # # 将 AST 类型注解节点转换为字符串表示,写入 buf 的 pos 位置。 -# 支持: Name, Attribute, Subscript, BinOp(|), Constant(前向引用), Tuple +# 支持集与 TPC StubGen/_GetTypeString 等价: +# Name, Attribute, Subscript, Tuple, BinOp, UnaryOp, Compare, BoolOp, +# Constant, List, Call +# 差异项:TPV 额外支持裸 Tuple(TPC 仅在 Subscript.slice 位置接受 Tuple); +# TPC 额外有 Index 分支(vast 遗留类,解析器不产出该节点)。 # ============================================================ def _PyiTypeStr(buf: bytes, buf_size: sizet, pos: sizet, annot: rawptr[ast.AST], @@ -248,14 +278,77 @@ def _PyiTypeStr(buf: bytes, buf_size: sizet, pos: sizet, pos = _PyiEmit(buf, buf_size, pos, "]") return pos - # BinOp: 联合类型 (如 int | str, File | t.CPtr) + # BinOp: 二元运算 (联合类型 int | str;CArray 计数表达式 MAX_ORDER + 1、 + # 2 * ZHUFF_MAX_CODES;条件宏实参 MASK_BYTE & 0x0F)。 + # 运算符文本按 _PyiBinOpOpText 输出,与 TPC 的运算符表逐字节一致。 if k == ast.ASTKind.BinOp: bop: rawptr[ast.BinOp] = rawptr[ast.BinOp](annot) pos = _PyiTypeStr(buf, buf_size, pos, bop.left, depth + 1) - pos = _PyiEmit(buf, buf_size, pos, " | ") + pos = _PyiBinOpOpText(buf, buf_size, pos, bop.op, annot) pos = _PyiTypeStr(buf, buf_size, pos, bop.right, depth + 1) return pos + # UnaryOp: 一元运算 (计数表达式 -1 / ~0;条件宏实参 not ct.isptr(x))。 + # 输出与 TPC 的 UnaryOp 分支逐字节一致:Not → "not "、Invert → "~"、 + # USub → "-";其余一元运算符两侧都不支持 → fail-fast(不静默写错符号)。 + if k == ast.ASTKind.UnaryOp: + uop_t: rawptr[ast.UnaryOp] = rawptr[ast.UnaryOp](annot) + if uop_t.op == ast.OpKind.Not: + pos = _PyiEmit(buf, buf_size, pos, "not ") + elif uop_t.op == ast.OpKind.Invert: + pos = _PyiEmit(buf, buf_size, pos, "~") + elif uop_t.op == ast.OpKind.USub: + pos = _PyiEmit(buf, buf_size, pos, "-") + else: + GenApi.fatal_error(annot, "vpi 类型注解节点类型未知", "TypeError") + return pos # 不可达(fatal_error 已退出) + return _PyiTypeStr(buf, buf_size, pos, uop_t.operand, depth + 1) + + # Compare: 比较表达式 (条件宏实参 MAX_SIZE > 512 / val == 1)。 + # 运算符文本与 TPC 的 Compare 分支逐字节一致:== != < <= > >=; + # 表外运算符(is/in 等)两侧一致输出 " ? "。 + if k == ast.ASTKind.Compare: + cmp_t: rawptr[ast.Compare] = rawptr[ast.Compare](annot) + pos = _PyiTypeStr(buf, buf_size, pos, cmp_t.left, depth + 1) + ops_ct: list[int] | t.CPtr = (list[int] | t.CPtr)(cmp_t.ops) + cmps_ct: list[ast.AST*]* = cmp_t.comparators + if ops_ct is not None: + on_ct: sizet = len(ops_ct) + oi_ct: sizet = 0 + while oi_ct < on_ct: + oid_ct: int = ops_ct.get(oi_ct) + if oid_ct == ast.OpKind.Eq: pos = _PyiEmit(buf, buf_size, pos, " == ") + elif oid_ct == ast.OpKind.Ne: pos = _PyiEmit(buf, buf_size, pos, " != ") + elif oid_ct == ast.OpKind.Lt: pos = _PyiEmit(buf, buf_size, pos, " < ") + elif oid_ct == ast.OpKind.Le: pos = _PyiEmit(buf, buf_size, pos, " <= ") + elif oid_ct == ast.OpKind.Gt: pos = _PyiEmit(buf, buf_size, pos, " > ") + elif oid_ct == ast.OpKind.Ge: pos = _PyiEmit(buf, buf_size, pos, " >= ") + else: pos = _PyiEmit(buf, buf_size, pos, " ? ") + # TPC 按 ops 的下标取 comparators[i];此处加长度守卫, + # 畸形 AST(ops/comparators 不等长)不越界。 + if cmps_ct is not None and oi_ct < len(cmps_ct): + pos = _PyiTypeStr(buf, buf_size, pos, cmps_ct.get(oi_ct), depth + 1) + oi_ct += 1 + return pos + + # BoolOp: 布尔运算 (条件宏实参 ENABLED and MAX_SIZE > 0 / A or B)。 + # 与 TPC 的 BoolOp 分支一致:and → " and "、or → " or "。 + if k == ast.ASTKind.BoolOp: + bool_t: rawptr[ast.BoolOp] = rawptr[ast.BoolOp](annot) + vals_bt: list[ast.AST*]* = bool_t.values + if vals_bt is not None: + vn_bt: sizet = len(vals_bt) + vi_bt: sizet = 0 + while vi_bt < vn_bt: + if vi_bt > 0: + if bool_t.op == ast.OpKind.Or: + pos = _PyiEmit(buf, buf_size, pos, " or ") + else: + pos = _PyiEmit(buf, buf_size, pos, " and ") + pos = _PyiTypeStr(buf, buf_size, pos, vals_bt.get(vi_bt), depth + 1) + vi_bt += 1 + return pos + # Constant: 字符串前向引用 (如 'MyClass')、None 或数字常量(CArray count) if k == ast.ASTKind.Constant: cn: rawptr[ast.Constant] = rawptr[ast.Constant](annot) @@ -280,11 +373,31 @@ def _PyiTypeStr(buf: bytes, buf_size: sizet, pos: sizet, return pos return _PyiEmit(buf, buf_size, pos, "int") + # List: 函数指针参数表 (如 t.Callable[[t.CPtr, ULONG], BOOL] 的内层 + # [t.CPtr, ULONG];t.Callable[[], int] 的空表)。TPV 的 AST 把 Callable + # 的参数表成型为 List(TypeGenResolve 的类型名提取同样按 List 递归), + # 故必须输出 "[a, b]" 形式——缺失该分支时该节点落入未知类型 fail-fast。 + if k == ast.ASTKind.List: + lst: rawptr[ast.List] = rawptr[ast.List](annot) + pos = _PyiEmit(buf, buf_size, pos, "[") + if lst.elts is not None: + lst_elts: list[ast.AST*]* = lst.elts + ln: sizet = len(lst_elts) + li: sizet = 0 + while li < ln: + if li > 0: + pos = _PyiEmit(buf, buf_size, pos, ", ") + le: rawptr[ast.AST] = lst_elts.get(li) + pos = _PyiTypeStr(buf, buf_size, pos, le, depth + 1) + li += 1 + pos = _PyiEmit(buf, buf_size, pos, "]") + return pos + # Tuple: 多参数下标 (如 Dict[str, int]) if k == ast.ASTKind.Tuple: tup: rawptr[ast.Tuple] = rawptr[ast.Tuple](annot) if tup.elts is not None: - tup_elts: rawptr[list[rawptr[ast.AST]]] = tup.elts + tup_elts: list[ast.AST*]* = tup.elts tn: sizet = len(tup_elts) ti: sizet = 0 while ti < tn: @@ -301,7 +414,7 @@ def _PyiTypeStr(buf: bytes, buf_size: sizet, pos: sizet, pos = _PyiTypeStr(buf, buf_size, pos, cl.func, depth + 1) pos = _PyiEmit(buf, buf_size, pos, "(") if cl.args is not None: - cl_args: rawptr[list[rawptr[ast.AST]]] = cl.args + cl_args: list[ast.AST*]* = cl.args can: sizet = len(cl_args) cai: sizet = 0 while cai < can: @@ -395,7 +508,7 @@ def _PyiDefaultStr(buf: bytes, buf_size: sizet, pos: sizet, # 据此合成 `-> tuple[a, b, c]`(tuple 分支同样打包成 Struct),使 stub declare 与 define 的返回类型一致。 # 返回 None = 装饰器列表里没有 CReturn。 # ============================================================ -def _PyiCreturnArgs(decorators: rawptr[list[rawptr[ast.AST]]]) -> rawptr[list[rawptr[ast.AST]]]: +def _PyiCreturnArgs(decorators: list[ast.AST*]*) -> list[ast.AST*]*: """在装饰器列表里找 @c.CReturn(...),返回其实参列表;未找到返回 None""" if decorators is None: return None @@ -426,7 +539,7 @@ def _PyiFunction(buf: bytes, buf_size: sizet, pos: sizet, """生成函数签名到 buf,返回新的 pos""" # 装饰器 if fn.decorator_list is not None: - fn_decorator_list: rawptr[list[rawptr[ast.AST]]] = fn.decorator_list + fn_decorator_list: list[ast.AST*]* = fn.decorator_list dn: sizet = len(fn_decorator_list) di: sizet = 0 while di < dn: @@ -452,12 +565,12 @@ def _PyiFunction(buf: bytes, buf_size: sizet, pos: sizet, # 位置参数 if args.args is not None: - args_args: rawptr[list[rawptr[ast.AST]]] = args.args + args_args: list[ast.AST*]* = args.args an: sizet = len(args_args) # 默认值右对齐到 args 尾部:defaults[i] 对应 args[an - dn + i]。 # 必须输出 '= ',否则 VpiReader 解析出的默认参数表无条目,跨模块缺参调用(如 depth=0)读到栈垃圾。 # arg_defaults 必须内联声明:联合接收者的 .get 会被误分派,预声明 + 重赋值会解析成 list[int].get 返回 i32 截断指针而崩溃。 - arg_defaults: rawptr[list[rawptr[ast.AST]]] = args.defaults + arg_defaults: list[ast.AST*]* = args.defaults dn: sizet = 0 if arg_defaults is not None: dn = len(arg_defaults) @@ -508,7 +621,7 @@ def _PyiFunction(buf: bytes, buf_size: sizet, pos: sizet, pos = _PyiTypeStr(buf, buf_size, pos, fn.returns) else: # 无 `->` 注解但带 @c.CReturn(...):合成 " -> tuple[a, b, c]"(N11) - cre_args: rawptr[list[rawptr[ast.AST]]] = _PyiCreturnArgs(fn.decorator_list) + cre_args: list[ast.AST*]* = _PyiCreturnArgs(fn.decorator_list) if cre_args is not None: cre_n: sizet = len(cre_args) if cre_n > 0: @@ -538,7 +651,7 @@ def _PyiClass(buf: bytes, buf_size: sizet, pos: sizet, """生成类定义到 buf,返回新的 pos""" # 装饰器 if cls.decorator_list is not None: - cls_decorator_list: rawptr[list[rawptr[ast.AST]]] = cls.decorator_list + cls_decorator_list: list[ast.AST*]* = cls.decorator_list dcn: sizet = len(cls_decorator_list) dci: sizet = 0 while dci < dcn: @@ -557,7 +670,7 @@ def _PyiClass(buf: bytes, buf_size: sizet, pos: sizet, # PEP 695 类型参数 (class Foo[T]:) if cls.type_params is not None: - cls_type_params: rawptr[list[rawptr[ast.AST]]] = cls.type_params + cls_type_params: list[ast.AST*]* = cls.type_params tpn: sizet = len(cls_type_params) if tpn > 0: pos = _PyiEmit(buf, buf_size, pos, "[") @@ -573,7 +686,7 @@ def _PyiClass(buf: bytes, buf_size: sizet, pos: sizet, # 基类 if cls.bases is not None: - cls_bases: rawptr[list[rawptr[ast.AST]]] = cls.bases + cls_bases: list[ast.AST*]* = cls.bases bn: sizet = len(cls_bases) if bn > 0: pos = _PyiEmit(buf, buf_size, pos, "(") @@ -592,7 +705,7 @@ def _PyiClass(buf: bytes, buf_size: sizet, pos: sizet, has_member: int = 0 if cls.children is not None: - cls_children: rawptr[list[rawptr[ast.AST]]] = cls.children + cls_children: list[ast.AST*]* = cls.children cn: sizet = len(cls_children) ci: sizet = 0 while ci < cn: @@ -606,7 +719,7 @@ def _PyiClass(buf: bytes, buf_size: sizet, pos: sizet, # __init__ 方法:先提取 self.x: Type 属性 if mfn.name is not None and string.strcmp(mfn.name, "__init__") == 0: if mfn.children is not None: - mfn_children: rawptr[list[rawptr[ast.AST]]] = mfn.children + mfn_children: list[ast.AST*]* = mfn.children icn: sizet = len(mfn_children) ici: sizet = 0 while ici < icn: @@ -663,7 +776,7 @@ def _PyiClass(buf: bytes, buf_size: sizet, pos: sizet, # ============================================================ # _GeneratePyiFromAst - 遍历 AST Module 节点,提取函数签名/类定义/变量声明/import 语句,生成 .vpi 存根文件内容到 buf # -# Args: mb 内存池(保留以符合调用约定,实际未使用);tree AST Module 根节点;rel_path 相对路径(用于模块名,当前未使用);buf 输出缓冲区;buf_size 缓冲区大小 +# Args: mb 内存池(保留以符合调用约定,实际未使用);tree AST Module 根节点;rel_path 源文件路径(仅用于报错归因的"当前文件",应传绝对路径以便 fatal_error 读取源码行);buf 输出缓冲区;buf_size 缓冲区大小 # Returns: 写入的字节数(0 表示失败) # ============================================================ def _GeneratePyiFromAst(mb: rawptr[memhub.MemBuddy], tree: rawptr[ast.AST], @@ -673,13 +786,25 @@ def _GeneratePyiFromAst(mb: rawptr[memhub.MemBuddy], tree: rawptr[ast.AST], if tree is None or buf is None or buf_size == 0: return 0 + # 错误归因:把"当前文件"指向正在生成 stub 的这份(rel_path 非 None 时)。 + # 本函数内触发的 fatal_error(如 L227/L303/L430 的注解节点类型未知)应指向 + # 本源文件,而非上一次 set_current_file 的残留文件。 + # 调用侧必须传**绝对路径**:fatal_error 的 _read_source_line 直接用该路径 + # 打开文件读取行,传相对路径会打不开 ⇒ 误报"无法读取该行源码"。 + # 放在函数入口而非各分支:全函数共用同一套遍历/报错路径,逐分支设置会漏掉 + # 中途新增的分支;入口设一次覆盖全部(含后续所有 fatal_error / push_trace)。 + # rel_path 为 None 时不改写 CurrentFile(保持上一次设置值):调用侧确实拿不到 + # 路径时,宁可沿用"上一次设置的文件"也不要把归因抹成 None。 + if rel_path is not None: + GenApi.set_current_file_type(rel_path) + pos: sizet = 0 # 预处理:按声明顺序注册本文件 CDefine 到 _g_pyi 缓存(vpi 生成早于翻译阶段注册,直接查全局表为空会使 BinOp 内 Name 引用退化为 0,跨模块读回错误常量)。 # 必须带 global 声明:TPV 语义下函数内对模块级变量赋值无 global 时会创建局部变量,_lookup_cdefine_for_pyi 读到的仍是模块级 None,缓存永不命中。 global _g_pyi_cdef_names, _g_pyi_cdef_vals, _g_pyi_cdef_count if tree.children is not None: - pre_children: rawptr[list[rawptr[ast.AST]]] = tree.children + pre_children: list[ast.AST*]* = tree.children pcn: sizet = len(pre_children) # 第一遍:数本文件 t.CDefine 声明数(C 数组需先定长分配) _g_pyi_cdef_count = 0 @@ -724,7 +849,7 @@ def _GeneratePyiFromAst(mb: rawptr[memhub.MemBuddy], tree: rawptr[ast.AST], buf[pos] = 0 return pos - tree_children: rawptr[list[rawptr[ast.AST]]] = tree.children + tree_children: list[ast.AST*]* = tree.children n: sizet = len(tree_children) # 第一遍扫描:检测 import t/c @@ -735,7 +860,7 @@ def _GeneratePyiFromAst(mb: rawptr[memhub.MemBuddy], tree: rawptr[ast.AST], if stmt is not None and stmt.kind() == ast.ASTKind.Import: imp: rawptr[ast.Import] = rawptr[ast.Import](stmt) if imp.names is not None: - imp_names: rawptr[list[rawptr[ast.AST]]] = imp.names + imp_names: list[ast.AST*]* = imp.names an: sizet = len(imp_names) for ai in range(an): al: rawptr[ast.Alias] = rawptr[ast.Alias](imp_names.get(ai)) @@ -766,7 +891,7 @@ def _GeneratePyiFromAst(mb: rawptr[memhub.MemBuddy], tree: rawptr[ast.AST], imp2: rawptr[ast.Import] = rawptr[ast.Import](stmt) pos = _PyiEmit(buf, buf_size, pos, "import ") if imp2.names is not None: - imp2_names: rawptr[list[rawptr[ast.AST]]] = imp2.names + imp2_names: list[ast.AST*]* = imp2.names an2: sizet = len(imp2_names) ai2: sizet = 0 while ai2 < an2: @@ -793,7 +918,7 @@ def _GeneratePyiFromAst(mb: rawptr[memhub.MemBuddy], tree: rawptr[ast.AST], pos = _PyiEmit(buf, buf_size, pos, ifr.module) pos = _PyiEmit(buf, buf_size, pos, " import ") if ifr.names is not None: - ifr_names: rawptr[list[rawptr[ast.AST]]] = ifr.names + ifr_names: list[ast.AST*]* = ifr.names an3: sizet = len(ifr_names) ai3: sizet = 0 while ai3 < an3: @@ -884,7 +1009,7 @@ def _GeneratePyiFromAst(mb: rawptr[memhub.MemBuddy], tree: rawptr[ast.AST], # 提取第一个参数的整数值并输出 call_cv: rawptr[ast.Call] = rawptr[ast.Call](aa.value) if call_cv.args is not None: - call_args_cv: rawptr[list[rawptr[ast.AST]]] = call_cv.args + call_args_cv: list[ast.AST*]* = call_cv.args if len(call_args_cv) > 0: first_arg: rawptr[ast.AST] = call_args_cv.get(0) if first_arg is not None: @@ -952,7 +1077,7 @@ def _GeneratePyiFromAst(mb: rawptr[memhub.MemBuddy], tree: rawptr[ast.AST], elif k == ast.ASTKind.Assign: asg: rawptr[ast.Assign] = rawptr[ast.Assign](stmt) if asg.targets is not None and asg.value is not None: - asg_targets: rawptr[list[rawptr[ast.AST]]] = asg.targets + asg_targets: list[ast.AST*]* = asg.targets atn: sizet = len(asg_targets) if atn == 1: atg: rawptr[ast.AST] = asg_targets.get(0) diff --git a/App/lib/Stub/StubGen/VpiReader.vp b/App/lib/Stub/StubGen/VpiReader.vp index 0471ff3..263b607 100644 --- a/App/lib/Stub/StubGen/VpiReader.vp +++ b/App/lib/Stub/StubGen/VpiReader.vp @@ -18,6 +18,77 @@ import lib.Stub.StubMerger.StubMergerStore as StubMergerStore import lib.Projectrans.Config as Config +# ============================================================ +# REPL 存根布局恢复:常量与类表条目(实现见文件末同名段) +# +# 定义前置(先于 `_inject_class_struct_declare` 的 REPL 钩子使用):类型注解必须 +# 在首次使用之前可见,故常量与条目类放在文件头部,函数体统一放在文件末。 +# 该机制仅在 ReplStubLayoutMode != 0(REPL 会话初始化)时生效,批处理恒 0。 +# ============================================================ + +_REPL_STUB_CLS_MAX: t.CDefine = 1024 +_REPL_STUB_FIELD_MAX: int = 128 +_REPL_STUB_VT_MAX: int = 128 +_REPL_STUB_NAME_CAP: sizet = 256 +_REPL_STUB_TXT_CAP: sizet = 4194304 +_REPL_STUB_WARN_MAX: int = 10 +_REPL_STUB_FINISH_PASSES: int = 8 + +# ReplStubClass.LayoutCount 的两个哨兵值(**不是字段数**) +_REPL_STUB_NOCOUNT_NOCLASS: int = -1 # 存根文本里没有该 key 的 `= type` 行(或整份存根没读到) +_REPL_STUB_NOCOUNT_OPAQUE: int = -2 # 存根里该 key 是 opaque(无 body) + +# 布局"不可用 ⇒ 明确拒绝"的原因码(_repl_stub_warn_nodata):把"数据缺失" +# (上层数据没到位,补存根/重跑即可消除)与"判据拒绝"(数据在但不可信,绝不放行) +# 在文案与汇总计数上分开 —— 两类都会走到"不登记 StructEntry",但成因完全不同。 +_REPL_SU_NOSTUB: t.CDefine = 1 # 数据缺失:整个模块没有 .stub.ll 文件 +_REPL_SU_NOCLASS: t.CDefine = 2 # 判据拒绝:存根存在但无该类的 `= type` 行 +_REPL_SU_OPAQUE: t.CDefine = 3 # 判据拒绝:存根里该类为 opaque(无 body) +_REPL_SU_PARENT: t.CDefine = 4 # 上游无数据:父类布局不可用/未在会话注册(走 _repl_stub_warn_parent) + +# ReplStubClass.Flags / MethodFlags 位 +_REPL_SF_CVTABLE: t.CDefine = 1 +_REPL_SF_NOVTABLE: t.CDefine = 2 +_REPL_SF_ANYFUNC: t.CDefine = 4 +_REPL_SF_PACKED: t.CDefine = 8 + +# ReplStubClass.State +_REPL_SS_REFUSED: t.CDefine = 0 # 布局不可信:永不登记(字段访问明确失败) +_REPL_SS_OK: t.CDefine = 1 # 完整布局已登记 +_REPL_SS_PENDING: t.CDefine = 2 # 父类尚未就绪:留待收尾遍 +_REPL_SS_EMPTY: t.CDefine = 3 # 无父类/无虚表/字段类型不可解析:交既有 vpi 注入口径 + +class ReplStubClass: + Key: str # ".<类名>"(= 定义侧命名结构体名) + ClassName: str + Sha1: str + Flags: int + OwnNames: sizet | t.CPtr # 元素 = str 指针(本类字段名,AnnAssign 顺序) + OwnTypes: sizet | t.CPtr # 元素 = LLVMType 指针 + OwnCount: int + MethodNames: sizet | t.CPtr + MethodFlags: sizet | t.CPtr + MethodCount: int + Bases: sizet | t.CPtr + BaseCount: int + LayoutCount: int # 存根布局字段数(>=0 = 命中该类的 `= type {…}`;负数 = **哨兵,不是字段数**, + # 见 _REPL_STUB_NOCOUNT_NOCLASS/-OPAQUE:存根里没这个类 / 没读到存根 / opaque) + VtArity: int # 存根 .vtable 全局槽数(0 = 存根无该全局) + Names: sizet | t.CPtr + Types: sizet | t.CPtr + Count: int + HasVt: int + IsNoVTable: int + IsOOP: int + HasInit: int + HasNew: int + VtNames: sizet | t.CPtr + VtCount: int + VtTrust: int # 1 = 虚表槽数已与存根一致(可登记 VTableMethods) + OwnFail: int # 1 = 本类有字段注解解析失败 ⇒ OwnCount 只是"已解析字段数"(不完整) + State: int + + # ============================================================ # 方法返回注解缓存(stdlib 持久) # @@ -1785,7 +1856,7 @@ def _vpi_register_typedef_closure(pool: rawptr[memhub.MemBuddy], tokens_r: rawptr[ast.Token] = ast.tokenize(lx_r) tree_r: rawptr[ast.AST] = ast.parse_tokens(pool, tokens_r) if tree_r is not None and tree_r.children is not None: - ch_r: rawptr[list[rawptr[ast.AST]]] = tree_r.children + ch_r: list[ast.AST*]* = tree_r.children cn_r: sizet = len(ch_r) # 收集子模块 imports(递归闭包):两遍处理——先累计所需长度,再按精确长度分配后填充,不存在长度上限,故无需丢弃。 # (定长缓冲 + "装不下就静默丢弃" 会让闭包缺模块 ⇒ 该模块的 CTypedef/CDefine 不可见 ⇒ 注解解析退化。) @@ -1801,7 +1872,6 @@ def _vpi_register_typedef_closure(pool: rawptr[memhub.MemBuddy], imp_c: rawptr[ast.Import] = rawptr[ast.Import](child_c) if imp_c.names is not None: in_nc: sizet = len(imp_c.names) - ii_c: sizet for ii_c in range(in_nc): al_c: rawptr[ast.Alias] = rawptr[ast.Alias](imp_c.names.get(ii_c)) if al_c is not None and al_c.name is not None: @@ -1960,7 +2030,7 @@ def _ParseVpiIntoModuleImpl(pool: rawptr[memhub.MemBuddy], _cache_vpi_param_annots_text(vpi_buf, src_sha1) # 3. 遍历 AST 顶层节点 - ch: rawptr[list[rawptr[ast.AST]]] = tree.children + ch: list[ast.AST*]* = tree.children if ch is None: if src_sha1 is not None: stdlib.free(src_sha1) @@ -2131,6 +2201,16 @@ def _ParseVpiIntoModuleImpl(pool: rawptr[memhub.MemBuddy], funcs_ptr: rawptr[GenTypes.FuncEntry] = trans._funcs max_funcs: int = 1024 + # ============================================================ + # REPL(ReplStubLayoutMode):先从 .stub.ll 恢复各类的**完整布局** + # (__vtable__ + 父类展平字段 + 本类字段)与虚表方法索引,再走下方既有 + # vpi 注入(已登记者只标可见)。详见文件末"REPL:includes 类完整布局与 + # 虚表恢复"段;批处理该标志恒 0,本块不执行。 + # ============================================================ + if ReplStubLayoutMode != 0: + _repl_stub_collect_module(pool, mod, ch, n, trans, src_sha1, + stub_imported_modules, stub_from_imports) + # ============================================================ # 第一遍 A:先注册**所有** ClassDef(字段 + 结构体),不处理方法。 # 否则类定义位于全局变量之后时(如 devfs.vp 的 `directories: t.CArray[devfs_directory, MAX]` 在 `class devfs_directory` 之前), @@ -2173,7 +2253,7 @@ def _ParseVpiIntoModuleImpl(pool: rawptr[memhub.MemBuddy], tp_stub1: list[str] | t.CPtr = class_cd1.type_params if tp_stub1 is not None and len(tp_stub1) > 0: continue - cls_children1: rawptr[list[rawptr[ast.AST]]] = class_cd1.children + cls_children1: list[ast.AST*]* = class_cd1.children if cls_children1 is None: continue cn21: sizet = len(cls_children1) @@ -2434,7 +2514,7 @@ def _vpi_tattr_name(node: rawptr[ast.AST], attr_name: str) -> int: return 0 -def _vpi_class_has_packed(decorator_list: rawptr[list[rawptr[ast.AST]]]) -> int: +def _vpi_class_has_packed(decorator_list: list[ast.AST*]*) -> int: """检查 vpi ClassDef 装饰器列表是否含 t.attr.packed""" if decorator_list is None: return 0 @@ -2475,7 +2555,7 @@ def _vpi_class_is_renum(class_cd: rawptr[ast.ClassDef]) -> int: """ if class_cd is None or class_cd.bases is None: return 0 - bases: rawptr[list[rawptr[ast.AST]]] = class_cd.bases + bases: list[ast.AST*]* = class_cd.bases bn: sizet = len(bases) for bi in range(bn): base_node: rawptr[ast.AST] = bases.get(bi) @@ -2515,6 +2595,20 @@ def _inject_class_struct_declare(pool: rawptr[memhub.MemBuddy], """从 vpi ClassDef 注册类为 struct 类型(含字段),使同 vpi 内裸类名注解可解析""" if pool is None or mod is None or class_cd is None or cls_name is None: return 0 + # REPL(ReplStubLayoutMode,见文件末"REPL:includes 类完整布局与虚表恢复"): + # 本类若已由 .stub.ll 驱动的路径接管 ⇒ 这里整类让位——已登记(或本遍就地 + # 登记成功)只标可见;父类未就绪 ⇒ 本遍不登记(收尾遍补齐);布局不可信 ⇒ + # 不登记(片段侧字段访问明确失败,绝不按错偏移静默访问)。 + # State=EMPTY 的类(无父类/无虚表/字段类型不可解析)不在此列,仍走下方既有口径。 + if ReplStubLayoutMode != 0: + se: rawptr[ReplStubClass] = _repl_stub_find( + _repl_stub_key(pool, src_sha1, cls_name)) + if se is not None and se.State != _REPL_SS_EMPTY: + if se.State == _REPL_SS_PENDING: + _repl_stub_try(se, mod) + if se.State == _REPL_SS_OK: + GenApi.add_visible_struct(pool, cls_name) + return 1 # 1. 已注册检查(含源模块编译时注册的 entry):仅标记可见,避免重复注册 if src_sha1 is not None and src_sha1[0] != '\0': existing_c: rawptr[GenTypes.StructEntry] = GenApi.find_struct_by_module(cls_name, src_sha1) @@ -2536,7 +2630,7 @@ def _inject_class_struct_declare(pool: rawptr[memhub.MemBuddy], string.memset(field_names_buf, 0, 8 * GenTypes.FIELD_MAX) string.memset(field_types_buf, 0, 8 * GenTypes.FIELD_MAX) field_count: int = 0 - cls_children_c: rawptr[list[rawptr[ast.AST]]] = class_cd.children + cls_children_c: list[ast.AST*]* = class_cd.children if cls_children_c is not None: cn_c: sizet = len(cls_children_c) for ci_c in range(cn_c): @@ -2646,8 +2740,7 @@ def _ensure_full_value_types(mod: rawptr[llvmlite.LLVMModule], pool: rawptr[memhub.MemBuddy], ty: rawptr[llvmlite.LLVMType]) -> None: """递归为值 struct / 数组元素 struct 注册完整定义(递归深度守卫)""" - if mod is None or pool is None or ty is None: - return + if mod is None or pool is None or ty is None: return match ty: case llvmlite.LLVMType.Struct(_, _, _): full_se_h: rawptr[GenTypes.StructEntry] = GenApi.find_struct_by_type(ty) @@ -2921,7 +3014,7 @@ def _inject_function_declare(pool: rawptr[memhub.MemBuddy], if fd.args is not None: args_node: rawptr[ast.Arguments] = rawptr[ast.Arguments](fd.args) if args_node.args is not None: - args_list: rawptr[list[rawptr[ast.AST]]] = args_node.args + args_list: list[ast.AST*]* = args_node.args param_count = len(args_list) # 参数数上限哨兵(fail-fast):vpi tree 深层节点在遍历处理过程中 # 可能被覆盖损坏(见本文件顶部注释),fd.args 悬空时 __len__() @@ -2931,7 +3024,8 @@ def _inject_function_declare(pool: rawptr[memhub.MemBuddy], stdio.printf( "[VPIS] 参数数异常: 函数名=%s param_count=%d(fd.args=%llu,tree 可能被覆盖,终止)\n", fd.name, param_count, uint64(fd.args)) - stdlib.exit(1) + # 终止点收敛:REPL 会话内 longjmp 回 REPL 循环(诊断已打印) + VLogger.repl_abort() pi: sizet for pi in range(param_count): ag: rawptr[ast.Arg] = rawptr[ast.Arg](args_list.get(pi)) @@ -2994,7 +3088,7 @@ def _inject_function_declare(pool: rawptr[memhub.MemBuddy], # 列表注册默认参数表——跨模块缺参调用(如 resolve_annotation_type 的 # depth=0)才能按表填充,否则被调函数读到栈垃圾(假报递归过深/崩溃)。 if args_node.defaults is not None: - fd_dlist: rawptr[list[rawptr[ast.AST]]] = args_node.defaults + fd_dlist: list[ast.AST*]* = args_node.defaults fd_default_count = len(fd_dlist) if fd_default_count < 0 or fd_default_count > 128: # vpi tree 深层节点(args.defaults)可能被覆盖损坏,len 返回 @@ -3104,7 +3198,7 @@ def _inject_method_declare(pool: rawptr[memhub.MemBuddy], if m_fd.args is not None: args_node: rawptr[ast.Arguments] = rawptr[ast.Arguments](m_fd.args) if args_node.args is not None: - args_list: rawptr[list[rawptr[ast.AST]]] = args_node.args + args_list: list[ast.AST*]* = args_node.args param_count = len(args_list) # 参数数上限哨兵(fail-fast):与 _inject_function_declare 同因 # (vpi tree 深层节点被覆盖损坏时 __len__() 返回垃圾大数)。 @@ -3112,7 +3206,8 @@ def _inject_method_declare(pool: rawptr[memhub.MemBuddy], stdio.printf( "[VPIS] 参数数异常: 方法名=%s.%s param_count=%d(m_fd.args=%llu,tree 可能被覆盖,终止)\n", cls_name, m_fd.name, param_count, uint64(m_fd.args)) - stdlib.exit(1) + # 终止点收敛:REPL 会话内 longjmp 回 REPL 循环(诊断已打印) + VLogger.repl_abort() pi: sizet for pi in range(param_count): ag: rawptr[ast.Arg] = rawptr[ast.Arg](args_list.get(pi)) @@ -3196,7 +3291,7 @@ def _inject_method_declare(pool: rawptr[memhub.MemBuddy], if m_fd.args is not None: md_args_node: rawptr[ast.Arguments] = rawptr[ast.Arguments](m_fd.args) if md_args_node.defaults is not None: - md_dlist: rawptr[list[rawptr[ast.AST]]] = md_args_node.defaults + md_dlist: list[ast.AST*]* = md_args_node.defaults md_default_count = len(md_dlist) if md_default_count < 0 or md_default_count > 128: # 同 _inject_function_declare:vpi tree 深层节点被覆盖时 @@ -3253,3 +3348,1056 @@ def _register_ctypedef_from_annassign(pool: rawptr[memhub.MemBuddy], return # 注册 CTypedef GenApi.register_ctypedef(pool, tgt.id, type_str) + + +# ============================================================ +# REPL:includes 类**完整布局**与虚表恢复(.stub.ll 驱动) +# +# 为什么需要(M4 遗留缺口):会话初始化用 .vpi 注入注册 includes 类,而 .vpi 只含 +# "类名 + 本类 AnnAssign 字段 + 方法签名" ⇒ **父类字段**与 **__vtable__ 占位 +# 字段**全部缺失 ⇒ 带父类/虚表的类在片段侧的字段索引与宿主真实布局错位 +# (静默读错字段)。 +# +# 布局真源 = .stub.ll(批处理落盘点:Phase1Main.vp:1078 / Phase2/__init__.vp:1050 +# 用 tr.dump_ir(..., llvmlite.OUTPUT_STUB)): +# - `%"." = type { ... }` 是**展平后**的完整布局(ClassGenDef 的 +# 字段展平:__vtable__ 首字段 + 父类字段 + 本类字段); +# - `@.....vtable` 全局的类型槽数 = 该类的虚方法数 +# (ClassGenVTable._generate_vtable 按 _collect_vtable_methods 的顺序发射)。 +# +# 本段仅在 ReplStubLayoutMode != 0(REPL 会话初始化)时生效,批处理永不置位: +# ① 在 _inject_class_struct_declare **之前**登记完整布局:字段名/类型与 +# ClassGenDef 同源(父类字段来自已在会话登记的父条目=字段展平;本类字段 +# 来自本 vpi 的 AnnAssign,走同一条 resolve_annotation_type); +# ② 布局由 .stub.ll 的**字段数**校验:Count(名单) 必须等于存根的字段数 +# (有虚表时 +1 = __vtable__);不符即判"布局不可信"; +# ③ 虚表:HasVTable 与虚方法**短名顺序**按 ClassGenDef._detect_vtable_status / +# ClassGenBase._should_method_be_virtual 的规则转写(父类虚方法在前、本类 +# 合格方法按类体顺序追加、按短名去重),并用存根 .vtable 槽数校验; +# 槽数不符 ⇒ 不登记 VTableMethods(退化为直接调用 {sha1}.{Class}.{m})。 +# +# 拒绝策略(能报错就别静默):布局不可信 ⇒ **不登记**该类的 StructEntry ⇒ 片段侧 +# 字段访问报"类未注册/属性访问失败",绝不按错偏移静默访问;方法调用不受影响 +# (self 只是指针,方法体在宿主里按真实布局编译,符号经宿主 import lib 解析)。 +# +# 拒绝的两类成因必须分开(见 _REPL_SU_* 与 _repl_stub_warn_nodata): +# - 『数据缺失』= LayoutCount 只是哨兵(本模块没有 .stub.ll 文件 / 存根里查不到该 +# 类),任一侧无数据 ⇒ **不比较**、也就不存在"字段数不符"这回事;文案与汇总计数 +# 都写明成因,避免"-1 vs 推断 -1"这种两侧都无数据却像在比较的误导输出; +# - 『判据拒绝』= 数据在但不可信(字段数不符 / opaque / 父类被拒),一律不放行。 +# 两者都**不登记**(M4.2 原则不变),差别只在"能不能靠补数据消除"。 +# +# 逐模块隔离:全部工作发生在一次 .vpi 注入内(调用方已有 per-module _setjmp 兜底), +# 单模块失败只告警 + 该类不可用,不终止会话;配合 ReplInitIncludesClasses 的 +# 多遍收敛,跨模块父类在后续遍就绪后即可登记(存根元数据已缓存在类表,无需重解析)。 +# ============================================================ + +# 会话级状态(PermPool;会话初始化期间有效) +ReplStubLayoutMode: int = 0 +ReplStubPool: rawptr[memhub.MemBuddy] = None +ReplStubTempDir: str = None +ReplStubTable: sizet | t.CPtr = None +ReplStubCount: int = 0 +ReplStubOk: int = 0 +ReplStubVtOk: int = 0 +ReplStubRefused: int = 0 +ReplStubRefusedNoStub: int = 0 # 拒绝原因分布(_REPL_SU_*):数据缺失(模块无 .stub.ll) +ReplStubRefusedNoClass: int = 0 # 判据拒绝:存根有该模块但无此类 +ReplStubRefusedOpaque: int = 0 # 判据拒绝:存根里该类 opaque +ReplStubRefusedParent: int = 0 # 上游无数据:父类不可用/未注册 +ReplStubNoStub: int = 0 +ReplStubWarnLeft: int = 0 +ReplStubTmpCount: int = -1 +ReplStubTmpPacked: int = 0 +ReplStubTmpArity: int = 0 +ReplStubNameBuf: str = None + + +def _repl_stub_dupstr(pool: rawptr[memhub.MemBuddy], src: str) -> str: + """把字符串深拷贝到指定池""" + if src is None or pool is None: + return None + ln: sizet = string.strlen(src) + dst: str = pool.alloc(ln + 1) + if dst is None: + return None + string.strcpy(dst, src) + return dst + + +def _repl_stub_key(pool: rawptr[memhub.MemBuddy], sha1: str, cls: str) -> str: + """拼接 .<类名>:与定义侧 ClassGenDef 的命名结构体名逐字一致""" + if pool is None or sha1 is None or cls is None: + return None + if sha1[0] == '\0' or cls[0] == '\0': + return None + ln: sizet = string.strlen(sha1) + 1 + string.strlen(cls) + 1 + buf: str = pool.alloc(ln) + if buf is None: + return None + viperlib.snprintf(buf, ln, "%s.%s", sha1, cls) + return buf + + +def _repl_stub_path(sha1: str, ext: str) -> str: + """REPL 临时目录下的切片路径 //.(stdlib 分配)""" + if ReplStubTempDir is None or sha1 is None: + return None + td_len: sizet = string.strlen(ReplStubTempDir) + sh_len: sizet = string.strlen(sha1) + sub: str = Config.slice_subdir(sha1, Config.Sha1SliceLevel) + sub_len: sizet = 0 + if sub is not None: + sub_len = string.strlen(sub) + cap: sizet = td_len + sub_len + sh_len + 8 + buf: str = stdlib.malloc(cap) + if buf is None: + return None + if sub_len > 0: + viperlib.snprintf(buf, cap, "%s/%s/%s.%s", ReplStubTempDir, sub, sha1, ext) + else: + viperlib.snprintf(buf, cap, "%s/%s.%s", ReplStubTempDir, sha1, ext) + return buf + + +def _repl_stub_readtext(path: str) -> str: + """读入存根文本(stdlib.malloc,调用方 free);不存在/超限返回 None""" + if path is None: + return None + f: rawptr[fileio.File] = fileio.File(path, fileio.MODE.R) + if f is None or f.closed: + return None + sz: sizet = f.size() + if sz <= 0 or sz >= _REPL_STUB_TXT_CAP: + f.close() + return None + buf: str = stdlib.malloc(sz + 1) + if buf is None: + f.close() + return None + rd: LONG = f.read_all(buf, sz + 1) + f.close() + if rd <= 0: + stdlib.free(buf) + return None + if rd < LONG(sz + 1): + buf[rd] = 0 + return buf + + +# 从 txt[pos] 起取一个标识符(跳过 '%' / '@' 前缀与可选引号)写进 ReplStubNameBuf; +# 返回名字之后的位置(0 = 失败/超长;合法调用至少前进 2 字符,故 0 可作失败哨兵) +def _repl_stub_take_name(txt: str, pos: sizet) -> sizet: + global ReplStubNameBuf + if txt is None or ReplStubNameBuf is None: + return 0 + n: sizet = string.strlen(txt) + i: sizet = pos + if i < n and (txt[i] == '%' or txt[i] == '@'): + i += 1 + if i >= n: + return 0 + if txt[i] == '\"': + i += 1 + k: sizet = 0 + while i < n and txt[i] != '\"': + if k + 1 >= _REPL_STUB_NAME_CAP: + return 0 + ReplStubNameBuf[k] = txt[i] + k += 1 + i += 1 + if i >= n: + return 0 + ReplStubNameBuf[k] = 0 + return i + 1 + k2: sizet = 0 + while i < n: + ch: char = txt[i] + if ch == ' ' or ch == '\t' or ch == '=' or ch == ',' or ch == '{' or ch == '}': + break + if ch == '[' or ch == ']' or ch == '(' or ch == ')' or ch == '*' or ch == '<' or ch == '>': + break + if k2 + 1 >= _REPL_STUB_NAME_CAP: + return 0 + ReplStubNameBuf[k2] = ch + k2 += 1 + i += 1 + if k2 == 0: + return 0 + ReplStubNameBuf[k2] = 0 + return i + + +# 从 txt[pos](应指向 '{')起数顶层字段数:{} / [] / () / <> / "..." 内的逗号不计; +# 返回字段数(-1 = 未闭合/格式异常) +def _repl_stub_body_count(txt: str, pos: sizet) -> int: + if txt is None: + return -1 + n: sizet = string.strlen(txt) + i: sizet = pos + if i >= n or txt[i] != '{': + return -1 + i += 1 + depth: int = 0 + in_str: int = 0 + cnt: int = 0 + has: int = 0 + while i < n: + ch: char = txt[i] + if in_str != 0: + if ch == '\"': + in_str = 0 + i += 1 + continue + if ch == '\"': + in_str = 1 + has = 1 + i += 1 + continue + if ch == 0: + return -1 + if ch == ',': + if depth == 0: + cnt += 1 + i += 1 + continue + if ch == '{' or ch == '[' or ch == '(' or ch == '<': + depth += 1 + has = 1 + i += 1 + continue + if ch == '}' or ch == ']' or ch == ')' or ch == '>': + if ch == '}' and depth == 0: + if has == 0: + return 0 + return cnt + 1 + depth -= 1 + i += 1 + continue + if ch != ' ' and ch != '\t' and ch != '\n' and ch != '\r': + has = 1 + i += 1 + return -1 + + +# 扫描存根文本,取 `%"" = type ...` 与 `@....vtable = ...` 两条元数据, +# 结果写全局 scratch:ReplStubTmpCount(字段数 / -1 无此类 / -2 opaque)、 +# ReplStubTmpPacked、ReplStubTmpArity(.vtable 槽数,0 = 无该全局) +def _repl_stub_scan_meta(txt: str, key: str) -> None: + global ReplStubTmpCount, ReplStubTmpPacked, ReplStubTmpArity + ReplStubTmpCount = -1 + ReplStubTmpPacked = 0 + ReplStubTmpArity = 0 + if txt is None or key is None: + return + n: sizet = string.strlen(txt) + key_len: sizet = string.strlen(key) + i: sizet = 0 + while i < n: + ls: sizet = i + while i < n and txt[i] != '\n': + i += 1 + le: sizet = i + if i < n: + i += 1 + p: sizet = ls + while p < le and (txt[p] == ' ' or txt[p] == '\t' or txt[p] == '\r'): + p += 1 + if p >= le: + continue + c0: char = txt[p] + if c0 == '%': + # 类型定义行 + q: sizet = _repl_stub_take_name(txt, p) + if q == 0 or q > le: + continue + if string.strcmp(ReplStubNameBuf, key) != 0: + continue + # 找 "= type" + j: sizet = q + while j + 6 < le and string.strncmp(txt + j, "= type", 6) != 0: + j += 1 + if j + 6 >= le: + continue + j += 6 + while j < le and (txt[j] == ' ' or txt[j] == '\t'): + j += 1 + if j + 6 <= le and string.strncmp(txt + j, "opaque", 6) == 0: + ReplStubTmpCount = -2 + continue + if j < le and txt[j] == '<': + ReplStubTmpPacked = 1 + j += 1 + if j < le and txt[j] == '{': + ReplStubTmpCount = _repl_stub_body_count(txt, j) + continue + if c0 == '@': + # 全局变量行:只关心 .vtable + q2: sizet = _repl_stub_take_name(txt, p) + if q2 == 0 or q2 > le: + continue + nm_len: sizet = string.strlen(ReplStubNameBuf) + if nm_len != key_len + 7: + continue + if string.strncmp(ReplStubNameBuf, key, key_len) != 0: + continue + if string.strncmp(ReplStubNameBuf + key_len, ".vtable", 7) != 0: + continue + j2: sizet = q2 + while j2 < le and txt[j2] != '=': + j2 += 1 + if j2 >= le: + continue + j2 += 1 + while j2 < le and txt[j2] != '{': + j2 += 1 + if j2 < le and txt[j2] == '{': + ar: int = _repl_stub_body_count(txt, j2) + if ar > 0: + ReplStubTmpArity = ar + continue + return + + +def ReplStubLayoutBegin(temp_dir: str) -> int: + """开启会话级"存根布局恢复"(Repl.vp 在 includes 类注册前调用一次);0=成功""" + global ReplStubLayoutMode, ReplStubPool, ReplStubTempDir, ReplStubTable + global ReplStubCount, ReplStubOk, ReplStubVtOk, ReplStubRefused, ReplStubNoStub + global ReplStubWarnLeft, ReplStubNameBuf + pool: rawptr[memhub.MemBuddy] = CompilerFactory.factory.PermPool + if pool is None or temp_dir is None: + return 1 + ReplStubPool = pool + ReplStubTempDir = _repl_stub_dupstr(pool, temp_dir) + if ReplStubTempDir is None: + return 1 + ReplStubTable = pool.alloc(8 * _REPL_STUB_CLS_MAX) + if ReplStubTable is None: + return 1 + string.memset(ReplStubTable, 0, 8 * _REPL_STUB_CLS_MAX) + ReplStubNameBuf = pool.alloc(_REPL_STUB_NAME_CAP) + if ReplStubNameBuf is None: + return 1 + ReplStubNameBuf[0] = 0 + ReplStubCount = 0 + ReplStubOk = 0 + ReplStubVtOk = 0 + ReplStubRefused = 0 + ReplStubRefusedNoStub = 0 + ReplStubRefusedNoClass = 0 + ReplStubRefusedOpaque = 0 + ReplStubRefusedParent = 0 + ReplStubNoStub = 0 + ReplStubWarnLeft = _REPL_STUB_WARN_MAX + ReplStubLayoutMode = 1 + return 0 + + +def _repl_stub_at(i: int) -> rawptr[ReplStubClass]: + """类表第 i 项(无则 None)""" + if ReplStubTable is None or i < 0 or i >= ReplStubCount: + return None + addr: sizet = ReplStubTable[i] + if addr == 0: + return None + return rawptr[ReplStubClass](uint64(addr)) + + +def _repl_stub_find(key: str) -> rawptr[ReplStubClass]: + """按 ".<类名>" 查类表条目(无则 None)""" + if key is None: + return None + i: int + for i in range(ReplStubCount): + e: rawptr[ReplStubClass] = _repl_stub_at(i) + if e is None or e.Key is None: + continue + if string.strcmp(e.Key, key) == 0: + return e + return None + + +def _repl_stub_find_base(e: rawptr[ReplStubClass], bi: int) -> rawptr[ReplStubClass]: + """父类表条目:先同模块 SHA1 精确匹配,再按裸类名首个匹配(镜像 + StructGen.find_struct_by_module → find_struct 的父类回退口径)""" + if e is None or e.Bases is None or bi < 0 or bi >= e.BaseCount: + return None + pname: str = (str | t.CPtr)(e.Bases[bi]) + if pname is None: + return None + i: int + for i in range(ReplStubCount): + pe: rawptr[ReplStubClass] = _repl_stub_at(i) + if pe is None or pe.ClassName is None: + continue + if string.strcmp(pe.ClassName, pname) != 0: + continue + if e.Sha1 is not None and pe.Sha1 is not None: + if string.strcmp(pe.Sha1, e.Sha1) == 0: + return pe + for i in range(ReplStubCount): + pe2: rawptr[ReplStubClass] = _repl_stub_at(i) + if pe2 is None or pe2.ClassName is None: + continue + if string.strcmp(pe2.ClassName, pname) == 0: + return pe2 + return None + + +def _repl_stub_has_name(arr: sizet | t.CPtr, cnt: int, name: str) -> int: + """名单里是否已有该名(先声明优先去重)""" + if arr is None or name is None: + return 0 + i: int + for i in range(cnt): + addr: sizet = arr[i] + if addr == 0: + continue + ex: str = (str | t.CPtr)(addr) + if ex is not None and string.strcmp(ex, name) == 0: + return 1 + return 0 + + +def _repl_stub_deco_flags(deco_list: list[ast.AST*]*) -> int: + """类/方法装饰器标志(位0=@t.NoVTable,位1=@t.CVTable) + + 与 ClassGenBase._has_decorator 同判据(Attribute(t.NoVTable) 与 Name(NoVTable) 两种写法)。 + """ + if deco_list is None: + return 0 + flags: int = 0 + dn: sizet = len(deco_list) + di: sizet + for di in range(dn): + deco: rawptr[ast.AST] = deco_list.get(di) + if deco is None: + continue + nm: str = None + k: int = deco.kind() + if k == ast.ASTKind.Attribute: + at: rawptr[ast.Attribute] = rawptr[ast.Attribute](deco) + if at.attr is not None and at.value is not None \ + and at.value.kind() == ast.ASTKind.Name: + vn: rawptr[ast.Name] = rawptr[ast.Name](at.value) + if vn.id is not None and string.strcmp(vn.id, "t") == 0: + nm = at.attr + elif k == ast.ASTKind.Name: + nn: rawptr[ast.Name] = rawptr[ast.Name](deco) + nm = nn.id + if nm is None: + continue + if string.strcmp(nm, "NoVTable") == 0: + flags = flags | 1 + elif string.strcmp(nm, "CVTable") == 0: + flags = flags | 2 + return flags + + +def _repl_stub_is_virtual(e: rawptr[ReplStubClass], mi: int, + first: rawptr[ReplStubClass]) -> int: + """本类第 mi 个方法是否进虚表(转写 ClassGenBase._should_method_be_virtual)""" + if e is None or e.MethodFlags is None or mi < 0 or mi >= e.MethodCount: + return 0 + mf: int = int(e.MethodFlags[mi]) + if (mf & 1) != 0: + return 0 + if (mf & 2) != 0: + return 1 + if (e.Flags & _REPL_SF_CVTABLE) != 0: + return 1 + if first is not None: + if first.IsNoVTable != 0: + return 0 + return first.HasVt + return 0 + + +def _repl_stub_warn_refuse(e: rawptr[ReplStubClass], why: str, a: int, b: int) -> None: + """**判据拒绝**告警(两侧都是真数字时才用它):存根字段数 a 与展平+虚表推断 b 不符""" + global ReplStubWarnLeft, ReplStubRefused + ReplStubRefused += 1 + if ReplStubWarnLeft <= 0: + return + ReplStubWarnLeft -= 1 + stdio.printf("includes 类布局: %s.%s 布局不可信 ⇒ 明确拒绝(原因=判据拒绝:%s;" + "存根字段数=%d ≠ 展平字段数+虚表位=%d)。该类字段访问被明确拒绝" + "(方法调用不受影响)\n", + e.Sha1, e.ClassName, why, a, b) + + +def _repl_stub_warn_nodata(e: rawptr[ReplStubClass], cause: int) -> None: + """『无数据 ⇒ 明确拒绝』告警(与 _repl_stub_warn_refuse 分开) + + 与"字段数不符"不同,这条路径上根本没做比较(任一侧无数据即不比较,也不假装成 + "不匹配"),所以文案打印两侧数据的来源而不是数字,并把『数据缺失』 + (补存根/重跑即可消除)与『判据拒绝』(数据在但不可信,绝不放行)分开。 + """ + global ReplStubWarnLeft, ReplStubRefused + global ReplStubRefusedNoStub, ReplStubRefusedNoClass + global ReplStubRefusedOpaque, ReplStubRefusedParent + ReplStubRefused += 1 + if cause == _REPL_SU_NOSTUB: + ReplStubRefusedNoStub += 1 + elif cause == _REPL_SU_NOCLASS: + ReplStubRefusedNoClass += 1 + elif cause == _REPL_SU_OPAQUE: + ReplStubRefusedOpaque += 1 + else: + ReplStubRefusedParent += 1 + if ReplStubWarnLeft <= 0: + return + ReplStubWarnLeft -= 1 + if cause == _REPL_SU_NOSTUB: + # 数据缺失:连模块存根文件都没有(LayoutCount = -1 只是"没读到存根"的哨兵) + stdio.printf("includes 类布局: %s.%s 布局不可用 ⇒ 明确拒绝(原因=『数据缺失』:" + "模块 %s 没有 .stub.ll 文件;存根字段数=无 / 推断字段数=未计算 ⇒ " + "两侧都没数据,『未做比较』,不是字段数不符)。该类字段访问被" + "拒绝(方法调用不受影响);补上该模块存根后用同一 --project 重跑 " + "REPL 即恢复\n", e.Sha1, e.ClassName, e.Sha1) + elif cause == _REPL_SU_OPAQUE: + # 判据拒绝:存根里该 key 是 opaque(无 body ⇒ 无字段数可比) + stdio.printf("includes 类布局: %s.%s 布局不可用 ⇒ 明确拒绝(原因=『判据拒绝』:" + "存根 %s.stub.ll 里键 %s 为 opaque 声明(无 body);存根字段数=不可得 / " + "推断字段数=未计算 ⇒ 『未做比较』)。该类字段访问被拒绝" + "(方法调用不受影响)\n", e.Sha1, e.ClassName, e.Sha1, e.Key) + else: + # 判据拒绝:存根文件在,但没有该类的 `= type` 行(LayoutCount = -1 只是"未找到"的哨兵) + stdio.printf("includes 类布局: %s.%s 布局不可用 ⇒ 明确拒绝(原因=『判据拒绝』:" + "存根 %s.stub.ll 存在但其中没有键 %s 的 `= type {…}` 定义行;" + "存根字段数=未找到 / 推断字段数=未计算 ⇒ 『未做比较』)。该类字段访问" + "被拒绝(方法调用不受影响);若该类确实定义于该模块,说明存根陈旧/" + "不完整,请重建\n", e.Sha1, e.ClassName, e.Sha1, e.Key) + + +def _repl_stub_warn_parent(e: rawptr[ReplStubClass], detail: str, pname: str) -> None: + """**上游无数据 ⇒ 明确拒绝**:父类布局不可用/未注册(本类无法展平 ⇒ 不比较)""" + global ReplStubWarnLeft, ReplStubRefused, ReplStubRefusedParent + ReplStubRefused += 1 + ReplStubRefusedParent += 1 + if ReplStubWarnLeft <= 0: + return + ReplStubWarnLeft -= 1 + stdio.printf("includes 类布局: %s.%s 布局不可用 ⇒ 明确拒绝(原因=『上游无数据』:%s," + "父类=%s;父类字段与虚表位无法展平 ⇒ 本类未做字段数比较)。该类字段访问" + "被拒绝(方法调用不受影响)\n", e.Sha1, e.ClassName, detail, pname) + + +def _repl_stub_try(e: rawptr[ReplStubClass], mod: rawptr[llvmlite.LLVMModule]) -> int: + """按存根校验并登记完整布局:1=已登记 / 0=父类未就绪(推迟)/ -1=拒绝""" + global ReplStubOk, ReplStubVtOk, ReplStubWarnLeft + if e is None: + return -1 + if e.State == _REPL_SS_OK: + return 1 + if e.State == _REPL_SS_REFUSED: + return -1 + if e.State != _REPL_SS_PENDING: + return -1 + pool: rawptr[memhub.MemBuddy] = ReplStubPool + if pool is None: + e.State = _REPL_SS_REFUSED + return -1 + # ① 父类必须已就绪(未就绪 ⇒ 推迟;被拒 ⇒ 本类布局不可信) + bi: int + for bi in range(e.BaseCount): + pe: rawptr[ReplStubClass] = _repl_stub_find_base(e, bi) + if pe is None: + return 0 + if pe.State == _REPL_SS_PENDING: + return 0 + if pe.State == _REPL_SS_REFUSED: + e.State = _REPL_SS_REFUSED + _repl_stub_warn_parent(e, "父类布局不可信", pe.ClassName) + return -1 + if pe.State == _REPL_SS_EMPTY and pe.OwnFail != 0: + # 父类自身有字段注解解析失败 ⇒ 其 Count 只是"已解析字段数"(不是完整布局) + # ⇒ 展平出来的本类字段数不可用:既不比较(避免误报"字段数不符")、也不放行 + # (M4.2:布局不可信 ⇒ 明确拒绝)。 + e.State = _REPL_SS_REFUSED + _repl_stub_warn_parent(e, "父类的字段类型未全部解析(其字段数不完整)", + pe.ClassName) + return -1 + first: rawptr[ReplStubClass] = None + if e.BaseCount > 0: + first = _repl_stub_find_base(e, 0) + # ② HasVTable(转写 ClassGenBase._detect_vtable_status) + hv: int = 0 + if (e.Flags & _REPL_SF_CVTABLE) != 0: + hv = 1 + elif (e.Flags & _REPL_SF_NOVTABLE) != 0: + hv = 1 if (e.Flags & _REPL_SF_ANYFUNC) != 0 else 0 + elif first is not None: + if first.IsNoVTable != 0: + hv = 1 if (e.Flags & _REPL_SF_ANYFUNC) != 0 else 0 + else: + hv = first.HasVt + else: + hv = 1 if (e.Flags & _REPL_SF_ANYFUNC) != 0 else 0 + # ③ 字段展平(父类字段在前 + 本类字段,短名去重先声明优先) + cn: int = 0 + for bi in range(e.BaseCount): + pe: rawptr[ReplStubClass] = _repl_stub_find_base(e, bi) + fi: int + for fi in range(pe.Count): + if cn >= _REPL_STUB_FIELD_MAX: + break + nm: str = (str | t.CPtr)(pe.Names[fi]) + if nm is None: + continue + if _repl_stub_has_name(e.Names, cn, nm) != 0: + continue + e.Names[cn] = pe.Names[fi] + e.Types[cn] = pe.Types[fi] + cn += 1 + oi: int + for oi in range(e.OwnCount): + if cn >= _REPL_STUB_FIELD_MAX: + break + nm2: str = (str | t.CPtr)(e.OwnNames[oi]) + if nm2 is None: + continue + if _repl_stub_has_name(e.Names, cn, nm2) != 0: + continue + e.Names[cn] = e.OwnNames[oi] + e.Types[cn] = e.OwnTypes[oi] + cn += 1 + e.Count = cn + e.HasVt = hv + # ④ 布局校验(**仅当两侧都可用时才走到这里**:LayoutCount >= 0 已由 PENDING 前提 + # 保证,展平侧由 ① 保证父类字段数完整):存根字段数 == __vtable__(有虚表时) + 展平字段数 + if e.LayoutCount != cn + hv: + e.State = _REPL_SS_REFUSED + _repl_stub_warn_refuse(e, "存根字段数与字段名单不符", e.LayoutCount, cn + hv) + return -1 + # ⑤ 虚方法短名顺序(父类虚方法在前 + 本类合格方法按类体顺序) + vn: int = 0 + if hv == 1: + for bi in range(e.BaseCount): + pe: rawptr[ReplStubClass] = _repl_stub_find_base(e, bi) + vi: int + for vi in range(pe.VtCount): + if vn >= _REPL_STUB_VT_MAX: + break + mn: str = (str | t.CPtr)(pe.VtNames[vi]) + if mn is None: + continue + if _repl_stub_has_name(e.VtNames, vn, mn) != 0: + continue + e.VtNames[vn] = pe.VtNames[vi] + vn += 1 + mi: int + for mi in range(e.MethodCount): + if vn >= _REPL_STUB_VT_MAX: + break + mn2: str = (str | t.CPtr)(e.MethodNames[mi]) + if mn2 is None: + continue + if string.strcmp(mn2, "__init__") == 0: + continue + if string.strcmp(mn2, "__before_init__") == 0: + continue + if string.strcmp(mn2, "__new__") == 0: + continue + if _repl_stub_has_name(e.VtNames, vn, mn2) != 0: + continue + if _repl_stub_is_virtual(e, mi, first) == 0: + continue + e.VtNames[vn] = e.MethodNames[mi] + vn += 1 + e.VtCount = vn + e.VtTrust = 0 + if hv == 1 and vn > 0: + if vn == e.VtArity: + e.VtTrust = 1 + elif ReplStubWarnLeft > 0: + ReplStubWarnLeft -= 1 + stdio.printf("includes 类布局: %s.%s 的虚表槽数与存根不符(%d vs %d)" + "——该类不做虚分派(退化为直接调用,会话继续)\n", + e.Sha1, e.ClassName, vn, e.VtArity) + # ⑥ 建 LLVM 结构体(命名 "%.<类名>",与定义侧同口径) + total: int = cn + hv + i8_ptr: rawptr[llvmlite.LLVMType] = llvmlite.Ptr(pool, llvmlite.Int8(pool)) + if i8_ptr is None: + return 0 + first_node: rawptr[llvmlite.ParamNode] = None + prev_node: rawptr[llvmlite.ParamNode] = None + if hv == 1: + nd0: rawptr[llvmlite.ParamNode] = pool.alloc(llvmlite.ParamNode.__sizeof__()) + if nd0 is None: + return 0 + string.memset(nd0, 0, llvmlite.ParamNode.__sizeof__()) + nd0.Ty = i8_ptr + first_node = nd0 + prev_node = nd0 + fi2: int + for fi2 in range(cn): + fty: rawptr[llvmlite.LLVMType] = (rawptr[llvmlite.LLVMType])(e.Types[fi2]) + if fty is None: + continue + nd: rawptr[llvmlite.ParamNode] = pool.alloc(llvmlite.ParamNode.__sizeof__()) + if nd is None: + continue + string.memset(nd, 0, llvmlite.ParamNode.__sizeof__()) + nd.Ty = fty + if first_node is None: + first_node = nd + if prev_node is not None: + prev_node.Next = nd + prev_node = nd + struct_ty: rawptr[llvmlite.LLVMType] = llvmlite.Struct(pool, first_node, total, e.Key) + if struct_ty is None: + return 0 + # ⑦ 登记(StdlibAlloc=1 期间的注册走 PermPool,跨片段存活) + entry: rawptr[GenTypes.StructEntry] = GenApi.register_struct( + pool, e.ClassName, struct_ty, e.Sha1) + if entry is None: + e.State = _REPL_SS_REFUSED + return -1 + if mod is not None: + llvmlite.module_add_named_type(mod, pool, struct_ty) + if (e.Flags & _REPL_SF_PACKED) != 0: + entry.IsPacked = 1 + if mod is not None: + llvmlite.module_set_named_type_packed(mod, pool, struct_ty, 1) + if hv == 1: + entry.HasVTable = 1 + GenApi.add_field(pool, entry, "__vtable__", i8_ptr) + fi3: int + for fi3 in range(cn): + fname: str = (str | t.CPtr)(e.Names[fi3]) + fty3: rawptr[llvmlite.LLVMType] = (rawptr[llvmlite.LLVMType])(e.Types[fi3]) + if fname is None or fty3 is None: + continue + GenApi.add_field(pool, entry, fname, fty3) + if e.IsNoVTable != 0: + entry.IsNoVTable = 1 + entry.IsOOP = e.IsOOP + entry.HasInit = e.HasInit + entry.HasNew = e.HasNew + # 父类名(多继承数组;顺序同声明序,供 vtable/MRO/字段继承查询) + if e.BaseCount > 0: + arr: sizet | t.CPtr = pool.alloc(8 * e.BaseCount) + if arr is not None: + string.memset(arr, 0, 8 * e.BaseCount) + bi3: int + for bi3 in range(e.BaseCount): + bnm: str = (str | t.CPtr)(e.Bases[bi3]) + bdup: str = _repl_stub_dupstr(pool, bnm) + if bdup is not None: + arr[bi3] = sizet(bdup) + entry.ParentNames = arr + entry.ParentCount = e.BaseCount + pn: str = (str | t.CPtr)(e.Bases[0]) + entry.ParentName = _repl_stub_dupstr(pool, pn) + if hv == 1 and e.VtTrust == 1 and e.VtCount > 0: + entry.VTableMethods = e.VtNames + entry.VTableMethodCount = e.VtCount + ReplStubVtOk += 1 + e.State = _REPL_SS_OK + ReplStubOk += 1 + return 1 + + +def _repl_stub_add_class(pool: rawptr[memhub.MemBuddy], + mod: rawptr[llvmlite.LLVMModule], + cd: rawptr[ast.ClassDef], + sha1: str, + txt: str, + have_stub: int, + trans: rawptr[HT.Translator], + imported_modules: str, + from_imports: str) -> int: + """收集单个 vpi ClassDef 的布局要素并尝试登记(1=已登记/推迟处理,0=跳过) + + have_stub:本模块是否读到了 .stub.ll 文本(0 时本模块带父类/虚表的类按 + 『数据缺失』拒绝,与"存根在但无此类"的『判据拒绝』在文案/计数上分开)。 + """ + global ReplStubCount + if pool is None or cd is None or cd.name is None or ReplStubTable is None: + return 0 + if cd.name[0] == '\0' or ReplStubCount >= _REPL_STUB_CLS_MAX: + return 0 + key: str = _repl_stub_key(pool, sha1, cd.name) + if key is None: + return 0 + if _repl_stub_find(key) is not None: + return 0 + e: rawptr[ReplStubClass] = pool.alloc(ReplStubClass.__sizeof__()) + if e is None: + return 0 + string.memset(e, 0, ReplStubClass.__sizeof__()) + e.Key = key + e.ClassName = _repl_stub_dupstr(pool, cd.name) + e.Sha1 = _repl_stub_dupstr(pool, sha1) + e.OwnNames = pool.alloc(8 * _REPL_STUB_FIELD_MAX) + e.OwnTypes = pool.alloc(8 * _REPL_STUB_FIELD_MAX) + e.MethodNames = pool.alloc(8 * _REPL_STUB_FIELD_MAX) + e.MethodFlags = pool.alloc(8 * _REPL_STUB_FIELD_MAX) + e.Bases = pool.alloc(8 * _REPL_STUB_FIELD_MAX) + e.Names = pool.alloc(8 * _REPL_STUB_FIELD_MAX) + e.Types = pool.alloc(8 * _REPL_STUB_FIELD_MAX) + e.VtNames = pool.alloc(8 * _REPL_STUB_VT_MAX) + if e.OwnNames is None or e.OwnTypes is None or e.MethodNames is None \ + or e.MethodFlags is None or e.Bases is None or e.Names is None \ + or e.Types is None or e.VtNames is None: + return 0 + string.memset(e.OwnNames, 0, 8 * _REPL_STUB_FIELD_MAX) + string.memset(e.OwnTypes, 0, 8 * _REPL_STUB_FIELD_MAX) + string.memset(e.MethodNames, 0, 8 * _REPL_STUB_FIELD_MAX) + string.memset(e.MethodFlags, 0, 8 * _REPL_STUB_FIELD_MAX) + string.memset(e.Bases, 0, 8 * _REPL_STUB_FIELD_MAX) + string.memset(e.Names, 0, 8 * _REPL_STUB_FIELD_MAX) + string.memset(e.Types, 0, 8 * _REPL_STUB_FIELD_MAX) + string.memset(e.VtNames, 0, 8 * _REPL_STUB_VT_MAX) + # 类装饰器 + cf: int = _repl_stub_deco_flags(cd.decorator_list) + if (cf & 2) != 0: + e.Flags = e.Flags | _REPL_SF_CVTABLE + if (cf & 1) != 0: + e.Flags = e.Flags | _REPL_SF_NOVTABLE + e.IsNoVTable = 1 + if _vpi_class_has_packed(cd.decorator_list) != 0: + e.Flags = e.Flags | _REPL_SF_PACKED + # 真实父类(复用 ClassGenBase._collect_parents:别名解析 + marker 剔除) + pb: sizet | t.CPtr = pool.alloc(8 * GenTypes.PARENT_MAX) + bcnt: int = 0 + if pb is not None and GenApi._g_gen is not None: + string.memset(pb, 0, 8 * GenTypes.PARENT_MAX) + bcnt = GenApi._g_gen.ClassGenBase._collect_parents( + pool, cd, pb, GenTypes.PARENT_MAX, trans) + if bcnt > 0: + if bcnt > _REPL_STUB_FIELD_MAX: + bcnt = _REPL_STUB_FIELD_MAX + bi: int + for bi in range(bcnt): + bnm: str = (str | t.CPtr)(pb[bi]) + bdup: str = _repl_stub_dupstr(pool, bnm) + if bdup is not None: + e.Bases[bi] = sizet(bdup) + e.BaseCount = bcnt + # 类体扫描:字段(AnnAssign)+ 方法(FunctionDef) + children: list[ast.AST*]* = cd.children + own_fail: int = 0 + if children is not None: + cn: sizet = len(children) + ci: sizet + for ci in range(cn): + ch2: rawptr[ast.AST] = children.get(ci) + if ch2 is None: + continue + k2: int = ch2.kind() + if k2 == ast.ASTKind.AnnAssign: + if e.OwnCount >= _REPL_STUB_FIELD_MAX: + continue + aa2: rawptr[ast.AnnAssign] = rawptr[ast.AnnAssign](ch2) + if aa2 is None or aa2.target is None or aa2.annotation is None: + continue + if aa2.target.kind() != ast.ASTKind.Name: + continue + tg: rawptr[ast.Name] = rawptr[ast.Name](aa2.target) + if tg.id is None or tg.id[0] == '\0': + continue + fty2: rawptr[llvmlite.LLVMType] = GenApi.resolve_annotation_type( + pool, aa2.annotation, imported_modules, from_imports, trans, 0) + if fty2 is None: + own_fail = 1 + continue + fdup: str = _repl_stub_dupstr(pool, tg.id) + if fdup is None: + own_fail = 1 + continue + e.OwnNames[e.OwnCount] = sizet(fdup) + e.OwnTypes[e.OwnCount] = sizet(fty2) + e.OwnCount += 1 + continue + if k2 == ast.ASTKind.FunctionDef: + fd2: rawptr[ast.FunctionDef] = rawptr[ast.FunctionDef](ch2) + if fd2 is None or fd2.name is None or fd2.name[0] == '\0': + continue + mf2: int = _repl_stub_deco_flags(fd2.decorator_list) + if (mf2 & 2) != 0: + e.Flags = e.Flags | _REPL_SF_ANYFUNC + if string.strcmp(fd2.name, "__init__") == 0: + e.HasInit = 1 + elif string.strcmp(fd2.name, "__new__") == 0: + e.HasNew = 1 + e.IsOOP = 1 + if e.MethodCount < _REPL_STUB_FIELD_MAX: + e.MethodNames[e.MethodCount] = sizet(_repl_stub_dupstr(pool, fd2.name)) + e.MethodFlags[e.MethodCount] = sizet(mf2) + e.MethodCount += 1 + continue + # 存根元数据(缺存根时 LayoutCount 保持"未找到"哨兵;两种成因由 have_stub 区分) + e.LayoutCount = _REPL_STUB_NOCOUNT_NOCLASS + if txt is not None: + _repl_stub_scan_meta(txt, key) + e.LayoutCount = ReplStubTmpCount + e.VtArity = ReplStubTmpArity + if ReplStubTmpPacked != 0: + e.Flags = e.Flags | _REPL_SF_PACKED + # 状态判定 + if own_fail != 0: + # 字段类型解析失败:交既有 vpi 注入口径(其整类跳过),本表只做展平来源 + # (OwnFail 记下来:本类 OwnCount 只是"已解析字段数",不可当作完整布局供子类展平) + e.OwnFail = 1 + e.State = _REPL_SS_EMPTY + e.Count = e.OwnCount + e.Names = e.OwnNames + e.Types = e.OwnTypes + elif e.LayoutCount >= 0: + e.State = _REPL_SS_PENDING + elif e.BaseCount == 0 and (e.Flags & (_REPL_SF_CVTABLE | _REPL_SF_ANYFUNC)) == 0: + # 无父类、无虚表 ⇒ 本类自有字段即完整布局(与既有口径等价) + e.State = _REPL_SS_EMPTY + e.Count = e.OwnCount + e.Names = e.OwnNames + e.Types = e.OwnTypes + else: + # 布局无数据 ⇒ 明确拒绝(不登记 StructEntry)。两类成因必须分开报: + # ① have_stub == 0:连模块 .stub.ll 都没有(**数据缺失**,补存根即可恢复) + # ② LayoutCount == -2:存根里该类是 opaque(判据拒绝) + # ③ 其余:存根在但没有该类的 `= type` 行(判据拒绝;若该类确实定义于此模块 + # 则说明存根陈旧/不完整) + e.State = _REPL_SS_REFUSED + if have_stub == 0: + _repl_stub_warn_nodata(e, _REPL_SU_NOSTUB) + elif e.LayoutCount == _REPL_STUB_NOCOUNT_OPAQUE: + _repl_stub_warn_nodata(e, _REPL_SU_OPAQUE) + else: + _repl_stub_warn_nodata(e, _REPL_SU_NOCLASS) + ReplStubTable[ReplStubCount] = sizet(e) + ReplStubCount += 1 + if e.State == _REPL_SS_PENDING: + _repl_stub_try(e, mod) + return 1 + + +def _repl_stub_collect_module(pool: rawptr[memhub.MemBuddy], + mod: rawptr[llvmlite.LLVMModule], + ch: list[ast.AST*]*, + n: sizet, + trans: rawptr[HT.Translator], + src_sha1: str, + imported_modules: str, + from_imports: str) -> int: + """(REPL)为当前模块的类收集布局要素 + 尝试登记;返回处理的类数""" + global ReplStubNoStub, ReplStubWarnLeft + if ReplStubLayoutMode == 0 or pool is None or ch is None: + return 0 + if src_sha1 is None or src_sha1[0] == '\0' or ReplStubTable is None: + return 0 + # 存根文本(缺失:带父类/虚表的类将因"数据缺失"被明确拒绝,逐个可见) + txt: str = None + sp: str = _repl_stub_path(src_sha1, "stub.ll") + if sp is not None: + txt = _repl_stub_readtext(sp) + stdlib.free(sp) + have_stub: int = 0 + if txt is not None: + have_stub = 1 + else: + ReplStubNoStub += 1 + if ReplStubWarnLeft > 0: + ReplStubWarnLeft -= 1 + stdio.printf("includes 类布局: 模块 %s 无 .stub.ll 存根(数据缺失)—— 该模块" + "带父类/虚表的类将逐个按『数据缺失』明确拒绝(读不到存根 ⇒ 无" + "字段数可比,不是字段数不符)。用同一工程先跑一次批处理生成存根," + "再 `--project .\\project.vpj --repl`(TransPyV 目录)即可复用\n", + src_sha1) + cnt: int = 0 + i: sizet + for i in range(n): + child: rawptr[ast.AST] = ch.get(i) + if child is None: + continue + if child.kind() != ast.ASTKind.ClassDef: + continue + cd: rawptr[ast.ClassDef] = rawptr[ast.ClassDef](child) + if cd is None or cd.name is None: + continue + # 泛型模板类:与 _inject_class_struct_declare / 第一遍 A 同口径跳过(模板签名 + # 含类级类型参数 T,注入时 T 无法确定)。但**归属模块必须登记**:REPL 会话不 + # 注册 includes 泛型模板,故注解里的泛型下标(list[rawptr[AST]] 等)要靠 + # "模板模块 sha1" 拼定义侧特化名造类型占位(TypeGenResolve 的 ReplCtx 分支)。 + tp: list[str] | t.CPtr = cd.type_params + if tp is not None and len(tp) > 0: + StubMergerStore.RegisterGenericOwner(cd.name, src_sha1) + continue + # REnum 类:布局 {i32 tag, payload} 由 ClassGenEnum 计算,既有路径整体跳过 + if _vpi_class_is_renum(cd) != 0: + continue + if _repl_stub_add_class(pool, mod, cd, src_sha1, txt, have_stub, trans, + imported_modules, from_imports) != 0: + cnt += 1 + if txt is not None: + stdlib.free(txt) + return cnt + + +def ReplStubLayoutFinish() -> int: + """收尾:收敛登记剩余类 + 打印汇总;返回已登记完整布局的类数""" + global ReplStubRefused, ReplStubWarnLeft + if ReplStubLayoutMode == 0: + return 0 + rounds: int + for rounds in range(_REPL_STUB_FINISH_PASSES): + prog: int = 0 + i: int + for i in range(ReplStubCount): + e: rawptr[ReplStubClass] = _repl_stub_at(i) + if e is None or e.State != _REPL_SS_PENDING: + continue + if _repl_stub_try(e, None) != 0: + prog += 1 + if prog == 0: + break + # 仍未就绪 ⇒ 父类始终未注册(缺存根/被拒):明确拒绝(本类无法展平 ⇒ 不比较) + i2: int + for i2 in range(ReplStubCount): + e2: rawptr[ReplStubClass] = _repl_stub_at(i2) + if e2 is None or e2.State != _REPL_SS_PENDING: + continue + e2.State = _REPL_SS_REFUSED + # 找出仍未就绪的父类名(自证"上游是谁") + pbad: str = "(未定位)" + bi2: int + for bi2 in range(e2.BaseCount): + peb: rawptr[ReplStubClass] = _repl_stub_find_base(e2, bi2) + if peb is not None and peb.State == _REPL_SS_OK: + continue + if peb is not None and peb.ClassName is not None: + pbad = peb.ClassName + break + _repl_stub_warn_parent(e2, "父类未在会话注册或其布局被拒", pbad) + stdio.printf("includes 类布局: 完整布局 %d 个(其中虚表索引可用 %d 个)/ " + "被拒 %d 个 / 缺存根模块 %d 个\n", + ReplStubOk, ReplStubVtOk, ReplStubRefused, ReplStubNoStub) + stdio.printf(" 拒绝原因分布(自证数据来源):数据缺失-模块无 .stub.ll %d 个类 / " + "判据拒绝-存根无此类 %d 个 / 判据拒绝-存根为 opaque %d 个 / " + "上游无数据-父类不可用 %d 个\n", + ReplStubRefusedNoStub, ReplStubRefusedNoClass, + ReplStubRefusedOpaque, ReplStubRefusedParent) + if ReplStubRefused > 0 or ReplStubNoStub > 0: + stdio.printf(" 说明:被拒类的字段访问会报『类未注册/属性访问失败』" + "(不按错偏移静默访问);方法调用不受影响。\n") + if ReplStubRefusedNoStub > 0: + stdio.printf(" 其中『数据缺失』的 %d 个类只是没读到模块存根(本会话临时目录 %s " + "缺这些 .stub.ll):批处理才会落盘 .stub.ll(REPL 分支在 Phase1 之前" + "返回,故 --repl 自身不生成存根)。修法:用同一个工程跑一次批处理" + "(TransPyV 目录:.\\tpv_build\\output\\TransPyV.exe --project " + ".\\project.vpj;includes 有改动时加 --rebuild-includes),随后 " + "--project .\\project.vpj --repl 复用其临时目录即可\n", ReplStubRefusedNoStub, + ReplStubTempDir) + if ReplStubRefusedNoClass + ReplStubRefusedOpaque > 0: + stdio.printf(" 其中『判据拒绝』的 %d 个类:数据在但不可信(存根里没有该类的字段数、" + "或其声明为 opaque),按 M4.2 一律不放行——请确认该类是否本应按结构体" + "登记:应按结构体登记而存根里查不到 ⇒ 存根陈旧/不完整(重建 includes " + "存根)\n", ReplStubRefusedNoClass + ReplStubRefusedOpaque) + return ReplStubOk + + +def ReplStubLayoutEnd() -> None: + """关闭存根布局恢复(批处理路径此后完全不受影响)""" + global ReplStubLayoutMode + ReplStubLayoutMode = 0 diff --git a/App/lib/Stub/StubMerger/StubMergerStore.vp b/App/lib/Stub/StubMerger/StubMergerStore.vp index 0b14ad2..1b9be24 100644 --- a/App/lib/Stub/StubMerger/StubMergerStore.vp +++ b/App/lib/Stub/StubMerger/StubMergerStore.vp @@ -269,6 +269,83 @@ def GetSha1StoreCount() -> int: def LookupSha1ByModName(mod_name: str) -> str: """从模块名查找 SHA1(精确匹配 + 包名回退),委托通用实现 _FindSha1ByModName""" return StubMergerIR._FindSha1ByModName(StubMerger._sha1_store_arr, StubMerger._sha1_store_mod, StubMerger._sha1_store_count, mod_name) + + +# ============================================================ +# 泛型类归属表(泛型类名 → 定义模块 SHA1)——仅 REPL 会话登记/查询 +# +# 为什么需要:REPL 的 includes 会话初始化只注入 `.vpi` 的类(泛型类被跳过), +# 不跑批处理的 _PreRegister*GenericTemplates ⇒ 会话内泛型模板表为空 ⇒ 注解里的 +# `list[rawptr[AST]]` 等泛型下标解析(`TypeGenResolve._resolve_generic_subscript_ty`) +# 恒 miss。会话侧的最后兜底要按**定义侧特化命名** "%{模板模块 sha1}.{mangled}" +# 造 opaque 具名类型(实证:`%"b56d2f995104690d.list[rawptr[AST]]"`),因此需要 +# "泛型类定义在哪个模块"这一事实——由注入期扫 `.vpi` 的 ClassDef 登记(见 +# VpiReader._repl_stub_collect_module)。 +# +# 归属模块 sha1 无法从类名反推(模块名取自文件路径,class list ← _list.vp;用户 +# 泛型类更是名字与模块名无关),故必须显式登记,不做"按名字猜"。 +# 批处理不登记(表恒空 ⇒ 查询恒 None ⇒ 批处理路径一字不改)。 +# 容量:includes 现有 6 个泛型类定义(list/dict 系 + Vector/GSList/ndarray),取 32 槽。 +# ============================================================ +GENERIC_OWNER_MAX: t.CDefine = 32 +_generic_owner_names: bytes = None +_generic_owner_sha1s: bytes = None +_generic_owner_count: int = 0 + + +def ResetGenericOwners() -> None: + """清空泛型类归属表(REPL 会话初始化开始时调用一次)""" + global _generic_owner_names, _generic_owner_sha1s, _generic_owner_count + if _generic_owner_names is not None: + stdlib.free(_generic_owner_names) + _generic_owner_names = None + if _generic_owner_sha1s is not None: + stdlib.free(_generic_owner_sha1s) + _generic_owner_sha1s = None + _generic_owner_count = 0 + + +def RegisterGenericOwner(cls_name: str, mod_sha1: str) -> int: + """登记"泛型类名 → 定义模块 SHA1";1=已登记(含重复)/ 0=失败(参数/满/同名异 sha1) + + 同名异 sha1(跨模块同名泛型类)保留首个,与泛型模板表按类名去重的口径一致。 + """ + global _generic_owner_names, _generic_owner_sha1s, _generic_owner_count + if cls_name is None or mod_sha1 is None: + return 0 + if cls_name[0] == '\0' or mod_sha1[0] == '\0': + return 0 + if _generic_owner_names is None: + _generic_owner_names = stdlib.malloc(GENERIC_OWNER_MAX * 64) + _generic_owner_sha1s = stdlib.malloc(GENERIC_OWNER_MAX * 17) + if _generic_owner_names is None or _generic_owner_sha1s is None: + return 0 + string.memset(_generic_owner_names, 0, GENERIC_OWNER_MAX * 64) + string.memset(_generic_owner_sha1s, 0, GENERIC_OWNER_MAX * 17) + _generic_owner_count = 0 + i_go: int + for i_go in range(_generic_owner_count): + if string.strcmp(_generic_owner_names + sizet(i_go) * 64, cls_name) == 0: + if string.strcmp(_generic_owner_sha1s + sizet(i_go) * 17, mod_sha1) == 0: + return 1 + return 0 + if _generic_owner_count >= GENERIC_OWNER_MAX: + return 0 + string.strncpy(_generic_owner_names + sizet(_generic_owner_count) * 64, cls_name, 63) + string.strncpy(_generic_owner_sha1s + sizet(_generic_owner_count) * 17, mod_sha1, 16) + _generic_owner_count += 1 + return 1 + + +def LookupGenericOwnerSha1(cls_name: str) -> str: + """按泛型类名取定义模块 SHA1(返回内部指针,无需释放;未登记 None)""" + if cls_name is None or _generic_owner_names is None: + return None + i_lg: int + for i_lg in range(_generic_owner_count): + if string.strcmp(_generic_owner_names + sizet(i_lg) * 64, cls_name) == 0: + return _generic_owner_sha1s + sizet(i_lg) * 17 + return None # ============================================================ # LookupSha1ByModNameSuffix - 后缀匹配查找模块 SHA1(用于 from .X import Y:impf.module 是 ".X",而映射表键是 "pkg.X") # diff --git a/App/lib/core/Gen/AssignGenMixin.vp b/App/lib/core/Gen/AssignGenMixin.vp index 8b1b3f4..f282ec6 100644 --- a/App/lib/core/Gen/AssignGenMixin.vp +++ b/App/lib/core/Gen/AssignGenMixin.vp @@ -125,7 +125,7 @@ class AssignGenMixin: cl: rawptr[ast.Call] = rawptr[ast.Call](val_node) # 必须先把属性赋给显式类型为 list[...] | t.CPtr 的局部变量再调用 __len__(), # 否则编译器无法识别属性返回的 list 类型,GEP base 会变成 i32 0 导致 llc 报错 - cl_args_list: rawptr[list[rawptr[ast.AST]]] = cl.args + cl_args_list: list[ast.AST*]* = cl.args if cl_args_list is not None and len(cl_args_list) > 0: first_arg: rawptr[ast.AST] = cl_args_list.get(0) arg_val: int64 = self.extract_cdefine_int_value(first_arg) @@ -167,7 +167,7 @@ class AssignGenMixin: if val_node is not None and val_node.kind() == ast.ASTKind.Call: cl_cd: rawptr[ast.Call] = rawptr[ast.Call](val_node) if cl_cd is not None and cl_cd.args is not None: - cl_args_cd: rawptr[list[rawptr[ast.AST]]] = cl_cd.args + cl_args_cd: list[ast.AST*]* = cl_cd.args if len(cl_args_cd) > 0: first_arg_cd: rawptr[ast.AST] = cl_args_cd.get(0) if first_arg_cd is not None and first_arg_cd.kind() == ast.ASTKind.Constant: @@ -640,7 +640,7 @@ class AssignGenMixin: lst: rawptr[ast.List] = rawptr[ast.List](list_node) if lst is None or lst.elts is None: return 1 - elts: rawptr[list[rawptr[ast.AST]]] = lst.elts + elts: list[ast.AST*]* = lst.elts elts_count: sizet = len(elts) # 特例:单个字符串字面量初始化字符数组([N x i8],N = strlen+1) @@ -839,8 +839,17 @@ class AssignGenMixin: if target_ty is None: # fail-fast: 字段类型信息缺失,禁止兜底 i32 self.Gen.TypeGenErr.fatal_error(target, "字段类型信息缺失", "TypeError") + # t.CVolatile 字段:+= 的 load 与 store 均带 volatile 限定符 + vol_fa: int = self.Gen.ExprGenAttr.attr_field_is_volatile(pool, trans, target) + # rawptr[t.CVolatile|T] 基址(volatile 指针指向物):经其访问的 + # 字段 += 的 load/store 亦带 volatile(两来源取或)。 + at_aug: rawptr[ast.Attribute] = rawptr[ast.Attribute](target) + if at_aug is not None: + if self.Gen.ExprGenAttr.base_pointee_is_volatile( + pool, trans, at_aug.value) != 0: + vol_fa = 1 cur_val: rawptr[llvmlite.Value] = llvmlite.build_load( - builder, target_ty, field_ptr) + builder, target_ty, field_ptr, vol_fa) if cur_val is None: # fail-fast: 属性当前值加载失败 self.Gen.TypeGenErr.fatal_error(target, "无法加载 attribute 当前值", "TypeError") @@ -864,7 +873,7 @@ class AssignGenMixin: if result is None: # fail-fast: 类型对齐失败 self.Gen.TypeGenErr.fatal_error(target, "类型对齐失败", "TypeError") - llvmlite.build_store(builder, result, field_ptr) + llvmlite.build_store(builder, result, field_ptr, vol_fa) return 0 # Subscript 目标: self.state[i] += x / arr[i] += x @@ -888,8 +897,15 @@ class AssignGenMixin: if target_ty is None: # fail-fast: 元素类型信息缺失,禁止兜底 i32 self.Gen.TypeGenErr.fatal_error(target, "元素类型信息缺失", "TypeError") + # rawptr[t.CVolatile|T] 基址(volatile 指针指向物):p[i] += 的 + # load/store 均带 volatile。仅基址可解析到 VarEntry/FieldEntry 时为 1。 + vol_sa: int = 0 + sub_aug: rawptr[ast.Subscript] = rawptr[ast.Subscript](target) + if sub_aug is not None: + vol_sa = self.Gen.ExprGenAttr.base_pointee_is_volatile( + pool, trans, sub_aug.value) cur_val: rawptr[llvmlite.Value] = llvmlite.build_load( - builder, target_ty, elem_ptr) + builder, target_ty, elem_ptr, vol_sa) if cur_val is None: # fail-fast: 元素当前值加载失败 self.Gen.TypeGenErr.fatal_error(target, "无法加载 subscript 元素当前值", "TypeError") @@ -913,7 +929,7 @@ class AssignGenMixin: if result is None: # fail-fast: 类型对齐失败 self.Gen.TypeGenErr.fatal_error(target, "类型对齐失败", "TypeError") - llvmlite.build_store(builder, result, elem_ptr) + llvmlite.build_store(builder, result, elem_ptr, vol_sa) return 0 if tk != ast.ASTKind.Name: @@ -925,6 +941,17 @@ class AssignGenMixin: if nm is None or nm.id is None: return 0 + # REPL 模式(M2/M3)会话变量增强赋值:读槽 → 二元运算 → 写槽 → 写结果区。 + # 未登记的会话名返回 0(继续原路径 → 明确的"无法加载…当前值"报错)。 + # 守卫 trans.ReplCtx:非 REPL 恒 None ⇒ 本分支整体不经过。 + # 作用域用 repl_in_frag_entry_scope(M3):被提升的会话函数体内 `x += 1` + # 按普通局部变量路径处理(不写会话槽)。 + if trans.ReplCtx is not None \ + and self.Gen.VarGen.repl_in_frag_entry_scope(trans) != 0: + if self.Gen.VarGen.repl_try_augassign( + trans, nm.id, aug.op, aug.value, node) != 0: + return 0 + pool: rawptr[memhub.MemBuddy] = trans.Pool builder: rawptr[llvmlite.IRBuilder] = trans._cur_builder mod: rawptr[llvmlite.LLVMModule] = trans.Module @@ -936,6 +963,10 @@ class AssignGenMixin: elif GenApi.is_nonlocal_name(trans, nm.id) != 0: scope_type = 2 + # t.CVolatile 变量:+= 的 load 与 store 均带 volatile 限定符 + vol_aa: int = self.Gen.VarGen.var_is_volatile( + self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)) + # 2. 加载当前值 cur_val: rawptr[llvmlite.Value] = None target_alloca: rawptr[llvmlite.Value] = None @@ -949,7 +980,7 @@ class AssignGenMixin: # fail-fast: 变量类型信息缺失,禁止兜底 i32 self.Gen.TypeGenErr.fatal_error(target, "变量类型信息缺失", "TypeError") target_ty = target_alloca.Ty.Pointee - cur_val = llvmlite.build_load(builder, target_ty, target_alloca) + cur_val = llvmlite.build_load(builder, target_ty, target_alloca, vol_aa) elif scope_type == 2: # nonlocal 变量(通过闭包 env) cur_val = self.Gen.StmtGenDecl.load_nonlocal_var(trans, nm.id) @@ -963,7 +994,7 @@ class AssignGenMixin: # fail-fast: 变量类型信息缺失,禁止兜底 i32 self.Gen.TypeGenErr.fatal_error(target, "变量类型信息缺失", "TypeError") target_ty = target_alloca.Ty.Pointee - cur_val = llvmlite.build_load(builder, target_ty, target_alloca) + cur_val = llvmlite.build_load(builder, target_ty, target_alloca, vol_aa) if cur_val is None: # fail-fast: 目标变量当前值加载失败 @@ -996,7 +1027,7 @@ class AssignGenMixin: if scope_type == 1: # global 变量 if target_alloca is not None: - llvmlite.build_store(builder, result, target_alloca) + llvmlite.build_store(builder, result, target_alloca, vol_aa) elif scope_type == 2: # nonlocal 变量 nl_ptr: rawptr[llvmlite.Value] = self.Gen.StmtGenDecl.get_nonlocal_var_ptr( @@ -1006,7 +1037,7 @@ class AssignGenMixin: else: # 普通局部变量 if target_alloca is not None: - llvmlite.build_store(builder, result, target_alloca) + llvmlite.build_store(builder, result, target_alloca, vol_aa) return 0 @@ -1176,6 +1207,15 @@ class AssignGenMixin: if self.is_ctypedef_annotation(aa.annotation) != 0: return 0 + # REPL 模式(M2/M3):片段**入口**函数本体内的 AnnAssign 一律走会话槽(见 + # translate_annassign 的会话分支),此处不得预建局部 alloca——否则局部名 + # 会遮蔽同名会话变量(此后读 `x` 命中空局部槽,槽值丢失)。 + # 被提升的会话函数体内不走该分支(那里按普通局部声明预建 alloca)。 + # CDefine/CTypedef 两个分支在本次返回之前,其行为不变。 + if trans.ReplCtx is not None \ + and self.Gen.VarGen.repl_in_frag_entry_scope(trans) != 0: + return 0 + # global/nonlocal 变量不需要局部 alloca if GenApi.is_global_name(trans, nm.id) != 0: return 0 @@ -1252,6 +1292,18 @@ class AssignGenMixin: if self._is_ptr_element_annotation(annot) != 0: self.Gen.VarGen.set_var_ptr_element( trans.SymTab, nm.id) + # t.CVolatile:声明期即标记该变量的 load/store 带 volatile。 + # 覆盖「无初值声明」`x: t.CVolatile | T`(有初值路径 + # translate_annassign 亦会登记,幂等)。动机:volatile 禁优化器 + # 把忙等循环的重复 load 提升/消除,不能依赖优化器保序。 + if self.Gen.TypeGenErr.has_decorator_marker( + annot, "CVolatile", 0) != 0: + self.Gen.VarGen.set_var_volatile(trans.SymTab, nm.id) + # rawptr[t.CVolatile | T](volatile 指针指向物,等价 C 的 + # volatile T*):解引用(p[i]/p.field/c.Deref(p))的 load/store + # 带 volatile。声明期登记,覆盖「无初值声明」。 + if self.Gen.TypeGenErr.has_pointee_volatile_marker(annot) != 0: + self.Gen.VarGen.set_var_pointee_volatile(trans.SymTab, nm.id) return 0 return 0 @@ -1401,6 +1453,20 @@ class AssignGenMixin: self.Gen.TypeGenCType.register_ctypedef(pool_ct, nm.id, ctd_str) return 0 + # REPL 模式(M2/M3)会话变量:片段**入口**函数本体内的带注解声明/赋值 + # (`x: int = 5` 定义并定型;`x: int` 仅定型)。注解先解析并定型条目, + # 定型后的槽类型再作为 RHS 的 target_ty(与普通 AnnAssign 的 P1-A 同口径)。 + # 守卫 trans.ReplCtx:非 REPL 恒 None ⇒ 本分支整体不经过。 + # 被提升的会话函数体内不走该分支(`x: int = 5` 是局部声明,Python 语义)。 + if trans.ReplCtx is not None \ + and self.Gen.VarGen.repl_in_frag_entry_scope(trans) != 0: + rp_e: rawptr[GenTypes.ReplVarEntry] = \ + self.Gen.VarGen.repl_ensure_entry(trans, nm.id, aa) + if rp_e is not None: + self.Gen.VarGen.repl_try_annassign( + trans, nm.id, aa.annotation, aa.value, aa) + return 0 + pool: rawptr[memhub.MemBuddy] = trans.Pool builder: rawptr[llvmlite.IRBuilder] = trans._cur_builder mod: rawptr[llvmlite.LLVMModule] = trans.Module @@ -1434,7 +1500,12 @@ class AssignGenMixin: callable_ret: rawptr[llvmlite.LLVMType] = self.Gen.TypeGenErr.try_extract_callable_ret( pool, annot, trans._imported_modules, trans._from_imports, trans) if callable_ret is not None: - self.Gen.CallGenLeafKit.register_func_ptr_sig(nm.id, callable_ret) + # 返回值:0=成功/同名同类型幂等命中;非 0 已在 register_func_ptr_sig + # 内部 fatal_error(表满/同名不同类型/表分配失败)终止编译。此处分支 + # 仅为防御——报错通道异常时不带着未登记的签名继续翻译。 + if self.Gen.CallGenLeafKit.register_func_ptr_sig( + pool, nm.id, callable_ret, aa) != 0: + return 0 # global 变量:写入模块作用域中的全局变量 if GenApi.is_global_name(trans, nm.id) != 0: @@ -1447,12 +1518,12 @@ class AssignGenMixin: aa_glob_ty: rawptr[llvmlite.LLVMType] = None if mod_alloca.Ty is not None: aa_glob_ty = mod_alloca.Ty.Pointee - # 供 c.Deref(i8*) 推断读取宽度 / 裸名泛型构造从注解推断类型实参: - # 与主分支(普通局部变量路径)同一口径——必须在翻译 RHS 之前写入 - # CurAssignAnnot/CurAssignNode。四处(主分支 / 本 global 分支 / - # 下方 mod_glob 分支 / 下方 nonlocal 分支)必须同步,否则 - # `x: int = c.Deref(p)` 的读取宽度与 `s: Stack[int] = Stack()` - # 的类型实参推断都会拿不到注解。 + # 供裸名泛型构造从注解推断类型实参(消费者见 CallGenCtor.vp 的 + # CurAssignNode 判定):与主分支(普通局部变量路径)同一口径—— + # 必须在翻译 RHS 之前写入 CurAssignAnnot/CurAssignNode。四处 + # (主分支 / 本 global 分支 / 下方 mod_glob 分支 / 下方 nonlocal + # 分支)必须同步,否则 `s: Stack[int] = Stack()` 的类型实参推断 + # 会拿不到注解。 trans.CurAssignAnnot = aa.annotation trans.CurAssignNode = aa.value rhs_val: rawptr[llvmlite.Value] = self.Gen.ExprGen.translate_value( @@ -1493,11 +1564,13 @@ class AssignGenMixin: target_ty = mod_alloca.Ty.Pointee if target_ty is not None: rhs_val = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val, target_ty, node, trans) - llvmlite.build_store(builder, rhs_val, mod_alloca) + # t.CVolatile(global 声明)全局:初始化 store 带 volatile 限定符 + vol_gaa: int = self.Gen.VarGen.var_is_volatile( + self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)) + llvmlite.build_store(builder, rhs_val, mod_alloca, vol_gaa) # 清除赋值上下文(防残留):与主分支清空时机同款。四处(主分支 / # 本 global 分支 / 下方 mod_glob 分支 / 下方 nonlocal 分支)必须同步清除, - # 否则残留注解会让后续 while 条件/比较等处的 c.Deref 被误判读取宽度 - # (i8* 按 i32* 读 4 字节),也会污染裸名泛型构造的类型实参推断 + # 否则残留注解会污染后续语句里裸名泛型构造的类型实参推断 trans.CurAssignAnnot = None trans.CurAssignNode = None return 0 @@ -1515,12 +1588,11 @@ class AssignGenMixin: nl_target_ty: rawptr[llvmlite.LLVMType] = None if nl_ptr is not None and nl_ptr.Ty is not None: nl_target_ty = nl_ptr.Ty.Pointee - # 供 c.Deref(i8*) 推断读取宽度 / 裸名泛型构造从注解推断类型实参: - # 与主分支、上方 global 分支、下方 mod_glob 分支同一口径——必须在 - # 翻译 RHS 之前写入 CurAssignAnnot/CurAssignNode。四处(主分支 / - # global / mod_glob / 本 nonlocal 分支)必须同步,否则 - # `nonlocal x: int = c.Deref(p)` 的读取宽度与 - # `nonlocal s: Stack[int] = Stack()` 的类型实参推断都会拿不到注解。 + # 供裸名泛型构造从注解推断类型实参:与主分支、上方 global 分支、 + # 下方 mod_glob 分支同一口径——必须在翻译 RHS 之前写入 + # CurAssignAnnot/CurAssignNode。四处(主分支 / global / mod_glob / + # 本 nonlocal 分支)必须同步,否则 + # `nonlocal s: Stack[int] = Stack()` 的类型实参推断会拿不到注解。 trans.CurAssignAnnot = aa.annotation trans.CurAssignNode = aa.value rhs_val: rawptr[llvmlite.Value] = self.Gen.ExprGen.translate_value( @@ -1542,9 +1614,8 @@ class AssignGenMixin: builder, rhs_val, nl_target_ty, node) llvmlite.build_store(builder, rhs_coerced, nl_ptr) # 清除赋值上下文(防残留):与主分支清空时机同款。四处(主分支 / - # global / mod_glob / 本 nonlocal 分支)必须同步清除,否则残留注解会让 - # 后续 while 条件/比较等处的 c.Deref 被误判读取宽度(i8* 按 i32* 读 4 - # 字节),也会污染裸名泛型构造的类型实参推断 + # global / mod_glob / 本 nonlocal 分支)必须同步清除,否则残留注解会 + # 污染后续语句里裸名泛型构造的类型实参推断 trans.CurAssignAnnot = None trans.CurAssignNode = None return 0 @@ -1568,11 +1639,10 @@ class AssignGenMixin: mg_val_ty: rawptr[llvmlite.LLVMType] = None if mod_glob.Ty is not None: mg_val_ty = mod_glob.Ty.Pointee - # 供 c.Deref(i8*) 推断读取宽度 / 裸名泛型构造从注解推断类型实参: - # 与主分支、上方 global 分支同一口径——翻译 RHS 前写入 - # CurAssignAnnot/CurAssignNode。模块级 `s: Stack[int] = Stack()` - # 走的就是本分支,四处(主分支 / global / 本 mod_glob 分支 / - # 上方 nonlocal 分支)必须同步。 + # 供裸名泛型构造从注解推断类型实参:与主分支、上方 global 分支 + # 同一口径——翻译 RHS 前写入 CurAssignAnnot/CurAssignNode。 + # 模块级 `s: Stack[int] = Stack()` 走的就是本分支,四处(主分支 / + # global / 本 mod_glob 分支 / 上方 nonlocal 分支)必须同步。 trans.CurAssignAnnot = aa.annotation trans.CurAssignNode = aa.value rhs_val_mg: rawptr[llvmlite.Value] = self.Gen.ExprGen.translate_value( @@ -1613,11 +1683,13 @@ class AssignGenMixin: target_ty_mg = mod_glob.Ty.Pointee if target_ty_mg is not None: rhs_val_mg = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val_mg, target_ty_mg, node) - llvmlite.build_store(builder, rhs_val_mg, mod_glob) + # t.CVolatile 模块级全局:初始化 store 带 volatile 限定符 + vol_mga: int = self.Gen.VarGen.var_is_volatile( + self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)) + llvmlite.build_store(builder, rhs_val_mg, mod_glob, vol_mga) # 清除赋值上下文(防残留):与主分支清空时机同款。四处(主分支 / # 上方 global 分支 / 本 mod_glob 分支 / 上方 nonlocal 分支)必须同步清除, - # 否则残留注解会让后续语句的 c.Deref 被误判读取宽度,并污染裸名泛型 - # 构造的类型实参推断 + # 否则残留注解会污染后续语句里裸名泛型构造的类型实参推断 trans.CurAssignAnnot = None trans.CurAssignNode = None return 0 @@ -1759,9 +1831,8 @@ class AssignGenMixin: self._init_global_array_from_list( builder, pool, mod, alloca, aa.value, trans) else: - # 供 c.Deref(i8*) 推断读取宽度:v: int = c.Deref(p) → 读 i32 - # CurAssignNode 记录 RHS 节点:translate_c_deref 据此判定 Deref - # 是否为赋值直接 RHS(对齐 TPC _infer_deref_target_type)。 + # 供裸名泛型构造从注解推断类型实参(消费者见 CallGenCtor.vp 的 + # CurAssignNode 判定:`s: Stack[int] = Stack()` 用注解里的 int 特化)。 # 注意:本主分支(普通局部变量)、上方 global 分支、mod_glob 分支、 # nonlocal 分支四处的 set/clear 必须同步(模块级 AnnAssign 走 # mod_glob;闭包内 AnnAssign 走 nonlocal)。 @@ -1854,7 +1925,19 @@ class AssignGenMixin: target_ty = alloca.Ty.Pointee if target_ty is not None: rhs_val = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val, target_ty, node, trans) - llvmlite.build_store(builder, rhs_val, alloca) + # t.CVolatile 局部变量:标记 VarEntry 并让本次 store 带 volatile + vol_laa: int = 0 + if aa.annotation is not None and nm.id is not None: + if self.Gen.TypeGenErr.has_decorator_marker( + aa.annotation, "CVolatile", 0) != 0: + self.Gen.VarGen.set_var_volatile(trans.SymTab, nm.id) + vol_laa = 1 + # rawptr[t.CVolatile | T](volatile 指针指向物):登记 + # PointeeVolatile;本 store 写的是指针变量自身,不影响。 + if self.Gen.TypeGenErr.has_pointee_volatile_marker( + aa.annotation) != 0: + self.Gen.VarGen.set_var_pointee_volatile(trans.SymTab, nm.id) + llvmlite.build_store(builder, rhs_val, alloca, vol_laa) # FakeDuck: 初始化影子结构体 __data__ 字段 = 初始字符串指针 # (对齐 TPC HandlesAnnAssign.py: 仅当有初值时写入 __data__) if fd_str_ty is not None and fd_meta_alloca is not None: @@ -1868,8 +1951,8 @@ class AssignGenMixin: if fd_data_ptr is not None: llvmlite.build_store(builder, rhs_val, fd_data_ptr) # 清除赋值上下文(防残留):CurAssignAnnot/CurAssignNode 只在该 - # AnnAssign 的 RHS 翻译期间有效,否则后续 while 条件/比较等处的 - # c.Deref 会被残留注解误判读取宽度(i8* 被 bitcast i32* 读 4 字节)。 + # AnnAssign 的 RHS 翻译期间有效,否则后续语句里裸名泛型构造的类型 + # 实参推断会拿到残留注解。 # 四处(本主分支 / global / mod_glob / nonlocal)必须同步清除。 trans.CurAssignAnnot = None trans.CurAssignNode = None @@ -1911,7 +1994,7 @@ class AssignGenMixin: VLogger.error("cast failed", "ASGN") return 0 - targets: rawptr[list[rawptr[ast.AST]]] = asgn.targets + targets: list[ast.AST*]* = asgn.targets if targets is None: VLogger.error("targets is None", "ASGN") return 0 @@ -1923,7 +2006,7 @@ class AssignGenMixin: # 翻译 RHS 值 # 无注解赋值无目标类型可推断,清空 CurAssignAnnot/CurAssignNode - # (防残留误判 c.Deref 宽度) + # (防残留污染裸名泛型构造的类型实参推断) trans.CurAssignAnnot = None trans.CurAssignNode = None # P1-A(上下文位宽传播):单 Name 目标且其存储类型已知时,把存储类型作为 @@ -1943,6 +2026,16 @@ class AssignGenMixin: trans.SymTab, asg_nm0.id) if asg_slot is not None and asg_slot.Ty is not None: asg_target_ty = asg_slot.Ty.Pointee + elif trans.ReplCtx is not None \ + and self.Gen.VarGen.repl_in_frag_entry_scope(trans) != 0: + # REPL 模式(M2/M3):已定型的会话变量也用槽类型做 target_ty + # (与 P1-A 同口径;未定型则维持 None,由 RHS 类型定型)。 + # 只在片段入口函数生效(被提升的会话函数体内同名赋值是局部变量) + asg_re: rawptr[GenTypes.ReplVarEntry] = \ + self.Gen.VarGen.repl_find_entry(trans.ReplCtx, asg_nm0.id) + if asg_re is not None \ + and asg_re.Kind != GenTypes.REPL_KIND_UNTYPED: + asg_target_ty = self.Gen.VarGen.repl_slot_ty(pool, asg_re) rhs_val: rawptr[llvmlite.Value] = self.Gen.ExprGen.translate_value( builder, pool, mod, asgn.value, None, 0, trans, asg_target_ty) if rhs_val is None: @@ -2008,7 +2101,7 @@ class AssignGenMixin: # store + GEP 逐字段 load → store 到各 Name 目标。 if tk == ast.ASTKind.Tuple: tgt_tup: rawptr[ast.Tuple] = rawptr[ast.Tuple](target) - tgt_elts: rawptr[list[rawptr[ast.AST]]] = tgt_tup.elts + tgt_elts: list[ast.AST*]* = tgt_tup.elts # 守卫:rhs 不是 struct(tuple 打包)时跳过本 target if tgt_elts is None or rhs_val is None or rhs_val.Ty is None: continue @@ -2084,7 +2177,14 @@ class AssignGenMixin: if elem_ptr.Ty is not None and elem_ptr.Ty.Pointee is not None: store_val = self.Gen.ExprGenBase.coerce_to_type( builder, rhs_val, elem_ptr.Ty.Pointee, node, trans) - llvmlite.build_store(builder, store_val, elem_ptr) + # rawptr[t.CVolatile|T] 基址(volatile 指针指向物):p[i] = v + # 的 store 带 volatile。仅基址可解析到条目时为 1(否则文本不变)。 + vol_sw: int = 0 + sub_wr: rawptr[ast.Subscript] = rawptr[ast.Subscript](target) + if sub_wr is not None: + vol_sw = self.Gen.ExprGenAttr.base_pointee_is_volatile( + pool, trans, sub_wr.value) + llvmlite.build_store(builder, store_val, elem_ptr, vol_sw) continue # get_subscript_ptr 返回 None: 尝试 __setitem__ 运算符重载 # 适用于自定义类(如 hashtable[key]=val → hashtable.__setitem__(key, val)) @@ -2262,7 +2362,17 @@ class AssignGenMixin: if field_ptr.Ty is not None and field_ptr.Ty.Pointee is not None: store_val = self.Gen.ExprGenBase.coerce_to_type( builder, rhs_val, field_ptr.Ty.Pointee, node, trans) - llvmlite.build_store(builder, store_val, field_ptr) + # t.CVolatile 字段:store 带 volatile 限定符 + vol_fw: int = self.Gen.ExprGenAttr.attr_field_is_volatile( + pool, trans, target) + # rawptr[t.CVolatile|T] 基址(volatile 指针指向物):p.field = v + # 的 store 亦带 volatile(两来源取或)。 + at_wr: rawptr[ast.Attribute] = rawptr[ast.Attribute](target) + if at_wr is not None: + if self.Gen.ExprGenAttr.base_pointee_is_volatile( + pool, trans, at_wr.value) != 0: + vol_fw = 1 + llvmlite.build_store(builder, store_val, field_ptr, vol_fw) continue # 守卫:field_ptr 为 None → 构造详细错误信息 attr_node: rawptr[ast.Attribute] = rawptr[ast.Attribute](target) @@ -2333,7 +2443,12 @@ class AssignGenMixin: target_ty = mod_alloca.Ty.Pointee if target_ty is not None: rhs_val = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val, target_ty, node, trans) - llvmlite.build_store(builder, rhs_val, mod_alloca) + # t.CVolatile 全局:store 带 volatile 限定符 + vol_gas: int = 0 + if nm.id is not None: + vol_gas = self.Gen.VarGen.var_is_volatile( + self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)) + llvmlite.build_store(builder, rhs_val, mod_alloca, vol_gas) continue # nonlocal 变量:通过闭包 env 写入 @@ -2362,6 +2477,20 @@ class AssignGenMixin: llvmlite.build_store(builder, rhs_coerced, nl_ptr) continue + # REPL 模式(M2/M3)会话变量:片段**入口**函数本体内的 Name 目标即 + # 会话变量(首次出现=定义:申请槽位 + 按 RHS 定型 + 写槽 + 写结果区)。 + # 守卫 trans.ReplCtx(非 REPL 恒 None ⇒ 本分支整体不经过); + # repl_in_frag_entry_scope 排除被提升的会话函数体与片段内嵌套函数体 + # (那些仍用普通局部 alloca:Python 局部语义 + 防形参同名串味)。 + if trans.ReplCtx is not None \ + and self.Gen.VarGen.repl_in_frag_entry_scope(trans) != 0: + rp_entry: rawptr[GenTypes.ReplVarEntry] = \ + self.Gen.VarGen.repl_ensure_entry(trans, nm.id, target) + if rp_entry is not None: + self.Gen.VarGen.repl_try_assign( + trans, nm.id, rhs_val, asgn.value, node) + continue + # 拦截:函数内对模块级全局变量赋值但没 global 声明 # 否则静默创建局部变量遮蔽全局,全局保持 null 导致运行时崩溃 # (_mbuddy = mb 缺 global 声明即此类 bug:Int32 解引用 null 崩溃) @@ -2387,7 +2516,10 @@ class AssignGenMixin: if alloca.Ty is not None and alloca.Ty.Pointee is not None: store_val = self.Gen.ExprGenBase.coerce_to_type( builder, rhs_val, alloca.Ty.Pointee, node, trans) - llvmlite.build_store(builder, store_val, alloca) + # t.CVolatile 局部变量:store 带 volatile 限定符 + vol_las: int = self.Gen.VarGen.var_is_volatile( + self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)) + llvmlite.build_store(builder, store_val, alloca, vol_las) # lambda 表达式赋给变量:注册为闭包变量名, # 调用分派(is_closure_name)才走闭包间接调用路径; # 否则 i8** alloca 被 _is_func_ptr_alloca 误判为 diff --git a/App/lib/core/Gen/AsyncGenMixin.vp b/App/lib/core/Gen/AsyncGenMixin.vp index 76ad5f2..3825d7c 100644 --- a/App/lib/core/Gen/AsyncGenMixin.vp +++ b/App/lib/core/Gen/AsyncGenMixin.vp @@ -198,7 +198,7 @@ class AsyncGenMixin(CallGenBase.CallGenBase): self._scan_sleep_stmts(trans, fd.children) def _scan_sleep_stmts(self, trans: rawptr[HT.Translator], - stmts: rawptr[list[rawptr[ast.AST]]]): + stmts: list[ast.AST*]*): if stmts is None or trans._coro_uses_sleep != 0: return bn: sizet = len(stmts) @@ -332,7 +332,7 @@ class AsyncGenMixin(CallGenBase.CallGenBase): # ---------- 第二遍:填额外槽表 + 状态号计数 ---------- def _scan_stmts(self, trans: rawptr[HT.Translator], - stmts: rawptr[list[rawptr[ast.AST]]]): + stmts: list[ast.AST*]*): if stmts is None: return bn: sizet = len(stmts) @@ -578,7 +578,7 @@ class AsyncGenMixin(CallGenBase.CallGenBase): return self._stmts_deep_await(tr3.finalbody) return self._stmt_has_await(stmt) - def _stmts_deep_await(self, stmts: rawptr[list[rawptr[ast.AST]]]) -> int: + def _stmts_deep_await(self, stmts: list[ast.AST*]*) -> int: if stmts is None: return 0 bn6: sizet = len(stmts) @@ -633,7 +633,7 @@ class AsyncGenMixin(CallGenBase.CallGenBase): return trans._coro_local_count def _collect_locals_stmts(self, trans: rawptr[HT.Translator], - stmts: rawptr[list[rawptr[ast.AST]]]): + stmts: list[ast.AST*]*): if stmts is None: return bn: sizet = len(stmts) @@ -729,8 +729,8 @@ class AsyncGenMixin(CallGenBase.CallGenBase): trans._coro_local_base = 5 + nparams trans._coro_extra_base = trans._coro_local_base + nlocals total: int = 5 + nparams + nlocals + nextra - head: rawptr[llvmlite.Function] = None - prev: rawptr[llvmlite.Function] = None + head: rawptr[llvmlite.ParamNode] = None + prev: rawptr[llvmlite.ParamNode] = None fi: int = 0 for fi in range(total): fty: rawptr[llvmlite.LLVMType] = i32_ty @@ -1205,7 +1205,7 @@ class AsyncGenMixin(CallGenBase.CallGenBase): builder: rawptr[llvmlite.IRBuilder], pool: rawptr[memhub.MemBuddy], mod: rawptr[llvmlite.LLVMModule], - stmts: rawptr[list[rawptr[ast.AST]]]): + stmts: list[ast.AST*]*): """递归翻译协程体语句。builder 当前块 = 活跃执行块(state0 或某恢复块)。""" if stmts is None: return @@ -2001,7 +2001,7 @@ class AsyncGenMixin(CallGenBase.CallGenBase): for hi in range(nhandlers): h_node2: rawptr[ast.AST] = tr.handlers.get(hi) handler_bb2: rawptr[llvmlite.BasicBlock] = ( - rawptr[llvmlite.BasicBlock])(handler_bbs[hi], t.CPtr) + rawptr[llvmlite.BasicBlock])(handler_bbs[hi]) llvmlite.position_at_end(builder, handler_bb2) if h_node2 is not None: hd2: rawptr[ast.ExceptHandler] = rawptr[ast.ExceptHandler](h_node2) diff --git a/App/lib/core/Gen/Call/CallGenBase.vp b/App/lib/core/Gen/Call/CallGenBase.vp index 37817d7..6053ef8 100644 --- a/App/lib/core/Gen/Call/CallGenBase.vp +++ b/App/lib/core/Gen/Call/CallGenBase.vp @@ -29,8 +29,17 @@ import lib.Stub.StubGen.VpiReader as VpiReader # ============================================================ -# 常量(与 HandlesExprCall 保持一致,本地副本避免跨模块 CDefine 查找 bug) +# 常量(Gen 层各处容量上限) +# +# GLOBAL_FUNC_MAX / GLOBAL_DEFAULTS_MAX / MAX_CLOSURE_NAMES / +# MAX_FUNC_PTR_SIGS / ISINSTANCE_CLASS_MAX 五个容量上限以本文件为**全仓唯一 +# 来源**,消费方 CallGenLeafKit.vp 在函数体内以 CallGenBase. 取值: +# 跨模块整型 t.CDefine 在函数体内可读(与 GenTypes.FIELD_MAX / +# StubMerger.MAX_INCLUDES 走同一条 ExprGenAttr 跨模块 CDefine 查找路径), +# 限制仅在「模块级 t.CDefine 初始化式里引用跨模块常量」。改此一处即全仓生效。 # ============================================================ +# 全局函数表 / 默认参数表容量(消费方:CallGenLeafKit._init_global_func_table / +# _register_global_func / _init_global_defaults_table / _register_global_defaults) GLOBAL_FUNC_MAX: t.CDefine = 8192 GLOBAL_DEFAULTS_MAX: t.CDefine = 4096 _CEXPORT_MAX: t.CDefine = 256 @@ -41,11 +50,12 @@ ASM_ARGS_BUF_SIZE: t.CDefine = 512 ASM_LINE_BUF_SIZE: t.CDefine = 4096 LLVMIR_MAX_OPS: t.CDefine = 16 LLVMIR_TEMPLATE_BUF: t.CDefine = 512 -# 闭包名表大小(原 HandlesExprCall 模块级常量) +# 闭包名表大小(消费方:CallGenLeafKit._ensure_closure_names/register_closure_name) MAX_CLOSURE_NAMES: t.CDefine = 128 -# 函数指针签名表大小(原 HandlesExprCall 模块级常量) +# 函数指针签名表大小(消费方:CallGenLeafKit._ensure_funcptr_sigs/register_func_ptr_sig) MAX_FUNC_PTR_SIGS: t.CDefine = 64 -# isinstance 子类收集上限(防数组越界;t.CDefine 注解由 TPC 解析为 i32) +# isinstance 子类收集上限(防数组越界;t.CDefine 注解由 TPC 解析为 i32。 +# 消费方:CallGenLeafKit._collect_subclass_vtable_classes) ISINSTANCE_CLASS_MAX: t.CDefine = 64 @@ -603,7 +613,8 @@ class CallGenBase: existing_steps += 1 if existing_steps > DECLARE_CHAIN_LIMIT: stdio.printf("[CALLGEN] 链表异常: module funcs(call_name=%s,遍历超限,终止)\n", call_name) - stdlib.exit(1) + # 终止点收敛:REPL 会话内 longjmp 回 REPL 循环(诊断已打印) + VLogger.repl_abort() if existing.Name is not None: # 快速路径:strcmp 判等蕴含首字符相等,首字符不同者跳过 strcmp if existing.Name[0] == call_name[0] \ @@ -622,7 +633,8 @@ class CallGenBase: cur_count += 1 if cur_count > DECLARE_CHAIN_LIMIT: stdio.printf("[CALLGEN] 链表异常: func params(call_name=%s,参数链遍历超限,终止)\n", call_name) - stdlib.exit(1) + # 终止点收敛:REPL 会话内 longjmp 回 REPL 循环(诊断已打印) + VLogger.repl_abort() cur_p = cur_p.Next # 只追加缺失的参数(cur_count < param_count 时) if cur_count < param_count: diff --git a/App/lib/core/Gen/Call/CallGenCBuiltin.vp b/App/lib/core/Gen/Call/CallGenCBuiltin.vp index fe25c8a..8285e7b 100644 --- a/App/lib/core/Gen/Call/CallGenCBuiltin.vp +++ b/App/lib/core/Gen/Call/CallGenCBuiltin.vp @@ -17,42 +17,12 @@ from . import CallGenBase # ============================================================ -# _IsTypeExpr / _IsCastWrappingDeref - c.Deref 加载宽度推断的转换包裹判定 -# (镜像 TPC HandlesSpecialCall:转换链包裹 Deref 时允许从赋值注解推断 -# 加载宽度;ifexp/binop 包裹不放行,避免 i8* 错 bitcast 成其他位宽读) +# 常量(与 TPC 侧 HandlesExprCall 保持一致) +# +# GLOBAL_FUNC_MAX / GLOBAL_DEFAULTS_MAX / MAX_CLOSURE_NAMES / +# MAX_FUNC_PTR_SIGS / ISINSTANCE_CLASS_MAX:唯一来源在 CallGenBase.vp +# (本文件不使用,故不留副本;消费方 CallGenLeafKit.vp 经 CallGenBase. 引用)。 # ============================================================ -def _IsTypeExpr(node: rawptr[ast.AST]) -> int: - """节点是否为类型表达式(Name/Attribute/Subscript/BinOp|)""" - if node is None: - return 0 - k: int = node.kind() - if k == ast.ASTKind.Name or k == ast.ASTKind.Attribute or k == ast.ASTKind.Subscript: - return 1 - if k == ast.ASTKind.BinOp: - bop: rawptr[ast.BinOp] = rawptr[ast.BinOp](node) - if bop.op == ast.OpKind.BitOr: - return 1 - return 0 - - -def _IsCastWrappingDeref(rhs: rawptr[ast.AST], deref_node: rawptr[ast.AST]) -> int: - """rhs 是否为"类型转换链包裹 c.Deref"(如 (T | t.CPtr)(t.CPtr(c.Deref(p))))""" - if rhs is None: - return 0 - if uint64(rhs) == uint64(deref_node): - return 1 - if rhs.kind() == ast.ASTKind.Call: - cl: rawptr[ast.Call] = rawptr[ast.Call](rhs) - if cl.args is not None and len(cl.args) == 1 and _IsTypeExpr(cl.func) != 0: - return _IsCastWrappingDeref(cl.args.get(0), deref_node) - return 0 - - -# ============================================================ -# 常量(与 HandlesExprCall 保持一致,本地副本避免跨模块 CDefine 查找 bug) -# ============================================================ -GLOBAL_FUNC_MAX: t.CDefine = 8192 -GLOBAL_DEFAULTS_MAX: t.CDefine = 4096 _CEXPORT_MAX: t.CDefine = 256 ASM_MAX_OPERANDS: t.CDefine = 16 # clobber(~{...})容量独立于操作数上限:vipersimd ×8 内核单条 c.Asm @@ -66,12 +36,6 @@ ASM_ARGS_BUF_SIZE: t.CDefine = 512 ASM_LINE_BUF_SIZE: t.CDefine = 4096 LLVMIR_MAX_OPS: t.CDefine = 16 LLVMIR_TEMPLATE_BUF: t.CDefine = 512 -# 闭包名表大小(原 HandlesExprCall 模块级常量) -MAX_CLOSURE_NAMES: t.CDefine = 128 -# 函数指针签名表大小(原 HandlesExprCall 模块级常量) -MAX_FUNC_PTR_SIGS: t.CDefine = 64 -# isinstance 子类收集上限(防数组越界;t.CDefine 注解由 TPC 解析为 i32) -ISINSTANCE_CLASS_MAX: t.CDefine = 64 # ============================================================ @@ -87,10 +51,16 @@ class CallGenCBuiltin(CallGenBase.CallGenBase): mod: rawptr[llvmlite.LLVMModule], cl: rawptr[ast.Call], trans: rawptr[HT.Translator] = None) -> rawptr[llvmlite.Value]: - """c.Deref(ptr) → load 按 ptr 的 pointee 类型""" + """c.Deref(ptr) → load,宽度只由 ptr 的 pointee 类型决定 + + pointee 是 i8(t.CPtr / void* / str / bytes)恒读 1 字节;不再按赋值 + 目标注解(CurAssignAnnot)反推宽度——那会让同一处 c.Deref(i8*) 的读取 + 宽度取决于它是否恰好出现在带注解赋值的 RHS 里。需要按更宽类型读时, + 由调用点显式给出元素类型指针后再解引用(见下方读取分支注释)。 + """ if cl is None: return None - cargs: rawptr[list[rawptr[ast.AST]]] = cl.args + cargs: list[ast.AST*]* = cl.args if cargs is None or len(cargs) < 1: return None @@ -99,53 +69,22 @@ class CallGenCBuiltin(CallGenBase.CallGenBase): if ptr_val is None: return None - # 按 pointee 类型读取;非指针参数默认按 i8(保持向后兼容) + # rawptr[t.CVolatile | T] 实参(volatile 指针指向物):c.Deref(p) 的 + # 解引用 load 带 volatile。仅当实参可解析到 VarEntry(Name)/ FieldEntry + # (Attribute)时为 1;其余(函数返回/调用/复杂表达式)为 0(明确不支持)。 + vol_deref: int = self.Gen.ExprGenAttr.base_pointee_is_volatile( + pool, trans, cargs.get(0)) + + # 读取宽度只由指针类型决定:按 pointee 类型 load 一个元素。 + # pointee 是 i8(t.CPtr / void* / str / bytes)即恒读 1 字节。 + # 需要 4/8 字节等更宽读取时,必须在调用点把指针显式变成有元素类型的 + # 指针再解引用(c.Deref((rawptr[T])(p)) / c.Deref((T | t.CPtr)(x))), + # 不再由赋值目标注解反推——否则同一处 c.Deref(i8*) 的宽度会随上下文 + # (是否带注解赋值的 RHS)而变。 if self.Gen.ExprGenBase.is_ptr_type(ptr_val.Ty) != 0: pointee: rawptr[llvmlite.LLVMType] = ptr_val.Ty.Pointee if pointee is not None: - # i8*(void*)时按赋值目标类型推断读取宽度(对齐 TPC - # HandlesSpecialCall._infer_deref_target_type): - # v: int = c.Deref(p) → bitcast i32* 后 load 4 字节。 - # 背景:DictTest d3[key5]=500 读出 -12——固定按 i8 读 1 字节 + - # sext 截断(0xF4 → 0xFFFFFFF4);Test 1 的小值(10)恰好正确 - # 掩盖了缺陷。 - match pointee: - case llvmlite.LLVMType.Int(pointee_width): - # 允许推断:c.Deref 是赋值语句直接 RHS(v: int = c.Deref(p)) - # 或处于类型转换链包裹中(如 vt: t.CPtr = - # t\.CVoid(c.Deref(p)))。嵌套在 while 条件/比较/binop/ - # 其他调用中(如 while c.Deref(p) != 0、total += - # int(c.Deref(p)))不推断,否则 i8* 会被残留 - # CurAssignAnnot 误判为 i32* 读 4 字节 → StringIterTest - # ASCII sum/manual strcmp 失败。 - allow_infer: int = 0 - if trans is not None and trans.CurAssignNode is not None: - if uint64(cl) == uint64(trans.CurAssignNode): - allow_infer = 1 - elif _IsCastWrappingDeref(trans.CurAssignNode, cl) != 0: - allow_infer = 1 - if pointee_width == 8 and allow_infer != 0 \ - and trans is not None and trans.CurAssignAnnot is not None: - dref_ty: rawptr[llvmlite.LLVMType] = self.Gen.TypeGenResolve.resolve_annotation_type( - trans.Pool, trans.CurAssignAnnot, - trans._imported_modules, trans._from_imports, trans, 0) - if dref_ty is not None: - dref_bits: int = self.Gen.ExprGenBase.get_llvm_type_bits(dref_ty) - dref_fbits: int = self.Gen.ExprGenBase.get_llvm_float_bits(dref_ty) - dref_is_ptr: int = self.Gen.ExprGenBase.is_ptr_type(dref_ty) - # 整数/浮点/指针目标均推断(指针 → bitcast 后 - # load 8 字节;struct 值目标不推断,保持 i8 读) - dref_ptr: rawptr[llvmlite.Value] = None - if (dref_bits != 0 or dref_fbits != 0 or dref_is_ptr != 0) \ - and dref_bits != 8: - dref_ptr_ty: rawptr[llvmlite.LLVMType] = llvmlite.Ptr(pool, dref_ty) - dref_ptr = llvmlite.build_bitcast( - builder, ptr_val, dref_ptr_ty) - if dref_ptr is not None: - return llvmlite.build_load(builder, dref_ty, dref_ptr) - case _: - pass - return llvmlite.build_load(builder, pointee, ptr_val) + return llvmlite.build_load(builder, pointee, ptr_val, vol_deref) # 非指针参数即类型推断退化:fail-fast,绝不按 i8 兜底读取(会读到垃圾宽度) self.Gen.TypeGenErr.fatal_error(cl, "c.Deref 参数不是指针类型(类型推断退化)", "TypeError") return None @@ -161,14 +100,15 @@ class CallGenCBuiltin(CallGenBase.CallGenBase): """ if cl is None: return None - cargs: rawptr[list[rawptr[ast.AST]]] = cl.args + cargs: list[ast.AST*]* = cl.args if cargs is None or len(cargs) < 1: return None # 单参形式:deref 读(paging.vp save_pt_range L263 # saved = c.DerefAs(save_count)、restore_pt_range L277-278 # c.DerefAs(entry_ptr) 根因——此前 len<2 直接 return None → # 跌落跨模块函数调用生成 call @DerefAs → ld.lld undefined symbol: - # DerefAs。复用 translate_c_deref 的类型推断读取逻辑。 + # DerefAs。读取宽度与 c.Deref 同口径(只认 pointee 类型), + # 故直接复用 translate_c_deref。 if len(cargs) < 2: return self.translate_c_deref(pool, builder, mod, cl, trans) @@ -221,7 +161,7 @@ class CallGenCBuiltin(CallGenBase.CallGenBase): """c.Set(a, b) → a = b(赋值语义,a 为左值)""" if cl is None: return None - cargs: rawptr[list[rawptr[ast.AST]]] = cl.args + cargs: list[ast.AST*]* = cl.args if cargs is None or len(cargs) < 2: return None a_node: rawptr[ast.AST] = cargs.get(0) @@ -236,7 +176,7 @@ class CallGenCBuiltin(CallGenBase.CallGenBase): if d_call.func is not None and d_call.func.kind() == ast.ASTKind.Attribute: d_at: rawptr[ast.Attribute] = rawptr[ast.Attribute](d_call.func) if d_at.attr is not None and string.strcmp(d_at.attr, "Deref") == 0: - d_args: rawptr[list[rawptr[ast.AST]]] = d_call.args + d_args: list[ast.AST*]* = d_call.args if d_args is not None and len(d_args) >= 1: ptr_val: rawptr[llvmlite.Value] = self.Gen.ExprGen.translate_value( builder, pool, mod, d_args.get(0), None, 0, trans) @@ -345,7 +285,7 @@ class CallGenCBuiltin(CallGenBase.CallGenBase): """c.Addr(var) → 返回变量的 alloca 指针(不 load)""" if cl is None: return None - cargs: rawptr[list[rawptr[ast.AST]]] = cl.args + cargs: list[ast.AST*]* = cl.args if cargs is None or len(cargs) < 1: return None @@ -385,6 +325,14 @@ class CallGenCBuiltin(CallGenBase.CallGenBase): if nl_addr is not None: return nl_addr return_var: rawptr[llvmlite.Value] = self.Gen.VarGen.lookup_var(trans.SymTab, nm.id) + # REPL 模式(M2)会话变量取址:c.Addr(x) → ctx 内该槽的地址(地址同一性)。 + # 位置在局部查找之后(片段自己声明的局部名优先,与读路径同序)、 + # 模块全局/函数引用之前。守卫 trans.ReplCtx:非 REPL 恒 None ⇒ 不变。 + if return_var is None and trans is not None and trans.ReplCtx is not None: + sess_addr: rawptr[llvmlite.Value] = self.Gen.VarGen.repl_try_addr( + trans, nm.id, arg0) + if sess_addr is not None: + return sess_addr if return_var is None and mod is not None: # 模块全局变量 c.Addr(模块全局) → 返回全局变量指针:必须经 make_global_ref 转成带引号的 Ptr(Ty) 引用(直接返回 GlobalVariable 节点时其 Ty 是 pointee、Name 无 @ 引号,参与 ptrtoint 会渲染成 "ptrtoint void to i64") gv_ref_nm: rawptr[llvmlite.GlobalVariable] = self.Gen.ExprGenAttr.find_global_in_module(mod, nm.id) @@ -646,7 +594,7 @@ class CallGenCBuiltin(CallGenBase.CallGenBase): """c.Load(a, b) → *a = *b(加载 b 的值,存储到 a)""" if cl is None: return None - cargs: rawptr[list[rawptr[ast.AST]]] = cl.args + cargs: list[ast.AST*]* = cl.args if cargs is None or len(cargs) < 2: return None @@ -1061,7 +1009,7 @@ class CallGenCBuiltin(CallGenBase.CallGenBase): if cl is None: return None - cargs: rawptr[list[rawptr[ast.AST]]] = cl.args + cargs: list[ast.AST*]* = cl.args if cargs is None or len(cargs) < 1: return None @@ -1117,7 +1065,7 @@ class CallGenCBuiltin(CallGenBase.CallGenBase): if tk == ast.ASTKind.JoinedStr: js: rawptr[ast.JoinedStr] = rawptr[ast.JoinedStr](tmpl_node) - jvalues: rawptr[list[rawptr[ast.AST]]] = js.values + jvalues: list[ast.AST*]* = js.values if jvalues is not None: jvlen: sizet = len(jvalues) for vi in range(jvlen): @@ -1144,7 +1092,7 @@ class CallGenCBuiltin(CallGenBase.CallGenBase): else: continue - fargs: rawptr[list[rawptr[ast.AST]]] = fcall.args + fargs: list[ast.AST*]* = fcall.args if fargs is None or len(fargs) < 1: continue @@ -1237,7 +1185,7 @@ class CallGenCBuiltin(CallGenBase.CallGenBase): # ============================================================ # 2. 解析关键字参数 out=/inp=/op=/clobber= # ============================================================ - keywords: rawptr[list[rawptr[ast.AST]]] = cl.keywords + keywords: list[ast.AST*]* = cl.keywords if keywords is not None: kwlen: sizet = len(keywords) for ki in range(kwlen): @@ -1251,7 +1199,7 @@ class CallGenCBuiltin(CallGenBase.CallGenBase): if kwk != ast.ASTKind.List: continue out_lst: rawptr[ast.List] = rawptr[ast.List](kw.value) - out_elts: rawptr[list[rawptr[ast.AST]]] = out_lst.elts + out_elts: list[ast.AST*]* = out_lst.elts if out_elts is None: continue oelen: sizet = len(out_elts) @@ -1263,7 +1211,7 @@ class CallGenCBuiltin(CallGenBase.CallGenBase): oattr: str = self._asm_is_c_call(ocall) if oattr is None or string.strcmp(oattr, "AsmOut") != 0: continue - oargs: rawptr[list[rawptr[ast.AST]]] = ocall.args + oargs: list[ast.AST*]* = ocall.args if oargs is None or len(oargs) < 1: continue oval_node: rawptr[ast.AST] = oargs.get(0) @@ -1294,7 +1242,7 @@ class CallGenCBuiltin(CallGenBase.CallGenBase): if kwk != ast.ASTKind.List: continue in_lst: rawptr[ast.List] = rawptr[ast.List](kw.value) - in_elts: rawptr[list[rawptr[ast.AST]]] = in_lst.elts + in_elts: list[ast.AST*]* = in_lst.elts if in_elts is None: continue ielen: sizet = len(in_elts) @@ -1306,7 +1254,7 @@ class CallGenCBuiltin(CallGenBase.CallGenBase): iattr: str = self._asm_is_c_call(icall) if iattr is None or string.strcmp(iattr, "AsmInp") != 0: continue - iargs: rawptr[list[rawptr[ast.AST]]] = icall.args + iargs: list[ast.AST*]* = icall.args if iargs is None or len(iargs) < 1: continue ival_node: rawptr[ast.AST] = iargs.get(0) @@ -1325,7 +1273,7 @@ class CallGenCBuiltin(CallGenBase.CallGenBase): if kwk != ast.ASTKind.List: continue cl_lst: rawptr[ast.List] = rawptr[ast.List](kw.value) - cl_elts: rawptr[list[rawptr[ast.AST]]] = cl_lst.elts + cl_elts: list[ast.AST*]* = cl_lst.elts if cl_elts is None: continue clen: sizet = len(cl_elts) @@ -1349,7 +1297,7 @@ class CallGenCBuiltin(CallGenBase.CallGenBase): pos_node: rawptr[ast.AST] = cargs.get(1) if pos_node is not None and pos_node.kind() == ast.ASTKind.List: pos_lst: rawptr[ast.List] = rawptr[ast.List](pos_node) - pos_elts: rawptr[list[rawptr[ast.AST]]] = pos_lst.elts + pos_elts: list[ast.AST*]* = pos_lst.elts if pos_elts is not None: plen: sizet = len(pos_elts) for pi in range(plen): @@ -1501,7 +1449,7 @@ class CallGenCBuiltin(CallGenBase.CallGenBase): tk2: int = tmpl_node.kind() if tk2 == ast.ASTKind.JoinedStr: js2: rawptr[ast.JoinedStr] = rawptr[ast.JoinedStr](tmpl_node) - jvalues2: rawptr[list[rawptr[ast.AST]]] = js2.values + jvalues2: list[ast.AST*]* = js2.values if jvalues2 is not None: jvlen2: sizet = len(jvalues2) fv_fi: int = 0 @@ -1696,7 +1644,7 @@ class CallGenCBuiltin(CallGenBase.CallGenBase): if cl is None or builder is None or pool is None or trans is None: return None - cargs: rawptr[list[rawptr[ast.AST]]] = cl.args + cargs: list[ast.AST*]* = cl.args if cargs is None or len(cargs) < 1: self.Gen.TypeGenErr.fatal_error(cl, "c.LLVMIR 缺少模板参数", "TypeError") @@ -1726,7 +1674,7 @@ class CallGenCBuiltin(CallGenBase.CallGenBase): if tk == ast.ASTKind.JoinedStr: js: rawptr[ast.JoinedStr] = rawptr[ast.JoinedStr](first_arg) - jvalues: rawptr[list[rawptr[ast.AST]]] = js.values + jvalues: list[ast.AST*]* = js.values if jvalues is not None: jvlen: sizet = len(jvalues) for vi in range(jvlen): @@ -1751,7 +1699,7 @@ class CallGenCBuiltin(CallGenBase.CallGenBase): fcall: rawptr[ast.Call] = rawptr[ast.Call](fexpr) attr_nm: str = self._asm_is_c_call(fcall) if attr_nm is not None: - fargs: rawptr[list[rawptr[ast.AST]]] = fcall.args + fargs: list[ast.AST*]* = fcall.args if fargs is None or len(fargs) < 1: continue val_node: rawptr[ast.AST] = fargs.get(0) diff --git a/App/lib/core/Gen/Call/CallGenCall.vp b/App/lib/core/Gen/Call/CallGenCall.vp index 4c80fbe..5d62206 100644 --- a/App/lib/core/Gen/Call/CallGenCall.vp +++ b/App/lib/core/Gen/Call/CallGenCall.vp @@ -724,7 +724,7 @@ class CallGenCall(CallGenBase.CallGenBase): case _: pass if lb_obj is not None: - lb_cargs: rawptr[list[rawptr[ast.AST]]] = cl.args + lb_cargs: list[ast.AST*]* = cl.args lb_can: sizet = 0 if lb_cargs is not None: lb_can = len(lb_cargs) @@ -1191,11 +1191,11 @@ class CallGenCall(CallGenBase.CallGenBase): "TypeError") # 翻译调用参数 - cargs: rawptr[list[rawptr[ast.AST]]] = cl.args + cargs: list[ast.AST*]* = cl.args can: sizet = 0 if cargs is not None: can = len(cargs) - kw_calls: rawptr[list[rawptr[ast.AST]]] = cl.keywords + kw_calls: list[ast.AST*]* = cl.keywords kwc: sizet = 0 if kw_calls is not None: kwc = len(kw_calls) @@ -1689,7 +1689,7 @@ class CallGenCall(CallGenBase.CallGenBase): cl: rawptr[ast.Call], lm_name: str, lm_obj: rawptr[llvmlite.Value], - cargs: rawptr[list[rawptr[ast.AST]]], + cargs: list[ast.AST*]*, can: sizet, trans: rawptr[HT.Translator]) -> rawptr[llvmlite.Value]: """翻译 list 内置方法调用,返回 call 结果 Value(void 方法返回 None)""" @@ -2033,7 +2033,7 @@ class CallGenCall(CallGenBase.CallGenBase): if mangled_sp is not None: call_name = mangled_sp # 5. 翻译参数 + 按形参 coerce(self 是形参 0,第 ai 个实参对应形参 ai+1) - cargs_sp: rawptr[list[rawptr[ast.AST]]] = cl.args + cargs_sp: list[ast.AST*]* = cl.args can_sp: sizet = 0 if cargs_sp is not None: can_sp = len(cargs_sp) diff --git a/App/lib/core/Gen/Call/CallGenCtor.vp b/App/lib/core/Gen/Call/CallGenCtor.vp index e538ceb..1c79888 100644 --- a/App/lib/core/Gen/Call/CallGenCtor.vp +++ b/App/lib/core/Gen/Call/CallGenCtor.vp @@ -111,7 +111,7 @@ class CallGenCtor(CallGenBase.CallGenBase): mod: rawptr[llvmlite.LLVMModule], struct_ty: rawptr[llvmlite.LLVMType], class_name: str, - cargs: rawptr[list[rawptr[ast.AST]]], + cargs: list[ast.AST*]*, can: sizet, trans: rawptr[HT.Translator]) -> rawptr[llvmlite.Value]: """结构体构造函数:按字段顺序 store 参数值,返回结构体值 @@ -361,7 +361,7 @@ class CallGenCtor(CallGenBase.CallGenBase): class_name: str, variant_name: str, tag_value: int, - cargs: rawptr[list[rawptr[ast.AST]]], + cargs: list[ast.AST*]*, can: sizet, trans: rawptr[HT.Translator]) -> rawptr[llvmlite.Value]: """构造 REnum 变体实例,返回指向 alloca 的指针 @@ -462,9 +462,9 @@ class CallGenCtor(CallGenBase.CallGenBase): mod: rawptr[llvmlite.LLVMModule], struct_ty: rawptr[llvmlite.LLVMType], class_name: str, - cargs: rawptr[list[rawptr[ast.AST]]], + cargs: list[ast.AST*]*, can: sizet, - keywords: rawptr[list[rawptr[ast.AST]]], + keywords: list[ast.AST*]*, kw_count: sizet, trans: rawptr[HT.Translator]) -> rawptr[llvmlite.Value]: """结构体构造函数(关键字参数版本)""" @@ -791,7 +791,7 @@ class CallGenCtor(CallGenBase.CallGenBase): tn: str = None if k == ast.ASTKind.Tuple: tp: rawptr[ast.Tuple] = rawptr[ast.Tuple](slice_node) - elts: rawptr[list[rawptr[ast.AST]]] = tp.elts + elts: list[ast.AST*]* = tp.elts if elts is not None: n: sizet = len(elts) i: sizet @@ -922,7 +922,7 @@ class CallGenCtor(CallGenBase.CallGenBase): return self._annotation_refers_type_param(bop_a.right, tp_name) return 0 - def _get_ctor_init_params(self, class_name: str) -> rawptr[list[rawptr[ast.AST]]]: + def _get_ctor_init_params(self, class_name: str) -> list[ast.AST*]*: """取泛型类模板 __init__ 的形参列表(含 self,形参 0) 模板不存在 / 无 __init__ / args 形态不可用 → None。 @@ -937,7 +937,7 @@ class CallGenCtor(CallGenBase.CallGenBase): template_cd: rawptr[ast.ClassDef] = self.Gen.ClassGenGeneric._find_generic_template(class_name) if template_cd is None or template_cd.children is None: return None - children: rawptr[list[rawptr[ast.AST]]] = template_cd.children + children: list[ast.AST*]* = template_cd.children cn: sizet = len(children) ci: sizet for ci in range(cn): @@ -967,7 +967,7 @@ class CallGenCtor(CallGenBase.CallGenBase): """ if class_name is None or tp_name is None: return -1 - plist: rawptr[list[rawptr[ast.AST]]] = self._get_ctor_init_params(class_name) + plist: list[ast.AST*]* = self._get_ctor_init_params(class_name) if plist is None: return -1 pn: sizet = len(plist) @@ -1011,7 +1011,7 @@ class CallGenCtor(CallGenBase.CallGenBase): """ if class_name is None or kw_name is None: return -1 - plist_sb: rawptr[list[rawptr[ast.AST]]] = self._get_ctor_init_params(class_name) + plist_sb: list[ast.AST*]* = self._get_ctor_init_params(class_name) if plist_sb is None: return -1 pn_sb: sizet = len(plist_sb) @@ -1029,7 +1029,7 @@ class CallGenCtor(CallGenBase.CallGenBase): def _find_ctor_type_param_kw_arg_index(self, class_name: str, tp_name: str, - keywords: rawptr[list[rawptr[ast.AST]]], + keywords: list[ast.AST*]*, kw_count: sizet, pos_count: int) -> int: """仅关键字实参的泛型构造:类型形参 tp_name 对应的关键字实参下标 @@ -1051,7 +1051,7 @@ class CallGenCtor(CallGenBase.CallGenBase): return -1 # 未找到,或落在 self 位(0 不是实参) if pi_kw - 1 < pos_count: return -1 # 该形参位已被位置实参占用 - plist_kw: rawptr[list[rawptr[ast.AST]]] = self._get_ctor_init_params(class_name) + plist_kw: list[ast.AST*]* = self._get_ctor_init_params(class_name) if plist_kw is None: return -1 pa_kw: rawptr[ast.AST] = plist_kw.get(sizet(pi_kw)) @@ -1079,10 +1079,11 @@ class CallGenCtor(CallGenBase.CallGenBase): 场景:`s: Stack[int] = Stack()`(__init__ 无参 ⇒ 实参推断不到 T)、 `def f() -> Stack[int]: return Stack()`。注解里已显式写出 T,必须按它 特化——这不是"取不到就默认 int"的兜底,而是用户显式写下的类型实参。 - 来源(两者都要求本次 Call 是注解语句的**直接值**,identity 判定先例: - CallGenCBuiltin 的 CurAssignNode 判定 L122-125): + 来源(两者都要求本次 Call 是注解语句的**直接值**,判据即下方 ① 的 + identity 比较): ① trans.CurAssignAnnot:AnnAssign 赋值目标注解(AssignGenMixin 在 - 翻译 RHS 前写入,L1760)+ trans.CurAssignNode 为本次 Call; + 翻译 RHS 前写入,四处 set:主分支 / global / mod_glob / nonlocal) + + trans.CurAssignNode 为本次 Call; ② trans.CurFuncRetAnnot:当前函数返回注解(FuncGenDef 进入函数体时 写入)+ trans.CurRetNode 为本次 Call。 注解 → 类型实参的解析复用 _extract_type_args_from_slice(与 @@ -1162,11 +1163,11 @@ class CallGenCtor(CallGenBase.CallGenBase): return None # 翻译实参为参数指针数组(extra_args[slot] = Value 指针):位置实参 → 槽 0..can-1;关键字实参 → 按 __init__ 形参名定位槽位(与 _find_ctor_type_param_kw_arg_index 共用同一套形参遍历)。位置实参优先,关键字不可按出现顺序追加——乱序会串位 - cargs: rawptr[list[rawptr[ast.AST]]] = cl.args + cargs: list[ast.AST*]* = cl.args can: sizet = 0 if cargs is not None: can = len(cargs) - keywords: rawptr[list[rawptr[ast.AST]]] = cl.keywords + keywords: list[ast.AST*]* = cl.keywords kw_count: sizet = 0 if keywords is not None: kw_count = len(keywords) diff --git a/App/lib/core/Gen/Call/CallGenDispatch.vp b/App/lib/core/Gen/Call/CallGenDispatch.vp index 19a7da2..8132eb2 100644 --- a/App/lib/core/Gen/Call/CallGenDispatch.vp +++ b/App/lib/core/Gen/Call/CallGenDispatch.vp @@ -41,7 +41,7 @@ class CallGenDispatch(CallGenBase.CallGenBase): func_count: int, trans: rawptr[HT.Translator], func_name: str, - cargs: rawptr[list[rawptr[ast.AST]]], + cargs: list[ast.AST*]*, can: sizet, hit_box: t.CPtr = None) -> rawptr[llvmlite.Value]: """super()/ctraits 编译期检测(未命中返回 None) @@ -77,7 +77,7 @@ class CallGenDispatch(CallGenBase.CallGenBase): ct_nm: rawptr[ast.Name] = rawptr[ast.Name](ct_at.value) if ct_nm.id is not None and string.strcmp(ct_nm.id, "ctraits") == 0 \ and string.strcmp(func_name, "isptr") == 0: - ct_args: rawptr[list[rawptr[ast.AST]]] = cl.args + ct_args: list[ast.AST*]* = cl.args if ct_args is not None and len(ct_args) >= 1: ct_arg0: rawptr[ast.AST] = ct_args.get(0) if ct_arg0 is not None and ct_arg0.kind() == ast.ASTKind.Name: @@ -97,7 +97,7 @@ class CallGenDispatch(CallGenBase.CallGenBase): func_count: int, trans: rawptr[HT.Translator], func_name: str, - cargs: rawptr[list[rawptr[ast.AST]]], + cargs: list[ast.AST*]*, can: sizet, hit_box: t.CPtr = None) -> rawptr[llvmlite.Value]: """c.Deref/Asm/LLVMIR 等特殊调用(未命中返回 None) @@ -146,7 +146,7 @@ class CallGenDispatch(CallGenBase.CallGenBase): elif string.strcmp(func_name, "CIf") == 0: # 运行时 c.CIf(expr)(编译期求值失败回退):expr 与 0 比较得到 i1 # 对齐 TPC HandlesExprCall.py L2434-2444 - cif_args: rawptr[list[rawptr[ast.AST]]] = cl.args + cif_args: list[ast.AST*]* = cl.args if cif_args is not None and len(cif_args) >= 1: cif_arg_val: rawptr[llvmlite.Value] = self.Gen.ExprGen.translate_value( builder, pool, mod, cif_args.get(0), funcs_ptr, func_count, trans) @@ -201,7 +201,7 @@ class CallGenDispatch(CallGenBase.CallGenBase): func_count: int, trans: rawptr[HT.Translator], func_name: str, - cargs: rawptr[list[rawptr[ast.AST]]], + cargs: list[ast.AST*]*, can: sizet) -> rawptr[llvmlite.Value]: """stdarg 糖 + str/int/bool 内置强转(未命中返回 None)""" # stdarg 糖函数处理: va_arg / arg / va_start / va_end @@ -290,7 +290,7 @@ class CallGenDispatch(CallGenBase.CallGenBase): func_count: int, trans: rawptr[HT.Translator], func_name: str, - cargs: rawptr[list[rawptr[ast.AST]]], + cargs: list[ast.AST*]*, can: sizet) -> rawptr[llvmlite.Value]: """t.XXX / typedef 类型转换(未命中返回 None)""" # 检测 t.XXX 类型转换: uint64(ptr), t.CPtr(val), int(val) 等 @@ -558,7 +558,7 @@ class CallGenDispatch(CallGenBase.CallGenBase): func_count: int, trans: rawptr[HT.Translator], func_name: str, - cargs: rawptr[list[rawptr[ast.AST]]], + cargs: list[ast.AST*]*, can: sizet) -> rawptr[llvmlite.Value]: """__sizeof__/len/isinstance 内置(未命中返回 None)""" # 检测 ClassName.__sizeof__() — 返回结构体大小常量(i64) @@ -1010,7 +1010,7 @@ class CallGenDispatch(CallGenBase.CallGenBase): func_count: int, trans: rawptr[HT.Translator], func_name: str, - cargs: rawptr[list[rawptr[ast.AST]]], + cargs: list[ast.AST*]*, can: sizet, hit_box: t.CPtr = None) -> rawptr[llvmlite.Value]: """静态类/方法调用/表达式接收者/self.field/list 内置(未命中返回 None) @@ -1151,6 +1151,50 @@ class CallGenDispatch(CallGenBase.CallGenBase): if st_res2 is not None: return st_res2 + # [M4·REPL 专用分支] 会话变量接收者的方法调用:`obj.m(args)` 的 obj 是**会话 + # 变量**时,其值在 ctx 槽里、不在 SymTab ⇒ 上面的 lookup_var 分支恒不命中 + # (本次之前:整个分派都靠 SymTab,跨片段的 `p.get()` 直接落空)。此处按 + # 会话表判定:命中即取接收者(PTR 条目取槽内指针值;STRUCT 条目取**槽地址** + # = 对象自身地址,正是 self 需要的形态),再用接收者类型的类名走同一条 + # `_translate_method_call`(符号 .<类>.<方法>、返回类型经 text.ll/vpi)。 + # 守卫 trans.ReplCtx is not None ⇒ 非 REPL 编译路径一行不经过(逐字不变)。 + if cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute \ + and trans is not None and trans.ReplCtx is not None and pool is not None: + rp_at: rawptr[ast.Attribute] = rawptr[ast.Attribute](cl.func) + if rp_at is not None and rp_at.value is not None and rp_at.attr is not None: + if rp_at.value.kind() == ast.ASTKind.Name: + rp_nm: rawptr[ast.Name] = rawptr[ast.Name](rp_at.value) + if rp_nm.id is not None: + rp_ent: rawptr[GenTypes.ReplVarEntry] = \ + self.Gen.VarGen.repl_find_entry(trans.ReplCtx, rp_nm.id) + if rp_ent is not None: + rp_val: rawptr[llvmlite.Value] = None + if rp_ent.TyTag == GenTypes.REPL_TY_STRUCT: + # 结构体值内联在槽里:槽地址即对象地址(self) + rp_val = self.Gen.VarGen.repl_try_addr(trans, rp_nm.id, cl) + else: + rp_val = self.Gen.VarGen.repl_try_read(trans, rp_nm.id, cl) + rp_cls: str = None + if rp_val is not None and rp_val.Ty is not None: + rp_sty: rawptr[llvmlite.LLVMType] = \ + self.Gen.StructGen.get_struct_type_from_value(rp_val) + if rp_sty is not None: + rp_cls = self.Gen.StructGen.get_class_name_by_type( + pool, rp_sty) + if rp_cls is not None: + rp_obj: rawptr[llvmlite.Value] = rp_val + if self.Gen.ExprGenBase.is_ptr_type(rp_val.Ty) != 0 \ + and rp_val.Ty.Pointee is not None \ + and self.Gen.ExprGenBase.is_ptr_type(rp_val.Ty.Pointee) != 0: + rp_obj = llvmlite.build_load(builder, rp_val.Ty, rp_val) + if rp_obj is not None: + if hit_box is not None: + hit_box[0] = 1 + return self.Gen.CallGenCall._translate_method_call( + pool, builder, mod, rp_cls, rp_at.attr, rp_obj, cl, trans) + # 会话变量命中但接收者不是结构体类(如 int/str/其它指针): + # 不拦截、不置 hit,交给下方既有分支(list 内置/FakeDuck 等) + # 检测方法调用: obj.method(args) # 当 cl.func 是 Attribute 且 obj 是已注册的结构体变量时,翻译为方法调用 if cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute: diff --git a/App/lib/core/Gen/Call/CallGenFd.vp b/App/lib/core/Gen/Call/CallGenFd.vp index 91b9015..7834a5b 100644 --- a/App/lib/core/Gen/Call/CallGenFd.vp +++ b/App/lib/core/Gen/Call/CallGenFd.vp @@ -132,7 +132,7 @@ class CallGenFd(CallGenBase.CallGenBase): llvmlite.value_set_next(fd_self, None) fd_head: rawptr[llvmlite.Value] = fd_self fd_tail: rawptr[llvmlite.Value] = fd_self - fd_args: rawptr[list[rawptr[ast.AST]]] = cl.args + fd_args: list[ast.AST*]* = cl.args fd_can: int = 0 if fd_args is not None: fd_can = len(fd_args) diff --git a/App/lib/core/Gen/Call/CallGenLeafKit.vp b/App/lib/core/Gen/Call/CallGenLeafKit.vp index 60dff58..eb68c61 100644 --- a/App/lib/core/Gen/Call/CallGenLeafKit.vp +++ b/App/lib/core/Gen/Call/CallGenLeafKit.vp @@ -14,6 +14,7 @@ import lib.core.CompilerFactory as CompilerFactory import lib.core.Gen.Type.TypeSpec as TSpec from . import CallGenCBuiltin +from . import CallGenBase # ============================================================ @@ -28,10 +29,12 @@ from . import CallGenCBuiltin # ============================================================ -# 常量(与 HandlesExprCall 保持一致,本地副本避免跨模块 CDefine 查找 bug) +# 常量(与 TPC 侧 HandlesExprCall 保持一致) +# +# GLOBAL_FUNC_MAX / GLOBAL_DEFAULTS_MAX / MAX_CLOSURE_NAMES / +# MAX_FUNC_PTR_SIGS / ISINSTANCE_CLASS_MAX:唯一来源在 CallGenBase.vp +# (本文件是唯一消费方,经 CallGenBase. 取值,本地不再保留副本)。 # ============================================================ -GLOBAL_FUNC_MAX: t.CDefine = 8192 -GLOBAL_DEFAULTS_MAX: t.CDefine = 4096 _CEXPORT_MAX: t.CDefine = 256 ASM_MAX_OPERANDS: t.CDefine = 16 ASM_TEXT_BUF_SIZE: t.CDefine = 2048 @@ -40,17 +43,13 @@ ASM_ARGS_BUF_SIZE: t.CDefine = 512 ASM_LINE_BUF_SIZE: t.CDefine = 4096 LLVMIR_MAX_OPS: t.CDefine = 16 LLVMIR_TEMPLATE_BUF: t.CDefine = 512 -# 闭包名表大小(原 HandlesExprCall 模块级常量) -MAX_CLOSURE_NAMES: t.CDefine = 128 -# 函数指针签名表大小(原 HandlesExprCall 模块级常量) -MAX_FUNC_PTR_SIGS: t.CDefine = 64 -# isinstance 子类收集上限(防数组越界;t.CDefine 注解由 TPC 解析为 i32) -ISINSTANCE_CLASS_MAX: t.CDefine = 64 # ============================================================ # 全局函数表名索引(名 → 槽下标,开放寻址 | 线性探测) # -# 槽数 = 2 * GLOBAL_FUNC_MAX(2 的幂,负载 < 0.5,线性探测必然终止)。 +# 槽数 = 2 * CallGenBase.GLOBAL_FUNC_MAX(2 的幂,负载 < 0.5,线性探测必然终止)。 +# 值写成字面量 16384:模块级 t.CDefine 初始化式不能引用跨模块常量 +# (跨模块 t.CDefine 只能在函数体内读),故与 CallGenBase.GLOBAL_FUNC_MAX 手工保持 2 倍关系。 # 槽存槽下标 + 1(0=空槽)。GlobalFuncs 在 PermPool 定长分配、只追加不重排、 # 从不 reset ⇒ 槽下标稳定;Built == GlobalFuncCount 才可信。 # ============================================================ @@ -402,7 +401,7 @@ class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): arr[0] = sizet(at.attr) elif k == ast.ASTKind.Tuple: tp: rawptr[ast.Tuple] = rawptr[ast.Tuple](node) - elts: rawptr[list[rawptr[ast.AST]]] = tp.elts + elts: list[ast.AST*]* = tp.elts if elts is not None: n: sizet = len(elts) for i in range(n): @@ -431,14 +430,14 @@ class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): """反向 BFS 收集目标类及全部子类(含自身,仅 HasVTable==1),返回 0 结尾数组""" if class_name is None: return None - names: sizet | t.CPtr = pool.alloc(8 * (ISINSTANCE_CLASS_MAX + 1)) - queue: sizet | t.CPtr = pool.alloc(8 * ISINSTANCE_CLASS_MAX) - visited: sizet | t.CPtr = pool.alloc(8 * ISINSTANCE_CLASS_MAX) + names: sizet | t.CPtr = pool.alloc(8 * (CallGenBase.ISINSTANCE_CLASS_MAX + 1)) + queue: sizet | t.CPtr = pool.alloc(8 * CallGenBase.ISINSTANCE_CLASS_MAX) + visited: sizet | t.CPtr = pool.alloc(8 * CallGenBase.ISINSTANCE_CLASS_MAX) if names is None or queue is None or visited is None: return None - string.memset(names, 0, 8 * (ISINSTANCE_CLASS_MAX + 1)) - string.memset(queue, 0, 8 * ISINSTANCE_CLASS_MAX) - string.memset(visited, 0, 8 * ISINSTANCE_CLASS_MAX) + string.memset(names, 0, 8 * (CallGenBase.ISINSTANCE_CLASS_MAX + 1)) + string.memset(queue, 0, 8 * CallGenBase.ISINSTANCE_CLASS_MAX) + string.memset(visited, 0, 8 * CallGenBase.ISINSTANCE_CLASS_MAX) qcount: int = 1 outcount: int = 0 vcount: int = 0 @@ -459,12 +458,12 @@ class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): break if dup != 0: continue - if vcount < ISINSTANCE_CLASS_MAX: + if vcount < CallGenBase.ISINSTANCE_CLASS_MAX: visited[vcount] = sizet(cur) vcount += 1 centry: rawptr[GenTypes.StructEntry] = self.Gen.StructGen.find_struct(cur) if centry is not None and centry.HasVTable != 0: - if outcount < ISINSTANCE_CLASS_MAX: + if outcount < CallGenBase.ISINSTANCE_CLASS_MAX: names[outcount] = sizet(cur) outcount += 1 # 子类入队:遍历全部已注册 struct,ParentNames 含 cur 者 @@ -483,7 +482,7 @@ class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): if pa != 0: pn: str = (str | t.CPtr)(pa) if pn is not None and string.strcmp(pn, cur) == 0: - if qcount < ISINSTANCE_CLASS_MAX: + if qcount < CallGenBase.ISINSTANCE_CLASS_MAX: queue[qcount] = sizet(e2.Name) qcount += 1 break @@ -574,7 +573,7 @@ class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): builder: rawptr[llvmlite.IRBuilder], mod: rawptr[llvmlite.LLVMModule], closure_alloca: rawptr[llvmlite.Value], - cargs: rawptr[list[rawptr[ast.AST]]], + cargs: list[ast.AST*]*, can: sizet, trans: rawptr[HT.Translator] = None) -> rawptr[llvmlite.Value]: """通过闭包指针间接调用(支持带参 lambda:i32(i8*, args...))""" @@ -663,9 +662,9 @@ class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): def _ensure_closure_names(self) -> int: """确保闭包名表已分配,0 失败""" if self.ClosureNames is not None: return 1 - self.ClosureNames = CompilerFactory.factory.PermPool.alloc(64 * MAX_CLOSURE_NAMES) + self.ClosureNames = CompilerFactory.factory.PermPool.alloc(64 * CallGenBase.MAX_CLOSURE_NAMES) if self.ClosureNames is None: return 0 - string.memset(self.ClosureNames, 0, 64 * MAX_CLOSURE_NAMES) + string.memset(self.ClosureNames, 0, 64 * CallGenBase.MAX_CLOSURE_NAMES) return 1 @@ -673,7 +672,7 @@ class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): """注册嵌套函数(闭包变量)名,0 成功""" if name is None: return 1 if self._ensure_closure_names() == 0: return 1 - if self.ClosureCount >= MAX_CLOSURE_NAMES: return 1 + if self.ClosureCount >= CallGenBase.MAX_CLOSURE_NAMES: return 1 idx: sizet = sizet(self.ClosureCount) * 64 string.strncpy(self.ClosureNames + idx, name, 63) self.ClosureCount += 1 @@ -695,22 +694,84 @@ class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): """确保函数指针签名表已分配(stdlib.malloc,懒初始化)""" if self.FuncptrNames is not None: return 1 - self.FuncptrNames = CompilerFactory.factory.PermPool.alloc(64 * MAX_FUNC_PTR_SIGS) - self.FuncptrRets = CompilerFactory.factory.PermPool.alloc(8 * MAX_FUNC_PTR_SIGS) + self.FuncptrNames = CompilerFactory.factory.PermPool.alloc(64 * CallGenBase.MAX_FUNC_PTR_SIGS) + self.FuncptrRets = CompilerFactory.factory.PermPool.alloc(8 * CallGenBase.MAX_FUNC_PTR_SIGS) if self.FuncptrNames is None or self.FuncptrRets is None: return 0 - string.memset(self.FuncptrNames, 0, 64 * MAX_FUNC_PTR_SIGS) + string.memset(self.FuncptrNames, 0, 64 * CallGenBase.MAX_FUNC_PTR_SIGS) return 1 - def register_func_ptr_sig(self, name: str, - ret_ty: rawptr[llvmlite.LLVMType]) -> int: - """注册函数指针变量的返回类型签名,0 成功""" + def _func_ptr_sig_fatal(self, pool: rawptr[memhub.MemBuddy], + node: rawptr[ast.AST], name: str, + ret_ty: rawptr[llvmlite.LLVMType], + reason: str) -> None: + """函数指针签名登记失败 → 编译期报错(文案含变量名/返回类型/上限/处置建议) + + 原实现满表时静默 return 1:登记被丢弃后,调用点才以 + "函数指针变量 'X' 的返回类型未注册(t.Callable 注解缺失)" + (下方 _translate_func_ptr_call:843-850)报错,定位到调用行而非登记处, + 掩盖真因(表满/重名冲突)。此处统一在登记点 fail-fast。 + fatal_error 终止编译、不返回,故调用方的返回值分支仅为防御性写法。 + """ + cap: int = CallGenBase.MAX_FUNC_PTR_SIGS + ret_str: str = "?" + if ret_ty is not None: + typed_buf: str = pool.alloc(96) + if typed_buf is not None: + llvmlite.TypePrint(typed_buf, 96, ret_ty, pool) + if typed_buf[0] != '\0': + ret_str = typed_buf + msg_buf: str = pool.alloc(1024) + if msg_buf is not None: + viperlib.snprintf( + msg_buf, 1024, + "函数指针签名表登记失败:变量 '%s'(返回类型 %s)——%s。" + "当前上限 MAX_FUNC_PTR_SIGS=%d,已登记 %d 条。" + "同名同返回类型的重复登记已做原地命中、不占新槽位;" + "若确实需要更多函数指针变量,请上调 CallGenBase.vp 的 " + "MAX_FUNC_PTR_SIGS(全仓唯一来源,改此一处即全仓生效)", + name, ret_str, reason, cap, self.FuncptrCount) + self.Gen.TypeGenErr.fatal_error(node, msg_buf, "TypeError") + return + self.Gen.TypeGenErr.fatal_error( + node, "函数指针签名表登记失败(签名表已满或变量名冲突)", "TypeError") + + def register_func_ptr_sig(self, pool: rawptr[memhub.MemBuddy], name: str, + ret_ty: rawptr[llvmlite.LLVMType], + node: rawptr[ast.AST] = None) -> int: + """注册函数指针变量的返回类型签名,0=成功(含幂等命中)/ 1=失败(已报错终止) + + 表按**裸变量名**全局共享(PermPool,跨文件/跨阶段存活),同一变量可能被 + 多轮登记(模块级:Translator.vp:568-575 的预注册遍与 translate_children + TranslatorGenMixin.vp:130-136 各见同一 AnnAssign 一次): + * 同名同返回类型 → 原地命中、不新增槽位(否则重复登记把表撑爆); + * 同名不同返回类型 → 报错。既不能静默丢弃(查找返回先登记值:先翻译 + 模块已按旧值生成间接调用,后登记模块却按新值,同一名字两种 ABI), + 也不能静默覆盖(先翻译模块的调用点已按旧值生成)——两者都是无声误编译; + * 满表 → 报错(原实现静默丢弃)。 + """ if name is None or ret_ty is None: return 1 if self._ensure_funcptr_sigs() == 0: + self._func_ptr_sig_fatal(pool, node, name, ret_ty, + "签名表分配失败(PermPool 内存不足)") return 1 - if self.FuncptrCount >= MAX_FUNC_PTR_SIGS: + # 幂等/冲突检查:线性扫已登记裸名(FuncptrCount 上限 64,代价可忽略) + i: sizet + for i in range(self.FuncptrCount): + idx_hit: sizet = sizet(i) * 64 + if string.strcmp(self.FuncptrNames + idx_hit, name) == 0: + prev_ret: rawptr[llvmlite.LLVMType] = ( + rawptr[llvmlite.LLVMType])(self.FuncptrRets[i]) + if prev_ret is not None \ + and self.Gen.AssignGen._stg_ty_equal(pool, prev_ret, ret_ty) != 0: + return 0 + self._func_ptr_sig_fatal(pool, node, name, ret_ty, + "该名字已按另一种返回类型登记过") + return 1 + if self.FuncptrCount >= CallGenBase.MAX_FUNC_PTR_SIGS: + self._func_ptr_sig_fatal(pool, node, name, ret_ty, "签名表已满") return 1 idx: sizet = sizet(self.FuncptrCount) * 64 # 槽位定长 64 字节,与 register_closure_name 一致用 strncpy 钳制防越界 @@ -743,7 +804,7 @@ class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): mod: rawptr[llvmlite.LLVMModule], fp_alloca: rawptr[llvmlite.Value], func_name: str, - cargs: rawptr[list[rawptr[ast.AST]]], + cargs: list[ast.AST*]*, can: sizet, trans: rawptr[HT.Translator]) -> rawptr[llvmlite.Value]: """函数指针变量间接调用,返回结果 Value""" @@ -819,7 +880,7 @@ class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): cl: rawptr[ast.Call], trans: rawptr[HT.Translator] = None) -> None: """翻译 printf 调用(特殊处理格式字符串)""" - cargs: rawptr[list[rawptr[ast.AST]]] = cl.args + cargs: list[ast.AST*]* = cl.args if cargs is None: return can: sizet = len(cargs) @@ -880,7 +941,7 @@ class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): cl: rawptr[ast.Call], trans: rawptr[HT.Translator] = None) -> None: """翻译 print 调用(类型感知格式化 + 默认换行)""" - cargs: rawptr[list[rawptr[ast.AST]]] = cl.args + cargs: list[ast.AST*]* = cl.args if cargs is None: return @@ -1119,7 +1180,7 @@ class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): """初始化全局函数表(用 stdlib.malloc 分配,生命周期=整个程序)""" if self.GlobalFuncs is not None: return 1 - size: sizet = GLOBAL_FUNC_MAX * GenTypes.FuncEntry.__sizeof__() + size: sizet = CallGenBase.GLOBAL_FUNC_MAX * GenTypes.FuncEntry.__sizeof__() self.GlobalFuncs = CompilerFactory.factory.PermPool.alloc(size) if self.GlobalFuncs is None: return 0 @@ -1154,7 +1215,7 @@ class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): # 已存在则跳过(避免重复) if self.find_func_in_table(self.GlobalFuncs, self.GlobalFuncCount, name) is not None: return 0 - if self.GlobalFuncCount >= GLOBAL_FUNC_MAX: + if self.GlobalFuncCount >= CallGenBase.GLOBAL_FUNC_MAX: return 1 # 直接写入全局表(不调用 add_func_to_table,避免递归) entry_size: sizet = GenTypes.FuncEntry.__sizeof__() @@ -1281,7 +1342,7 @@ class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): """初始化全局默认参数表""" if self.GlobalDefaults is not None: return 1 - size: sizet = GLOBAL_DEFAULTS_MAX * GenTypes.FuncDefaultsEntry.__sizeof__() + size: sizet = CallGenBase.GLOBAL_DEFAULTS_MAX * GenTypes.FuncDefaultsEntry.__sizeof__() self.GlobalDefaults = CompilerFactory.factory.PermPool.alloc(size) if self.GlobalDefaults is None: return 0 @@ -1321,7 +1382,7 @@ class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): # 同模块同名已存在则跳过 if self.find_func_defaults(name, cur_sha) is not None: return 0 - if self.GlobalDefaultsCount >= GLOBAL_DEFAULTS_MAX: + if self.GlobalDefaultsCount >= CallGenBase.GLOBAL_DEFAULTS_MAX: return 1 entry_size: sizet = GenTypes.FuncDefaultsEntry.__sizeof__() entry_addr: uint64 = uint64(self.GlobalDefaults) + self.GlobalDefaultsCount * entry_size @@ -1359,7 +1420,7 @@ class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): string.memset(kinds, 0, default_count * 4) string.memset(ints, 0, default_count * 8) string.memset(strs, 0, default_count * 8) - dl: rawptr[list[rawptr[ast.AST]]] = rawptr[list[rawptr[ast.AST]]](uint64(defaults)) + dl: list[ast.AST*]* = (list[ast.AST*] | t.CPtr)(uint64(defaults)) di: int for di in range(default_count): dnode: rawptr[ast.AST] = dl.get(di) diff --git a/App/lib/core/Gen/Call/CallGenLib.vp b/App/lib/core/Gen/Call/CallGenLib.vp index 5b1ab56..dc4d207 100644 --- a/App/lib/core/Gen/Call/CallGenLib.vp +++ b/App/lib/core/Gen/Call/CallGenLib.vp @@ -193,7 +193,7 @@ class CallGenLib(CallGenBase.CallGenBase): call_name: str, head: rawptr[llvmlite.Value], count: int, - cargs: rawptr[list[rawptr[ast.AST]]] = None, + cargs: list[ast.AST*]* = None, self_off: int = 0, trans: rawptr[HT.Translator] = None) -> rawptr[llvmlite.Value]: """把跨模块调用实参 coerce 到 vpi 定义侧形参类型,返回重建的实参链 @@ -738,9 +738,9 @@ class CallGenLib(CallGenBase.CallGenBase): mod: rawptr[llvmlite.LLVMModule], func_name: str, found_func: rawptr[llvmlite.Function], - cargs: rawptr[list[rawptr[ast.AST]]], + cargs: list[ast.AST*]*, can: sizet, - keywords: rawptr[list[rawptr[ast.AST]]], + keywords: list[ast.AST*]*, kw_count: sizet, trans: rawptr[HT.Translator]) -> rawptr[llvmlite.Value]: """带关键字参数的函数调用,按参数顺序构建调用链表 diff --git a/App/lib/core/Gen/Call/CallGenMixin.vp b/App/lib/core/Gen/Call/CallGenMixin.vp index 8618da3..44f447a 100644 --- a/App/lib/core/Gen/Call/CallGenMixin.vp +++ b/App/lib/core/Gen/Call/CallGenMixin.vp @@ -31,10 +31,12 @@ import lib.core.Gen.GenApi as GenApi # ============================================================ -# 常量(与 HandlesExprCall 保持一致,本地副本避免跨模块 CDefine 查找 bug) +# 常量(与 TPC 侧 HandlesExprCall 保持一致) +# +# GLOBAL_FUNC_MAX / GLOBAL_DEFAULTS_MAX / MAX_CLOSURE_NAMES / +# MAX_FUNC_PTR_SIGS / ISINSTANCE_CLASS_MAX:唯一来源在 CallGenBase.vp +# (本文件不使用,故不留副本;消费方 CallGenLeafKit.vp 经 CallGenBase. 引用)。 # ============================================================ -GLOBAL_FUNC_MAX: t.CDefine = 8192 -GLOBAL_DEFAULTS_MAX: t.CDefine = 4096 _CEXPORT_MAX: t.CDefine = 256 ASM_MAX_OPERANDS: t.CDefine = 16 ASM_TEXT_BUF_SIZE: t.CDefine = 2048 @@ -43,12 +45,6 @@ ASM_ARGS_BUF_SIZE: t.CDefine = 512 ASM_LINE_BUF_SIZE: t.CDefine = 4096 LLVMIR_MAX_OPS: t.CDefine = 16 LLVMIR_TEMPLATE_BUF: t.CDefine = 512 -# 闭包名表大小(原 HandlesExprCall 模块级常量) -MAX_CLOSURE_NAMES: t.CDefine = 128 -# 函数指针签名表大小(原 HandlesExprCall 模块级常量) -MAX_FUNC_PTR_SIGS: t.CDefine = 64 -# isinstance 子类收集上限(防数组越界;t.CDefine 注解由 TPC 解析为 i32) -ISINSTANCE_CLASS_MAX: t.CDefine = 64 # ============================================================ @@ -75,7 +71,7 @@ class CallGenMixin(CallGenBase.CallGenBase): func_name: str = self.Gen.ExprGenBase.get_func_name(cl.func) - cargs: rawptr[list[rawptr[ast.AST]]] = cl.args + cargs: list[ast.AST*]* = cl.args can: sizet = 0 if cargs is not None: can = len(cargs) @@ -159,7 +155,7 @@ class CallGenMixin(CallGenBase.CallGenBase): binop_ty: rawptr[llvmlite.LLVMType] = self.Gen.TypeGenResolve.resolve_annotation_type( pool, cl.func, trans._imported_modules, trans._from_imports, trans, 0) if binop_ty is not None: - binop_args: rawptr[list[rawptr[ast.AST]]] = cl.args + binop_args: list[ast.AST*]* = cl.args if binop_args is not None and len(binop_args) == 1: binop_arg_val: rawptr[llvmlite.Value] = self.Gen.ExprGen.translate_value( builder, pool, mod, binop_args.get(0), funcs_ptr, func_count, trans) @@ -223,7 +219,7 @@ class CallGenMixin(CallGenBase.CallGenBase): return None # 获取关键字参数 - keywords: rawptr[list[rawptr[ast.AST]]] = cl.keywords + keywords: list[ast.AST*]* = cl.keywords kw_count: sizet = 0 if keywords is not None: kw_count = len(keywords) @@ -931,6 +927,17 @@ class CallGenMixin(CallGenBase.CallGenBase): if trans is not None and trans._from_imports is not None: if self._lookup_mod_sha1(pool, trans._from_imports, func_name, 0) is not None: bare_resolvable = 1 + # M3 会话函数(跨片段):名字命中会话函数表 ⇒ 同样按"可解析"占位, + # 不 fail-fast。返回类型由下方 found_func==None 分支的 + # repl_try_session_func_call 按表内签名(宿主从定义片段 IR 解析)构造, + # 与 _infer_external_func_ret_ty 的白名单无关;此处的 call_ret_ty 只是 + # 占位(命中后立即 return,不会用到)。 + # 守卫 trans.ReplCtx:非 REPL 恒 None ⇒ 不经过;且限定裸名(Name)—— + # 会话函数只以裸名导出,模块限定调用(mod.f)不走本表。 + if trans is not None and trans.ReplCtx is not None \ + and cl.func is not None and cl.func.kind() == ast.ASTKind.Name \ + and self.Gen.VarGen.repl_find_func_entry(trans.ReplCtx, func_name) is not None: + bare_resolvable = 1 call_ret_ty = self.Gen.CallGenLib._infer_external_func_ret_ty( pool, func_name, node, 1 - bare_resolvable) @@ -991,6 +998,31 @@ class CallGenMixin(CallGenBase.CallGenBase): # 其他 i8* 变量按闭包调用路径处理 return self.Gen.CallGenLeafKit.translate_closure_call(pool, builder, mod, fp_alloca, cargs, can, trans) + # ---- M3:会话函数表命中 → ctx 函数指针槽取址 + 间接调用 ---- + # 位置(为什么在这里):本步位于 `if found_func is None:` 块**末尾**—— + # ① found_func 为 None ⇒ 本片段的 LLVM 模块里没有该函数的定义/前向声明 + # (会话函数定义在**先前片段**的 DLL 里,代码不在本模块内); + # ② 放在闭包名/局部函数指针变量分支**之后** ⇒ 局部名与闭包正确地**遮蔽** + # 会话函数(Python 的局部优先),只有"既非本模块函数/声明、又非局部/ + # 闭包/函数指针变量"的裸名才查会话函数表; + # ③ 仍先于 from_imports 解析与"裸名调用未定义且未导入"的 fail-fast。 + # 命中后从 ctx 的函数指针槽 load 地址、按表内签名间接调用:函数代码只在 + # 定义片段里一份(不复制/不重编译),调用点只持指针。 + sfn_box: t.CPtr = None + if trans is not None and trans.ReplCtx is not None \ + and (cl.func is None or cl.func.kind() == ast.ASTKind.Name): + sfn_box = stdlib.malloc(4) + if sfn_box is not None: + sfn_box[0] = 0 + sfn_val: rawptr[llvmlite.Value] = self.Gen.VarGen.repl_try_session_func_call( + trans, pool, builder, mod, func_name, cargs, int(can), + int(kw_count), node, sfn_box) + sfn_handled: int = int(sfn_box[0]) + stdlib.free(sfn_box) + if sfn_handled != 0: + # 命中(含命中但按 M3 规则明确报错的情形):不再走任何 + # "未定义/未导入"路径。void 会话函数返回 None 属预期。 + return sfn_val # 翻译参数并链接 # 有关键字参数且找到函数定义时,按参数名匹配 @@ -1139,7 +1171,26 @@ class CallGenMixin(CallGenBase.CallGenBase): # 否则下方 _build_const_from_snapshot 读到未初始化栈垃圾指针崩溃。 # 与 _translate_method_call 的 mdf_gd 无条件查询(L2677)保持一致。 gd_entry: rawptr[GenTypes.FuncDefaultsEntry] = self.Gen.CallGenLeafKit.find_func_defaults(func_name, dfx_callee_sha) - if df_defaults is None and gd_entry is not None: + # 包重导出补查(如 json.write 实际定义在 includes/json/__writer.vp): + # AST 侧模块名 'json' 只能解析到包 __init__ 的 SHA1,而默认参数表按 + # **定义该函数的模块 SHA1** 登记(_register_global_defaults),于是直接 + # 查不到 → 缺参填充整体跳过 → call 实参数 < declare/define 形参数 → + # 被调函数读寄存器/栈残留垃圾(VET `json.write(pool, val, False)` 的 + # 缺省 cap 读成指针量级 → pool.alloc(巨量) → [OOM] 根因)。 + # 与下方 L1473-1480 调用名重定向同一套 re-export 解析:命中后用 + # **定义模块 SHA1** 复查默认参数表(SHA1 已校验,不会跨模块串味)。 + if gd_entry is None and dfx_callee_sha is not None: + rr_src_dx: str = self.Gen.ImportsGen.lookup_reexport(dfx_callee_sha, func_name) + if rr_src_dx is None: + rr_src_dx = self._lookup_reexport_from_vpi(pool, dfx_callee_sha, func_name) + if rr_src_dx is not None: + rr_sha_dx: str = self._lookup_module_sha1(rr_src_dx) + if rr_sha_dx is not None: + gd_entry = self.Gen.CallGenLeafKit.find_func_defaults(func_name, rr_sha_dx) + # 覆盖条件含 df_default_count<=0:调用方函数表可能命中一条同名条目但 + # Defaults 为空/默认值为 0 条(VpiReader 注入的 declare 条目), + # 此时必须用全局默认参数表的真实条目覆盖,否则缺参填充整体不触发。 + if gd_entry is not None and (df_defaults is None or df_default_count <= 0): df_defaults = gd_entry.Defaults df_default_count = gd_entry.DefaultCount df_param_count = gd_entry.ParamCount @@ -1162,7 +1213,7 @@ class CallGenMixin(CallGenBase.CallGenBase): df_default_count = 0 df_param_count = 0 # 填充缺失参数 - if df_defaults is not None and df_default_count > 0 and can < df_param_count: + if df_default_count > 0 and can < df_param_count and (df_defaults is not None or gd_entry is not None): df_offset: int = df_param_count - df_default_count for df_di in range(df_default_count): df_param_idx: int = df_offset + df_di @@ -1172,8 +1223,8 @@ class CallGenMixin(CallGenBase.CallGenBase): if gd_entry is not None: df_arg_val = self.Gen.CallGenLeafKit._build_const_from_snapshot( pool, mod, gd_entry, df_di, trans, builder) - if df_arg_val is None: - df_defaults_list: rawptr[list[rawptr[ast.AST]]] = rawptr[list[rawptr[ast.AST]]](uint64(df_defaults)) + if df_arg_val is None and df_defaults is not None: + df_defaults_list: list[ast.AST*]* = (list[ast.AST*] | t.CPtr)(uint64(df_defaults)) df_default_node: rawptr[ast.AST] = df_defaults_list.get(df_di) if df_default_node is not None: df_arg_val = self.Gen.ExprGen.translate_value( @@ -1185,6 +1236,12 @@ class CallGenMixin(CallGenBase.CallGenBase): else: tail.Next = df_arg_val tail = df_arg_val + # 追加的默认实参必须计入实参数:下方 + # _coerce_args_to_def_params/_ensure_method_declare 等 + # 均以 can 为界重建/校验实参链,count 不增会把补齐的 + # 尾随默认实参整体截断丢弃(VET json.write 缺省 cap + # 丢失 → 被调方读寄存器垃圾 → pool.alloc 巨量 OOM 根因)。 + can += 1 elif found_func is not None: # 默认参数缺失/表信息不全时的零值兜底:函数表有参数数但无默认值记录、或调用方先于定义文件编译导致查不到默认值时,用 found_func 实际参数数(stub declare,与 define 一致)兜底并缺参填零值(对齐 TPC _fill_default_args 语义)。注意排除 EH 输出参数(%__eh_msg_out__/%__eh_code_out__),否则会与 _emit_eh_guarded_call 追加的真实 EH 实参重复导致 ABI 错位 fc_cnt: int = 0 @@ -1213,6 +1270,7 @@ class CallGenMixin(CallGenBase.CallGenBase): else: tail.Next = zero_val_z tail = zero_val_z + can += 1 # 零值兜底实参同样计入实参数(同默认实参) fp_z = llvmlite.param_get_next(fp_z) fpi_z += 1 diff --git a/App/lib/core/Gen/Call/CallGenProp.vp b/App/lib/core/Gen/Call/CallGenProp.vp index 509cf01..5ea8264 100644 --- a/App/lib/core/Gen/Call/CallGenProp.vp +++ b/App/lib/core/Gen/Call/CallGenProp.vp @@ -353,7 +353,7 @@ class CallGenProp(CallGenBase.CallGenBase): total_count = 1 # 翻译调用参数 - cargs: rawptr[list[rawptr[ast.AST]]] = cl.args + cargs: list[ast.AST*]* = cl.args can: sizet = 0 if cargs is not None: can = len(cargs) diff --git a/App/lib/core/Gen/Call/CallGenVa.vp b/App/lib/core/Gen/Call/CallGenVa.vp index 4650a30..640786d 100644 --- a/App/lib/core/Gen/Call/CallGenVa.vp +++ b/App/lib/core/Gen/Call/CallGenVa.vp @@ -78,7 +78,7 @@ class CallGenVa(CallGenBase.CallGenBase): builder: rawptr[llvmlite.IRBuilder], mod: rawptr[llvmlite.LLVMModule], cl: rawptr[ast.Call], - cargs: rawptr[list[rawptr[ast.AST]]], + cargs: list[ast.AST*]*, can: sizet, funcs_ptr: t.CPtr, func_count: int, @@ -126,7 +126,7 @@ class CallGenVa(CallGenBase.CallGenBase): builder: rawptr[llvmlite.IRBuilder], mod: rawptr[llvmlite.LLVMModule], cl: rawptr[ast.Call], - cargs: rawptr[list[rawptr[ast.AST]]], + cargs: list[ast.AST*]*, can: sizet, funcs_ptr: t.CPtr, func_count: int, @@ -161,7 +161,7 @@ class CallGenVa(CallGenBase.CallGenBase): def _translate_va_start_sugar(self, pool: rawptr[memhub.MemBuddy], builder: rawptr[llvmlite.IRBuilder], mod: rawptr[llvmlite.LLVMModule], - cargs: rawptr[list[rawptr[ast.AST]]], + cargs: list[ast.AST*]*, can: sizet, funcs_ptr: t.CPtr, func_count: int, @@ -193,7 +193,7 @@ class CallGenVa(CallGenBase.CallGenBase): def _translate_va_end_sugar(self, pool: rawptr[memhub.MemBuddy], builder: rawptr[llvmlite.IRBuilder], mod: rawptr[llvmlite.LLVMModule], - cargs: rawptr[list[rawptr[ast.AST]]], + cargs: list[ast.AST*]*, can: sizet, funcs_ptr: t.CPtr, func_count: int, @@ -282,7 +282,7 @@ class CallGenVa(CallGenBase.CallGenBase): call_name: str, head: rawptr[llvmlite.Value], can: sizet, - arg_nodes: rawptr[list[rawptr[ast.AST]]] = None, + arg_nodes: list[ast.AST*]* = None, self_offset: int = 0, trans: rawptr[HT.Translator] = None) -> int: """检查被调用函数是否为变长函数,若是则对变长参数进行 C ABI 提升 diff --git a/App/lib/core/Gen/Class/ClassGenBase.vp b/App/lib/core/Gen/Class/ClassGenBase.vp index a780e92..3ac74ed 100644 --- a/App/lib/core/Gen/Class/ClassGenBase.vp +++ b/App/lib/core/Gen/Class/ClassGenBase.vp @@ -192,7 +192,7 @@ class ClassGenBase: return 1 if cd.bases is None: return 0 - bases: rawptr[list[rawptr[ast.AST]]] = cd.bases + bases: list[ast.AST*]* = cd.bases bn: sizet = len(bases) if bn == 0: return 0 @@ -245,7 +245,7 @@ class ClassGenBase: return 1 if cd.bases is None: return 0 - bases: rawptr[list[rawptr[ast.AST]]] = cd.bases + bases: list[ast.AST*]* = cd.bases bn: sizet = len(bases) if bn == 0: return 0 @@ -298,7 +298,7 @@ class ClassGenBase: return 1 if cd.bases is None: return 0 - bases: rawptr[list[rawptr[ast.AST]]] = cd.bases + bases: list[ast.AST*]* = cd.bases bn: sizet = len(bases) if bn == 0: return 0 @@ -309,7 +309,7 @@ class ClassGenBase: if self._is_crenum_base(base_node, trans) == 1: return 1 return 0 - def _has_decorator(self, decorator_list: rawptr[list[rawptr[ast.AST]]], + def _has_decorator(self, decorator_list: list[ast.AST*]*, deco_name: str) -> int: """检查 decorator_list 中是否有指定装饰器名""" if decorator_list is None: @@ -356,7 +356,7 @@ class ClassGenBase: return 0 return 1 if string.strcmp(at2.attr, attr_name) == 0 else 0 return 0 - def _class_has_tattr(self, decorator_list: rawptr[list[rawptr[ast.AST]]], + def _class_has_tattr(self, decorator_list: list[ast.AST*]*, attr_name: str) -> int: """检查装饰器列表是否含 t.attr.(@c.Attribute(t.attr.xxx) 或 @t.attr.xxx)。 @@ -413,7 +413,7 @@ class ClassGenBase: return 0 return int(cn.int_val) return 0 - def _class_aligned_value(self, decorator_list: rawptr[list[rawptr[ast.AST]]]) -> int: + def _class_aligned_value(self, decorator_list: list[ast.AST*]*) -> int: """取 @c.Attribute(t.attr.aligned(N)) 的对齐字节数,无 aligned 返回 0""" if decorator_list is None: return 0 @@ -445,7 +445,7 @@ class ClassGenBase: if av2 > 0: return av2 return 0 - def _extract_func_meta(self, decorator_list: rawptr[list[rawptr[ast.AST]]]) -> int: + def _extract_func_meta(self, decorator_list: list[ast.AST*]*) -> int: """从装饰器列表提取元数据位掩码,返回 0=无装饰器元数据""" meta: int = 0 if decorator_list is None: @@ -734,7 +734,7 @@ class ClassGenBase: """类定义是否是异常类:基类是 Exception 或已注册异常类(1=是)""" if cd is None or cd.bases is None: return 0 - bases_e: rawptr[list[rawptr[ast.AST]]] = cd.bases + bases_e: list[ast.AST*]* = cd.bases bn_e: sizet = len(bases_e) for bi_e in range(bn_e): base_e: rawptr[ast.AST] = bases_e.get(bi_e) @@ -758,7 +758,7 @@ class ClassGenBase: """返回异常类定义的真实父类名(第一个非 Exception 的已注册异常类),无则 None""" if cd is None or cd.bases is None: return None - bases_e2: rawptr[list[rawptr[ast.AST]]] = cd.bases + bases_e2: list[ast.AST*]* = cd.bases bn_e2: sizet = len(bases_e2) for bi_e2 in range(bn_e2): base_e2: rawptr[ast.AST] = bases_e2.get(bi_e2) @@ -795,7 +795,7 @@ class ClassGenBase: lst_rcm: rawptr[ast.List] = rawptr[ast.List](value_node) if lst_rcm.elts is None: return - elts_rcm: rawptr[list[rawptr[ast.AST]]] = lst_rcm.elts + elts_rcm: list[ast.AST*]* = lst_rcm.elts en_rcm: sizet = len(elts_rcm) for ei_rcm in range(en_rcm): el_rcm: rawptr[ast.AST] = elts_rcm.get(ei_rcm) @@ -960,7 +960,7 @@ class ClassGenBase: pool: rawptr[memhub.MemBuddy] = None if trans is not None: pool = trans.Pool - bases: rawptr[list[rawptr[ast.AST]]] = cd.bases + bases: list[ast.AST*]* = cd.bases bn: sizet = len(bases) if bn == 0: return None @@ -1012,7 +1012,7 @@ class ClassGenBase: """收集所有非 marker 父类名到 parents_buf,返回数量""" if pool is None or cd is None or cd.bases is None or parents_buf is None: return 0 - bases: rawptr[list[rawptr[ast.AST]]] = cd.bases + bases: list[ast.AST*]* = cd.bases bn: sizet = len(bases) count: int = 0 for bi in range(bn): @@ -1059,7 +1059,7 @@ class ClassGenBase: if trans is None or cd is None or cd.bases is None: return pool: rawptr[memhub.MemBuddy] = trans.Pool - bases: rawptr[list[rawptr[ast.AST]]] = cd.bases + bases: list[ast.AST*]* = cd.bases bn: sizet = len(bases) if bn == 0: return @@ -1099,7 +1099,7 @@ class ClassGenBase: """检查类中是否有任何函数标记了 @t.CVTable,返回 1=有 / 0=无""" if cd is None or cd.children is None: return 0 - children: rawptr[list[rawptr[ast.AST]]] = cd.children + children: list[ast.AST*]* = cd.children cn: sizet = len(children) for ci in range(cn): stmt: rawptr[ast.AST] = children.get(ci) diff --git a/App/lib/core/Gen/Class/ClassGenDef.vp b/App/lib/core/Gen/Class/ClassGenDef.vp index a7e59c9..40914b8 100644 --- a/App/lib/core/Gen/Class/ClassGenDef.vp +++ b/App/lib/core/Gen/Class/ClassGenDef.vp @@ -200,14 +200,24 @@ class ClassGenDef(ClassGenBase.ClassGenBase): field_types_buf: sizet | t.CPtr = pool.alloc(8 * GenTypes.FIELD_MAX) field_defaults_buf: sizet | t.CPtr = pool.alloc(8 * GenTypes.FIELD_MAX) field_annot_buf: sizet | t.CPtr = pool.alloc(8 * GenTypes.FIELD_MAX) + # 字段 volatile 标记缓冲(1=字段注解联合含 t.CVolatile);与 field_annot_buf + # 并行,取值 0/1(sizet 数组复用,避免另设类型)。 + field_volatile_buf: sizet | t.CPtr = pool.alloc(8 * GenTypes.FIELD_MAX) + # 字段「所指物 volatile」标记缓冲(1=字段注解为 rawptr[t.CVolatile | T], + # 等价 C 的 volatile T*);与 field_volatile_buf 并行,取值 0/1。 + field_pointee_volatile_buf: sizet | t.CPtr = pool.alloc(8 * GenTypes.FIELD_MAX) if field_names_buf is None or field_types_buf is None or field_defaults_buf is None: return 0 - if field_annot_buf is None: + if field_annot_buf is None or field_volatile_buf is None: + return 0 + if field_pointee_volatile_buf is None: return 0 string.memset(field_names_buf, 0, 8 * GenTypes.FIELD_MAX) string.memset(field_types_buf, 0, 8 * GenTypes.FIELD_MAX) string.memset(field_defaults_buf, 0, 8 * GenTypes.FIELD_MAX) string.memset(field_annot_buf, 0, 8 * GenTypes.FIELD_MAX) + string.memset(field_volatile_buf, 0, 8 * GenTypes.FIELD_MAX) + string.memset(field_pointee_volatile_buf, 0, 8 * GenTypes.FIELD_MAX) field_count: int = 0 @@ -273,9 +283,13 @@ class ClassGenDef(ClassGenBase.ClassGenBase): field_annot_buf[field_count] = stored_val else: field_annot_buf[field_count] = 0 + # 继承父类字段的 volatile 标记(字段展平后语义不变) + field_volatile_buf[field_count] = sizet(pfe.IsVolatile) + # 继承父类字段的「所指物 volatile」标记(展平后语义不变) + field_pointee_volatile_buf[field_count] = sizet(pfe.PointeeVolatile) field_count += 1 - children: rawptr[list[rawptr[ast.AST]]] = cd.children + children: list[ast.AST*]* = cd.children if children is None: return 0 @@ -412,8 +426,25 @@ class ClassGenDef(ClassGenBase.ClassGenBase): field_annot_buf[field_count] = diag_cn_val else: field_annot_buf[field_count] = 0 + # t.CVolatile 存储类标记 → 该字段经对象访问的 load/store 带 + # volatile(如 Spinlock.locked: t.CVolatile | t.CInt)。 + if self.Gen.TypeGenErr.has_decorator_marker( + aa.annotation, "CVolatile", 0) != 0: + field_volatile_buf[field_count] = 1 + else: + field_volatile_buf[field_count] = 0 + # rawptr[t.CVolatile | T](volatile 指针指向物,等价 C 的 + # volatile T*)→ 经该字段解引用(self.ptr[i]/self.ptr.field) + # 的 load/store 带 volatile。 + if self.Gen.TypeGenErr.has_pointee_volatile_marker( + aa.annotation) != 0: + field_pointee_volatile_buf[field_count] = 1 + else: + field_pointee_volatile_buf[field_count] = 0 else: field_annot_buf[field_count] = 0 + field_volatile_buf[field_count] = 0 + field_pointee_volatile_buf[field_count] = 0 field_count += 1 if field_count == 0: @@ -590,7 +621,20 @@ class ClassGenDef(ClassGenBase.ClassGenBase): if fannot_addr != 0: fannot = str(fannot_addr) if fname is not None and fty2 is not None: - self.Gen.StructGen.add_field(pool, entry, fname, fty2, fdef, fannot) + fidx_add: int = self.Gen.StructGen.add_field(pool, entry, fname, fty2, fdef, fannot) + # t.CVolatile 字段标记(field_volatile_buf[fi] 与 add_field 顺序一一对应) + if fidx_add >= 0 and field_volatile_buf[fi] != 0: + fe_vol: rawptr[GenTypes.FieldEntry] = \ + self.Gen.StructGen._get_field_entry(entry, fidx_add) + if fe_vol is not None: + fe_vol.IsVolatile = 1 + # rawptr[t.CVolatile | T] 字段标记(field_pointee_volatile_buf[fi] 与 + # add_field 顺序一一对应):经该字段解引用时 load/store 带 volatile。 + if fidx_add >= 0 and field_pointee_volatile_buf[fi] != 0: + fe_pv: rawptr[GenTypes.FieldEntry] = \ + self.Gen.StructGen._get_field_entry(entry, fidx_add) + if fe_pv is not None: + fe_pv.PointeeVolatile = 1 # Phase 1a 声明模式:只注册 struct + 设置 OOP 标志,不翻译方法体 if trans._declare_only == 1: diff --git a/App/lib/core/Gen/Class/ClassGenEnum.vp b/App/lib/core/Gen/Class/ClassGenEnum.vp index 5bcaed5..7d07070 100644 --- a/App/lib/core/Gen/Class/ClassGenEnum.vp +++ b/App/lib/core/Gen/Class/ClassGenEnum.vp @@ -159,7 +159,7 @@ class ClassGenEnum(ClassGenBase.ClassGenBase): # 成员缺失(FR_OK 找不到)。register_enum 幂等返回已有 entry, # add_enum_member 成员名去重,重复处理安全合并。 - children: rawptr[list[rawptr[ast.AST]]] = cd.children + children: list[ast.AST*]* = cd.children if children is None: VLogger.warning(f"{class_name} has no body", "ENUM") return 0 @@ -226,7 +226,7 @@ class ClassGenEnum(ClassGenBase.ClassGenBase): ag: rawptr[ast.Assign] = rawptr[ast.Assign](stmt) if ag is None or ag.targets is None: continue - tgts: rawptr[list[rawptr[ast.AST]]] = ag.targets + tgts: list[ast.AST*]* = ag.targets tn: sizet = len(tgts) if tn == 0: continue @@ -412,7 +412,7 @@ class ClassGenEnum(ClassGenBase.ClassGenBase): # SHA1 已在 register_struct 时设置,无需再补 return 0 - children: rawptr[list[rawptr[ast.AST]]] = cd.children + children: list[ast.AST*]* = cd.children if children is None: VLogger.warning("{class_name} has no body", "UNION") return 0 @@ -646,7 +646,7 @@ class ClassGenEnum(ClassGenBase.ClassGenBase): existing.Ty = None existing.FieldCount = 0 - children: rawptr[list[rawptr[ast.AST]]] = cd.children + children: list[ast.AST*]* = cd.children if children is None: # [D32 fail-fast] 此前只 warning + return 0:变体表**静默不注册** → # _is_renum_class_name 恒 0、变体构造拿不到 tag(__types.vp L703 @@ -680,7 +680,7 @@ class ClassGenEnum(ClassGenBase.ClassGenBase): if variant_cd is None or variant_cd.children is None: variant_count += 1 continue - variant_children: rawptr[list[rawptr[ast.AST]]] = variant_cd.children + variant_children: list[ast.AST*]* = variant_cd.children vcn: sizet = len(variant_children) field_idx: int = 0 for vi in range(vcn): @@ -732,7 +732,7 @@ class ClassGenEnum(ClassGenBase.ClassGenBase): ag_v: rawptr[ast.Assign] = rawptr[ast.Assign](stmt) if ag_v is None or ag_v.targets is None: continue - tgts_v: rawptr[list[rawptr[ast.AST]]] = ag_v.targets + tgts_v: list[ast.AST*]* = ag_v.targets if len(tgts_v) == 0: continue t0_v: rawptr[ast.AST] = tgts_v.get(0) @@ -886,7 +886,7 @@ class ClassGenEnum(ClassGenBase.ClassGenBase): """ if trans is None or pool is None or class_name is None or cd is None: return - children: rawptr[list[rawptr[ast.AST]]] = cd.children + children: list[ast.AST*]* = cd.children if children is None: return cn: sizet = len(children) @@ -898,7 +898,7 @@ class ClassGenEnum(ClassGenBase.ClassGenBase): if variant_cd is None or variant_cd.name is None \ or variant_cd.children is None: continue - vchildren: rawptr[list[rawptr[ast.AST]]] = variant_cd.children + vchildren: list[ast.AST*]* = variant_cd.children vcn: sizet = len(vchildren) field_idx: int = 0 for vi in range(vcn): @@ -938,7 +938,7 @@ class ClassGenEnum(ClassGenBase.ClassGenBase): return if variant_count == 0: return - children: rawptr[list[rawptr[ast.AST]]] = cd.children + children: list[ast.AST*]* = cd.children if children is None: return @@ -1011,7 +1011,7 @@ class ClassGenEnum(ClassGenBase.ClassGenBase): ag_p: rawptr[ast.Assign] = rawptr[ast.Assign](stmt_p) if ag_p is None or ag_p.targets is None or ag_p.value is None: continue - tgts_p: rawptr[list[rawptr[ast.AST]]] = ag_p.targets + tgts_p: list[ast.AST*]* = ag_p.targets if len(tgts_p) == 0: continue t0_p: rawptr[ast.AST] = tgts_p.get(0) @@ -1054,7 +1054,7 @@ class ClassGenEnum(ClassGenBase.ClassGenBase): ag_rv: rawptr[ast.Assign] = rawptr[ast.Assign](stmt) if ag_rv is None or ag_rv.targets is None: continue - tgts_rv: rawptr[list[rawptr[ast.AST]]] = ag_rv.targets + tgts_rv: list[ast.AST*]* = ag_rv.targets if len(tgts_rv) == 0: continue t0_rv: rawptr[ast.AST] = tgts_rv.get(0) diff --git a/App/lib/core/Gen/Class/ClassGenGeneric.vp b/App/lib/core/Gen/Class/ClassGenGeneric.vp index 86c7104..5b32894 100644 --- a/App/lib/core/Gen/Class/ClassGenGeneric.vp +++ b/App/lib/core/Gen/Class/ClassGenGeneric.vp @@ -52,17 +52,61 @@ class ClassGenGeneric(ClassGenBase.ClassGenBase): if tp is None: return 0 return 1 + def _dup_perm_str(self, s: str) -> str: + """深拷贝字符串到永久池(无永久池/分配失败时原样返回入参)""" + if s is None: + return None + perm: rawptr[memhub.MemBuddy] = CompilerFactory.factory.PermPool + if perm is None: + return s + n: sizet = string.strlen(s) + b: str = perm.alloc(n + 1) + if b is None: + return s + string.strcpy(b, s) + return b def _register_generic_template(self, pool: rawptr[memhub.MemBuddy], cd: rawptr[ast.ClassDef], module_sha1: str) -> None: - """注册泛型类模板(类名 + ClassDef 节点 + 来源模块 SHA1)""" + """注册泛型类模板(类名 + ClassDef 节点 + 来源模块 SHA1) + + [池归属修复] 这四张表 + GenericClassTpParams 的语义都是**永久表**: + 批量编译下要在 Phase1 的 reset_to(mark1) 与 Phase2 的 reset_to(mark2) 之后 + 继续可读(读点见 _find_generic_template/_find_generic_template_sha1/ + _find_generic_template_file/_find_generic_template_tp_params,均有"遍历 + 悬空内存 strcmp 即崩溃"的记录),REPL 下还要跨片段存活。但注册点有三个: + - Phase1Scan.vp:322 → perm(永久池); + - Phase2/__init__.vp:321 → perm(永久池); + - ClassGenDef.vp:78 → trans.Pool(**会被 reset_to 回收的池**)。 + 于是"表在哪个池"取决于哪条路径先注册到表:批量编译里两遍预注册 + (_PreRegisterAllGenericTemplates / _PreRegisterAppGenericTemplates)在前, + 表被建在永久池 ⇒ 第三条路径只是追加;而两遍预注册都没建成表时(例如 + includes 的泛型 marker 文本预筛未命中),第三条路径把表建在 trans.Pool 上, + 随 mark 回收 ⇒ 后续 _find_generic_template 遍历悬空表(UAF)。REPL 从不跑 + Phase1/Phase2,第三条路径是**唯一**注册点 ⇒ 表必然落在会被每片段 reset 的 + 池上,M4 一旦在片段里定义泛型类就是必现 UAF。 + 故此处统一:表体一律用永久池(无永久池时退回入参池,即旧行为),并把 + 调用方池里的三个串(类名/SHA1/来源文件)深拷贝进永久池——它们同样会随 + 调用方池回收而悬空(挂名 strcmp 即读悬空内存)。 + 批量编译不变量:变更只影响"表与串分配在哪个池",写入的**内容**逐字不变 + (同样的类名/节点/ SHA1/文件路径,同样的插入顺序与查重),发射的 IR 与 + 符号名不受池选择影响 ⇒ 逐字不变。 + """ + if cd is None or cd.name is None: + return + perm: rawptr[memhub.MemBuddy] = CompilerFactory.factory.PermPool + table_pool: rawptr[memhub.MemBuddy] = perm + if table_pool is None: + table_pool = pool if self.GenericClassNames is None: - self.GenericClassNames = list[str](pool, 8) - self.GenericClassNodes = list[rawptr[ast.ClassDef]](pool, 8) - self.GenericClassSha1s = list[str](pool, 8) - self.GenericClassFiles = list[str](pool, 8) + self.GenericClassNames = list[str](table_pool, 8) + self.GenericClassNodes = list[rawptr[ast.ClassDef]](table_pool, 8) + self.GenericClassSha1s = list[str](table_pool, 8) + self.GenericClassFiles = list[str](table_pool, 8) if self.GenericClassTpParams is None: - self.GenericClassTpParams = CompilerFactory.factory.PermPool.alloc(GENERIC_TEMPLATE_MAX * 8) + if perm is None: + return + self.GenericClassTpParams = perm.alloc(GENERIC_TEMPLATE_MAX * 8) if self.GenericClassTpParams is None: return string.memset(self.GenericClassTpParams, 0, GENERIC_TEMPLATE_MAX * 8) @@ -76,11 +120,12 @@ class ClassGenGeneric(ClassGenBase.ClassGenBase): return # type_params stdlib 深拷贝:模板节点的 type_params 是 pool 分配的 tp_names_buf: str = self._copy_type_params_stdlib(cd.type_params) - self.GenericClassNames.append(cd.name) + self.GenericClassNames.append(self._dup_perm_str(cd.name)) self.GenericClassNodes.append(cd) - self.GenericClassSha1s.append(module_sha1) + self.GenericClassSha1s.append(self._dup_perm_str(module_sha1)) # 同时保存模板来源文件路径(错误定位用,见字段注释) - self.GenericClassFiles.append(self.Gen.TypeGenResolve.get_current_file()) + self.GenericClassFiles.append( + self._dup_perm_str(self.Gen.TypeGenResolve.get_current_file())) if tp_names_buf is not None and cn < GENERIC_TEMPLATE_MAX: self.GenericClassTpParams[cn] = sizet(tp_names_buf) def _copy_type_params_stdlib(self, tp: list[str] | t.CPtr) -> str: @@ -298,9 +343,9 @@ class ClassGenGeneric(ClassGenBase.ClassGenBase): # Tuple: (T, int) 等 if k == ast.ASTKind.Tuple: tup: rawptr[ast.Tuple] = rawptr[ast.Tuple](node) - new_elts: rawptr[list[rawptr[ast.AST]]] = list[rawptr[ast.AST]](pool, 8) + new_elts: list[ast.AST*]* = list[rawptr[ast.AST]](pool, 8) if tup.elts is not None: - tup_elts: rawptr[list[rawptr[ast.AST]]] = tup.elts + tup_elts: list[ast.AST*]* = tup.elts en: sizet = len(tup_elts) ei: sizet for ei in range(en): @@ -329,9 +374,9 @@ class ClassGenGeneric(ClassGenBase.ClassGenBase): return args old_args: rawptr[ast.Arguments] = rawptr[ast.Arguments](args) # 拷贝 args 列表,替换每个 Arg 的 annotation - new_arg_list: rawptr[list[rawptr[ast.AST]]] = list[rawptr[ast.AST]](pool, 8) + new_arg_list: list[ast.AST*]* = list[rawptr[ast.AST]](pool, 8) if old_args.args is not None: - old_arg_list: rawptr[list[rawptr[ast.AST]]] = old_args.args + old_arg_list: list[ast.AST*]* = old_args.args an: sizet = len(old_arg_list) ai: sizet for ai in range(an): @@ -364,7 +409,7 @@ class ClassGenGeneric(ClassGenBase.ClassGenBase): cd.decorator_list, None) # 拷贝 children if cd.children is not None: - cd_children: rawptr[list[rawptr[ast.AST]]] = cd.children + cd_children: list[ast.AST*]* = cd.children cn: sizet = len(cd_children) ci: sizet for ci in range(cn): @@ -389,7 +434,7 @@ class ClassGenGeneric(ClassGenBase.ClassGenBase): fd.decorator_list, new_returns2, 0) # 方法体直接引用原始节点 if fd.children is not None: - fd_children: rawptr[list[rawptr[ast.AST]]] = fd.children + fd_children: list[ast.AST*]* = fd.children bcn: sizet = len(fd_children) bci: sizet for bci in range(bcn): diff --git a/App/lib/core/Gen/Class/ClassGenVTable.vp b/App/lib/core/Gen/Class/ClassGenVTable.vp index 4d21cbe..ae9a602 100644 --- a/App/lib/core/Gen/Class/ClassGenVTable.vp +++ b/App/lib/core/Gen/Class/ClassGenVTable.vp @@ -87,7 +87,7 @@ class ClassGenVTable(ClassGenBase.ClassGenBase): pool: rawptr[memhub.MemBuddy] = trans.Pool mod: rawptr[llvmlite.LLVMModule] = trans.Module - children: rawptr[list[rawptr[ast.AST]]] = cd.children + children: list[ast.AST*]* = cd.children if children is None: return 0 cn: sizet = len(children) @@ -444,7 +444,7 @@ class ClassGenVTable(ClassGenBase.ClassGenBase): if trans is None or cd is None or struct_ty is None or class_name is None: return 0 - children: rawptr[list[rawptr[ast.AST]]] = cd.children + children: list[ast.AST*]* = cd.children if children is None: return 0 @@ -681,13 +681,13 @@ class ClassGenVTable(ClassGenBase.ClassGenBase): if md_args_node is not None: md_ags: rawptr[ast.Arguments] = rawptr[ast.Arguments](md_args_node) if md_ags.args is not None: - md_alist: rawptr[list[rawptr[ast.AST]]] = md_ags.args + md_alist: list[ast.AST*]* = md_ags.args if is_static_m != 0: md_param_count = len(md_alist) else: md_param_count = len(md_alist) - 1 if md_ags.defaults is not None: - md_dlist: rawptr[list[rawptr[ast.AST]]] = md_ags.defaults + md_dlist: list[ast.AST*]* = md_ags.defaults md_defaults = md_dlist md_default_count = len(md_dlist) @@ -718,7 +718,7 @@ class ClassGenVTable(ClassGenBase.ClassGenBase): if args_node is not None: ags: rawptr[ast.Arguments] = rawptr[ast.Arguments](args_node) if ags.args is not None: - alist: rawptr[list[rawptr[ast.AST]]] = ags.args + alist: list[ast.AST*]* = ags.args an: sizet = len(alist) start_idx: int = 0 if is_static_m == 0: @@ -868,7 +868,7 @@ class ClassGenVTable(ClassGenBase.ClassGenBase): if args_node is not None: ags2: rawptr[ast.Arguments] = rawptr[ast.Arguments](args_node) if ags2.args is not None: - alist2: rawptr[list[rawptr[ast.AST]]] = ags2.args + alist2: list[ast.AST*]* = ags2.args an2: sizet = len(alist2) start_idx2: int = 0 if is_static_m == 0: @@ -948,7 +948,7 @@ class ClassGenVTable(ClassGenBase.ClassGenBase): # 否则 `global BootInfo` 未注册时 prescan 把 BootInfo 当局部创建 alloca, # 后续翻译 is_global_name 为真又写全局 → 局部 alloca 永不写入 → 读全 0 # (main.vp __init__ BootInfo memmap 全 0 根因)。 - body: rawptr[list[rawptr[ast.AST]]] = fd.children + body: list[ast.AST*]* = fd.children if body is not None: bn: sizet = len(body) for bi in range(bn): diff --git a/App/lib/core/Gen/Expr/ExprGenAttr.vp b/App/lib/core/Gen/Expr/ExprGenAttr.vp index d3027fa..83b0cbb 100644 --- a/App/lib/core/Gen/Expr/ExprGenAttr.vp +++ b/App/lib/core/Gen/Expr/ExprGenAttr.vp @@ -801,6 +801,11 @@ class ExprGenAttr(ExprGenBase.ExprGenBase): # load 解引用获取 Ptr(Struct) obj_ptr = self._deref_if_ptr_ptr(builder, obj_ptr) + # rawptr[t.CVolatile | T] 基址(volatile 指针指向物):经该指针访问 + # 字段(p.field)的 load 带 volatile。仅当基址可解析到 VarEntry + # (Name)/ FieldEntry(Attribute)时为 1;解析失败为 0(文本不变)。 + vol_ba_rd: int = self.base_pointee_is_volatile(pool, trans, at.value) + # 查找结构体类型信息 # obj_ptr 类型应该是 Ptr(Struct(...)) match obj_ptr.Ty: @@ -839,7 +844,7 @@ class ExprGenAttr(ExprGenBase.ExprGenBase): llvmlite.build_bitcast(builder, alias_ptr, decl_ptr_rn) if cast_ptr_rn is not None: return llvmlite.build_load(builder, decl_ty_rn, cast_ptr_rn) - return llvmlite.build_load(builder, alias_fe.Ty, alias_ptr) + return llvmlite.build_load(builder, alias_fe.Ty, alias_ptr, vol_ba_rd) # property getter 回退: obj.attr 是 @property/@x.getter 方法时生成调用 # (字段未找到时先尝试,避免报误导性的"字段未找到"错误) if field_info is None and at.value is not None and trans is not None: @@ -904,13 +909,19 @@ class ExprGenAttr(ExprGenBase.ExprGenBase): pool, mod, annot_se.Ty) if field_info is not None: field_ty: rawptr[llvmlite.LLVMType] = field_info.Ty + # t.CVolatile 字段:经对象访问的 load 带 volatile 限定符 + field_vol_rd: int = self.Gen.StructGen.field_is_volatile(field_info) + # 合并"字段所指物 volatile"(基址是 rawptr[t.CVolatile|T] 时, + # 经其访问的字段 load 亦须 volatile;两者取或) + if vol_ba_rd != 0: + field_vol_rd = 1 # 联合体:bitcast obj_ptr 到 field_ty* 后 load if self.Gen.StructGen.is_union_by_type(struct_ty) == 1: field_ptr_ty: rawptr[llvmlite.LLVMType] = llvmlite.Ptr(pool, field_ty) casted: rawptr[llvmlite.Value] = llvmlite.build_bitcast( builder, obj_ptr, field_ptr_ty) if casted is not None: - return llvmlite.build_load(builder, field_ty, casted) + return llvmlite.build_load(builder, field_ty, casted, field_vol_rd) return None # 普通结构体:GEP + load field_idx: int = field_info.Index @@ -935,12 +946,12 @@ class ExprGenAttr(ExprGenBase.ExprGenBase): cast_ptr_rd: rawptr[llvmlite.Value] = llvmlite.build_bitcast( builder, field_ptr, decl_ptr_rd) if cast_ptr_rd is not None: - return llvmlite.build_load(builder, decl_ty_rd, cast_ptr_rd) + return llvmlite.build_load(builder, decl_ty_rd, cast_ptr_rd, field_vol_rd) # 数组类型字段不能 load 为 SSA value,直接返回字段指针 # 用于后续下标访问: self.state[0] → GEP state 字段 → GEP 数组元素 if self.is_array_type(field_ty) != 0: return field_ptr - loaded_val: rawptr[llvmlite.Value] = llvmlite.build_load(builder, field_ty, field_ptr) + loaded_val: rawptr[llvmlite.Value] = llvmlite.build_load(builder, field_ty, field_ptr, field_vol_rd) # 联合类型字段(如 Token | t.CPtr)被编译为 i8*, # 若 AnnotClassName 指示了具体结构体类型,bitcast 为正确的结构体指针。 # 注意:仅当字段类型本身是指针时才 bitcast(指针→指针); @@ -1063,6 +1074,131 @@ class ExprGenAttr(ExprGenBase.ExprGenBase): if fe is None: return None return self.Gen.StructGen.get_field_annot_class_name(fe) + + def attr_field_is_volatile(self, pool: rawptr[memhub.MemBuddy], + trans: rawptr[HT.Translator], + attr_node: rawptr[ast.AST]) -> int: + """obj.field 访问的字段是否 volatile(1=是 / 0=否) + + 仅解析一层属性:先定基座(Name 变量注解名 / 属性链字段注解名)得到父类, + 再按名查字段取 FieldEntry.IsVolatile。查不到(vtable 方法、影子字段、 + 依赖未就绪等)返回 0,不影响既有行为(写路径退回非 volatile store)。 + """ + if attr_node is None or trans is None: + return 0 + if attr_node.kind() != ast.ASTKind.Attribute: + return 0 + at: rawptr[ast.Attribute] = rawptr[ast.Attribute](attr_node) + if at is None or at.attr is None or at.value is None: + return 0 + parent_class: str = None + if at.value.kind() == ast.ASTKind.Name: + nm: rawptr[ast.Name] = rawptr[ast.Name](at.value) + if nm.id is None: + return 0 + ve: rawptr[GenTypes.VarEntry] = self.Gen.VarGen.lookup_var_entry( + trans.SymTab, nm.id) + if ve is None: + return 0 + parent_class = self.Gen.VarGen.var_annot_class_name(ve) + elif at.value.kind() == ast.ASTKind.Attribute: + parent_class = self._resolve_attr_field_class(pool, trans, at.value) + if parent_class is None: + return 0 + fe: rawptr[GenTypes.FieldEntry] = self.Gen.StructGen.lookup_field_by_class( + parent_class, at.attr, trans.ModuleSha1, at, trans._from_imports) + return self.Gen.StructGen.field_is_volatile(fe) + + # ============================================================ + # 指针指向物 volatile(rawptr[t.CVolatile | T],等价 C 的 volatile T*) + # 基址→VarEntry/FieldEntry 解析与消费点。仅在基址能解析到具体条目时生效, + # 其余(函数返回/复杂表达式等)返回 0(明确不支持,见限制清单)。 + # ============================================================ + def _resolve_attr_field_entry(self, pool: rawptr[memhub.MemBuddy], + trans: rawptr[HT.Translator], + attr_node: rawptr[ast.AST]) -> rawptr[GenTypes.FieldEntry]: + """obj.field 访问的 FieldEntry(**非 fatal 只读解析**,失败返回 None) + + 与 attr_field_is_volatile 的区别:后者走 lookup_field_by_class(模块限定名 + 分支会 fatal_error),仅用于写路径;本函数要能在读路径无条件调用,故 + 对齐 ExprGenOps._node_type_unsigned 的字段遍历——解析基座类名后 find_struct + 取裸结构体、自遍历字段,未命中一律返回 None,绝不 fatal,不改变既有报错行为。 + 仅支持单层基座(基座是 Name,如 self.ptr);基座是属性链/调用/下标等返回 None。 + """ + if attr_node is None or trans is None: + return None + if attr_node.kind() != ast.ASTKind.Attribute: + return None + at: rawptr[ast.Attribute] = rawptr[ast.Attribute](attr_node) + if at is None or at.attr is None or at.value is None: + return None + if at.value.kind() != ast.ASTKind.Name: + return None + nm: rawptr[ast.Name] = rawptr[ast.Name](at.value) + if nm.id is None: + return None + ve: rawptr[GenTypes.VarEntry] = self.Gen.VarGen.lookup_var_entry( + trans.SymTab, nm.id) + if ve is None: + return None + cls_nm: str = self.Gen.VarGen.var_annot_class_name(ve) + if cls_nm is None: + return None + # 取裸类名(点前部分):只做裸名结构体查找,避免模块限定名解析 fatal + bare: str = cls_nm + dot: str = self.Gen.StructGen._last_dot_outside_brackets(cls_nm) + if dot is not None: + bare = dot + 1 + if bare is None: + return None + st: rawptr[GenTypes.StructEntry] = self.Gen.StructGen.find_struct(bare) + if st is None: + return None + fi: int = 0 + while fi < st.FieldCount: + fe_u: rawptr[GenTypes.FieldEntry] = \ + self.Gen.StructGen._get_field_entry(st, fi) + if fe_u is not None and fe_u.Name is not None \ + and string.strcmp(fe_u.Name, at.attr) == 0: + return fe_u + fi += 1 + return None + + def attr_field_pointee_is_volatile(self, pool: rawptr[memhub.MemBuddy], + trans: rawptr[HT.Translator], + attr_node: rawptr[ast.AST]) -> int: + """obj.field 字段的**所指物**是否 volatile(1=是 / 0=否) + + 即该字段注解是否为 rawptr[t.CVolatile | T](等价 C 的 volatile T*)。 + 消费点:经该字段解引用(self.ptr[i] / self.ptr.field)的 load/store + 据此加 LLVM volatile 限定符。 + """ + fe: rawptr[GenTypes.FieldEntry] = self._resolve_attr_field_entry( + pool, trans, attr_node) + return self.Gen.StructGen.field_pointee_is_volatile(fe) + + def base_pointee_is_volatile(self, pool: rawptr[memhub.MemBuddy], + trans: rawptr[HT.Translator], + base_node: rawptr[ast.AST]) -> int: + """指针基址的所指物是否 volatile(rawptr[t.CVolatile|T] 变量/字段) + + 仅当基址可解析到具体 VarEntry(Name)/ FieldEntry(Attribute)时生效; + 基址是函数返回/下标/调用/更复杂表达式时返回 0(明确不支持)。 + 消费点:p[i] / p.field / c.Deref(p) 的 load/store 据此加 LLVM volatile。 + """ + if base_node is None or trans is None: + return 0 + if base_node.kind() == ast.ASTKind.Name: + nm: rawptr[ast.Name] = rawptr[ast.Name](base_node) + if nm.id is None: + return 0 + ve: rawptr[GenTypes.VarEntry] = self.Gen.VarGen.lookup_var_entry( + trans.SymTab, nm.id) + return self.Gen.VarGen.var_pointee_is_volatile(ve) + if base_node.kind() == ast.ASTKind.Attribute: + return self.attr_field_pointee_is_volatile(pool, trans, base_node) + return 0 + def get_attribute_ptr(self, builder: rawptr[llvmlite.IRBuilder], pool: rawptr[memhub.MemBuddy], mod: rawptr[llvmlite.LLVMModule], diff --git a/App/lib/core/Gen/Expr/ExprGenCls.vp b/App/lib/core/Gen/Expr/ExprGenCls.vp index 8736474..e8afff5 100644 --- a/App/lib/core/Gen/Expr/ExprGenCls.vp +++ b/App/lib/core/Gen/Expr/ExprGenCls.vp @@ -46,7 +46,7 @@ class ExprGenCls(ExprGenBase.ExprGenBase): # 符号依赖),未命中返回 null。幂等查重,多遍翻译安全。 # ============================================================ - def _extract_docstring_from_body(self, body: rawptr[list[rawptr[ast.AST]]]) -> str: + def _extract_docstring_from_body(self, body: list[ast.AST*]*) -> str: """从函数/类体提取 docstring:body[0] 为 Expr(Constant str) 时返回内容""" if body is None or len(body) == 0: return None @@ -111,7 +111,7 @@ class ExprGenCls(ExprGenBase.ExprGenBase): """扫描模块 AST:登记模块级函数/类/方法的 docstring(幂等)""" if tree is None or module_sha1 is None: return - ch: rawptr[list[rawptr[ast.AST]]] = tree.children + ch: list[ast.AST*]* = tree.children if ch is None: return cn: sizet = len(ch) @@ -123,7 +123,7 @@ class ExprGenCls(ExprGenBase.ExprGenBase): if kd == ast.ASTKind.FunctionDef: fd: rawptr[ast.FunctionDef] = rawptr[ast.FunctionDef](child) if fd is not None and fd.name is not None: - fd_body: rawptr[list[rawptr[ast.AST]]] = fd.children + fd_body: list[ast.AST*]* = fd.children fdoc: str = self._extract_docstring_from_body(fd_body) if fdoc is not None: self.register_docstring(module_sha1, fd.name, fdoc) @@ -131,12 +131,12 @@ class ExprGenCls(ExprGenBase.ExprGenBase): cd: rawptr[ast.ClassDef] = rawptr[ast.ClassDef](child) if cd is None or cd.name is None: continue - cd_body0: rawptr[list[rawptr[ast.AST]]] = cd.children + cd_body0: list[ast.AST*]* = cd.children cdoc: str = self._extract_docstring_from_body(cd_body0) if cdoc is not None: self.register_docstring(module_sha1, cd.name, cdoc) # 类方法 docstring(key="{ClassName}.{method}") - cd_body: rawptr[list[rawptr[ast.AST]]] = cd.children + cd_body: list[ast.AST*]* = cd.children if cd_body is not None: cbn: sizet = len(cd_body) for mi in range(cbn): diff --git a/App/lib/core/Gen/Expr/ExprGenConst.vp b/App/lib/core/Gen/Expr/ExprGenConst.vp index 917cd8f..12c9c99 100644 --- a/App/lib/core/Gen/Expr/ExprGenConst.vp +++ b/App/lib/core/Gen/Expr/ExprGenConst.vp @@ -262,7 +262,7 @@ class ExprGenConst(ExprGenBase.ExprGenBase): if list_node is None: return None - elts: rawptr[list[rawptr[ast.AST]]] = list_node.elts + elts: list[ast.AST*]* = list_node.elts elts_count: sizet = 0 if elts is not None: elts_count = len(elts) diff --git a/App/lib/core/Gen/Expr/ExprGenFstr.vp b/App/lib/core/Gen/Expr/ExprGenFstr.vp index 4e674a6..4515089 100644 --- a/App/lib/core/Gen/Expr/ExprGenFstr.vp +++ b/App/lib/core/Gen/Expr/ExprGenFstr.vp @@ -58,7 +58,7 @@ class ExprGenFstr(ExprGenBase.ExprGenBase): arg_tail: rawptr[llvmlite.Value] = None arg_count: int = 0 - parts: rawptr[list[rawptr[ast.AST]]] = js.values + parts: list[ast.AST*]* = js.values pn: sizet = len(parts) for pi in range(pn): part: rawptr[ast.AST] = parts.get(pi) diff --git a/App/lib/core/Gen/Expr/ExprGenName.vp b/App/lib/core/Gen/Expr/ExprGenName.vp index 218d002..29d5497 100644 --- a/App/lib/core/Gen/Expr/ExprGenName.vp +++ b/App/lib/core/Gen/Expr/ExprGenName.vp @@ -167,7 +167,13 @@ class ExprGenName(ExprGenBase.ExprGenBase): return mod_alloca case _: pass - return llvmlite.build_load(builder, load_ty, mod_alloca) + # t.CVolatile 全局:load 带 volatile 限定符(禁优化器消除重复读) + vol_g: int = 0 + if trans is not None: + ve_g: rawptr[GenTypes.VarEntry] = self.Gen.VarGen.lookup_var_entry( + trans.SymTab, nm_id) + vol_g = self.Gen.VarGen.var_is_volatile(ve_g) + return llvmlite.build_load(builder, load_ty, mod_alloca, vol_g) # nonlocal 变量:通过闭包 env 访问 if GenApi.is_nonlocal_name(trans, nm_id) != 0: @@ -206,7 +212,23 @@ class ExprGenName(ExprGenBase.ExprGenBase): # load [N x i8] 值传给 %s 会读到错误数据崩溃 # (ClassArrayTest TestCharArray 运行时崩溃根因)。 return alloca - return llvmlite.build_load(builder, load_ty, alloca) + # t.CVolatile 局部变量:load 带 volatile 限定符 + vol_l: int = 0 + if trans is not None: + ve_l: rawptr[GenTypes.VarEntry] = self.Gen.VarGen.lookup_var_entry( + trans.SymTab, nm_id) + vol_l = self.Gen.VarGen.var_is_volatile(ve_l) + return llvmlite.build_load(builder, load_ty, alloca, vol_l) + + # REPL 模式(M2)会话变量:局部表未命中的名字若是会话变量,从 ctx 槽按类型读回。 + # 位置在两处:① 局部查找之后(片段自己声明的局部名/参数优先,走原路径); + # ② 模块别名/模块函数引用之前(会话名不得被 printf 等本模块 declare 抢走)。 + # 非 REPL(trans.ReplCtx is None)时 repl_try_read 立即返回 None ⇒ 原路径逐字不变。 + if trans is not None and trans.ReplCtx is not None: + sess_val: rawptr[llvmlite.Value] = self.Gen.VarGen.repl_try_read( + trans, nm_id, node) + if sess_val is not None: + return sess_val # 模块别名检查:SymTab 查找失败后才检查 # 如果 nm_id 是已导入模块名(如 win32file, fileio)且未被声明为局部变量, @@ -756,6 +778,11 @@ class ExprGenName(ExprGenBase.ExprGenBase): if sub.value is not None and sub.value.kind() == ast.ASTKind.Name: nm: rawptr[ast.Name] = rawptr[ast.Name](sub.value) if nm.id is not None and trans is not None: + # rawptr[t.CVolatile | T] 变量(volatile 指针指向物):本分支内 + # 的**元素 load**(p[i] 解引用)带 volatile;仅读取指针值自身的 + # load(alloca → 指针值)不带(那是指针变量而非所指物)。 + vol_sub_rd: int = self.Gen.VarGen.var_pointee_is_volatile( + self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)) # nonlocal 数组/指针:从 env 读共享堆地址(提升函数内不能 # lookup_var 外层 alloca——SSA 编号跨函数错乱崩溃) # 读路径:元素指针 load 出元素值 @@ -850,7 +877,8 @@ class ExprGenName(ExprGenBase.ExprGenBase): elem_ptr_pp: rawptr[llvmlite.Value] = llvmlite.build_gep( builder, inner_ty, ptr_val_pp, idx_val) if elem_ptr_pp is not None: - return llvmlite.build_load(builder, inner_ty, elem_ptr_pp) + # volatile 指针指向物:解引用 load 带 volatile + return llvmlite.build_load(builder, inner_ty, elem_ptr_pp, vol_sub_rd) return None # 自定义结构体 (如 HashTable|t.CPtr, JsonValue|t.CPtr): # 仅当类定义了 __getitem__(当前模块定义或跨模块 stub declare 注入) @@ -880,7 +908,8 @@ class ExprGenName(ExprGenBase.ExprGenBase): elem_rd_g: rawptr[llvmlite.Value] = llvmlite.build_gep( builder, inner_ty, ptr_val_rd_g, idx_val) if elem_rd_g is not None: - return llvmlite.build_load(builder, inner_ty, elem_rd_g) + # volatile 指针指向物:解引用 load 带 volatile + return llvmlite.build_load(builder, inner_ty, elem_rd_g, vol_sub_rd) return None # bytes|t.CPtr / str|t.CPtr: alloca 是 i8**, # 直接 GEP 按 i8* 步长(8 字节),load i8* @@ -900,7 +929,8 @@ class ExprGenName(ExprGenBase.ExprGenBase): elem_ptr_ts: rawptr[llvmlite.Value] = llvmlite.build_gep( builder, pointee, ptr_val_pe_ts, idx_val) if elem_ptr_ts is not None: - return llvmlite.build_load(builder, pointee, elem_ptr_ts) + # volatile 指针指向物:解引用 load 带 volatile + return llvmlite.build_load(builder, pointee, elem_ptr_ts, vol_sub_rd) return None # 普通指针遍历: 先 load 指针值,再 GEP ptr_val: rawptr[llvmlite.Value] = llvmlite.build_load( @@ -909,7 +939,8 @@ class ExprGenName(ExprGenBase.ExprGenBase): elem_ptr2: rawptr[llvmlite.Value] = llvmlite.build_gep( builder, inner_ty, ptr_val, idx_val) if elem_ptr2 is not None: - return llvmlite.build_load(builder, inner_ty, elem_ptr2) + # volatile 指针指向物:解引用 load 带 volatile + return llvmlite.build_load(builder, inner_ty, elem_ptr2, vol_sub_rd) return None case _: # 联合类型变量(IsPtrElement=1,如 _list.vp 的 @@ -959,7 +990,8 @@ class ExprGenName(ExprGenBase.ExprGenBase): elem_sub_g: rawptr[llvmlite.Value] = llvmlite.build_gep( builder, pointee, ptr_val_sub_g, idx_val) if elem_sub_g is not None: - return llvmlite.build_load(builder, pointee, elem_sub_g) + # volatile 指针指向物:解引用 load 带 volatile + return llvmlite.build_load(builder, pointee, elem_sub_g, vol_sub_rd) return None pass @@ -971,6 +1003,11 @@ class ExprGenName(ExprGenBase.ExprGenBase): # 检查是否是指针类型 if self.is_ptr_type(ptr_val.Ty) != 0: + # 通用路径基址(如 self.ptr / 复杂表达式):仅当基址可解析到 + # VarEntry/FieldEntry 且其为 rawptr[t.CVolatile|T] 时,元素 load 带 volatile; + # 其余基址解析返回 0(明确不支持,见限制清单)。守卫为 0 时文本不变。 + vol_sub_gen: int = self.Gen.ExprGenAttr.base_pointee_is_volatile( + pool, trans, sub.value) elem_ty2: rawptr[llvmlite.LLVMType] = ptr_val.Ty.Pointee if elem_ty2 is not None: # 数组类型: 双索引 GEP + load 元素 @@ -980,7 +1017,7 @@ class ExprGenName(ExprGenBase.ExprGenBase): builder, elem_ty2, arr_elem_ty2, ptr_val, self._zext_index_to_i32(builder, idx_val)) if elem_ptr3 is not None: - return llvmlite.build_load(builder, arr_elem_ty2, elem_ptr3) + return llvmlite.build_load(builder, arr_elem_ty2, elem_ptr3, vol_sub_gen) return None case _: pass @@ -1014,7 +1051,7 @@ class ExprGenName(ExprGenBase.ExprGenBase): builder, cell_p_u64, i8pp_ty_u64) if cast_cell_u64 is not None: return llvmlite.build_load( - builder, i8p_ty_u64, cast_cell_u64) + builder, i8p_ty_u64, cast_cell_u64, vol_sub_gen) case _: pass # 自定义结构体 (如 self._ht[key]): 仅当类定义了 __getitem__ 才转发方法调用; @@ -1037,13 +1074,13 @@ class ExprGenName(ExprGenBase.ExprGenBase): elem_ptr_gen: rawptr[llvmlite.Value] = llvmlite.build_gep( builder, elem_ty2, ptr_val, idx_val) if elem_ptr_gen is not None: - return llvmlite.build_load(builder, elem_ty2, elem_ptr_gen) + return llvmlite.build_load(builder, elem_ty2, elem_ptr_gen, vol_sub_gen) return None # 指针类型: 单索引 GEP + load elem_ptr4: rawptr[llvmlite.Value] = llvmlite.build_gep( builder, elem_ty2, ptr_val, idx_val) if elem_ptr4 is not None: - return llvmlite.build_load(builder, elem_ty2, elem_ptr4) + return llvmlite.build_load(builder, elem_ty2, elem_ptr4, vol_sub_gen) return None # ============================================================ # 获取下标表达式的元素指针(不加载值,用于赋值) diff --git a/App/lib/core/Gen/Expr/ExprGenOps.vp b/App/lib/core/Gen/Expr/ExprGenOps.vp index e02e52e..2ff4eb7 100644 --- a/App/lib/core/Gen/Expr/ExprGenOps.vp +++ b/App/lib/core/Gen/Expr/ExprGenOps.vp @@ -479,7 +479,7 @@ class ExprGenOps(ExprGenBase.ExprGenBase): return None op: int = ops_list.get(0) - comparators: rawptr[list[rawptr[ast.AST]]] = cmp.comparators + comparators: list[ast.AST*]* = cmp.comparators if comparators is None or len(comparators) == 0: return None rhs_node: rawptr[ast.AST] = comparators.get(0) @@ -966,7 +966,7 @@ class ExprGenOps(ExprGenBase.ExprGenBase): return None op: int = bo.op - values: rawptr[list[rawptr[ast.AST]]] = bo.values + values: list[ast.AST*]* = bo.values if values is None: return None count: sizet = len(values) diff --git a/App/lib/core/Gen/Func/FuncGenBase.vp b/App/lib/core/Gen/Func/FuncGenBase.vp index ea78a41..8604f57 100644 --- a/App/lib/core/Gen/Func/FuncGenBase.vp +++ b/App/lib/core/Gen/Func/FuncGenBase.vp @@ -32,7 +32,7 @@ class FuncGenBase(CallGenBase.CallGenBase): # 返回 LLVM IR 属性字符串(如 "alwaysinline nounwind"),无属性返回 None # ============================================================ def extract_func_attrs(self, pool: rawptr[memhub.MemBuddy], - decorator_list: rawptr[list[rawptr[ast.AST]]], + decorator_list: list[ast.AST*]*, imported_modules: str) -> str: """从 decorator_list 提取 c.Attribute 属性,返回 LLVM IR 属性字符串""" if decorator_list is None: @@ -84,7 +84,7 @@ class FuncGenBase(CallGenBase.CallGenBase): # 遍历参数提取属性 if call_node.args is None: continue - args_list: rawptr[list[rawptr[ast.AST]]] = call_node.args + args_list: list[ast.AST*]* = call_node.args an: sizet = len(args_list) for ai in range(an): arg_node: rawptr[ast.AST] = args_list.get(ai) @@ -108,7 +108,7 @@ class FuncGenBase(CallGenBase.CallGenBase): return None return attrs_buf def extract_func_section(self, pool: rawptr[memhub.MemBuddy], - decorator_list: rawptr[list[rawptr[ast.AST]]], + decorator_list: list[ast.AST*]*, imported_modules: str) -> str: """提取 @c.Attribute(t.attr.section("xxx")) 为属性组文本 '"section"="xxx"'。 @@ -133,7 +133,7 @@ class FuncGenBase(CallGenBase.CallGenBase): continue if call_node.args is None: continue - args_list: rawptr[list[rawptr[ast.AST]]] = call_node.args + args_list: list[ast.AST*]* = call_node.args an: sizet = len(args_list) for ai in range(an): arg_node: rawptr[ast.AST] = args_list.get(ai) @@ -331,7 +331,7 @@ class FuncGenBase(CallGenBase.CallGenBase): """函数体顶层是否含 raise(非 StopIteration),1=是 / 0=否""" if fd is None or fd.children is None: return 0 - body_f: rawptr[list[rawptr[ast.AST]]] = fd.children + body_f: list[ast.AST*]* = fd.children bn_f: sizet = len(body_f) for bi_f in range(bn_f): stmt_f: rawptr[ast.AST] = body_f.get(bi_f) @@ -388,7 +388,7 @@ class FuncGenBase(CallGenBase.CallGenBase): deco_fa_cr: rawptr[ast.Attribute] = rawptr[ast.Attribute](deco_call_cr.func) if deco_fa_cr.attr is None or string.strcmp(deco_fa_cr.attr, "CReturn") != 0: continue - cr_args: rawptr[list[rawptr[ast.AST]]] = deco_call_cr.args + cr_args: list[ast.AST*]* = deco_call_cr.args if cr_args is None: continue cr_an: sizet = len(cr_args) @@ -427,7 +427,7 @@ class FuncGenBase(CallGenBase.CallGenBase): if args_node is not None: ags: rawptr[ast.Arguments] = rawptr[ast.Arguments](args_node) if ags.args is not None: - alist: rawptr[list[rawptr[ast.AST]]] = ags.args + alist: list[ast.AST*]* = ags.args an: sizet = len(alist) i32_ty: rawptr[llvmlite.LLVMType] = llvmlite.Int32(pool) for ai in range(an): diff --git a/App/lib/core/Gen/Func/FuncGenDef.vp b/App/lib/core/Gen/Func/FuncGenDef.vp index 6c4a225..77271ce 100644 --- a/App/lib/core/Gen/Func/FuncGenDef.vp +++ b/App/lib/core/Gen/Func/FuncGenDef.vp @@ -49,7 +49,7 @@ class FuncGenDef(CallGenBase.CallGenBase): i8_ty: rawptr[llvmlite.LLVMType] = llvmlite.Int8(pool) i8_ptr_ty: rawptr[llvmlite.LLVMType] = llvmlite.Ptr(pool, i8_ty) - ch: rawptr[list[rawptr[ast.AST]]] = tree.children + ch: list[ast.AST*]* = tree.children if ch is None: return 0 @@ -210,10 +210,10 @@ class FuncGenDef(CallGenBase.CallGenBase): if fd_args_node is not None: fd_ags: rawptr[ast.Arguments] = rawptr[ast.Arguments](fd_args_node) if fd_ags.args is not None: - fd_alist: rawptr[list[rawptr[ast.AST]]] = fd_ags.args + fd_alist: list[ast.AST*]* = fd_ags.args fd_param_count = len(fd_alist) if fd_ags.defaults is not None: - fd_dlist: rawptr[list[rawptr[ast.AST]]] = fd_ags.defaults + fd_dlist: list[ast.AST*]* = fd_ags.defaults fd_defaults = fd_dlist fd_default_count = len(fd_dlist) @@ -234,7 +234,7 @@ class FuncGenDef(CallGenBase.CallGenBase): if args_node is not None: ags: rawptr[ast.Arguments] = rawptr[ast.Arguments](args_node) if ags.args is not None: - alist: rawptr[list[rawptr[ast.AST]]] = ags.args + alist: list[ast.AST*]* = ags.args an: sizet = len(alist) for ai in range(an): arg: rawptr[ast.Arg] = rawptr[ast.Arg](alist.get(ai)) @@ -512,7 +512,7 @@ class FuncGenDef(CallGenBase.CallGenBase): if args_node_extern is not None: ags_e: rawptr[ast.Arguments] = rawptr[ast.Arguments](args_node_extern) if ags_e.args is not None: - alist_e: rawptr[list[rawptr[ast.AST]]] = ags_e.args + alist_e: list[ast.AST*]* = ags_e.args an_e: sizet = len(alist_e) for ai_e in range(an_e): arg_e: rawptr[ast.Arg] = rawptr[ast.Arg](alist_e.get(ai_e)) @@ -611,10 +611,10 @@ class FuncGenDef(CallGenBase.CallGenBase): if args_node is not None: tfd_ags: rawptr[ast.Arguments] = rawptr[ast.Arguments](args_node) if tfd_ags.args is not None: - tfd_alist: rawptr[list[rawptr[ast.AST]]] = tfd_ags.args + tfd_alist: list[ast.AST*]* = tfd_ags.args tfd_param_count = len(tfd_alist) if tfd_ags.defaults is not None: - tfd_dlist: rawptr[list[rawptr[ast.AST]]] = tfd_ags.defaults + tfd_dlist: list[ast.AST*]* = tfd_ags.defaults tfd_defaults = tfd_dlist tfd_default_count = len(tfd_dlist) @@ -629,7 +629,7 @@ class FuncGenDef(CallGenBase.CallGenBase): if args_node is not None: ags: rawptr[ast.Arguments] = rawptr[ast.Arguments](args_node) if ags.args is not None: - alist: rawptr[list[rawptr[ast.AST]]] = ags.args + alist: list[ast.AST*]* = ags.args an: sizet = len(alist) for ai in range(an): arg: rawptr[ast.Arg] = rawptr[ast.Arg](alist.get(ai)) @@ -787,7 +787,7 @@ class FuncGenDef(CallGenBase.CallGenBase): if args_node is not None: ags2: rawptr[ast.Arguments] = rawptr[ast.Arguments](args_node) if ags2.args is not None: - alist2: rawptr[list[rawptr[ast.AST]]] = ags2.args + alist2: list[ast.AST*]* = ags2.args an2: sizet = len(alist2) for ai2 in range(an2): arg2: rawptr[ast.Arg] = rawptr[ast.Arg](alist2.get(ai2)) @@ -829,12 +829,29 @@ class FuncGenDef(CallGenBase.CallGenBase): if cls_nm is not None: self.Gen.VarGen.set_var_annot_class_name( trans.SymTab, arg2.arg, cls_nm) + # t.CVolatile 形参:声明期标记,函数体内该参数名的 + # load/store 带 volatile(volatile 参数对象的读改写不得 + # 被优化器提升/合并,如自旋等待入参)。 + if arg2.annotation is not None: + if self.Gen.TypeGenErr.has_decorator_marker( + arg2.annotation, "CVolatile", 0) != 0: + self.Gen.VarGen.set_var_volatile( + trans.SymTab, arg2.arg) + # rawptr[t.CVolatile | T] 形参(volatile 指针指向物): + # 函数体内该参数解引用(p[i]/p.field/c.Deref(p))带 volatile。 + if self.Gen.TypeGenErr.has_pointee_volatile_marker( + arg2.annotation) != 0: + self.Gen.VarGen.set_var_pointee_volatile( + trans.SymTab, arg2.arg) pname2: str = pool.alloc(32) if pname2 is not None: viperlib.snprintf(pname2, 32, "%%%s", arg2.arg) param_val: rawptr[llvmlite.Value] = llvmlite.SSAValue( pool, param_ty2, pname2) - llvmlite.build_store(func_builder, param_val, alloca) + vol_p2: int = self.Gen.VarGen.var_is_volatile( + self.Gen.VarGen.lookup_var_entry( + trans.SymTab, arg2.arg)) + llvmlite.build_store(func_builder, param_val, alloca, vol_p2) # *args 变长参数:创建 va_list 缓冲区 + 调用 va_start + 注册到符号表 # 生成 IR: @@ -924,7 +941,7 @@ class FuncGenDef(CallGenBase.CallGenBase): # 1. 先预扫描 Global/Nonlocal 声明(Python 语义:global/nonlocal # 对整个函数作用域生效,与声明位置无关,故须在任何赋值前注册) # 2. 再预扫描 AnnAssign 为局部变量提前创建 alloca - body: rawptr[list[rawptr[ast.AST]]] = fd.children + body: list[ast.AST*]* = fd.children if body is not None: bn: sizet = len(body) for bi in range(bn): diff --git a/App/lib/core/Gen/Func/FuncGenMixin.vp b/App/lib/core/Gen/Func/FuncGenMixin.vp index 0a49090..3bcec90 100644 --- a/App/lib/core/Gen/Func/FuncGenMixin.vp +++ b/App/lib/core/Gen/Func/FuncGenMixin.vp @@ -113,7 +113,7 @@ class FuncGenMixin(CallGenBase.CallGenBase): if fd.args is not None: ags_n: rawptr[ast.Arguments] = rawptr[ast.Arguments](fd.args) if ags_n.args is not None: - alist_n: rawptr[list[rawptr[ast.AST]]] = ags_n.args + alist_n: list[ast.AST*]* = ags_n.args an_n: sizet = len(alist_n) for ai_n in range(an_n): arg_n: rawptr[ast.Arg] = rawptr[ast.Arg](alist_n.get(ai_n)) @@ -168,7 +168,7 @@ class FuncGenMixin(CallGenBase.CallGenBase): if fd.args is not None: ags_p: rawptr[ast.Arguments] = rawptr[ast.Arguments](fd.args) if ags_p.args is not None: - alist_p: rawptr[list[rawptr[ast.AST]]] = ags_p.args + alist_p: list[ast.AST*]* = ags_p.args idx_p: int = 0 apn: sizet = len(alist_p) for api in range(apn): @@ -196,11 +196,25 @@ class FuncGenMixin(CallGenBase.CallGenBase): if cls_nm_p is not None: self.Gen.VarGen.set_var_annot_class_name( trans.SymTab, arg_p.arg, cls_nm_p) + # t.CVolatile 形参:声明期标记(与 FuncGenDef 主路径同款), + # 函数体内该参数名的 load/store 带 volatile。 + if self.Gen.TypeGenErr.has_decorator_marker( + arg_p.annotation, "CVolatile", 0) != 0: + self.Gen.VarGen.set_var_volatile( + trans.SymTab, arg_p.arg) + # rawptr[t.CVolatile | T] 形参(volatile 指针指向物): + # 函数体内该参数解引用带 volatile(与 FuncGenDef 主路径同款)。 + if self.Gen.TypeGenErr.has_pointee_volatile_marker( + arg_p.annotation) != 0: + self.Gen.VarGen.set_var_pointee_volatile( + trans.SymTab, arg_p.arg) pname2_n: str = pool.alloc(32) if pname2_n is not None: viperlib.snprintf(pname2_n, 32, "%%%s", arg_p.arg) p_val: rawptr[llvmlite.Value] = llvmlite.SSAValue(pool, pty2_n, pname2_n) - llvmlite.build_store(func_builder, p_val, p_alloca) + vol_pn: int = self.Gen.VarGen.var_is_volatile( + self.Gen.VarGen.lookup_var_entry(trans.SymTab, arg_p.arg)) + llvmlite.build_store(func_builder, p_val, p_alloca, vol_pn) idx_p += 1 # 6. 保存父函数作用域状态(仅非变量表相关) @@ -232,7 +246,7 @@ class FuncGenMixin(CallGenBase.CallGenBase): # 8. 预扫描函数体: # a. 先预扫描 Global/Nonlocal 声明(Python 语义:对整个函数作用域生效) # b. 再预扫描 AnnAssign 为局部变量提前创建 alloca - body: rawptr[list[rawptr[ast.AST]]] = fd.children + body: list[ast.AST*]* = fd.children if body is not None: bn: sizet = len(body) for bi in range(bn): @@ -951,7 +965,7 @@ class FuncGenMixin(CallGenBase.CallGenBase): fd: rawptr[ast.FunctionDef] = rawptr[ast.FunctionDef](node) if fd is None or fd.children is None: return count - ch: rawptr[list[rawptr[ast.AST]]] = fd.children + ch: list[ast.AST*]* = fd.children cn: sizet = len(ch) for ci in range(cn): st: rawptr[ast.AST] = ch.get(ci) @@ -963,7 +977,7 @@ class FuncGenMixin(CallGenBase.CallGenBase): elif k == ast.ASTKind.Nonlocal: nl_node: rawptr[ast.Nonlocal] = rawptr[ast.Nonlocal](st) if nl_node is not None and nl_node.names is not None: - nl_list: rawptr[list[rawptr[ast.AST]]] = nl_node.names + nl_list: list[ast.AST*]* = nl_node.names nn: sizet = len(nl_list) for ni in range(nn): nm_node: rawptr[ast.AST] = nl_list.get(ni) @@ -1137,7 +1151,7 @@ class FuncGenMixin(CallGenBase.CallGenBase): if lam.args is not None: ags: rawptr[ast.Arguments] = rawptr[ast.Arguments](lam.args) if ags.args is not None: - alist: rawptr[list[rawptr[ast.AST]]] = ags.args + alist: list[ast.AST*]* = ags.args pn: sizet = len(alist) for pi in range(pn): if param_count >= 64: @@ -1482,7 +1496,7 @@ class FuncGenMixin(CallGenBase.CallGenBase): bop2: rawptr[ast.BoolOp] = rawptr[ast.BoolOp](node) cc2: int = cap_count if bop2.values is not None: - vs: rawptr[list[rawptr[ast.AST]]] = bop2.values + vs: list[ast.AST*]* = bop2.values vn: sizet = len(vs) for vi in range(vn): cc2 = self._lambda_scan_body(trans, vs.get(vi), param_names, param_count, @@ -1493,7 +1507,7 @@ class FuncGenMixin(CallGenBase.CallGenBase): cc3: int = self._lambda_scan_body(trans, cmp.left, param_names, param_count, cap_names, cap_allocas, cap_tys, cap_count) if cmp.comparators is not None: - cl: rawptr[list[rawptr[ast.AST]]] = cmp.comparators + cl: list[ast.AST*]* = cmp.comparators cn: sizet = len(cl) for cvi in range(cn): cc3 = self._lambda_scan_body(trans, cl.get(cvi), param_names, param_count, @@ -1504,13 +1518,13 @@ class FuncGenMixin(CallGenBase.CallGenBase): cc4: int = self._lambda_scan_body(trans, cal.func, param_names, param_count, cap_names, cap_allocas, cap_tys, cap_count) if cal.args is not None: - als: rawptr[list[rawptr[ast.AST]]] = cal.args + als: list[ast.AST*]* = cal.args an: sizet = len(als) for ai in range(an): cc4 = self._lambda_scan_body(trans, als.get(ai), param_names, param_count, cap_names, cap_allocas, cap_tys, cc4) if cal.keywords is not None: - kws: rawptr[list[rawptr[ast.AST]]] = cal.keywords + kws: list[ast.AST*]* = cal.keywords kn: sizet = len(kws) for ki in range(kn): kw: rawptr[ast.Keyword] = rawptr[ast.Keyword](kws.get(ki)) @@ -1542,7 +1556,7 @@ class FuncGenMixin(CallGenBase.CallGenBase): ls: rawptr[ast.List] = rawptr[ast.List](node) cc7: int = cap_count if ls.elts is not None: - es: rawptr[list[rawptr[ast.AST]]] = ls.elts + es: list[ast.AST*]* = ls.elts en: sizet = len(es) for ei in range(en): cc7 = self._lambda_scan_body(trans, es.get(ei), param_names, param_count, diff --git a/App/lib/core/Gen/GenApi.vp b/App/lib/core/Gen/GenApi.vp index 2d7e198..a5666d7 100644 --- a/App/lib/core/Gen/GenApi.vp +++ b/App/lib/core/Gen/GenApi.vp @@ -37,9 +37,20 @@ def set_current_file_imports(file_path: str) -> None: # ---------------- set_current_file_type(委托 TypeGenResolve.set_current_file) ---------------- def set_current_file_type(file_path: str) -> None: - """委托到 gen.set_current_file(原 Handles 壳)""" + """委托到 gen.set_current_file —— 报错归因的"当前文件"写入通道(原 Handles 壳) + + 与 fatal_error 同因同解:Converter 的 vpi 生成(Phase 1)、Phase A-pre-vpi + 跑在 BuildPipeline 注入 GenApi._g_gen 之前(BuildPipeline.vp 的 + _GeneratePyiFromAst 调用点早于 GenApi._g_gen 赋值),此时 _g_gen 为空; + 写侧若缺兜底会静默 no-op → fatal_error 的 get_current_file 读到的仍是 + 上一个文件(归因错位)。故回退读 Generator 模块单例(main.vp 启动即注入)。 + """ if _g_gen is not None: _g_gen.TypeGenResolve.set_current_file(file_path) + return + gg: rawptr[Generator.Generator] = Generator._g_gen + if gg is not None: + gg.TypeGenResolve.set_current_file(file_path) # ---------------- set_current_module_sha1(委托 TypeGenBase.set_current_module_sha1) ---------------- diff --git a/App/lib/core/Gen/GenTypes.vp b/App/lib/core/Gen/GenTypes.vp index d9df84b..6450f13 100644 --- a/App/lib/core/Gen/GenTypes.vp +++ b/App/lib/core/Gen/GenTypes.vp @@ -72,6 +72,15 @@ class VarEntry: # set_var_ptr_element 经 SpecWithElemBits 补「指针元素」语义(原 # IsPtrElement 字段已并入 Spec.ElemBits)。 Spec: rawptr[TSpec.TypeSpec] + # 1=该变量的 load/store 带 LLVM volatile 限定符(注解联合含 t.CVolatile)。 + # 追加在末尾:既有字段偏移不变,__sizeof__() 自动纳入,条目数组步长同步。 + IsVolatile: int + # 1=该变量是 rawptr[t.CVolatile | T](volatile 指针指向物,等价 C 的 + # volatile T*):解引用(p[i] / p.field / c.Deref(p))的 load/store 带 LLVM + # volatile,禁止优化器重排序/消除对所指对象的每次访问。注意与 IsVolatile + # 的区别:IsVolatile 限定"指针变量自身"的读写,本字段限定"指针所指物"。 + # 追加在末尾:既有字段偏移不变,__sizeof__() 自动纳入,条目数组步长同步。 + PointeeVolatile: int # ============================================================ @@ -110,6 +119,15 @@ class FieldEntry: # 注解侧再经 SpecWithSign / SpecWithSource 补符号性与注解来源名 # (Spec.SourceName,原 AnnotClassName 字段已并入)。 Spec: rawptr[TSpec.TypeSpec] + # 1=该字段经对象访问的 load/store 带 LLVM volatile 限定符 + # (字段注解联合含 t.CVolatile,如 Spinlock.locked: t.CVolatile | t.CInt)。 + # 追加在末尾:既有字段偏移不变,__sizeof__() 自动纳入,字段数组步长同步。 + IsVolatile: int + # 1=该字段是 rawptr[t.CVolatile | T](volatile 指针指向物,等价 C 的 + # volatile T*):经该字段解引用(self.ptr[i] / self.ptr.field)的 load/store + # 带 LLVM volatile。与 IsVolatile 的区别同 VarEntry.PointeeVolatile。 + # 追加在末尾:既有字段偏移不变,__sizeof__() 自动纳入,字段数组步长同步。 + PointeeVolatile: int # ============================================================ @@ -207,3 +225,205 @@ class FuncDefaultsEntry: ConstKinds: int | t.CPtr # const_kind 数组(DefaultCount 个 i32) ConstInts: int64 | t.CPtr # int_val 数组(DefaultCount 个 i64) ConstStrs: sizet | t.CPtr # str_val 指针数组(DefaultCount 个 i8*) + + +# ============================================================ +# REPL 会话(M2/M3):ctx 缓冲区布局 + 会话表条目 + 结果区头 +# +# ctx 缓冲区由宿主(lib/core/Repl.vp)在 PermPool 一次性分配,片段函数以首形参 +# 接收(注解 bytes ⇒ i8*),同一进程内宿主与翻译器共享同一块内存。 +# +# 布局(用户拍板子约定;字节全小端,无符号/有符号按位模式): +# +# 偏移 0 magic(u32 = 'REPL' = 0x5245504C) 4 version(u32 = 2) +# 偏移 8 result_kind(i32) 12 保留(0) +# 偏移 16 result_a(u64) 24 result_b(u64) +# 偏移 32 var_count(i32) 36 last_frag(i32) +# 偏移 40..63 保留(0) +# 偏移 64 + i*16 第 i 个会话变量的槽(步长 16 字节),i < REPL_VAR_CAP +# 偏移 1088 + j*16 第 j 个会话函数的指针槽(步长 16 字节),j < REPL_FUNC_CAP +# +# 版本与向后兼容(version 1 → 2,**纯尾部追加**): +# v1 = 头 64B + 变量槽区 1024B(共 1088B);v2 在 1088 之后追加函数指针槽区 +# (REPL_FUNC_CAP*REPL_FUNC_SLOT_SIZE)。前 1088 字节的字段偏移/宽度/语义逐字 +# 未变 ⇒ 按 v1 生成的旧片段 DLL 与 v2 宿主混用仍然正确(旧片段只读写 +# < 1088 的字节);新片段读函数指针槽时宿主必为 v2 ⇒ 无版本协商需求。 +# 头 +4 写 2 仅作"含函数指针槽区"的显式标记(片段侧不校验该字段)。 +# +# result_kind: +# 0 = none 本片段无结果(声明类片段) +# 1 = int/uint result_a = 值(补码位模式,宿主按有符号 64 位回显) +# 2 = float64 result_a = IEEE754 double 位模式(宿主重解释后回显) +# 3 = str/bytes result_a = 指针;result_b = 长度(0 = 以 NUL 结尾,宿主按 +# C 字符串处理——片段侧不额外调 strlen,故长度未知时写 0) +# 4 = struct/其它 result_a = 值地址(结构体值装箱后的盒子地址 / 非字符串指针) +# +# 槽内存放形态(ReplVarEntry.Kind): +# SCALAR — 标量/指针(≤8 字节)的值直接存槽; +# BOX — 结构体值:片段侧 malloc 盒子(值存盒内),槽内存盒子指针。 +# 槽步长 16 字节(结构体/字符串统一走指针 ⇒ 宿主无需知道类型);负载一律 +# ≤8 字节且 8 字节对齐即合法(MemBuddy 用户指针实测 8 字节对齐)。 +# 槽数上限 REPL_VAR_CAP ⇒ 变量区尺寸 = REPL_SLOT_BASE + REPL_VAR_CAP*REPL_SLOT_SIZE。 +# +# 函数指针槽(M3):槽内是会话函数在**定义片段 DLL** 里的入口地址(void*,8 字节; +# DLL 常驻不卸载 ⇒ 地址在会话期内有效)。宿主在片段 DLL 加载成功后 +# GetProcAddress(裸名) 取得地址并写入槽;后续片段的调用点从槽 load 地址后 +# 间接调用 ⇒ 函数代码只有一份(在定义片段里),调用点不复制代码。 +# ============================================================ +REPL_CTX_MARKER: t.CDefine = 1397571916 # 宿主侧对象标记(自比对;非 ctx 头字段) +REPL_CTX_MAGIC: t.CDefine = 0x5245504C # 'REPL'(ctx 头 +0,小端字节序为 R E P L) +REPL_CTX_VERSION: t.CDefine = 2 # ctx 头 +4(1 → 2:尾部追加函数指针槽区) +REPL_SLOT_SIZE: t.CDefine = 16 # 每个会话变量占用字节数(步长) +REPL_SLOT_BASE: t.CDefine = 64 # 槽区起点 = 固定头字节数(= +40..63 保留之后) +REPL_CTX_PARAM: t.CDefine = "__repl_ctx__" # 包裹模板里 ctx 形参名(与 Repl.vp 同源) +REPL_VAR_CAP: t.CDefine = 64 # 会话变量上限(变量区 = 64 + 64*16 = 1088 字节) +# 会话函数指针槽区(M3):起点紧跟变量区,步长与变量槽一致(16B,低 8 字节存地址) +REPL_FUNC_BASE: t.CDefine = 1088 # = REPL_SLOT_BASE + REPL_VAR_CAP*REPL_SLOT_SIZE +REPL_FUNC_SLOT_SIZE: t.CDefine = 16 # 每个会话函数占用字节数(步长) +REPL_FUNC_CAP: t.CDefine = 32 # 会话函数上限(函数区 = 32*16 = 512 字节) +# ctx 总字节数 = 64 + 64*16 + 32*16 = 1600(宿主 Repl._REPL_CTX_SIZE 必须一致) +REPL_CTX_SIZE: t.CDefine = 1600 +# 会话函数的形参上限(签名 token 数组定长;超出 ⇒ 注册期明确报错) +REPL_FUNC_PARAM_MAX: t.CDefine = 8 +# 片段入口函数名前缀(Repl.vp 生成 "__repl_frag_%d";用于区分"片段入口函数"与 +# 被提升的会话函数本体——前者是会话变量写入的作用域,后者只读会话槽) +REPL_ENTRY_PREFIX: t.CDefine = "__repl_frag_" +# 会话模块 SHA1(M4):片段翻译器的 ModuleSha1 取此**稳定**值 ⇒ 片段内定义的类型 +# 名形如 "<16位hex>.",且**跨片段逐字一致**——这是"先前片段定义的类在后续 +# 片段可见/可用"的基础(类型名是跨片段查找的唯一键)。 +# 派生:sha1("TransPyV-REPL-Session").hexdigest()[:16] +# 得 71f67b7cb405f0e4(一次性计算,见 repl-design.md §M4 的可复算命令)。 +# 形态:16 位小写 hex 与 includes 模块 SHA1 同形 ⇒ 被既有判定 +# GenApi.is_sha1_hex16 / StructGen.find_struct 的"模块段即 SHA1"分支 +# 直接当作模块限定名处理(无需新增解析规则)。 +# 反例(为什么不给每个片段各设一个 sha1):类型名会随片段漂移,先前片段注册的 +# 结构体/方法在后续片段的 find_struct_by_module 全部落空。 +# 与裸名约定的关系:`export def`(含片段入口与会话函数)走 has_export 分支取裸名 +# (FuncGenDef.vp:482-485),**不受**该值影响;受影响的只有类/方法/模块级全局 +# 等经 _mangle_name/_mangle_name_with_sha1 命名的符号。 +REPL_SESSION_SHA1: t.CDefine = "71f67b7cb405f0e4" + +# ctx 头字段偏移(与 ReplResult 字段顺序/宽度一一对应) +REPL_RES_KIND_OFF: t.CDefine = 8 # i32 +REPL_RES_A_OFF: t.CDefine = 16 # u64 +REPL_RES_B_OFF: t.CDefine = 24 # u64 +REPL_RES_VARCOUNT_OFF: t.CDefine = 32 # i32 +REPL_RES_LASTFRAG_OFF: t.CDefine = 36 # i32 + +# 结果 Kind(宿主按此回显) +REPL_RES_NONE: t.CDefine = 0 # 本片段无结果(如仅声明) +REPL_RES_INT: t.CDefine = 1 # 整数(读 result_a 位模式) +REPL_RES_FLOAT: t.CDefine = 2 # float64(读 result_a 的 double 位模式) +REPL_RES_STR: t.CDefine = 3 # str/bytes(读 result_a 指针 + result_b 长度) +REPL_RES_OBJ: t.CDefine = 4 # 结构体/其它指针(读 result_a 地址) + +# 会话变量类型标签(ReplVarEntry.TyTag) +REPL_TY_INT: t.CDefine = 1 +REPL_TY_FLOAT: t.CDefine = 2 +REPL_TY_STR: t.CDefine = 3 # i8*(str/bytes) +REPL_TY_PTR: t.CDefine = 4 # 其他指针(Bits=pointee 位宽 / TyName=结构体名) +REPL_TY_STRUCT: t.CDefine = 5 # 结构体值(按声明类型内联在槽里;≤ 槽容量) + +# 会话变量存放形态(ReplVarEntry.Kind) +REPL_KIND_UNTYPED: t.CDefine = 0 # 已登记槽位但尚未定型(未赋值/无注解) +REPL_KIND_SCALAR: t.CDefine = 1 # 值内联存槽(标量/指针/≤16B 结构体值) + +# 会话函数签名的返回类型 token(M3;只用于 ReplFuncEntry.RetTag) +REPL_TY_VOID: t.CDefine = 0 # 无返回值(void;不作为形参 token) + + +# ============================================================ +# ReplVarEntry - REPL 会话变量条目(跨片段存活,表本体在 PermPool) +# ============================================================ +@t.NoVTable +class ReplVarEntry: + """REPL 会话变量条目:名字 + ctx 槽偏移 + 定型信息 + + Name/TyName 必须是 PermPool 深拷贝——宿主的 AST 名字串属于片段 pool, + 行末 reset_to 后即失效,直接存指针会读到已回收内存。 + """ + Name: str # 变量名(PermPool 深拷贝,NUL 结尾) + TyName: str # 结构体名(TyTag=PTR 且 pointee 为结构体 / TyTag=STRUCT);否则 None + Offset: int # ctx 内槽偏移(REPL_SLOT_BASE + i * REPL_SLOT_SIZE) + Kind: int # REPL_KIND_* + TyTag: int # REPL_TY_* + Bits: int # 标量位宽;TyTag=PTR 时是 pointee 位宽(0=未知) + + +# ============================================================ +# ReplFuncEntry - REPL 会话函数条目(M3;跨片段存活,表本体在 PermPool) +# +# 语义:片段 N 里 `def f(...)` 被提升为片段模块里的**顶层 export def**(裸名导出), +# 该 DLL 加载后宿主 GetProcAddress 取到地址并写入 ctx 的函数指针槽;本条目记录 +# "名字 → 符号名 + 定义片段号 + 槽偏移 + 签名 token",后续片段的调用点据此 +# 从槽 load 地址并间接调用(与 M2 的会话变量槽同理:单一真相 + 间接寻址)。 +# +# 签名 token(ParamTags/ParamBits 每形参 1 字节、RetTag/RetBits)与来源: +# 定义片段翻译出的 IR 里 `define @f(i8* %__repl_ctx__, %p1, ...)` +# 是 ABI 的唯一真相(注解 → LLVM 类型由 codegen 解析),宿主在注册前解析该 +# define 行并**只接受**可往返重建的 token 集合: +# REPL_TY_INT(bits=1/8/16/32/64) / REPL_TY_FLOAT(bits=32/64) / REPL_TY_STR(i8*) +# 其余类型(结构体值、数组、多级指针等)⇒ 注册期明确报错,绝不静默按 i32 兜底。 +# ============================================================ +@t.NoVTable +class ReplFuncEntry: + """REPL 会话函数条目:名字 + 导出符号名 + ctx 函数指针槽 + 签名 token""" + Name: str # 会话函数名(= 用户写的裸名;PermPool 深拷贝,NUL 结尾) + SymName: str # 片段 DLL 里的导出符号名(PermPool 深拷贝;M3 = 裸名) + FragNo: int # 定义该函数的片段号(重定义时更新为最新片段号) + SlotOff: int # ctx 内函数指针槽偏移(REPL_FUNC_BASE + j*REPL_FUNC_SLOT_SIZE) + RetTag: int # REPL_TY_VOID / REPL_TY_INT / REPL_TY_FLOAT / REPL_TY_STR + RetBits: int # 对齐 ReplVarEntry.Bits 的口径(VOID 时为 0) + ParamCount: int # 形参数(**不含**注入的 ctx 首形参) + ParamTags: str # PermPool 字节数组(每形参 1 字节 tag,REPL_FUNC_PARAM_MAX 个) + ParamBits: str # PermPool 字节数组(每形参 1 字节 bits,REPL_FUNC_PARAM_MAX 个) + + +# ============================================================ +# ReplCtx - REPL 会话上下文(宿主建、翻译器读) +# +# 开关语义:trans.ReplCtx != None ⇒ REPL 模式(仅 Repl.vp 翻译片段前置位, +# 翻译后清空)。Gen 侧全部拦截点都以此字段为唯一守卫——普通编译路径下该字段 +# 恒为 None,逐字走原路径。 +# ============================================================ +@t.NoVTable +class ReplCtx: + """REPL 模式上下文(宿主持有;翻译器只读)""" + Marker: int # REPL_CTX_MARKER(防误传非 ReplCtx 对象) + Table: rawptr[ReplVarEntry] # 会话表数组(PermPool,容量 Cap) + Count: int # 已登记条目数(= 已分配槽数) + Cap: int # 容量(REPL_VAR_CAP) + CtxParam: str # 片段函数 ctx 形参名(PermPool 深拷贝) + SlotSize: int # 槽步长(REPL_SLOT_SIZE) + # 影子条目(宿主建、PermPool):仅供"裸表达式结果"做类型分类用,不占会话槽、 + # 不进表(Count 不计入),Name 固定为 "" 以便错误消息可读。 + Scratch: rawptr[ReplVarEntry] + # 宿主提供的永久池:会话表条目名/结构体名等**跨片段存活**的数据必须在此池 + # 深拷贝(片段的 trans.Pool 行末 reset_to 即回收,直接存其指针会悬垂)。 + Perm: rawptr[memhub.MemBuddy] + # 会话函数表(M3;宿主写、Gen 只读):数组容量 FuncCap,已登记 FuncCount 条。 + # 条目只在"定义片段 DLL 加载成功且 GetProcAddress 命中"后由宿主追加 ⇒ 片段 + # 翻译期看到的表恒为"先前片段的已就绪函数"(本片段新定义的同名函数走直接调用)。 + FuncTable: rawptr[ReplFuncEntry] + FuncCount: int + FuncCap: int # 容量(REPL_FUNC_CAP) + + +# ============================================================ +# ReplResult - ctx 头(宿主按字段读;片段按 REPL_RES_*_OFF 字节偏移写) +# +# 字段顺序/宽度即 ctx 布局:Magic(+0,i32) Version(+4,i32) Kind(+8,i32) +# Reserved0(+12,i32) A(+16,i64) B(+24,i64) VarCount(+32,i32) LastFrag(+36,i32)。 +# 宿主另有"同一头的浮点视图"(把 +16 的 8 字节按 double 读回,见 Repl.vp)。 +# ============================================================ +@t.NoVTable +class ReplResult: + """ctx 固定头(前 64 字节中的前 40 字节;+40..63 为保留)""" + Magic: int # REPL_CTX_MAGIC('REPL') + Version: int # REPL_CTX_VERSION + Kind: int # REPL_RES_* + Reserved0: int # 保留(0) + A: t.CInt64T # result_a + B: t.CInt64T # result_b + VarCount: int # 已分配槽数(宿主写,供检视) + LastFrag: int # 最近执行的片段号(宿主写,供检视) diff --git a/App/lib/core/Gen/ImportsGenMixin.vp b/App/lib/core/Gen/ImportsGenMixin.vp index bf74e71..55f86c3 100644 --- a/App/lib/core/Gen/ImportsGenMixin.vp +++ b/App/lib/core/Gen/ImportsGenMixin.vp @@ -13,7 +13,8 @@ import w32.fileio as fileio import lib.Stub.StubMerger as StubMerger import lib.core.CompilerFactory as CompilerFactory import lib.core.Gen.Translator as HT -import lib.Stub.StubMerger.StubMergerStore as StubMergerStore +import lib.Stub.StubMerger.StubMergerStore as StubMergerStore +import lib.core.VLogger as VLogger # ============================================================ @@ -731,7 +732,8 @@ class ImportsGenMixin: stdlib.free(line_buf_err) f_err.close() stdio.printf("ImportError: %s\n", msg) - sys.exit(1) + # 终止点收敛:REPL 会话内 longjmp 回 REPL 循环(诊断已打印),批处理下终止 + VLogger.repl_abort() # ============================================================ # _is_local_module_file - 当前文件同目录存在 {mod_name}.vp/.py @@ -819,7 +821,7 @@ class ImportsGenMixin: imp: rawptr[ast.Import] = rawptr[ast.Import](node) if imp is None: return 0 - names: rawptr[list[rawptr[ast.AST]]] = imp.names + names: list[ast.AST*]* = imp.names if names is None: return 0 nn: sizet = len(names) @@ -893,7 +895,7 @@ class ImportsGenMixin: # 否则别名 "syscall" 精确匹配不到包名 "vpsdk" → is_module_imported 返回 0 # → syscall._syscall1 / syscall.LOAD_SO 跨模块解析失败。 if impf.module is None: - names_m: rawptr[list[rawptr[ast.AST]]] = impf.names + names_m: list[ast.AST*]* = impf.names if names_m is not None: nn_m: sizet = len(names_m) for ni_m in range(nn_m): @@ -937,7 +939,7 @@ class ImportsGenMixin: trans.Pool, trans.CurrentPackage, impf.level, impf.module) if resolved is not None: - names: rawptr[list[rawptr[ast.AST]]] = impf.names + names: list[ast.AST*]* = impf.names if names is not None: nn: sizet = len(names) for ni in range(nn): diff --git a/App/lib/core/Gen/MatchGenMixin.vp b/App/lib/core/Gen/MatchGenMixin.vp index 8183932..6de5d58 100644 --- a/App/lib/core/Gen/MatchGenMixin.vp +++ b/App/lib/core/Gen/MatchGenMixin.vp @@ -39,7 +39,7 @@ class MatchGenMixin: Gen: rawptr[Generator.Generator] def _case_tail_unreachable(self, trans: rawptr[HT.Translator], builder: rawptr[llvmlite.IRBuilder], - body_list: rawptr[list[rawptr[ast.AST]]]) -> int: + body_list: list[ast.AST*]*) -> int: if trans is None or builder is None: return 0 if llvmlite.builder_cur_block_is_terminated(builder) != 0: @@ -132,7 +132,7 @@ class MatchGenMixin: # 2b. 若未确定,遍历 cases 查 MatchClass 变体 if is_renum == 0: - cases_list: rawptr[list[rawptr[ast.AST]]] = m_node.cases + cases_list: list[ast.AST*]* = m_node.cases if cases_list is not None: cn: sizet = len(cases_list) ci: int = 0 @@ -227,7 +227,7 @@ class MatchGenMixin: # # 遍历 case body 顶层语句,查找 Expr(Call(Attribute(Name('c'), 'NoBreak'))) # ============================================================ - def _has_no_break(self, body_list: rawptr[list[rawptr[ast.AST]]]) -> int: + def _has_no_break(self, body_list: list[ast.AST*]*) -> int: if body_list is None: return 0 n: sizet = len(body_list) @@ -352,7 +352,7 @@ class MatchGenMixin: pool, func, name_buf) # 3. 准备收集数组(用 CSizeT 数组存指针) - cases_list: rawptr[list[rawptr[ast.AST]]] = node.cases + cases_list: list[ast.AST*]* = node.cases if cases_list is None: # fail-fast: match cases 缺失 self.Gen.TypeGenErr.fatal_error(node, "match cases 缺失", "TypeError") @@ -452,7 +452,7 @@ class MatchGenMixin: elif pk == ast.ASTKind.MatchOr: # MatchOr: 多个值共享一个 BB(取第一个值做 case) mo: rawptr[ast.MatchOr] = rawptr[ast.MatchOr](pat) - pats: rawptr[list[rawptr[ast.AST]]] = mo.patterns + pats: list[ast.AST*]* = mo.patterns if pats is not None and len(pats) > 0: first_pat: rawptr[ast.AST] = pats.get(0) if first_pat is not None \ @@ -474,7 +474,7 @@ class MatchGenMixin: bb = default_bb # 检测 NoBreak - body_list: rawptr[list[rawptr[ast.AST]]] = mc.children + body_list: list[ast.AST*]* = mc.children nb: int = self._has_no_break(body_list) # 存入数组 @@ -576,7 +576,7 @@ class MatchGenMixin: subject_ptr, trans, pool, builder) # 翻译 case body - body_list: rawptr[list[rawptr[ast.AST]]] = mc.children + body_list: list[ast.AST*]* = mc.children if body_list is not None: bc: sizet = len(body_list) bi: int = 0 @@ -614,7 +614,7 @@ class MatchGenMixin: rm_merge_reach = 1 else: # 有显式 default:翻译 default case body(找到 is_def==1 的 case) - dbody: rawptr[list[rawptr[ast.AST]]] = None + dbody: list[ast.AST*]* = None di: int = 0 while di < collected: if def_arr[di] == 1: @@ -627,7 +627,7 @@ class MatchGenMixin: # (i32→inttoptr 截断指针 → translate_stmt 读垃圾节点崩溃, # gen1 IR 证据)。用 dbody2 内联声明接收者遍历(对齐 # Converter arg_defaults 内联声明规避模式)。 - dbody2: rawptr[list[rawptr[ast.AST]]] = dmc.children + dbody2: list[ast.AST*]* = dmc.children dbody = dbody2 if dbody2 is not None: dn: sizet = len(dbody2) @@ -670,7 +670,7 @@ class MatchGenMixin: if pat is None or renum_entry is None or renum_ty is None: return mc: rawptr[ast.MatchClass] = rawptr[ast.MatchClass](pat) - pats: rawptr[list[rawptr[ast.AST]]] = mc.patterns + pats: list[ast.AST*]* = mc.patterns if pats is None: return # bitcast subject_ptr 到 renum_ptr_ty(确保 GEP 类型匹配) @@ -821,7 +821,7 @@ class MatchGenMixin: sq_merge_reach: int = 0 # 1. 求值 subject 各元素 - elts: rawptr[list[rawptr[ast.AST]]] = None + elts: list[ast.AST*]* = None if subject_node.kind() == ast.ASTKind.Tuple: st: rawptr[ast.Tuple] = rawptr[ast.Tuple](subject_node) if st is not None: @@ -841,7 +841,7 @@ class MatchGenMixin: for ei in range(ecount): # elts 预声明+重赋值会把 .get 误分派成 list[int].get(i32 截断); # 用内联别名接收者规避(对齐 Converter arg_defaults 内联模式) - elts_inl: rawptr[list[rawptr[ast.AST]]] = elts + elts_inl: list[ast.AST*]* = elts elt: rawptr[ast.AST] = elts_inl.get(ei) sv: rawptr[llvmlite.Value] = self.Gen.ExprGen.translate_value( builder, pool, trans.Module, elt, @@ -865,7 +865,7 @@ class MatchGenMixin: pool, func, name_buf) # 3. 第一遍:收集 case 信息(check/body BB、default 标记、nobreak、guard) - cases_list: rawptr[list[rawptr[ast.AST]]] = node.cases + cases_list: list[ast.AST*]* = node.cases if cases_list is None: return 0 case_count: sizet = len(cases_list) @@ -927,7 +927,7 @@ class MatchGenMixin: body_arr[collected] = sizet(bbb) if first_check < 0: first_check = collected - body_list: rawptr[list[rawptr[ast.AST]]] = mc.children + body_list: list[ast.AST*]* = mc.children nb: int = self._has_no_break(body_list) def_arr[collected] = is_def nb_arr[collected] = nb @@ -950,8 +950,8 @@ class MatchGenMixin: if def_arr[ci] == 1: ci = ci + 1 continue - cbb2: rawptr[llvmlite.BasicBlock] = rawptr[llvmlite.BasicBlock](check_arr[ci], t.CPtr) - bbb2: rawptr[llvmlite.BasicBlock] = rawptr[llvmlite.BasicBlock](body_arr[ci], t.CPtr) + cbb2: rawptr[llvmlite.BasicBlock] = rawptr[llvmlite.BasicBlock](check_arr[ci]) + bbb2: rawptr[llvmlite.BasicBlock] = rawptr[llvmlite.BasicBlock](body_arr[ci]) llvmlite.position_at_end(builder, cbb2) # 计算匹配条件(元素比较 && guard) @@ -960,7 +960,7 @@ class MatchGenMixin: mc2: rawptr[ast.MatchCase] = rawptr[ast.MatchCase](cnode) ms: rawptr[ast.MatchSequence] = ( rawptr[ast.MatchSequence])(mc2.pattern) - pats: rawptr[list[rawptr[ast.AST]]] = None + pats: list[ast.AST*]* = None if ms is not None: pats = ms.patterns pcount: sizet = 0 @@ -973,7 +973,7 @@ class MatchGenMixin: for pi in range(ecount): # pats 预声明+重赋值会把 .get 误分派成 list[int].get(i32 截断); # 用内联别名接收者规避(对齐 Converter arg_defaults 内联模式) - pats_inl: rawptr[list[rawptr[ast.AST]]] = pats + pats_inl: list[ast.AST*]* = pats pat1: rawptr[ast.AST] = pats_inl.get(pi) if pat1 is None: continue @@ -987,7 +987,7 @@ class MatchGenMixin: if pv is None: self.Gen.TypeGenErr.fatal_error( pat1, f"序列模式元素 {pi} 翻译失败", "TypeError") - sv1: rawptr[llvmlite.Value] = rawptr[llvmlite.Value](subj_vals[pi], t.CPtr) + sv1: rawptr[llvmlite.Value] = rawptr[llvmlite.Value](subj_vals[pi]) c1: rawptr[llvmlite.Value] = self._seq_cmp_eq(trans, builder, sv1, pv) if c1 is None: self.Gen.TypeGenErr.fatal_error(pat1, "序列模式元素比较失败", "TypeError") @@ -1059,7 +1059,7 @@ class MatchGenMixin: mc3: rawptr[ast.MatchCase] = ( rawptr[ast.MatchCase])(cnode2) self.Gen.VarGen.enter_scope(trans.SymTab, SCOPE_MATCH) - body_list2: rawptr[list[rawptr[ast.AST]]] = mc3.children + body_list2: list[ast.AST*]* = mc3.children if body_list2 is not None: bc: sizet = len(body_list2) bi: int = 0 @@ -1092,7 +1092,7 @@ class MatchGenMixin: # 7. default 块 llvmlite.position_at_end(builder, default_bb) - dbody: rawptr[list[rawptr[ast.AST]]] = None + dbody: list[ast.AST*]* = None if has_default == 1: di: int = 0 while di < collected: @@ -1106,7 +1106,7 @@ class MatchGenMixin: # (i32→inttoptr 截断指针 → translate_stmt 读垃圾节点崩溃, # gen1 IR 证据)。用 dbody2 内联声明接收者遍历(对齐 # Converter arg_defaults 内联声明规避模式)。 - dbody2: rawptr[list[rawptr[ast.AST]]] = dmc.children + dbody2: list[ast.AST*]* = dmc.children dbody = dbody2 if dbody2 is not None: dn: sizet = len(dbody2) @@ -1178,7 +1178,7 @@ class MatchGenMixin: pool, func, name_buf) # 3. 收集 case 信息 - cases_list: rawptr[list[rawptr[ast.AST]]] = node.cases + cases_list: list[ast.AST*]* = node.cases if cases_list is None: # fail-fast: match cases 缺失 self.Gen.TypeGenErr.fatal_error(node, "match cases 缺失", "TypeError") @@ -1233,7 +1233,7 @@ class MatchGenMixin: has_default = 1 elif pk == ast.ASTKind.MatchOr: mo: rawptr[ast.MatchOr] = rawptr[ast.MatchOr](pat) - pats: rawptr[list[rawptr[ast.AST]]] = mo.patterns + pats: list[ast.AST*]* = mo.patterns if pats is not None and len(pats) > 0: first_pat: rawptr[ast.AST] = pats.get(0) if first_pat is not None \ @@ -1266,7 +1266,7 @@ class MatchGenMixin: else: bb = default_bb - body_list: rawptr[list[rawptr[ast.AST]]] = mc.children + body_list: list[ast.AST*]* = mc.children nb: int = self._has_no_break(body_list) if collected < MAX_MATCH_CASES: @@ -1316,7 +1316,7 @@ class MatchGenMixin: self.Gen.VarGen.enter_scope(trans.SymTab, SCOPE_MATCH) - body_list: rawptr[list[rawptr[ast.AST]]] = mc.children + body_list: list[ast.AST*]* = mc.children if body_list is not None: bc: sizet = len(body_list) bi: int = 0 @@ -1346,7 +1346,7 @@ class MatchGenMixin: trans._ctrl_merge_reachable = 1 sm_merge_reach = 1 else: - dbody: rawptr[list[rawptr[ast.AST]]] = None + dbody: list[ast.AST*]* = None di: int = 0 while di < collected: if def_arr[di] == 1: @@ -1359,7 +1359,7 @@ class MatchGenMixin: # (i32→inttoptr 截断指针 → translate_stmt 读垃圾节点崩溃, # gen1 IR 证据)。用 dbody2 内联声明接收者遍历(对齐 # Converter arg_defaults 内联声明规避模式)。 - dbody2: rawptr[list[rawptr[ast.AST]]] = dmc.children + dbody2: list[ast.AST*]* = dmc.children dbody = dbody2 if dbody2 is not None: dn: sizet = len(dbody2) diff --git a/App/lib/core/Gen/Stmt/StmtGenCifMixin.vp b/App/lib/core/Gen/Stmt/StmtGenCifMixin.vp index da43456..fe708fb 100644 --- a/App/lib/core/Gen/Stmt/StmtGenCifMixin.vp +++ b/App/lib/core/Gen/Stmt/StmtGenCifMixin.vp @@ -238,7 +238,7 @@ class StmtGenCifMixin: return 0 if k == ast.ASTKind.BoolOp: bo: rawptr[ast.BoolOp] = rawptr[ast.BoolOp](node) - vals: rawptr[list[rawptr[ast.AST]]] = bo.values + vals: list[ast.AST*]* = bo.values if vals is None: self.CifEvalOk = 0 return 0 @@ -267,7 +267,7 @@ class StmtGenCifMixin: if self.CifEvalOk == 0: return 0 ops_list: list[int] | t.CPtr = (list[int] | t.CPtr)(cmp.ops) - comps: rawptr[list[rawptr[ast.AST]]] = cmp.comparators + comps: list[ast.AST*]* = cmp.comparators if ops_list is None or comps is None: self.CifEvalOk = 0 return 0 @@ -373,7 +373,7 @@ class StmtGenCifMixin: return 0 if string.strcmp(vn.id, "ctraits") != 0 or string.strcmp(at.attr, "isptr") != 0: return 0 - cargs: rawptr[list[rawptr[ast.AST]]] = cl.args + cargs: list[ast.AST*]* = cl.args if cargs is None or len(cargs) < 1: self.CifEvalOk = 0 return 0 @@ -414,7 +414,7 @@ class StmtGenCifMixin: kind = nm.id if kind is None: return 0 - cargs: rawptr[list[rawptr[ast.AST]]] = cl.args + cargs: list[ast.AST*]* = cl.args if cargs is None or len(cargs) < 1: return 0 arg0: rawptr[ast.AST] = cargs.get(0) @@ -473,7 +473,7 @@ class StmtGenCifMixin: # return a, b → 打包匿名 struct 值(多返回值,函数返回类型 tuple[T1,T2]) if rt.value.kind() == ast.ASTKind.Tuple: rtp: rawptr[ast.Tuple] = rawptr[ast.Tuple](rt.value) - rtp_elts: rawptr[list[rawptr[ast.AST]]] = rtp.elts + rtp_elts: list[ast.AST*]* = rtp.elts rt_n: sizet = 0 if rtp_elts is not None: rt_n = len(rtp_elts) @@ -714,7 +714,7 @@ class StmtGenCifMixin: cif_val: int = self._evaluate_cif_test(pool, trans, if_node.test) if self.CifEvalOk != 0: if cif_val != 0: - body_cif: rawptr[list[rawptr[ast.AST]]] = if_node.children + body_cif: list[ast.AST*]* = if_node.children if body_cif is not None: bc_cif: sizet = len(body_cif) bi_cif: sizet @@ -723,7 +723,7 @@ class StmtGenCifMixin: if stmt_cif is not None: self.Gen.StmtGen.translate_stmt(trans, stmt_cif) else: - orelse_cif: rawptr[list[rawptr[ast.AST]]] = if_node.orelse + orelse_cif: list[ast.AST*]* = if_node.orelse if orelse_cif is not None and len(orelse_cif) == 1: sub_cif: rawptr[ast.AST] = orelse_cif.get(0) if sub_cif is not None and sub_cif.kind() == ast.ASTKind.If: @@ -776,7 +776,7 @@ class StmtGenCifMixin: # 检查是否有 else 分支 has_else: int = 0 - orelse_list: rawptr[list[rawptr[ast.AST]]] = if_node.orelse + orelse_list: list[ast.AST*]* = if_node.orelse if orelse_list is not None: if len(orelse_list) > 0: has_else = 1 @@ -798,7 +798,7 @@ class StmtGenCifMixin: # 末句 If 全 return 被 fail-fast 误判"流程掉出末尾"的根因链一环) trans._ctrl_merge_reachable = 1 llvmlite.position_at_end(builder, then_bb) - body: rawptr[list[rawptr[ast.AST]]] = if_node.children + body: list[ast.AST*]* = if_node.children if body is not None: body_count: sizet = len(body) for bi in range(body_count): @@ -929,7 +929,7 @@ class StmtGenCifMixin: trans._break_bb = end_bb trans._continue_bb = cond_bb - body: rawptr[list[rawptr[ast.AST]]] = while_node.children + body: list[ast.AST*]* = while_node.children if body is not None: body_count: sizet = len(body) for bi in range(body_count): @@ -980,7 +980,7 @@ class StmtGenCifMixin: """递归扫描节点子树(跳过嵌套 While/For 子树),含 Break 返回 1""" if node is None: return 0 - ch: rawptr[list[rawptr[ast.AST]]] = node.children + ch: list[ast.AST*]* = node.children if ch is not None: n: sizet = len(ch) for bi in range(n): @@ -996,7 +996,7 @@ class StmtGenCifMixin: return 1 return 0 - def _StmtHasBreak(self, body: rawptr[list[rawptr[ast.AST]]]) -> int: + def _StmtHasBreak(self, body: list[ast.AST*]*) -> int: """语句列表是否含本层 break(嵌套循环的 break 归内层)""" if body is None: return 0 @@ -1194,7 +1194,7 @@ class StmtGenCifMixin: call: rawptr[ast.Call] = rawptr[ast.Call](iter_node) # 3. 解析 range 参数: range(stop) / range(start, stop) / range(start, stop, step) - args: rawptr[list[rawptr[ast.AST]]] = call.args + args: list[ast.AST*]* = call.args if args is None: return 0 arg_count: sizet = len(args) @@ -1429,7 +1429,7 @@ class StmtGenCifMixin: trans._break_bb = end_bb trans._continue_bb = incr_bb - body: rawptr[list[rawptr[ast.AST]]] = for_node.children + body: list[ast.AST*]* = for_node.children if body is not None: body_count: sizet = len(body) for bi in range(body_count): @@ -1479,7 +1479,7 @@ class StmtGenCifMixin: """从 Tuple target 提取前两个 Name 变量名,写入 out1/out2,成功返回 1""" if tup is None or tup.elts is None or out1 is None or out2 is None: return 0 - elts: rawptr[list[rawptr[ast.AST]]] = tup.elts + elts: list[ast.AST*]* = tup.elts n: sizet = len(elts) if n < 2: return 0 @@ -1566,7 +1566,7 @@ class StmtGenCifMixin: r1_stop = llvmlite.const_int64(pool, 0) # 先赋中间变量再取 args(cast 链式属性访问会推断错字段偏移) call_src1: rawptr[ast.Call] = rawptr[ast.Call](src1) - cargs1: rawptr[list[rawptr[ast.AST]]] = call_src1.args + cargs1: list[ast.AST*]* = call_src1.args if cargs1 is not None: n1: sizet = len(cargs1) if n1 >= 1: @@ -1636,7 +1636,7 @@ class StmtGenCifMixin: r2_stop = llvmlite.const_int64(pool, 0) # 先赋中间变量再取 args(cast 链式属性访问会推断错字段偏移) call_src2: rawptr[ast.Call] = rawptr[ast.Call](src2) - cargs2: rawptr[list[rawptr[ast.AST]]] = call_src2.args + cargs2: list[ast.AST*]* = call_src2.args if cargs2 is not None: n2: sizet = len(cargs2) if n2 >= 1: @@ -1808,7 +1808,7 @@ class StmtGenCifMixin: trans._break_bb = end_bb trans._continue_bb = incr_bb - body: rawptr[list[rawptr[ast.AST]]] = for_node.children + body: list[ast.AST*]* = for_node.children if body is not None: body_count: sizet = len(body) for bi in range(body_count): @@ -1969,7 +1969,7 @@ class StmtGenCifMixin: trans._break_bb = end_bb trans._continue_bb = incr_bb - body: rawptr[list[rawptr[ast.AST]]] = for_node.children + body: list[ast.AST*]* = for_node.children if body is not None: body_count: sizet = len(body) for bi in range(body_count): @@ -2136,7 +2136,7 @@ class StmtGenCifMixin: trans._break_bb = end_bb trans._continue_bb = incr_bb - body: rawptr[list[rawptr[ast.AST]]] = for_node.children + body: list[ast.AST*]* = for_node.children if body is not None: body_count: sizet = len(body) for bi in range(body_count): @@ -2180,7 +2180,7 @@ class StmtGenCifMixin: lst: rawptr[ast.List] = rawptr[ast.List](list_node) if lst is None or lst.elts is None: self.Gen.TypeGenErr.fatal_error(for_node, "列表字面量迭代: 空列表") - elts: rawptr[list[rawptr[ast.AST]]] = lst.elts + elts: list[ast.AST*]* = lst.elts elts_count: sizet = len(elts) if elts_count <= 0: self.Gen.TypeGenErr.fatal_error(for_node, "列表字面量迭代: 空列表") @@ -2334,7 +2334,7 @@ class StmtGenCifMixin: trans._break_bb = end_bb trans._continue_bb = incr_bb - body: rawptr[list[rawptr[ast.AST]]] = for_node.children + body: list[ast.AST*]* = for_node.children if body is not None: body_count: sizet = len(body) for bi in range(body_count): @@ -2445,7 +2445,7 @@ class StmtGenCifMixin: trans._break_bb = end_bb trans._continue_bb = incr_bb - body: rawptr[list[rawptr[ast.AST]]] = for_node.children + body: list[ast.AST*]* = for_node.children if body is not None: body_count: sizet = len(body) for bi in range(body_count): @@ -2637,7 +2637,7 @@ class StmtGenCifMixin: old_continue: rawptr[llvmlite.BasicBlock] = trans._continue_bb trans._break_bb = end_bb trans._continue_bb = step_bb - body: rawptr[list[rawptr[ast.AST]]] = for_node.children + body: list[ast.AST*]* = for_node.children if body is not None: body_count: sizet = len(body) for bi in range(body_count): @@ -2664,7 +2664,7 @@ class StmtGenCifMixin: """enumerate 迭代: for i, v in enumerate(x)""" if call is None or call.args is None: return 0 - cargs: rawptr[list[rawptr[ast.AST]]] = call.args + cargs: list[ast.AST*]* = call.args if len(cargs) < 1: self.Gen.TypeGenErr.fatal_error(for_node, "enumerate 需要至少一个参数") return self._build_seq_loop(trans, for_node, t1, t2, cargs.get(0), None, 1) @@ -2677,7 +2677,7 @@ class StmtGenCifMixin: if call is None or call.args is None: # fail-fast: zip 参数缺失 self.Gen.TypeGenErr.fatal_error(call, "zip 参数缺失", "SyntaxError") - cargs: rawptr[list[rawptr[ast.AST]]] = call.args + cargs: list[ast.AST*]* = call.args if len(cargs) < 2: self.Gen.TypeGenErr.fatal_error(for_node, "zip 需要至少两个参数") return self._build_seq_loop(trans, for_node, t1, t2, cargs.get(0), cargs.get(1), 2) diff --git a/App/lib/core/Gen/Stmt/StmtGenDeclMixin.vp b/App/lib/core/Gen/Stmt/StmtGenDeclMixin.vp index 2a11bdc..fddd96c 100644 --- a/App/lib/core/Gen/Stmt/StmtGenDeclMixin.vp +++ b/App/lib/core/Gen/Stmt/StmtGenDeclMixin.vp @@ -46,7 +46,7 @@ class StmtGenDeclMixin(StmtGenCifMixin.StmtGenCifMixin): gn: rawptr[ast.Global] = rawptr[ast.Global](node) if gn is None: return 0 - names: rawptr[list[rawptr[ast.AST]]] = gn.names + names: list[ast.AST*]* = gn.names if names is None: return 0 n: sizet = len(names) @@ -67,7 +67,7 @@ class StmtGenDeclMixin(StmtGenCifMixin.StmtGenCifMixin): nl: rawptr[ast.Nonlocal] = rawptr[ast.Nonlocal](node) if nl is None: return 0 - names: rawptr[list[rawptr[ast.AST]]] = nl.names + names: list[ast.AST*]* = nl.names if names is None: return 0 n: sizet = len(names) @@ -105,6 +105,14 @@ class StmtGenDeclMixin(StmtGenCifMixin.StmtGenCifMixin): return 0 ck: int = call_node.kind() if ck != ast.ASTKind.Call: + # REPL 模式(M2):片段函数本体内的裸表达式语句(如 1+2 / x / x + 1) + # 求值后写入 ctx 结果区,由宿主回显。调用表达式仍走下方原路径 + # (执行 + 丢弃返回值,同 print/printf 语义)。 + # 守卫 trans.ReplCtx:非 REPL 恒 None ⇒ "表达式语句除调用外一律忽略" + # 的原行为逐字不变。 + if trans is not None and trans.ReplCtx is not None \ + and self.Gen.VarGen.repl_in_frag_scope(trans.SymTab) != 0: + self.Gen.VarGen.repl_expr_result(trans, call_node) return 0 cl: rawptr[ast.Call] = rawptr[ast.Call](call_node) @@ -126,6 +134,30 @@ class StmtGenDeclMixin(StmtGenCifMixin.StmtGenCifMixin): trans.Pool, trans._cur_builder, trans.Module, cl, trans) else: # 通用函数调用(返回值丢弃) + # M3:裸名调用**会话函数**(跨片段)时额外把返回值写进 ctx 结果区回显 + # (M2 的限制条目"调用表达式的结果不回显"对会话函数放开——用户在 + # `>>> f(41)` 后期望看到 `=> 42`)。结果区的类型分类直接用会话函数表里 + # 记录的返回类型 token(宿主从定义片段 IR 解析而来,是定义端 ABI 真相), + # 不用影子条目推断 ⇒ 与调用点签名同源。 + # 守卫 trans.ReplCtx:非 REPL 恒 None ⇒ 原"执行 + 丢弃返回值"逐字不变; + # 仅裸名(Name)且表中登记的会话函数走本分支(printf/print 等在上方已分流)。 + if trans is not None and trans.ReplCtx is not None \ + and func_node.kind() == ast.ASTKind.Name \ + and self.Gen.VarGen.repl_in_frag_scope(trans.SymTab) != 0: + sfn_ent: rawptr[GenTypes.ReplFuncEntry] = \ + self.Gen.VarGen.repl_find_func_entry(trans.ReplCtx, func_name) + if sfn_ent is not None and sfn_ent.RetTag != GenTypes.REPL_TY_VOID: + sfn_v: rawptr[llvmlite.Value] = self.Gen.CallGen.translate_call( + trans.Pool, trans._cur_builder, trans.Module, call_node, + trans._funcs, trans._func_count, trans) + if sfn_v is not None: + ctx_v: rawptr[llvmlite.Value] = self.Gen.VarGen.repl_ctx_ptr( + trans, trans.ReplCtx, call_node) + if ctx_v is not None: + self.Gen.VarGen.repl_emit_result( + trans, ctx_v, sfn_v, sfn_ent.RetTag, sfn_ent.RetBits, + call_node) + return 0 self.Gen.CallGen.translate_call( trans.Pool, trans._cur_builder, trans.Module, call_node, trans._funcs, trans._func_count, trans) diff --git a/App/lib/core/Gen/Stmt/StmtGenMixin.vp b/App/lib/core/Gen/Stmt/StmtGenMixin.vp index 8a2eb21..bd03e34 100644 --- a/App/lib/core/Gen/Stmt/StmtGenMixin.vp +++ b/App/lib/core/Gen/Stmt/StmtGenMixin.vp @@ -597,7 +597,7 @@ class StmtGenMixin(StmtGenDeclMixin.StmtGenDeclMixin): mod: rawptr[llvmlite.LLVMModule] = trans.Module if pool is None or builder is None or mod is None: return 0 - items: rawptr[list[rawptr[ast.AST]]] = wt.items + items: list[ast.AST*]* = wt.items if items is None: return 0 n: sizet = len(items) @@ -695,7 +695,7 @@ class StmtGenMixin(StmtGenDeclMixin.StmtGenDeclMixin): trans, pool, builder, mod, fd_cls_bare, ctx_val) # 2. 翻译 body - body: rawptr[list[rawptr[ast.AST]]] = wt.children + body: list[ast.AST*]* = wt.children if body is not None: bn: sizet = len(body) for bi in range(bn): diff --git a/App/lib/core/Gen/StructGenMixin.vp b/App/lib/core/Gen/StructGenMixin.vp index 45f2f40..c01ac90 100644 --- a/App/lib/core/Gen/StructGenMixin.vp +++ b/App/lib/core/Gen/StructGenMixin.vp @@ -684,10 +684,10 @@ class StructGenMixin: dst_l.end_col_offset = src_l.end_col_offset dst_l.ctx = src_l.ctx # 深拷贝 elts:stdlib 手动构造 list[AST | t.CPtr] 容器 - src_elts_l: rawptr[list[rawptr[ast.AST]]] = src_l.elts + src_elts_l: list[ast.AST*]* = src_l.elts if src_elts_l is not None: cnt_l: sizet = len(src_elts_l) - lst_copy_l: rawptr[list[rawptr[ast.AST]]] = rawptr[list[rawptr[ast.AST]]]( + lst_copy_l: list[ast.AST*]* = (list[ast.AST*] | t.CPtr)( pool.alloc(list[rawptr[ast.AST]].__sizeof__())) if lst_copy_l is not None: string.memset(lst_copy_l, 0, list[rawptr[ast.AST]].__sizeof__()) @@ -1906,3 +1906,24 @@ class StructGenMixin: if fe is None: return None return TSpec.SpecSourceName(fe.Spec) + + def field_is_volatile(self, fe: rawptr[GenTypes.FieldEntry]) -> int: + """字段是否 volatile(FieldEntry.IsVolatile 承载),1=是 / 0=否 + + 由 ClassGenDef 收集字段注解时检测 t.CVolatile 写入;消费点(成员读写的 + load/store)据此加 LLVM volatile 限定符。 + """ + if fe is None: + return 0 + return fe.IsVolatile + + def field_pointee_is_volatile(self, fe: rawptr[GenTypes.FieldEntry]) -> int: + """字段所指物是否 volatile(FieldEntry.PointeeVolatile 承载),1=是 / 0=否 + + 字段注解为 rawptr[t.CVolatile | T](等价 C 的 volatile T*)时由 ClassGenDef + 写入;消费点(经该字段解引用 self.ptr[i] / self.ptr.field 的 load/store) + 据此加 LLVM volatile 限定符。 + """ + if fe is None: + return 0 + return fe.PointeeVolatile diff --git a/App/lib/core/Gen/Translator.vp b/App/lib/core/Gen/Translator.vp index 6a8cd85..098a779 100644 --- a/App/lib/core/Gen/Translator.vp +++ b/App/lib/core/Gen/Translator.vp @@ -137,16 +137,13 @@ class Translator: _cur_builder: rawptr[llvmlite.IRBuilder] # 当前赋值语句的注解节点(AnnAssign 翻译 rhs 前设置,Assign 翻译前清空)。 - # 供 c.Deref(i8*) 推断读取宽度:v: int = c.Deref(p) → bitcast i32* load - # (对齐 TPC HandlesSpecialCall._infer_deref_target_type;否则固定读 1 字节 + sext 截断)。 + # 供裸名泛型构造从注解推断类型实参:`s: Stack[int] = Stack()` 用注解里的 + # int 特化(消费者见 CallGenCtor.vp 的 CurAssignNode/CurAssignAnnot 判定)。 CurAssignAnnot: rawptr[ast.AST] # 当前赋值语句的 RHS 节点(AnnAssign 翻译 rhs 前设置,rhs 翻译后清除)。 - # 供 c.Deref 判定"是否为赋值直接 RHS":仅 v: int = c.Deref(p) 时用 - # CurAssignAnnot 推断读取宽度;嵌套在 while 条件/比较/binop/类型转换 - # 调用中(如 while c.Deref(p) != 0、total += int(c.Deref(p)))不推断, - # 否则 i8* 会被残留 CurAssignAnnot 误判为 i32* 读 4 字节(对齐 TPC - # _infer_deref_target_type 的直接 RHS 检查)。 + # 供裸名泛型构造判定"构造调用是否为赋值直接 RHS":仅该 identity 命中时才用 + # CurAssignAnnot 推断类型实参(对齐 TPC 的直接 RHS 检查)。 CurAssignNode: rawptr[ast.AST] # 当前函数的返回注解节点(translate_function_def 进入函数体前设置,退出时 @@ -254,6 +251,11 @@ class Translator: # 嵌套作用域符号表 SymTab: rawptr[GenTypes.SymbolTable] + # REPL 模式上下文(M2 会话变量):None ⇒ 非 REPL(普通编译路径逐字不变)。 + # 仅 Repl.vp 在翻译片段前置位、翻译后清空;Gen 侧全部会话槽拦截点以 + # `trans.ReplCtx is not None` 为唯一守卫。 + ReplCtx: rawptr[GenTypes.ReplCtx] + def __init__(self): self.Module = None self.Pool = None @@ -293,6 +295,8 @@ class Translator: self.GenericTypeArgs = None self._declare_only = 0 self.SymTab = None + # REPL 模式上下文:默认未设置(= 非 REPL,Gen 侧拦截全部旁路) + self.ReplCtx = None self._async_funcs = None self._async_func_count = 0 self._coro_var_async = None @@ -475,7 +479,7 @@ class Translator: # 检查用户是否定义了 main 函数 has_user_main: int = 0 - ch: rawptr[list[rawptr[ast.AST]]] = tree.children + ch: list[ast.AST*]* = tree.children if ch is not None: cn_count: sizet = len(ch) for ci in range(cn_count): @@ -523,7 +527,7 @@ class Translator: # (前向声明需要解析类型注解,如 t.CArray[str] 依赖 t 模块已导入) # 记录最后处理的 import 行号,供 vpi 注入错误上下文帧定位 cur_imp_lineno: int = 0 - ch: rawptr[list[rawptr[ast.AST]]] = tree.children + ch: list[ast.AST*]* = tree.children if ch is not None: cn_count: sizet = len(ch) for ci in range(cn_count): @@ -711,7 +715,7 @@ class Translator: if fd_s.args is not None: args_s: rawptr[ast.Arguments] = rawptr[ast.Arguments](fd_s.args) if args_s.args is not None: - args_list_s: rawptr[list[rawptr[ast.AST]]] = args_s.args + args_list_s: list[ast.AST*]* = args_s.args an_s: sizet = len(args_list_s) ai_s: sizet for ai_s in range(an_s): @@ -729,7 +733,7 @@ class Translator: # translate_list_literal 阶段 fail-fast 提示 _list 未注册(需显式注解)。 # 递归语句块子节点 if node.children is not None: - ch_s: rawptr[list[rawptr[ast.AST]]] = node.children + ch_s: list[ast.AST*]* = node.children n_s: sizet = len(ch_s) for ci_s in range(n_s): self._scan_container_usage(ch_s.get(ci_s), need_list_box, need_dict_box) diff --git a/App/lib/core/Gen/TranslatorGenMixin.vp b/App/lib/core/Gen/TranslatorGenMixin.vp index 82144ea..3dfe573 100644 --- a/App/lib/core/Gen/TranslatorGenMixin.vp +++ b/App/lib/core/Gen/TranslatorGenMixin.vp @@ -83,7 +83,7 @@ class TranslatorGenMixin: Returns: int: 新增的变量数 """ - ch: rawptr[list[rawptr[ast.AST]]] = tree.children + ch: list[ast.AST*]* = tree.children if ch is None: return 0 @@ -164,7 +164,7 @@ class TranslatorGenMixin: if fd.args is not None: ags: rawptr[ast.Arguments] = rawptr[ast.Arguments](fd.args) if ags.args is not None: - al: rawptr[list[rawptr[ast.AST]]] = ags.args + al: list[ast.AST*]* = ags.args an: sizet = len(al) ai: sizet for ai in range(an): @@ -220,7 +220,7 @@ class TranslatorGenMixin: is_int_elem = 0 # 获取列表元素(通过 ln.elts 而非 list_node.elts,确保类型正确识别) - elts: rawptr[list[rawptr[ast.AST]]] = ln.elts + elts: list[ast.AST*]* = ln.elts if elts is None: return None elts_count: sizet = len(elts) @@ -575,6 +575,81 @@ class TranslatorGenMixin: viperlib.snprintf(buf, body_len + 8, "{ %s }", body) return buf + # ============================================================ + # 模块级全局变量 t.attr.* 属性提取 + # + # 语法:属性写在类型注解的联合里(`T | t.attr.section(".x")`),因为 + # Assign/AnnAssign 节点没有 decorator_list(解析器对 `@c.Attribute(...)` + # 后接赋值会直接报 "decorator must be followed by def/class")。 + # 与函数侧口径差异:函数属性来自 decorator_list,提取为属性组文本 + # `"section"="xxx"`(LLVM 字符串属性必须带引号,走 attributes #N); + # 全局变量属性没有属性组,直接写入 GlobalVariable.Section/Align/Linkage + # 字段,打印时由 includes/llvmlite/__module.vp 的 _print_global 补引号 + # (`, section "xxx"` / `, align N`),故此处 Section 值不带引号。 + # ============================================================ + def _scan_global_tattr(self, node: rawptr[ast.AST], + want_name: str) -> rawptr[ast.AST]: + """在全局变量类型注解中查找 t.attr. 节点。 + + 注解形态是 BinOp(BitOr) 左深链(`A | B | C` = BinOp(BinOp(A,B),C)), + 递归左右子节点。命中返回该 t.attr 节点(Call: t.attr.aligned(16) / + Attribute: t.attr.weak),未命中返回 None。属性名识别复用类侧 + ClassGenBase._tattr_name(Call→func→attr / Attribute→attr)。 + """ + if node is None: + return None + if node.kind() == ast.ASTKind.BinOp: + bop: rawptr[ast.BinOp] = rawptr[ast.BinOp](node) + if bop.op == ast.OpKind.BitOr: + hit_l: rawptr[ast.AST] = self._scan_global_tattr(bop.left, want_name) + if hit_l is not None: + return hit_l + return self._scan_global_tattr(bop.right, want_name) + return None + if self.Gen.ClassGenBase._tattr_name(node, want_name) != 0: + return node + return None + def _extract_global_attr_section(self, node: rawptr[ast.AST], + var_name: str) -> str: + """取 `T | t.attr.section("xxx")` 的段名(不含引号)。 + + 缺参/空段名属非法值 → fail-fast(与 handle_module_level_var 既有 + TypeGenErr.fatal_error 风格一致):空段名会生成 `section ""` 这种 + 无意义/非法的 IR,静默忽略只会掩盖拼写错误。 + """ + hit: rawptr[ast.AST] = self._scan_global_tattr(node, "section") + if hit is None: + return None + sec_val: str = self.Gen.FuncGenBase._get_attr_call_arg_str(hit) + if sec_val is None or sec_val[0] == 0: + self.Gen.TypeGenErr.fatal_error( + node, f"模块级变量 '{var_name}' 的 t.attr.section(...) 段名缺失或为空", + "ValueError") + return None + return sec_val + def _extract_global_attr_aligned(self, node: rawptr[ast.AST], + var_name: str) -> int: + """取 `T | t.attr.aligned(N)` 的对齐字节数(无该属性返回 0)。 + + N 必须是正整数字面量:aligned(0)/aligned(-4)/aligned("x") 均属非法值 + → fail-fast(llc 亦拒绝 `align 0`,静默忽略会掩盖参数错误)。 + """ + hit: rawptr[ast.AST] = self._scan_global_tattr(node, "aligned") + if hit is None: + return 0 + al_val: int = self.Gen.ClassGenBase._tattr_aligned_int(hit) + if al_val <= 0: + self.Gen.TypeGenErr.fatal_error( + node, f"模块级变量 '{var_name}' 的 t.attr.aligned(N) 需要正整数字面量 N", + "ValueError") + return 0 + return al_val + def _extract_global_attr_weak(self, node: rawptr[ast.AST]) -> int: + """检测 `T | t.attr.weak` / `t.attr.weak()`(1=有,0=无)""" + if self._scan_global_tattr(node, "weak") is not None: + return 1 + return 0 + # ============================================================ # handle_module_level_var - 模块级变量声明 → 创建 LLVM 全局变量 # @@ -643,6 +718,28 @@ class TranslatorGenMixin: nm: rawptr[ast.Name] = rawptr[ast.Name](aa.target) var_name = nm.id annot_node = aa.annotation + # t.Callable 模块级函数指针:登记 (变量名 → 返回类型) 签名。此路径原先 + # 只创建全局变量、不登记签名,模块级 t.Callable 变量被间接调用时在 + # CallGenLeafKit._translate_func_ptr_call 报"返回类型未注册(t.Callable + # 注解缺失)"(CallGenLeafKit.vp:843-850)。与函数内 AnnAssign 同一登记函数 + # (AssignGenMixin.vp:1465-1474),参数形态照抄该处: + # (pool, 注解, trans._imported_modules, trans._from_imports, trans)。 + # 时机:_translate_module_level 在 translate_children 之前先遍历全部模块级 + # AnnAssign/Assign(Translator.vp:568-575,其后才是 :578 translate_children), + # 故登记早于本文件任何函数体翻译;签名表本体与返回类型深拷贝都在 PermPool + # (CallGenLeafKit.vp:698-699、:783-788),跨文件查找可见。 + # t.CDefine/t.CTypedef 等分支已在 :675-689 提前返回,行为不变。 + if annot_node is not None: + ml_callable_ret: rawptr[llvmlite.LLVMType] = ( + self.Gen.TypeGenErr.try_extract_callable_ret( + pool, annot_node, trans._imported_modules, + trans._from_imports, trans)) + if ml_callable_ret is not None: + # 返回值:0=成功/同类型幂等命中;非 0 已在 register_func_ptr_sig + # 内部 fatal_error 终止编译,此处分支仅防御(见 AssignGenMixin 同处)。 + if self.Gen.CallGenLeafKit.register_func_ptr_sig( + pool, var_name, ml_callable_ret, aa) != 0: + return 0 # 解析类型 if aa.annotation is not None: resolved: rawptr[llvmlite.LLVMType] = self.Gen.TypeGenResolve.resolve_annotation_type( @@ -711,7 +808,7 @@ class TranslatorGenMixin: asgn: rawptr[ast.Assign] = rawptr[ast.Assign](node) if asgn is None or asgn.targets is None: return 0 - targets: rawptr[list[rawptr[ast.AST]]] = asgn.targets + targets: list[ast.AST*]* = asgn.targets if len(targets) < 1: return 0 t0: rawptr[ast.AST] = targets.get(0) @@ -807,6 +904,16 @@ class TranslatorGenMixin: if annot_node is not None: is_global_cexport = self.Gen.TypeGenErr.has_decorator_marker( annot_node, "CExport", 0) + # 全局变量侧 t.attr.* 属性(`T | t.attr.xxx` 注解联合形态): + # section→gv.Section、aligned→gv.Align、weak→gv.Linkage(见下方应用点)。 + # 仅在 AnnAssign(annot_node 非 None)时存在,与 t.CExport 同一条路。 + gv_attr_section: str = None + gv_attr_align: int = 0 + gv_attr_weak: int = 0 + if annot_node is not None: + gv_attr_section = self._extract_global_attr_section(annot_node, var_name) + gv_attr_align = self._extract_global_attr_aligned(annot_node, var_name) + gv_attr_weak = self._extract_global_attr_weak(annot_node) mangled_name: str = var_name if is_global_cexport == 0: mangled_name = self.Gen.FuncGenBase._mangle_name(trans, var_name) @@ -815,8 +922,14 @@ class TranslatorGenMixin: return 0 llvmlite.module_add_global(mod, gv) + # 段名:直接写字段(_print_global 会补 `, section "xxx"` 的引号)。 + if gv_attr_section is not None: + llvmlite.global_set_section(gv, gv_attr_section) + # B5:@c.Attribute(t.attr.aligned(N)) 类的全局实例施加对齐(如 fpu_context 16 对齐, # FXSAVE 区域要求 16 字节对齐,否则 fxsave/fxrstor #GP)。 + # 优先级:变量级显式 `T | t.attr.aligned(N)` 比类级(struct entry Align)更具体, + # 显式值胜出;无显式属性时沿用类级对齐(既有行为不变)。 al_ty: rawptr[llvmlite.LLVMType] = var_ty match var_ty: case llvmlite.LLVMType.Ptr(pointee_al): @@ -826,11 +939,16 @@ class TranslatorGenMixin: al_entry: rawptr[GenTypes.StructEntry] = None if al_ty is not None and al_ty.kind() == llvmlite.LLVMType.Struct: al_entry = self.Gen.StructGen.find_struct_by_type(al_ty) - if al_entry is not None and al_entry.Align > 0: + if gv_attr_align > 0: + gv.Align = gv_attr_align + elif al_entry is not None and al_entry.Align > 0: gv.Align = al_entry.Align # 设置初始值(有初始值时清除 external linkage,因为 LLVM 22+ 不允许 external global 带初始值) gv.Linkage = None + # t.attr.weak → weak linkage(须在 Linkage 清空之后设置)。 + if gv_attr_weak != 0: + llvmlite.global_set_linkage(gv, "weak") if init_kind == 1: # 整数初始值 init_buf: str = pool.alloc(48) @@ -1015,6 +1133,17 @@ class TranslatorGenMixin: # 注册到模块作用域 if self.Gen.VarGen.define_module_var(trans.SymTab, var_name, gv_ref) == 0: + # t.CVolatile 存储类标记(注解联合含 t.CVolatile)→ 标记全局变量 + # volatile:所有 load/store 带 LLVM volatile,禁止优化器把忙等循环 + # 的重复 load 提升/消除(如 `locked: t.CVolatile | t.CInt`)。 + if annot_node is not None: + if self.Gen.TypeGenErr.has_decorator_marker(annot_node, "CVolatile", 0) != 0: + self.Gen.VarGen.set_var_volatile(trans.SymTab, var_name) + # rawptr[t.CVolatile | T](volatile 指针指向物):解引用(p[i]/ + # p.field/c.Deref(p))的 load/store 带 volatile。与上一分支互斥: + # 前者是变量自身限定(BinOp 联合),后者是 rawptr 下标实参标记。 + if self.Gen.TypeGenErr.has_pointee_volatile_marker(annot_node) != 0: + self.Gen.VarGen.set_var_pointee_volatile(trans.SymTab, var_name) # 设置 AnnotClassName(联合类型如 Logger|t.CPtr 简化为 Ptr(i8) 时, # 属性访问需要通过 AnnotClassName 回退到类名查找结构体) if k == ast.ASTKind.AnnAssign: @@ -1089,7 +1218,7 @@ class TranslatorGenMixin: # 预处理模块级变量:为 AnnAssign/Assign 创建 LLVM 全局变量 # 必须在 pre_scan_allocas 之前执行,否则 PreScan 会创建局部 alloca, # 导致其他函数通过 SSA 编号引用 wrapper main 的局部变量(无效 IR) - ch: rawptr[list[rawptr[ast.AST]]] = tree.children + ch: list[ast.AST*]* = tree.children if ch is not None: cn_count: sizet = len(ch) for ci in range(cn_count): diff --git a/App/lib/core/Gen/Type/TypeGenCDef.vp b/App/lib/core/Gen/Type/TypeGenCDef.vp index dbfa9f2..727823f 100644 --- a/App/lib/core/Gen/Type/TypeGenCDef.vp +++ b/App/lib/core/Gen/Type/TypeGenCDef.vp @@ -120,6 +120,28 @@ class TypeGenCDef(TypeGenBase.TypeGenBase): else: self.CDefineNameIdxBuilt = -1 + # ============================================================ + # CDefine 表本体的池归一 + # + # 表指针挂在**进程级单例** _g_gen.TypeGenCDef 上,不在任何 pool 的管辖 + # 范围内;而调用方传进来的 pool 在翻译期是「每文件 reset_to 回收」的瞬时池 + # (Phase1 1a/1b、Phase2 PhaseA、StubMerger 扫描、Repl 每片段都按 mark/reset + # 轮转)。表本体若落在该池:reset 之后表指针**悬空但非 None** ⇒ `is None` + # 守卫失效 ⇒ 下一次 register_cdefine_* 的 append 写进已回收内存(堆损坏; + # 检测模式下该区被 free 填成 0xCD ⇒ append 的接收者 = 0xcdcdcdcdcdcdcdcd)。 + # 故表本体(四个 list + 名索引)一律落 PermPool(进程级永久池,从不 reset), + # 与"文件级/片段级 reset"彻底解耦。 + # PermPool 不可用(无 CompilerFactory.factory 的极简单元程序)时退回入参 + # pool —— 与既有语义一致,只是不再有"表本体跨 reset 存活"的隐式假设。 + # ============================================================ + def _cdefine_table_pool(self, + pool: rawptr[memhub.MemBuddy]) -> rawptr[memhub.MemBuddy]: + """CDefine 表本体的池:恒取 PermPool(不可用则退回入参 pool)""" + if CompilerFactory.factory is not None \ + and CompilerFactory.factory.PermPool is not None: + return CompilerFactory.factory.PermPool + return pool + def register_cdefine_constant(self, pool: rawptr[memhub.MemBuddy], name: str, value: int64) -> None: """注册 CDefine 编译期常量到本地表 @@ -139,11 +161,29 @@ class TypeGenCDef(TypeGenBase.TypeGenBase): if name is None: return if self.CDefineNames is None: - self.CDefineNames = list[str](pool, 64) - self.CDefineValues = list[str](pool, 64) - self.CDefineDoubleFlags = list[int](pool, 64) - self.CDefineStrFlags = list[int](pool, 64) - self._cdef_idx_init(pool) + tp: rawptr[memhub.MemBuddy] = self._cdefine_table_pool(pool) + tn: list[str] | t.CPtr = list[str](tp, 64) + tv: list[str] | t.CPtr = list[str](tp, 64) + td: list[int] | t.CPtr = list[int](tp, 64) + tsf: list[int] | t.CPtr = list[int](tp, 64) + # 建表失败(池不可用/耗尽)⇒ 明确报错并返回,绝不让下面的 append + # 落到 None 或未初始化地址(原实现无检查 ⇒ append 写进 NULL/0xCD 区崩) + if tn is None or tv is None or td is None or tsf is None: + self.Gen.TypeGenErr.fatal_error( + None, "CDefine 表分配失败(表本体池不可用或耗尽)", "MemoryError") + return + self.CDefineNames = tn + self.CDefineValues = tv + self.CDefineDoubleFlags = td + self.CDefineStrFlags = tsf + self._cdef_idx_init(tp) + # 表就绪守卫:表本体缺失/半初始化(建表失败或外部置空)⇒ fail-fast, + # 不写垃圾(append 之前必有非空断言) + if self.CDefineNames is None or self.CDefineValues is None \ + or self.CDefineDoubleFlags is None or self.CDefineStrFlags is None: + self.Gen.TypeGenErr.fatal_error( + None, "CDefine 表未就绪(CDefineNames/Values/Flags 为空)", "MemoryError") + return name_copy: str = pool.alloc(string.strlen(name) + 1) if name_copy is None: self.Gen.TypeGenErr.fatal_error(None, f"CDefine '{name}' 名称缓冲区分配失败", "MemoryError") @@ -177,11 +217,26 @@ class TypeGenCDef(TypeGenBase.TypeGenBase): if name is None: return if self.CDefineNames is None: - self.CDefineNames = list[str](pool, 64) - self.CDefineValues = list[str](pool, 64) - self.CDefineDoubleFlags = list[int](pool, 64) - self.CDefineStrFlags = list[int](pool, 64) - self._cdef_idx_init(pool) + tp: rawptr[memhub.MemBuddy] = self._cdefine_table_pool(pool) + tn: list[str] | t.CPtr = list[str](tp, 64) + tv: list[str] | t.CPtr = list[str](tp, 64) + td: list[int] | t.CPtr = list[int](tp, 64) + tsf: list[int] | t.CPtr = list[int](tp, 64) + if tn is None or tv is None or td is None or tsf is None: + self.Gen.TypeGenErr.fatal_error( + None, "CDefine 浮点表分配失败(表本体池不可用或耗尽)", "MemoryError") + return + self.CDefineNames = tn + self.CDefineValues = tv + self.CDefineDoubleFlags = td + self.CDefineStrFlags = tsf + self._cdef_idx_init(tp) + # 表就绪守卫(同 register_cdefine_constant):append 之前必有非空断言 + if self.CDefineNames is None or self.CDefineValues is None \ + or self.CDefineDoubleFlags is None or self.CDefineStrFlags is None: + self.Gen.TypeGenErr.fatal_error( + None, "CDefine 浮点表未就绪(CDefineNames/Values/Flags 为空)", "MemoryError") + return name_copy_d: str = pool.alloc(string.strlen(name) + 1) if name_copy_d is None: self.Gen.TypeGenErr.fatal_error(None, f"CDefine 浮点 '{name}' 名称缓冲区分配失败", "MemoryError") @@ -220,11 +275,26 @@ class TypeGenCDef(TypeGenBase.TypeGenBase): self.Gen.TypeGenErr.fatal_error(None, f"CDefine 字符串值缺失: {name}", "InternalCompilerError") return if self.CDefineNames is None: - self.CDefineNames = list[str](pool, 64) - self.CDefineValues = list[str](pool, 64) - self.CDefineDoubleFlags = list[int](pool, 64) - self.CDefineStrFlags = list[int](pool, 64) - self._cdef_idx_init(pool) + tp: rawptr[memhub.MemBuddy] = self._cdefine_table_pool(pool) + tn: list[str] | t.CPtr = list[str](tp, 64) + tv: list[str] | t.CPtr = list[str](tp, 64) + td: list[int] | t.CPtr = list[int](tp, 64) + tsf: list[int] | t.CPtr = list[int](tp, 64) + if tn is None or tv is None or td is None or tsf is None: + self.Gen.TypeGenErr.fatal_error( + None, "CDefine 字符串表分配失败(表本体池不可用或耗尽)", "MemoryError") + return + self.CDefineNames = tn + self.CDefineValues = tv + self.CDefineDoubleFlags = td + self.CDefineStrFlags = tsf + self._cdef_idx_init(tp) + # 表就绪守卫(同 register_cdefine_constant):append 之前必有非空断言 + if self.CDefineNames is None or self.CDefineValues is None \ + or self.CDefineDoubleFlags is None or self.CDefineStrFlags is None: + self.Gen.TypeGenErr.fatal_error( + None, "CDefine 字符串表未就绪(CDefineNames/Values/Flags 为空)", "MemoryError") + return ncopy_s: str = pool.alloc(string.strlen(name) + 1) if ncopy_s is None: self.Gen.TypeGenErr.fatal_error(None, f"CDefine 字符串 '{name}' 名称缓冲区分配失败", "MemoryError") @@ -459,13 +529,33 @@ class TypeGenCDef(TypeGenBase.TypeGenBase): """设置 cdefine_found 标志(供跨模块查找函数使用)""" self.CDefineFound = val def clear_cdefine_constants(self) -> None: - """清空 CDefine 常量表(模块切换时调用)""" - self.CDefineNames = None - self.CDefineValues = None - self.CDefineDoubleFlags = None - self.CDefineStrFlags = None - # 名索引随表复位(下次 register 重建);不同步会使 Built 与空表错配 - self.CDefineNameIdx = None - self.CDefineNameIdxCap = 0 - self.CDefineNameIdxBuilt = -1 + """清空 CDefine 常量表(模块切换时调用) + + 表本体自首次创建起常驻 PermPool(见 _cdefine_table_pool)⇒ 此处只 + **清空内容**、不丢弃表指针: + - 丢指针会退回"下一次 register_cdefine_* 按**调用方 pool** 重建", + 表本体又落回会被 reset_to 回收的瞬时池(冷 temp 下翻译 includes + 时才走到注册路径 ⇒ 表本体跨 reset 悬空 ⇒ append 写进已回收/未 + 初始化内存的根因); + - 空表与既有语义完全等价:lookup 命中空表即"未命中", + register 首次 append 即写回第 0 槽。 + """ + if self.CDefineNames is not None: + self.CDefineNames.clear() + if self.CDefineValues is not None: + self.CDefineValues.clear() + if self.CDefineDoubleFlags is not None: + self.CDefineDoubleFlags.clear() + if self.CDefineStrFlags is not None: + self.CDefineStrFlags.clear() + # 名索引清槽(槽存 index+1,0=空槽)+ Built 归零(空表与索引同步; + # 与"新建表后首次 register"的索引状态逐字一致) + if self.CDefineNameIdx is not None and self.CDefineNameIdxCap > 0: + string.memset(self.CDefineNameIdx, 0, + sizet(self.CDefineNameIdxCap) * 8) + self.CDefineNameIdxBuilt = 0 + else: + self.CDefineNameIdx = None + self.CDefineNameIdxCap = 0 + self.CDefineNameIdxBuilt = -1 self.CDefineIsStr = 0 \ No newline at end of file diff --git a/App/lib/core/Gen/Type/TypeGenCType.vp b/App/lib/core/Gen/Type/TypeGenCType.vp index 12a29e4..9c8bcfb 100644 --- a/App/lib/core/Gen/Type/TypeGenCType.vp +++ b/App/lib/core/Gen/Type/TypeGenCType.vp @@ -121,6 +121,28 @@ class TypeGenCType(TypeGenBase.TypeGenBase): else: self.CTypedefNameIdxBuilt = -1 + # ============================================================ + # CTypedef 表本体的池归一 + # + # 表指针挂在**进程级单例** _g_gen.TypeGenCType 上,不在任何 pool 的管辖 + # 范围内;而调用方传进来的 pool 在翻译期是「每文件 reset_to 回收」的瞬时池 + # (Phase1 1a/1b、Phase2 PhaseA、Repl 每片段/每模块都按 mark/reset 轮转)。 + # 表本体若落在该池:reset 之后表指针**悬空但非 None** ⇒ `is None` 守卫失效 + # ⇒ 下一次 register_ctypedef 的 append 写进已回收内存(堆损坏;检测模式下 + # 该区被 free 填成 0xCD ⇒ append 收到 rdi=0xcdcdcdcdcdcdcdcd 后崩)。 + # 故表本体(表对象 + 名索引 + visiting 表)一律落 PermPool(进程级永久池, + # 从不 reset),与"文件级/片段级 reset"彻底解耦。 + # PermPool 不可用(无 CompilerFactory.factory 的极简单元程序)时退回入参 + # pool —— 与既有语义一致,只是不再有"表本体跨 reset 存活"的隐式假设。 + # ============================================================ + def _ctypedef_table_pool(self, + pool: rawptr[memhub.MemBuddy]) -> rawptr[memhub.MemBuddy]: + """CTypedef 表本体的池:恒取 PermPool(不可用则退回入参 pool)""" + if CompilerFactory.factory is not None \ + and CompilerFactory.factory.PermPool is not None: + return CompilerFactory.factory.PermPool + return pool + def register_ctypedef(self, pool: rawptr[memhub.MemBuddy], name: str, type_str: str) -> None: """注册 CTypedef 到本地表 @@ -152,20 +174,31 @@ class TypeGenCType(TypeGenBase.TypeGenBase): return string.strcpy(type_copy, type_str) if self.CTypedefNames is None: - self.CTypedefNames = list[str](pool, 64) - self.CTypedefTypeStrs = list[str](pool, 64) - # 名索引随表创建(同池;分配失败则标记不可信,查找回退线性扫描) - self._ctypedef_idx_init(pool) + tp: rawptr[memhub.MemBuddy] = self._ctypedef_table_pool(pool) + tn: list[str] | t.CPtr = list[str](tp, 64) + ts: list[str] | t.CPtr = list[str](tp, 64) + # 建表失败(池不可用/耗尽)⇒ 明确报错并返回,绝不让下面的 append + # 落到 None 或未初始化地址(原实现无检查 ⇒ 写进 0xCDCD 区崩) + if tn is None or ts is None: + VLogger.error("CTypedef 表分配失败(表本体池不可用或耗尽)", "TypeGen") + return + self.CTypedefNames = tn + self.CTypedefTypeStrs = ts + # 名索引随表创建(同池 = PermPool;分配失败则标记不可信,查找回退线性扫描) + self._ctypedef_idx_init(tp) + # 表就绪守卫:表本体缺失(建表失败/被外部置空)⇒ fail-fast,不写垃圾 + if self.CTypedefNames is None or self.CTypedefTypeStrs is None: + VLogger.error("CTypedef 表未就绪(CTypedefNames/CTypedefTypeStrs 为空)", "TypeGen") + return # 去重:若已存在同名 CTypedef,更新其类型字符串 - if self.CTypedefNames is not None: - exist_n: sizet = len(self.CTypedefNames) - ei: sizet - for ei in range(exist_n): - en: str = self.CTypedefNames.get(ei) - if en is not None and string.strcmp(en, name_copy) == 0: - # 已存在,更新类型字符串 - self.CTypedefTypeStrs.set(ei, type_copy) - return + exist_n: sizet = len(self.CTypedefNames) + ei: sizet + for ei in range(exist_n): + en: str = self.CTypedefNames.get(ei) + if en is not None and string.strcmp(en, name_copy) == 0: + # 已存在,更新类型字符串 + self.CTypedefTypeStrs.set(ei, type_copy) + return self.CTypedefNames.append(name_copy) self.CTypedefTypeStrs.append(type_copy) # 追加后同步名索引(槽下标 = 追加前 len = 新 len-1) @@ -387,15 +420,34 @@ class TypeGenCType(TypeGenBase.TypeGenBase): return TSpec.TS_SIGN_UNKNOWN return self._spec_sign_of_span(ts, 0, string.strlen(ts), depth + 1) def clear_ctypedefs(self) -> None: - """清空 CTypedef 表(模块切换时调用)""" - self.CTypedefNames = None - self.CTypedefTypeStrs = None - # 名索引随表复位(下次 register 重建);不同步会使 Built 与空表错配 - self.CTypedefNameIdx = None - self.CTypedefNameIdxCap = 0 - self.CTypedefNameIdxBuilt = -1 + """清空 CTypedef 表(模块切换时调用) + + 表本体自首次创建起常驻 PermPool(见 _ctypedef_table_pool)⇒ 此处只 + **清空内容**、不丢弃表指针: + - 丢指针会退回"下一次 register_ctypedef 按**调用方 pool** 重建", + 表本体又落回会被 reset_to 回收的瞬时池(冷 temp 下翻译 includes + 时才走到注册路径 ⇒ 表本体跨 reset 悬空 ⇒ append 写进已回收/未 + 初始化内存的根因); + - 空表与既有语义完全等价:lookup 命中空表即"未命中", + register 首次 append 即写回第 0 槽。 + """ + if self.CTypedefNames is not None: + self.CTypedefNames.clear() + if self.CTypedefTypeStrs is not None: + self.CTypedefTypeStrs.clear() + # 名索引清槽(槽存 index+1,0=空槽)+ Built 归零(空表与索引同步; + # 与"新建表后首次 register"的索引状态逐字一致) + if self.CTypedefNameIdx is not None and self.CTypedefNameIdxCap > 0: + string.memset(self.CTypedefNameIdx, 0, + sizet(self.CTypedefNameIdxCap) * 8) + self.CTypedefNameIdxBuilt = 0 + else: + self.CTypedefNameIdx = None + self.CTypedefNameIdxCap = 0 + self.CTypedefNameIdxBuilt = -1 self.CTypedefResolveDepth = 0 - self.CTypedefVisiting = None + if self.CTypedefVisiting is not None: + self.CTypedefVisiting.clear() # builtins 源扫描标记随表清空复位:每文件最多读一次 includes/builtins.vp # (_lookup_builtins_typedef 的"最后匹配"兜底,见 TypeGenResolve) self.BuiltinsTypedefScanned = 0 @@ -421,7 +473,10 @@ class TypeGenCType(TypeGenBase.TypeGenBase): if name is None: return 0 if self.CTypedefVisiting is None: - self.CTypedefVisiting = list[str](pool, 32) + # 与 CTypedef 表同池(PermPool):clear_ctypedefs 只清空、指针不丢, + # 避免这张表也落在会被 reset_to 回收的调用方池上 + vp: rawptr[memhub.MemBuddy] = self._ctypedef_table_pool(pool) + self.CTypedefVisiting = list[str](vp, 32) if self.CTypedefVisiting is None: return 0 vn: sizet = len(self.CTypedefVisiting) @@ -499,7 +554,7 @@ class TypeGenCType(TypeGenBase.TypeGenBase): """ if pool is None or tree is None: return 0 - ch: rawptr[list[rawptr[ast.AST]]] = tree.children + ch: list[ast.AST*]* = tree.children if ch is None: return 0 n: sizet = len(ch) diff --git a/App/lib/core/Gen/Type/TypeGenErr.vp b/App/lib/core/Gen/Type/TypeGenErr.vp index 42d1fba..84b95ca 100644 --- a/App/lib/core/Gen/Type/TypeGenErr.vp +++ b/App/lib/core/Gen/Type/TypeGenErr.vp @@ -81,9 +81,9 @@ class TypeGenErr(TypeGenBase.TypeGenBase): def dump_parse_traceback(self, file_path: str) -> None: """解析失败时以 Python 风格输出 traceback(仿 fatal_error)""" err_line: int = ast.get_parse_err_line() - err_col: int = ast.get_parse_err_col() - err_msg: str = ast.get_parse_err_msg() - stdio.printf("Traceback (most recent call last):\n") + err_col: int = ast.get_parse_err_col() + err_msg: str = ast.get_parse_err_msg() + stdio.printf("Traceback (most recent call last, compile-time error):\n") if file_path is None or file_path[0] == 0: file_path = "" if err_line > 0: @@ -95,8 +95,7 @@ class TypeGenErr(TypeGenBase.TypeGenBase): # 前缀 4 空格 + err_col 个空格后落点恰好是该 token 首字符) if err_col > 0: stdio.printf(" ") - c: int - for c in range(err_col): + for _ in range(err_col): stdio.printf(" ") stdio.printf("^\n") stdlib.free(src) @@ -112,9 +111,8 @@ class TypeGenErr(TypeGenBase.TypeGenBase): 向后兼容:err_type/col_start/col_end 有默认值,旧调用 fatal_error(node, msg) 仍可用。 """ - stdio.printf("Traceback (most recent call last):\n") + stdio.printf("Traceback (most recent call last, compile-time error):\n") # 输出调用栈帧(从底到顶) - i: int for i in range(self.Gen.TypeGenBase.TraceCount): frame: rawptr[GenTypes.TraceFrame] = self.Gen.TypeGenBase._get_trace_frame(i) if frame is not None and frame.file_name is not None: @@ -176,10 +174,12 @@ class TypeGenErr(TypeGenBase.TypeGenBase): if err_type is None or err_type[0] == 0: err_type = "SyntaxError" stdio.printf("%s: %s\n", err_type, msg) - # 退出前强制刷出:stdout 重定向到文件时是全缓冲,sys.exit 直接终止进程 - # 会把整段 traceback 丢掉(实测 2.log 只剩一行 VLogger 输出)。 + # 退出前强制刷出:stdout 重定向到文件时是全缓冲,直接终止进程会把整段 + # traceback 丢掉(实测 2.log 只剩一行 VLogger 输出)。 stdio.fflush(None) - sys.exit(1) + # 终止点收敛:REPL 会话内经 VLogger.repl_abort longjmp 回 REPL 循环 + # (Traceback 已打印,进程存活);批处理下该出口即 sys.exit(1),逐字不变。 + VLogger.repl_abort() def fatal_type_error(self, node: rawptr[ast.AST], msg: str, col_start: int = 0, col_end: int = 0): @@ -241,7 +241,35 @@ class TypeGenErr(TypeGenBase.TypeGenBase): return 0 return 0 - def is_pass_body(self, body: rawptr[list[rawptr[ast.AST]]]) -> int: + + def has_pointee_volatile_marker(self, node: rawptr[ast.AST]) -> int: + """检测指针注解 rawptr[...] 的下标实参是否含 t.CVolatile 标记,1=是 / 0=否 + + 语义:rawptr[t.CVolatile | T] 等价 C 的 volatile T*,即"指针所指对象按 + volatile 访问"——解引用(p[i] / p.field / c.Deref(p))的 load/store 带 + LLVM volatile。与 has_decorator_marker 的区别:后者检测变量/字段**自身** + 的可见性限定(如 `locked: t.CVolatile | t.CInt`),对 Subscript 节点返回 0; + 本函数专检 rawptr 的 pointee 侧标记(下标实参子树),两者互不串味。 + 标注形态(vast.parse 实证): + rawptr[t.CVolatile | t.CInt] + → Subscript(value=Name('rawptr'), + slice=BinOp(BitOr, Attribute(t,CVolatile), Attribute(t,CInt))) + 多级指针 rawptr[rawptr[t.CVolatile|T]] 的 slice 是 Subscript, + has_decorator_marker 不递归 Subscript ⇒ 返回 0(明确不支持,见限制清单)。 + """ + if node is None or node.kind() != ast.ASTKind.Subscript: + return 0 + sub: rawptr[ast.Subscript] = rawptr[ast.Subscript](node) + if sub.value is None or sub.value.kind() != ast.ASTKind.Name: + return 0 + rp_nm: rawptr[ast.Name] = rawptr[ast.Name](sub.value) + if rp_nm.id is None or string.strcmp(rp_nm.id, "rawptr") != 0: + return 0 + if sub.slice is None: + return 0 + return self.has_decorator_marker(sub.slice, "CVolatile", 0) + + def is_pass_body(self, body: list[ast.AST*]*) -> int: """检测函数体是否为 pass(空体或仅含 pass 语句)""" if body is None: return 1 @@ -319,7 +347,7 @@ class TypeGenErr(TypeGenBase.TypeGenBase): return llvmlite.Int32(pool) def infer_return_type(self, pool: rawptr[memhub.MemBuddy], - body: rawptr[list[rawptr[ast.AST]]], + body: list[ast.AST*]*, param_types: str) -> rawptr[llvmlite.LLVMType]: """扫描函数体中的 return 语句,推断返回类型""" if body is None: @@ -341,7 +369,7 @@ class TypeGenErr(TypeGenBase.TypeGenBase): ret_ty = expr_ty return ret_ty def body_has_value_return(self, pool: rawptr[memhub.MemBuddy], - body: rawptr[list[rawptr[ast.AST]]]) -> int: + body: list[ast.AST*]*) -> int: """扫描函数体,判断是否存在返回非 None 值的 return 递归检查控制流语句(if/while/for/try/with/match)内层 return,但跳过 @@ -373,7 +401,7 @@ class TypeGenErr(TypeGenBase.TypeGenBase): elif k == ast.ASTKind.If or k == ast.ASTKind.While or \ k == ast.ASTKind.For or k == ast.ASTKind.Try or \ k == ast.ASTKind.With or k == ast.ASTKind.Match: - sub: rawptr[list[rawptr[ast.AST]]] = stmt.children + sub: list[ast.AST*]* = stmt.children if sub is not None and len(sub) > 0: if self.body_has_value_return(pool, sub) != 0: return 1 @@ -395,7 +423,7 @@ class TypeGenErr(TypeGenBase.TypeGenBase): ags: rawptr[ast.Arguments] = rawptr[ast.Arguments](args_node) if ags.args is None: return None - alist: rawptr[list[rawptr[ast.AST]]] = ags.args + alist: list[ast.AST*]* = ags.args an: sizet = len(alist) if an == 0: return None diff --git a/App/lib/core/Gen/Type/TypeGenResolve.vp b/App/lib/core/Gen/Type/TypeGenResolve.vp index 8096e30..29c95dd 100644 --- a/App/lib/core/Gen/Type/TypeGenResolve.vp +++ b/App/lib/core/Gen/Type/TypeGenResolve.vp @@ -17,6 +17,7 @@ import lib.core.VLogger as VLogger import lib.core.Gen.Translator as HT import lib.Stub.StubMerger as StubMerger import lib.Stub.StubMerger.StubMergerStore as StubMergerStore +import lib.Stub.StubGen.VpiReader as VpiReader import lib.core.CompilerFactory as CompilerFactory import lib.Projectrans.Config as Config @@ -151,7 +152,7 @@ class TypeGenResolve(TypeGenBase.TypeGenBase): return self._list_elts_to_type_str(pool, tup.elts, "", "") return None def _list_elts_to_type_str(self, pool: rawptr[memhub.MemBuddy], - elts: rawptr[list[rawptr[ast.AST]]], + elts: list[ast.AST*]*, open_ch: str, close_ch: str) -> str: """元素列表转逗号分隔字符串(List/Tuple 共用),包上 open/close 括号 @@ -230,6 +231,42 @@ class TypeGenResolve(TypeGenBase.TypeGenBase): string.strcat(buf, ".") string.strcat(buf, cls_name) return buf + def _repl_opaque_class_ty(self, pool: rawptr[memhub.MemBuddy], + mod_sha1: str, + cls_name: str) -> rawptr[llvmlite.LLVMType]: + """(REPL 会话专用)未登记类别的"仅类型"占位:`%"{sha1}.{类名}"` opaque 具名结构体 + + 背景:REPL 的 includes 类注册(M4.2)只登记**布局可信**的类;缺 `.stub.ll` + 的模块其带父类/虚表的类被拒 ⇒ 该类的类型不在会话 StructTable 里。而其它模块 + 的 `.vpi` 一旦在形参/返回/字段注解里引用它(如 + `def __new__(self, pool: rawptr[memhub.MemManager])`),注入侧的 S4 + fail-fast(`VpiReader._inject_method_declare` 的"类型注解解析失败")就会 + 中断**整个模块**的 vpi 注入——报错点在该注解,真因是"被引类未登记"。 + + 本函数按定义侧命名(`{sha1}.{类名}`,与 ClassGenDef / ClassGenVTable 逐字 + 一致)造 opaque 具名结构体:指针引用与宿主签名逐字兼容(`%"{sha1}.{类名}"*`), + 且**不写 StructTable** ⇒ `lookup_field_by_class`/`find_struct_by_module` + 仍按"类未注册"明确报错,绝不按(未知的)字段偏移静默访问(M4.2 的 + "宁可报错"口径不变)。 + + 调用面严格限定在 `trans.ReplCtx != None`(REPL 会话)且**模块已知** + (sha1 已由 `StubMergerStore` 解析出)的路径:批处理 `trans.ReplCtx` + 恒 None ⇒ 本函数不被调用,批处理逐字不变;模块名未登记时调用方不接受 + 本兜底(那是真实的"未编译/未扫描"错误,仍 fail-fast)。 + """ + if pool is None or mod_sha1 is None or cls_name is None: + return None + if mod_sha1[0] == '\0' or cls_name[0] == '\0': + return None + full: str = self._concat_sha1_class(pool, mod_sha1, cls_name) + if full is None: + return None + ex: rawptr[llvmlite.LLVMType] = self.Gen.StructGen.get_struct_type(full) + if ex is not None: + return ex + # fcount=-1 = opaque 哨兵(区别于空结构体 {}:后者 fcount==0 是"完整定义、 + # 可 alloca");与 llvmlite 的跨模块 opaque 声明同一表示。 + return llvmlite.Struct(pool, None, -1, full) def _norm_type_arg_name(self, pool: rawptr[memhub.MemBuddy], type_name: str, from_imports: str) -> str: @@ -610,7 +647,7 @@ class TypeGenResolve(TypeGenBase.TypeGenBase): # parse_tokens 成功,故此处用文件级 parse 兜底) tree_s: rawptr[ast.AST] = ast.parse(type_str, pool) if tree_s is not None: - ch_s: rawptr[list[rawptr[ast.AST]]] = tree_s.children + ch_s: list[ast.AST*]* = tree_s.children if ch_s is not None and len(ch_s) > 0: st0: rawptr[ast.AST] = ch_s.get(0) if st0 is not None and st0.kind() == ast.ASTKind.Expr: @@ -1766,7 +1803,7 @@ class TypeGenResolve(TypeGenBase.TypeGenBase): # Tuple slice: dict[str, int] → "str,int" sub_tup: rawptr[ast.Tuple] = rawptr[ast.Tuple](sub.slice) if sub_tup.elts is not None: - tup_elts_sn: rawptr[list[rawptr[ast.AST]]] = sub_tup.elts + tup_elts_sn: list[ast.AST*]* = sub_tup.elts tn_sn: sizet = len(tup_elts_sn) # 计算总长度 tup_total: sizet = 1 @@ -1827,7 +1864,7 @@ class TypeGenResolve(TypeGenBase.TypeGenBase): # 用于函数 `-> tuple[str, t.CUnsignedShort]`(如 binascii.b2a_hqx) # 与调用端 `a, b = f()` 的打包/解包。 if string.strcmp(gen_cls_name, "tuple") == 0: - tp_elts: rawptr[list[rawptr[ast.AST]]] = None + tp_elts: list[ast.AST*]* = None tp_count: sizet = 0 if sub.slice.kind() == ast.ASTKind.Tuple: tp_tup: rawptr[ast.Tuple] = rawptr[ast.Tuple](sub.slice) @@ -1837,7 +1874,7 @@ class TypeGenResolve(TypeGenBase.TypeGenBase): else: # [FIX] elem_size=8(rawptr[ast.AST] 指针 8 字节),原传 1 → get 只读 # 1 字节拼指针(高 56 位残留)→ tuple 元素解析垃圾 - tp_single: rawptr[list[rawptr[ast.AST]]] = list[rawptr[ast.AST]](pool, 8) + tp_single: list[ast.AST*]* = list[rawptr[ast.AST]](pool, 8) if tp_single is not None: tp_single.append(sub.slice) tp_elts = tp_single @@ -1870,7 +1907,7 @@ class TypeGenResolve(TypeGenBase.TypeGenBase): gen_arg_names: list[str] | t.CPtr = list[str](pool, 8) if sub.slice.kind() == ast.ASTKind.Tuple: gen_tp: rawptr[ast.Tuple] = rawptr[ast.Tuple](sub.slice) - gen_elts: rawptr[list[rawptr[ast.AST]]] = gen_tp.elts + gen_elts: list[ast.AST*]* = gen_tp.elts if gen_elts is not None: gen_en: sizet = len(gen_elts) gen_ei: sizet @@ -1909,6 +1946,45 @@ class TypeGenResolve(TypeGenBase.TypeGenBase): if gen_atn is not None: string.strcat(gen_mangled, gen_atn) string.strcat(gen_mangled, "]") + # [REPL 注入期] 只做类型/签名层面的事,**绝不触发模板体翻译**(特化延后)。 + # + # 现场:Repl 会话初始化先**真注册**了 includes 泛型模板(Repl.vp 步骤③''), + # 随后逐模块注入 .vpi(ReplStubLayoutBegin → ReplInitIncludesClasses,逐模块 + # _setjmp)。注入的只是类字段/方法**签名**,此刻会话 StructTable 只含"已注入 + # 模块的已注册类":模板体引用的**跨模块**类型(_list.vp 的 memhub.MemManager) + # 与**同模块**类型(linkedlist.vp 的 NameEntry)都可能尚未注册(模板是全局 + # 预注册的,其归属模块未必已注入、同模块兄弟类也未必已注册)。 + # 故一旦在此特化,_specialize_generic_class 就会 translate_class_def 翻译模板 + # 类体源码(字段/方法注解解析)⇒ 命中未注册类型 ⇒ lookup_field_by_class / + # 形参注解解析失败 fatal ⇒ 该模块整个 .vpi 注入中断(异常被逐模块 _setjmp + # 隔离,但该模块的类全部注册不上 + Traceback 刷屏)。 + # 结论:注入期**不特化**,直接返回**可用的 opaque 特化名** + # (`%"{模板模块 sha1}.{mangled}"`,与定义侧特化命名逐字一致)——注解解析 + # 成功、不触碰任何未注册类型、不写 StructTable(字段访问照旧按"类未注册" + # 明确报错)。真正需要布局/方法时在**片段期**走下方既有特化路径:ReplCtx + # 仍在但 ReplStubLayoutMode 已归零 ⇒ 本分支不经过,特化照常发生(届时按需 + # 注册的依赖已就绪;确实缺失则明确报错,落在片段内、不影响启动)。 + # 批处理 ReplCtx 恒 None 且 ReplStubLayoutMode 恒 0 ⇒ 本分支不经过,逐字不变。 + if trans is not None and trans.ReplCtx is not None \ + and VpiReader.ReplStubLayoutMode != 0: + # 已登记的特化结构体(会话期先前注册)直接复用:本分支只读、不翻译 + spec_ty_inj: rawptr[llvmlite.LLVMType] = self.Gen.StructGen.get_struct_type(gen_mangled) + if spec_ty_inj is not None: + return spec_ty_inj + # 归属模块 sha1:优先取**全局模板表**(步骤③'' 已按 includes 全量注册, + # 与模块注入顺序无关 ⇒ 跨模块泛型如 GSList 也能拼出定义侧特化名); + # 退回注入期逐模块登记的归属表(StubMergerStore.RegisterGenericOwner)。 + inj_owner: str = self.Gen.ClassGenGeneric._find_generic_template_sha1(gen_cls_name) + if inj_owner is None: + inj_owner = StubMergerStore.LookupGenericOwnerSha1(gen_cls_name) + if inj_owner is not None: + ty_inj: rawptr[llvmlite.LLVMType] = self._repl_opaque_class_ty( + pool, inj_owner, gen_mangled) + if ty_inj is not None: + return ty_inj + # 归属未知(模板/归属均未登记)⇒ 保持原口径返回 None,由上层按 + # "注解解析失败"明确报错(不伪造无模块前缀的类名) + return None # 查找已特化的 struct(特化由实例化调用 list[int](pool) 触发) gen_spec_ty: rawptr[llvmlite.LLVMType] = self.Gen.StructGen.get_struct_type(gen_mangled) if gen_spec_ty is not None: @@ -1971,6 +2047,26 @@ class TypeGenResolve(TypeGenBase.TypeGenBase): f"泛型下标 '{gen_mangled}' 未解析出特化结构体(泛型模板='{gen_cls_name}');" f"该注解将退化为裸基类/i8* → 后续 .get() 按错误元素类型分派并截断指针", "TypeError") + # [REPL 会话] includes 泛型模板(list/dict/Vector/…)在会话里**未注册**:会话 + # 类注册只注入 `.vpi` 的类(泛型类被跳过),不跑批处理的 + # _PreRegister*GenericTemplates ⇒ 泛型下标的特化恒 miss。若该注解位于形参/ + # 返回位置,S4 fail-fast 会中断**整个模块**的 `.vpi` 注入(实测:93da… 的 + # `targets: rawptr[list[rawptr[AST]]]`;9 个 `.vpi` / 78 条 def 签名行含 + # list[/dict[,其中 93da… 30 条)。故按定义侧特化命名 + # "%{模板模块 sha1}.{mangled}" 造 opaque 具名类型占位(实证 + # `%"b56d2f995104690d.list[rawptr[AST]]"`):方法调用按类名仍可解析(符号在 + # 宿主里),字段访问按"类未注册/字段未找到"明确报错(不静默按错偏移访问)。 + # 模板模块 sha1 取**登记事实**(VpiReader 注入期扫 ClassDef 登记,见 + # StubMergerStore.RegisterGenericOwner),不按名字猜。 + # 批处理 trans.ReplCtx 恒 None ⇒ 不经过;本分支只在上述**全部**查找(含特化 + # 触发)失败后到达 ⇒ 不改变任何既有成功路径。 + if trans is not None and trans.ReplCtx is not None: + gowner_sha1: str = StubMergerStore.LookupGenericOwnerSha1(gen_cls_name) + if gowner_sha1 is not None: + ty_repl_g: rawptr[llvmlite.LLVMType] = self._repl_opaque_class_ty( + pool, gowner_sha1, gen_mangled) + if ty_repl_g is not None: + return ty_repl_g # 未特化:返回 None(不报错,让后续流程处理) return None def resolve_annotation_type(self, pool: rawptr[memhub.MemBuddy], @@ -2134,12 +2230,14 @@ class TypeGenResolve(TypeGenBase.TypeGenBase): # trans.ModuleSha1 是"正在翻译的文件",而注解里的类归属 stub 源模块 # (from_imports 即该 stub 的名字→模块映射)。仅靠"当前模块 sha1 + 全局可见性" # 在 App 严格模式下会查不到(如 ast.stmts.Expression 经 ast.__init__.vpi 注入)。 + fi_sha1_rn: str = None if trans is not None and from_imports is not None: fi_mod: str = StubMergerStore.LookupModNameByAlias(from_imports, nm.id) if fi_mod is not None: fi_sha1: str = StubMergerStore.LookupSha1ByModName(fi_mod) if fi_sha1 is None: fi_sha1 = StubMergerStore.LookupSha1ByModNameSuffix(fi_mod) + fi_sha1_rn = fi_sha1 stdlib.free(fi_mod) if fi_sha1 is not None: fi_entry: rawptr[GenTypes.StructEntry] = self.Gen.StructGen.find_struct_by_module(nm.id, fi_sha1) @@ -2237,6 +2335,29 @@ class TypeGenResolve(TypeGenBase.TypeGenBase): pool, lib_node_n, imported_modules, from_imports, trans, depth + 1) if lib_ty_n is not None: return lib_ty_n + # [REPL 会话] 归属模块已知(from_imports 解析出 sha1;或本模块自身 = + # 注入期 trans.ModuleSha1 = 当前 stub 的源模块 sha1)但该类未在会话 + # StructTable 登记(M4.2 布局不可信被拒 / 字段类型不可解析被跳过)⇒ + # 用 opaque 具名类型占位,使该注解可解析(不写 StructTable,字段访问 + # 仍按"类未注册"明确报错)。批处理 trans.ReplCtx 恒 None ⇒ 不经过; + # 本分支在上述全部查找失败后才到达 ⇒ 不改变任何既有成功路径。 + if trans is not None and trans.ReplCtx is not None: + own_sha_rn: str = fi_sha1_rn + # 本模块裸类名:仅当 trans.ModuleSha1 是"当前 stub 的源模块 sha1"时可用 + # (REPL 会话初始化注入期由 ReplInitIncludesClasses 逐模块设置)。 + # 片段翻译期 trans.ModuleSha1 = 会话 sha1(GenTypes.REPL_SESSION_SHA1): + # 此刻裸名找不到归属就**不能**拿会话 sha1 当归属(会造出 + # "%{会话sha1}.{类名}" 这种宿主里不存在的类名 → 方法调用符号名误导), + # 故排除该情形,保持原 fail-fast。 + if own_sha_rn is None: + if trans.ModuleSha1 is not None \ + and string.strcmp(trans.ModuleSha1, GenTypes.REPL_SESSION_SHA1) != 0: + own_sha_rn = trans.ModuleSha1 + if own_sha_rn is not None: + ty_repl_n: rawptr[llvmlite.LLVMType] = self._repl_opaque_class_ty( + pool, own_sha_rn, nm.id) + if ty_repl_n is not None: + return ty_repl_n return None # Attribute 节点: int, uint64, namespace_defs.PlainStruct 等 @@ -2306,9 +2427,14 @@ class TypeGenResolve(TypeGenBase.TypeGenBase): # SHA1 前缀形态优先用完整名 "{sha1}.{Class}" 精确命中定义侧 # struct(get_struct_type 类名回退可能命中跨模块同名类) full_attr_ty: rawptr[llvmlite.LLVMType] = None + # mod_sha1_rv:把本分支解析出的"模块段 sha1"带到末尾(REPL 会话的 + # "仅类型"占位兜底要用它;声明在此以覆盖下方 SHA1 前缀形态与枚举/ + # typedef 尝试都在同一作用域)。 + mod_sha1_rv: str = None # val_name.id 是模块名段:恰为 16 位小写 hex 才是 SHA1 别名 #(判定走唯一真源 GenApi.is_sha1_hex16,C10 收敛)。 if is_builtin_attr == 0 and GenApi.is_sha1_hex16(val_name.id) != 0: + mod_sha1_rv = val_name.id full_q: str = self._concat_sha1_class(pool, val_name.id, at.attr) if full_q is not None: full_attr_ty = self.Gen.StructGen.get_struct_type(full_q) @@ -2324,6 +2450,7 @@ class TypeGenResolve(TypeGenBase.TypeGenBase): # StructGenMixin._resolve_module_sha1 逐级尝试) mod_sha1_v: str = self.Gen.StructGen._resolve_module_sha1( val_name.id, from_imports) + mod_sha1_rv = mod_sha1_v if mod_sha1_v is not None: full_q_v: str = self._concat_sha1_class(pool, mod_sha1_v, at.attr) if full_q_v is not None: @@ -2366,6 +2493,15 @@ class TypeGenResolve(TypeGenBase.TypeGenBase): return mod_td_ty else: self.Gen.TypeGenCType._ctypedef_pop_visiting() + # [REPL 会话] 模块限定名(memhub.MemManager 等)的归属模块已知, + # 但该类未在会话 StructTable 登记(M4.2 布局不可信被拒 / 字段类型 + # 不可解析被跳过)⇒ 用 opaque 具名类型占位(不写 StructTable ⇒ + # 字段访问仍按"类未注册"明确报错)。批处理 trans.ReplCtx 恒 None。 + if trans is not None and trans.ReplCtx is not None and mod_sha1_rv is not None: + ty_repl_a: rawptr[llvmlite.LLVMType] = self._repl_opaque_class_ty( + pool, mod_sha1_rv, at.attr) + if ty_repl_a is not None: + return ty_repl_a return None # 嵌套 Attribute: w32.win32base.SECURITY_ATTRIBUTES 等三级及以上限定名 # at.value 是 Attribute 而非 Name,递归提取最终类名(at.attr)查结构体表 @@ -2755,7 +2891,7 @@ class TypeGenResolve(TypeGenBase.TypeGenBase): tup: rawptr[ast.Tuple] = rawptr[ast.Tuple](sub.slice) if tup.elts is None: self.Gen.TypeGenErr.fatal_type_error(node, "t.CArray Tuple 缺少 elts") - elts_list: rawptr[list[rawptr[ast.AST]]] = tup.elts + elts_list: list[ast.AST*]* = tup.elts elts_n: sizet = len(elts_list) if elts_n < 2: self.Gen.TypeGenErr.fatal_type_error(node, "t.CArray Tuple 元素不足") diff --git a/App/lib/core/Gen/VarGenMixin.vp b/App/lib/core/Gen/VarGenMixin.vp index e66bc91..daa490c 100644 --- a/App/lib/core/Gen/VarGenMixin.vp +++ b/App/lib/core/Gen/VarGenMixin.vp @@ -1,8 +1,10 @@ import lib.core.Gen.GenTypes as GenTypes import lib.core.Gen.Generator as Generator import lib.core.Gen.Type.TypeSpec as TSpec +import lib.core.Gen.Translator as HT import t, c from stdint import * +import ast import llvmlite import memhub import string @@ -205,6 +207,51 @@ class VarGenMixin: return 1 return 0 + # set_var_volatile — 标记变量为 volatile(注解联合含 t.CVolatile) + # 语义对齐 C 的 volatile 限定:该变量的每次 load/store 都带 LLVM volatile, + # 禁止优化器把忙等循环中的重复 load 提升/消除。由注解侧 + # (模块级已注册后 / 局部声明时)调用。 + def set_var_volatile(self, symtab: rawptr[GenTypes.SymbolTable], + name: str) -> int: + """标记变量为 volatile(VarEntry.IsVolatile=1),返回 0=成功 / 1=失败""" + if symtab is None or name is None: + return 1 + entry: rawptr[GenTypes.VarEntry] = self.lookup_var_entry(symtab, name) + if entry is None: + return 1 + entry.IsVolatile = 1 + return 0 + + # var_is_volatile — 变量是否 volatile(读路径/写路径统一入口) + def var_is_volatile(self, entry: rawptr[GenTypes.VarEntry]) -> int: + """变量条目是否 volatile(1=是 / 0=否;entry 为 None 时 0)""" + if entry is None: + return 0 + return entry.IsVolatile + + # set_var_pointee_volatile — 标记"指针所指物"为 volatile + # (变量注解为 rawptr[t.CVolatile | T],等价 C 的 volatile T*)。 + # 语义对齐 C 的 volatile 限定(作用于 pointee 而非指针自身):解引用 + # (p[i] / p.field / c.Deref(p))的每次 load/store 都带 LLVM volatile, + # 禁止优化器重排序/消除对所指对象的访问。由注解侧在登记变量时调用。 + def set_var_pointee_volatile(self, symtab: rawptr[GenTypes.SymbolTable], + name: str) -> int: + """标记变量所指物为 volatile(VarEntry.PointeeVolatile=1),返回 0=成功 / 1=失败""" + if symtab is None or name is None: + return 1 + entry: rawptr[GenTypes.VarEntry] = self.lookup_var_entry(symtab, name) + if entry is None: + return 1 + entry.PointeeVolatile = 1 + return 0 + + # var_pointee_is_volatile — 变量所指物是否 volatile(解引用读写统一入口) + def var_pointee_is_volatile(self, entry: rawptr[GenTypes.VarEntry]) -> int: + """变量条目所指物是否 volatile(1=是 / 0=否;entry 为 None 时 0)""" + if entry is None: + return 0 + return entry.PointeeVolatile + # ============================================================ # add_var — 添加变量到变量表 # ============================================================ @@ -504,3 +551,1153 @@ class VarGenMixin: f"变量 '{name}' 注册失败(作用域变量数超过上限 MAX_VARS)", "InternalCompilerError") return alloca + + # ============================================================ + # REPL 模式(M2)—— 会话变量槽位读写 + # + # 前置:trans.ReplCtx != None(仅 Repl.vp 翻译片段前置位、翻译后清空)。 + # 下面所有函数都只被带该守卫的调用点进入 ⇒ 普通编译路径不经过任何一行。 + # + # ctx 布局(详见 GenTypes 顶部注释):片段函数首形参是 ctx(i8*,注解 bytes), + # 会话变量 x 的槽位于 ctx + x.Offset(= REPL_SLOT_BASE + i*REPL_SLOT_SIZE)。 + # 槽内存放的是**值本身**(标量/指针/≤16B 结构体值内联)⇒ 宿主不需要知道类型, + # 且槽地址即值地址(c.Addr 的地址同一性)。 + # ============================================================ + + # ---- repl_find_entry — 会话表按名查找 ---- + def repl_find_entry(self, ctx: rawptr[GenTypes.ReplCtx], + name: str) -> rawptr[GenTypes.ReplVarEntry]: + """在会话表中按名查找条目;ctx 无效或未登记返回 None""" + if ctx is None or name is None: + return None + # 魔数校验:防止把非 ReplCtx 的指针当上下文用(宁可不拦截也不乱拦截) + if ctx.Marker != GenTypes.REPL_CTX_MARKER: + return None + if ctx.Table is None or ctx.Count <= 0: + return None + entry_size: sizet = GenTypes.ReplVarEntry.__sizeof__() + for i in range(ctx.Count): + entry_addr: uint64 = uint64(ctx.Table) + i * entry_size + entry: rawptr[GenTypes.ReplVarEntry] = rawptr[GenTypes.ReplVarEntry](entry_addr) + if entry.Name is not None and string.strcmp(entry.Name, name) == 0: + return entry + return None + + # ---- repl_in_frag_scope — 当前最近函数作用域是否就是片段函数本体 ---- + def repl_in_frag_scope(self, symtab: rawptr[GenTypes.SymbolTable]) -> int: + """1=当前最近函数作用域即片段函数本体(其父为模块根);0=其他 + + 拦截只在片段函数本体生效:片段自己声明的局部名/参数、片段内嵌套函数体 + (其函数作用域的父不是模块根)一律走原路径。if/while 分支不建新作用域, + 故分支内的赋值同样命中(整行片段语义)。 + """ + if symtab is None or symtab.Current is None or symtab.Root is None: + return 0 + scope: rawptr[GenTypes.Scope] = symtab.Current + while scope is not None: + if scope.Kind == SCOPE_FUNCTION: + if scope.Parent is not None and uint64(scope.Parent) == uint64(symtab.Root): + return 1 + return 0 + scope = scope.Parent + return 0 + + # ---- repl_ctx_ptr — 取片段函数 ctx 形参的值(i8*) ---- + def repl_ctx_ptr(self, trans: rawptr[HT.Translator], + ctx: rawptr[GenTypes.ReplCtx], + node: rawptr[ast.AST]) -> rawptr[llvmlite.Value]: + """load 片段函数 ctx 形参(形参 alloca 由 FuncGenDef 建);失败 fail-fast""" + if trans is None or ctx is None or ctx.CtxParam is None: + return None + symtab: rawptr[GenTypes.SymbolTable] = trans.SymTab + param_alloca: rawptr[llvmlite.Value] = self.lookup_var(symtab, ctx.CtxParam) + if param_alloca is None or param_alloca.Ty is None or param_alloca.Ty.Pointee is None: + self.Gen.TypeGenErr.fatal_error( + node, + f"REPL: 片段函数缺少 ctx 形参 '{ctx.CtxParam}'(包裹模板与 M2 调用约定不一致)", + "InternalCompilerError") + return None + return llvmlite.build_load(trans._cur_builder, param_alloca.Ty.Pointee, param_alloca) + + # ---- repl_in_frag_entry_scope — 当前最近函数作用域是否就是**片段入口**函数 ---- + # + # 与 repl_in_frag_scope 的差别(M3 引入):被提升的会话函数(片段里顶层 def 提升 + # 到模块级的 export def)在作用域上同样是"父为模块根的函数作用域",故 + # repl_in_frag_scope 对其返回 1;但它**不是**片段入口函数。 + # - 读路径(会话变量取值/取址/会话函数调用):两者都允许 ⇒ 用 repl_in_frag_scope + # (会话函数体里能读到会话变量,这是 M3 的核心能力); + # - 写路径(会话槽位分配/赋值/增强赋值/注解赋值):只允许片段入口函数 ⇒ 用本 + # 谓词。理由(正确性,不是风格): + # ① 会话函数体里的 `y = 1` 应是**局部**变量(Python 语义),若走会话槽 + # 会把局部名泄漏成全局、并使递归/重入共享同一槽; + # ② 形参同名遮蔽最危险:`x = 1` 建立会话变量 x 后,`def f(x: int): x = x + 1` + # 若走会话槽会把实参写进**会话变量 x**(静默数据串味); + # ③ 片段入口函数是唯一"用户一整行/一整块"的语义载体 ⇒ 与 M2 行为一致。 + # 判定:片段入口函数名由宿主生成,恒以 GenTypes.REPL_ENTRY_PREFIX 开头 + # (Repl.vp 的 "__repl_frag_%d")。非 REPL 路径 _cur_func 无该前缀 ⇒ 恒 0。 + def repl_in_frag_entry_scope(self, trans: rawptr[HT.Translator]) -> int: + """1=当前正在翻译片段入口函数本体;0=其他(含被提升的会话函数本体)""" + if trans is None or trans._cur_func is None: + return 0 + if self.repl_in_frag_scope(trans.SymTab) == 0: + return 0 + cur_nm: str = llvmlite.function_get_name(trans._cur_func) + if cur_nm is None: + return 0 + pfx: str = GenTypes.REPL_ENTRY_PREFIX + pl: sizet = string.strlen(pfx) + if string.strncmp(cur_nm, pfx, pl) != 0: + return 0 + return 1 + + # ---- repl_find_func_entry — 会话函数表按名查找(M3) ---- + def repl_find_func_entry(self, ctx: rawptr[GenTypes.ReplCtx], + name: str) -> rawptr[GenTypes.ReplFuncEntry]: + """在会话函数表中按名查找;ctx 无效/未登记返回 None(只读,不改表)""" + if ctx is None or name is None: + return None + if ctx.Marker != GenTypes.REPL_CTX_MARKER: + return None + if ctx.FuncTable is None or ctx.FuncCount <= 0: + return None + entry_size: sizet = GenTypes.ReplFuncEntry.__sizeof__() + for i in range(ctx.FuncCount): + entry_addr: uint64 = uint64(ctx.FuncTable) + i * entry_size + entry: rawptr[GenTypes.ReplFuncEntry] = rawptr[GenTypes.ReplFuncEntry](entry_addr) + if entry.Name is not None and string.strcmp(entry.Name, name) == 0: + return entry + return None + + # ---- repl_func_ty — 签名 token → LLVM 类型(M3) ---- + # + # token 由宿主在注册期从定义片段的 IR define 行解析而来(唯一 ABI 真相), + # 只允许可往返重建的集合:iN(N=1/8/16/32/64) / float / double / i8*。 + # 其余 token(含 REPL_TY_PTR / REPL_TY_STRUCT)返回 None ⇒ 调用点明确报错。 + def repl_func_ty(self, pool: rawptr[memhub.MemBuddy], + tag: int, bits: int) -> rawptr[llvmlite.LLVMType]: + """签名 token → LLVM 类型;不支持的 token 返回 None""" + if pool is None: + return None + if tag == GenTypes.REPL_TY_INT: + return self.repl_scalar_ty(pool, bits, 0) + if tag == GenTypes.REPL_TY_FLOAT: + return self.repl_scalar_ty(pool, bits, 1) + if tag == GenTypes.REPL_TY_STR: + return llvmlite.Ptr(pool, llvmlite.Int8(pool)) + return None + + # ---- repl_func_sig_label — 签名 token → 人类可读类型名(错误消息用) ---- + def repl_func_sig_label(self, tag: int, bits: int) -> str: + """签名 token → 显示名;未识别返回 "?"(VOID 显示为 void)""" + if tag == GenTypes.REPL_TY_VOID: + return "void" + return self.repl_tag_label(tag, bits) + + # ---- repl_try_session_func_call — 会话函数间接调用(M3 核心) ---- + # + # 命中(名字在会话函数表且当前在片段函数作用域内)时: + # ① 从 ctx 的函数指针槽(ctx + SlotOff,i8**)load 出函数地址(i8*); + # ② 首实参恒为本片段的 ctx(i8*)——被提升的会话函数首形参即 ctx, + # 其体内经该指针读写会话变量槽(与片段入口函数同一套寻址); + # ③ 其余实参与定义端签名逐个对齐(translate_value 带 target_ty + coerce_to_type); + # ④ 按表内签名(ret + (i8*, P1..Pn))bitcast 后 build_call_indirect。 + # 语义要点:函数**代码**只在定义片段的 DLL 里存在一份,调用点不复制/不重编译; + # 表内签名即定义端 ABI(宿主从 define 行解析)⇒ 调用点签名与定义端恒一致。 + # + # hit 是宿主调用方提供的 int 盒子(stdlib.malloc(4) 风格,与 translate_call 里 + # h1_hit 同款):命中后(含命中但报错)置 1,使调用方"已处理"而不是继续走 + # "裸名调用未定义且未导入"的 fail-fast 或搭上别的解析路径。 + # 守卫:调用方只在 trans.ReplCtx is not None 时调用;非 REPL 恒不进入。 + def repl_try_session_func_call(self, trans: rawptr[HT.Translator], + pool: rawptr[memhub.MemBuddy], + builder: rawptr[llvmlite.IRBuilder], + mod: rawptr[llvmlite.LLVMModule], + func_name: str, + cargs: list[ast.AST*]*, + can: int, + kw_count: int, + node: rawptr[ast.AST], + hit: t.CPtr) -> rawptr[llvmlite.Value]: + """会话函数调用:命中返回 call 结果(void 函数返回 None 但 hit 已置 1)""" + if trans is None or trans.ReplCtx is None or pool is None or builder is None: + return None + if func_name is None or hit is None: + return None + ctx: rawptr[GenTypes.ReplCtx] = trans.ReplCtx + entry: rawptr[GenTypes.ReplFuncEntry] = self.repl_find_func_entry(ctx, func_name) + if entry is None: + return None + # 本判定与 M2 的读路径同口径:片段入口函数**或被提升的会话函数**体内允许 + # 调用别的会话函数;片段内的嵌套闭包体(无 ctx 形参)不允许 ⇒ 返回未命中, + # 由既有路径给出明确报错(不生成非法 IR)。 + if self.repl_in_frag_scope(trans.SymTab) == 0: + return None + hit[0] = 1 + if kw_count != 0: + self.Gen.TypeGenErr.fatal_error( + node, + f"REPL: 会话函数 '{func_name}' 不支持关键字实参(M3 仅支持位置实参)", + "TypeError") + return None + if can != entry.ParamCount: + self.Gen.TypeGenErr.fatal_error( + node, + f"REPL: 会话函数 '{func_name}' 期待 {entry.ParamCount} 个位置实参," + f"调用处给了 {can} 个(M3 不支持默认参数/缺省补全)", + "TypeError") + return None + ctx_val: rawptr[llvmlite.Value] = self.repl_ctx_ptr(trans, ctx, node) + if ctx_val is None: + return None + i8_ty: rawptr[llvmlite.LLVMType] = llvmlite.Int8(pool) + i8_ptr_ty: rawptr[llvmlite.LLVMType] = llvmlite.Ptr(pool, i8_ty) + # ① 槽内取函数地址:槽地址是 i8**(Ptr(i8*)),load 得 i8* + slot: rawptr[llvmlite.Value] = self.repl_slot_ptr( + pool, builder, ctx_val, entry.SlotOff, i8_ptr_ty) + if slot is None: + self.Gen.TypeGenErr.fatal_error( + node, "REPL: 会话函数指针槽地址生成失败", "InternalCompilerError") + return None + fn_i8: rawptr[llvmlite.Value] = llvmlite.build_load(builder, i8_ptr_ty, slot) + if fn_i8 is None: + self.Gen.TypeGenErr.fatal_error( + node, f"REPL: 会话函数 '{func_name}' 的地址读取失败", "InternalCompilerError") + return None + # ② 签名构造前先取形参类型(token → LLVM 类型;不支持 ⇒ 明确报错) + ptypes: t.CPtr = pool.alloc(8 * GenTypes.REPL_FUNC_PARAM_MAX) + if ptypes is None: + return None + string.memset(ptypes, 0, 8 * GenTypes.REPL_FUNC_PARAM_MAX) + ai_chk: int + for ai_chk in range(entry.ParamCount): + pty_chk: rawptr[llvmlite.LLVMType] = self.repl_func_ty( + pool, int(entry.ParamTags[ai_chk]), int(entry.ParamBits[ai_chk])) + if pty_chk is None: + self.Gen.TypeGenErr.fatal_error( + node, + f"REPL: 会话函数 '{func_name}' 第 {ai_chk + 1} 个形参的类型 token " + f"不受支持(M3 支持 iN/float/double/i8*)", + "TypeError") + return None + pty_slot: str | t.CPtr = (t.CPtr)(uint64(ptypes) + ai_chk * 8) + pty_slot[0] = pty_chk + # ③ 实参:首实参 = 本片段 ctx,其后按签名类型逐个翻译并 coerce + head: rawptr[llvmlite.Value] = ctx_val + head.Next = None + tail: rawptr[llvmlite.Value] = head + arg_count: int = 1 + ai: int + for ai in range(entry.ParamCount): + arg: rawptr[ast.AST] = cargs.get(ai) + if arg is None: + self.Gen.TypeGenErr.fatal_error( + node, f"REPL: 会话函数 '{func_name}' 的第 {ai + 1} 个实参缺失", + "SyntaxError") + return None + pty_slot2: str | t.CPtr = (t.CPtr)(uint64(ptypes) + ai * 8) + pty: rawptr[llvmlite.LLVMType] = (rawptr[llvmlite.LLVMType])(pty_slot2[0]) + arg_val: rawptr[llvmlite.Value] = self.Gen.ExprGen.translate_value( + builder, pool, mod, arg, None, 0, trans, pty) + if arg_val is None: + self.Gen.TypeGenErr.fatal_error( + node, f"REPL: 会话函数 '{func_name}' 的第 {ai + 1} 个实参翻译失败", + "TypeError") + return None + coerced: rawptr[llvmlite.Value] = self.Gen.ExprGenBase.coerce_to_type( + builder, arg_val, pty, arg, trans) + if coerced is None: + self.Gen.TypeGenErr.fatal_error( + node, + f"REPL: 会话函数 '{func_name}' 的第 {ai + 1} 个实参与签名类型不兼容", + "TypeError") + return None + coerced.Next = None + tail.Next = coerced + tail = coerced + arg_count += 1 + # ④ 函数类型:ret + (i8*, P1..Pn)(ret=void 时用 llvmlite.Void, + # build_call_indirect 返回 None ⇒ 宿主调用方按"void 调用"处理) + ret_ty: rawptr[llvmlite.LLVMType] = llvmlite.Void(pool) + if entry.RetTag != GenTypes.REPL_TY_VOID: + ret_ty = self.repl_func_ty(pool, entry.RetTag, entry.RetBits) + if ret_ty is None: + self.Gen.TypeGenErr.fatal_error( + node, + f"REPL: 会话函数 '{func_name}' 的返回类型 token 不受支持" + f"(M3 支持 void/iN/float/double/i8*)", + "TypeError") + return None + fp_first: rawptr[llvmlite.Function] = None + fp_prev: rawptr[llvmlite.Function] = None + pi: int + for pi in range(arg_count): + cur_ty: rawptr[llvmlite.LLVMType] = i8_ptr_ty + if pi > 0: + pty_slot3: str | t.CPtr = (t.CPtr)(uint64(ptypes) + (pi - 1) * 8) + cur_ty = (rawptr[llvmlite.LLVMType])(pty_slot3[0]) + pn: rawptr[llvmlite.Function] = llvmlite.new_param_node(pool, cur_ty) + if pn is not None: + if fp_first is None: + fp_first = pn + if fp_prev is not None: + llvmlite.paramnode_set_next(fp_prev, pn) + fp_prev = pn + func_ty: rawptr[llvmlite.LLVMType] = llvmlite.Func(pool, ret_ty, fp_first, arg_count) + if func_ty is None: + return None + func_ptr_ty: rawptr[llvmlite.LLVMType] = llvmlite.Ptr(pool, func_ty) + fn_typed: rawptr[llvmlite.Value] = llvmlite.build_bitcast(builder, fn_i8, func_ptr_ty) + if fn_typed is None: + self.Gen.TypeGenErr.fatal_error( + node, f"REPL: 会话函数 '{func_name}' 的地址转换失败", "InternalCompilerError") + return None + return llvmlite.build_call_indirect(builder, fn_typed, head, arg_count, ret_ty) + + # ---- repl_slot_ptr — 槽地址(ctx + off 的字节 GEP 后 bitcast 成 Ptr(val_ty)) ---- + def repl_slot_ptr(self, pool: rawptr[memhub.MemBuddy], + builder: rawptr[llvmlite.IRBuilder], + ctx_val: rawptr[llvmlite.Value], + off: int, + val_ty: rawptr[llvmlite.LLVMType]) -> rawptr[llvmlite.Value]: + """返回槽指针(Ptr(val_ty));参数不完整返回 None + + 用 i8 步长 GEP 做纯字节偏移(同 list_getitem_inline 的既有做法), + 再 bitcast 到目标类型指针——槽步长与负载类型解耦。 + """ + if pool is None or builder is None or ctx_val is None or val_ty is None: + return None + i8_ty: rawptr[llvmlite.LLVMType] = llvmlite.Int8(pool) + target_ptr_ty: rawptr[llvmlite.LLVMType] = llvmlite.Ptr(pool, val_ty) + off_val: rawptr[llvmlite.Value] = llvmlite.const_int64(pool, t.CInt64T(off)) + if off_val is None: + return None + base: rawptr[llvmlite.Value] = llvmlite.build_gep(builder, i8_ty, ctx_val, off_val) + if base is None: + return None + # 同型时不发 bitcast:llvmlite 类型非 canonical(同类型不同实例地址不同), + # 而 LLVM 拒绝 `bitcast i8* %x to i8*`(同型 cast 非法)——val_ty 为 i8 + # (char 会话变量)时目标恰是 i8*,必须直接返回 GEP 结果。 + if base.Ty is not None and self.Gen.AssignGen._stg_ty_equal( + pool, base.Ty, target_ptr_ty) != 0: + return base + return llvmlite.build_bitcast(builder, base, target_ptr_ty) + + # ---- repl_scalar_ty — 位宽 → LLVM 标量类型 ---- + def repl_scalar_ty(self, pool: rawptr[memhub.MemBuddy], + bits: int, is_float: int) -> rawptr[llvmlite.LLVMType]: + """按位宽还原 LLVM 标量类型;不支持的位宽返回 None""" + if is_float != 0: + if bits == 32: + return llvmlite.Float(pool) + if bits == 64: + return llvmlite.Double(pool) + return None + if bits == 1: + return llvmlite.Int1(pool) + if bits == 8: + return llvmlite.Int8(pool) + if bits == 16: + return llvmlite.Int16(pool) + if bits == 32: + return llvmlite.Int32(pool) + if bits == 64: + return llvmlite.Int64(pool) + return None + + # ---- repl_llvm_ty — 条目 → LLVM 值类型(结构体按名重解析) ---- + def repl_llvm_ty(self, pool: rawptr[memhub.MemBuddy], + entry: rawptr[GenTypes.ReplVarEntry]) -> rawptr[llvmlite.LLVMType]: + """条目反推 LLVM 值类型;未定型/结构体类型不可解析返回 None + + TyTag=STRUCT 时返回结构体**值**类型(内联存槽,槽类型即它本身)。 + 结构体按 TyName 在当前片段重解析(类型对象不跨片段缓存——片段 pool 行末 + 即回收,缓存指针会悬垂);当前片段不可见该结构体时返回 None 由调用点 + 给明确消息(跨片段类型可见性属 M4)。 + """ + if entry is None or pool is None: + return None + if entry.Kind == GenTypes.REPL_KIND_UNTYPED: + return None + if entry.TyTag == GenTypes.REPL_TY_INT: + return self.repl_scalar_ty(pool, entry.Bits, 0) + if entry.TyTag == GenTypes.REPL_TY_FLOAT: + return self.repl_scalar_ty(pool, entry.Bits, 1) + if entry.TyTag == GenTypes.REPL_TY_STR: + return llvmlite.Ptr(pool, llvmlite.Int8(pool)) + if entry.TyTag == GenTypes.REPL_TY_PTR: + if entry.TyName is not None: + st: rawptr[llvmlite.LLVMType] = self.Gen.StructGen.get_struct_type(entry.TyName) + if st is None: + return None + return llvmlite.Ptr(pool, st) + ptee: rawptr[llvmlite.LLVMType] = self.repl_scalar_ty(pool, entry.Bits, 0) + if ptee is None: + ptee = llvmlite.Int8(pool) + return llvmlite.Ptr(pool, ptee) + if entry.TyTag == GenTypes.REPL_TY_STRUCT: + if entry.TyName is None: + return None + return self.Gen.StructGen.get_struct_type(entry.TyName) + return None + + # ---- repl_slot_ty — 槽内存放的值类型 ---- + def repl_slot_ty(self, pool: rawptr[memhub.MemBuddy], + entry: rawptr[GenTypes.ReplVarEntry]) -> rawptr[llvmlite.LLVMType]: + """槽内存放的值类型 = 条目的值类型本身(标量/指针/结构体值都内联在槽里) + + 结构体值另有体积约束:尺寸 > REPL_SLOT_SIZE 或对齐 > REPL_SLOT_SIZE 时 + 不可放入槽 → 返回 None(定型期 repl_seal 已先给明确报错,此处为防御)。 + """ + if entry is None or pool is None: + return None + vt: rawptr[llvmlite.LLVMType] = self.repl_llvm_ty(pool, entry) + if vt is None: + return None + if entry.TyTag == GenTypes.REPL_TY_STRUCT: + sz: int = self.repl_type_size(vt) + al: int = self.repl_type_align(vt) + if sz <= 0 or al <= 0 or sz > GenTypes.REPL_SLOT_SIZE \ + or al > GenTypes.REPL_SLOT_SIZE: + return None + return vt + + # ---- repl_type_align / repl_type_size — 结构体值的尺寸/对齐(LLVM 标准布局) ---- + def repl_type_align(self, ty: rawptr[llvmlite.LLVMType], depth: int = 0) -> int: + """类型自然对齐字节;未知类型返回 0(deep=守卫递归深度)""" + if ty is None or depth > 64: + return 0 + ib: int = self.Gen.ExprGenBase.get_llvm_type_bits(ty) + if ib != 0: + if ib <= 8: + return 1 + if ib <= 16: + return 2 + if ib <= 32: + return 4 + return 8 + fb: int = self.Gen.ExprGenBase.get_llvm_float_bits(ty) + if fb != 0: + if fb <= 16: + return 2 + if fb <= 32: + return 4 + return 8 + if self.Gen.ExprGenBase.is_ptr_type(ty) != 0: + return 8 + elem_ty: rawptr[llvmlite.LLVMType] = None + match ty: + case llvmlite.LLVMType.Array(elem_a, _cnt_a): + elem_ty = elem_a + case _: + pass + if elem_ty is not None: + return self.repl_type_align(elem_ty, depth + 1) + fields: rawptr[llvmlite.ParamNode] = llvmlite.get_struct_fields(ty) + if fields is None: + return 0 + cur: rawptr[llvmlite.ParamNode] = fields + best: int = 1 + while cur is not None: + fty: rawptr[llvmlite.LLVMType] = llvmlite.paramnode_get_ty(cur) + fa: int = self.repl_type_align(fty, depth + 1) + if fa <= 0: + return 0 + if fa > best: + best = fa + cur = llvmlite.paramnode_get_next(cur) + return best + + def repl_type_size(self, ty: rawptr[llvmlite.LLVMType], depth: int = 0) -> int: + """类型字节尺寸(LLVM 标准布局:字段按自然对齐排布 + 尾填充);未知返回 0 + + 用于结构体值的"能否放入槽"判定(标量/指针见 repl_scalar_ty)。 + """ + if ty is None or depth > 64: + return 0 + ib: int = self.Gen.ExprGenBase.get_llvm_type_bits(ty) + if ib != 0: + return (ib + 7) // 8 + fb: int = self.Gen.ExprGenBase.get_llvm_float_bits(ty) + if fb != 0: + return fb // 8 + if self.Gen.ExprGenBase.is_ptr_type(ty) != 0: + return 8 + elem_ty: rawptr[llvmlite.LLVMType] = None + cnt: int = 0 + match ty: + case llvmlite.LLVMType.Array(elem_s, cnt_s): + elem_ty = elem_s + cnt = cnt_s + case _: + pass + if elem_ty is not None: + es: int = self.repl_type_size(elem_ty, depth + 1) + if es <= 0 or cnt <= 0: + return 0 + return es * cnt + fields: rawptr[llvmlite.ParamNode] = llvmlite.get_struct_fields(ty) + if fields is None: + return 0 + align_struct: int = self.repl_type_align(ty, depth + 1) + if align_struct <= 0: + return 0 + cur: rawptr[llvmlite.ParamNode] = fields + off: int = 0 + counted: int = 0 + while cur is not None: + fty2: rawptr[llvmlite.LLVMType] = llvmlite.paramnode_get_ty(cur) + fsz: int = self.repl_type_size(fty2, depth + 1) + fa2: int = self.repl_type_align(fty2, depth + 1) + if fsz <= 0 or fa2 <= 0: + return 0 + rem: int = off % fa2 + if rem != 0: + off += fa2 - rem + off += fsz + counted += 1 + cur = llvmlite.paramnode_get_next(cur) + if counted <= 0: + return 0 + rem2: int = off % align_struct + if rem2 != 0: + off += align_struct - rem2 + return off + + # ---- repl_seal — 首次定型(已定型则校验类型标签一致) ---- + def repl_seal(self, trans: rawptr[HT.Translator], + entry: rawptr[GenTypes.ReplVarEntry], + val_ty: rawptr[llvmlite.LLVMType], + node: rawptr[ast.AST]) -> int: + """按 val_ty 给条目定型;返回 0=成功(含幂等命中)/ 1=不支持或冲突(已 fail-fast) + + 规则: + - 首次(Kind=UNTYPED)写入 Kind/TyTag/Bits/TyName; + - 已定型:TyTag 必须一致(int↔float↔指针不隐式换型,避免槽语义漂移), + Bits 差异允许(由 coerce_to_type 做位宽对齐,常量溢出仍有既有 fail-fast)。 + """ + if trans is None or entry is None or val_ty is None: + return 1 + perm: rawptr[memhub.MemBuddy] = self.repl_perm_pool(trans) + if perm is None: + self.Gen.TypeGenErr.fatal_error( + node, "REPL: 会话永久池不可用(宿主未注入)", "InternalCompilerError") + return 1 + tag: int = 0 + bits: int = 0 + kind: int = GenTypes.REPL_KIND_SCALAR + tname: str = None + ib: int = self.Gen.ExprGenBase.get_llvm_type_bits(val_ty) + fb: int = self.Gen.ExprGenBase.get_llvm_float_bits(val_ty) + if ib != 0: + tag = GenTypes.REPL_TY_INT + bits = ib + elif fb != 0: + tag = GenTypes.REPL_TY_FLOAT + bits = fb + elif self.Gen.ExprGenBase.is_ptr_type(val_ty) != 0: + ptee: rawptr[llvmlite.LLVMType] = val_ty.Pointee + pname: str = None + if ptee is not None: + pname = llvmlite.get_struct_name(ptee) + if pname is not None: + tag = GenTypes.REPL_TY_PTR + bits = 64 + tname = pname + else: + pb: int = 0 + pf: int = 0 + if ptee is not None: + pb = self.Gen.ExprGenBase.get_llvm_type_bits(ptee) + pf = self.Gen.ExprGenBase.get_llvm_float_bits(ptee) + if pb == 8: + tag = GenTypes.REPL_TY_STR + bits = 8 + else: + tag = GenTypes.REPL_TY_PTR + if pb != 0: + bits = pb + elif pf != 0: + bits = pf + else: + bits = 0 + else: + sname: str = llvmlite.get_struct_name(val_ty) + if sname is not None: + # 结构体**值**:按声明类型内联在槽里(不做装箱)。 + # 槽容量/对齐上限 = REPL_SLOT_SIZE(16 字节):结构体尺寸或对齐 + # 超出即明确报错(不静默截断、不换用堆盒子)。 + sz: int = self.repl_type_size(val_ty) + al: int = self.repl_type_align(val_ty) + if sz <= 0 or al <= 0: + self.Gen.TypeGenErr.fatal_error( + node, + f"REPL: 会话变量 '{entry.Name}' 的结构体值 '{sname}' 尺寸/对齐未知" + f"(无法判定能否放入 {GenTypes.REPL_SLOT_SIZE} 字节槽)", + "TypeError") + return 1 + if sz > GenTypes.REPL_SLOT_SIZE or al > GenTypes.REPL_SLOT_SIZE: + self.Gen.TypeGenErr.fatal_error( + node, + f"REPL: 会话变量 '{entry.Name}' 的结构体值 '{sname}' 体积 {sz} 字节、" + f"对齐 {al} 字节,无法放入 {GenTypes.REPL_SLOT_SIZE} 字节槽" + f"(M2 结构体值须内联进槽;大对象请改用对象指针/堆分配)", + "TypeError") + return 1 + tag = GenTypes.REPL_TY_STRUCT + bits = sz * 8 + kind = GenTypes.REPL_KIND_SCALAR + tname = sname + else: + self.Gen.TypeGenErr.fatal_error( + node, + f"REPL: 会话变量 '{entry.Name}' 的类型暂不支持" + f"(M2 支持 int/bool/浮点/str/bytes/指针/结构体值)", + "TypeError") + return 1 + if entry.Kind == GenTypes.REPL_KIND_UNTYPED: + entry.Kind = kind + entry.TyTag = tag + entry.Bits = bits + entry.TyName = None + if tname is not None: + entry.TyName = self.repl_perm_strdup(perm, tname) + return 0 + if entry.Kind != kind or entry.TyTag != tag: + self.Gen.TypeGenErr.fatal_error( + node, + f"REPL: 会话变量 '{entry.Name}' 已定型为 {self.repl_tag_label(entry.TyTag, entry.Bits)}," + f"不能再赋成 {self.repl_tag_label(tag, bits)}(如需换型请重启会话)", + "TypeError") + return 1 + if tname is not None and entry.TyName is None: + entry.TyName = self.repl_perm_strdup(perm, tname) + return 0 + + # ---- repl_perm_strdup — 深拷贝到 PermPool(跨片段存活) ---- + def repl_perm_strdup(self, pool: rawptr[memhub.MemBuddy], + s: str) -> str: + """把 s 深拷贝进 pool(调用方传 PermPool)供会话表长期持有;失败返回 None""" + if s is None or pool is None: + return None + n: sizet = string.strlen(s) + buf: str = pool.alloc(n + 1) + if buf is None: + return None + string.strcpy(buf, s) + return buf + + # ---- repl_tag_label — 类型标签 → 显示名(宿主回显同口径) ---- + def repl_tag_label(self, tag: int, bits: int) -> str: + """TyTag/Bits → 人类可读类型名(仅用于错误消息)""" + if tag == GenTypes.REPL_TY_INT: + if bits == 1: + return "bool" + if bits == 8: + return "char" + if bits == 16: + return "int16" + if bits == 32: + return "int" + if bits == 64: + return "int64" + return "int?" + if tag == GenTypes.REPL_TY_FLOAT: + if bits == 32: + return "float" + if bits == 64: + return "double" + return "float?" + if tag == GenTypes.REPL_TY_STR: + return "str" + if tag == GenTypes.REPL_TY_PTR: + return "ptr" + if tag == GenTypes.REPL_TY_STRUCT: + return "struct" + return "?" + + # ---- repl_ensure_entry — 首次遇到未定型的会话变量名时申请槽位 ---- + # + # 分配时机:片段翻译期(translate_assign / translate_annassign 的 Name 目标 + # 在局部表未命中时)。表满 ⇒ 明确报错(fail-fast,经 M0 setjmp 恢复点继续 + # 下一行),不静默丢弃。 + def repl_ensure_entry(self, trans: rawptr[HT.Translator], + name: str, + node: rawptr[ast.AST]) -> rawptr[GenTypes.ReplVarEntry]: + """按名取条目,未登记则分配槽位并登记;失败返回 None(已 fail-fast)""" + if trans is None or name is None: + return None + ctx: rawptr[GenTypes.ReplCtx] = trans.ReplCtx + if ctx is None or ctx.Marker != GenTypes.REPL_CTX_MARKER: + return None + hit: rawptr[GenTypes.ReplVarEntry] = self.repl_find_entry(ctx, name) + if hit is not None: + return hit + if ctx.Table is None or ctx.Cap <= 0: + self.Gen.TypeGenErr.fatal_error( + node, "REPL: 会话变量表未初始化(宿主未建 ctx)", "InternalCompilerError") + return None + if ctx.Count >= ctx.Cap: + self.Gen.TypeGenErr.fatal_error( + node, + f"REPL: 会话变量已达上限 {ctx.Cap} 个(ctx 槽位用尽)," + f"无法为 '{name}' 再分配槽位;请重启会话", + "ResourceError") + return None + perm: rawptr[memhub.MemBuddy] = self.repl_perm_pool(trans) + if perm is None: + self.Gen.TypeGenErr.fatal_error( + node, "REPL: 会话永久池不可用(宿主未注入)", "InternalCompilerError") + return None + name_copy: str = self.repl_perm_strdup(perm, name) + if name_copy is None: + self.Gen.TypeGenErr.fatal_error( + node, f"REPL: 会话变量 '{name}' 的名字深拷贝失败", "InternalCompilerError") + return None + entry_size: sizet = GenTypes.ReplVarEntry.__sizeof__() + entry: rawptr[GenTypes.ReplVarEntry] = rawptr[GenTypes.ReplVarEntry]( + uint64(ctx.Table) + uint64(ctx.Count) * uint64(entry_size)) + if entry is None: + return None + entry.Name = name_copy + entry.TyName = None + entry.Offset = GenTypes.REPL_SLOT_BASE + ctx.Count * GenTypes.REPL_SLOT_SIZE + entry.Kind = GenTypes.REPL_KIND_UNTYPED + entry.TyTag = 0 + entry.Bits = 0 + ctx.Count = ctx.Count + 1 + return entry + + # ---- repl_perm_pool — 会话数据的永久池(跨片段存活) ---- + def repl_perm_pool(self, trans: rawptr[HT.Translator]) -> rawptr[memhub.MemBuddy]: + """宿主注入的 PermPool(ctx.Perm);无 ctx/未注入返回 None""" + if trans is None or trans.ReplCtx is None: + return None + return trans.ReplCtx.Perm + + # ---- repl_emit_result — 把本片段结果写入 ctx 结果区 ---- + def repl_emit_result(self, trans: rawptr[HT.Translator], + ctx_val: rawptr[llvmlite.Value], + val: rawptr[llvmlite.Value], + tag: int, bits: int, + node: rawptr[ast.AST] = None) -> int: + """按 tag 把 val 写入 ctx 结果区(见 GenTypes 的 result_kind 表);0=成功 + + tag: REPL_TY_INT/FLOAT/STR/PTR/STRUCT。 + INT → kind=1,result_a = 值(sext 到 i64,按位模式;bits 仅用于诊断) + FLOAT → kind=2,result_a = double 位模式(f32 先 fpext) + STR → kind=3,result_a = 指针,result_b = 0(NUL 结尾,长度未知) + PTR → kind=4,result_a = 地址 + STRUCT→ kind=4,result_a = 0(结构体值内联在槽里,本身没有地址) + 宿主只按 (kind, result_a, result_b) 读,不需要知道槽内类型。 + """ + if trans is None or ctx_val is None or val is None: + return 1 + pool: rawptr[memhub.MemBuddy] = trans.Pool + builder: rawptr[llvmlite.IRBuilder] = trans._cur_builder + if pool is None or builder is None: + return 1 + i64_ty: rawptr[llvmlite.LLVMType] = llvmlite.Int64(pool) + zero64: rawptr[llvmlite.Value] = llvmlite.const_int64(pool, 0) + if tag == GenTypes.REPL_TY_INT: + iv: rawptr[llvmlite.Value] = self.Gen.ExprGenBase.coerce_to_type( + builder, val, i64_ty, node, trans) + if iv is None: + return 1 + if self._repl_store_res_i32(trans, ctx_val, GenTypes.REPL_RES_KIND_OFF, + GenTypes.REPL_RES_INT) != 0: + return 1 + if self._repl_store_res_i64(trans, ctx_val, GenTypes.REPL_RES_A_OFF, iv) != 0: + return 1 + return self._repl_store_res_i64(trans, ctx_val, GenTypes.REPL_RES_B_OFF, zero64) + if tag == GenTypes.REPL_TY_FLOAT: + dv: rawptr[llvmlite.Value] = self.Gen.ExprGenBase.coerce_to_type( + builder, val, llvmlite.Double(pool), node, trans) + if dv is None: + return 1 + # double → i64 位模式(bitcast,不做数值转换):宿主按位重解释回 double + dbits: rawptr[llvmlite.Value] = llvmlite.build_bitcast(builder, dv, i64_ty) + if dbits is None: + return 1 + if self._repl_store_res_i32(trans, ctx_val, GenTypes.REPL_RES_KIND_OFF, + GenTypes.REPL_RES_FLOAT) != 0: + return 1 + if self._repl_store_res_i64(trans, ctx_val, GenTypes.REPL_RES_A_OFF, dbits) != 0: + return 1 + return self._repl_store_res_i64(trans, ctx_val, GenTypes.REPL_RES_B_OFF, zero64) + if tag == GenTypes.REPL_TY_STR or tag == GenTypes.REPL_TY_PTR: + i8_ptr_ty: rawptr[llvmlite.LLVMType] = llvmlite.Ptr(pool, llvmlite.Int8(pool)) + pv: rawptr[llvmlite.Value] = val + if val.Ty is not None and self.Gen.ExprGenBase.is_ptr_type(val.Ty) != 0 \ + and self.Gen.AssignGen._stg_ty_equal(pool, val.Ty, i8_ptr_ty) == 0: + # 已是指向 i8 的指针(str/bytes/t.CPtr)时跳过——LLVM 拒绝同型 bitcast + pv = llvmlite.build_bitcast(builder, val, i8_ptr_ty) + if pv is None: + return 1 + # 指针 → i64(ptrtoint):宿主按整数读回后再 inttoptr 成地址 + pbits: rawptr[llvmlite.Value] = self.Gen.ExprGenBase.coerce_to_type( + builder, pv, i64_ty, node, trans) + if pbits is None: + return 1 + kind: int = GenTypes.REPL_RES_OBJ + if tag == GenTypes.REPL_TY_STR: + kind = GenTypes.REPL_RES_STR + if self._repl_store_res_i32(trans, ctx_val, GenTypes.REPL_RES_KIND_OFF, + kind) != 0: + return 1 + if self._repl_store_res_i64(trans, ctx_val, GenTypes.REPL_RES_A_OFF, pbits) != 0: + return 1 + return self._repl_store_res_i64(trans, ctx_val, GenTypes.REPL_RES_B_OFF, zero64) + if tag == GenTypes.REPL_TY_STRUCT: + # 结构体值:内联在会话槽里,值本身没有地址 ⇒ 结果区只报"类别" + # (kind=4 且 result_a=0;宿主据此打印 ) + if self._repl_store_res_i32(trans, ctx_val, GenTypes.REPL_RES_KIND_OFF, + GenTypes.REPL_RES_OBJ) != 0: + return 1 + if self._repl_store_res_i64(trans, ctx_val, GenTypes.REPL_RES_A_OFF, zero64) != 0: + return 1 + return self._repl_store_res_i64(trans, ctx_val, GenTypes.REPL_RES_B_OFF, zero64) + return 1 + + # ---- _repl_store_res_i64 / _repl_store_res_i32 — 结果区某偏移写定宽常量/值 ---- + def _repl_store_res_i64(self, trans: rawptr[HT.Translator], + ctx_val: rawptr[llvmlite.Value], + off: int, + val: rawptr[llvmlite.Value]) -> int: + """把 i64 值写到 ctx 结果区的 off 偏移(0=成功)""" + if trans is None or ctx_val is None or val is None: + return 1 + pool: rawptr[memhub.MemBuddy] = trans.Pool + builder: rawptr[llvmlite.IRBuilder] = trans._cur_builder + slot: rawptr[llvmlite.Value] = self.repl_slot_ptr( + pool, builder, ctx_val, off, llvmlite.Int64(pool)) + if slot is None: + return 1 + llvmlite.build_store(builder, val, slot) + return 0 + + def _repl_store_res_i32(self, trans: rawptr[HT.Translator], + ctx_val: rawptr[llvmlite.Value], + off: int, + val: int) -> int: + """把 i32 常量写到 ctx 结果区的 off 偏移(kind / var_count 等,0=成功)""" + if trans is None or ctx_val is None: + return 1 + pool: rawptr[memhub.MemBuddy] = trans.Pool + builder: rawptr[llvmlite.IRBuilder] = trans._cur_builder + if pool is None or builder is None: + return 1 + slot: rawptr[llvmlite.Value] = self.repl_slot_ptr( + pool, builder, ctx_val, off, llvmlite.Int32(pool)) + if slot is None: + return 1 + cv: rawptr[llvmlite.Value] = llvmlite.const_int32(pool, val) + if cv is None: + return 1 + llvmlite.build_store(builder, cv, slot) + return 0 + + # ---- repl_try_read — 会话变量读取(命中返回 load 值,未命中返回 None) ---- + def repl_try_read(self, trans: rawptr[HT.Translator], + name: str, + node: rawptr[ast.AST]) -> rawptr[llvmlite.Value]: + """REPL 会话变量读:命中返回槽内值(结构体返回盒子地址);否则 None + + 返回 None 表示"不是会话变量",调用方继续原有查找/报错路径。 + """ + if trans is None or trans.ReplCtx is None or name is None: + return None + entry: rawptr[GenTypes.ReplVarEntry] = self.repl_find_entry(trans.ReplCtx, name) + if entry is None: + return None + if self.repl_in_frag_scope(trans.SymTab) == 0: + return None + if entry.Kind == GenTypes.REPL_KIND_UNTYPED: + self.Gen.TypeGenErr.fatal_error( + node, + f"REPL: 会话变量 '{name}' 尚未赋值(未定型,先执行一条 'x = ...' 或 'x: int = ...')", + "NameError") + return None + pool: rawptr[memhub.MemBuddy] = trans.Pool + slot_ty: rawptr[llvmlite.LLVMType] = self.repl_slot_ty(pool, entry) + if slot_ty is None: + self.Gen.TypeGenErr.fatal_error( + node, + f"REPL: 会话变量 '{name}' 的类型 " + f"{self.repl_tag_label(entry.TyTag, entry.Bits)} 在当前片段不可用" + f"(跨片段类型可见性属 M4)", + "NameError") + return None + ctx_val: rawptr[llvmlite.Value] = self.repl_ctx_ptr(trans, trans.ReplCtx, node) + if ctx_val is None: + return None + slot: rawptr[llvmlite.Value] = self.repl_slot_ptr( + pool, trans._cur_builder, ctx_val, entry.Offset, slot_ty) + if slot is None: + self.Gen.TypeGenErr.fatal_error(node, "REPL: 会话槽地址生成失败", "InternalCompilerError") + return None + return llvmlite.build_load(trans._cur_builder, slot_ty, slot) + + # ---- repl_try_addr — 会话变量取址(c.Addr(x) → ctx 槽地址) ---- + def repl_try_addr(self, trans: rawptr[HT.Translator], + name: str, + node: rawptr[ast.AST]) -> rawptr[llvmlite.Value]: + """会话变量取址:返回槽地址(Ptr(值类型)) + + 值只存一份(宿主持有的 ctx arena)且内联在槽里,故 c.Addr(x) 得到的 + 就是该槽的地址:后续经该指针的读写直接作用于会话变量(地址同一性)。 + 未登记/未定型/不在片段函数本体返回 None,由调用方继续原有查找与报错路径。 + """ + if trans is None or trans.ReplCtx is None or name is None: + return None + entry: rawptr[GenTypes.ReplVarEntry] = self.repl_find_entry(trans.ReplCtx, name) + if entry is None: + return None + if self.repl_in_frag_scope(trans.SymTab) == 0: + return None + if entry.Kind == GenTypes.REPL_KIND_UNTYPED: + self.Gen.TypeGenErr.fatal_error( + node, + f"REPL: 会话变量 '{name}' 尚未赋值(未定型),无法取址(c.Addr)", + "NameError") + return None + pool: rawptr[memhub.MemBuddy] = trans.Pool + slot_ty: rawptr[llvmlite.LLVMType] = self.repl_slot_ty(pool, entry) + if slot_ty is None: + self.Gen.TypeGenErr.fatal_error( + node, + f"REPL: 会话变量 '{name}' 的类型在当前片段不可用(跨片段类型可见性属 M4)", + "NameError") + return None + ctx_val: rawptr[llvmlite.Value] = self.repl_ctx_ptr(trans, trans.ReplCtx, node) + if ctx_val is None: + return None + slot: rawptr[llvmlite.Value] = self.repl_slot_ptr( + pool, trans._cur_builder, ctx_val, entry.Offset, slot_ty) + if slot is None: + self.Gen.TypeGenErr.fatal_error( + node, "REPL: 会话槽地址生成失败", "InternalCompilerError") + return None + # 值内联在槽里(无装箱间接层)⇒ 槽地址即值地址 + return slot + + # ---- repl_store_slot — 定型后的值写槽(并把该值写入结果区) ---- + def repl_store_slot(self, trans: rawptr[HT.Translator], + entry: rawptr[GenTypes.ReplVarEntry], + ctx_val: rawptr[llvmlite.Value], + rhs_val: rawptr[llvmlite.Value], + node: rawptr[ast.AST]) -> int: + """coerce 到槽类型 → 写槽 → 写结果区;0=成功(失败已 fail-fast) + + 槽内一律"值本身"(标量/指针/≤16B 结构体值内联),无装箱间接层 ⇒ + 槽地址即值地址(c.Addr 的地址同一性由此成立)。 + """ + if trans is None or entry is None or ctx_val is None or rhs_val is None: + return 1 + pool: rawptr[memhub.MemBuddy] = trans.Pool + builder: rawptr[llvmlite.IRBuilder] = trans._cur_builder + slot_ty: rawptr[llvmlite.LLVMType] = self.repl_slot_ty(pool, entry) + if slot_ty is None: + self.Gen.TypeGenErr.fatal_error( + node, + f"REPL: 会话变量 '{entry.Name}' 的槽类型不可用" + f"(类型在当前片段不可解析,或结构体值体积超出槽容量)", + "TypeError") + return 1 + store_val: rawptr[llvmlite.Value] = self.Gen.ExprGenBase.coerce_to_type( + builder, rhs_val, slot_ty, node, trans) + if store_val is None: + self.Gen.TypeGenErr.fatal_error( + node, + f"REPL: 会话变量 '{entry.Name}' 无法把右侧值转换为" + f" {self.repl_tag_label(entry.TyTag, entry.Bits)}", + "CastError") + return 1 + if self.Gen.ExprGenBase.is_ptr_type(slot_ty) != 0 \ + and store_val.Ty is not None \ + and self.Gen.ExprGenBase.is_ptr_type(store_val.Ty) != 0 \ + and self.Gen.AssignGen._stg_ty_equal(pool, store_val.Ty, slot_ty) == 0: + # 指针同型化(槽类型与右侧指针可能不是同一类型实例;位宽相同)。 + # 同型(含两侧都是 i8*)时跳过——LLVM 拒绝同型 bitcast。 + store_val = llvmlite.build_bitcast(builder, store_val, slot_ty) + if store_val is None: + self.Gen.TypeGenErr.fatal_error(node, "REPL: 会话槽指针同型化失败", "CastError") + return 1 + slot: rawptr[llvmlite.Value] = self.repl_slot_ptr( + pool, builder, ctx_val, entry.Offset, slot_ty) + if slot is None: + self.Gen.TypeGenErr.fatal_error(node, "REPL: 会话槽写入失败", "InternalCompilerError") + return 1 + llvmlite.build_store(builder, store_val, slot) + self.repl_emit_result(trans, ctx_val, store_val, entry.TyTag, entry.Bits, node) + return 0 + + # ---- repl_try_assign — 会话变量赋值(命中返回 1) ---- + def repl_try_assign(self, trans: rawptr[HT.Translator], + name: str, + rhs_val: rawptr[llvmlite.Value], + rhs_node: rawptr[ast.AST], + node: rawptr[ast.AST]) -> int: + """REPL 会话变量赋值:命中(已登记 + 在**片段入口函数**本体)返回 1,否则 0 + + 命中路径:定型 → coerce 到条目类型 → 写槽 → 写结果区。 + 返回 1 时调用方必须 continue(不得再走局部 alloca 路径)。 + 作用域判据用 repl_in_frag_entry_scope(M3):被提升的会话函数体内不做 + 会话槽写入(那里 `y = 1` 是局部变量、形参同名遮蔽也走局部)——详见该谓词注释。 + """ + if trans is None or trans.ReplCtx is None or name is None or rhs_val is None: + return 0 + entry: rawptr[GenTypes.ReplVarEntry] = self.repl_find_entry(trans.ReplCtx, name) + if entry is None: + return 0 + if self.repl_in_frag_entry_scope(trans) == 0: + return 0 + if rhs_val.Ty is None: + self.Gen.TypeGenErr.fatal_error( + node, f"REPL: 会话变量 '{name}' 的赋值右侧类型未知", "TypeError") + return 1 + if self.repl_seal(trans, entry, rhs_val.Ty, rhs_node) != 0: + return 1 + ctx_val: rawptr[llvmlite.Value] = self.repl_ctx_ptr(trans, trans.ReplCtx, node) + if ctx_val is None: + return 1 + self.repl_store_slot(trans, entry, ctx_val, rhs_val, node) + return 1 + + # ---- repl_try_annassign — 会话变量带注解声明/赋值(命中返回 1) ---- + def repl_try_annassign(self, trans: rawptr[HT.Translator], + name: str, + annot_node: rawptr[ast.AST], + value_node: rawptr[ast.AST], + node: rawptr[ast.AST]) -> int: + """REPL 会话变量注解路径:x: int = 1 / x: int(仅定型) + + 注解先解析并定型条目,再用定型后的类型作为 RHS 的 target_ty(与普通 + AnnAssign 的 P1-A 位宽传播同口径:`x: int64 = 12345` 直接物化 i64)。 + 作用域判据用 repl_in_frag_entry_scope(M3):被提升的会话函数体内 + `x: int = 1` 是局部声明(Python 语义),不占会话槽。 + """ + if trans is None or trans.ReplCtx is None or name is None: + return 0 + entry: rawptr[GenTypes.ReplVarEntry] = self.repl_find_entry(trans.ReplCtx, name) + if entry is None: + return 0 + if self.repl_in_frag_entry_scope(trans) == 0: + return 0 + pool: rawptr[memhub.MemBuddy] = trans.Pool + builder: rawptr[llvmlite.IRBuilder] = trans._cur_builder + if annot_node is None: + self.Gen.TypeGenErr.fatal_error( + node, f"REPL: 会话变量 '{name}' 的注解缺失", "SyntaxError") + return 1 + # 与普通 AnnAssign 同口径:先注册泛型特化,避免 list[X] 退化为裸基类 + # (裸 list* 会让后续 .get() 误分派并截断指针) + self.Gen.AssignGen._prespecialize_generic_annotation(trans, pool, annot_node) + annot_ty: rawptr[llvmlite.LLVMType] = self.Gen.TypeGenResolve.resolve_annotation_type( + pool, annot_node, trans._imported_modules, trans._from_imports, trans, 0) + if annot_ty is None: + self.Gen.TypeGenErr.fatal_error( + node, + f"REPL: 会话变量 '{name}' 的类型注解解析失败" + f"(片段无 import;仅内建名 int/str/bytes/bool/float/double/char 等可解析)", + "NameError") + return 1 + if self.repl_seal(trans, entry, annot_ty, node) != 0: + return 1 + if value_node is None: + # 仅定型(x: int):不产生值;结果区本行由宿主清零 ⇒ 无结果 + return 1 + slot_ty: rawptr[llvmlite.LLVMType] = self.repl_slot_ty(pool, entry) + # 与普通 AnnAssign 四处同口径:翻译 RHS 前设置上下文(裸名泛型构造推断用) + trans.CurAssignAnnot = annot_node + trans.CurAssignNode = value_node + rhs_val: rawptr[llvmlite.Value] = self.Gen.ExprGen.translate_value( + builder, pool, trans.Module, value_node, None, 0, trans, slot_ty) + trans.CurAssignAnnot = None + trans.CurAssignNode = None + if rhs_val is None: + self.Gen.TypeGenErr.fatal_error( + node, f"REPL: 会话变量 '{name}' 的初值翻译失败", "TypeError") + return 1 + ctx_val: rawptr[llvmlite.Value] = self.repl_ctx_ptr(trans, trans.ReplCtx, node) + if ctx_val is None: + return 1 + self.repl_store_slot(trans, entry, ctx_val, rhs_val, node) + return 1 + + # ---- repl_try_augassign — 会话变量增强赋值(命中返回 1) ---- + def repl_try_augassign(self, trans: rawptr[HT.Translator], + name: str, + op: int, + rhs_node: rawptr[ast.AST], + node: rawptr[ast.AST]) -> int: + """REPL 会话变量 x += v:读槽 → 二元运算 → 写槽 → 写结果(命中返回 1) + + 作用域判据用 repl_in_frag_entry_scope(M3):被提升的会话函数体内 + `x += 1` 按 Python 局部语义处理(不写会话槽)。 + """ + if trans is None or trans.ReplCtx is None or name is None: + return 0 + entry: rawptr[GenTypes.ReplVarEntry] = self.repl_find_entry(trans.ReplCtx, name) + if entry is None: + return 0 + if self.repl_in_frag_entry_scope(trans) == 0: + return 0 + pool: rawptr[memhub.MemBuddy] = trans.Pool + builder: rawptr[llvmlite.IRBuilder] = trans._cur_builder + if entry.TyTag == GenTypes.REPL_TY_STRUCT: + self.Gen.TypeGenErr.fatal_error( + node, + f"REPL: 会话变量 '{name}' 是结构体值,不支持增强赋值", + "TypeError") + return 1 + cur_val: rawptr[llvmlite.Value] = self.repl_try_read(trans, name, node) + if cur_val is None: + return 1 + rhs_val: rawptr[llvmlite.Value] = self.Gen.ExprGen.translate_value( + builder, pool, trans.Module, rhs_node, None, 0, trans) + if rhs_val is None: + self.Gen.TypeGenErr.fatal_error(node, "REPL: 增强赋值右侧翻译失败", "TypeError") + return 1 + result: rawptr[llvmlite.Value] = self.Gen.AssignGen._apply_aug_op( + pool, builder, op, cur_val, rhs_val, node, trans, node) + if result is None: + self.Gen.TypeGenErr.fatal_error( + node, f"REPL: 会话变量 '{name}' 的增强赋值运算失败 op={op}", "TypeError") + return 1 + # 写回槽(复用赋值路径:定型已存在 ⇒ 只做转换与存储) + return self.repl_try_assign(trans, name, result, node, node) + + # ---- repl_expr_result — 片段内裸表达式语句的结果回传 ---- + def repl_expr_result(self, trans: rawptr[HT.Translator], + value_node: rawptr[ast.AST]) -> int: + """REPL 模式下把裸表达式语句(如 x + 1)求值并写入结果区;0=成功 + + 非 REPL 模式不调用本函数(translate_expr_stmt 只在 REPL 分支调用), + 故"表达式语句除调用外一律忽略"的原行为不变。 + """ + if trans is None or trans.ReplCtx is None or value_node is None: + return 1 + pool: rawptr[memhub.MemBuddy] = trans.Pool + builder: rawptr[llvmlite.IRBuilder] = trans._cur_builder + val: rawptr[llvmlite.Value] = self.Gen.ExprGen.translate_value( + builder, pool, trans.Module, value_node, None, 0, trans) + if val is None or val.Ty is None: + self.Gen.TypeGenErr.fatal_error( + value_node, "REPL: 表达式片段求值失败", "TypeError") + return 1 + ctx_val: rawptr[llvmlite.Value] = self.repl_ctx_ptr(trans, trans.ReplCtx, value_node) + if ctx_val is None: + return 1 + # 类型分类用宿主的影子条目(不占会话槽、不进表):每次先复位为未定型 + sh: rawptr[GenTypes.ReplVarEntry] = trans.ReplCtx.Scratch + if sh is None: + self.Gen.TypeGenErr.fatal_error( + value_node, "REPL: 会话缺少影子条目(宿主未初始化)", "InternalCompilerError") + return 1 + sh.Kind = GenTypes.REPL_KIND_UNTYPED + sh.TyTag = 0 + sh.Bits = 0 + sh.TyName = None + if self.repl_seal(trans, sh, val.Ty, value_node) != 0: + return 1 + return self.repl_emit_result(trans, ctx_val, val, sh.TyTag, sh.Bits, value_node) diff --git a/App/lib/core/Repl.vp b/App/lib/core/Repl.vp new file mode 100644 index 0000000..a6c14eb --- /dev/null +++ b/App/lib/core/Repl.vp @@ -0,0 +1,2980 @@ +import t, c +from stdint import * +import stdio +import stdlib +import string +import viperlib +import llvmlite +import memhub +import ast +import w32.fileio as fileio +import w32.win32file as w32file +import w32.win32base as win32base +import w32.win32process as win32process +import lib.core.VLogger as VLogger +import lib.core.Gen.GenApi as GenApi +import lib.core.Gen.GenTypes as GenTypes +import lib.core.Gen.Translator as HandlesTranslator +import lib.core.IncludesScanner as IncludesScanner +import lib.Phase.Phase1.Phase1Scan as Phase1Scan +import lib.Phase.BuildPipeline as BuildPipeline +import lib.Projectrans.Config as Config +import lib.Stub.StubMerger.StubMergerStore as StubMergerStore +import lib.Stub.StubMerger.StubMergerCollect as StubMergerCollect +import lib.Stub.StubGen.Converter as StubConverter +import lib.Stub.StubGen.VpiReader as VpiReader + + +# ============================================================ +# Repl - TransPyV 交互式循环(M2:会话变量 + ctx 回传结果) +# +# 输入:单行片段(既有路径)与**块级续行**——整行去行尾空白后以 ':' 结尾 ⇒ 进入续行 +# (提示符 "... ",本行不执行),空行或 EOF 结束并把整块作为**一个**片段执行一次; +# 块文本存会话级 PermPool 缓冲(_REPL_BLOCK_CAP),失败/溢出后清空(残块不污染下一行)。 +# +# 片段通路:行 → 包裹为 `export def __repl_frag_(__repl_ctx__: bytes) -> int` +# → 翻译为完整模块 IR(会话变量按 ctx 槽寻址)→ .ll → llc → clang++ -shared +# → LoadLibraryA/GetProcAddress → t.Callable 调用(传 ctx 基址)→ 从 ctx 结果区 +# 读回并回显。 +# +# ctx(会话内存,宿主 PermPool 一次性分配;布局见 GenTypes 顶部注释): +# 偏移 0..63 固定头(magic/version/result_kind/result_a/result_b/var_count/last_frag) +# 偏移 64 起为变量槽(每槽 16 字节)。宿主与片段在**同一进程**内共享该内存: +# 会话变量只存一份 ⇒ c.Addr(x) 得到的就是槽地址(地址同一性)。 +# 值的类型按首次赋值推导(`x = 1` 与 `x: int = 1` 等价,均定型并分配槽)。 +# +# 错误可恢复依赖 VLogger 的 jit_flag + setjmp/longjmp: +# - Repl 用 PermPool 分配 jmp_buf 并注册给 VLogger(PermPool 不受 mb.reset_to 影响); +# - 每轮 mb.mark() 后 _setjmp,解析/翻译全用 mb 分配;无论成败都 reset_to(mark) 回收。 +# +# 复用既有机制(不新造进程拉起步子): +# - BuildPipeline.write_ir_to_file / build_sliced_path / compile_ll_to_obj(llc); +# - BuildPipeline._run_cmd_wait(CreateProcessA + WaitForSingleObject,见 :488); +# - Config 的 compiler/linker/target 通道(project.vpj 原样透传); +# - includes 会话初始化:IncludesScanner.scan_includes + StubMergerStore.PopulateSha1MapStore +# + StubConverter._GeneratePyiFromAst + StubMergerCollect._sliced_path(与 Phase1 同款, +# 见 ReplInitIncludes;否则片段里的 `import ` 会因 SHA1 表为空 fail-fast)。 +# ============================================================ + +# 输入行缓冲容量(含 NUL 终止符) +_REPL_LINE_CAP: t.CDefine = 4096 + +# 块输入缓冲容量(会话级 PermPool,含 NUL 终止符):续行状态下逐行追加的用户原文, +# 行间以一个 '\n' 连接、无行尾换行。 +# 取 8 KiB 的理由:常见多行块(10 行 × 80 列 ≈ 0.8 KiB)绰绰有余;整段粘贴 +# 100 行 × 80 列 ≈ 8 KiB 也刚好容纳;再大属异常输入,按"明确报错 + 丢弃该块" +# 处理(绝不截断后执行)。上界同时约束包裹源码缓冲的下界(见 _REPL_FRAG_SRC_CAP)。 +_REPL_BLOCK_CAP: t.CDefine = 8192 + +# jmp_buf 实测大小:mingw-w64 x86_64 setjmp.h 中 _JBLEN=16、_JBTYPE=SETJMP_FLOAT128(16B) +# ⇒ 16 * 16 = 256 字节(已用 C 探针 sizeof(jmp_buf) 实测 = 256) +_REPL_JMPBUF_SIZE: t.CDefine = 256 + +# setjmp/longjmp:mingw-w64 x86_64 的 _setjmp 第二参数为 frame 指针, +# setjmp 宏即展开为 _setjmp(buf, NULL)(非 SEH 场景传 None 即可) +extern def _setjmp(buf: t.CPtr, frame: t.CPtr) -> int +extern def longjmp(buf: t.CPtr, val: int) + +# PermPool(由 main 注入):jmp_buf 生命周期须覆盖整个会话,不能被 mb.reset_to 回收 +_perm_pool: rawptr[memhub.MemBuddy] + + +# ============================================================ +# M2/M3 常量 +# ============================================================ +# 片段包裹源码容量(含 NUL)。三条路径的上界取大者: +# 单行路径 = 行缓冲 4096 + 包裹开销 128(原口径,未变); +# 块路径 = 块缓冲 8192 + 逐行 4 空格缩进(块内每行至少 1 字符 + 一个行间 '\n' +# ⇒ 行数 ≤ 4096 ⇒ 缩进 ≤ 4096*4 = 16384)≤ 24576; +# 提升路径(M3)= 上面 24576 + 每个被提升 def 的头部注入("export " 7 + +# "__repl_ctx__: bytes, " 21 ⇒ 按 64 字节/个上 slack,def 数 ≤ 16 +# ⇒ ≤ 1024)+ 包裹头尾开销 512 ⇒ ≤ 26112。 +# 缩进是"块路径"的唯一放大项,故上界必须按"行数上界"而非"行数×行长"估; +# 提升行按原文进入模块区(不缩进),其代价低于按 4 空格放大的上界,故不再附加项。 +_REPL_FRAG_SRC_CAP: t.CDefine = 26624 +# 提升出的模块级 def 源码缓冲容量(含 NUL):提升行原文 ≤ 8192 + 注入 ≤ 1024 +_REPL_LIFT_CAP: t.CDefine = 12288 +# 单片段最多提升的会话函数数(≤ GenTypes.REPL_FUNC_CAP;超出 ⇒ 明确报错) +_REPL_LIFT_MAX: t.CDefine = 16 +# 会话函数名长度上限(写入 ReplLiftNames 的槽宽 = 上限 + 1) +_REPL_FN_NAME_MAX: t.CDefine = 64 +# 会话函数名表容量(_REPL_LIFT_MAX 个定长槽) +_REPL_FN_NAMES_CAP: t.CDefine = 1024 +# define 行里单个类型串的拷贝缓冲("i32"/"i8*"/"%struct.x*" 截断到这里再比较) +_REPL_FN_TY_CAP: t.CDefine = 64 +# 诊断显示缓冲(把非 NUL 结尾的行/名字截断后供 %s 打印) +_REPL_DIAG_CAP: t.CDefine = 128 +# 签名渲染缓冲容量("(int, str) -> int" 最长 ≈ 8×(5+2)+8+8 < 96) +_REPL_SIG_TEXT_CAP: t.CDefine = 192 +# 片段符号名/探针缓冲容量("__repl_frag_" / "define i32 @(i8* %__repl_ctx__") +_REPL_NAME_CAP: t.CDefine = 64 +_REPL_NEEDLE_CAP: t.CDefine = 128 +# 临时文件路径缓冲容量(目录/源文件路径) +_REPL_PATH_CAP: t.CDefine = 512 +# 完整模块 IR 文本容量(与 App/main.vp IR_BUF_SIZE 一致) +_REPL_IR_CAP: t.CDefine = 262144 +# GetTempPathA 输出缓冲容量(MAX_PATH) +_REPL_TMP_CAP: t.CDefine = 260 +# 外部命令(clang++ -shared)命令行容量。 +# M4 扩容 1024 → 16384:片段链接命令行现在除了宿主 import lib,还要带上**先前 +# 片段**的 import lib(每个 ≈ 80~110 字节路径),否则"片段 N 定义的类/方法"在 +# 后续片段里是 undefined symbol(见 ReplFragImplibs 段)。 +_REPL_CMD_CAP: t.CDefine = 16384 +# includes 源文件读取缓冲(与 Phase1.SRC_BUF_SIZE / BuildPipeline.SRC_BUF_SIZE 一致) +_REPL_SRC_BUF_CAP: t.CDefine = 1048576 +# .vpi 存根缓冲(与 BuildPipeline.PYI_BUF_SIZE 一致) +_REPL_VPI_BUF_CAP: t.CDefine = 262144 +# ---- M4:会话 sha1 / 片段 import lib / 会话基线(CDefine + CTypedef)---- +# 先前片段 import lib 的槽数上限(每个"IR 里出现会话前缀符号"的片段最多一条; +# 判据是"出现过"而非"定义过" ⇒ 纯调用片段也会占一个槽,故留足余量) +_REPL_FRAG_IMPLIB_MAX: t.CDefine = 128 +# 片段 import lib 记录数组容量(槽数 × 8 字节指针) +_REPL_FRAG_IMPLIB_ARR_CAP: t.CDefine = 1024 +# 会话基线(CDefine)条目上限:每片段重建表时按基线回灌 +_REPL_BASE_CDEFINE_MAX: t.CDefine = 512 +# 会话基线(CTypedef)条目上限 +_REPL_BASE_CTYPEDEF_MAX: t.CDefine = 512 +# includes 类注册(会话初始化做一次):参与 vpi 注入的模块数上限 +_REPL_INC_CLS_MOD_MAX: t.CDefine = 512 +# includes 类注册的收敛遍数(字段类型依赖同批其它类 ⇒ 多遍收敛) +_REPL_INC_CLS_PASSES: t.CDefine = 3 +# includes 类注册的结构体表水位守卫(STRUCT_MAX = 512;预留余量) +_REPL_INC_CLS_STRUCT_GUARD: t.CDefine = 448 +# includes 类注册的逐模块告警行上限(超出只累计计数,由汇总行给出总数) +_REPL_INC_CLS_WARN_MAX: t.CDefine = 3 +# includes 类注册(会话初始化)的启动时间预算(ms):多遍 vpi 注入 + 逐类布局校验是 +# 会话启动的主要耗时,且失败模块会在后续遍重试 ⇒ 模块数 × 遍数的上界虽有限,但 +# 上界本身可能很长(缺存根时每模块仍要读一遍 .vpi + 解析 + 注入了 declare)。 +# 超预算即停止"启动期注册",剩余模块留待片段 `import` 时按既有 on-demand 路径注册。 +# 取舍:宁可让部分 includes 类在启动后仍不可用(且明确告知),也不能让启动"看起来 +# 死了"——提示符必须先出现(REPL 可用性 > 启动期注册完整度)。 +_REPL_INC_CLS_BUDGET_MS: t.CDefine = 20000 +# includes 类注册用的临时 LLVM 模块名(.vpi 注入的目标模块,会话初始化时一次) +_REPL_INC_MOD_NAME: t.CDefine = "repl_inc_init" +# ctx 会话缓冲字节数 = 固定头 64 + 槽数上限 64 × 槽步长 16 +#(M3 追加函数指针槽区:+ 函数上限 32 × 槽步长 16 ⇒ 64 + 1024 + 512 = 1600) +# 必须与 GenTypes.REPL_CTX_SIZE / REPL_SLOT_BASE / REPL_VAR_CAP / REPL_SLOT_SIZE / +# REPL_FUNC_BASE / REPL_FUNC_CAP / REPL_FUNC_SLOT_SIZE 一致 +_REPL_CTX_SIZE: t.CDefine = 1600 +# 模板工程文件名(--repl 未给 --project 时在 cwd → exe 同目录查找) +_REPL_PROJECT_FILE: t.CDefine = "repl.vpj" +# cwd 候选后缀:GetCurrentDirectoryA 返回值不含结尾分隔符,故须补 '/' +_REPL_PROJECT_SEP_FILE: t.CDefine = "/repl.vpj" +# 上述两种后缀的最长长度 + NUL 余量(候选缓冲余量检查用) +_REPL_PROJECT_PROBE_CAP: t.CDefine = 16 + + +# ============================================================ +# ReplFloatView - ctx 头的浮点视图(宿主回显浮点结果用) +# +# 与 GenTypes.ReplResult 同一块内存:F 位于偏移 16,正是 ReplResult.A 的 8 字节。 +# 片段把 double 的 IEEE754 位模式写进 result_a;宿主按本视图读回即得 double +# (位模式重解释,不做数值转换)。 +# ============================================================ +@t.NoVTable +class ReplFloatView: + """ctx 头(+16 处按 double 读)""" + Magic: int + Version: int + Kind: int + Reserved0: int + F: t.CDouble + + +# ============================================================ +# ReplFuncSlotView - ctx 函数指针槽视图(M3;宿主写入片段函数的入口地址) +# +# 与 GenTypes.REPL_FUNC_BASE/REPL_FUNC_SLOT_SIZE 同一布局约定:每个会话函数占 +# 16 字节(低 8 字节 = 地址,高 8 字节保留)。片段侧按同一偏移(i8** 视图) +# load 该地址后间接调用;宿主用本视图写入 ⇒ 两侧对"槽内是什么"只有一个约定。 +# ============================================================ +@t.NoVTable +class ReplFuncSlotView: + """ctx 函数指针槽(Addr = 定义片段 DLL 里该函数的入口地址)""" + Addr: VOIDPTR + Reserved: VOIDPTR + + +# ============================================================ +# ReplSigOut - 会话函数签名解析结果(M3;宿主从片段 IR 的 define 行解析) +# +# 出处:定义片段 translate 后 dump 的 IR 里 +# `define @f(i8* %__repl_ctx__, %p1, ...)` +# 是定义端 ABI 的唯一真相(注解 → LLVM 类型由 codegen 解析)。宿主据此填 +# ReplFuncEntry 的签名 token;调用片段再用 token 重建同型签名 ⇒ 两侧一致。 +# 形参/返回的 token 数组写进会话 scratch(ReplSigTags/ReplSigBits)。 +# ============================================================ +@t.NoVTable +class ReplSigOut: + """签名解析结果(Bad/EHTag 非 0 时拒绝注册)""" + RetTag: int # GenTypes.REPL_TY_VOID / INT / FLOAT / STR + RetBits: int + ParamCount: int # 不含 ctx 首形参 + EHTag: int # 1=define 含 %__eh_msg_out__/%__eh_code_out__(raise 语义) + Bad: int # 1=含不受支持的类型 token + BadType: str # 不受支持的类型串(指向 IR 文本内,仅用于打印;否则 None) + + +# ============================================================ +# 模板工程查找 - --repl 未给 --project 时的启动便利 +# +# 顺序:① 当前工作目录 repl.vpj → ② TransPyV.exe 同目录 repl.vpj。 +# 命中路径由 main 按 --project 等价路径喂给既有的 Config.Load_project_config +# 加载流程(不另写加载器);两处都没有时由调用方按未命中报错。 +# Out/Diag 由调用方(main,stdlib.malloc)提供:本函数在会话初始化之前调用, +# 故只用入参缓冲 + 一次性 stdlib.malloc,不碰 Repl 会话缓冲与 PermPool。 +# ============================================================ +def ReplFindProject(Out: bytes, Cap: int, Diag: bytes, DiagCap: int) -> int: + """cwd → exe 同目录 查找 repl.vpj:1=命中(写 Out),0=未命中(写 Diag)""" + if Out is None or Diag is None: + return 0 + if Cap < 64 or DiagCap < 64: + return 0 + # 两个候选各用独立缓冲:未命中时两条路径都要进诊断文本 + c1: bytes = stdlib.malloc(Cap) + c2: bytes = stdlib.malloc(Cap) + if c1 is None or c2 is None: + if c1 is not None: + stdlib.free(c1) + if c2 is not None: + stdlib.free(c2) + return 0 + c1[0] = 0 + c2[0] = 0 + # ① cwd/repl.vpj + n_cwd: ULONG = w32file.GetCurrentDirectoryA(ULONG(Cap), c1) + if n_cwd > 0: + cl: int = int(n_cwd) + # 防御:缓冲区不足时 GetCurrentDirectoryA 的写入内容未定义,强制补 NUL + c1[Cap - 1] = 0 + if cl + _REPL_PROJECT_PROBE_CAP <= Cap: + viperlib.snprintf(c1 + cl, Cap - cl, "%s", _REPL_PROJECT_SEP_FILE) + if _is_file(c1) != 0: + string.strcpy(Out, c1) + stdlib.free(c1) + stdlib.free(c2) + return 1 + else: + string.strcpy(c1, "(GetCurrentDirectoryA 失败)") + # ② exe 同目录/repl.vpj(GetModuleFileNameA(NULL) 取自身全路径) + n_exe: ULONG = w32file.GetModuleFileNameA(None, c2, ULONG(Cap)) + if n_exe > 0: + el: int = int(n_exe) + # 防御:截断时返回值 == Cap 且串可能未 NUL 结尾 + c2[Cap - 1] = 0 + if el < Cap: + c2[el] = 0 + # 定位最后一个分隔符:c2[0..dlen) 为目录前缀(含分隔符); + # dlen=0 表示 exe 路径无目录部分(等价于 cwd,交由 OS 解析) + dlen: int = 0 + si: int = el + while si > 0: + si -= 1 + ch: char = c2[si] + if ch == '/' or ch == '\\': + dlen = si + 1 + break + if dlen + _REPL_PROJECT_PROBE_CAP <= Cap: + viperlib.snprintf(c2 + dlen, Cap - dlen, "%s", _REPL_PROJECT_FILE) + if _is_file(c2) != 0: + string.strcpy(Out, c2) + stdlib.free(c1) + stdlib.free(c2) + return 1 + else: + string.strcpy(c2, "(GetModuleFileNameA 失败)") + # 两处都没有:诊断文本(需要 --project + 两个已查位置 + 模板位置) + viperlib.snprintf(Diag, DiagCap, + " 已查找(1): %s\n" + " 已查找(2): %s\n" + " 需要 --project: 未找到 repl.vpj 时 REPL 拿不到工程配置," + "请用 --project 指定\n" + " 模板位置: 仓库 TransPyV/repl.vpj;拷到 TransPyV.exe 同目录" + "或当前工作目录即可被自动找到\n", + c1, c2) + stdlib.free(c1) + stdlib.free(c2) + return 0 + + +# ============================================================ +# 会话状态 +# +# 常驻缓冲一律 PermPool 分配(ReplInitSession 一次性建,进程退出随池释放); +# 按片段产生的短命串(.ll/.obj/.dll 路径、命令行)来自 mb,行末 reset_to(mark) 回收。 +# ============================================================ +# 片段函数指针槽:t.Callable 注解 → i8** 槽(VET/App/lib/VGuard.vp:358 同款范式)。 +# 模块级 AnnAssign 的返回类型签名由 TranslatorGenMixin.vp:721-742 登记 → +# 调用点经 CallGenMixin.vp:960-984 走"按实参构造签名 + 间接 call"路径。 +# M2 约定:唯一实参是 ctx 基址(i8*),返回状态码 int(结果经 ctx 回传)。 +ReplFragFn: t.Callable[[t.CPtr], int] = None +# 片段序号:模块级(longjmp 恢复后仍单调递增)→ 符号名/DLL 文件名在会话内不复用 +ReplFragNo: int = 1 +# 会话临时目录 %TEMP%\TransPyV_repl\(不污染用户工程目录;pid 隔离并发会话) +ReplTmpDir: bytes = None +# 片段符号名 __repl_frag_ +ReplNameBuf: bytes = None +# 包裹后的片段源码(供解析/翻译) +ReplSrcBuf: bytes = None +# 包裹源码落盘路径(错误回显用真实源文件) +ReplSrcPath: bytes = None +# 块输入缓冲(会话级 PermPool,容量 _REPL_BLOCK_CAP):续行状态下逐行追加的用户原文, +# 行间以 '\n' 连接、无行尾换行。一次性分配,不按行 malloc。 +ReplBlockBuf: bytes = None +# 块缓冲当前字节数(0 = 无缓存内容;NUL 恒写在 ReplBlockBuf[ReplBlockLen]) +ReplBlockLen: int = 0 +# 续行状态:1 = 正在缓存多行块(提示符 "... ");0 = 正常(提示符 ">>> ") +ReplInBlock: int = 0 +# 完整模块 IR 文本(dump_ir OUTPUT_FULL 的输出缓冲) +ReplIrBuf: bytes = None +# IR 探针 "define i32 @(i8* %__repl_ctx__"(确认导出函数签名与调用约定一致) +ReplNeedleBuf: bytes = None +# ctx 会话缓冲(宿主与片段共享;字节视图,作为片段首实参传递) +ReplCtxBuf: bytes = None +# 会话上下文对象(Gen 侧读:表/容量/形参名/永久池/影子条目) +ReplCtxObj: rawptr[GenTypes.ReplCtx] = None +# 会话变量表(PermPool,ReplCtxObj.Table) +ReplVarTable: rawptr[GenTypes.ReplVarEntry] = None +# 影子条目(PermPool):仅供"裸表达式结果"做类型分类,不占会话槽、不进表 +ReplScratch: rawptr[GenTypes.ReplVarEntry] = None +# 恢复点 jmp_buf(PermPool,会话期存活):run 里 _setjmp 到它, +# VLogger.repl_abort 从流水线任意深度 longjmp 回它。每片段入口重注册(幂等)。 +ReplJmpBuf: t.CPtr = None +# 片段开始时的会话表水位(ReplCtxObj.Count):恢复时回滚到它, +# 丢弃失败片段的半成品登记(repl_ensure_entry 已 Count+1 但 repl_seal 报错的条目)。 +ReplSavedVarCount: int = 0 + +# ==== includes 会话初始化结果(会话级一次;供摘要行与恢复分支报告进度)==== +# 已注册的 includes 模块数(StubMerger SHA1 表条目数) +ReplIncMods: int = 0 +# includes 扫描耗时(ms) +ReplIncMs: int = 0 +# 新生成的 .vpi 存根数 +ReplIncVpiNew: int = 0 +# 复用的既有 .vpi 存根数 +ReplIncVpiReuse: int = 0 +# .vpi 生成失败数(读/解析/写盘失败) +ReplIncVpiFail: int = 0 + +# ==== 宿主导出符号(跨模块调用)==== +# 宿主 exe 的 import lib 路径(.dll.a;PermPool,会话级一次探测)。 +# 非 None ⇒ 片段链接命令把它加进去 ⇒ 片段 DLL 从宿主 exe 导入 includes 符号, +# 与宿主共用同一份代码与数据(无状态重复)。None ⇒ 宿主未按导出模式构建。 +ReplHostImplib: bytes = None + +# ==== M3 会话函数(跨片段函数)==== +# 提升出的模块级 def 源码(会话级 PermPool;每片段开头清零长度) +ReplLiftBuf: bytes = None +# 提升出的会话函数名表(PermPool,_REPL_FN_NAME_MAX 定长槽 × _REPL_LIFT_MAX 个) +ReplLiftNames: bytes = None +# 本次片段提升出的会话函数个数 +ReplLiftNameCount: int = 0 +# 签名 token scratch(PermPool,各 GenTypes.REPL_FUNC_PARAM_MAX 字节;逐函数复用) +ReplSigTags: bytes = None +ReplSigBits: bytes = None +# 签名解析结果体(PermPool,会话级复用;名字避开类名 ReplSigOut) +ReplSigResult: rawptr[ReplSigOut] = None +# 会话函数表水位(ReplCtxObj.FuncCount)在片段开始时的快照:失败/回滚时恢复到它, +# 丢弃失败片段的半成品登记(与 ReplSavedVarCount 同思路,见 ReplExecFragment ⓪) +ReplSavedFuncCount: int = 0 +# 诊断用行/名字显示缓冲(PermPool,_REPL_DIAG_CAP;%s 不能直接打非 NUL 结尾的行) +ReplLineShow: bytes = None +# def 头名字 scratch(PermPool,_REPL_FN_NAME_MAX) +ReplMsgName: bytes = None +# 签名解析的类型串 scratch(PermPool,_REPL_FN_TY_CAP) +ReplTySpan: bytes = None +# 签名解析的"%+形参名"合成 scratch(PermPool,_REPL_FN_TY_CAP) +ReplTySpan3: bytes = None +# IR 里最近命中的 define 行起点偏移(_repl_ir_find_define_at 写、_repl_ir_func_sig 读) +ReplIrDefOff: int = -1 +# 签名渲染缓冲 ×2(PermPool,_REPL_SIG_TEXT_CAP):新签名 / 已有条目签名对比展示 +ReplSigText: bytes = None +ReplSigText2: bytes = None +# 片段体(包裹)当前写入位置(_repl_split_body 写、_repl_build_frag_src_multi 读) +ReplWrapPos: int = 0 + +# ==== M4:会话模块 sha1(类型名的跨片段唯一键)==== +# PermPool 上的 16 位 hex 串(GenTypes.REPL_SESSION_SHA1 的副本)。 +# 每个片段翻译器都取它作 ModuleSha1 ⇒ 片段内定义的类名恒定形如 +# ".",后续片段据同一前缀查到同一结构体条目。 +ReplSessionSha1: bytes = None + +# ==== M4:先前片段的 import lib(跨片段方法/构造符号的解析来源)==== +# 片段 DLL 只把自己 obj 与"已记录 import lib"链在一起:片段 N 里的 +# `p = Point(1, 2)`(Point 定义在片段 M.Point.__init__ +# 的引用,该符号只存在于片段 M 的 DLL 里 ⇒ 必须把 M 的 import lib 加进 N 的链接 +# 命令行。数组槽内是 PermPool 里的路径串指针(8 字节/槽)。 +ReplFragImplibs: sizet | t.CPtr = None +ReplFragImplibCount: int = 0 + +# ==== M4:会话基线(CDefine / CTypedef 的跨片段存活)==== +# 每片段开头的 clear_cdefine_constants/clear_ctypedefs 会把表指针置 None +# (表内容随片段池一起回收),而表的语义是"整个会话一份"(includes 注入的常量、 +# include 类型别名、片段里 `N: t.CDefine = 3` 等)⇒ 用会话基线 + 片段增量: +# - 基线:PermPool 里的 (名字, 值/类型串, 种类) 三元组数组,只在**成功**片段 +# 结束时追加该片段新增的条目(失败片段不追加 ⇒ 无污染,水位回滚天然成立); +# - 每片段清空表后按基线回灌(条目分配在本片段池,随片段回收); +# - 失败片段用 ReplSavedCDefineCount/ReplSavedCTypedefCount 截断(防御性, +# 与 ReplSavedVarCount/ReplSavedFuncCount 同思路)。 +ReplBaseCDefineNames: sizet | t.CPtr = None +ReplBaseCDefineValues: sizet | t.CPtr = None +ReplBaseCDefineKinds: sizet | t.CPtr = None # 每条目 8 字节:0=int / 1=double / 2=str +ReplBaseCDefineCount: int = 0 +ReplBaseCTypedefNames: sizet | t.CPtr = None +ReplBaseCTypedefTypeStrs: sizet | t.CPtr = None +ReplBaseCTypedefCount: int = 0 +# 片段开始时的基线水位(失败/回滚时截断回它) +ReplSavedCDefineCount: int = 0 +ReplSavedCTypedefCount: int = 0 + +# ==== M4:includes 类注册(会话初始化做一次)==== +# 已成功注入 .vpi(⇒ 其类进入会话 StructTable)的模块数 / 失败数 / 缺存根数 +ReplIncClsMods: int = 0 +ReplIncClsFail: int = 0 +ReplIncClsMissing: int = 0 + + +# ============================================================ +# 私有 helper +# ============================================================ + +# 路径是否为"存在的普通文件"(GetFileAttributesA:只返回属性值,无输出缓冲、无越界风险) +def _is_file(path: str) -> int: + """1=存在且非目录;0=不存在/是目录/参数错误""" + if path is None: + return 0 + attr: ULONG = w32file.GetFileAttributesA(path) + if attr == 0xFFFFFFFF: # INVALID_FILE_ATTRIBUTES + return 0 + if (attr & w32file.FILE_ATTRIBUTE_DIRECTORY) != 0: + return 0 + return 1 + + +# ============================================================ +# 宿主导出符号:定位宿主 exe 的 import lib(跨模块调用) +# +# 为什么需要:片段 IR 对 includes 的引用是 `.<函数名>`(.text.ll 实证: +# `define i32 @".utf8_is_valid"`;模块级全局同理 `.<变量名>`,如 +# `@"._memdbg_enable" = global i32 zeroinitializer`),这些符号由**宿主 exe** +# 定义(exe 链接时已把 includes.binary 的 obj 全部链入,实测 output/TransPyV.exe +# 的 COFF 符号表含 118 个 `<16位hex>.` 前缀)。片段链接命令若只含片段自身 obj, +# 跨模块符号会以 undefined symbol 停在链接期。 +# +# 机制(方案 C):宿主以 `-Wl,--export-all-symbols` 构建 ⇒ exe 带导出表; +# BuildPipeline 同时产出 `{OutputDir}/{exe 名}.dll.a`(`-Wl,--out-implib=`)。 +# 片段链接时把该 import lib 加进命令行 ⇒ 片段 DLL 的导入表指向宿主 exe, +# 加载时由同一进程内已加载的宿主满足 ⇒ 宿主与片段**共用同一份** includes 代码与 +# 数据(状态不重复;片段 DLL 内不存在任何 `.` 定义)。 +# +# 查找顺序:① 本 exe 同目录(默认构建把 exe 与 .dll.a 一起写在 OutputDir) +# ② Config.OutputDir(--project 指向真正宿主的 vpj 时) +# 两处都没有 ⇒ 不报错,按旧口径只用片段自身 obj 链接(失败时给出明确提示)。 +# ============================================================ +def ReplFindHostImplib() -> int: + """定位宿主 import lib(.dll.a):1=命中(写 ReplHostImplib),0=未找到""" + global ReplHostImplib + if _perm_pool is None: + return 0 + ReplHostImplib = None + exe_buf: bytes = _perm_pool.alloc(_REPL_PATH_CAP) + cand: bytes = _perm_pool.alloc(_REPL_PATH_CAP) + if exe_buf is None or cand is None: + return 0 + exe_buf[0] = 0 + cand[0] = 0 + n_exe: ULONG = w32file.GetModuleFileNameA(None, exe_buf, ULONG(_REPL_PATH_CAP)) + if n_exe <= 0: + return 0 + el: int = int(n_exe) + # 防御:截断时写入内容可能未 NUL 结尾(与 ReplFindProject 同口径) + if el >= _REPL_PATH_CAP: + el = _REPL_PATH_CAP - 1 + exe_buf[el] = 0 + # 拆目录/文件名:dlen = 目录前缀长度(含分隔符) + dlen: int = 0 + si: int = el + while si > 0: + si -= 1 + ch: char = exe_buf[si] + if ch == '/' or ch == '\\': + dlen = si + 1 + break + # 候选 ①:exe 同目录(exe_buf 已含目录前缀) + viperlib.snprintf(cand, _REPL_PATH_CAP, "%s%s.dll.a", exe_buf, exe_buf + dlen) + if _is_file(cand) != 0: + ReplHostImplib = cand + return 1 + # 候选 ②:Config.OutputDir(REPL 用 repl.vpj 起、宿主用 project.vpj 建时) + if Config.OutputDir is not None: + viperlib.snprintf(cand, _REPL_PATH_CAP, "%s/%s.dll.a", + Config.OutputDir, exe_buf + dlen) + if _is_file(cand) != 0: + ReplHostImplib = cand + return 1 + return 0 + + +# 逐字节 ReadFile 读取一行:控制台(ENABLE_LINE_INPUT,回车后返回整行)与管道均适用 +def _read_line(h: win32base.HANDLE, buf: bytes, cap: int) -> int: + """从 h 读取一行写入 buf(NUL 结尾),返回字节数(不含 NUL);EOF/错误返回 -1。 + + 剥离 CR(兼容 CRLF 输入),遇 LF 结束;缓冲区满时停止(余量留作下一行)。 + """ + if buf is None or cap < 2: + return -1 + total: int = 0 + bytes_read: ULONG = 0 + ok: BOOL = 0 + eof: int = 0 + while total < cap - 1: + bytes_read = 0 + ok = w32file.ReadFile(h, buf + total, 1, t.CPtr(c.Addr(bytes_read)), None) + if ok == 0 or bytes_read == 0: + eof = 1 + break + # 注意:不能用 c.Deref(buf + total)——其 IR 生成 i32 load(读 4 字节), + # 比较 '\n'/'\r' 永不命中;字节索引 buf[total] 才是 i8 load + ch: char = buf[total] + if ch == 10: # '\n':行结束 + break + if ch == 13: # '\r':CRLF,丢弃后原位覆盖读取 + continue + total += 1 + buf[total] = 0 + if eof != 0 and total == 0: + return -1 + return total + + +# 统计顶层语句数(Module.children) +def _count_top_stmts(tree: rawptr[ast.AST]) -> int: + """返回 Module 的顶层语句数(children 为空时为 0)。""" + if tree is None or tree.children is None: + return 0 + stmts: list[ast.AST*]* = tree.children + return int(len(stmts)) + + +# ============================================================ +# 块级续行(多行块输入) +# +# 判据(与用户拍板口径一致,刻意保持"最简单可预测"): +# 进入:非续行状态下,整行去掉行尾空白后以 ':' 结尾 ⇒ 进入续行,该行进缓冲、不执行; +# 结束:续行中读到空行(长度 0)或 EOF ⇒ 整块作为**一个**片段执行一次; +# 续行中不做任何缩进/关键字分析,行尾 ':' 也不反复切换状态。 +# 为什么只看行尾 ':':Viper 的块头(def/class/for/if/while/with/try/match/case…) +# 一律以 ':' 收尾,而完整单行语句几乎不会以 ':' 结尾 ⇒ 判据零误报面; +# 唯一代价是"行尾带 ':' 的非法/罕见单行"会多缓存一行,空行即执行,可预期。 +# 为什么空行结束:与 Python REPL 一致(空行 = 块输入完毕),且无需缩进分析; +# 批处理源码里空行不影响语法(lexer 的 _handle_indent 跳过空行/纯空白行)。 +# ============================================================ + +# 行尾判据:去掉行尾空白后以 ':' 结尾;或(M3)首个非空白字符是 '@'(装饰器行) +def _line_starts_block(line: str, n: int) -> int: + """1=该行进入块缓冲(':' 结尾 / '@' 开头);0=否 + + '@' 开头的行单独成句在 Viper/Python 里都是语法错误(装饰器必须后接 def/class), + 故把它纳入"进入续行"判据零误报面;这是 M3 会话函数能用 @decorator 的前提 + (装饰器行不以 ':' 结尾,否则用户无法把装饰器连同 def 一起送进一个块)。 + """ + if line is None or n <= 0: + return 0 + s: int = 0 + while s < n and (line[s] == ' ' or line[s] == '\t'): + s += 1 + if s >= n: + return 0 + if line[s] == '@': + return 1 + i: int = n + while i > 0: + ch: char = line[i - 1] + if ch != ' ' and ch != '\t': + break + i -= 1 + if i <= 0: + return 0 + if line[i - 1] == ':': + return 1 + return 0 + + +# 追加一行到块缓冲:行间补一个 '\n',末尾写 NUL +def _block_append(line: str, n: int) -> int: + """把 n 字节行追加到会话块缓冲;0=成功,1=容量不足(此时缓冲内容未改)""" + global ReplBlockLen + if ReplBlockBuf is None or line is None or n <= 0: + return 1 + sep: int = 0 + if ReplBlockLen > 0: + sep = 1 # 行间 '\n' + if ReplBlockLen + sep + n >= _REPL_BLOCK_CAP: # 末尾还需 1 字节 NUL + return 1 # 先判容量后写 ⇒ 失败时无需回滚 + if ReplBlockLen > 0: + ReplBlockBuf[ReplBlockLen] = '\n' + ReplBlockLen += 1 + string.memcpy(ReplBlockBuf + ReplBlockLen, line, t.CSizeT(n)) + ReplBlockLen += n + ReplBlockBuf[ReplBlockLen] = 0 + return 0 + + +# ctx 头视图(int64 解释):宿主读写 magic/kind/result_a... +def _ctx_head() -> rawptr[GenTypes.ReplResult]: + """ctx 头视图;会话未初始化返回 None""" + if ReplCtxBuf is None: + return None + return rawptr[GenTypes.ReplResult](ReplCtxBuf) + + +# ctx 头视图(double 解释 result_a):回显浮点结果用 +def _ctx_float_head() -> rawptr[ReplFloatView]: + """ctx 头浮点视图;会话未初始化返回 None""" + if ReplCtxBuf is None: + return None + return rawptr[ReplFloatView](ReplCtxBuf) + + +# 结果回显:按 result_kind 读 ctx 结果区并打印 +def _repl_echo_result(frag_name: str, res: rawptr[GenTypes.ReplResult]) -> int: + """按 result_kind 回显本片段结果;0=已处理(含"无结果")""" + if frag_name is None or res is None: + return 1 + k: int = res.Kind + if k == GenTypes.REPL_RES_NONE: + # 声明类片段(如 `x: int`):本行无结果 + return 0 + if k == GenTypes.REPL_RES_INT: + stdio.printf("%s => %lld\n", frag_name, res.A) + return 0 + if k == GenTypes.REPL_RES_FLOAT: + fv: rawptr[ReplFloatView] = _ctx_float_head() + if fv is None: + return 1 + stdio.printf("%s => %f\n", frag_name, fv.F) + return 0 + if k == GenTypes.REPL_RES_STR: + sp: t.CPtr = t.CPtr(res.A) + if sp is None: + stdio.printf("%s => \n", frag_name) + return 0 + stdio.printf("%s => %s\n", frag_name, sp) + return 0 + # REPL_RES_OBJ:result_a != 0 → 非字符串指针(报地址);result_a == 0 → + # 结构体值(内联在槽里,本身没有地址,类型名需跨片段类型注册表,属 M4) + if res.A == 0: + stdio.printf("%s => \n", frag_name) + return 0 + stdio.printf("%s => \n", frag_name, res.A) + return 0 + + +# ============================================================ +# M4 - 会话 sha1 / 片段 import lib / 会话基线(CDefine + CTypedef)/ includes 类注册 +# +# 三段机制各自解决一个"跨片段"问题(设计论证见 repl-design.md §M4): +# ① 会话 sha1:片段翻译器的 ModuleSha1 恒定取 GenTypes.REPL_SESSION_SHA1 +# ⇒ 片段内定义的类型名恒定 ".",会话 StructTable 的注册与后续 +# 片段查找用同一个键(这是类型跨片段可见的唯一基础)。 +# ② 片段 import lib:片段链接时加 --out-implib;若该片段 IR 里**定义**了会话前缀 +# 符号(类方法/构造/类全局),登记其 .dll.a,后续片段链接时全部带上 ⇒ 那些 +# 符号在链接期解析到**定义片段**的 DLL(代码与数据都只有一份)。 +# ③ 会话基线:CDefine/CTypedef 表每片段被 clear_* 置空 ⇒ 用 PermPool 基线回灌 + +# 片段成功后追加增量,失败只按水位截断(表语义 = 整会话一份)。 +# ============================================================ + +# 串深拷贝进 pool(调用方给池;池/串无效或分配失败返回 None) +def _repl_str_dup(pool: rawptr[memhub.MemBuddy], s: str) -> str: + """深拷贝 s 到 pool;失败返回 None""" + if pool is None or s is None: + return None + n: int = int(string.strlen(s)) + b: str = pool.alloc(n + 1) + if b is None: + return None + string.memcpy(b, s, t.CSizeT(n)) + b[n] = 0 + return b + + +# 建会话 sha1 串(PermPool;片段翻译期作为 ModuleSha1 使用) +def ReplInitSessionSha1() -> int: + """0=成功;非 0=永久池/分配失败""" + global ReplSessionSha1 + if _perm_pool is None: + return 1 + ReplSessionSha1 = _repl_str_dup(_perm_pool, GenTypes.REPL_SESSION_SHA1) + if ReplSessionSha1 is None: + return 1 + return 0 + + +# ---- 会话基线(CDefine/CTypedef):查找 / 回灌 / 追加 / 截断 ---- + +def _repl_base_cdefine_find(name: str) -> int: + """基线里按名查找 CDefine 条目下标;未命中返回 -1""" + if name is None or ReplBaseCDefineNames is None: + return -1 + i: int + for i in range(ReplBaseCDefineCount): + nm: str = (str | t.CPtr)(ReplBaseCDefineNames[i]) + if nm is not None and string.strcmp(nm, name) == 0: + return i + return -1 + + +def ReplBaselineRestore(pool: rawptr[memhub.MemBuddy]) -> int: + """把会话基线(CDefine + CTypedef)回灌进刚被 clear_* 清空的表;0=成功 + + 条目分配在 pool(= 本片段池):表每片段被清空,条目只需活到本片段结束。 + 回灌在 clear_* 之后、翻译之前调用 ⇒ 片段内的名字解析能同时看到 + "会话基线(先前片段/先前 import 带进来的)∪ 本片段新增"。 + """ + if pool is None or ReplBaseCDefineNames is None: + return 1 + if GenApi._g_gen is None: + return 1 + i: int + for i in range(ReplBaseCDefineCount): + nm: str = (str | t.CPtr)(ReplBaseCDefineNames[i]) + val: str = (str | t.CPtr)(ReplBaseCDefineValues[i]) + kind: int = int(ReplBaseCDefineKinds[i]) + if nm is None or val is None: + continue + if kind == 0: + GenApi.register_cdefine_constant(pool, nm, int64(string.atoll(val))) + elif kind == 1: + GenApi.register_cdefine_double(pool, nm, string.atof(val)) + else: + # 字符串 CDefine:GenApi 无同名壳,经 gen 单例直达(与 clear_cdefine_constants + # 的壳同款调用形态) + GenApi._g_gen.TypeGenCDef.register_cdefine_string(pool, nm, val) + for i in range(ReplBaseCTypedefCount): + tn: str = (str | t.CPtr)(ReplBaseCTypedefNames[i]) + tv: str = (str | t.CPtr)(ReplBaseCTypedefTypeStrs[i]) + if tn is None or tv is None: + continue + GenApi.register_ctypedef(pool, tn, tv) + return 0 + + +def ReplBaselineCapture() -> int: + """把当前表里"基线里还没有"的 CDefine/CTypedef 条目追加进会话基线;0=成功 + + 只在片段**成功执行**后调用(失败片段不调用 ⇒ 基线天然不含失败片段的半成品, + 无需回滚内容;截断只是防御性的一致性保障)。新条目深拷贝进 PermPool。 + """ + global ReplBaseCDefineCount, ReplBaseCTypedefCount + if _perm_pool is None or GenApi._g_gen is None: + return 1 + cdef_names: list[str] | t.CPtr = GenApi._g_gen.TypeGenCDef.CDefineNames + if cdef_names is not None: + cdef_vals: list[str] | t.CPtr = GenApi._g_gen.TypeGenCDef.CDefineValues + cdef_dbl: list[int] | t.CPtr = GenApi._g_gen.TypeGenCDef.CDefineDoubleFlags + cdef_str: list[int] | t.CPtr = GenApi._g_gen.TypeGenCDef.CDefineStrFlags + n: sizet = len(cdef_names) + i: sizet + for i in range(n): + nm: str = cdef_names.get(i) + if nm is None: + continue + if _repl_base_cdefine_find(nm) >= 0: + continue + if ReplBaseCDefineCount >= _REPL_BASE_CDEFINE_MAX: + break + val: str = None + if cdef_vals is not None: + val = cdef_vals.get(i) + if val is None: + continue + kind: int = 0 + if cdef_str is not None and int(cdef_str.get(i)) == 1: + kind = 2 + elif cdef_dbl is not None and int(cdef_dbl.get(i)) == 1: + kind = 1 + nc: str = _repl_str_dup(_perm_pool, nm) + vc: str = _repl_str_dup(_perm_pool, val) + if nc is None or vc is None: + continue + j: int = ReplBaseCDefineCount + ReplBaseCDefineNames[j] = sizet(nc) + ReplBaseCDefineValues[j] = sizet(vc) + ReplBaseCDefineKinds[j] = sizet(kind) + ReplBaseCDefineCount = j + 1 + ct_names: list[str] | t.CPtr = GenApi._g_gen.TypeGenCType.CTypedefNames + if ct_names is not None: + ct_vals: list[str] | t.CPtr = GenApi._g_gen.TypeGenCType.CTypedefTypeStrs + cn2: sizet = len(ct_names) + i2: sizet + for i2 in range(cn2): + tn: str = ct_names.get(i2) + if tn is None: + continue + if ReplBaseCTypedefCount >= _REPL_BASE_CTYPEDEF_MAX: + break + dup_hit: int = 0 + k: int + for k in range(ReplBaseCTypedefCount): + ex: str = (str | t.CPtr)(ReplBaseCTypedefNames[k]) + if ex is not None and string.strcmp(ex, tn) == 0: + dup_hit = 1 + break + if dup_hit != 0: + continue + tv: str = None + if ct_vals is not None: + tv = ct_vals.get(i2) + if tv is None: + continue + tnc: str = _repl_str_dup(_perm_pool, tn) + tvc: str = _repl_str_dup(_perm_pool, tv) + if tnc is None or tvc is None: + continue + j2: int = ReplBaseCTypedefCount + ReplBaseCTypedefNames[j2] = sizet(tnc) + ReplBaseCTypedefTypeStrs[j2] = sizet(tvc) + ReplBaseCTypedefCount = j2 + 1 + return 0 + + +def ReplBaselineTruncate(cd_cnt: int, ct_cnt: int) -> None: + """把基线水位截断回片段开始时的值(失败片段的增量不得进基线)""" + global ReplBaseCDefineCount, ReplBaseCTypedefCount + if cd_cnt >= 0 and cd_cnt <= ReplBaseCDefineCount: + ReplBaseCDefineCount = cd_cnt + if ct_cnt >= 0 and ct_cnt <= ReplBaseCTypedefCount: + ReplBaseCTypedefCount = ct_cnt + + +# ---- 片段 import lib:登记 / 追加到链接命令行 ---- + +def ReplFragImplibRecord(implib_path: str) -> int: + """登记一个片段 import lib(PermPool 深拷贝);1=已登记,0=未登记(上限/分配失败)""" + global ReplFragImplibCount + if implib_path is None or ReplFragImplibs is None or _perm_pool is None: + return 0 + if ReplFragImplibCount >= _REPL_FRAG_IMPLIB_MAX: + return 0 + dup: str = _repl_str_dup(_perm_pool, implib_path) + if dup is None: + return 0 + ReplFragImplibs[ReplFragImplibCount] = sizet(dup) + ReplFragImplibCount += 1 + return 1 + + +# 把已登记的全部片段 import lib 追加到命令缓冲(空格分隔); +# 返回新的位置(-1 = 容量不足,调用方按"不带片段 import lib"重试也不可——直接报错) +def _repl_cmd_append_implibs(cmd: bytes, cap: int, pos: int) -> int: + if cmd is None or ReplFragImplibs is None: + return pos + i: int + for i in range(ReplFragImplibCount): + p: str = (str | t.CPtr)(ReplFragImplibs[i]) + if p is None: + continue + pos = _repl_cat_str(cmd, cap, pos, " ") + if pos < 0: + return -1 + pos = _repl_cat_str(cmd, cap, pos, p) + if pos < 0: + return -1 + return pos + + +# ---- includes 类注册(会话初始化一次;逐模块隔离,失败只告警)---- + +def ReplInitIncludesClasses(mb: rawptr[memhub.MemBuddy], + sr: rawptr[IncludesScanner.ScanResult], + vpi_dir: str) -> int: + """把各 includes 模块的 .vpi 注入临时模块 ⇒ 其类进入会话 StructTable;0=完成 + + 为什么需要:REPL 从不翻译 includes 源码 ⇒ 会话 StructTable(类型解析/字段查找/ + 方法归属的唯一入口)里没有任何 includes 类。`.vpi` 存根含类名 + 字段 + (Converter._PyiClass 序列化类体 AnnAssign 与 __init__ 里的 `self.x: T`)+ 方法 + 签名;VpiReader.ParseVpiIntoModule(即批处理 LoadDepVpiIntoModule 的最后一步) + 会把"字段类型全部可解析"的类注册为 StructEntry(含字段表)。 + 多遍收敛:字段类型可能引用同批的另一个类 ⇒ 单遍必然注册不全;本函数第 1 遍试 + 全部模块,第 2..N 遍只重试上一遍失败的模块(成功者不重复注入 ⇒ 代价受控)。 + 启动上界:整段注册有 `_REPL_INC_CLS_BUDGET_MS` 时间预算,超预算即停止尝试剩余 + 模块(计数进汇总行;这些模块的类在会话里不可用,片段 `import` 时仍按需注册)。 + 逐模块隔离:每模块一次 _setjmp(复用会话 jmp_buf)⇒ 单模块的 fatal/解析失败只 + 影响该模块(逐模块告警 + 该模块的类不可用),**不终止会话**、不影响其它模块。 + 告警聚合:逐模块告警最多打印 `_REPL_INC_CLS_WARN_MAX` 条,其余只累计计数, + 末尾一条汇总行给出"成功/失败/缺存根/未尝试/被省略告警"的数量。 + ⚠ 退出约定:本函数内部改写了 jmp_buf 的跳转目标(指向本帧)⇒ 返回后调用方必须 + 在**自己的帧上**重新武装恢复点(见 Repl.run 的 includes 段),否则后续 longjmp + 会跳到已返回的帧(未定义行为)。 + 收尾(ReplStubLayoutFinish/ReplStubLayoutEnd)**不在**本函数内做:它们只能在 + 调用方重新武装恢复点之后调用(见 Repl.run),否则"表满 → VLogger.error → + longjmp 到已返回帧"会在提示符出现前把进程带走。 + """ + global ReplIncClsMods, ReplIncClsFail, ReplIncClsMissing + if mb is None or sr is None or vpi_dir is None or sr.Entries is None: + return 1 + if ReplJmpBuf is None or _perm_pool is None: + return 1 + n_mod: int = int(sr.Count) + if n_mod <= 0: + return 1 + if n_mod > _REPL_INC_CLS_MOD_MAX: + n_mod = _REPL_INC_CLS_MOD_MAX + # 每模块状态(8 字节/槽,PermPool):0=未尝试/待重试、1=已完成(成功或确认不可用) + st: sizet | t.CPtr = _perm_pool.alloc(_REPL_INC_CLS_MOD_MAX * 8) + if st is None: + return 1 + string.memset(st, 0, _REPL_INC_CLS_MOD_MAX * 8) + # 注入目标:临时 LLVM 模块 + 临时翻译器(只用其注解解析上下文;模块本身丢弃) + # 结构体注册走永久分配(StdlibAlloc=1)⇒ 条目/字段/类型跨片段存活。 + GenApi.set_stdlib_alloc(1) + GenApi.reset_visible_structs(mb, 0) + GenApi.set_temp_dir(vpi_dir) + GenApi.SetTempDir(vpi_dir) + GenApi.set_current_file_type(vpi_dir) + tmp_mod: rawptr[llvmlite.LLVMModule] = llvmlite.new_module(_perm_pool, _REPL_INC_MOD_NAME) + tr_inc: rawptr[HandlesTranslator.Translator] = _perm_pool.alloc( + HandlesTranslator.Translator.__sizeof__()) + if tmp_mod is None or tr_inc is None: + return 1 + tr_inc.__before_init__() + tr_inc.__init__() + # 给临时翻译器一套真状态(函数表/符号表/名集合缓冲区 + Pool):VpiReader 的 + # 注入会读 trans._funcs(把 inject 出的 declare 登记进函数表,VpiReader:2130) + # 与 trans.Pool/imported_modules 等;缺它们会在注入中途解引用 None。 + tr_inc._init_state(_perm_pool) + tr_inc.ModuleSha1 = ReplSessionSha1 + tr_inc._declare_only = 0 + # ReplCtx 置位 = 声明本翻译器处于 REPL 会话(Gen 侧唯一开关,见 + # Translator.vp:256-257)。注入各模块 `.vpi` 时,其注解可能引用"会话里 + # **未登记**(M4.2 布局不可信被拒 / 字段类型不可解析被跳过)的类"——如 + # ast 包的 `rawptr[memhub.MemManager]`。此时由 TypeGenResolve 的 REPL 分支 + # (`trans.ReplCtx != None` 守卫)按定义侧命名造 opaque 具名类型占位,使注解 + # 可解析,避免 S4 fail-fast 中断整个模块的注入;StructTable 仍不登记该类 + # (字段访问照旧明确报错)。批处理 ReplCtx 恒 None ⇒ 该分支不经过。 + # 片段翻译另有自己的 Translator(ReplExecFragment 内同样置 ReplCtx)。 + tr_inc.ReplCtx = ReplCtxObj + es: sizet = IncludesScanner.FileEntry.__sizeof__() + dl: sizet = string.strlen(vpi_dir) + warn_left: int = _REPL_INC_CLS_WARN_MAX + warn_dropped: int = 0 + budget_t0: uint64 = win32base.GetTickCount64() + budget_hit: int = 0 + p_i: int + for p_i in range(_REPL_INC_CLS_PASSES): + pass_ok: int = 0 + i: int + for i in range(n_mod): + if int(st[i]) != 0: + continue + # 启动预算(每个模块一次取时;GetTickCount64 无系统调用开销): + # 超预算即停手,剩余待注册模块留给片段 `import` 的按需路径。 + if int(win32base.GetTickCount64() - budget_t0) > _REPL_INC_CLS_BUDGET_MS: + budget_hit = 1 + break + if GenApi.get_struct_count() >= _REPL_INC_CLS_STRUCT_GUARD: + stdio.printf("includes 类注册: 结构体表已达 %d 条(上限附近)," + "停止注册;未注册的类在会话里不可用(import 其模块" + "仍可按需注册)\n", GenApi.get_struct_count()) + stdio.fflush(None) + return 0 + ent: rawptr[IncludesScanner.FileEntry] = rawptr[IncludesScanner.FileEntry]( + uint64(sr.Entries) + uint64(i) * uint64(es)) + if ent is None or ent.Sha1 is None: + st[i] = sizet(1) + ReplIncClsFail += 1 + continue + vm: t.CSizeT = mb.mark() + vpi_path: str = StubMergerCollect._sliced_path(vpi_dir, dl, ent.Sha1, "vpi") + if vpi_path is None: + st[i] = sizet(1) + ReplIncClsFail += 1 + mb.reset_to(vm) + continue + if _is_file(vpi_path) == 0: + # 缺存根(未构建过宿主 / includes 目录不含该模块):该模块的类 + # 不可用,会话继续(import 该模块时同样按缺存根退化) + ReplIncClsMissing += 1 + if warn_left > 0: + stdio.printf("includes 类注册: 模块 %s 无 .vpi 存根 —— 其类在" + "会话中不可用(不影响其它模块;先按 project.vpj " + "构建一次宿主可生成全部存根)\n", ent.Sha1) + warn_left -= 1 + else: + warn_dropped += 1 + stdlib.free(vpi_path) + mb.reset_to(vm) + st[i] = sizet(1) + continue + # 注入期把 trans.ModuleSha1 置为**当前 stub 的源模块 sha1**(对齐批处理 + # "trans.ModuleSha1 = 正在翻译的文件"):注解里的"本模块裸类名"走 + # find_struct_by_module(name, trans.ModuleSha1),父类判定 + # (_collect_parents → find_struct_by_module(parent_name, ModuleSha1))也读它; + # 同时 REPL 的"未登记类类型占位"(TypeGenResolve 的 ReplCtx 分支)按它拼 + # "{sha1}.{类名}"。沿用会话 sha1 会让本模块类查找与占位命名都错位。 + tr_inc.ModuleSha1 = ent.Sha1 + # 每模块一个恢复点:单模块 fatal 只丢弃该模块**本次**注入(已注册的类 + # 由结构体表去重语义兜住:同名同 sha1 再注入时 find_struct_by_module + # 命中即只 add_visible_struct,不新增条目)。注意:longjmp 回来后 + # vpi_path/vm 之外的本帧局部(在 _setjmp 之后赋值的)值不确定,故恢复 + # 分支只做 mb.reset_to(vm)——vpi_path 是 stdlib 串,此处不释放(每模块 + # 泄漏 ≤512B,仅发生在 fatal 路径)。 + rc_m: int = _setjmp(ReplJmpBuf, None) + if rc_m != 0: + ReplIncClsFail += 1 + if warn_left > 0: + stdio.printf("includes 类注册: 模块 %d 的 .vpi 注入中断" + "(其类可能不完整;会话继续)\n", i) + warn_left -= 1 + else: + warn_dropped += 1 + mb.reset_to(vm) + continue + ok_m: int = VpiReader.ParseVpiIntoModule(_perm_pool, tmp_mod, vpi_path, tr_inc) + if ok_m != 0: + st[i] = sizet(1) + ReplIncClsMods += 1 + pass_ok += 1 + else: + ReplIncClsFail += 1 + if warn_left > 0: + stdio.printf("includes 类注册: 模块 %s 的 .vpi 注入失败" + "(该模块的类不完整/不可用;会话继续)\n", ent.Sha1) + warn_left -= 1 + else: + warn_dropped += 1 + stdlib.free(vpi_path) + mb.reset_to(vm) + # 本遍没有任何模块成功 ⇒ 后续遍不会带来新信息(失败的模块留在待重试集里, + # 但重试同样会失败;省下全部重试的解析开销) + if pass_ok == 0: + break + # 汇总(聚合告警):逐模块告警已按上限截断,这里给出全部计数与未尝试模块数。 + skip_cnt: int = 0 + si: int + for si in range(n_mod): + if int(st[si]) == 0: + skip_cnt += 1 + stdio.printf("includes 类: 已注册 %d 个模块的 vpi 类(失败 %d,缺存根 %d," + "未尝试/待重试 %d,省略告警 %d)\n", + ReplIncClsMods, ReplIncClsFail, ReplIncClsMissing, + skip_cnt, warn_dropped) + if budget_hit != 0: + stdio.printf("includes 类: 启动预算 %d ms 用尽,已停止启动期注册(剩余 %d 个" + "模块的类在会话里不可用;片段 `import` 该模块时仍按需注册)\n", + _REPL_INC_CLS_BUDGET_MS, skip_cnt) + stdio.fflush(None) + return 0 + + +# ============================================================ +# 会话初始化:建 %TEMP%\TransPyV_repl\\ + 一次性分配常驻缓冲 + ctx +# 返回 0 成功;非 0 失败(原因由调用方打印)。 +# ============================================================ +def ReplInitSession() -> int: + """建临时目录 + PermPool 常驻缓冲 + ctx 会话内存;0=成功""" + global ReplTmpDir, ReplNameBuf, ReplSrcBuf, ReplSrcPath, ReplIrBuf, ReplNeedleBuf + global ReplCtxBuf, ReplCtxObj, ReplVarTable, ReplScratch + global ReplBlockBuf, ReplBlockLen, ReplInBlock + global ReplLiftBuf, ReplLiftNames, ReplLiftNameCount, ReplSigTags, ReplSigBits + global ReplSigResult, ReplLineShow, ReplMsgName, ReplTySpan, ReplTySpan3 + global ReplIrDefOff, ReplSigText, ReplSigText2, ReplWrapPos, ReplSavedFuncCount + global ReplSessionSha1, ReplFragImplibs, ReplFragImplibCount + global ReplBaseCDefineNames, ReplBaseCDefineValues, ReplBaseCDefineKinds + global ReplBaseCDefineCount, ReplBaseCTypedefNames, ReplBaseCTypedefTypeStrs + global ReplBaseCTypedefCount, ReplSavedCDefineCount, ReplSavedCTypedefCount + global ReplIncClsMods, ReplIncClsFail, ReplIncClsMissing + if _perm_pool is None: + return 1 + # ① %TEMP% 前缀(GetTempPathA 返回值含结尾反斜杠;0 表示失败) + tmp_buf: bytes = _perm_pool.alloc(_REPL_TMP_CAP) + if tmp_buf is None: + return 1 + tmp_len: ULONG = w32file.GetTempPathA(_REPL_TMP_CAP, tmp_buf) + if tmp_len == 0: + return 1 + if int(tmp_len) >= _REPL_TMP_CAP: + return 1 + # ② 会话目录:TransPyV_repl/(pid 隔离并发会话;路径统一用 '/') + ReplTmpDir = _perm_pool.alloc(_REPL_PATH_CAP) + if ReplTmpDir is None: + return 1 + viperlib.snprintf(ReplTmpDir, _REPL_PATH_CAP, "%sTransPyV_repl/%u", + tmp_buf, win32process.GetCurrentProcessId()) + if BuildPipeline.ensure_dir(ReplTmpDir) != 0: + return 1 + # ③ 常驻缓冲(会话期存活,不按片段反复分配) + ReplNameBuf = _perm_pool.alloc(_REPL_NAME_CAP) + ReplSrcBuf = _perm_pool.alloc(_REPL_FRAG_SRC_CAP) + ReplSrcPath = _perm_pool.alloc(_REPL_PATH_CAP) + ReplIrBuf = _perm_pool.alloc(_REPL_IR_CAP) + ReplNeedleBuf = _perm_pool.alloc(_REPL_NEEDLE_CAP) + ReplBlockBuf = _perm_pool.alloc(_REPL_BLOCK_CAP) + ReplBlockLen = 0 + ReplInBlock = 0 + if ReplNameBuf is None or ReplSrcBuf is None or ReplSrcPath is None: + return 1 + if ReplIrBuf is None or ReplNeedleBuf is None: + return 1 + if ReplBlockBuf is None: + return 1 + ReplBlockBuf[0] = 0 + # ③' M3 常驻缓冲:提升源码 / 会话函数名表 / 签名 token scratch / 诊断与渲染缓冲 + ReplLiftBuf = _perm_pool.alloc(_REPL_LIFT_CAP) + ReplLiftNames = _perm_pool.alloc(_REPL_FN_NAMES_CAP) + ReplSigTags = _perm_pool.alloc(GenTypes.REPL_FUNC_PARAM_MAX) + ReplSigBits = _perm_pool.alloc(GenTypes.REPL_FUNC_PARAM_MAX) + ReplSigResult = _perm_pool.alloc(ReplSigOut.__sizeof__()) + ReplLineShow = _perm_pool.alloc(_REPL_DIAG_CAP) + ReplMsgName = _perm_pool.alloc(_REPL_FN_NAME_MAX) + ReplTySpan = _perm_pool.alloc(_REPL_FN_TY_CAP) + ReplTySpan3 = _perm_pool.alloc(_REPL_FN_TY_CAP) + ReplSigText = _perm_pool.alloc(_REPL_SIG_TEXT_CAP) + ReplSigText2 = _perm_pool.alloc(_REPL_SIG_TEXT_CAP) + if ReplLiftBuf is None or ReplLiftNames is None or ReplSigTags is None \ + or ReplSigBits is None or ReplSigResult is None: + return 1 + if ReplLineShow is None or ReplMsgName is None or ReplTySpan is None \ + or ReplTySpan3 is None or ReplSigText is None or ReplSigText2 is None: + return 1 + ReplLiftBuf[0] = 0 + ReplLiftNames[0] = 0 + ReplLiftNameCount = 0 + ReplIrDefOff = -1 + ReplWrapPos = 0 + ReplSavedFuncCount = 0 + # ④ ctx 会话内存(片段首形参指向它):头 64B + 变量槽 64×16B + 函数指针槽 32×16B,全部清零 + ReplCtxBuf = _perm_pool.alloc(_REPL_CTX_SIZE) + if ReplCtxBuf is None: + return 1 + string.memset(ReplCtxBuf, 0, _REPL_CTX_SIZE) + # ⑤ 会话变量表 + 影子条目(PermPool;条目名/结构体名由 Gen 侧深拷贝进同一池) + entry_size: sizet = GenTypes.ReplVarEntry.__sizeof__() + ReplVarTable = _perm_pool.alloc(entry_size * GenTypes.REPL_VAR_CAP) + if ReplVarTable is None: + return 1 + string.memset(ReplVarTable, 0, entry_size * GenTypes.REPL_VAR_CAP) + ReplScratch = _perm_pool.alloc(entry_size) + if ReplScratch is None: + return 1 + string.memset(ReplScratch, 0, entry_size) + # 影子条目的名字(错误消息可读:"") + repl_scratch_name: str = _perm_pool.alloc(8) + if repl_scratch_name is None: + return 1 + viperlib.snprintf(repl_scratch_name, 8, "%s", "") + ReplScratch.Name = repl_scratch_name + ReplScratch.TyName = None + ReplScratch.Offset = 0 + ReplScratch.Kind = GenTypes.REPL_KIND_UNTYPED + ReplScratch.TyTag = 0 + ReplScratch.Bits = 0 + # ⑤' 会话函数表(M3;PermPool,宿主写、Gen 只读)——条目数组清零 + func_entry_size: sizet = GenTypes.ReplFuncEntry.__sizeof__() + ReplFuncTable: rawptr[GenTypes.ReplFuncEntry] = _perm_pool.alloc( + func_entry_size * GenTypes.REPL_FUNC_CAP) + if ReplFuncTable is None: + return 1 + string.memset(ReplFuncTable, 0, func_entry_size * GenTypes.REPL_FUNC_CAP) + # ⑥ 会话上下文对象(Gen 侧唯一开关:trans.ReplCtx) + ReplCtxObj = _perm_pool.alloc(GenTypes.ReplCtx.__sizeof__()) + if ReplCtxObj is None: + return 1 + string.memset(ReplCtxObj, 0, GenTypes.ReplCtx.__sizeof__()) + ctx_param: str = _perm_pool.alloc(_REPL_NAME_CAP) + if ctx_param is None: + return 1 + viperlib.snprintf(ctx_param, _REPL_NAME_CAP, "%s", GenTypes.REPL_CTX_PARAM) + ReplCtxObj.Marker = GenTypes.REPL_CTX_MARKER + ReplCtxObj.Table = ReplVarTable + ReplCtxObj.Count = 0 + ReplCtxObj.Cap = GenTypes.REPL_VAR_CAP + ReplCtxObj.CtxParam = ctx_param + ReplCtxObj.SlotSize = GenTypes.REPL_SLOT_SIZE + ReplCtxObj.Scratch = ReplScratch + ReplCtxObj.Perm = _perm_pool + # 会话函数表(M3):切片跨片段存活 ⇒ 表体/条目名/token 数组都在 PermPool + ReplCtxObj.FuncTable = ReplFuncTable + ReplCtxObj.FuncCount = 0 + ReplCtxObj.FuncCap = GenTypes.REPL_FUNC_CAP + ReplSavedFuncCount = 0 + # ⑦ ctx 固定头:magic/version 由宿主写一次(片段只读约定,不校验) + head: rawptr[GenTypes.ReplResult] = _ctx_head() + if head is None: + return 1 + head.Magic = GenTypes.REPL_CTX_MAGIC + head.Version = GenTypes.REPL_CTX_VERSION + head.Kind = GenTypes.REPL_RES_NONE + head.Reserved0 = 0 + head.A = 0 + head.B = 0 + head.VarCount = 0 + head.LastFrag = 0 + # ⑧ M4 常驻状态:会话 sha1 + 片段 import lib 表 + 会话基线(CDefine/CTypedef) + if ReplInitSessionSha1() != 0: + return 1 + ReplFragImplibs = _perm_pool.alloc(_REPL_FRAG_IMPLIB_ARR_CAP) + ReplBaseCDefineNames = _perm_pool.alloc(8 * _REPL_BASE_CDEFINE_MAX) + ReplBaseCDefineValues = _perm_pool.alloc(8 * _REPL_BASE_CDEFINE_MAX) + ReplBaseCDefineKinds = _perm_pool.alloc(8 * _REPL_BASE_CDEFINE_MAX) + ReplBaseCTypedefNames = _perm_pool.alloc(8 * _REPL_BASE_CTYPEDEF_MAX) + ReplBaseCTypedefTypeStrs = _perm_pool.alloc(8 * _REPL_BASE_CTYPEDEF_MAX) + if ReplFragImplibs is None or ReplBaseCDefineNames is None \ + or ReplBaseCDefineValues is None or ReplBaseCDefineKinds is None: + return 1 + if ReplBaseCTypedefNames is None or ReplBaseCTypedefTypeStrs is None: + return 1 + string.memset(ReplFragImplibs, 0, _REPL_FRAG_IMPLIB_ARR_CAP) + string.memset(ReplBaseCDefineNames, 0, 8 * _REPL_BASE_CDEFINE_MAX) + string.memset(ReplBaseCDefineValues, 0, 8 * _REPL_BASE_CDEFINE_MAX) + string.memset(ReplBaseCDefineKinds, 0, 8 * _REPL_BASE_CDEFINE_MAX) + string.memset(ReplBaseCTypedefNames, 0, 8 * _REPL_BASE_CTYPEDEF_MAX) + string.memset(ReplBaseCTypedefTypeStrs, 0, 8 * _REPL_BASE_CTYPEDEF_MAX) + ReplFragImplibCount = 0 + ReplBaseCDefineCount = 0 + ReplBaseCTypedefCount = 0 + ReplSavedCDefineCount = 0 + ReplSavedCTypedefCount = 0 + ReplIncClsMods = 0 + ReplIncClsFail = 0 + ReplIncClsMissing = 0 + return 0 + + +# ============================================================ +# includes 会话级初始化:扫描 includes 目录 → 注册"模块名 → SHA1" → 预生成 .vpi 存根 +# +# 为什么必须做:片段按**全量翻译**走(tr._declare_only = 0),片段体里的 import 会 +# 经 ImportsGenMixin._check_import_sha1(ImportsGenMixin.vp:781-812)校验"模块名 → +# SHA1"是否已注册,未注册即 fail-fast("模块 'X' 未注册 SHA1(includes 扫描未找到 +# 该模块…)")。该判据查的是 StubMerger 的全局存储器 +# (StubMerger/__init__.vp:46-50 的 _sha1_store_arr/_mod/_rel/_incdir + _count, +# 由 StubMergerStore.vp 读写);批处理路径上这张表由 Phase1 依赖图扫描填充 +# (Phase1Main.RunPhase1 → StubMergerBuild._BuildReachableSha1Set → +# AddIncludeToSha1MapStore,StubMergerBuild.vp:1049),而 REPL 从不跑 Phase1 +# ⇒ 表为空 ⇒ 片段里任何 `import ` 都报未注册 SHA1。 +# +# .vpi 存根同理:跨模块的返回类型/CDefine/CExport 判定都要读 +# {GenApi temp_dir}/{sha1[0:2]}/{sha1}.vpi(CallGenMixin.vp:1364-1370、 +# CallGenBase._LookupMethodRetTyFromTextLL:1092、ExprGenCdef.vp:282),批处理由 +# Phase1 1b-pre 预生成(Phase1Main.vp:716-798),REPL 不跑 Phase1 ⇒ 目录里没有 +# ⇒ 跨模块解析退化为默认推断(如返回类型默认 i32)。 +# +# 复用既有能力(不另写扫描): +# - 扫描:IncludesScanner.scan_includes(与 Phase1Main.vp:526 同款调用); +# - 注册:StubMergerStore.PopulateSha1MapStore(全量 includes → 同一张表, +# 写入字段与 AddIncludeToSha1MapStore 一致)。批处理现入口 +# _BuildReachableSha1Set 与 Phase1 强耦合(需 Config.SourceDir、deps 图、 +# worklist、写 deps.txt),repl.vpj 刻意不含 source_dir,故取同族的"全量扫描 +# + 注册"入口;差别是该表在批处理只含**可达** includes,REPL 含全部 includes +# (超集,注册内容/消费者完全一致:LookupSha1ByModName/Suffix); +# - 存根:StubConverter._GeneratePyiFromAst + StubMergerCollect._sliced_path +# (与 Phase1Main.vp:787-798、BuildPipeline.TranslateFileGetTrans:121-131 同款)。 +# +# 一次性 vs 每次:放会话开始时做一次。includes 源文件在一个会话内视为静态 +# (片段新增的是片段源码,不落 includes 目录),重扫会给每个片段增加 O(文件数) +# 的读盘 + SHA1/parse 成本;改动 includes 后重启会话即可。 +# +# 失败口径(宽容,与 M0 的恢复点一致):includes 目录缺失/扫描为 0/存根生成失败 +# 都只告警,**不终止会话**——"不带 import 的片段"仍照常可用。 +# ============================================================ +def _repl_gen_includes_vpi(mb: rawptr[memhub.MemBuddy], + sr: rawptr[IncludesScanner.ScanResult], + vpi_dir: str) -> int: + """为 includes 扫描结果预生成 .vpi 存根;返回新生成个数(-1=参数错误) + + 已存在同名 .vpi 直接复用(.vpi 按内容 SHA1 命名 ⇒ 同名必然同内容,与 + Phase1 1b-pre 的"vpi 已存在则跳过"同判据)。每文件取一次 mb.mark() 并在末尾 + reset_to:读文件/parse/generate 的瞬时分配不跨文件累积(对齐 Phase1 1b-pre)。 + """ + global ReplIncVpiNew, ReplIncVpiReuse, ReplIncVpiFail + if mb is None or sr is None or vpi_dir is None: + return -1 + if sr.Entries is None: + return -1 + es: sizet = IncludesScanner.FileEntry.__sizeof__() + dl: sizet = string.strlen(vpi_dir) + n: int + for n in range(sr.Count): + ent: rawptr[IncludesScanner.FileEntry] = rawptr[IncludesScanner.FileEntry]( + uint64(sr.Entries) + uint64(n) * es) + if ent is None or ent.Sha1 is None or ent.Path is None: + ReplIncVpiFail += 1 + continue + fm: t.CSizeT = mb.mark() + vpi_path: str = StubMergerCollect._sliced_path(vpi_dir, dl, ent.Sha1, "vpi") + if vpi_path is None: + ReplIncVpiFail += 1 + mb.reset_to(fm) + continue + # 已有存根 → 复用 + vf: rawptr[fileio.File] = fileio.File(vpi_path, fileio.MODE.R) + if not vf.closed: + vf.close() + stdlib.free(vpi_path) + ReplIncVpiReuse += 1 + mb.reset_to(fm) + continue + # 读源文件(1MB 上限与 Phase1/BuildPipeline 的 SRC_BUF_SIZE 一致) + f: rawptr[fileio.File] = fileio.File(ent.Path, fileio.MODE.R) + if f.closed: + stdlib.free(vpi_path) + ReplIncVpiFail += 1 + mb.reset_to(fm) + continue + if f.size() >= _REPL_SRC_BUF_CAP: + f.close() + stdlib.free(vpi_path) + ReplIncVpiFail += 1 + mb.reset_to(fm) + continue + src_buf: bytes = stdlib.malloc(_REPL_SRC_BUF_CAP) + if src_buf is None: + f.close() + stdlib.free(vpi_path) + ReplIncVpiFail += 1 + mb.reset_to(fm) + continue + bytes_read: LONG = f.read_all(src_buf, _REPL_SRC_BUF_CAP) + f.close() + if bytes_read <= 0: + stdlib.free(src_buf) + stdlib.free(vpi_path) + ReplIncVpiFail += 1 + mb.reset_to(fm) + continue + if bytes_read < _REPL_SRC_BUF_CAP: + src_buf[bytes_read] = 0 + # 解析(AST 全部分配在本文件的 mb 窗口内) + lx: rawptr[ast.Lexer] = ast.new_lexer(mb) + tree: rawptr[ast.AST] = None + if lx is not None: + ast._lexer_init(lx, src_buf, mb) + tokens: rawptr[ast.Token] = ast.tokenize(lx) + tree = ast.parse_tokens(mb, tokens) + if tree is None: + stdlib.free(src_buf) + stdlib.free(vpi_path) + ReplIncVpiFail += 1 + mb.reset_to(fm) + continue + pyi_buf: bytes = stdlib.malloc(_REPL_VPI_BUF_CAP) + if pyi_buf is None: + stdlib.free(src_buf) + stdlib.free(vpi_path) + ReplIncVpiFail += 1 + mb.reset_to(fm) + continue + # rel_path 传绝对路径 ent.Path:vpi 生成期的 fatal_error 需按该路径读源码行 + pos: sizet = StubConverter._GeneratePyiFromAst(mb, tree, ent.Path, + pyi_buf, _REPL_VPI_BUF_CAP) + if pos > 0: + pf: rawptr[fileio.File] = fileio.File(vpi_path, fileio.MODE.W) + if not pf.closed: + pf.write(pyi_buf, pos) + pf.close() + ReplIncVpiNew += 1 + else: + ReplIncVpiFail += 1 + else: + ReplIncVpiFail += 1 + stdlib.free(pyi_buf) + stdlib.free(src_buf) + stdlib.free(vpi_path) + mb.reset_to(fm) + return ReplIncVpiNew + + +def ReplInitIncludes(mb: rawptr[memhub.MemBuddy]) -> int: + """会话级 includes 初始化:扫描 → 注册 SHA1 → 预生成 .vpi → 注册泛型模板 + → 逐模块类注册;0=成功,非 0=降级跳过 + + 降级(返回非 0)时调用方只告警、继续进入读行循环(不带 import 的片段不受影响)。 + ⚠ 本函数内部会经 `ReplInitIncludesClasses` 使用**嵌套 _setjmp**(逐模块隔离), + 返回后 jmp_buf 的跳转目标指向那些已返回的帧 ⇒ 调用方必须在**自己的帧上**重新 + 武装恢复点,再做 includes 收尾(ReplStubLayoutFinish/ReplStubLayoutEnd,见 run)。 + 故收尾不放在本函数内。 + """ + global ReplIncMods, ReplIncMs + if mb is None: + return 1 + inc_dirs: str = Config.IncludesDirs + if inc_dirs is None or string.strlen(inc_dirs) == 0: + stdio.printf("REPL: 工程未配置 includes 目录(project.vpj 的 includes)," + "跳过 includes 初始化;import includes 模块会报未注册 SHA1\n") + return 1 + # .vpi 目录与片段侧一致:优先工程临时目录,无工程配置时退回会话目录 + vpi_dir: str = Config.TempDir + if vpi_dir is None: + vpi_dir = ReplTmpDir + if vpi_dir is None: + stdio.printf("REPL: 无可用 .vpi 目录(Config.TempDir 与会话目录均为空)," + "跳过 includes 初始化\n") + return 1 + # ① 扫描 includes(可达性不判定:REPL 无 SourceDir/deps 图,取全量超集) + t0: uint64 = win32base.GetTickCount64() + sr: rawptr[IncludesScanner.ScanResult] = IncludesScanner.scan_includes(mb, inc_dirs) + if sr is None: + stdio.printf("REPL: includes 扫描失败(目录: %s);" + "import includes 模块会报未注册 SHA1\n", inc_dirs) + return 1 + # ② 注册"模块名 → SHA1"(同一张 StubMerger 全局表,批处理消费者直接可用) + n: int = StubMergerStore.PopulateSha1MapStore(sr) + ReplIncMs = int(win32base.GetTickCount64() - t0) + if n <= 0: + stdio.printf("REPL: includes 扫描未注册任何模块(目录: %s):" + "请检查 includes 路径与 .vp 文件\n", inc_dirs) + return 1 + ReplIncMods = n + # ③ 预生成 .vpi 存根(失败逐个计数,不终止会话) + _repl_gen_includes_vpi(mb, sr, vpi_dir) + stdio.printf("includes: 已注册 %d 个模块(扫描耗时 %d ms);" + "存根 新生成 %d / 复用 %d / 失败 %d\n", + n, ReplIncMs, ReplIncVpiNew, ReplIncVpiReuse, ReplIncVpiFail) + # ③'' 会话泛型模板注册(**真注册**,不再只造 opaque 类型占位):按批处理同一 + # 机制把 includes 的泛型类模板(list/dict/Vector/…)注册进会话泛型模板表—— + # Phase1Scan._PreRegisterAllGenericTemplates 复用 includes 源码,先按 + # "class X[" 文本标记预筛(不 parse 全部文件),仅对命中文件用**永久池** + # 解析并调 GenApi._register_generic_template ⇒ 模板节点与 4 张表 + # (GenericClassNames/Nodes/Sha1s/Files + GenericClassTpParams)都落在 + # PermPool(M4 已定的会话级常驻池;不随片段 reset 回收),与批处理建表 + # 内容/顺序逐字一致。 + # 为什么必须:REPL 从不跑 Phase1/Phase2、也不翻译 includes 源码 ⇒ 会话里 + # 模板表为空 ⇒ 注解里的 `list[...]`/`dict[...]` 泛型下标恒 miss(日志 + # "[GENERIC-DBG] 模板未注册: list"),只能退到"仅类型"opaque 占位;而 + # opaque 只让类型"有名"(可过注入校验、指针签名与宿主兼容),**没有元素 + # 类型/布局/方法**(.get() 无从按正确元素类型分派,字段与方法都不可用)。 + # 真注册后 _resolve_generic_subscript_ty 走既有特化路径:`list[rawptr[AST]]` + # → 真实特化 struct(含 __data__/__count__/__capacity__ 布局)+ 方法 declare。 + # 失败隔离:本调用在 run 的恢复点保护下(其内部唯一终止出口是"源文件超 + # 上限"的 VLogger.error → longjmp 回 run 的 includes 恢复点,会话继续、 + # 提示符照常出现);函数自身对每个文件 continue,不抛异常。 + # 影响面:只写会话泛型模板表;批处理不调用本函数(其表由 Phase1/Phase2 的 + # 两遍预注册建),表内容与插入顺序不受影响 ⇒ 批处理逐字不变。 + cur_file_saved: str = GenApi.get_current_file() + n_gen_tpl: int = Phase1Scan._PreRegisterAllGenericTemplates(mb, sr) + if cur_file_saved is not None: + GenApi.set_current_file_type(cur_file_saved) + if n_gen_tpl <= 0: + stdio.printf("includes 泛型模板: 0 个 —— 注解里的 list[...]/dict[...] 只能按" + "\"仅类型\"占位解析(元素布局与方法不可用)\n") + # ③' M4.2:开启"存根布局恢复"——随后的 vpi 注入会按 .stub.ll 校验并登记 + # includes 类的**完整布局**(含父类展平字段与 __vtable__)与虚表方法索引; + # 布局不可信 ⇒ 该类的 StructEntry 不登记(字段访问明确报错,不静默错位)。 + # 同时清空"泛型类归属表"(跟随后的 vpi 注入逐 .vpi 重新登记;会话级单例, + # 见 StubMergerStore.RegisterGenericOwner):注解里的泛型下标要靠它取 + # "模板模块 sha1" 拼定义侧特化名造类型占位(M4.3)。 + StubMergerStore.ResetGenericOwners() + VpiReader.ReplStubLayoutBegin(vpi_dir) + # ④ M4 类注册:把各模块 .vpi 注入临时模块 ⇒ 其类(含字段表)进入**会话** + # StructTable(这是后续片段里 `memhub.NewMemPool(...)` 之类跨模块构造/ + # 方法调用的前提:类型解析与字段查找都只认 StructEntry)。 + # 逐模块隔离 + 多遍收敛 + 启动时间预算,失败只告警(见 ReplInitIncludesClasses)。 + ReplInitIncludesClasses(mb, sr, vpi_dir) + return 0 + + +# ============================================================ +# M2 - 片段执行通路(语句 → IR → llc → clang++ -shared → DLL → 调用 → ctx 回显) +# M3 - 顶层 def 提升 + 会话函数表 + 定义端签名解析(跨片段函数) +# +# 本段(到 _repl_ir_func_sig 结束)是 M3 的全部宿主侧机制:装配期提升顶层 def、 +# 从片段 IR 解析定义端 ABI、注册前校验、DLL 加载后取址写 ctx 槽 + 建表。 +# 片段执行通路本体见 _build_frag_src / _exec_repl_src / ReplExecFragment(其后)。 +# +# ---- M3 顶层 def 提升 ---- + +# 目标:片段里顶层(顶格)的 `def` 块被**提升**为片段模块里的顶层 `export def` +# (裸名导出),片段体只留其余语句 ⇒ 该函数进 DLL 导出表,宿主 +# GetProcAddress(裸名) 取到地址、写入 ctx 的函数指针槽,后续片段即可经槽间接 +# 调用(函数代码只在定义片段里一份,调用点不复制、不重编译)。 +# +# 为什么选**源码行分割**而不是 AST 拆分 + 重新打印: +# ① 本仓库没有"AST → 源码"的打印器(Gen 侧只有 IR 打印);自写打印器要覆盖 +# 全部节点类型/字面量转义/注解还原/缩进规则,代码量与走样风险远超收益; +# ② 行分割只做两件可预测的事——把 def 块**原样搬**到模块区、在 def 头上注入 +# ctx 形参并加 `export `;其余字符(docstring/注释/多行表达式/字符串)逐字保留, +# 不存在"重新打印后语义走样"这一整类风险。 +# 误判面(已知,写进文档):判断只看"是否顶格 def(async 拒绝)"+"签名是否单行", +# 不做缩进语义分析;签名行内的字符串字面量若含括号会干扰 '(' ')' 配对。 +# +# 精确规则: +# ① 提升块 = [可选若干顶格 '@' 装饰器行] + [顶格 def 头行] + 其后所有 +# "缩进 > 0 的行或纯空白行";遇到下一个**顶格非空行**即结束(该行重新判定)。 +# ② def 头行形如 `def <名>[(<泛型参数>)](…)…:`,且 '(' 到行尾的 ':' 必须在 +# **同一行**(多行签名 ⇒ 明确报错,不猜);注入规则:名字后的 '(' 之后插入 +# "__repl_ctx__: bytes, "(形参表为空时插入不带逗号的版本)。 +# ③ 提升行按原文写出(不缩进);其余行进片段体,每行统一加 4 空格(块路径口径)。 +# ④ 拒绝(明确报错 + **整片段不执行**): +# - 顶格 async def(协程 ABI 与 M3 的签名 token 不相容) +# - 装饰器行后不是 def 头行 / def 头缺 '(' / ')' 或 ':' 不在同一行 +# - 形参表里已出现 __repl_ctx__(用户自带 ctx 形参) +# - 函数体(含嵌套)任一行以 raise 开头(EH 出参 ⇒ 间接调用 arity 不匹配) +# - 同一片段内同名 def 出现两次 / 名字超长 / 数量超 _REPL_LIFT_MAX +# ⑤ 不提升:缩进 > 0 的 def(走原嵌套函数/闭包路径);`fn`/`func`/`function`/ +# `define` 形式不在本次范围(同样走原路径)。 +# ============================================================ + +# 把一行(可能非 NUL 结尾)截断拷进会话缓冲,供 %s 打印 +def _repl_show_line(p: bytes, n: int) -> str: + """行片段 → 会话缓冲里的 NUL 结尾串(截断到 118 字节)""" + if ReplLineShow is None or p is None: + return "(?)" + m: int = n + if m > 118: + m = 118 + if m < 0: + m = 0 + string.memcpy(ReplLineShow, p, t.CSizeT(m)) + ReplLineShow[m] = 0 + return ReplLineShow + + +# 行长度(到 '\n' 或 NUL 为止) +def _repl_line_len(p: bytes) -> int: + n: int = 0 + while p[n] != '\n' and p[n] != 0: + n += 1 + return n + + +# 前导空白字节数 +def _repl_indent_len(p: bytes, n: int) -> int: + i: int = 0 + while i < n and (p[i] == ' ' or p[i] == '\t'): + i += 1 + return i + + +# 在 [ind, n) 处是否以关键词 kw 开头(且 kw 后是空白) +def _repl_line_has_kw(p: bytes, n: int, ind: int, kw: str) -> int: + k: int = int(string.strlen(kw)) + if ind + k + 1 > n: + return 0 + if string.strncmp(p + ind, kw, t.CSizeT(k)) != 0: + return 0 + c: char = p[ind + k] + if c != ' ' and c != '\t': + return 0 + return 1 + + +# 追加字节段到 dst(维护末尾 NUL);返回新长度,-1=容量不足 +def _repl_cat(dst: bytes, cap: int, pos: int, src: bytes, n: int) -> int: + if dst is None or src is None or pos < 0 or n < 0: + return -1 + if pos + n + 1 > cap: + return -1 + string.memcpy(dst + pos, src, t.CSizeT(n)) + pos += n + dst[pos] = 0 + return pos + + +# 追加 NUL 结尾串到 dst;返回新长度,-1=容量不足 +def _repl_cat_str(dst: bytes, cap: int, pos: int, s: str) -> int: + if s is None: + return pos + return _repl_cat(dst, cap, pos, s, int(string.strlen(s))) + + +# 提升出的函数名入表(保留前缀检查 + 同片段查重 + 上限);0=成功,1=拒绝(已打印) +def _repl_lift_add_name(name: str) -> int: + global ReplLiftNameCount + if name is None or ReplLiftNames is None: + return 1 + # 保留前缀:片段入口函数名("__repl_frag_")是 Gen 侧判定"片段入口作用域"的 + # 依据(会话变量的写路径只在该作用域生效),用户不得占用该前缀 + pfx: str = GenTypes.REPL_ENTRY_PREFIX + pl: int = int(string.strlen(pfx)) + if string.strncmp(name, pfx, t.CSizeT(pl)) == 0: + stdio.printf("M3: 会话函数名不能以保留前缀 '%s' 开头:%s\n", pfx, name) + return 1 + i: int + for i in range(ReplLiftNameCount): + if string.strcmp(ReplLiftNames + i * _REPL_FN_NAME_MAX, name) == 0: + stdio.printf("M3: 同一片段内重复定义会话函数 '%s'(请只定义一次)\n", name) + return 1 + if ReplLiftNameCount >= _REPL_LIFT_MAX: + stdio.printf("M3: 单个片段最多提升 %d 个会话函数(本片段已提升 %d 个)\n", + _REPL_LIFT_MAX, ReplLiftNameCount) + return 1 + string.strcpy(ReplLiftNames + ReplLiftNameCount * _REPL_FN_NAME_MAX, name) + ReplLiftNameCount += 1 + return 0 + + +# 解析并改写一行顶格 def 头 → 追加到 ReplLiftBuf +#("export def <名>(__repl_ctx__: bytes, <原形参表>) -> <原返回注解>:",其余逐字保留) +# 名字写入 name_out(NUL 结尾)。返回 0=成功;1=规则拒绝(已打印);2=缓冲不足 +def _repl_lift_def_header(line: bytes, n: int, name_out: bytes, name_cap: int) -> int: + if n < 5 or name_out is None or ReplLiftBuf is None: + return 1 + p: int = 3 # 跳过 "def" + while p < n and (line[p] == ' ' or line[p] == '\t'): + p += 1 + # 名字(标识符字符) + ns: int = p + while p < n: + c: char = line[p] + is_id: int = 0 + if (c >= 'a' and c <= 'z') or (c >= 'A' and c <= 'Z') or (c >= '0' and c <= '9') \ + or c == '_': + is_id = 1 + if is_id == 0: + break + p += 1 + nlen: int = p - ns + if nlen <= 0: + stdio.printf("M3: def 头缺少函数名:%s\n", _repl_show_line(line, n)) + return 1 + if nlen >= name_cap: + stdio.printf("M3: 会话函数名过长(上限 %d 字节):%s\n", + name_cap - 1, _repl_show_line(line, n)) + return 1 + string.memcpy(name_out, line + ns, t.CSizeT(nlen)) + name_out[nlen] = 0 + # PEP 695 泛型参数 [T]:跳过平衡的方括号(翻译侧把 T 映射为 int) + if p < n and line[p] == '[': + d: int = 0 + while p < n: + if line[p] == '[': + d += 1 + elif line[p] == ']': + d -= 1 + if d == 0: + p += 1 + break + p += 1 + if d != 0: + stdio.printf("M3: def 头的泛型参数方括号不闭合:%s\n", _repl_show_line(line, n)) + return 1 + if p >= n or line[p] != '(': + stdio.printf("M3: def 头在函数名之后缺少 '(':%s\n", _repl_show_line(line, n)) + return 1 + lp: int = p + # 匹配的 ')' + d2: int = 0 + rp: int = -1 + q: int = lp + while q < n: + c2: char = line[q] + if c2 == '(': + d2 += 1 + elif c2 == ')': + d2 -= 1 + if d2 == 0: + rp = q + break + q += 1 + if rp < 0: + stdio.printf("M3: def 头的 ')' 缺失(签名必须写在一行内):%s\n", _repl_show_line(line, n)) + return 1 + # ':' 必须在同一行(返回注解也必须在同一行) + has_colon: int = 0 + q2: int = rp + 1 + while q2 < n: + if line[q2] == ':': + has_colon = 1 + break + q2 += 1 + if has_colon == 0: + stdio.printf("M3: def 头的 ':' 不在同一行(签名必须写在一行内):%s\n", + _repl_show_line(line, n)) + return 1 + # 形参表是否为空 + empty: int = 1 + q3: int = lp + 1 + while q3 < rp: + if line[q3] != ' ' and line[q3] != '\t': + empty = 0 + break + q3 += 1 + # 用户自带 __repl_ctx__ 形参 ⇒ 拒绝(避免与注入的首形参重复) + if empty == 0: + seg: int = lp + 1 + while seg + 12 <= rp: + if line[seg] == '_' and string.strncmp(line + seg, GenTypes.REPL_CTX_PARAM, + t.CSizeT(12)) == 0: + stdio.printf("M3: 会话函数 '%s' 不能自定义 %s 形参" + "(提升时自动注入为第一个形参)\n", name_out, GenTypes.REPL_CTX_PARAM) + return 1 + seg += 1 + # 写出:"export " + 原头 [0,lp] + 注入 + 原头 [lp+1,n) + '\n' + pos: int = int(string.strlen(ReplLiftBuf)) + pos = _repl_cat_str(ReplLiftBuf, _REPL_LIFT_CAP, pos, "export ") + pos = _repl_cat(ReplLiftBuf, _REPL_LIFT_CAP, pos, line, lp + 1) + if empty != 0: + pos = _repl_cat_str(ReplLiftBuf, _REPL_LIFT_CAP, pos, "__repl_ctx__: bytes") + else: + pos = _repl_cat_str(ReplLiftBuf, _REPL_LIFT_CAP, pos, "__repl_ctx__: bytes, ") + pos = _repl_cat(ReplLiftBuf, _REPL_LIFT_CAP, pos, line + lp + 1, n - lp - 1) + pos = _repl_cat_str(ReplLiftBuf, _REPL_LIFT_CAP, pos, "\n") + if pos < 0: + stdio.printf("M3: 提升源码缓冲(%d 字节)不足\n", _REPL_LIFT_CAP) + return 2 + return 0 + + +# 拆分片段体:提升块 → ReplLiftBuf(模块级 export def,原文不缩进); +# 其余行 → ReplSrcBuf(自 ReplWrapPos 起,每行 4 空格);并登记被提升的函数名。 +# 返回 0=成功;1=规则拒绝(已打印);2=缓冲不足。成功时 ReplWrapPos 更新为片段体末尾位置。 +def _repl_split_body(body: bytes) -> int: + global ReplWrapPos + if body is None or ReplLiftBuf is None or ReplSrcBuf is None: + return 2 + total: int = int(string.strlen(body)) + i: int = 0 + pos: int = ReplWrapPos + in_lift: int = 0 # 1=正在提升块内 + need_header: int = 0 # 提升块内已见装饰器行、尚未见 def 头 + in_body: int = 0 # 已越过 def 头(后续缩进行是函数体,逐行查 raise) + name_buf: bytes = ReplMsgName + while i < total: + n: int = _repl_line_len(body + i) + ind: int = _repl_indent_len(body + i, n) + blank: int = 0 + if ind >= n: + blank = 1 + if in_lift != 0: + if blank != 0: + # 纯空白行:留在提升块内(函数体里的空行,对缩进无语义) + lp0: int = int(string.strlen(ReplLiftBuf)) + lp0 = _repl_cat(ReplLiftBuf, _REPL_LIFT_CAP, lp0, body + i, n) + lp0 = _repl_cat_str(ReplLiftBuf, _REPL_LIFT_CAP, lp0, "\n") + if lp0 < 0: + return 2 + i += n + 1 + continue + if ind > 0: + if need_header != 0: + stdio.printf("M3: 装饰器之后必须紧跟 def 头行:%s\n", + _repl_show_line(body + i, n)) + return 1 + if in_body != 0 and _repl_line_has_kw(body + i, n, ind, "raise") != 0: + stdio.printf("M3: 会话函数 '%s' 的函数体含 raise:%s\n" + " M3 不支持跨片段异常传播(raise 会给函数追加 " + "__eh_msg_out__/__eh_code_out__ 出参," + "而经函数指针的间接调用不会补这些实参)——该片段整体不执行\n", + name_buf, _repl_show_line(body + i, n)) + return 1 + lp1: int = int(string.strlen(ReplLiftBuf)) + lp1 = _repl_cat(ReplLiftBuf, _REPL_LIFT_CAP, lp1, body + i, n) + lp1 = _repl_cat_str(ReplLiftBuf, _REPL_LIFT_CAP, lp1, "\n") + if lp1 < 0: + return 2 + i += n + 1 + continue + # 顶格非空行 ⇒ 提升块结束,本行按"未进入提升"重新判定(不推进 i) + in_lift = 0 + need_header = 0 + in_body = 0 + continue + if blank != 0 or ind > 0: + # 进片段体(每行 4 空格;与块路径逐字同口径) + pos = _repl_cat_str(ReplSrcBuf, _REPL_FRAG_SRC_CAP, pos, " ") + pos = _repl_cat(ReplSrcBuf, _REPL_FRAG_SRC_CAP, pos, body + i, n) + pos = _repl_cat_str(ReplSrcBuf, _REPL_FRAG_SRC_CAP, pos, "\n") + if pos < 0: + return 2 + i += n + 1 + continue + # 顶格非空行 + if body[i] == '@': + in_lift = 1 + need_header = 1 + in_body = 0 + lp2: int = int(string.strlen(ReplLiftBuf)) + lp2 = _repl_cat(ReplLiftBuf, _REPL_LIFT_CAP, lp2, body + i, n) + lp2 = _repl_cat_str(ReplLiftBuf, _REPL_LIFT_CAP, lp2, "\n") + if lp2 < 0: + return 2 + i += n + 1 + continue + if _repl_line_has_kw(body + i, n, 0, "def") != 0: + rc_h: int = _repl_lift_def_header(body + i, n, name_buf, _REPL_FN_NAME_MAX) + if rc_h != 0: + return rc_h + if _repl_lift_add_name(name_buf) != 0: + return 1 + in_lift = 1 + need_header = 0 + in_body = 1 + i += n + 1 + continue + if _repl_line_has_kw(body + i, n, 0, "async") != 0 \ + and _repl_line_has_kw(body + i, n, 6, "def") != 0: + stdio.printf("M3: 不支持 async def 会话函数(协程 ABI 与 M3 签名 token " + "不相容,请改写成普通 def):%s\n", _repl_show_line(body + i, n)) + return 1 + pos = _repl_cat_str(ReplSrcBuf, _REPL_FRAG_SRC_CAP, pos, " ") + pos = _repl_cat(ReplSrcBuf, _REPL_FRAG_SRC_CAP, pos, body + i, n) + pos = _repl_cat_str(ReplSrcBuf, _REPL_FRAG_SRC_CAP, pos, "\n") + if pos < 0: + return 2 + i += n + 1 + if need_header != 0: + stdio.printf("M3: 装饰器之后缺少 def 头行(片段末尾)\n") + return 1 + ReplWrapPos = pos + return 0 + + +# 多行块的包裹装配(提升 + 包裹):返回 0=成功;1=规则拒绝(已打印);2=缓冲不足 +def _repl_build_frag_src_multi(name: str, body: bytes) -> int: + global ReplWrapPos + if name is None or body is None: + return 2 + if ReplLiftBuf is None or ReplLiftNames is None or ReplSrcBuf is None: + return 2 + # 提升缓冲与名表水位已由调用方 _build_frag_src 每片段清零(单行路径也必须清零, + # 故清零点在调用方而非此处);这里只负责装配 + # 片段函数头(与单行模板同一形态);片段体随后逐行 4 空格 + viperlib.snprintf(ReplSrcBuf, _REPL_FRAG_SRC_CAP, + "export def %s(%s: bytes) -> int:\n", + name, GenTypes.REPL_CTX_PARAM) + ReplWrapPos = int(string.strlen(ReplSrcBuf)) + rc: int = _repl_split_body(body) + if rc != 0: + return rc + pos: int = ReplWrapPos + pos = _repl_cat_str(ReplSrcBuf, _REPL_FRAG_SRC_CAP, pos, " return 0\n") + if pos < 0: + return 2 + # 提升出的模块级 def 追加在片段入口函数之后(同为模块级,顺序无关) + lift_len: int = int(string.strlen(ReplLiftBuf)) + if lift_len > 0: + pos = _repl_cat(ReplSrcBuf, _REPL_FRAG_SRC_CAP, pos, ReplLiftBuf, lift_len) + if pos < 0: + return 2 + ReplWrapPos = pos + return 0 + + +# ============================================================ +# M3 - 会话函数表:宿主持有(写表 + 写 ctx 指针槽),Gen 只读 +# +# 时序(见 ReplExecFragment): +# ① 定义片段:提升 → 翻译 → **注册前校验**(本节的 _repl_precheck_session_funcs, +# 在 llc/加载之前 ⇒ 失败则整片段不执行)→ 加载 DLL → GetProcAddress(裸名) +# → 写 ctx 槽 → 追加/更新表条目; +# ② 调用片段:翻译期命中表 → 生成"从槽 load 地址 + 按表内签名间接调用"。 +# 一致性:表条目与 ctx 槽只在"定义片段 DLL 加载成功且符号取址成功"后一起写; +# 失败路径由 ReplSavedFuncCount 水位回滚(与 ReplSavedVarCount 同思路)。 +# ============================================================ + +# 会话函数表按名查找(宿主侧;与 Gen 侧 repl_find_func_entry 同口径) +def _repl_func_entry_find(name: str) -> rawptr[GenTypes.ReplFuncEntry]: + """1=命中条目;None=未登记/表未建""" + if ReplCtxObj is None or ReplCtxObj.FuncTable is None or name is None: + return None + es: sizet = GenTypes.ReplFuncEntry.__sizeof__() + i: int + for i in range(ReplCtxObj.FuncCount): + e: rawptr[GenTypes.ReplFuncEntry] = rawptr[GenTypes.ReplFuncEntry]( + uint64(ReplCtxObj.FuncTable) + uint64(i) * uint64(es)) + if e.Name is not None and string.strcmp(e.Name, name) == 0: + return e + return None + + +# 提升出的第 idx 个函数名(NUL 结尾);越界返回 None +def _repl_lift_name(idx: int) -> str: + if ReplLiftNames is None or idx < 0 or idx >= ReplLiftNameCount: + return None + return ReplLiftNames + idx * _REPL_FN_NAME_MAX + + +# 深拷贝到 PermPool(会话表条目名等跨片段存活的数据) +def _repl_perm_dup(s: str) -> str: + if s is None or _perm_pool is None: + return None + n: sizet = string.strlen(s) + b: str = _perm_pool.alloc(n + 1) + if b is None: + return None + string.strcpy(b, s) + return b + + +# 签名解析结果 → 人类可读串("((int, str) -> int)"式;写进调用方缓冲) +def _repl_sig_to_text(tag: int, bits: int, pc: int) -> str: + """把 ReplSigResult + ReplSigTags/Bits 渲染成 "(P1, P2) -> R"(PermPool 缓冲)""" + if ReplSigText is None: + return "(?)" + pos: int = _repl_cat_str(ReplSigText, _REPL_SIG_TEXT_CAP, 0, "(") + i: int = 0 + while i < pc: + if i > 0: + pos = _repl_cat_str(ReplSigText, _REPL_SIG_TEXT_CAP, pos, ", ") + pos = _repl_cat_str(ReplSigText, _REPL_SIG_TEXT_CAP, pos, + _repl_tag_text(int(ReplSigTags[i]), int(ReplSigBits[i]))) + i += 1 + pos = _repl_cat_str(ReplSigText, _REPL_SIG_TEXT_CAP, pos, ") -> ") + pos = _repl_cat_str(ReplSigText, _REPL_SIG_TEXT_CAP, pos, _repl_tag_text(tag, bits)) + if pos < 0: + return "(?)" + return ReplSigText + + +# 注册前校验(在 llc/加载之前):逐个提升函数解析 IR 签名,判 +# ① EH 出参 ② 类型不受支持 ③ 首形参不是 ctx ④ 形参过多 ⑤ 未找到 define +# ⑥ 重定义签名不一致 ⑦ 表容量不足。任一项失败 ⇒ 打印原因并返回 1(整片段不执行)。 +def _repl_precheck_session_funcs() -> int: + if ReplLiftNameCount <= 0: + return 0 + if ReplCtxObj is None or ReplCtxObj.FuncTable is None or ReplSigResult is None: + stdio.printf("M3: 会话函数表/签名缓冲未初始化(会话初始化失败)\n") + return 1 + new_cnt: int = 0 + idx: int + for idx in range(ReplLiftNameCount): + nm: str = _repl_lift_name(idx) + if nm is None: + return 1 + rc: int = _repl_ir_func_sig(nm, ReplSigResult) + if rc == 1: + stdio.printf("M3: 片段 IR 里找不到 '%s' 的 define 行(提升未生效?)\n", nm) + return 1 + if rc == 2: + stdio.printf("M3: 会话函数 '%s' 的签名含不受支持的类型 '%s'\n" + " M3 支持 void / i1 / i8 / i16 / i32 / i64 / float / double / " + "i8*(str、bytes、t.CPtr);结构体值/数组/多级指针请改用指针参数" + "或等 M4\n", + nm, ReplSigResult.BadType) + return 1 + if rc == 3: + stdio.printf("M3: 会话函数 '%s' 的签名含异常出参(%%__eh_msg_out__/" + "%%__eh_code_out__)——其函数体含 raise\n" + " M3 不支持跨片段异常传播:经函数指针的间接调用不会补这两个实参" + "(arity 不匹配);该片段整体不执行\n", nm) + return 1 + if rc == 4: + stdio.printf("M3: 会话函数 '%s' 的 define 首形参不是 %s(提升模板与调用约定" + "不一致)\n", nm, GenTypes.REPL_CTX_PARAM) + return 1 + if rc == 5: + stdio.printf("M3: 会话函数 '%s' 的形参超过 %d 个\n", + nm, GenTypes.REPL_FUNC_PARAM_MAX) + return 1 + if rc != 0: + stdio.printf("M3: 会话函数 '%s' 的签名解析失败(rc=%d)\n", nm, rc) + return 1 + old: rawptr[GenTypes.ReplFuncEntry] = _repl_func_entry_find(nm) + if old is None: + new_cnt += 1 + continue + # 重定义:只允许**同签名**覆盖(换签名会让先前片段里已编好的调用点按旧 ABI + # 调新代码 ⇒ 静默 UB)。签名一致时只更新槽内地址(段号随之刷新)。 + sig_same: int = 1 + if old.RetTag != ReplSigResult.RetTag or old.RetBits != ReplSigResult.RetBits \ + or old.ParamCount != ReplSigResult.ParamCount: + sig_same = 0 + if sig_same != 0: + pi: int = 0 + while pi < old.ParamCount: + if int(old.ParamTags[pi]) != int(ReplSigTags[pi]) \ + or int(old.ParamBits[pi]) != int(ReplSigBits[pi]): + sig_same = 0 + break + pi += 1 + if sig_same == 0: + stdio.printf("M3: 会话函数 '%s' 重定义签名不一致(旧 %s,新 %s)\n" + " M3 允许同签名覆盖(只换地址);换签名会让先前片段里已编好的" + "调用点按旧 ABI 调新代码 —— 该片段整体不执行,请换名字\n", + nm, + _repl_entry_sig_text(old), + _repl_sig_to_text(ReplSigResult.RetTag, ReplSigResult.RetBits, + ReplSigResult.ParamCount)) + return 1 + if ReplCtxObj.FuncCount + new_cnt > ReplCtxObj.FuncCap: + stdio.printf("M3: 会话函数已达上限 %d 个(ctx 函数指针槽用尽),本次新增 %d 个;" + "请重启会话或减少会话函数\n", + ReplCtxObj.FuncCap, new_cnt) + return 1 + return 0 + + +# 已有条目的签名渲染(重定义冲突诊断用;写进 ReplSigText2) +def _repl_entry_sig_text(e: rawptr[GenTypes.ReplFuncEntry]) -> str: + if e is None or ReplSigText2 is None: + return "(?)" + pos: int = _repl_cat_str(ReplSigText2, _REPL_SIG_TEXT_CAP, 0, "(") + i: int = 0 + while i < e.ParamCount: + if i > 0: + pos = _repl_cat_str(ReplSigText2, _REPL_SIG_TEXT_CAP, pos, ", ") + pos = _repl_cat_str(ReplSigText2, _REPL_SIG_TEXT_CAP, pos, + _repl_tag_text(int(e.ParamTags[i]), int(e.ParamBits[i]))) + i += 1 + pos = _repl_cat_str(ReplSigText2, _REPL_SIG_TEXT_CAP, pos, ") -> ") + pos = _repl_cat_str(ReplSigText2, _REPL_SIG_TEXT_CAP, pos, + _repl_tag_text(e.RetTag, e.RetBits)) + if pos < 0: + return "(?)" + return ReplSigText2 + + +# 注册(DLL 加载成功后调用):GetProcAddress(裸名) → 写 ctx 函数指针槽 → 追加/更新表 +# 返回 0=成功;1=失败(已打印;调用方按 ReplSavedFuncCount 回滚表水位后放弃本片段) +def _repl_register_session_funcs(h_lib: win32base.HANDLE, frag_no: int) -> int: + if ReplLiftNameCount <= 0: + return 0 + if ReplCtxObj is None or ReplCtxObj.FuncTable is None or ReplCtxBuf is None: + stdio.printf("M3: 会话函数表/ctx 未初始化\n") + return 1 + idx: int + for idx in range(ReplLiftNameCount): + nm: str = _repl_lift_name(idx) + if nm is None: + return 1 + # 导出符号名 = 裸名:定义端 `export def` ⇒ has_export=1 不加 SHA1 前缀 + # (FuncGenDef.vp:482),且 REPL 片段 ModuleSha1=None 使 _mangle_name 原样返回 + # (FuncGenBase.vp:264);-shared 未用 DEF 文件 ⇒ 全部符号自动进导出表(M1 实测) + addr: VOIDPTR = win32base.GetProcAddress(h_lib, nm) + if addr is None: + stdio.printf("M3: 会话函数 '%s' 未出现在片段 DLL 的导出表" + "(GetProcAddress 返回空;本片段不执行)\n", nm) + return 1 + ent: rawptr[GenTypes.ReplFuncEntry] = _repl_func_entry_find(nm) + if ent is None: + # 新建条目(签名已在校验阶段通过;此处重新解析以取回 scratch 里的 token) + rc: int = _repl_ir_func_sig(nm, ReplSigResult) + if rc != 0: + stdio.printf("M3: 会话函数 '%s' 的签名重新解析失败(rc=%d)\n", nm, rc) + return 1 + j: int = ReplCtxObj.FuncCount + es: sizet = GenTypes.ReplFuncEntry.__sizeof__() + ent = rawptr[GenTypes.ReplFuncEntry]( + uint64(ReplCtxObj.FuncTable) + uint64(j) * uint64(es)) + nm_c: str = _repl_perm_dup(nm) + nm_s: str = _repl_perm_dup(nm) + tags_b: bytes = _perm_pool.alloc(GenTypes.REPL_FUNC_PARAM_MAX) + bits_b: bytes = _perm_pool.alloc(GenTypes.REPL_FUNC_PARAM_MAX) + if nm_c is None or nm_s is None or tags_b is None or bits_b is None: + stdio.printf("M3: 会话函数条目内存分配失败('%s')\n", nm) + return 1 + string.memset(tags_b, 0, GenTypes.REPL_FUNC_PARAM_MAX) + string.memset(bits_b, 0, GenTypes.REPL_FUNC_PARAM_MAX) + string.memcpy(tags_b, ReplSigTags, t.CSizeT(ReplSigResult.ParamCount)) + string.memcpy(bits_b, ReplSigBits, t.CSizeT(ReplSigResult.ParamCount)) + ent.Name = nm_c + ent.SymName = nm_s + ent.FragNo = frag_no + ent.SlotOff = GenTypes.REPL_FUNC_BASE + j * GenTypes.REPL_FUNC_SLOT_SIZE + ent.RetTag = ReplSigResult.RetTag + ent.RetBits = ReplSigResult.RetBits + ent.ParamCount = ReplSigResult.ParamCount + ent.ParamTags = tags_b + ent.ParamBits = bits_b + ReplCtxObj.FuncCount = j + 1 + else: + # 重定义(同签名):只换地址与段号,槽偏移不变 + ent.FragNo = frag_no + # 写 ctx 槽(低 8 字节=地址,高 8 字节保留 0) + sv: rawptr[ReplFuncSlotView] = rawptr[ReplFuncSlotView]( + uint64(ReplCtxBuf) + uint64(ent.SlotOff)) + sv.Addr = addr + sv.Reserved = None + stdio.printf("会话函数 %s: 导出符号 %s → ctx 槽 +%d(定义片段 %d,签名 %s)\n", + nm, nm, ent.SlotOff, frag_no, + _repl_entry_sig_text(ent)) + return 0 + + +# ============================================================ +# M3 - 会话函数签名解析(从片段 IR 的 define 行取定义端 ABI) +# +# 出处:定义片段 translate 后 dump 的 IR 里 +# `define @<名>(i8* %__repl_ctx__[, %

]*)` +# 是定义端 ABI 的唯一真相(注解 → LLVM 类型由 codegen 解析)。宿主据此填 +# ReplFuncEntry 的签名 token;调用片段用同一 token 重建同型签名 ⇒ 两侧一致。 +# 只接受可往返重建的类型:i1/i8/i16/i32/i64、float、double、i8*(+ 返回 void)。 +# ============================================================ + +# 把 [src, src+n) 截断拷进会话缓冲并 NUL 结尾(供 strcmp/%s):缓冲区 1 +def _repl_span_str(src: bytes, n: int) -> str: + if ReplTySpan is None or src is None: + return None + m: int = n + if m > _REPL_FN_TY_CAP - 1: + m = _REPL_FN_TY_CAP - 1 + if m < 0: + m = 0 + string.memcpy(ReplTySpan, src, t.CSizeT(m)) + ReplTySpan[m] = 0 + return ReplTySpan + + +# LLVM 类型串 → 打包 token(tag*1000 + bits);0=不支持 +def _repl_llvm_ty_token(ty: str) -> int: + if ty is None: + return 0 + if string.strcmp(ty, "i1") == 0: + return GenTypes.REPL_TY_INT * 1000 + 1 + if string.strcmp(ty, "i8") == 0: + return GenTypes.REPL_TY_INT * 1000 + 8 + if string.strcmp(ty, "i16") == 0: + return GenTypes.REPL_TY_INT * 1000 + 16 + if string.strcmp(ty, "i32") == 0: + return GenTypes.REPL_TY_INT * 1000 + 32 + if string.strcmp(ty, "i64") == 0: + return GenTypes.REPL_TY_INT * 1000 + 64 + if string.strcmp(ty, "float") == 0: + return GenTypes.REPL_TY_FLOAT * 1000 + 32 + if string.strcmp(ty, "double") == 0: + return GenTypes.REPL_TY_FLOAT * 1000 + 64 + if string.strcmp(ty, "i8*") == 0: + return GenTypes.REPL_TY_STR * 1000 + 8 + return 0 + + +# token → 显示名(仅用于日志/诊断) +def _repl_tag_text(tag: int, bits: int) -> str: + if tag == GenTypes.REPL_TY_VOID: + return "void" + if tag == GenTypes.REPL_TY_INT: + if bits == 1: + return "bool" + if bits == 8: + return "char" + if bits == 16: + return "int16" + if bits == 32: + return "int" + if bits == 64: + return "int64" + return "int?" + if tag == GenTypes.REPL_TY_FLOAT: + if bits == 32: + return "float" + if bits == 64: + return "double" + return "float?" + if tag == GenTypes.REPL_TY_STR: + return "str" + return "?" + + +# 在 IR 文本里定位符号 name 的 define 行:命中返回 '(' 的**字节偏移** +#(并把 "define " 的偏移写进 ReplIrDefOff),未命中返回 -1。 +# 全程用整数偏移 + 逐字节扫描(不借 strchr/strstr 的返回指针,避免 str/bytes +# 指针类型分叉带来的编译期不确定性);只做正向扫描。 +def _repl_ir_find_define_at(ir: bytes, name: str) -> int: + global ReplIrDefOff + ReplIrDefOff = -1 + if ir is None or name is None: + return -1 + kw: str = "define " + nl: int = int(string.strlen(name)) + if nl <= 0: + return -1 + i: int = 0 + while ir[i] != 0: + hit: int = 1 + k: int = 0 + while k < 7: + if ir[i + k] == 0 or ir[i + k] != kw[k]: + hit = 0 + break + k += 1 + if hit == 0: + i += 1 + continue + # i+7 起是返回类型,直到 '@';要求 '@' 与 '(' 在同一行内(同 define 行) + j: int = i + 7 + while ir[j] != 0 and ir[j] != '@' and ir[j] != '\n': + j += 1 + if ir[j] != '@': + i += 1 + continue + n0: int = j + 1 + n1: int = n0 + while ir[n1] != 0 and ir[n1] != '(' and ir[n1] != '\n': + n1 += 1 + if ir[n1] != '(' or n1 - n0 != nl: + i += 1 + continue + if string.strncmp(ir + n0, name, nl) == 0: + ReplIrDefOff = i + return n1 + i += 1 + return -1 + + +# 解析会话函数的 define 行签名 → ReplSigResult + ReplSigTags/ReplSigBits +# 返回 0=成功;1=未找到 define;2=类型不受支持;3=含 EH 出参;4=首形参不是 ctx; +# 5=形参过多(> REPL_FUNC_PARAM_MAX) +def _repl_ir_func_sig(name: str, out: rawptr[ReplSigOut]) -> int: + if name is None or out is None or ReplIrBuf is None: + return 1 + out.RetTag = GenTypes.REPL_TY_VOID + out.RetBits = 0 + out.ParamCount = 0 + out.EHTag = 0 + out.Bad = 0 + out.BadType = None + ir: bytes = ReplIrBuf + lp: int = _repl_ir_find_define_at(ir, name) + if lp < 0 or ReplIrDefOff < 0: + return 1 + # ① 返回类型:'define ' 之后到 '@'(去首尾空白) + at: int = ReplIrDefOff + 7 + while ir[at] != 0 and ir[at] != '@': + at += 1 + if ir[at] != '@' or at >= lp: + return 1 + ret_f: int = ReplIrDefOff + 7 + ret_e: int = at + while ret_f < ret_e and (ir[ret_f] == ' ' or ir[ret_f] == '\t'): + ret_f += 1 + while ret_e > ret_f and (ir[ret_e - 1] == ' ' or ir[ret_e - 1] == '\t'): + ret_e -= 1 + ret_str: str = _repl_span_str(ir + ret_f, ret_e - ret_f) + if ret_str is None: + return 2 + if string.strcmp(ret_str, "void") == 0: + out.RetTag = GenTypes.REPL_TY_VOID + out.RetBits = 0 + else: + rt: int = _repl_llvm_ty_token(ret_str) + if rt == 0: + out.Bad = 1 + out.BadType = ret_str + return 2 + out.RetTag = rt // 1000 + out.RetBits = rt % 1000 + # ② 形参表:'(' 到匹配的 ')'(按括号深度配平;IR 类型里可能有 {..} / [..]) + d: int = 0 + rp: int = lp + while ir[rp] != 0: + if ir[rp] == '(': + d += 1 + elif ir[rp] == ')': + d -= 1 + if d == 0: + break + rp += 1 + if ir[rp] != ')': + return 1 + # ③ 逐个形参段(按 ',' 切;段内形态 "类型 [%名字]") + # 类型 span = [ps+ty_f, ps+ty_n);名字 span = [ps+nm_f, ps+nm_e)(相对段起点) + ps: int = lp + 1 + cur: int = ps + pi: int = 0 + while 1: + is_sep: int = 0 + if cur >= rp: + is_sep = 1 + elif ir[cur] == ',': + is_sep = 1 + if is_sep == 0: + cur += 1 + continue + seg_n: int = cur - ps + pc: int = -1 + k: int = 0 + while k < seg_n: + if ir[ps + k] == '%': + pc = k + k += 1 + ty_n: int = seg_n + if pc >= 0: + ty_n = pc + ty_f: int = 0 + while ty_f < ty_n and (ir[ps + ty_f] == ' ' or ir[ps + ty_f] == '\t'): + ty_f += 1 + while ty_n > ty_f and (ir[ps + ty_n - 1] == ' ' or ir[ps + ty_n - 1] == '\t'): + ty_n -= 1 + nm_f: int = -1 + nm_e: int = -1 + if pc >= 0: + nm_f = pc + 1 + nm_e = seg_n + while nm_f < nm_e and (ir[ps + nm_f] == ' ' or ir[ps + nm_f] == '\t'): + nm_f += 1 + while nm_e > nm_f and (ir[ps + nm_e - 1] == ' ' or ir[ps + nm_e - 1] == '\t'): + nm_e -= 1 + if nm_e <= nm_f: + nm_f = -1 + if pi == 0: + # 首形参固定为 ctx:类型 i8*、名字逐字等于 __repl_ctx__ + # 名字 span [nm_f, nm_e) 是**去掉前导 '%'** 的(nm_f = pc + 1,见上"名字 span" + # 口径),故期望串就是 ctx 形参名本身。此处**不能**再拼 '%':拼出来的 + # "%__repl_ctx__" 长 13,而 span 长 12 ⇒ 长度判据恒不等,任何合法提升函数 + # 都被误判 rc=4(`会话函数 'f' 的 define 首形参不是 __repl_ctx__`), + # M3 定义片段必在注册前校验处失败(提升装配其实是对的)。 + ty_str0: str = _repl_span_str(ir + ps + ty_f, ty_n - ty_f) + if ty_str0 is None or string.strcmp(ty_str0, "i8*") != 0 or nm_f < 0: + return 4 + c0: int = _repl_cat_str(ReplTySpan3, _REPL_FN_TY_CAP, 0, GenTypes.REPL_CTX_PARAM) + if c0 < 0: + return 4 + if nm_e - nm_f != int(string.strlen(ReplTySpan3)) \ + or string.strncmp(ir + ps + nm_f, ReplTySpan3, nm_e - nm_f) != 0: + return 4 + else: + # EH 出参(raise 语义):define 形参名 %__eh_msg_out__/%__eh_code_out__ + if nm_f >= 0: + c1: int = _repl_cat_str(ReplTySpan3, _REPL_FN_TY_CAP, 0, "%") + c1 = _repl_cat(ReplTySpan3, _REPL_FN_TY_CAP, c1, ir + ps + nm_f, nm_e - nm_f) + if c1 >= 0: + if string.strcmp(ReplTySpan3, "%__eh_msg_out__") == 0 \ + or string.strcmp(ReplTySpan3, "%__eh_code_out__") == 0: + out.EHTag = 1 + return 3 + ty_str: str = _repl_span_str(ir + ps + ty_f, ty_n - ty_f) + if ty_str is None: + return 2 + if out.ParamCount >= GenTypes.REPL_FUNC_PARAM_MAX: + return 5 + tk: int = _repl_llvm_ty_token(ty_str) + if tk == 0: + out.Bad = 1 + out.BadType = _repl_span_str(ir + ps + ty_f, ty_n - ty_f) + return 2 + ReplSigTags[out.ParamCount] = tk // 1000 + ReplSigBits[out.ParamCount] = tk % 1000 + out.ParamCount = out.ParamCount + 1 + pi += 1 + if cur >= rp: + break + ps = cur + 1 + cur = ps + return 0 + + + +# ============================================================ +# 装配片段包裹源码到 ReplSrcBuf(片段体恒为**函数体**)。 +# 单行(体不含 '\n'):走既有 snprintf 模板,**逐字不变**; +# 多行(块):M3 起走 _repl_build_frag_src_multi —— 顶层 def 提升为模块级 +# export def(原文不缩进),其余行逐行加 4 空格进片段体,随后片段入口函数, +# 最后追加提升出的模块级 def。 +# 为什么逐行处理:片段体缩进由模板给出,原来单行是"整行前加 4 空格";多行若只给 +# 第一行加 4 空格,第 2 行起顶格 ⇒ IndentationError。故每行独立加 4 空格, +# 而每行**原有的相对缩进原样保留**(块内嵌套层级因此不变)。 +# 为什么单行/多行分两条:单行路径要求与改动前逐字一致(同一 snprintf、同一模板、 +# 同一输出),多行路径才需要逐行装配与提升;两者互不干扰。 +# 返回 0=已写入 ReplSrcBuf;1=提升规则拒绝(原因已打印);2=缓冲不足。 +def _build_frag_src(name: str, body: str) -> int: + """装配包裹源码:单行=既有模板(逐字不变);多行=提升 + 逐行 4 空格""" + global ReplLiftNameCount + if name is None or body is None: + return 2 + # 每片段装配前清零上一片段留下的提升状态(名表水位 + 提升缓冲): + # **单行路径不提升任何函数**,若不清零,上一片段提升出的名字会被本片段的 + # 注册前校验(_repl_precheck_session_funcs 按 ReplLiftNameCount 逐名遍历)当成 + # "本片段提升的函数"去本片段 IR 里找 define ⇒ 必然找不到 ⇒ 该片段被误拒。 + # 典型症状:定义片段成功后的**第一个调用片段**(如 `f(41)`)报 + # `M3: 片段 IR 里找不到 'f' 的 define 行(提升未生效?)`。 + # 多行路径随后按需追加提升内容(_repl_split_body 写、_repl_build_frag_src_multi 读)。 + ReplLiftNameCount = 0 + ReplLiftBuf[0] = 0 + ReplLiftNames[0] = 0 + if string.strchr(body, '\n') is None: + # 单行:既有模板(ctx 形参 bytes ⇒ i8*;export ⇒ 不加 SHA1 前缀) + viperlib.snprintf(ReplSrcBuf, _REPL_FRAG_SRC_CAP, + "export def %s(%s: bytes) -> int:\n %s\n return 0\n", + name, GenTypes.REPL_CTX_PARAM, body) + return 0 + # 多行:提升顶层 def + 逐行装配(M3;见 _repl_split_body 的规则) + return _repl_build_frag_src_multi(name, body) + + +# 执行一份片段源码(单行与整块同一入口):先按**未包裹**源码解析一次(诊断行号 +# 对用户可读、且与单行路径同款消息),再交给 ReplExecFragment 包裹/翻译/执行。 +# 行为与改动前 run 里的单行逻辑逐语句一致(moved,非新增)。 +def _exec_repl_src(mb: rawptr[memhub.MemBuddy], src: str) -> int: + """解析 + 执行一份片段源码;0=已处理(含各类诊断已打印)""" + if src is None: + return 1 + tree: rawptr[ast.AST] = ast.parse(src, mb) + if tree is None: + el: int = ast.get_parse_err_line() + ec: int = ast.get_parse_err_col() + em: str = ast.get_parse_err_msg() + if em is None: + em = "(未知)" + stdio.printf("ParseError: 第 %d 行第 %d 列: %s\n", el, ec, em) + return 0 + if _count_top_stmts(tree) <= 0: + stdio.printf("已解析: 0 个顶层语句(跳过)\n") + return 0 + # M2/M3 执行通路:整块/整行作为**一个**片段函数体翻译执行;失败只打印原因 + # (或经 longjmp 回到恢复点),不终止进程 + rc_frag: int = ReplExecFragment(mb, src) + if rc_frag != 0: + # 非 longjmp 的失败(提升规则拒绝、签名校验失败、llc/链接失败、 + # LoadLibraryA/GetProcAddress 为空、导出签名不符等):片段内部已逐条打印 + # 原因(ReplExecFragment 的各失败分支都有 printf),此处只回滚会话表水位 + # (变量表 Count + 函数表 FuncCount),保证"失败后表与 ctx 槽一致"。 + if ReplCtxObj is not None: + ReplCtxObj.Count = ReplSavedVarCount + ReplCtxObj.FuncCount = ReplSavedFuncCount + # M4:会话基线(CDefine/CTypedef)水位回滚。基线只在片段成功时追加增量 + # (ReplBaselineCapture),故此处截断对正常路径是恒等操作;它保证"失败片段 + # 绝不改变基线"这一不变式在将来捕获点位置变化时仍然成立。 + ReplBaselineTruncate(ReplSavedCDefineCount, ReplSavedCTypedefCount) + return 0 + + +# 执行一个片段(任意顶层语句:表达式/赋值/注解赋值/增强赋值/...)。 +# 返回 0=成功(已按 result_kind 回显);非 0=失败(已打印原因,未崩)。 +# 注意:翻译期 fatal_error / llc 失败会经 VLogger.error → longjmp 回到 run 的恢复点, +# 此时本函数不返回;失败片段占用的符号序号已提前递增(名字/文件名不复用)。 +def ReplExecFragment(mb: rawptr[memhub.MemBuddy], src: str) -> int: + """片段 → DLL → 调用 → ctx 回显;0=成功""" + global ReplFragFn, ReplFragNo, ReplSavedVarCount, ReplSavedFuncCount + global ReplSavedCDefineCount, ReplSavedCTypedefCount + if mb is None or src is None: + return 1 + if ReplTmpDir is None or ReplNameBuf is None or ReplSrcBuf is None: + stdio.printf("REPL: 会话缓冲未初始化\n") + return 1 + if ReplSrcPath is None or ReplIrBuf is None or ReplNeedleBuf is None: + stdio.printf("REPL: 会话缓冲未初始化\n") + return 1 + if ReplCtxBuf is None or ReplCtxObj is None or ReplVarTable is None: + stdio.printf("REPL: ctx 会话内存未初始化\n") + return 1 + + # ⓪ 每片段重新武装恢复点 + 会话表水位快照 + # - set_repl_jmpbuf 幂等重注册:jmp_buf 生命周期 = 整个会话(PermPool,只在 + # run 退出时 clear_repl_jmpbuf),中途不清 ⇒ 覆盖整条流水线(翻译 → 写 .ll + # → llc → clang++ → LoadLibrary → GetProcAddress → 调用 → 回显); + # - 真正的"武装"是 run 在本函数调用前的 _setjmp(jb, None)(恢复点存于同一 + # 存活栈帧);此处重注册只是保证注册值不因任何路径丢失。 + # - 水位快照供恢复分支回滚:失败片段可能已 repl_ensure_entry(Count+1)后才 + # 在 repl_seal/后续阶段 fail-fast,若不回滚会把半成品条目留给下一片段。 + # - M3 同时快照会话函数表水位(FuncCount):定义片段的登记是"逐函数"的, + # 中途失败(如第 2 个符号取址失败)必须整体回滚,否则表里留下半批登记。 + VLogger.set_repl_jmpbuf(ReplJmpBuf) + ReplSavedVarCount = ReplCtxObj.Count + ReplSavedFuncCount = ReplCtxObj.FuncCount + # M4:会话基线水位(CDefine/CTypedef)——失败片段不得把增量带进基线, + # 与变量表/函数表水位同思路(成功才由 ReplBaselineCapture 追加增量)。 + ReplSavedCDefineCount = ReplBaseCDefineCount + ReplSavedCTypedefCount = ReplBaseCTypedefCount + + # ① 取号并立即递增:即使后续失败(含 longjmp)也不复用号,保证会话内名字/文件唯一 + n: int = ReplFragNo + ReplFragNo = n + 1 + viperlib.snprintf(ReplNameBuf, _REPL_NAME_CAP, "__repl_frag_%d", n) + + # ② 剥行首空白:片段整体作为函数体,缩进由模板统一给出(否则与模板缩进叠加 + # 会产生 IndentationError)。多行块时该步只作用于**第一行**的行首空白 + # (块内其余行由 ③ 的逐行装配统一加 4 空格、原相对缩进保留)。 + body: str = src + if body is not None: + while body[0] == ' ' or body[0] == '\t': + body = body + 1 + + # ③ 包裹模板:ctx 形参(bytes ⇒ i8*,名与 GenTypes.REPL_CTX_PARAM 同源) + # export → 不加 SHA1 前缀(parser 改写返回注解为 |t.CExport; + # FuncGenDef 命中 has_export → 用裸名 define) + # 单行 = 既有模板逐字不变;多行(块)= M3 提升顶层 def + 逐行缩进 4 空格 + # (见 _repl_split_body / _repl_build_frag_src_multi;只缩进第一行会让 + # 第 2 行起顶格 ⇒ IndentationError) + # 返回:0=成功;1=提升规则拒绝(原因已由提升函数打印);2=缓冲不足 + rc_build: int = _build_frag_src(ReplNameBuf, body) + if rc_build == 2: + stdio.printf("片段 %s: 包裹源码装配失败(缓冲 %d 字节不足)\n", + ReplNameBuf, _REPL_FRAG_SRC_CAP) + return 1 + if rc_build != 0: + # 提升规则拒绝(async def / raise / 重名 / 签名非单行 / 自带 ctx 形参…): + # 整片段不执行(无任何部分副作用),会话继续 + return 1 + + # ④ 包裹源码落盘(仅用于报错回显源行:fatal_error 经 _read_source_line 读该文件) + viperlib.snprintf(ReplSrcPath, _REPL_PATH_CAP, "%s/%s.vp", ReplTmpDir, ReplNameBuf) + sf: rawptr[fileio.File] = fileio.File(ReplSrcPath, fileio.MODE.W) + if not sf.closed: + sf.write(ReplSrcBuf, string.strlen(ReplSrcBuf)) + sf.close() + + # ⑤ 每片段重置模块级翻译状态(对齐 Phase1Main.vp:1027-1032 / BuildPipeline.vp:155-168) + # temp_dir 供依赖 .vpi 注入使用:优先用工程临时目录(含 includes 存根), + # 无工程配置时退回本会话目录 + vpi_dir: str = Config.TempDir + if vpi_dir is None: + vpi_dir = ReplTmpDir + GenApi.set_temp_dir(vpi_dir) + # CallGenBase.TempDir/TempDirLen(跨模块返回类型 .vpi/.text.ll 查找、CExport 判定 + # 的目录)只由 SetTempDir 设置(CallGenBase.vp:824-828);批处理在 RunPhase1 开头 + # 设置(Phase1Main.vp:332),REPL 此前漏设 ⇒ 跨模块解析读不到 .vpi。与上面的 + # set_temp_dir 同值、幂等,每片段设置一次。 + GenApi.SetTempDir(vpi_dir) + GenApi.set_current_file_type(ReplSrcPath) + GenApi.set_current_file_imports(ReplSrcPath) + GenApi.clear_trace() + GenApi.clear_cdefine_constants() + GenApi.clear_ctypedefs() + # 可见性用**宽松模式(strict=0,全部已注册结构体可见)**:片段的类型来自 + # 会话(先前片段定义/注册的类)而非"本文件 import",严格模式(批处理的每文件 + # 隔离口径)会把它们全判为不可见 → 裸类名注解解析失败。宽松模式使 + # `p: Point = ...` 在定义过 Point 的后续片段里可解析。 + GenApi.reset_visible_structs(mb, 0) + # 结构体注册走"永久分配"路径(对齐 Phase 1b/Phase2 的 StdlibAlloc=1): + # 结构体表体/条目名/字段类型进 PermPool,否则它们随本片段 mb.reset_to 回收, + # 后续片段的 find_struct/get_struct_type 读悬空内存(M1 无结构体,故未设)。 + GenApi.set_stdlib_alloc(1) + # M4:CDefine/CTypedef 的"会话基线"回灌。上面两个 clear_* 把表指针置 None + # (表语义是"整个会话一份",见 M4 设计)⇒ 此处把基线条目重新登记进本片段池: + # 片段内看到的是"会话基线(先前片段 / 先前 import 带进来的常量与类型别名) + # ∪ 本片段新增";本片段新增由成功后 ReplBaselineCapture 追加进基线。 + ReplBaselineRestore(mb) + + # ⑥ 解析包裹源码。池选择(M4):片段源码含 "class " 时用**永久池**解析—— + # 片段里的 class 定义会被翻译进会话(结构体 + 方法 + 泛型模板表),其中 + # **泛型模板**会把 ClassDef **节点本身**存进永久表(GenericClassNodes), + # 供后续片段特化时重读模板;若节点在本片段池,后续片段特化即读已回收内存。 + # 永久池解析让"模板节点"与"模板表"同寿命(代价:含 class 的片段 AST 常驻, + # 量级 KB/片段)。不含 class 的片段仍用 mb,行为与 M2/M3 逐字一致。 + parse_pool: rawptr[memhub.MemBuddy] = mb + if _perm_pool is not None: + if string.strstr(ReplSrcBuf, "class ") is not None: + parse_pool = _perm_pool + tree: rawptr[ast.AST] = ast.parse(ReplSrcBuf, parse_pool) + if tree is None: + el: int = ast.get_parse_err_line() + ec: int = ast.get_parse_err_col() + em: str = ast.get_parse_err_msg() + if em is None: + em = "(未知)" + stdio.printf("片段 %s: 包裹源码解析失败(第 %d 行第 %d 列: %s)\n", + ReplNameBuf, el, ec, em) + return 1 + + # ⑦ 翻译为完整模块 IR(Translator 本体在 mb) + # ModuleSha1 = 会话 sha1(M4):片段内定义的类型名恒定 ".", + # 与后续片段的查找用同一键(类型跨片段可见的唯一基础); + # `export def`(片段入口 / 会话函数)走 has_export 分支取**裸名** + # (FuncGenDef.vp:482-485,经 _mangle_func_name 亦不前缀),故 M1-M3 的 + # 裸名约定不受影响(片段入口名/会话函数名/GetProcAddress/探针全部照旧)。 + # ReplCtx 置位 = 打开 REPL 模式(会话变量按 ctx 槽寻址);翻译后清空。 + # 每个片段用新建的 Translator ⇒ longjmp 中断也不会把 ReplCtx 残留给下一片段。 + tr: rawptr[HandlesTranslator.Translator] = mb.alloc(HandlesTranslator.Translator.__sizeof__()) + if tr is None: + stdio.printf("片段 %s: Translator 分配失败\n", ReplNameBuf) + return 1 + tr.__before_init__() + tr.__init__() + tr.ModuleSha1 = ReplSessionSha1 + tr._declare_only = 0 + tr.ReplCtx = ReplCtxObj + rc_tr: int = tr.translate(tree) + tr.ReplCtx = None + if rc_tr != 0: + stdio.printf("片段 %s: 翻译失败\n", ReplNameBuf) + return 1 + tr.dump_ir(ReplIrBuf, _REPL_IR_CAP, llvmlite.OUTPUT_FULL) + + # ⑦' M4:本片段是否**定义**了会话前缀符号(`@".…"` = 类方法/ + # 构造/类全局)?是 ⇒ 其 import lib 必须产出并登记,供后续片段链接期解析 + # 这些符号(片段 DLL 之间没有隐式链接)。用 "@\"" + sha1 + "." 作探针 + # (def/global 的命名起点;类型定义行是 %"…" 形态,不会误命中)。 + needs_implib: int = 0 + if ReplSessionSha1 is not None and ReplNeedleBuf is not None: + viperlib.snprintf(ReplNeedleBuf, _REPL_NEEDLE_CAP, "@\"%s.", ReplSessionSha1) + if string.strstr(ReplIrBuf, ReplNeedleBuf) is not None: + needs_implib = 1 + + # ⑧ 导出符号探针:签名必须是 (i8* %__repl_ctx__) 且返回 i32。 + # 不含该形参 ⇒ 包裹模板/调用约定不一致,或片段含 raise 使函数被追加 + # __eh_msg_out__/__eh_code_out__ 两个出参——此时宿主按单参调用是未定义行为, + # 故直接明确报错(不加载、不调用)。 + viperlib.snprintf(ReplNeedleBuf, _REPL_NEEDLE_CAP, "define i32 @%s(i8* %%%s", + ReplNameBuf, GenTypes.REPL_CTX_PARAM) + if string.strstr(ReplIrBuf, ReplNeedleBuf) is None: + stdio.printf("片段 %s: 未生成约定的导出函数(期望 `define i32 @%s(i8* %%__repl_ctx__)`);" + "含 raise 的片段会追加异常出参,M2 不支持\n", + ReplNameBuf, ReplNameBuf) + return 1 + + # ⑧' 会话函数签名校验(M3):在 llc/加载**之前**判完(EH 出参 / 类型不受支持 / + # 首形参非 ctx / 形参过多 / 找不到 define / 重定义签名不一致 / 表容量不足) + # ⇒ 任一不通过就整片段不执行(无部分副作用;ctx 槽与表都不动)。 + # ReplLiftNameCount 为 0(本片段没有提升任何会话函数)时立即返回 0。 + if _repl_precheck_session_funcs() != 0: + return 1 + + # ⑨ 写 .ll(复用 BuildPipeline:切片路径 + ensure_dir) + ir_len: sizet = string.strlen(ReplIrBuf) + if BuildPipeline.write_ir_to_file(ReplIrBuf, ir_len, ReplTmpDir, ReplNameBuf) != 0: + stdio.printf("片段 %s: 写 .ll 失败(目录 %s)\n", ReplNameBuf, ReplTmpDir) + return 1 + ir_path: str = BuildPipeline.build_sliced_path(ReplTmpDir, ReplNameBuf, "ll") + if ir_path is None: + stdio.printf("片段 %s: .ll 路径构造失败\n", ReplNameBuf) + return 1 + + # ⑩ llc → .obj(复用既有机制;失败路径内部 VLogger.error 报 module/exit/cmd 后 longjmp) + cc_cmd: str = Config.CompilerCmd + if cc_cmd is None: + cc_cmd = "llc" + cc_flags: str = Config.CompilerFlags + if cc_flags is None: + cc_flags = "-filetype=obj -relocation-model=pic" + if BuildPipeline.compile_ll_to_obj(ir_path, ReplTmpDir, ReplNameBuf, cc_cmd, cc_flags) != 0: + # 走不到这里的 llc 失败:compile_ll_to_obj 内部 VLogger.error 已打印 module/exit/cmd + # 并 longjmp(BuildPipeline.vp:373-375)。能返回非 0 的只有其内部"分配失败"分支 + # (obj_path/cmd 缓冲为 NULL,BuildPipeline.vp:315/367)⇒ 补一行明示,避免静默回提示符。 + stdio.printf("片段 %s: llc 编译 .obj 失败(命令/路径缓冲分配失败,见上方诊断)\n", + ReplNameBuf) + return 1 + + # ⑪ clang++ -shared → .dll(命令拼法对齐 BuildPipeline.link_obj_to_exe 的 + # "cmd ... -o 输出 输入";拉起步子复用 BuildPipeline._run_cmd_wait) + obj_path: str = BuildPipeline.build_sliced_path(ReplTmpDir, ReplNameBuf, "obj") + dll_path: str = BuildPipeline.build_sliced_path(ReplTmpDir, ReplNameBuf, "dll") + if obj_path is None or dll_path is None: + stdio.printf("片段 %s: .obj/.dll 路径构造失败\n", ReplNameBuf) + return 1 + linker_cmd: str = Config.LinkerCmd + if linker_cmd is None: + linker_cmd = "clang++" + cmd: bytes = mb.alloc(_REPL_CMD_CAP) + if cmd is None: + stdio.printf("片段 %s: 命令行缓冲分配失败\n", ReplNameBuf) + return 1 + # 跨模块符号(`.<函数>` / `.<全局>`)由宿主 exe 定义:把宿主导出的 + # import lib 加进命令行 ⇒ 片段 DLL 的导入表指向宿主 exe,符号在链接期解析、 + # 加载期由同一进程内已加载的宿主满足(同一份 includes 代码与数据,无状态重复)。 + # ReplHostImplib 为 None(宿主未按导出模式构建)时保持旧口径:只链片段自身 obj, + # 此时跨模块调用会在链接期报 undefined symbol(不终止会话,见下方提示)。 + # M4 追加:① 本片段产出 import lib(--out-implib)供后续片段链接; + # ② 把先前片段登记的 import lib 全部带上(本片段引用其定义的类/方法)。 + implib_path: str = mb.alloc(_REPL_PATH_CAP) + if implib_path is None: + stdio.printf("片段 %s: import lib 路径缓冲分配失败\n", ReplNameBuf) + return 1 + viperlib.snprintf(implib_path, _REPL_PATH_CAP, "%s.dll.a", dll_path) + pos_c: int = _repl_cat_str(cmd, _REPL_CMD_CAP, 0, linker_cmd) + pos_c = _repl_cat_str(cmd, _REPL_CMD_CAP, pos_c, " -shared -o ") + pos_c = _repl_cat_str(cmd, _REPL_CMD_CAP, pos_c, dll_path) + pos_c = _repl_cat_str(cmd, _REPL_CMD_CAP, pos_c, " ") + pos_c = _repl_cat_str(cmd, _REPL_CMD_CAP, pos_c, obj_path) + if ReplHostImplib is not None: + pos_c = _repl_cat_str(cmd, _REPL_CMD_CAP, pos_c, " ") + pos_c = _repl_cat_str(cmd, _REPL_CMD_CAP, pos_c, ReplHostImplib) + pos_c = _repl_cmd_append_implibs(cmd, _REPL_CMD_CAP, pos_c) + if pos_c >= 0: + pos_c = _repl_cat_str(cmd, _REPL_CMD_CAP, pos_c, " -Wl,--out-implib=") + pos_c = _repl_cat_str(cmd, _REPL_CMD_CAP, pos_c, implib_path) + if pos_c < 0: + stdio.printf("片段 %s: 链接命令行超过 %d 字节(已登记的片段 import lib 共 %d " + "个)——本片段不执行;可重启会话减少片段数\n", + ReplNameBuf, _REPL_CMD_CAP, ReplFragImplibCount) + return 1 + link_rc: int = BuildPipeline._run_cmd_wait(cmd) + if link_rc != 0: + stdio.printf("片段 %s: 生成 .dll 失败 (exit=%d)\n 命令: %s\n", + ReplNameBuf, link_rc, cmd) + if ReplHostImplib is None: + stdio.printf(" 原因提示: 未找到宿主 import lib(.dll.a)——" + "跨模块符号(形如 <16位hex>.<函数名>)无法解析;" + "宿主需按 TransPyV/project.vpj 构建(含 -Wl,--export-all-symbols)," + "且 .dll.a 与 exe 同目录\n") + else: + stdio.printf(" 原因提示: 若输出含 undefined symbol 且形如 <16位hex>.<名>," + "则该符号未被宿主定义/导出(宿主构建时未链入对应 includes 模块);" + "若该函数在源码里是 `export def`(CExport,裸名定义)却仍带前缀," + "多为 include 存根 .vpi 缺失(见会话开头的 includes 扫描行)\n") + return 1 + + # ⑪' M4:若本片段 IR 里定义了会话前缀符号 ⇒ 登记其 import lib + # (链接已产出 .dll.a)。后续片段链接时自动带上它 ⇒ 片段 N 里 + # `p = Point(1,2)` / `p.x` 之外的**方法调用**(`p.get()`)能解析到片段 1 + # 的 `.Point.get` 定义(构造/类全局同理)。 + # 登记失败(上限/文件缺失)只告警:更晚的片段对这些符号会在链接期 + # undefined(会话继续,见 ReplFragImplibCount 的上限说明)。 + if needs_implib != 0: + if _is_file(implib_path) != 0 and ReplFragImplibRecord(implib_path) != 0: + stdio.printf("片段 %s: 已登记片段 import lib(后续片段可解析本片段定义的" + "类型/方法符号): %s\n", ReplNameBuf, implib_path) + else: + stdio.printf("片段 %s: 片段 import lib 未登记(已登记 %d 个,上限 %d;或文件" + "缺失)——更晚的片段可能解析不到本片段定义的类型/方法符号\n", + ReplNameBuf, ReplFragImplibCount, _REPL_FRAG_IMPLIB_MAX) + + # ⑫ 动态加载 + 取地址(LoadLibraryA/GetProcAddress 见 includes/w32/win32base.vp:65/68) + h_lib: win32base.HANDLE = win32base.LoadLibraryA(dll_path) + if h_lib is None: + stdio.printf("片段 %s: LoadLibraryA 失败: %s\n", ReplNameBuf, dll_path) + if ReplHostImplib is not None: + stdio.printf(" 原因提示: 片段 DLL 的导入表指向宿主 exe(%s);" + "若 GetLastError 是 127(找不到指定过程)/ 加载器 0xC0000139," + "多为宿主与该 import lib 非同一次构建产物、或宿主缺该导出;" + "进程镜像名(%s)与 import lib 记录名不一致时同样会失败\n", + ReplHostImplib, dll_path) + return 1 + fp_addr: VOIDPTR = win32base.GetProcAddress(h_lib, ReplNameBuf) + if fp_addr is None: + stdio.printf("片段 %s: GetProcAddress 返回空(导出表中无该符号): %s\n", + ReplNameBuf, dll_path) + return 1 + + # ⑫' 会话函数注册(M3):DLL 已加载 ⇒ GetProcAddress(裸名) 取地址 → 写 ctx + # 函数指针槽 → 追加/更新会话函数表。放在**调用片段入口之前**:失败则本片段 + # 的语句一律不执行(只留下已加载但未被引用的 DLL),且由水位回滚丢弃半批登记。 + # 本片段内对同名函数的调用仍走直接调用(模块内有 define)⇒ 注册只服务后续片段。 + if _repl_register_session_funcs(h_lib, n) != 0: + if ReplCtxObj is not None: + ReplCtxObj.FuncCount = ReplSavedFuncCount + return 1 + + # ⑬ 调用前复位结果区(kind=none),并刷新 var_count/last_frag(供检视/调试); + # 存入 t.Callable 槽并间接调用(函数地址必须存活到调用之后,故不 FreeLibrary: + # 会话模型下模块常驻,供后续片段复用) + head: rawptr[GenTypes.ReplResult] = _ctx_head() + if head is None: + return 1 + head.Kind = GenTypes.REPL_RES_NONE + head.VarCount = ReplCtxObj.Count + head.LastFrag = n + ReplFragFn = fp_addr + # 实参类型即构造出的调用签名形参类型(bytes ⇒ i8*),与片段定义一致 + r_rc: int = ReplFragFn(ReplCtxBuf) + + # ⑭ 从 ctx 结果区读回并回显(片段返回值只作状态码) + if _repl_echo_result(ReplNameBuf, head) != 0: + stdio.printf("片段 %s: 结果区读取失败\n", ReplNameBuf) + return 1 + if r_rc != 0: + stdio.printf("片段 %s: 片段返回非零状态码 %d\n", ReplNameBuf, r_rc) + # ⑭' M4:片段**成功** ⇒ 把本片段新增的 CDefine/CTypedef 条目并入会话基线。 + # 失败路径(早返回 / longjmp)不执行 ⇒ 基线天然不含失败片段的半成品; + # 截断(ReplBaselineTruncate)只是水位一致性的防御性保障。 + ReplBaselineCapture() + return 0 + + +# ============================================================ +# run - M2 REPL 主循环 +# ============================================================ +def run(mb: rawptr[memhub.MemBuddy]) -> int: + """M2/M3 REPL:读一行/一整块 → 解析 → 编译为 DLL 并调用(会话变量经 ctx 槽)→ 回显; + 行尾 ':' 进入块级续行(提示符 "... "),空行或 EOF 结束并整块执行一次;错误不终止进程。""" + global ReplJmpBuf, ReplSavedVarCount, ReplSavedFuncCount, ReplInBlock, ReplBlockLen + if mb is None or _perm_pool is None: + stdio.printf("REPL: 需要主池与 PermPool(由 main 注入 _perm_pool)\n") + return 1 + + # 解析表(关键字/运算符)建在 PermPool:它们是 ast 的模块级全局、仅首次建表; + # 若建在 mb 上会被每行 reset_to(mark) 释放 → 下一行解析 use-after-free 崩溃。 + ast._init_tables(_perm_pool) + + jb: t.CPtr = _perm_pool.alloc(_REPL_JMPBUF_SIZE) + if jb is None: + stdio.printf("REPL: jmp_buf 分配失败\n") + return 1 + # 会话级注册:ReplJmpBuf 供每片段入口幂等重注册;VLogger 侧登记一次即可长期有效, + # 只在 run 退出时 clear(中途不清 —— 这是"覆盖整条流水线"的前提)。 + ReplJmpBuf = jb + ReplSavedVarCount = 0 + ReplSavedFuncCount = 0 + VLogger.set_repl_jmpbuf(jb) + # jit_flag 由 main 在调用 run 前置 True(见 main.vp --repl 分支),run 退出时复位 False + lg: VLogger.Logger = VLogger.logger + + # 恢复点必须"**先武装再使用**":ReplInitSession 及其调用(GetTempPathA / 建目录 / + # 一次性常驻分配)内部若走 VLogger.error(→ repl_abort → longjmp),jmp_buf 必须 + # 已被 _setjmp 初始化过;否则是对**未初始化 jmp_buf** 的 longjmp(未定义行为, + # 表现为"启动期进程消失、提示符永不出现")。会话初始化是**唯一**必须成功的阶段 + # (没有常驻缓冲/ctx 就没有片段通路),故这里恢复后按失败退出,不做"带伤继续"。 + # (注:JmpBuf 尚未武装的窗口只有上面的 ast._init_tables —— 那时 _repl_jmpbuf 也 + # 尚未注册,repl_abort 走 sys.exit(1),与批处理同口径。) + rc_init: int = _setjmp(jb, None) + if rc_init != 0: + stdio.printf("REPL: 会话初始化中断(诊断见上)—— 无常驻缓冲/ctx,REPL 无法启动\n") + stdio.fflush(None) + ReplJmpBuf = None + VLogger.clear_repl_jmpbuf() + if lg is not None: + lg.set_jit_flag(False) + return 1 + + # 会话初始化:临时目录 + 常驻缓冲 + ctx(均 PermPool,见 ReplInitSession) + if ReplInitSession() != 0: + stdio.printf("REPL: 会话初始化失败(临时目录/常驻缓冲/ctx)\n") + ReplJmpBuf = None + VLogger.clear_repl_jmpbuf() + if lg is not None: + lg.set_jit_flag(False) + return 1 + + h_in: win32base.HANDLE = w32file.GetStdHandle(w32file.STD_INPUT_HANDLE) + line: bytes = stdlib.malloc(_REPL_LINE_CAP) + if line is None: + stdio.printf("REPL: 行缓冲分配失败\n") + ReplJmpBuf = None + VLogger.clear_repl_jmpbuf() + if lg is not None: + lg.set_jit_flag(False) + return 1 + + stdio.printf("TransPyV REPL (M2/M3/M4) - 会话变量 + 会话函数(跨片段)+ ctx 回传结果;\n") + stdio.printf(" 支持表达式(1+2)、定义(x = 1 / x: int = 5)、引用(x + 1)、\n") + stdio.printf(" 增强赋值(x += 2)、取址(p = c.Addr(x))等;空行跳过,Ctrl+Z 或 EOF 退出\n") + stdio.printf(" 多行块:行尾 ':' 进入续行(提示符 ...),空行结束并整块执行一次\n") + stdio.printf(" 跨片段函数:块里顶格的 def 会被提升为片段模块的 export def(裸名导出),\n") + stdio.printf(" 后续片段可直接调用(如 def f(x: int) -> int: 换行 +4 空格 return x + 1);\n") + stdio.printf(" 限制:签名须单行、不支持 async def、函数体内不支持 raise、不支持偏函数\n") + stdio.printf(" 跨片段类型(M4):块里的 class 会被翻译进会话(类型名 %s.<类名>),\n", + ReplSessionSha1) + stdio.printf(" 后续片段可实例化、读写字段、调用方法;import 的 includes 模块\n") + stdio.printf(" 其类同样注册进会话(缺 .vpi 存根时该模块的类不可用,会话继续)\n") + stdio.printf(" 片段临时目录: %s\n", ReplTmpDir) + stdio.fflush(None) + + # === 会话级 includes 初始化(只做一次):扫描 + 注册 SHA1 + 预生成 .vpi + # + 泛型模板注册 + 逐模块类注册 === + # 会话开始即武装一次恢复点:includes 扫描/存根生成/模板注册内部若 fail-fast + # (VLogger.error → repl_abort → longjmp),必须落到本帧,否则跳到未武装的 + # jmp_buf 是未定义行为。恢复后按宽容口径继续进入读行循环。 + # 【硬保证】提示符必现:includes 的任何失败(含"类注册整体失败")都只影响 + # includes 类的可用性,绝不阻止进入读行循环 —— 本段所有出口都不 return。 + inc_mark: t.CSizeT = mb.mark() + rc_inc: int = _setjmp(jb, None) + if rc_inc != 0: + stdio.printf("REPL: includes 初始化中断(已注册 %d 个模块、存根 %d 个):" + "import includes 模块可能失败,其余片段不受影响\n", + ReplIncMods, ReplIncVpiNew) + mb.reset_to(inc_mark) + else: + # 扫描结果/注册表都在 stdlib(create_scan_result 用 stdlib.malloc), + # 扫描期的 mb 分配(sha1 十六进制串等)在注册后即可回收。 + # M4:本调用内部还会做"includes 类注册"(逐模块 _setjmp,见 + # ReplInitIncludesClasses 的退出约定)。 + ReplInitIncludes(mb) + mb.reset_to(inc_mark) + # ⚠ 必须在 run 的帧上重新武装恢复点(注意:**不能**把这几行包成辅助函数—— + # _setjmp 留下的跳转目标属于"调用它的那个帧",包成函数后该帧立刻返回, + # 目标又变成死帧)。ReplInitIncludes 内部的逐模块 _setjmp + # (ReplInitIncludesClasses)已把目标指向那些**已返回**的帧;此处重设之后, + # 下面的收尾与整个读行循环都在本帧的恢复点保护下 —— 这正是"includes 类注册 + # 之后提示符消失"(longjmp 到已返回的帧 → 未定义行为)的修复点。 + rc_arm: int = _setjmp(jb, None) + if rc_arm != 0: + stdio.printf("REPL: includes 类布局收尾中断(诊断见上):布局注册可能不完整," + "会话继续\n") + else: + # 收尾:收敛登记"父类在后续遍才就绪"的类 + 逐类告警拒绝 + 汇总。放在 + # **重武装之后**:其内部可能经 GenApi.register_struct → VLogger.error 触发 + # longjmp,此刻必须落在本帧(否则就是上面那个死帧问题)。 + VpiReader.ReplStubLayoutFinish() + # 关闭"存根布局恢复"必须无条件执行:ReplStubLayoutMode 不归零会让片段翻译 + # 误走 REPL 存根路径(本函数只置标志,无失败路径)。 + VpiReader.ReplStubLayoutEnd() + + # === 宿主导出符号(跨模块调用):定位宿主 exe 的 import lib(只读探测) === + # 命中 ⇒ 片段链接时把它加进命令行(片段 DLL 从宿主导入 includes 符号,共用同一份 + # 代码与数据);未命中 ⇒ 只告警,不带 import lib 链接(跨模块调用会在链接期失败, + # 由 ReplExecFragment 打印明确原因,会话继续)。 + if ReplFindHostImplib() != 0: + stdio.printf("宿主符号导入库: %s(片段可跨模块调用 includes 函数/全局)\n", + ReplHostImplib) + else: + stdio.printf("宿主符号导入库: 未找到 .dll.a;片段调用 includes 模块级" + "函数会在链接期报 undefined symbol(会话不终止)。请用 " + "TransPyV/project.vpj(已声明 -Wl,--export-all-symbols)构建宿主," + "并让 exe 与 .dll.a 同目录\n") + stdio.fflush(None) + + # === 读行循环:单行片段 + 块级续行 === + # 走到这里 = 初始化阶段的**全部**失败路径都已消化(includes 类注册失败/超预算 + # 只降级功能,不影响可用性)⇒ 提示符必然出现。日志里这一行是"REPL 起来了"的 + # 硬判据(紧随其后即 ">>> ")。 + stdio.printf("REPL: 初始化完成,进入读行循环\n") + stdio.fflush(None) + # 块状态(ReplInBlock/ReplBlockLen)在每次"块结束/失败/EOF/溢出"后一律归零: + # ① 空行/EOF 结束续行 → 执行整块后归零;② 执行期 longjmp → 恢复分支归零; + # ③ 块缓冲溢出 → 丢弃整块并归零。 + # 归零是"残块不污染下一行"的依据:下一次 _block_append 从索引 0 覆盖写并重写 NUL, + # 旧字节不可达(且新块解析只经 NUL 结尾串,不读旧长度之后的内容)。 + ReplInBlock = 0 + ReplBlockLen = 0 + while 1: + # 提示符:续行 "... "、正常 ">>> "(与既有 ">>> " 同一打印点、同一刷新时机) + if ReplInBlock != 0: + stdio.printf("... ") + else: + stdio.printf(">>> ") + stdio.fflush(None) + n: int = _read_line(h_in, line, _REPL_LINE_CAP) + eof_hit: int = 0 + if n < 0: # EOF:先按"终止行"处理,再退出 + eof_hit = 1 + n = 0 + # 每行独立:先取水位,再 _setjmp(武装本片段/本块的恢复点,覆盖整条流水线); + # 解析/翻译/llc/加载/调用期的任何 longjmp 都回收到 mark 并继续。 + mark: t.CSizeT = mb.mark() + rc: int = _setjmp(jb, None) + if rc != 0: + # longjmp 返回:诊断已由 VLogger.error / fatal_error 等打印(均以换行 + # 结尾),此处只清块状态(残块不得留给下一行输入)+ 回滚会话表水位 + # (丢弃失败片段的半成品登记)+ 回收本行分配,随后直接打印下一个提示符 + # ——不再额外打印"已从错误恢复"。块状态归零对单行路径是恒等写入。 + ReplInBlock = 0 + ReplBlockLen = 0 + if ReplCtxObj is not None: + ReplCtxObj.Count = ReplSavedVarCount + ReplCtxObj.FuncCount = ReplSavedFuncCount + # M4:会话基线(CDefine/CTypedef)水位回滚(与上面的表水位同思路) + ReplBaselineTruncate(ReplSavedCDefineCount, ReplSavedCTypedefCount) + mb.reset_to(mark) + if eof_hit != 0: + break + continue + if eof_hit != 0: + # EOF 结束:续行中且缓冲非空 ⇒ 先把已缓存块执行掉(等价空行终止); + # 否则走既有退出路径(打印换行后退出) + if ReplInBlock != 0 and ReplBlockLen > 0: + _exec_repl_src(mb, ReplBlockBuf) + ReplInBlock = 0 + ReplBlockLen = 0 + mb.reset_to(mark) + stdio.printf("\n") + break + if ReplInBlock != 0: + if n == 0: + # 空行结束续行:整块作为**一个**片段执行一次;块内错误只打印、会话存活 + if ReplBlockLen > 0: + _exec_repl_src(mb, ReplBlockBuf) + ReplInBlock = 0 + ReplBlockLen = 0 + mb.reset_to(mark) + continue + # 续行中:每行都入缓冲;不因行尾 ':' 反复切换,也不做缩进分析 + if _block_append(line, n) != 0: + stdio.printf("REPL: 块输入超过块缓冲上界 %d 字节:本块已丢弃," + "回到 >>> 继续(可减少块体积或分块执行)\n", + _REPL_BLOCK_CAP) + ReplInBlock = 0 + ReplBlockLen = 0 + mb.reset_to(mark) + continue + # ==== 以下为既有单行路径(未进入续行时逐语句不变)==== + if n == 0: # 空行:跳过 + mb.reset_to(mark) + continue + if _line_starts_block(line, n) != 0: + # 行尾 ':' ⇒ 进入续行:该行进缓冲、本行不执行,提示符变 "... "。 + # 首行不可能溢出(行缓冲上界 4095 < 块缓冲上界 8191):append 失败即 + # 按下方单行路径处理,不进入续行状态。 + if _block_append(line, n) == 0: + ReplInBlock = 1 + mb.reset_to(mark) + continue + _exec_repl_src(mb, line) + mb.reset_to(mark) + + stdlib.free(line) + # 会话结束才清空恢复点:中途从不清(保证流水线全程可 longjmp) + ReplJmpBuf = None + VLogger.clear_repl_jmpbuf() + if lg is not None: + lg.set_jit_flag(False) + return 0 diff --git a/App/lib/core/VLogger.vp b/App/lib/core/VLogger.vp index 248dbe9..f39ec21 100644 --- a/App/lib/core/VLogger.vp +++ b/App/lib/core/VLogger.vp @@ -14,6 +14,53 @@ import w32.win32base as win32base # 输出格式: ├ LEVEL: message / ├ LEVEL[category]: message(与 TPC lib/core/VLogger.py 对齐) # ============================================================ +# 错误恢复(REPL/JIT 模式):error 打印后 longjmp 跳回 REPL 的 setjmp 点。 +# mingw-w64 x86_64:longjmp 原型为 void longjmp(jmp_buf, int);setjmp 宏展开为 +# _setjmp(buf, NULL)(jmp_buf 实为 _JBTYPE[16],见 Repl.vp 的 _REPL_JMPBUF_SIZE)。 +extern def longjmp(buf: t.CPtr, val: int) + + +# REPL 恢复点指针(由 Repl 在会话开始时注册、会话结束时清除)。 +# 生命周期 = 整个 REPL 会话:Repl 每轮片段都会重新武装(Repl.run 每轮 _setjmp + +# Repl.ReplExecFragment 入口重注册),故它覆盖整条片段流水线(翻译 → 写 .ll → +# llc → clang++ → LoadLibrary → GetProcAddress → 调用 → 回显),而不只是翻译期。 +# 批处理路径从不调用 set_repl_jmpbuf ⇒ 恒为 None ⇒ 所有出口退回 sys.exit(1)。 +_repl_jmpbuf: t.CPtr = None + + +# ============================================================ +# repl_abort - TPV 内"诊断后终止"的唯一出口 +# +# 约定:调用方**已打印**诊断(Traceback / VLogger.error / [OOM] 等),本函数只决定 +# "跳回 REPL 继续"还是"终止进程": +# * REPL 会话(_repl_jmpbuf 已注册):fflush 后 longjmp 回 Repl.run 的恢复点, +# 进程存活、提示符继续(宽容 Traceback); +# * 批处理 / 未武装:sys.exit(1),与改动前的 fail-fast 行为逐字一致。 +# 收敛点:Logger.error、模块级 error、TypeGenErr.fatal_error、 +# ImportsGenMixin._report_import_error、CallGenBase/VpiReader 的链表哨兵。 +# ============================================================ +def repl_abort() -> int: + """诊断后的统一出口:已注册恢复点则 longjmp 回 REPL,否则终止进程。""" + if _repl_jmpbuf is not None: + stdio.fflush(None) + longjmp(_repl_jmpbuf, 1) + sys.exit(1) + return 0 + + +def set_repl_jmpbuf(buf: t.CPtr) -> int: + """注册 REPL 的 jmp_buf(会话开始;每片段入口可幂等重注册)。""" + global _repl_jmpbuf + _repl_jmpbuf = buf + return 0 + + +def clear_repl_jmpbuf() -> int: + """清除已注册的 jmp_buf(REPL 会话结束时调用;中途不清)。""" + global _repl_jmpbuf + _repl_jmpbuf = None + return 0 + # 日志级别 class LogLevel(t.CEnum): DEBUG = 0 @@ -47,12 +94,14 @@ class Logger: _level: int _console_handle: win32base.HANDLE _use_color: int + jit_flag: bool __mbuddy__: rawptr[memhub.MemBuddy] def __init__(self, level: int = 1): self._level = level self._console_handle = w32file.GetStdHandle(w32file.STD_OUTPUT_HANDLE) self._use_color = 1 + self.jit_flag = False self.__mbuddy__ = _mbuddy def _set_color(self, attr: WORD) -> int: @@ -108,7 +157,11 @@ class Logger: ret: int = self._log(LogLevel.ERROR, "├", "ERROR", msg, FOREGROUND_RED, FOREGROUND_RED | FOREGROUND_INTENSITY, category) - # 错误立即终止进程(避免错误后继续执行导致连锁崩溃) + # JIT/REPL 模式:诊断已打印,经统一出口跳回 REPL 循环(未武装时该出口仍终止进程) + if self.jit_flag: + repl_abort() + return ret + # 批处理模式:错误立即终止进程(行为与改动前逐字一致) sys.exit(1) return ret @@ -128,6 +181,11 @@ class Logger: self._level = level return 0 + def set_jit_flag(self, v: bool) -> int: + """设置 JIT/REPL 模式标志:True 时 error 改为 longjmp 回 REPL 循环。""" + self.jit_flag = v + return 0 + # ============================================================ # 高级日志方法 # ============================================================ @@ -211,10 +269,10 @@ def warning(msg: str, category: str = "") -> int: def error(msg: str, category: str = "") -> int: - """输出 ERROR 级别日志并立即终止进程。""" + """输出 ERROR 级别日志:批处理模式终止进程;JIT/REPL 模式 longjmp 回 REPL。""" if log is not None: return log.error(msg, category) - # logger 不可用时也直接退出 - sys.exit(1) + # logger 不可用时也走统一出口(REPL 已注册恢复点时仍可恢复) + repl_abort() return 0 diff --git a/App/main.vp b/App/main.vp index 74d619b..d1eef87 100644 --- a/App/main.vp +++ b/App/main.vp @@ -1,5 +1,6 @@ import lib.core.Gen.GenApi as GenApi import lib.core.VLogger as VLogger +import lib.core.Repl as Repl import lib.core.Gen.Translator as HandlesTranslator import lib.Phase.BuildPipeline as BuildPipeline import lib.core.IncludesScanner as IncludesScanner @@ -41,13 +42,16 @@ CODE_PAGE: t.CDefine = 65001 SRC_BUF_SIZE: t.CDefine = 1048576 # Version (dev/beta/rc) V_VIPER_VERSION: t.CDefine = "1.0.3-dev.31 2026-07-27 Non-LTS" -V_TPV_VERSION: t.CDefine = "0.0.3-dev.428" +V_TPV_VERSION: t.CArray[char] | t.attr.section(".PRVERSN") = "0.0.3-dev.428" V_RUNTIME_VERSION: t.CDefine = "0.2.3+19-Non-LTS" V_TPC_COMMIT: t.CDefine = "e376b949c9" V_TPV_COMMIT: t.CDefine = "ab0f37e1a8" V_RUNTIME_COMMIT: t.CDefine = "f087aa88e2" -V_TARGET: t.CDefine = "x86_64-w64-windows-gnu" -V_LLVM: t.CDefine = "22.1.8" +V_TARGET: t.CDefine = "x86_64-w64-windows-gnu" +V_LLVM: t.CDefine = "22.1.8" +# --repl 模板工程查找缓冲(repl.vpj 命中路径 / 未命中诊断文本) +REPL_PROJ_PATH_CAP: t.CDefine = 1024 +REPL_PROJ_DIAG_CAP: t.CDefine = 2048 # ============================================================ # main: Command-line entry @@ -152,6 +156,7 @@ def main() -> int: # 早于 parse_args,值取自 L70-72 的原始命令行 strstr —— 与 --detection 同范式) parser.add_argument("--detection-deep", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Deep detection: probe every allocation and enable the periodic full-table scan (implies --detection; extremely slow, diagnostics only)") parser.add_argument("--show-args", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Show all parsed arguments") + parser.add_argument("--repl", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Start the interactive REPL (M2 session variables + M3 cross-fragment functions: fragment -> DLL -> call)") parser.add_argument("--build-dir", None, argparse.STRING, 0, None, False, argparse.STORE, "Override project.vpj build_dir (default ./.tpv_build; relative paths resolve against the project dir)") parser.add_argument("--no-llvmpeephole", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Disable all local llvmlite peephole passes (T2 store/load forward & ret-zero, T3 no-op bitcast) in FunctionPrint serialization") parser.add_argument("--version", "-v", argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Show version and exit") @@ -183,6 +188,37 @@ Target: %s """, V_VIPER_VERSION, V_RUNTIME_VERSION, V_TPV_VERSION, V_TPV_COMMIT, V_TPC_COMMIT, V_RUNTIME_COMMIT, V_TARGET, V_LLVM) return 0 + # === --repl 守卫:与 --phase2command / --asan 互斥 === + # 位置:在下面 --asan / --phase2command 的全局状态设置与清理副作用之前 + #(--asan 会切 includes 缓存后缀并清空 .binary_asan、--phase2command 会设置 + # BuildPipeline._phase2_cmd_path),故必须最先判定。 + # ① --phase2command:_phase2_cmd_path 非空时 compile_ll_to_obj 只把 opt/llc + # 命令写进 bat 而**不执行**(BuildPipeline.vp:322-344)⇒ 片段 .obj 不会生成, + # Repl 第 ⑩ 步的 clang++ -shared 必然失败(Repl.vp 的 ReplExecFragment)。 + # ② --asan:会给片段 .ll 先做 opt -passes=asan 插桩 + # (BuildPipeline.vp:346-361),而片段产物是运行期 LoadLibraryA 载入的 DLL, + # 宿主进程并未挂 ASan 运行库,"片段 obj 被插桩"语义不明。 + repl_flag: bool = args.get_bool("repl") + if repl_flag: + if args.get_str("phase2command") is not None: + stdio.printf("FAIL: --repl 与 --phase2command 互斥\n") + stdio.printf(" 原因: --phase2command 下 compile_ll_to_obj 只写 bat 不执行 llc,\n") + stdio.printf(" 片段 .obj 不会生成,随后的 clang++ -shared 必然失败\n") + stdio.printf(" 处理: 去掉 --phase2command(REPL 需要立即产出并加载片段 DLL)\n") + argparse.release(args) + return 2 + if args.get_bool("asan"): + stdio.printf("FAIL: --repl 与 --asan 互斥\n") + stdio.printf(" 原因: --asan 先对片段 .ll 做 ASan 插桩,而片段产物是运行期\n") + stdio.printf(" LoadLibraryA 载入的 DLL,宿主进程未挂 ASan 运行库,插桩语义不明\n") + stdio.printf(" 处理: 去掉 --asan(如需排查内存问题,请用工程模式构建 ASan 版 TransPyV)\n") + argparse.release(args) + return 2 + + # === --repl: M1 交互式循环(表达式片段 → DLL → 调用回显)=== + # 分支位置:见下方"工程配置加载"之后(Repl 需要 Config 的 compiler/linker/target 通道), + # 仍在 Phase1/Phase2 之前;该分支直接返回。 + # 打印解析结果 # log.banner("TransPyV 参数解析结果") @@ -249,9 +285,30 @@ Target: %s llvmlite.G_PeepEnable = 0 log.info("--no-llvmpeephole 已启用:关闭 FunctionPrint 本地窥孔(RenumberSSA 保留)", "peep") - # 加载 project.vpj 配置 + # === --repl 启动便利:未给 --project 时按 cwd → exe 同目录 查找 repl.vpj === + # 命中后按 --project 等价路径喂给下面既有的 Config.Load_project_config 流程 + #(不另写一套加载器);两处都没有则 fail-fast,诊断文本含"需要 --project"、 + # 两个已查位置与模板位置(由 Repl.ReplFindProject 写入 repl_diag_buf)。 proj_loaded: int = 0 proj_path: str = project + if repl_flag and proj_path is None: + repl_proj_buf: bytes = stdlib.malloc(REPL_PROJ_PATH_CAP) + repl_diag_buf: bytes = stdlib.malloc(REPL_PROJ_DIAG_CAP) + if repl_proj_buf is None or repl_diag_buf is None: + stdio.printf("FAIL: --repl 查找 repl.vpj 的路径缓冲分配失败\n") + argparse.release(args) + return 1 + if Repl.ReplFindProject(repl_proj_buf, REPL_PROJ_PATH_CAP, + repl_diag_buf, REPL_PROJ_DIAG_CAP) != 0: + proj_path = repl_proj_buf + log.info(f"--repl: 未指定 --project,使用查找到的模板工程 {proj_path}", "repl") + else: + stdio.printf("FAIL: --repl 未找到工程文件 repl.vpj\n") + stdio.printf("%s", repl_diag_buf) + stdio.printf(" 也可用 --project 显式指定工程文件(如本仓库的 TransPyV/project.vpj)\n") + argparse.release(args) + return 1 + # 加载工程配置(显式 --project / --repl 查找到的 repl.vpj) if proj_path is not None: log.banner("工程配置") cfg_ret: int = Config.Load_project_config(proj_path) @@ -290,6 +347,17 @@ Target: %s Config.resolve_paths(proj_dir) Config.print_config() + # === --repl: M1 交互式循环(表达式片段 → IR → llc → clang++ -shared → DLL → 调用回显)=== + # 位置:工程配置加载之后(Repl 经 Config 取 compiler/linker 命令与 target triple; + # 未加载 project.vpj 时 Repl 退回内置默认 llc/clang++),仍在 Phase1/Phase2 之前。 + if args.get_bool("repl"): + Repl._perm_pool = perm_pool # jmp_buf/解析表/会话缓冲须跨行存活,必须用 PermPool + if log is not None: + log.set_jit_flag(True) # error 改为 longjmp 回 REPL(Repl.run 退出时复位 False) + rc_repl: int = Repl.run(mb) + argparse.release(args) + return rc_repl + # --asan 要求工程在 project.vpj 的 linker.static_libs 声明 ASan 运行库(TPV 不硬编码路径),缺失则 fail-fast if BuildPipeline._asan_mode != 0 and Config.asan_runtime_declared() == 0: log.error("启用 --asan 需要在 project.vpj 的 linker.static_libs 里声明 ASan 运行库:", "asan") diff --git a/README.md b/README.md index e840b0c..861a3bc 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,7 @@ exe\TransPyV.exe --project TransPyV\Test\project.vpj --clean --run | `--rebuild-includes` | 只删 includes 预编译缓存(`includes.binary`),强制重编 includes | | `--clear-cache` | 只清 temp 缓存(`.vpi` / `.stub.ll` / `.text.ll`),强制重生成 stub | | `--run` | 编译成功后立即执行产物 | +| `--repl` | 交互式 REPL(M1:表达式片段 → DLL → 调用回显);未给 `--project` 时按 cwd → `TransPyV.exe` 同目录查找 `repl.vpj` 模板工程(找不到则报错并提示 `--project`);与 `--asan` / `--phase2command` 互斥 | | `--asan` | ASan 模式:先 `opt -passes=asan` 插桩;运行库须由 `project.vpj` 的 `linker.static_libs` 声明,否则 fail-fast | | `--phase2command ` | 不执行 `llc`/链接,只把命令写进指定 `.bat` | | `--nowarn-register` | 抑制 Phase A 依赖类 WARN(跨模块 `text.ll` 未生成,下遍收敛) | @@ -349,6 +350,7 @@ exe\TransPyV.exe --project TransPyV\Test\project.vpj --clean --run | `--rebuild-includes` | delete only the includes cache (`includes.binary`), forcing a full includes rebuild | | `--clear-cache` | clear only temp (`.vpi` / `.stub.ll` / `.text.ll`), forcing stub regeneration | | `--run` | execute the produced binary on success | +| `--repl` | interactive REPL (M1: expression fragment → DLL → call and echo); without `--project` it looks for the `repl.vpj` template project in cwd, then next to `TransPyV.exe` (errors out with a `--project` hint if neither exists); mutually exclusive with `--asan` / `--phase2command` | | `--asan` | ASan mode: `opt -passes=asan` first; runtimes must be declared in `project.vpj` `linker.static_libs`, else fail-fast | | `--phase2command ` | do not run `llc`/link; write the commands into the given `.bat` | | `--nowarn-register` | suppress Phase A dependency WARNs (cross-module `text.ll` not yet emitted, converges next pass) | diff --git a/project.vpj b/project.vpj index 3d53866..e07818e 100644 --- a/project.vpj +++ b/project.vpj @@ -24,6 +24,7 @@ "-Wl,--allow-multiple-definition", "-Wl,--stack,0x1000000", "-Wl,--no-insert-timestamp", + "-Wl,--export-all-symbols", "-g" ], "static_libs": [ diff --git a/repl.vpj b/repl.vpj new file mode 100644 index 0000000..702f4e1 --- /dev/null +++ b/repl.vpj @@ -0,0 +1,41 @@ +{ + "$schema": "https://raw.githubusercontent.com/TermiNexus/TransPyC/main/schemas/project-schema.json", + "name": "viperepl", + "version": "1.0.0", + "build_dir": "%temp%/viperepl/.tpv_build", + "compiler": { + "cmd": "llc", + "flags": [ + "-filetype=obj", + "-relocation-model=pic", + "-O0" + ] + }, + "linker": { + "cmd": "D:/msys64/mingw64/bin/clang++.exe", + "flags": [ + "-fuse-ld=lld", + "-lmsvcrt", + "-lucrt", + "-lpthread", + "-lmingwex", + "-lkernel32", + "-Wl,--allow-multiple-definition", + "-Wl,--stack,0x1000000", + "-Wl,--no-insert-timestamp", + "-g" + ], + "output": "repl.dll" + }, + "includes": [ + "../includes" + ], + "target": { + "triple": "x86_64-pc-windows-gnu", + "datalayout": "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" + }, + "options": { + "sha1_slice_level": 1, + "strict_mode": true + } +}