feat(gen): M2.3 v3 跨模块逃逸摘要 + 全局赋值判据双保险
【v3 跨模块摘要】Converter._PyiFunction 在每个 def 签名行尾写 `# esc=<mask> ret=<ret>`(仅非零时写);VpiReader 新增逃逸摘要缓存(键 {sha1}.{Class}.{method} / {sha1}.{func},值打包 mask|ret<<32),与返回注解缓存同一次加载解析,并加"模块已加载"备忘 + 键前缀过滤避免拖慢编译;StructGenMixin 抽出 compute_escape_summary 供登记与落盘共用;CallGenBase.lookup_escape_via_vpi 按本类 + MRO 祖先模块 sha1 查询,方法/构造器/模块级函数三处调用点在本地表未命中时回退查 vpi。
【修复】Converter._PyiEnsureEscScratch 补 global(漏写导致全局仍 NULL,box 算成 0+520 → ASan WRITE on 0x208);函数内写"本文件已声明的模块全局"且无 global/nonlocal 时必须 fail-fast——原判据只看 SymTab.Root.Vars,部分时序看不到模块全局而静默建局部影子;新增 FileVarNames(register_file_funcs 预扫收集顶层变量名)+ is_file_var() 兜底,普通赋值与注解式重声明两处都接上。
This commit is contained in:
1 parent
9f98e683a9
commit
cd68bb62ac
8 files changed
+347
-12
No files matched your search
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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=<mask> ret=<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=<mask> ret=<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=<mask> ret=<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."""
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"""
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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"""
|
||||
|
||||
@@ -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]
|
||||
|
||||
Reference in new issue
Block a user