diff --git a/App/lib/Stub/StubGen/Converter.vp b/App/lib/Stub/StubGen/Converter.vp index 85498c8..d7bd266 100644 --- a/App/lib/Stub/StubGen/Converter.vp +++ b/App/lib/Stub/StubGen/Converter.vp @@ -418,6 +418,23 @@ def _PyiRejectCReturnDecorator(node: ast.AST*, decorators: list[ast.AST*]*) -> i # ============================================================ +# 逃逸摘要计算用的复用临时区(74 槽:gset[0]=global 计数、gset[1..65)=名哈希、box=gset+65)。 +# 只分配一次,避免每个 def 都分配;布局与 StructGen 的 EscScratch 完全一致。 +_g_pyi_esc_scratch: sizet* = None + + +def _PyiEnsureEscScratch() -> int: + """惰性分配摘要临时区;1=可用 / 0=失败""" + # 必须声明 global:否则赋值会落到**局部**,调用方读到的仍是 NULL 全局, + # box 指针算成 0+520 → ASan: WRITE on 0x208(本文件它处同款写法见 _g_pyi_cdefine_found)。 + global _g_pyi_esc_scratch + if _g_pyi_esc_scratch is not None: return 1 + _g_pyi_esc_scratch = sizet*(stdlib.malloc(74 * 8)) + if _g_pyi_esc_scratch is None: return 0 + string.memset(_g_pyi_esc_scratch, 0, 74 * 8) + return 1 + + # _PyiFunction - emit a function signature (def name(...) -> Ret: pass) def _PyiFunction(buf: bytes, buf_size: sizet, pos: sizet, fn: ast.FunctionDef*, indent_level: int, class_name: str) -> sizet: """Emit a function signature into buf and return the new pos""" @@ -496,7 +513,20 @@ def _PyiFunction(buf: bytes, buf_size: sizet, pos: sizet, fn: ast.FunctionDef*, # Without a `->` annotation, @c.CReturn is removed: fail fast to regenerate vpi _PyiRejectCReturnDecorator(fn, fn.decorator_list) - pos = _PyiEmit(buf, buf_size, pos, ": pass\n") + pos = _PyiEmit(buf, buf_size, pos, ": pass") + # v3 跨模块逃逸摘要:把形参逃逸掩码与返回借用位写成**同一行**的行尾注释,供 VpiReader + # 解析后在别的模块的调用点查询(跨模块摘要表按文件重置,只能靠 vpi 传递)。 + # 只在真有逃逸/返回借用时写,保持 vpi 干净:无标记 = 无摘要 = 调用点保守放行。 + if _PyiEnsureEscScratch() != 0: + esc_box_c: sizet* = sizet*(uint64(_g_pyi_esc_scratch) + 520) + if GenApi.compute_escape_summary(None, fn, _g_pyi_esc_scratch, esc_box_c) == 0: + if esc_box_c[0] != 0 or esc_box_c[1] != 0: + tmp_c: bytes = stdlib.malloc(64) + if tmp_c is not None: + viperlib.snprintf(tmp_c, 64, " # esc=%d ret=%d", int(esc_box_c[0]), int(esc_box_c[1])) + pos = _PyiEmit(buf, buf_size, pos, tmp_c) + stdlib.free(tmp_c) + pos = _PyiEmit(buf, buf_size, pos, "\n") return pos diff --git a/App/lib/Stub/StubGen/VpiReader.vp b/App/lib/Stub/StubGen/VpiReader.vp index e15a786..5600dad 100644 --- a/App/lib/Stub/StubGen/VpiReader.vp +++ b/App/lib/Stub/StubGen/VpiReader.vp @@ -109,6 +109,48 @@ define VPI_IDX_FNV_OFFSET = 2166136261 define VPI_IDX_FNV_PRIME = 16777619 define VPI_IDX_FNV_MASK = 4294967295 +# v3 跨模块逃逸摘要缓存(.vpi 行尾 `# esc= ret=`,由 StubGen.Converter._PyiFunction 写出)。 +# 键 = "{sha1}.{Class}.{method}"(方法)或 "{sha1}.{func}"(模块级函数); +# 值打包:低 32 位 = 形参逃逸掩码,高 32 位 = 返回借用位。无逃逸/无返回借用的 def 不写条目, +# 因此"查不到"即"无摘要"(调用点保守放行),与本地表口径一致。 +define VPI_ESC_MAX = 8192 +define VPI_ESC_KEY_LEN = 224 + +_g_vpi_esc_keys: bytes = None +_g_vpi_esc_vals: sizet* = None +_g_vpi_esc_count: int = 0 +# "本模块的 vpi 已加载过"备忘(每项 17 字节 sha1)。调用点每处都要查摘要, +# 若每次都走 _ensure_vpi_ret_annot_loaded 的 O(N) 前缀判定会显著拖慢编译。 +define VPI_ESC_MOD_MAX = 512 +_g_vpi_esc_mods: bytes = None +_g_vpi_esc_mod_count: int = 0 + +def _vpi_escape_module_loaded(src_sha1: str) -> int: + """该模块的 vpi 是否已由逃逸查询路径加载过;1=是 / 0=否""" + if _g_vpi_esc_mods is None or src_sha1 is None: return 0 + s0: int = src_sha1[0] + s1: int = src_sha1[1] + i: int = 0 + while i < _g_vpi_esc_mod_count: + ent_m: bytes = _g_vpi_esc_mods + sizet(i) * 17 + if ent_m[0] == s0 and ent_m[1] == s1 and string.strcmp(ent_m, src_sha1) == 0: return 1 + i += 1 + return 0 + +def _vpi_escape_mark_module(src_sha1: str) -> int: + """记录"该模块的 vpi 已加载";1=成功 / 0=失败""" + global _g_vpi_esc_mods, _g_vpi_esc_mod_count + if src_sha1 is None: return 0 + if _g_vpi_esc_mods is None: + _g_vpi_esc_mods = stdlib.malloc(VPI_ESC_MOD_MAX * 17) + if _g_vpi_esc_mods is None: return 0 + string.memset(_g_vpi_esc_mods, 0, VPI_ESC_MOD_MAX * 17) + if _vpi_escape_module_loaded(src_sha1) != 0: return 1 + if _g_vpi_esc_mod_count >= VPI_ESC_MOD_MAX: return 0 + string.strcpy(_g_vpi_esc_mods + sizet(_g_vpi_esc_mod_count) * 17, src_sha1) + _g_vpi_esc_mod_count += 1 + return 1 + _g_vpi_param_keys: bytes = None _g_vpi_param_strs: bytes = None _g_vpi_param_count: int = 0 @@ -823,11 +865,128 @@ def _ensure_vpi_ret_annot_loaded(src_sha1: str, temp_dir: str) -> int: if bytes_read > 0: if bytes_read < VPI_READ_BUF_SIZE: vpi_buf[bytes_read] = 0 _cache_vpi_return_annots_text(vpi_buf, src_sha1) + # v3:同一次加载里把行尾逃逸摘要标记也收进缓存(两者共享"本模块是否已加载"的判定) + _cache_vpi_escape_annots_text(vpi_buf, src_sha1) stdlib.free(vpi_buf) stdlib.free(vpi_path) return 1 +# _cache_vpi_escape_annots_text - extract the trailing `# esc= ret=` markers. +# v3 跨模块逃逸摘要:不依赖 AST,按行扫描(与 _cache_vpi_return_annots_text 同构)。 +def _cache_vpi_escape_annots_text(vpi_buf: bytes, src_sha1: str) -> int: + """抽取 vpi 行尾逃逸摘要标记进缓存;1=处理完成 / 0=缓冲区或分配失败""" + global _g_vpi_esc_keys, _g_vpi_esc_vals, _g_vpi_esc_count + if vpi_buf is None or src_sha1 is None: return 0 + if _g_vpi_esc_keys is None: + _g_vpi_esc_keys = stdlib.malloc(VPI_ESC_MAX * VPI_ESC_KEY_LEN) + _g_vpi_esc_vals = sizet*(stdlib.malloc(VPI_ESC_MAX * 8)) + if _g_vpi_esc_keys is None or _g_vpi_esc_vals is None: return 0 + string.memset(_g_vpi_esc_keys, 0, VPI_ESC_MAX * VPI_ESC_KEY_LEN) + string.memset(_g_vpi_esc_vals, 0, VPI_ESC_MAX * 8) + sha1_len: sizet = string.strlen(src_sha1) + buf_len: sizet = string.strlen(vpi_buf) + cur_cls: bytes = None + pos: sizet = 0 + while pos < buf_len: + ls: sizet = pos + le: sizet = pos + while le < buf_len and vpi_buf[le] != '\n': le += 1 + i: sizet = ls + while i < le and vpi_buf[i] == ' ': i += 1 + if i < le: + # class X: -> 记录当前类上下文(与方法键一致) + if le - i >= 6 and string.strncmp(vpi_buf + i, "class ", 6) == 0: + cstart: sizet = i + 6 + cend: sizet = cstart + while cend < le and vpi_buf[cend] != ':' and vpi_buf[cend] != ' ' and vpi_buf[cend] != '(': cend += 1 + if cend > cstart: + if cur_cls is not None: stdlib.free(cur_cls) + cur_cls = stdlib.malloc(cend - cstart + 1) + if cur_cls is not None: + string.strncpy(cur_cls, vpi_buf + cstart, cend - cstart) + cur_cls[cend - cstart] = '\0' + # def name(...) ... # esc= ret= + elif le - i >= 4 and string.strncmp(vpi_buf + i, "def ", 4) == 0: + # 模块级函数(无缩进)清掉类上下文,避免上一类泄漏 + if i == ls and cur_cls is not None: + stdlib.free(cur_cls) + cur_cls = None + # 找同行 "# esc=" 标记 + mark: sizet = le + j: sizet = i + while j + 6 <= le: + if vpi_buf[j] == '#' and vpi_buf[j + 1] == ' ' and string.strncmp(vpi_buf + j + 2, "esc=", 4) == 0: + mark = j + break + j += 1 + if mark < le: + kp: sizet = mark + 6 + esc_v: int = 0 + while kp < le and vpi_buf[kp] >= '0' and vpi_buf[kp] <= '9': + esc_v = esc_v * 10 + (int(vpi_buf[kp]) - 48) + kp += 1 + ret_v: int = 0 + while kp < le and vpi_buf[kp] != 'e': kp += 1 + if kp + 4 <= le: + kp += 4 + while kp < le and vpi_buf[kp] >= '0' and vpi_buf[kp] <= '9': + ret_v = ret_v * 10 + (int(vpi_buf[kp]) - 48) + kp += 1 + if (esc_v != 0 or ret_v != 0) and _g_vpi_esc_count < VPI_ESC_MAX: + dstart: sizet = i + 4 + dend: sizet = dstart + while dend < le and vpi_buf[dend] != '(': dend += 1 + if dend > dstart: + mname_len: sizet = dend - dstart + cls_len_e: sizet = 0 + if cur_cls is not None: cls_len_e = string.strlen(cur_cls) + key_len_e: sizet = sha1_len + 1 + cls_len_e + 1 + mname_len + if key_len_e < VPI_ESC_KEY_LEN: + mname_buf_e: bytes = stdlib.malloc(mname_len + 1) + key_buf_e: bytes = stdlib.malloc(key_len_e + 1) + if mname_buf_e is not None and key_buf_e is not None: + string.strncpy(mname_buf_e, vpi_buf + dstart, mname_len) + mname_buf_e[mname_len] = '\0' + if cur_cls is not None: viperlib.snprintf(key_buf_e, key_len_e + 1, "%s.%s.%s", src_sha1, cur_cls, mname_buf_e) + else: viperlib.snprintf(key_buf_e, key_len_e + 1, "%s.%s", src_sha1, mname_buf_e) + string.strcpy(_g_vpi_esc_keys + sizet(_g_vpi_esc_count) * VPI_ESC_KEY_LEN, key_buf_e) + _g_vpi_esc_vals[_g_vpi_esc_count] = sizet(esc_v) | (sizet(ret_v) << 32) + _g_vpi_esc_count += 1 + if mname_buf_e is not None: stdlib.free(mname_buf_e) + if key_buf_e is not None: stdlib.free(key_buf_e) + pos = le + 1 + if cur_cls is not None: stdlib.free(cur_cls) + return 1 + + +# _lookup_vpi_escape_packed - query the cross-module escape summary cache (v3). +def _lookup_vpi_escape_packed(src_sha1: str, cls_name: str, method_name: str) -> int: + """查跨模块逃逸摘要;返回打包值(低32位 mask | ret<<32),0 = 无摘要""" + if _g_vpi_esc_keys is None or _g_vpi_esc_count == 0: return 0 + if src_sha1 is None or method_name is None: return 0 + sha1_len: sizet = string.strlen(src_sha1) + cl_len: sizet = 0 + if cls_name is not None: cl_len = string.strlen(cls_name) + me_len: sizet = string.strlen(method_name) + key_len: sizet = sha1_len + 1 + cl_len + 1 + me_len + kb: bytes = stdlib.malloc(key_len + 1) + if kb is None: return 0 + if cls_name is not None: viperlib.snprintf(kb, key_len + 1, "%s.%s.%s", src_sha1, cls_name, method_name) + else: viperlib.snprintf(kb, key_len + 1, "%s.%s", src_sha1, method_name) + res: int = 0 + idx: int = 0 + while idx < _g_vpi_esc_count: + ent: bytes = _g_vpi_esc_keys + sizet(idx) * VPI_ESC_KEY_LEN + # 键以模块 sha1 打头(前 2 个十六进制字符足以筛掉绝大多数候选,避免逐项 strcmp) + if ent[0] == kb[0] and ent[1] == kb[1] and string.strcmp(ent, kb) == 0: + res = int(_g_vpi_esc_vals[idx]) + break + idx += 1 + stdlib.free(kb) + return res + + # _vpi_param_annot_seg_is - equality check for a param-annotation segment (for the "str/bytes is not a generic pointer" rule). def _vpi_param_annot_seg_is(src_sha1: str, cls_name: str, method_name: str, seg_idx: int, want: str) -> int: """Check whether the definition-side param-annotation segment equals want exactly; 1=yes / 0=no.""" diff --git a/App/lib/core/Gen/AssignGenMixin.vp b/App/lib/core/Gen/AssignGenMixin.vp index f06eab9..9e18b03 100644 --- a/App/lib/core/Gen/AssignGenMixin.vp +++ b/App/lib/core/Gen/AssignGenMixin.vp @@ -1506,7 +1506,12 @@ novirtual class AssignGenMixin: # Duplicate-declaration check (fail fast): re-annotating a module-level name inside a function. # Legal alternatives: a global declaration plus unannotated assignment, or a differently named local. - if nm.id is not None and trans is not None and self.Gen.VarGen.is_module_scope(trans.SymTab) == 0 and self.Gen.VarGen.lookup_module_var(trans.SymTab, nm.id) is not None: + has_mod_decl_add: int = 0 + if nm.id is not None and trans is not None and self.Gen.VarGen.is_module_scope(trans.SymTab) == 0: + if self.Gen.VarGen.lookup_module_var(trans.SymTab, nm.id) is not None: has_mod_decl_add = 1 + # 双保险:本文件顶层变量名集合(SymTab.Root.Vars 可能在部分时序看不到) + if has_mod_decl_add == 0 and GenApi.is_file_var(nm.id) != 0: has_mod_decl_add = 1 + if has_mod_decl_add != 0: dup_decl: int = 0 if GenApi.is_global_name(trans, nm.id) != 0 or GenApi.is_nonlocal_name(trans, nm.id) != 0: dup_decl = 1 if dup_decl != 0: @@ -2320,7 +2325,13 @@ novirtual class AssignGenMixin: # Block assignment to a module-level global inside a function without a global declaration if self.Gen.VarGen.is_module_scope(trans.SymTab) == 0: mod_gv: mlirlite.Value* = self.Gen.VarGen.lookup_module_var(trans.SymTab, nm.id) - if mod_gv is not None: self.Gen.TypeGenErr.fatal_error(target, f"variable '{nm.id}' is a module-level global; assignment inside a function requires a global declaration (otherwise a local variable shadows the global, leaving it null and crashing)", "SyntaxError") + mod_decl_g: int = 0 + if mod_gv is not None: mod_decl_g = 1 + # 双保险:SymTab.Root.Vars 在部分时序/阶段可能看不到本文件已声明的模块全局 + # (现场:某函数对模块全局赋值漏写 global → 静默建局部影子 → 全局仍 NULL → + # 下游 ASan 写 0x208 崩)。按规则此处必须 fail-fast,不得静默建局部。 + if mod_decl_g == 0 and GenApi.is_file_var(nm.id) != 0: mod_decl_g = 1 + if mod_decl_g != 0: self.Gen.TypeGenErr.fatal_error(target, f"variable '{nm.id}' is a module-level global; assignment inside a function requires a global declaration (otherwise a local variable shadows the global, leaving it null and crashing)", "SyntaxError") # Ordinary local variable alloca: mlirlite.Value* = self.Gen.VarGen.get_or_create_sym(trans.SymTab, pool, builder, nm.id, rhs_val.Ty) diff --git a/App/lib/core/Gen/Call/CallGenBase.vp b/App/lib/core/Gen/Call/CallGenBase.vp index e340ff8..d1c35d7 100644 --- a/App/lib/core/Gen/Call/CallGenBase.vp +++ b/App/lib/core/Gen/Call/CallGenBase.vp @@ -794,6 +794,40 @@ novirtual class CallGenBase: if annot is None: return None return VpiReader._resolve_vpi_ret_annot_str(pool, annot, i32_ty, i8_ptr_ty) + def _vpi_escape_packed(self, sha1: str, cls_name: str, method_name: str) -> int: + """跨模块逃逸摘要查询(v3):按 (定义模块 sha1, Class?, method) 查 .vpi 缓存;0=无摘要。 + + 用"本模块已加载"备忘包住 _ensure_vpi_ret_annot_loaded(其内部是 O(N) 前缀判定): + 调用点每次都要查摘要,不做备忘会把编译拖慢一个量级。""" + if sha1 is None or method_name is None: return 0 + if VpiReader._vpi_escape_module_loaded(sha1) == 0: + if self.Gen.CallGenBase.TempDir is None or self.Gen.CallGenBase.TempDirLen == 0: return 0 + VpiReader._ensure_vpi_ret_annot_loaded(sha1, self.Gen.CallGenBase.TempDir) + VpiReader._vpi_escape_mark_module(sha1) + return VpiReader._lookup_vpi_escape_packed(sha1, cls_name, method_name) + + def lookup_escape_via_vpi(self, pool: memhub.MemBuddy*, cls_sha1: str, cls_name: str, method_name: str) -> int: + """跨模块逃逸摘要(v3):本类 + MRO 祖先各自的模块 sha1 依次查 .vpi,返回打包值(0=无摘要)。 + + 本文件的摘要表按文件重置,跨模块调用点上必为空,只能靠 .vpi 传递; + 键与方法路径一致:"Class.method"(方法)/ 裸名(模块级函数,此时 cls_name=None)。""" + if method_name is None: return 0 + if cls_sha1 is not None: + p0: int = self._vpi_escape_packed(cls_sha1, cls_name, method_name) + if p0 != 0: return p0 + if cls_name is not None: + mro: sizet* = self.Gen.StructGen.get_mro_chain(pool, cls_name) + mi: int = 1 + while mro is not None and mro[mi] != 0: + anc: str = rawptr(mro[mi]) + if anc is not None and string.strcmp(anc, cls_name) != 0: + sha_a: str = self.Gen.StructGen.get_struct_sha1(anc) + if sha_a is not None: + pa: int = self._vpi_escape_packed(sha_a, anc, method_name) + if pa != 0: return pa + mi += 1 + return 0 + # _vpi_method_param_annot: on-demand lookup of the vpi param annotation string def _vpi_method_param_annot(self, pool: memhub.MemBuddy*, cls_sha1: str, cls_name: str, method_name: str) -> str: """Load and query the vpi parameter annotation string on demand; None on miss""" diff --git a/App/lib/core/Gen/Call/CallGenCall.vp b/App/lib/core/Gen/Call/CallGenCall.vp index ff3294a..56d7b10 100644 --- a/App/lib/core/Gen/Call/CallGenCall.vp +++ b/App/lib/core/Gen/Call/CallGenCall.vp @@ -152,6 +152,11 @@ novirtual class CallGenCall(CallGenBase.CallGenBase): # `__new__` 不查:其形参与 __init__ 共享且可能被裁剪,位置对应不可靠。 if arg_nodes is not None and string.strcmp(method_name, "__init__") == 0: esc_mask_ci: int = GenApi.get_param_escapes_method(pool, class_name, method_name) + if esc_mask_ci == 0: + # 跨模块回退(v3):构造器常定义在别的模块(self.f = p 写在那边) + sha_ci: str = self.Gen.StructGen.get_struct_sha1(class_name) + pk_ci: int = self.Gen.CallGenBase.lookup_escape_via_vpi(pool, sha_ci, class_name, method_name) + if pk_ci != 0: esc_mask_ci = int(pk_ci & 4294967295) if esc_mask_ci != 0: dummy_ptr_ci: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool)) can_ci: sizet = len(arg_nodes) @@ -536,6 +541,11 @@ novirtual class CallGenCall(CallGenBase.CallGenBase): cargs_mc: list[ast.AST*]* = cl.args if cargs_mc is not None: esc_mask_mc: int = GenApi.get_param_escapes_method(pool, class_name, method_name) + if esc_mask_mc == 0: + # 跨模块回退(v3):本文件摘要表按文件重置,跨模块只能查 .vpi 落盘的摘要 + sha_mc: str = self.Gen.StructGen.get_struct_sha1(class_name) + pk_mc: int = self.Gen.CallGenBase.lookup_escape_via_vpi(pool, sha_mc, class_name, method_name) + if pk_mc != 0: esc_mask_mc = int(pk_mc & 4294967295) if esc_mask_mc != 0: dummy_ptr_mc: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool)) can_mc: sizet = len(cargs_mc) diff --git a/App/lib/core/Gen/Call/CallGenMixin.vp b/App/lib/core/Gen/Call/CallGenMixin.vp index 7e86229..1c0ab03 100644 --- a/App/lib/core/Gen/Call/CallGenMixin.vp +++ b/App/lib/core/Gen/Call/CallGenMixin.vp @@ -80,6 +80,27 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase): # 类型——只用于让 life_of_expr 判定"裸局部数组名处于指针位置 = 数组退让 = 栈地址"。 if cargs is not None and func_name is not None: esc_mask: int = GenApi.get_param_escapes(func_name) + if esc_mask == 0: + # 跨模块回退(v3):本文件摘要表按文件重置,跨模块的被调者只能靠 .vpi 传递。 + # 解析被调者所属模块 sha1:带模块前缀(mod.f)用限定名,否则用 from-import 别名。 + mod_sha_ec: str = None + if cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute: + at_ec: ast.Attribute* = ast.Attribute*(cl.func) + mod_p_ec: str = None + if at_ec.value is not None and at_ec.value.kind() == ast.ASTKind.Name: + mn_ec: ast.Name* = ast.Name*(at_ec.value) + if mn_ec.id is not None: mod_p_ec = mn_ec.id + elif at_ec.value is not None and at_ec.value.kind() == ast.ASTKind.Attribute: + mod_p_ec = self.Gen.ExprGenAttr._attr_module_path_str(pool, at_ec.value) + if mod_p_ec is not None: + fi_ec: str = None + if trans is not None: fi_ec = trans._from_imports + mod_sha_ec = self.Gen.StructGen._resolve_module_sha1(mod_p_ec, fi_ec) + if mod_sha_ec is None and trans is not None and trans._from_imports is not None: + mod_sha_ec = self._lookup_mod_sha1(pool, trans._from_imports, func_name, 0) + if mod_sha_ec is not None: + pk_ec: int = self.Gen.CallGenBase.lookup_escape_via_vpi(pool, mod_sha_ec, None, func_name) + if pk_ec != 0: esc_mask = int(pk_ec & 4294967295) if esc_mask != 0: _dummy_ptr: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool)) for ai in range(can): diff --git a/App/lib/core/Gen/GenApi.vp b/App/lib/core/Gen/GenApi.vp index 61aedcf..71a1626 100644 --- a/App/lib/core/Gen/GenApi.vp +++ b/App/lib/core/Gen/GenApi.vp @@ -285,6 +285,12 @@ def is_file_func(name: str) -> int: return 0 +def is_file_var(name: str) -> int: + """本文件是否顶层声明过该模块级变量名(AnnAssign/Assign 目标);1=是 / 0=否。""" + if _g_gen is not None: return _g_gen.StructGen.is_file_var(name) + return 0 + + # ---------------- 生命期摘要查询(M2.3 v1b, delegates to StructGenMixin) ---------------- def get_param_escapes(name: str) -> int: """形参逃逸掩码(第 k 位=第 k 个形参逃逸);无记录返回 0""" @@ -299,11 +305,17 @@ def get_ret_borrow(name: str) -> int: def get_param_escapes_method(pool: memhub.MemBuddy*, class_name: str, method_name: str) -> int: - """方法形参逃逸掩码:查 "Class.method" 并沿 MRO 回退,最后退裸名键;无记录返回 0""" + """方法形参逃逸掩码:查 "Class.method" 并沿 MRO 回退;无记录返回 0""" if _g_gen is not None: return _g_gen.StructGen.get_param_escapes_method(pool, class_name, method_name) return 0 +def compute_escape_summary(pool: memhub.MemBuddy*, fd: ast.AST*, gset: sizet*, box: sizet*) -> int: + """计算函数逃逸摘要(box[0]=形参逃逸掩码,box[1]=返回借用位);.vpi 生成时落盘用""" + if _g_gen is not None: return _g_gen.StructGen.compute_escape_summary(pool, ast.FunctionDef*(fd), gset, box) + return 1 + + # ---------------- find_struct_by_type (delegates to StructGenMixin.find_struct_by_type) ---------------- def find_struct_by_type(struct_ty: mlirlite.LLVMType*) -> GenTypes.StructEntry*: """Delegate to gen.find_struct_by_type""" diff --git a/App/lib/core/Gen/StructGenMixin.vp b/App/lib/core/Gen/StructGenMixin.vp index 00ba681..d0733d2 100644 --- a/App/lib/core/Gen/StructGenMixin.vp +++ b/App/lib/core/Gen/StructGenMixin.vp @@ -102,6 +102,10 @@ novirtual class StructGenMixin: # Top-level FunctionDef name table for this file (see register_file_funcs). # A stub-injected bare-name declaration (extern/state/export, no SHA1 prefix) that matches a local declaration would take the name first and make the local declaration silently dropped in forward_declare_functions; the injection side (VpiReader._inject_function_declare) skips these names. FileFuncNames: hashtable.HashTable* + # 本文件顶层**变量名**集合(AnnAssign/Assign 目标)。用于"函数内无 global 写同名模块变量" + # 判据的兜底:该判据原本只查 SymTab.Root.Vars,在部分时序/阶段会看不到已声明的模块全局, + # 于是赋值被静默当成本地影子(现场:漏写 global → 全局仍 NULL → 下游 ASan 写 0x208 崩)。 + FileVarNames: hashtable.HashTable* # --- Name | type index (fast lookup path; falls back to a linear scan when the index is untrusted) --- StructNameIdx: sizet* # name -> first registration index (u64 slot: 0 = empty, else index+1) @@ -133,6 +137,7 @@ novirtual class StructGenMixin: self.FileClassNames = None self.FileModuleSha1 = None self.FileFuncNames = None + self.FileVarNames = None self.EscTable = None self.EscCount = 0 self.EscScratch = None @@ -162,6 +167,7 @@ novirtual class StructGenMixin: self.FileClassNames = None self.FileModuleSha1 = None self.FileFuncNames = None + self.FileVarNames = None self.ModuleClasses = hashtable.HashTable(pool) if strict != 0: # Strict mode: create a new HashTable recording visible structs. @@ -301,6 +307,36 @@ novirtual class StructGenMixin: if child2.kind() != ast.ASTKind.ClassDef: continue self._RegisterClassMethods(esc_pool, ast.ClassDef*(child2)) + # 本文件顶层变量名集合(供"函数内无 global 写同名模块变量"判据兜底)。 + for ci3 in range(cn): + child3: ast.AST* = ch.get(ci3) + if child3 is None: continue + kc3: int = child3.kind() + tlist3: list[ast.AST*]* = None + if kc3 == ast.ASTKind.AnnAssign: + aa3: ast.AnnAssign* = ast.AnnAssign*(child3) + if aa3 is not None and aa3.target is not None: + tlist3 = list[ast.AST*](pool, 1) + if tlist3 is not None: tlist3.append(aa3.target) + elif kc3 == ast.ASTKind.Assign: + asg3: ast.Assign* = ast.Assign*(child3) + if asg3 is not None: tlist3 = asg3.targets + if tlist3 is None: continue + if self.FileVarNames is None: self.FileVarNames = hashtable.HashTable(pool) + if self.FileVarNames is None: continue + nt3: sizet = len(tlist3) + for ti3 in range(nt3): + tn3: ast.AST* = tlist3.get(ti3) + if tn3 is None or tn3.kind() != ast.ASTKind.Name: continue + vnm3: ast.Name* = ast.Name*(tn3) + if vnm3.id is None: continue + if self.FileVarNames.__contains__(vnm3.id) != 0: continue + nb3_len: sizet = string.strlen(vnm3.id) + nb3: str = pool.alloc(nb3_len + 1) + if nb3 is None: continue + string.strcpy(nb3, vnm3.id) + self.FileVarNames.set_int(nb3, 1) + # v2 传递闭包:反复重扫直到掩码不再变化(单调 OR-in 必收敛),上限 8 轮兜底。 # 原来固定 2 轮是近似,>2 层的链会漏;这里改为扫到不动点。 prev_sum: sizet = 0 @@ -317,6 +353,13 @@ novirtual class StructGenMixin: if name is None or self.FileFuncNames is None: return 0 return self.FileFuncNames.__contains__(name) + def is_file_var(self, name: str) -> int: + """本文件是否顶层声明过该模块级变量名(AnnAssign/Assign 目标);1=是 / 0=否。 + + 与 SymTab.Root.Vars 双保险:后者在部分时序/阶段会看不到已声明的模块全局。""" + if name is None or self.FileVarNames is None: return 0 + return self.FileVarNames.__contains__(name) + # ============================================================ # 生命期摘要(M2.3 v1b):本文件函数 → 形参逃逸掩码 / 返回借用标记 # 覆盖:`return p`、`global G` 下的 `G = p`、`obj.f = p`(含 AnnAssign); @@ -361,20 +404,35 @@ novirtual class StructGenMixin: if idx < 0: return 0 return int(self.EscTable[idx * 3 + 2]) + def compute_escape_summary(self, pool: memhub.MemBuddy*, fd: ast.FunctionDef*, gset: sizet*, box: sizet*) -> int: + """计算 fd 的摘要:box[0]=形参逃逸掩码、box[1]=返回借用位(写前清零)。 + + gset = global 名定长数组([0]=计数,[1..]=名哈希),box 必须在 gset 之后同一块内 + (调用方给 gset+65 槽)。gset 传 None 时用本文件的 EscScratch(复用区),两者都没有 + 才从 pool 现分配。两条路径共用同一实现:① 本文件登记(register_file_funcs); + ② .vpi 生成时把摘要落盘(v3 跨模块)。""" + if fd is None or box is None: return 1 + box[0] = 0 + box[1] = 0 + g: sizet* = gset + if g is None: g = self.EscScratch + if g is None: + if pool is None: return 1 + g = sizet*(pool.alloc(74 * 8)) + if g is None: return 1 + g[0] = 0 + self._CollectGlobalNames(fd.children, g, 0) + self._ScanEscapes(fd, fd.children, g, box, 0) + return 0 + def _RegisterFuncEscapeSummary(self, pool: memhub.MemBuddy*, fd: ast.FunctionDef*, key: str) -> int: """扫函数体登记摘要;key 必须是与 EscTable 同池的字符串""" if fd is None or key is None or self.EscTable is None or self.EscScratch is None: return 1 if self._EscLookup(key) >= 0: return 0 if self.EscCount >= 1024: return 1 - # gset 现在是**定长数组**(复用区;[0]=计数,[1..]=名字指针),不再是 HashTable; - # box 也在同一块复用区里(偏移 65 槽 = 520 字节)。 - gset: sizet* = self.EscScratch - gset[0] = 0 - self._CollectGlobalNames(fd.children, gset, 0) + # gset/box 复用 EscScratch([0]=global 计数,[1..]=名哈希;box 在偏移 65 槽 = 520 字节) box: sizet* = sizet*(uint64(self.EscScratch) + 520) - box[0] = 0 - box[1] = 0 - self._ScanEscapes(fd, fd.children, gset, box, 0) + if self.compute_escape_summary(pool, fd, None, box) != 0: return 1 slot: int = self.EscCount self.EscTable[slot * 3 + 0] = sizet(self._NameHash(key)) self.EscTable[slot * 3 + 1] = box[0]