fix(gen): 类归属不采信星号导入候选,收口 fatal_error 返回类型与默认值补全
TypeGenResolve._class_true_def_sha1 / CallGenBase._lookup_reexport_from_vpi 新增 allow_star 参数:类归属只采信具名 re-export。此前 `from X import *` 候选被当成类的真实定义模块,而几乎每个文件都 star-import stdint,导致 TypeGenErr 前向声明被建成 "stdint.TypeGenErr";该名字在 StructTable 里只剩一个错误 sha1 的条目,fatal_error 的跨模块返回类型解析全部落空并 fail-fast。 TypeGenErr.fatal_error/fatal_type_error 显式 -> int(留空会解析出退化类型 i0,命中 CallGenCall 的 i0 守卫);_fail_method_ret_ty 增加失败上下文转储并并入同一条 VLogger.error(批处理下 error 即 sys.exit,其后代码全是死代码)。 CallGenMixin 模块级调用:默认值已知但重建不出时 fail-fast,不再静默产出操作数不足的 llvm.call(只会被 MLIR 校验拦下);AssignGenMixin 两处 VLogger.error 补上 category 实参。 M2.3 v1b 收尾:逃逸摘要表与扫描临时区改为只分配一次、传递闭包扫到不动点、u64 改 sizet;VpiReader 不再 free 跨阶段引用的 src_sha1(heap-use-after-free)。
This commit is contained in:
1 parent
1fb0f4a4ea
commit
b03e55bb7e
9 files changed
+183
-49
No files matched your search
@@ -1920,8 +1920,11 @@ def _ParseVpiIntoModuleImpl(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, v
|
|||||||
|
|
||||||
# Import/ImportFrom -> skip (nothing to process)
|
# Import/ImportFrom -> skip (nothing to process)
|
||||||
|
|
||||||
# free the SHA1 buffer (allocated by _extract_sha1_from_path via stdlib.malloc)
|
# 不要 free src_sha1:它会被后续阶段继续引用(模块/类型注册按 `<sha1>.Name` 命名,
|
||||||
if src_sha1 is not None: stdlib.free(src_sha1)
|
# 之后 resolve_annotation_type 仍会对它 strcmp)——在此 free 会造成 heap-use-after-free
|
||||||
|
# (现场:alloc @ _extract_sha1_from_path → free @ 本函数 → 读 @ resolve_annotation_type:2065)。
|
||||||
|
# 跨阶段字符串按本仓惯例常驻;若将来确需回收,应改为在**存入注册表处**拷贝一份到 PermPool,
|
||||||
|
# 而不是在解析结束时释放。
|
||||||
# free vpi_buf only after all tree processing (see the parsing comment)
|
# free vpi_buf only after all tree processing (see the parsing comment)
|
||||||
stdlib.free(vpi_buf)
|
stdlib.free(vpi_buf)
|
||||||
return 1
|
return 1
|
||||||
|
|||||||
@@ -2011,8 +2011,10 @@ novirtual class AssignGenMixin:
|
|||||||
if rel_path is not None: VLogger.error(f"assignment rhs is None: {rel_path}:{val_line} call '{func_nm}(...)' returned None", "ASGN")
|
if rel_path is not None: VLogger.error(f"assignment rhs is None: {rel_path}:{val_line} call '{func_nm}(...)' returned None", "ASGN")
|
||||||
else: VLogger.error(f"assignment rhs is None: sha1={sha1}:{val_line} call '{func_nm}(...)' returned None", "ASGN")
|
else: VLogger.error(f"assignment rhs is None: sha1={sha1}:{val_line} call '{func_nm}(...)' returned None", "ASGN")
|
||||||
else:
|
else:
|
||||||
if rel_path is not None: VLogger.error(f"assignment rhs is None: {rel_path}:{val_line} [kind={val_kind}]")
|
# 必须与相邻分支一致显式传 category:跨模块模块级函数调用目前不会补默认值,
|
||||||
else: VLogger.error(f"assignment rhs is None: sha1={sha1}:{val_line} [kind={val_kind}]")
|
# 漏传会生成操作数不足的 llvm.call(MLIR 校验直接失败)。
|
||||||
|
if rel_path is not None: VLogger.error(f"assignment rhs is None: {rel_path}:{val_line} [kind={val_kind}]", "ASGN")
|
||||||
|
else: VLogger.error(f"assignment rhs is None: sha1={sha1}:{val_line} [kind={val_kind}]", "ASGN")
|
||||||
else: VLogger.error("assignment rhs is None (asgn.value is empty)", "ASGN")
|
else: VLogger.error("assignment rhs is None (asgn.value is empty)", "ASGN")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|||||||
@@ -294,8 +294,12 @@ novirtual class CallGenBase:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _lookup_reexport_from_vpi(self, pool: memhub.MemBuddy*, mod_sha1: str, func_name: str) -> str:
|
def _lookup_reexport_from_vpi(self, pool: memhub.MemBuddy*, mod_sha1: str, func_name: str, allow_star: int = 1) -> str:
|
||||||
"""On a re-export table miss, parse the module's .vpi from-import statements to locate the defining module (the table may be empty since it is only populated during full translation)."""
|
"""On a re-export table miss, parse the module's .vpi from-import statements to locate the defining module (the table may be empty since it is only populated during full translation).
|
||||||
|
allow_star=0 skips `from X import *` candidates: they are unverified hints (the caller must
|
||||||
|
check them), and a CLASS owner must never be taken from one -- nearly every file star-imports
|
||||||
|
stdint, so accepting one would attribute arbitrary class names to stdint (TypeGenErr was
|
||||||
|
forward-declared as "stdint.TypeGenErr" this way, then every vpi/text.ll lookup missed)."""
|
||||||
if pool is None or mod_sha1 is None or func_name is None: return None
|
if pool is None or mod_sha1 is None or func_name is None: return None
|
||||||
# accumulate star-import candidates (space-separated); caller validates each
|
# accumulate star-import candidates (space-separated); caller validates each
|
||||||
star_cands: str = None
|
star_cands: str = None
|
||||||
@@ -362,6 +366,8 @@ novirtual class CallGenBase:
|
|||||||
nm_start: sizet = imp_pos + 8
|
nm_start: sizet = imp_pos + 8
|
||||||
# star import (from X import *): collect all candidates; caller verifies
|
# star import (from X import *): collect all candidates; caller verifies
|
||||||
if nm_start < ls + ll and buf[nm_start] == '*':
|
if nm_start < ls + ll and buf[nm_start] == '*':
|
||||||
|
# Class-owner queries opt out: a star target is not evidence that it owns the name.
|
||||||
|
if allow_star == 0: continue
|
||||||
star_dot: int = 0
|
star_dot: int = 0
|
||||||
if buf[mod_start] == '.': star_dot = 1
|
if buf[mod_start] == '.': star_dot = 1
|
||||||
star_buf: str = pool.alloc(mod_end - mod_start + 1)
|
star_buf: str = pool.alloc(mod_end - mod_start + 1)
|
||||||
@@ -821,9 +827,66 @@ novirtual class CallGenBase:
|
|||||||
# _fail_method_ret_ty: ERROR + abort when the method return type cannot be resolved
|
# _fail_method_ret_ty: ERROR + abort when the method return type cannot be resolved
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
def _fail_method_ret_ty(self, method_name: str, node: ast.AST* = None) -> mlirlite.LLVMType*:
|
def _fail_method_ret_ty(self, method_name: str, node: ast.AST* = None, cls: str = None, anc: str = None, sha1: str = None) -> mlirlite.LLVMType*:
|
||||||
"""Fail fast when a method return type cannot be resolved precisely (module function, text.ll define, then vpi cache); never default to i32."""
|
"""Fail fast when a method return type cannot be resolved precisely (module function, text.ll define, then vpi cache); never default to i32."""
|
||||||
VLogger.error(f"method return type cannot be obtained precisely (function/text.ll/vpi stub all missed): {method_name}", "HandlesExprCall")
|
# NOTE: VLogger.error terminates the process in batch mode (sys.exit(1)), so the whole
|
||||||
|
# report must be a SINGLE call -- anything after it is dead code (this is why a separate
|
||||||
|
# diagnostic line never showed up, and why the following fatal_error traceback is never
|
||||||
|
# printed either). The context dump is therefore merged into this one message.
|
||||||
|
# Reports: receiver class, its registered struct sha1, the class/sha1 actually used for
|
||||||
|
# the lookup (MRO fallback candidate), whether that module's text.ll exists, and the raw
|
||||||
|
# vpi return-annotation string if the cache holds one.
|
||||||
|
c_cls: str = cls
|
||||||
|
if c_cls is None: c_cls = "?"
|
||||||
|
c_anc: str = anc
|
||||||
|
if c_anc is None: c_anc = "?"
|
||||||
|
# Probe with the sha1 actually used for the lookup, falling back to the receiver class's
|
||||||
|
# own registered sha1 when the caller passed none (so the dump is informative everywhere).
|
||||||
|
probe_sha: str = sha1
|
||||||
|
if probe_sha is None and anc is not None: probe_sha = self.Gen.StructGen.get_struct_sha1(anc)
|
||||||
|
if probe_sha is None and cls is not None: probe_sha = self.Gen.StructGen.get_struct_sha1(cls)
|
||||||
|
c_sha: str = probe_sha
|
||||||
|
if c_sha is None: c_sha = "?"
|
||||||
|
c_st_sha: str = "?"
|
||||||
|
if cls is not None:
|
||||||
|
st_sha_d: str = self.Gen.StructGen.get_struct_sha1(cls)
|
||||||
|
if st_sha_d is not None: c_st_sha = st_sha_d
|
||||||
|
c_tll: int = 0
|
||||||
|
c_vpi: str = "-"
|
||||||
|
if probe_sha is not None:
|
||||||
|
c_tll = self._dep_text_ll_exists(None, probe_sha)
|
||||||
|
if self.Gen.CallGenBase.TempDir is not None and self.Gen.CallGenBase.TempDirLen > 0:
|
||||||
|
VpiReader._ensure_vpi_ret_annot_loaded(probe_sha, self.Gen.CallGenBase.TempDir)
|
||||||
|
annot_d: str = VpiReader._lookup_vpi_ret_annot(probe_sha, cls, method_name)
|
||||||
|
if annot_d is not None: c_vpi = annot_d
|
||||||
|
# Same-named struct entries: find_struct returns the FIRST exact match, so an earlier bogus
|
||||||
|
# registration (alias/forward/stale, different ModuleSha1) silently shadows the real one.
|
||||||
|
dup_n_d: int = 0
|
||||||
|
dup_last_d: str = "-"
|
||||||
|
# The entry's LLVM type name embeds "{sha1}.{Class}", i.e. the sha1 used when the entry
|
||||||
|
# was first created (registration / forward-declare / stub injection) -- it reveals
|
||||||
|
# provenance even if a later path overwrote ModuleSha1.
|
||||||
|
dup_ty_nm_d: str = "-"
|
||||||
|
sc_n_d: int = self.Gen.StructGen.get_struct_count()
|
||||||
|
sc_i_d: int = 0
|
||||||
|
while sc_i_d < sc_n_d:
|
||||||
|
e_d: GenTypes.StructEntry* = self.Gen.StructGen._get_struct_entry(sc_i_d)
|
||||||
|
if e_d is not None and e_d.Name is not None:
|
||||||
|
if string.strcmp(e_d.Name, c_cls) == 0:
|
||||||
|
dup_n_d += 1
|
||||||
|
if e_d.ModuleSha1 is not None: dup_last_d = e_d.ModuleSha1
|
||||||
|
if e_d.Ty is not None:
|
||||||
|
nm_d: str = mlirlite.get_struct_name(e_d.Ty)
|
||||||
|
if nm_d is not None: dup_ty_nm_d = nm_d
|
||||||
|
sc_i_d += 1
|
||||||
|
# Current-file / defining-module sha1 at the moment of failure: distinguishes
|
||||||
|
# "wrong sha1 captured during (vpi injection) forward-declare" from "correct sha1
|
||||||
|
# overwritten later".
|
||||||
|
cur_file_d: str = self.Gen.StructGen.get_file_module_sha1()
|
||||||
|
if cur_file_d is None: cur_file_d = "-"
|
||||||
|
cur_def_d: str = self.Gen.StructGen.CurDefModuleSha1
|
||||||
|
if cur_def_d is None: cur_def_d = "-"
|
||||||
|
VLogger.error(f"method return type cannot be obtained precisely (function/text.ll/vpi stub all missed): {method_name} | [diag] cls={c_cls} struct_sha1={c_st_sha} lookup_anc={c_anc} lookup_sha1={c_sha} text_ll={c_tll} vpi_annot={c_vpi} same_name_entries={dup_n_d} last_same_sha1={dup_last_d} entry_ty={dup_ty_nm_d} cur_file_sha1={cur_file_d} cur_def_sha1={cur_def_d}", "HandlesExprCall")
|
||||||
self.Gen.TypeGenErr.fatal_error(node, f"method return type cannot be obtained precisely; defaulting to i32 would truncate pointers/i64: {method_name}", "TypeError")
|
self.Gen.TypeGenErr.fatal_error(node, f"method return type cannot be obtained precisely; defaulting to i32 would truncate pointers/i64: {method_name}", "TypeError")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|||||||
@@ -489,7 +489,7 @@ novirtual class CallGenCall(CallGenBase.CallGenBase):
|
|||||||
if bits == 0: cd_i0 = 1
|
if bits == 0: cd_i0 = 1
|
||||||
case _:
|
case _:
|
||||||
pass
|
pass
|
||||||
if cd_i0 != 0: call_ret_ty = self._fail_method_ret_ty(method_name, node)
|
if cd_i0 != 0: call_ret_ty = self._fail_method_ret_ty(method_name, node, class_name)
|
||||||
# Cross-module call (found_func is None): create a declare to avoid undefined value.
|
# Cross-module call (found_func is None): create a declare to avoid undefined value.
|
||||||
if found_func is None:
|
if found_func is None:
|
||||||
# Coerce args to the definition-side params before building the declare;
|
# Coerce args to the definition-side params before building the declare;
|
||||||
@@ -836,7 +836,7 @@ novirtual class CallGenCall(CallGenBase.CallGenBase):
|
|||||||
# Dependency not ready: keep default i32 (build_call(Void)
|
# Dependency not ready: keep default i32 (build_call(Void)
|
||||||
# returns None and breaks expression translation).
|
# returns None and breaks expression translation).
|
||||||
call_ret_ty = mlirlite.Int32(pool)
|
call_ret_ty = mlirlite.Int32(pool)
|
||||||
else: call_ret_ty = self._fail_method_ret_ty(method_name, cl)
|
else: call_ret_ty = self._fail_method_ret_ty(method_name, cl, class_name, ret_anc_name, ret_anc_sha1)
|
||||||
|
|
||||||
# found_func missed but the return type resolved: build the cross-module
|
# found_func missed but the return type resolved: build the cross-module
|
||||||
# call name from the defining class to avoid undefined references.
|
# call name from the defining class to avoid undefined references.
|
||||||
@@ -1559,7 +1559,7 @@ novirtual class CallGenCall(CallGenBase.CallGenBase):
|
|||||||
# Phase A not ready (anc_sha1_sp None / defining module text.ll
|
# Phase A not ready (anc_sha1_sp None / defining module text.ll
|
||||||
# missing): keep default i32 (build_call(Void) breaks expressions).
|
# missing): keep default i32 (build_call(Void) breaks expressions).
|
||||||
if anc_sha1_sp is None or self._dep_text_ll_exists(pool, anc_sha1_sp) == 0: ret_ty_sp = mlirlite.Int32(pool)
|
if anc_sha1_sp is None or self._dep_text_ll_exists(pool, anc_sha1_sp) == 0: ret_ty_sp = mlirlite.Int32(pool)
|
||||||
else: self._fail_method_ret_ty(method_name, cl)
|
else: self._fail_method_ret_ty(method_name, cl, cur_class, anc, anc_sha1_sp)
|
||||||
# 7. Cross-module: create the declare; emit the call.
|
# 7. Cross-module: create the declare; emit the call.
|
||||||
# Value-reference (&T / &temp T) params: auto-address the parent-method args (self is slot 0).
|
# Value-reference (&T / &temp T) params: auto-address the parent-method args (self is slot 0).
|
||||||
head = self._apply_auto_addr_to_call_args(builder, pool, mod, trans, call_name, cargs_sp, head, arg_count_sp, 1)
|
head = self._apply_auto_addr_to_call_args(builder, pool, mod, trans, call_name, cargs_sp, head, arg_count_sp, 1)
|
||||||
|
|||||||
@@ -940,6 +940,12 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase):
|
|||||||
# Appended default args must increment can: the downstream coerce/declare steps rebuild
|
# Appended default args must increment can: the downstream coerce/declare steps rebuild
|
||||||
# the chain bounded by can, so not counting them truncates the trailing defaults (json.write OOM root cause).
|
# the chain bounded by can, so not counting them truncates the trailing defaults (json.write OOM root cause).
|
||||||
can += 1
|
can += 1
|
||||||
|
# fail-fast: this callee is known to have defaults, but they could not be rebuilt into
|
||||||
|
# values (snapshot missing / not a simple constant). Emitting fewer operands than the
|
||||||
|
# callee declares yields invalid IR that only the MLIR verifier catches
|
||||||
|
# ("'llvm.call' op incorrect number of operands (N) for callee"); stop here instead.
|
||||||
|
if can < df_param_count:
|
||||||
|
self.Gen.TypeGenErr.fatal_error(cl, f"Cross-module default argument cannot be rebuilt into a value (snapshot missing, or the default is not a simple constant): {func_name}; parameter {can + 1} would be missing, which emits an llvm.call with fewer operands than the callee declares", "TypeError")
|
||||||
elif found_func is not None:
|
elif found_func is not None:
|
||||||
# Zero-value fallback when defaults are missing: use found_func's real param count (excluding EH out-params %__eh_msg_out__/%__eh_code_out__) and fill missing args with zeros.
|
# Zero-value fallback when defaults are missing: use found_func's real param count (excluding EH out-params %__eh_msg_out__/%__eh_code_out__) and fill missing args with zeros.
|
||||||
fc_cnt: int = 0
|
fc_cnt: int = 0
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ novirtual class CallGenProp(CallGenBase.CallGenBase):
|
|||||||
# Phase A deps not ready (cls_sha1_rt None / the defining module's text.ll missing):
|
# Phase A deps not ready (cls_sha1_rt None / the defining module's text.ll missing):
|
||||||
# keep the i32 default (build_call(Void) returning None would break expression translation).
|
# keep the i32 default (build_call(Void) returning None would break expression translation).
|
||||||
if cls_sha1_rt is None or self._dep_text_ll_exists(pool, cls_sha1_rt) == 0: call_ret_ty = mlirlite.Int32(pool)
|
if cls_sha1_rt is None or self._dep_text_ll_exists(pool, cls_sha1_rt) == 0: call_ret_ty = mlirlite.Int32(pool)
|
||||||
else: call_ret_ty = self._fail_method_ret_ty(method_name)
|
else: call_ret_ty = self._fail_method_ret_ty(method_name, None, class_name, None, cls_sha1_rt)
|
||||||
|
|
||||||
# Value-reference (&T / &temp T) params: auto-address the user args before param-type
|
# Value-reference (&T / &temp T) params: auto-address the user args before param-type
|
||||||
# coercion (self/obj or the classmethod cls occupies index 0, skipped via start_idx).
|
# coercion (self/obj or the classmethod cls occupies index 0, skipped via start_idx).
|
||||||
|
|||||||
@@ -114,7 +114,10 @@ novirtual class StructGenMixin:
|
|||||||
# VarEntry/FieldEntry 的 "Appended at the end so existing field offsets are unchanged")。
|
# VarEntry/FieldEntry 的 "Appended at the end so existing field offsets are unchanged")。
|
||||||
# 池数组 + 线性查找;每项 3 槽:name(池上 str 地址) | mask(第 k 位=第 k 形参逃逸) | ret_borrow
|
# 池数组 + 线性查找;每项 3 槽:name(池上 str 地址) | mask(第 k 位=第 k 形参逃逸) | ret_borrow
|
||||||
EscTable: sizet*
|
EscTable: sizet*
|
||||||
EscCount: int
|
EscCount: int
|
||||||
|
# 扫描临时区(也只分配一次、每函数复用),消除每函数每轮的 gset/box 分配:
|
||||||
|
# [0]=global 名计数 | [1..64]=global 名指针 | [65],[66]=box(mask, ret_borrow)
|
||||||
|
EscScratch: sizet*
|
||||||
|
|
||||||
def InitStructGen(self) -> int:
|
def InitStructGen(self) -> int:
|
||||||
"""Initialize struct-mixin fields; called inline from subclass constructors."""
|
"""Initialize struct-mixin fields; called inline from subclass constructors."""
|
||||||
@@ -132,6 +135,7 @@ novirtual class StructGenMixin:
|
|||||||
self.FileFuncNames = None
|
self.FileFuncNames = None
|
||||||
self.EscTable = None
|
self.EscTable = None
|
||||||
self.EscCount = 0
|
self.EscCount = 0
|
||||||
|
self.EscScratch = None
|
||||||
self.StructNameIdx = None
|
self.StructNameIdx = None
|
||||||
self.StructTypeIdx = None
|
self.StructTypeIdx = None
|
||||||
self.StructIdxCap = 0
|
self.StructIdxCap = 0
|
||||||
@@ -253,16 +257,20 @@ novirtual class StructGenMixin:
|
|||||||
if ch is None: return
|
if ch is None: return
|
||||||
if self.FileFuncNames is None: self.FileFuncNames = hashtable.HashTable(pool)
|
if self.FileFuncNames is None: self.FileFuncNames = hashtable.HashTable(pool)
|
||||||
if self.FileFuncNames is None: return
|
if self.FileFuncNames is None: return
|
||||||
# 生命期摘要表:每次进入本文件时重置(v1b 只覆盖同文件;跨文件属 v3)
|
# 生命期摘要表:**只分配一次**(PermPool,对照 init_struct_table 的长生命周期表口径),
|
||||||
# 两个要点(对照 init_struct_table):
|
# 此后每文件仅重置 EscCount。表内只存 u64 名哈希,且读只发生在 [0, EscCount) 区间;
|
||||||
# 1) 必须从 **PermPool** 分配——临时池在 Phase1 会被 reset_to(mark) 回收,同块内存
|
# 重置后该区间必然"先写后读",故无需每文件 memset —— 既避免每文件 24KB 泄漏,
|
||||||
# 被复用后我们会握着已变脏的旧指针(ASan 抓不到,表现为槽里读出垃圾 → strcmp 崩)。
|
# 又保留"槽内不含指针、不可能崩在 strcmp"的保证。
|
||||||
# 2) 必须显式清零:未写入的槽若不是 0,_EscLookup 的 strcmp 会对脏指针解引用。
|
|
||||||
esc_cap: sizet = 1024
|
esc_cap: sizet = 1024
|
||||||
esc_pool: memhub.MemBuddy* = CompilerFactory.factory.PermPool
|
esc_pool: memhub.MemBuddy* = CompilerFactory.factory.PermPool
|
||||||
if esc_pool is None: esc_pool = pool
|
if esc_pool is None: esc_pool = pool
|
||||||
self.EscTable = sizet*(esc_pool.alloc(esc_cap * 24))
|
if self.EscTable is None:
|
||||||
if self.EscTable is not None: string.memset(self.EscTable, 0, esc_cap * 24)
|
self.EscTable = sizet*(esc_pool.alloc(esc_cap * 24))
|
||||||
|
if self.EscTable is not None: string.memset(self.EscTable, 0, esc_cap * 24)
|
||||||
|
# 扫描临时区同样只分配一次:每函数复用前只把 global 计数清 0(读只到 [0, count))
|
||||||
|
if self.EscScratch is None:
|
||||||
|
self.EscScratch = sizet*(esc_pool.alloc(72 * 8))
|
||||||
|
if self.EscScratch is not None: string.memset(self.EscScratch, 0, 72 * 8)
|
||||||
self.EscCount = 0
|
self.EscCount = 0
|
||||||
cn: sizet = len(ch)
|
cn: sizet = len(ch)
|
||||||
ci: sizet
|
ci: sizet
|
||||||
@@ -283,9 +291,16 @@ novirtual class StructGenMixin:
|
|||||||
# 从根上消除"槽里是坏指针 → strcmp 崩在 0xffff…"这一整类问题。
|
# 从根上消除"槽里是坏指针 → strcmp 崩在 0xffff…"这一整类问题。
|
||||||
self._RegisterFuncEscapeSummary(esc_pool, fd, fd.name)
|
self._RegisterFuncEscapeSummary(esc_pool, fd, fd.name)
|
||||||
|
|
||||||
# v2 传递闭包:再迭代两次,让 `f` 经调用点继承 `g` 的逃逸(单调 OR-in,稳定即收敛)
|
# v2 传递闭包:反复重扫直到掩码不再变化(单调 OR-in 必收敛),上限 8 轮兜底。
|
||||||
for pi2 in range(2):
|
# 原来固定 2 轮是近似,>2 层的链会漏;这里改为扫到不动点。
|
||||||
|
prev_sum: sizet = 0
|
||||||
|
pass_i: int = 0
|
||||||
|
while pass_i < 8:
|
||||||
self._RescanEscapes(esc_pool, tree)
|
self._RescanEscapes(esc_pool, tree)
|
||||||
|
cur_sum: sizet = self._EscMaskSum()
|
||||||
|
if cur_sum == prev_sum: break
|
||||||
|
prev_sum = cur_sum
|
||||||
|
pass_i += 1
|
||||||
|
|
||||||
def is_file_func(self, name: str) -> int:
|
def is_file_func(self, name: str) -> int:
|
||||||
"""Return 1 when the bare name is a top-level function declared in this file."""
|
"""Return 1 when the bare name is a top-level function declared in this file."""
|
||||||
@@ -302,23 +317,25 @@ novirtual class StructGenMixin:
|
|||||||
def _EscLookup(self, name: str) -> int:
|
def _EscLookup(self, name: str) -> int:
|
||||||
"""线性查找摘要项下标;未找到返回 -1"""
|
"""线性查找摘要项下标;未找到返回 -1"""
|
||||||
if name is None or self.EscTable is None: return -1
|
if name is None or self.EscTable is None: return -1
|
||||||
h: u64 = self._NameHash(name)
|
h: sizet = self._NameHash(name)
|
||||||
cap: int = 1024
|
cap: int = 1024
|
||||||
i: int = 0
|
i: int = 0
|
||||||
while i < self.EscCount and i < cap:
|
while i < self.EscCount and i < cap:
|
||||||
# 槽 0 是名哈希(u64);未写入的槽为 0,与任何非零哈希都不相等,无需判空
|
# 槽 0 是名哈希(u64/sizet);未写入的槽为 0,与任何非零哈希都不相等,无需判空
|
||||||
if u64(self.EscTable[i * 3 + 0]) == h: return i
|
if self.EscTable[i * 3 + 0] == h: return i
|
||||||
i += 1
|
i += 1
|
||||||
return -1
|
return -1
|
||||||
|
|
||||||
def _NameHash(self, name: str) -> u64:
|
def _NameHash(self, name: str) -> sizet:
|
||||||
"""djb2-64(小立即数,溢出按 u64 回绕):把函数名压成表键"""
|
"""djb2-64(小立即数,溢出按无符号回绕):把函数名压成表键。
|
||||||
h: u64 = 5381
|
注:返回类型用 sizet(本文件通用、内建),不用 stdint 的 u64 —— 后者未 import,
|
||||||
|
TPC 会因"方法返回类型解析不出"而 fail-fast。"""
|
||||||
|
h: sizet = 5381
|
||||||
if name is None: return h
|
if name is None: return h
|
||||||
n: sizet = string.strlen(name)
|
n: sizet = string.strlen(name)
|
||||||
i: sizet = 0
|
i: sizet = 0
|
||||||
while i < n:
|
while i < n:
|
||||||
h = h * 33 + u64(uint8(name[i]))
|
h = h * 33 + sizet(uint8(name[i]))
|
||||||
i += 1
|
i += 1
|
||||||
return h
|
return h
|
||||||
|
|
||||||
@@ -336,13 +353,15 @@ novirtual class StructGenMixin:
|
|||||||
|
|
||||||
def _RegisterFuncEscapeSummary(self, pool: memhub.MemBuddy*, fd: ast.FunctionDef*, key: str) -> int:
|
def _RegisterFuncEscapeSummary(self, pool: memhub.MemBuddy*, fd: ast.FunctionDef*, key: str) -> int:
|
||||||
"""扫函数体登记摘要;key 必须是与 EscTable 同池的字符串"""
|
"""扫函数体登记摘要;key 必须是与 EscTable 同池的字符串"""
|
||||||
if pool is None or fd is None or key is None or self.EscTable is None: return 1
|
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._EscLookup(key) >= 0: return 0
|
||||||
if self.EscCount >= 1024: return 1
|
if self.EscCount >= 1024: return 1
|
||||||
gset: hashtable.HashTable* = hashtable.HashTable(pool)
|
# gset 现在是**定长数组**(复用区;[0]=计数,[1..]=名字指针),不再是 HashTable;
|
||||||
if gset is not None: self._CollectGlobalNames(fd.children, gset, 0)
|
# box 也在同一块复用区里(偏移 65 槽 = 520 字节)。
|
||||||
box: sizet* = sizet*(pool.alloc(16))
|
gset: sizet* = self.EscScratch
|
||||||
if box is None: return 1
|
gset[0] = 0
|
||||||
|
self._CollectGlobalNames(fd.children, gset, 0)
|
||||||
|
box: sizet* = sizet*(uint64(self.EscScratch) + 520)
|
||||||
box[0] = 0
|
box[0] = 0
|
||||||
box[1] = 0
|
box[1] = 0
|
||||||
self._ScanEscapes(fd, fd.children, gset, box, 0)
|
self._ScanEscapes(fd, fd.children, gset, box, 0)
|
||||||
@@ -353,8 +372,8 @@ novirtual class StructGenMixin:
|
|||||||
self.EscCount = slot + 1
|
self.EscCount = slot + 1
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def _CollectGlobalNames(self, stmts: list[ast.AST*]*, gset: hashtable.HashTable*, depth: int) -> int:
|
def _CollectGlobalNames(self, stmts: list[ast.AST*]*, gset: sizet*, depth: int) -> int:
|
||||||
"""收集本函数(含嵌套块)里 `global` 声明的名字"""
|
"""收集本函数(含嵌套块)`global` 声明的名字到定长数组:[0]=计数,[1..]=名字指针"""
|
||||||
if stmts is None or gset is None or depth > 64: return 0
|
if stmts is None or gset is None or depth > 64: return 0
|
||||||
n: sizet = len(stmts)
|
n: sizet = len(stmts)
|
||||||
for i in range(n):
|
for i in range(n):
|
||||||
@@ -369,7 +388,13 @@ novirtual class StructGenMixin:
|
|||||||
nnd: ast.AST* = gn.names.get(j)
|
nnd: ast.AST* = gn.names.get(j)
|
||||||
if nnd is not None and nnd.kind() == ast.ASTKind.Name:
|
if nnd is not None and nnd.kind() == ast.ASTKind.Name:
|
||||||
nnm: ast.Name* = ast.Name*(nnd)
|
nnm: ast.Name* = ast.Name*(nnd)
|
||||||
if nnm.id is not None: gset.set_int(nnm.id, 1)
|
if nnm.id is not None:
|
||||||
|
gc: sizet = gset[0]
|
||||||
|
if gc < 64:
|
||||||
|
# 存**名哈希**而非字符串指针:避免"str 当 str* 解"的用法错误
|
||||||
|
# (str*(x)[0] 只在 x 是 str 变量地址时成立;存 str 本身会读到其字节)
|
||||||
|
gset[1 + gc] = sizet(self._NameHash(nnm.id))
|
||||||
|
gset[0] = gc + 1
|
||||||
elif k == ast.ASTKind.If:
|
elif k == ast.ASTKind.If:
|
||||||
ifn: ast.If* = ast.If*(st)
|
ifn: ast.If* = ast.If*(st)
|
||||||
self._CollectGlobalNames(ifn.children, gset, depth + 1)
|
self._CollectGlobalNames(ifn.children, gset, depth + 1)
|
||||||
@@ -405,7 +430,7 @@ novirtual class StructGenMixin:
|
|||||||
if pa.arg is not None and string.strcmp(pa.arg, vnm.id) == 0: return int(i)
|
if pa.arg is not None and string.strcmp(pa.arg, vnm.id) == 0: return int(i)
|
||||||
return -1
|
return -1
|
||||||
|
|
||||||
def _MarkEscapingStore(self, fd: ast.FunctionDef*, target: ast.AST*, value: ast.AST*, gset: hashtable.HashTable*, box: sizet*) -> int:
|
def _MarkEscapingStore(self, fd: ast.FunctionDef*, target: ast.AST*, value: ast.AST*, gset: sizet*, box: sizet*) -> int:
|
||||||
"""`target = 形参`:若 target 是对象字段或已声明的全局,则该形参逃逸"""
|
"""`target = 形参`:若 target 是对象字段或已声明的全局,则该形参逃逸"""
|
||||||
pi: int = self._ParamIndex(fd, value)
|
pi: int = self._ParamIndex(fd, value)
|
||||||
if pi < 0 or target is None or box is None: return 0
|
if pi < 0 or target is None or box is None: return 0
|
||||||
@@ -414,11 +439,22 @@ novirtual class StructGenMixin:
|
|||||||
box[0] = box[0] | (sizet(1) << pi)
|
box[0] = box[0] | (sizet(1) << pi)
|
||||||
elif tk == ast.ASTKind.Name:
|
elif tk == ast.ASTKind.Name:
|
||||||
tnm: ast.Name* = ast.Name*(target)
|
tnm: ast.Name* = ast.Name*(target)
|
||||||
if tnm.id is not None and gset is not None and gset.__contains__(tnm.id) != 0:
|
if tnm.id is not None and gset is not None and self._IsGlobalNm(gset, tnm.id) != 0:
|
||||||
box[0] = box[0] | (sizet(1) << pi)
|
box[0] = box[0] | (sizet(1) << pi)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def _ScanEscapes(self, fd: ast.FunctionDef*, stmts: list[ast.AST*]*, gset: hashtable.HashTable*, box: sizet*, depth: int) -> int:
|
def _IsGlobalNm(self, garr: sizet*, name: str) -> int:
|
||||||
|
"""name 是否在定长 global 名单里([0]=计数,[1..]=名哈希);1=是 / 0=否"""
|
||||||
|
if garr is None or name is None: return 0
|
||||||
|
h: sizet = self._NameHash(name)
|
||||||
|
gc: sizet = garr[0]
|
||||||
|
i: sizet = 0
|
||||||
|
while i < gc:
|
||||||
|
if garr[1 + i] == h: return 1
|
||||||
|
i += 1
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def _ScanEscapes(self, fd: ast.FunctionDef*, stmts: list[ast.AST*]*, gset: sizet*, box: sizet*, depth: int) -> int:
|
||||||
"""递归扫描:return p / G = p / obj.f = p;只进 if/while/for,不进嵌套 def/class"""
|
"""递归扫描:return p / G = p / obj.f = p;只进 if/while/for,不进嵌套 def/class"""
|
||||||
if fd is None or stmts is None or box is None or depth > 64: return 0
|
if fd is None or stmts is None or box is None or depth > 64: return 0
|
||||||
n: sizet = len(stmts)
|
n: sizet = len(stmts)
|
||||||
@@ -497,10 +533,11 @@ novirtual class StructGenMixin:
|
|||||||
if fd is None or fd.name is None: continue
|
if fd is None or fd.name is None: continue
|
||||||
idx: int = self._EscLookup(fd.name)
|
idx: int = self._EscLookup(fd.name)
|
||||||
if idx < 0: continue
|
if idx < 0: continue
|
||||||
gset: hashtable.HashTable* = hashtable.HashTable(pool)
|
if self.EscScratch is None: continue
|
||||||
if gset is not None: self._CollectGlobalNames(fd.children, gset, 0)
|
gset: sizet* = self.EscScratch
|
||||||
box: sizet* = sizet*(pool.alloc(16))
|
gset[0] = 0
|
||||||
if box is None: continue
|
self._CollectGlobalNames(fd.children, gset, 0)
|
||||||
|
box: sizet* = sizet*(uint64(self.EscScratch) + 520)
|
||||||
box[0] = self.EscTable[idx * 3 + 1]
|
box[0] = self.EscTable[idx * 3 + 1]
|
||||||
box[1] = self.EscTable[idx * 3 + 2]
|
box[1] = self.EscTable[idx * 3 + 2]
|
||||||
self._ScanEscapes(fd, fd.children, gset, box, 0)
|
self._ScanEscapes(fd, fd.children, gset, box, 0)
|
||||||
@@ -508,6 +545,16 @@ novirtual class StructGenMixin:
|
|||||||
self.EscTable[idx * 3 + 2] = box[1]
|
self.EscTable[idx * 3 + 2] = box[1]
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
def _EscMaskSum(self) -> sizet:
|
||||||
|
"""所有条目 (mask, ret_borrow) 的滚动指纹,用于判定传递闭包是否已收敛"""
|
||||||
|
if self.EscTable is None: return 0
|
||||||
|
s: sizet = 0
|
||||||
|
i: int = 0
|
||||||
|
while i < self.EscCount:
|
||||||
|
s = s * 31 + self.EscTable[i * 3 + 1] * 1099511628211 + self.EscTable[i * 3 + 2]
|
||||||
|
i += 1
|
||||||
|
return s
|
||||||
|
|
||||||
def get_file_module_sha1(self) -> str:
|
def get_file_module_sha1(self) -> str:
|
||||||
"""Return the module SHA1 of the file being translated (None if unregistered)."""
|
"""Return the module SHA1 of the file being translated (None if unregistered)."""
|
||||||
return self.FileModuleSha1
|
return self.FileModuleSha1
|
||||||
|
|||||||
@@ -91,7 +91,11 @@ novirtual class TypeGenErr(TypeGenBase.TypeGenBase):
|
|||||||
else: stdio.printf(' File "%s"\n', file_path)
|
else: stdio.printf(' File "%s"\n', file_path)
|
||||||
if err_msg is None or err_msg[0] == 0: err_msg = "syntax error"
|
if err_msg is None or err_msg[0] == 0: err_msg = "syntax error"
|
||||||
stdio.printf("SyntaxError: %s\n", err_msg)
|
stdio.printf("SyntaxError: %s\n", err_msg)
|
||||||
def fatal_error(self, node: ast.AST*, msg: str, err_type: str = "SyntaxError", col_start: int = 0, col_end: int = 0):
|
# 注:返回类型必须显式给一个**真实类型**(int),不能留空、也不能用 None。
|
||||||
|
# 本方法是"必定终止"的 fail-fast,从不返回;但留空/None 都会让跨模块 call site 解析出
|
||||||
|
# 退化类型 i0,命中 CallGenCall.vp 的 i0 守卫 → _fail_method_ret_ty("fatal_error") 中止编译。
|
||||||
|
# 给 int 后解析为 i32,守卫不再命中;因从不返回,该类型不会被真正使用。
|
||||||
|
def fatal_error(self, node: ast.AST*, msg: str, err_type: str = "SyntaxError", col_start: int = 0, col_end: int = 0) -> int:
|
||||||
"""Report an error in Python style and exit (supports multiple frames, error type, column range)."""
|
"""Report an error in Python style and exit (supports multiple frames, error type, column range)."""
|
||||||
stdio.printf("Traceback (most recent call last, compile-time error):\n")
|
stdio.printf("Traceback (most recent call last, compile-time error):\n")
|
||||||
# Print the call stack frames (bottom to top)
|
# Print the call stack frames (bottom to top)
|
||||||
@@ -151,11 +155,15 @@ novirtual class TypeGenErr(TypeGenBase.TypeGenBase):
|
|||||||
stdio.fflush(None)
|
stdio.fflush(None)
|
||||||
# Termination convergence: in REPL, longjmp back to the loop via VLogger.repl_abort (the
|
# Termination convergence: in REPL, longjmp back to the loop via VLogger.repl_abort (the
|
||||||
# traceback is printed and the process survives); in batch this exit is sys.exit(1) unchanged.
|
# traceback is printed and the process survives); in batch this exit is sys.exit(1) unchanged.
|
||||||
VLogger.repl_abort()
|
VLogger.repl_abort()
|
||||||
|
# 不可达:repl_abort 要么 longjmp 回 REPL 循环、要么 sys.exit(1)。
|
||||||
|
# 显式 return 只为满足上面声明的 -> int 返回类型(避免"缺返回路径")。
|
||||||
|
return 0
|
||||||
|
|
||||||
def fatal_type_error(self, node: ast.AST*, msg: str, col_start: int = 0, col_end: int = 0):
|
# 同 fatal_error:跨模块调用需要**真实**返回类型,否则解析出 i0 会命中守卫中止编译。
|
||||||
|
def fatal_type_error(self, node: ast.AST*, msg: str, col_start: int = 0, col_end: int = 0) -> int:
|
||||||
"""Report an error in Python style and exit (TypeError variant)"""
|
"""Report an error in Python style and exit (TypeError variant)"""
|
||||||
self.fatal_error(node, msg, "TypeError", col_start, col_end)
|
return self.fatal_error(node, msg, "TypeError", col_start, col_end)
|
||||||
def try_extract_callable_ret(self, pool: memhub.MemBuddy*, annot: ast.AST*, imported_modules: str, from_imports: str, trans: HT.Translator*) -> mlirlite.LLVMType*:
|
def try_extract_callable_ret(self, pool: memhub.MemBuddy*, annot: ast.AST*, imported_modules: str, from_imports: str, trans: HT.Translator*) -> mlirlite.LLVMType*:
|
||||||
"""Extract the return type of a function-pointer annotation (t.Callable[[A,B], R]); None for non-Callable or signature-less forms."""
|
"""Extract the return type of a function-pointer annotation (t.Callable[[A,B], R]); None for non-Callable or signature-less forms."""
|
||||||
if annot is None or annot.kind() != ast.ASTKind.Subscript: return None
|
if annot is None or annot.kind() != ast.ASTKind.Subscript: return None
|
||||||
|
|||||||
@@ -187,7 +187,12 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase):
|
|||||||
# [reexport class ownership] cls not found under cand: it may be re-exported
|
# [reexport class ownership] cls not found under cand: it may be re-exported
|
||||||
# via from-import; look up the global reexport map, else parse the module .vpi.
|
# via from-import; look up the global reexport map, else parse the module .vpi.
|
||||||
rr_mod: str = self.Gen.ImportsGen.lookup_reexport(cand_sha1, cls_name)
|
rr_mod: str = self.Gen.ImportsGen.lookup_reexport(cand_sha1, cls_name)
|
||||||
if rr_mod is None: rr_mod = self.Gen.CallGenBase._lookup_reexport_from_vpi(pool, cand_sha1, cls_name)
|
# allow_star=0: only a NAMED re-export (`from X import Cls`) is evidence of ownership.
|
||||||
|
# A `from X import *` candidate must not be used here -- nearly every file star-imports
|
||||||
|
# stdint, and taking one as the class's true defining module mis-owns the forward
|
||||||
|
# declaration (entry type "<candidate_sha1>.ClassName"), after which every vpi/text.ll
|
||||||
|
# return-type lookup for that class misses and fail-fasts.
|
||||||
|
if rr_mod is None: rr_mod = self.Gen.CallGenBase._lookup_reexport_from_vpi(pool, cand_sha1, cls_name, 0)
|
||||||
if rr_mod is None: return cand_sha1
|
if rr_mod is None: return cand_sha1
|
||||||
rr_sha: str = self.Gen.StructGen._resolve_module_sha1(rr_mod)
|
rr_sha: str = self.Gen.StructGen._resolve_module_sha1(rr_mod)
|
||||||
if rr_sha is None or rr_sha[0] == '\0' or string.strcmp(rr_sha, cand_sha1) == 0: return cand_sha1
|
if rr_sha is None or rr_sha[0] == '\0' or string.strcmp(rr_sha, cand_sha1) == 0: return cand_sha1
|
||||||
|
|||||||
Reference in new issue
Block a user