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."""
|
||||
|
||||
Reference in new issue
Block a user