diff --git a/App/lib/Phase/Phase1/Phase1Main.vp b/App/lib/Phase/Phase1/Phase1Main.vp index 2d11d22..a1b4f99 100644 --- a/App/lib/Phase/Phase1/Phase1Main.vp +++ b/App/lib/Phase/Phase1/Phase1Main.vp @@ -437,6 +437,59 @@ def RunPhase1(mb: rowptr[memhub.MemBuddy], includes_dir: str, temp_dir: str, p1a_skp: int = 0 p1a_fl: int = 0 + # ============================================================ + # [临时取证打点] Phase1 早期扁平缓冲分配失败现场快照(定位后清理)。 + # 现场:class_names_buf/parent_names_buf 均成功,def_sha1s_buf 却返回 NULL; + # 此时主池(1GiB)远未耗尽 → 非 OOM。本打点仅只读 mb 状态 + 打印, + # 绝不改变任何分配行为与逻辑。三维度判定: + # ① OOM:dump_stats 显示 used≈total、free≈0; + # ② mb 损坏:usable 基址/大小荒谬、self_check != 0、max_order 异常; + # ③ size/order 异常:本次请求 size 算出的 order > max_order。 + # 用 VLogger.info(非终止;VLogger.error 会 sys.exit(1) 掩盖后续打点)。 + # ============================================================ + if mb is not None: + # 先做带环检测的 self_check:free list 被写坏成环时 dump_stats 的 + # free_bytes/free_count 无步数上限会死循环(打点自身卡死),故 + # self_check 返回 4(成环)时跳过 dump_stats。 + p1_sc: int = mb.self_check() + if p1_sc != 4: + mb.dump_stats("phase1-pre") + else: + VLogger.info("[取证] self_check=4(free list 成环),跳过 dump_stats 防死循环", "Phase1") + p1_mo: int = mb.max_order + if p1_mo < 0 or p1_mo > 32: + p1_mo = 32 # 钳制:max_order 被写坏时防本打点自身越界读 free_lists + p1_fl_head: uint64 = uint64(mb.free_lists[p1_mo]) + p1_mb_addr: uint64 = uint64(mb) + p1_base_addr: uint64 = uint64(mb.base) + p1_usable_addr: uint64 = uint64(mb.usable) + p1_chkbase_addr: uint64 = uint64(mb.chk_base) + VLogger.info( + f"[取证] mb={p1_mb_addr} base={p1_base_addr} usable={p1_usable_addr} " + f"usable_size={mb.usable_size} max_order={mb.max_order} self_check={p1_sc} " + f"detection={mb.detection}", "Phase1") + VLogger.info( + f"[取证] chk_base={p1_chkbase_addr} chk_depth={mb.chk_depth} " + f"chk_cap={mb.chk_cap} free_lists[clamp={p1_mo}]={p1_fl_head}", + "Phase1") + p1_sz_cn: sizet = sizet(Phase1.MAX_CLASSES * Phase1.CLASS_NAME_LEN) + p1_sz_ds: sizet = sizet(Phase1.MAX_CLASSES * Phase1.SHA1_LEN) + p1_sz_tp: sizet = sizet(4 * Phase1.MAX_FILE_DEPS) + p1_sz_bx: sizet = sizet(4) + p1_o_cn: int = mb._order_for_size(p1_sz_cn + 16) + p1_o_ds: int = mb._order_for_size(p1_sz_ds + 16) + p1_o_tp: int = mb._order_for_size(p1_sz_tp + 16) + p1_o_bx: int = mb._order_for_size(p1_sz_bx + 16) + p1_fl_cn: uint64 = uint64(mb.free_lists[p1_o_cn]) + p1_fl_ds: uint64 = uint64(mb.free_lists[p1_o_ds]) + VLogger.info( + f"[取证] order(class_names {p1_sz_cn}B+16)={p1_o_cn} fl={p1_fl_cn} " + f"order(def_sha1s {p1_sz_ds}B+16)={p1_o_ds} fl={p1_fl_ds} " + f"order(topo {p1_sz_tp}B+16)={p1_o_tp} order(box {p1_sz_bx}B+16)={p1_o_bx} " + f"(fl==0 只说明该阶空、仍可由更高阶拆分;若 alloc 失败而 dump_stats 的 free>0 才是元数据损坏铁证," + f"任一 order>max_order={mb.max_order} 则是 size 异常)", + "Phase1") + # 预扫描类继承关系,构建拓扑顺序(用扁平字节数组,避免结构体 str 指针 NULL 崩溃) class_names_buf: bytes = mb.alloc(Phase1.MAX_CLASSES * Phase1.CLASS_NAME_LEN) parent_names_buf: bytes = mb.alloc(Phase1.MAX_CLASSES * Phase1.CLASS_NAME_LEN) @@ -451,42 +504,57 @@ def RunPhase1(mb: rowptr[memhub.MemBuddy], includes_dir: str, temp_dir: str, string.memset(generic_file_count_box, 0, 4) topo_count: int = 0 - if class_names_buf is not None and parent_names_buf is not None and def_sha1s_buf is not None and topo_order is not None and topo_count_box is not None: - string.memset(class_names_buf, 0, Phase1.MAX_CLASSES * Phase1.CLASS_NAME_LEN) - string.memset(parent_names_buf, 0, Phase1.MAX_CLASSES * Phase1.CLASS_NAME_LEN) - string.memset(def_sha1s_buf, 0, Phase1.MAX_CLASSES * Phase1.SHA1_LEN) - string.memset(topo_order, 0, 4 * Phase1.MAX_FILE_DEPS) - string.memset(topo_count_box, 0, 4) - if generic_file_indices is not None: - string.memset(generic_file_indices, 0, 4 * Phase1.MAX_FILE_DEPS) - if generic_file_count_box is not None: - string.memset(generic_file_count_box, 0, 4) + # 三块扁平缓冲是 Phase1Scan._ScanClassInheritance 的契约前置缓冲:函数 + # 入口已对 NULL 做 fail-fast 检查(本处分配失败若静默跳过整段,将退化为 + # 字母序并掩盖 OOM 等内存故障)。故分配失败时逐块显式报错终止(VLogger.error + # 内部 sys.exit(1)),绝不静默回退。 + # 注:topo_order/topo_count_box 仅供后续 Phase1Topo._TopoSortFiles 使用, + # 不属 _ScanClassInheritance 契约,故不并入此处终止逻辑;其有效性由 + # _TopoSortFiles 入口自身校验(out_order/out_count 为 NULL 即返回 -1 并终止)。 + if class_names_buf is None: + VLogger.error("类继承预扫描扁平缓冲分配失败:class_names_buf 为 NULL,终止编译", "Phase1") + if parent_names_buf is None: + VLogger.error("类继承预扫描扁平缓冲分配失败:parent_names_buf 为 NULL,终止编译", "Phase1") + if def_sha1s_buf is None: + VLogger.error("类继承预扫描扁平缓冲分配失败:def_sha1s_buf 为 NULL,终止编译", "Phase1") - class_count_scanned: int = Phase1Scan._ScanClassInheritance( + string.memset(class_names_buf, 0, Phase1.MAX_CLASSES * Phase1.CLASS_NAME_LEN) + string.memset(parent_names_buf, 0, Phase1.MAX_CLASSES * Phase1.CLASS_NAME_LEN) + string.memset(def_sha1s_buf, 0, Phase1.MAX_CLASSES * Phase1.SHA1_LEN) + if topo_order is not None: + string.memset(topo_order, 0, 4 * Phase1.MAX_FILE_DEPS) + if topo_count_box is not None: + string.memset(topo_count_box, 0, 4) + if generic_file_indices is not None: + string.memset(generic_file_indices, 0, 4 * Phase1.MAX_FILE_DEPS) + if generic_file_count_box is not None: + string.memset(generic_file_count_box, 0, 4) + + class_count_scanned: int = Phase1Scan._ScanClassInheritance( + mb, result, reachable_set, reachable_count, + use_reachable, sha1_set, set_count, + class_names_buf, parent_names_buf, def_sha1s_buf, + generic_file_indices, generic_file_count_box) + + if class_count_scanned > 0: + td_len_ts_call: sizet = string.strlen(temp_dir) + ret_topo: int = Phase1Topo._TopoSortFiles( mb, result, reachable_set, reachable_count, use_reachable, sha1_set, set_count, - class_names_buf, parent_names_buf, def_sha1s_buf, - generic_file_indices, generic_file_count_box) - - if class_count_scanned > 0: - td_len_ts_call: sizet = string.strlen(temp_dir) - ret_topo: int = Phase1Topo._TopoSortFiles( - mb, result, reachable_set, reachable_count, - use_reachable, sha1_set, set_count, - class_names_buf, parent_names_buf, def_sha1s_buf, class_count_scanned, - topo_order, topo_count_box, - temp_dir, td_len_ts_call) - if ret_topo == 0: - tc_ptr: t.CPtr = (int | t.CPtr)(t.CVoid(topo_count_box, t.CPtr)) - if tc_ptr is not None: - topo_count = tc_ptr[0] - VLogger.info(f"拓扑排序: {class_count_scanned} 个类, {topo_count} 个文件按依赖顺序处理", "Phase1") - else: - VLogger.error("拓扑排序失败(类继承依赖无法解析),终止编译", "Phase1") - return 1 + class_names_buf, parent_names_buf, def_sha1s_buf, class_count_scanned, + topo_order, topo_count_box, + temp_dir, td_len_ts_call) + if ret_topo == 0: + tc_ptr: t.CPtr = (int | t.CPtr)(t.CVoid(topo_count_box, t.CPtr)) + if tc_ptr is not None: + topo_count = tc_ptr[0] + VLogger.info(f"拓扑排序: {class_count_scanned} 个类, {topo_count} 个文件按依赖顺序处理", "Phase1") else: - VLogger.warning("类继承预扫描无结果,回退到字母序", "Phase1") - topo_count = 0 + VLogger.error("拓扑排序失败(类继承依赖无法解析),终止编译", "Phase1") + return 1 + else: + VLogger.warning("类继承预扫描无结果,回退到字母序", "Phase1") + topo_count = 0 # ============================================================ # Phase 1a-pre-generic-all: mark1 前全量预注册 includes 泛型模板 diff --git a/App/lib/Phase/Phase1/Phase1Scan.vp b/App/lib/Phase/Phase1/Phase1Scan.vp index 38e1785..dd8112e 100644 --- a/App/lib/Phase/Phase1/Phase1Scan.vp +++ b/App/lib/Phase/Phase1/Phase1Scan.vp @@ -76,8 +76,15 @@ def _ScanClassInheritance(mb: rowptr[memhub.MemBuddy], generic_file_indices: t.CPtr, generic_file_count_box: t.CPtr) -> int: """预扫描 includes 文件,收集类继承关系""" - if result is None or class_names_buf is None or parent_names_buf is None or def_sha1s_buf is None: + if result is None: return -1 + # class_names_buf/parent_names_buf/def_sha1s_buf 由调用方 mb.alloc 分配 + # (Phase1Main 预扫描前),本函数将其地址直接作为 strcpy 的目的缓冲 + # (name_dst/parent_dst/sha1_dst)。分配失败返回 NULL 时目的指针为近零地址 + # → strcpy 写零页(ASan access-violation)。按 fail-fast 规则显式报错终止 + # (VLogger.error 内部 sys.exit(1)),不静默返回 -1 交由调用方宽容跳过。 + if class_names_buf is None or parent_names_buf is None or def_sha1s_buf is None: + VLogger.error("类继承预扫描扁平缓冲分配失败(class_names_buf/parent_names_buf/def_sha1s_buf 为 NULL)", "Phase1") entry_size: sizet = IncludesScanner.FileEntry.__sizeof__() class_count: int = 0 diff --git a/App/lib/Stub/StubGen/VpiReader.vp b/App/lib/Stub/StubGen/VpiReader.vp index 23e2528..8fc1908 100644 --- a/App/lib/Stub/StubGen/VpiReader.vp +++ b/App/lib/Stub/StubGen/VpiReader.vp @@ -904,6 +904,26 @@ def _resolve_single_vpi_type_str(pool: rowptr[memhub.MemBuddy], # None 与 void 同义,归一到 Void。 if string.strcmp(seg, "None") == 0: return llvmlite.Void(pool) + # rowptr[T] 内建裸指针(等价 t.CPtr[T] → Ptr(T)):vpi 返回/参数注解按源码 + # 原样保留 Subscript 形态(如 tokens.vp `new_token -> rowptr[Token]`)。 + # 此前无此分支 → 整个注解解析返回 None → text.ll 尚未生成(合法时序:依赖 + # 模块尚未翻译,占位 text.ll 为空)时跨模块调用 fail-fast 误报 + # (includes/ast/lexer.vp new_token 根因)。与定义侧 TypeGenResolve + # resolve_annotation_type 的 rowptr 分支同口径:Ptr(resolve(T))。 + if string.strncmp(seg, "rowptr[", 7) == 0: + seg_len_rp: sizet = string.strlen(seg) + if seg_len_rp > 8 and seg[seg_len_rp - 1] == ']': + inner_len_rp: sizet = seg_len_rp - 8 + inner_buf_rp: bytes = stdlib.malloc(inner_len_rp + 1) + if inner_buf_rp is not None: + string.strncpy(inner_buf_rp, seg + 7, inner_len_rp) + inner_buf_rp[inner_len_rp] = '\0' + inner_ty_rp: llvmlite.LLVMType | t.CPtr = _resolve_single_vpi_type_str( + pool, inner_buf_rp) + stdlib.free(inner_buf_rp) + if inner_ty_rp is not None: + return llvmlite.Ptr(pool, inner_ty_rp) + return None # t.XXX / c.XXX 内置类型映射 if string.strncmp(seg, "t.", 2) == 0 or string.strncmp(seg, "c.", 2) == 0: attr_nm: str = seg + 2 @@ -1112,8 +1132,14 @@ def _resolve_vpi_ret_annot_str(pool: rowptr[memhub.MemBuddy], # .vpi 读取缓冲区大小(256KB,与 PYI_BUF_SIZE 一致) VPI_READ_BUF_SIZE: sizet = 262144 -# SHA1 字符串长度(16 hex 字符) -SHA1_LEN: sizet = 16 +# SHA1 hex 字符数(16,仅文件名中的十六进制位,不含结尾 NUL) +# +# 注意:本常量原名 SHA1_LEN,与 lib.Phase.Phase1 的常量宏 +# Phase1.SHA1_LEN: t.CDefine = 17(16 字符 + NUL 的存储步长)**同名但语义不同**。 +# 同名跨模块符号会造成 CDefine 解析被遮蔽/串值(Phase1.MAX_CLASSES * Phase1.SHA1_LEN +# 被解析成 0 → mb.alloc(0) → 分配失败)。故改为唯一名 SHA1_HEX_LEN 消除冲突; +# 权威定义仍分别是:本处 16(hex 位数)、Phase1.SHA1_LEN 17(存储步长)。 +SHA1_HEX_LEN: sizet = 16 # ============================================================ @@ -1135,7 +1161,7 @@ def _extract_sha1_from_path(vpi_path: str) -> str: return None # 查找最后一个路径分隔符 / 或 \ total_len: sizet = string.strlen(vpi_path) - if total_len < SHA1_LEN + 4: # 至少需要 "xxxx.vpi" 的长度 + if total_len < SHA1_HEX_LEN + 4: # 至少需要 "xxxx.vpi" 的长度 return None # 从后向前查找最后一个 / 或 \ sep_pos: sizet = total_len @@ -1145,14 +1171,14 @@ def _extract_sha1_from_path(vpi_path: str) -> str: if ch == '/' or ch == '\\': sep_pos += 1 break - if sep_pos + SHA1_LEN > total_len: + if sep_pos + SHA1_HEX_LEN > total_len: return None # 分配缓冲区并复制 SHA1(16 字符 + null 终止符) - sha1_buf: str = stdlib.malloc(SHA1_LEN + 1) + sha1_buf: str = stdlib.malloc(SHA1_HEX_LEN + 1) if sha1_buf is None: return None - string.strncpy(sha1_buf, vpi_path + sep_pos, SHA1_LEN) - sha1_buf[SHA1_LEN] = '\0' + string.strncpy(sha1_buf, vpi_path + sep_pos, SHA1_HEX_LEN) + sha1_buf[SHA1_HEX_LEN] = '\0' return sha1_buf diff --git a/App/lib/core/Gen/AssignGenMixin.vp b/App/lib/core/Gen/AssignGenMixin.vp index 1e5d971..17bab09 100644 --- a/App/lib/core/Gen/AssignGenMixin.vp +++ b/App/lib/core/Gen/AssignGenMixin.vp @@ -1791,8 +1791,62 @@ class AssignGenMixin: pass if precise_rhs_ty is not None and alloca.Ty is not None: alloca_pt: llvmlite.LLVMType | t.CPtr = alloca.Ty.Pointee + # 禁止「降级重建」:RHS 的 pointee 若为**裸泛型基类**(丢类型 + # 实参,如 {sha1}.list / {sha1}.GSList),以它重建 alloca 会把后 + # 注解已解析出的「带类型实参特化」(如 list[ast.ASTptr])覆盖成裸 + # 基类 → 之后 .get() 按错误元素类型特化(list[int].get 返回 i32) + # → inttoptr 把 i32 当指针(截断)→ translate_stmt 读垃圾 AST 节 + # 点崩溃。本重建本意是「注解退化成裸基类/i8*,RHS 构造出精确特化」 + # 的**反向升级**(见上方注释),因此仅在 RHS 不是裸泛型基类时才执行。 + # 现场根因:AST.children 字段注册为裸 list*(其注解 + # list[rowptr[AST]] | t.CPtr 在字段注册期未解析出带实参特化), + # 故 `body_list: list[ast.AST | t.CPtr] | t.CPtr = mc.children` + # 的 RHS 静态类型退化为裸 list*,触发本降级重建 → + # MatchGenMixin._handle_renum_match 崩溃(IR 实证: + # %"body_list.1" = alloca %"….list"* 被使用,%body_list 特化 alloca + # 成死代码;.get 被分派到 list[int].get 返回 i32 → inttoptr 截断)。 + # 裸泛型基类不可作为 receiver 静态类型,否则泛型方法分派必错。 + # 取注解侧泛型基名(Base ∈ list/dict/...,仅 Name 形态的 + # Subscript 一侧),用于下方**不依赖泛型模板注册表**的方向判定。 + ann_gen_base: str = None + if annot is not None: + ag_side: ast.AST | t.CPtr = annot + if ag_side.kind() == ast.ASTKind.BinOp: + ag_bop: ast.BinOp | t.CPtr = (ast.BinOp | t.CPtr)(ag_side) + if ag_bop.op == ast.OpKind.BitOr: + if ag_bop.left is not None and ag_bop.left.kind() == ast.ASTKind.Subscript: + ag_side = ag_bop.left + elif ag_bop.right is not None and ag_bop.right.kind() == ast.ASTKind.Subscript: + ag_side = ag_bop.right + if ag_side.kind() == ast.ASTKind.Subscript: + ag_sub: ast.Subscript | t.CPtr = (ast.Subscript | t.CPtr)(ag_side) + if ag_sub.value is not None and ag_sub.value.kind() == ast.ASTKind.Name: + ag_nm: ast.Name | t.CPtr = (ast.Name | t.CPtr)(ag_sub.value) + if ag_nm.id is not None: + ann_gen_base = ag_nm.id + rb_is_bare_generic: int = 0 + if alloca_pt is not None \ + and uint64(alloca_pt) != uint64(precise_rhs_ty): + rhs_pt_rb: llvmlite.LLVMType | t.CPtr = precise_rhs_ty.Pointee + if rhs_pt_rb is not None: + rb_nm: str = llvmlite.get_struct_name(rhs_pt_rb) + if rb_nm is not None: + rb_pf: int = GenApi.sha1_prefix_len(rb_nm) + rb_short: str = rb_nm + rb_pf if rb_pf != 0 else rb_nm + if string.strchr(rb_short, '[') is None: + # ① 裸名命中泛型模板注册表(list/GSList/...) + if self.Gen.ClassGenGeneric._find_generic_template(rb_short) is not None: + rb_is_bare_generic = 1 + # ② 与注解同基名:注解是 `Base[...]`(带类型实参, + # 已比 RHS 的裸 Base 精确)⇒ 本次重建方向是**降级**, + # 必须禁止。此判定不依赖泛型模板注册表(list/dict 等 + # 内建容器未登记时①会漏判,②仍能命中)。 + elif ann_gen_base is not None \ + and string.strcmp(rb_short, ann_gen_base) == 0: + rb_is_bare_generic = 1 if alloca_pt is not None and \ - uint64(alloca_pt) != uint64(precise_rhs_ty): + uint64(alloca_pt) != uint64(precise_rhs_ty) \ + and rb_is_bare_generic == 0: new_alloca: llvmlite.Value | t.CPtr = self.Gen.VarGen._alloca_at_entry( builder, precise_rhs_ty) if new_alloca is not None: diff --git a/App/lib/core/Gen/Call/CallGenBase.vp b/App/lib/core/Gen/Call/CallGenBase.vp index 6f818fe..138c3a4 100644 --- a/App/lib/core/Gen/Call/CallGenBase.vp +++ b/App/lib/core/Gen/Call/CallGenBase.vp @@ -522,37 +522,44 @@ class CallGenBase: if mod is None or name is None: return None name_len: sizet = string.strlen(name) + # 热点优化:原实现把「精确匹配」+「本模块同名 mangled」+「后缀匹配 + # define 优先」+「后缀匹配 declare 兜底」拆成 4 趟完整链表扫描,每趟 + # 对每个节点调用一次 function_get_name/function_get_next(实测二者各 + # ~1.046 亿次的热点来源)。合并为 2 趟,链表顺序与优先级语义不变: + # 趟 1:精确匹配(命中即返回,优先级最高,与原第一遍一致)+ 收集 + # 首个本模块同名 mangled 候选(原"第一遍半",仅在无精确匹配 + # 时使用); + # 趟 2:收集首个 IsDeclared==0 后缀命中(原第二遍优先)与首个任意 + # 后缀命中(原第三遍兜底),扫描结束后按原优先级返回。 cur: llvmlite.Function | t.CPtr = mod.FuncHead - # 第一遍:精确匹配 + mangle_hit: llvmlite.Function | t.CPtr = None while cur is not None: cur_name: str = llvmlite.function_get_name(cur) if cur_name is not None: if string.strcmp(cur_name, name) == 0: return cur - cur = llvmlite.function_get_next(cur) - # 第一遍半(owner_sha1 提供时):本模块同名函数优先。 - # 仅接受恰为 "{owner_sha1}.{name}" 的形态(mangled 模块级函数名含 - # 且只含一个 '.'),不匹配 "{sha1}.Class.method" 等方法名。是否 - # IsDeclared==0 均可返回:前向 declare 与 define 同名同符号。 - if owner_sha1 is not None: - cur = mod.FuncHead - while cur is not None: - cur_name_o: str = llvmlite.function_get_name(cur) - if cur_name_o is not None: - cur_len_o: sizet = string.strlen(cur_name_o) + # 本模块同名候选:仅接受恰为 "{owner_sha1}.{name}" 的形态 + # (mangled 模块级函数名含且只含一个 '.'),不匹配 + # "{sha1}.Class.method" 等方法名。是否 IsDeclared==0 均可返回: + # 前向 declare 与 define 同名同符号。只在 owner_sha1 提供且尚未 + # 命中时计算,避免多做一次 strlen。 + if owner_sha1 is not None and mangle_hit is None: + cur_len_o: sizet = string.strlen(cur_name) if cur_len_o == 17 + name_len: - if string.strncmp(cur_name_o, owner_sha1, 16) == 0 and cur_name_o[16] == '.': + if string.strncmp(cur_name, owner_sha1, 16) == 0 and cur_name[16] == '.': suffix_start_o: sizet = cur_len_o - name_len - if cur_name_o[suffix_start_o - 1] == '.': + if cur_name[suffix_start_o - 1] == '.': match_o: int = 1 for i in range(name_len): - if cur_name_o[suffix_start_o + i] != name[i]: + if cur_name[suffix_start_o + i] != name[i]: match_o = 0 break if match_o == 1: - return cur - cur = llvmlite.function_get_next(cur) - # 第二遍:后缀匹配(.name) + mangle_hit = cur + cur = llvmlite.function_get_next(cur) + if mangle_hit is not None: + return mangle_hit + # 后缀匹配(.name) # 不跳过 declare:方法 stub 也是 declare,需要通过后缀匹配找到 # 调用处(translate_call 等)负责判断 IsDeclared 并决定使用裸名还是 sha1 前缀名 # @@ -562,6 +569,9 @@ class CallGenBase: # 匹配按链表顺序先命中跨模块 declare → 参数按 Lexer* coerce → Parser* 被 # bitcast 成 Lexer* → lexer 版 _advance 按 Lexer 布局操作 Parser 指针, # cur 永不推进 → parse 无限循环。本模块同名 define 必须优先。 + # 无同名 define 时回退到 stub declare(跨模块方法/函数调用)。 + declare_hit: llvmlite.Function | t.CPtr = None + any_hit: llvmlite.Function | t.CPtr = None cur = mod.FuncHead while cur is not None: cur_name2: str = llvmlite.function_get_name(cur) @@ -576,27 +586,16 @@ class CallGenBase: match = 0 break if match == 1: - if cur.IsDeclared == 0: - return cur + # 首个任意后缀命中(原第三遍兜底) + if any_hit is None: + any_hit = cur + # 首个 IsDeclared==0 命中(原第二遍优先) + if cur.IsDeclared == 0 and declare_hit is None: + declare_hit = cur cur = llvmlite.function_get_next(cur) - # 本模块无同名 define:回退到 stub declare(跨模块方法/函数调用) - cur = mod.FuncHead - while cur is not None: - cur_name3: str = llvmlite.function_get_name(cur) - if cur_name3 is not None: - cur_len3: sizet = string.strlen(cur_name3) - if cur_len3 > name_len + 1: - suffix_start3: sizet = cur_len3 - name_len - if cur_name3[suffix_start3 - 1] == '.': - match3: int = 1 - for i in range(name_len): - if cur_name3[suffix_start3 + i] != name[i]: - match3 = 0 - break - if match3 == 1: - return cur - cur = llvmlite.function_get_next(cur) - return None + if declare_hit is not None: + return declare_hit + return any_hit # ============================================================ diff --git a/App/lib/core/Gen/Call/CallGenCall.vp b/App/lib/core/Gen/Call/CallGenCall.vp index 29d1e94..02f48a0 100644 --- a/App/lib/core/Gen/Call/CallGenCall.vp +++ b/App/lib/core/Gen/Call/CallGenCall.vp @@ -1353,8 +1353,9 @@ class CallGenCall(CallGenBase.CallGenBase): arg_count_eff: int = 0 if kwc > 0 and found_func is not None: - # 关键字参数:遍历函数定义参数(跳过 self),位置参数优先,其余按名匹配 - # 缺失参数用零值兜底,保持 call 参数数量与 define 一致 + # 关键字参数:遍历函数定义参数(跳过 self),位置参数优先,其余按名匹配。 + # 未提供的形参按类型补占位零值以保持参数序列位置(见下方 (b)/(c) 注释); + # 实参表达式翻译失败则 fail-fast,绝不静默零值替换。 cur_param_k: llvmlite.Param | t.CPtr = llvmlite.function_get_param_head(found_func) if cur_param_k is not None: cur_param_k = llvmlite.param_get_next(cur_param_k) # 跳过 self @@ -1381,9 +1382,19 @@ class CallGenCall(CallGenBase.CallGenBase): arg_val_k = self.Gen.ExprGen.translate_value( builder, pool, mod, arg_node_k, None, 0, trans, pty_k0) if arg_val_k is None: - # 缺失参数:零值兜底(保持参数数量匹配)。 - # kw 省略的形参可能有默认值(查不到默认表 ≠ 无默认值), - # 零值兜底与原逻辑一致,禁止 fail-fast 误杀。 + if arg_node_k is not None: + # (a) 实参节点存在但表达式翻译失败(返回 None)→ fail-fast。 + # 禁止静默零值替换:那会把未解析实参(如跨模块同名冲突的 + # Phase1.SHA1_LEN)伪造成合法实参。 + self.Gen.TypeGenErr.fatal_error( + arg_node_k, + f"方法调用 '{class_name}.{method_name}' 第 {pi_k + 1} 个形参" + f"的实参表达式翻译失败(返回 None,禁止按类型零值兜底)", + "TypeError") + continue + # (b)/(c) 关键字/位置都未提供该形参:按形参类型补占位零值以保持 + # 参数序列位置(避免后续实参左移错位)。此占位不是"表达式翻译 + # 失败"的兜底(那条通路已在上方 fail-fast)。 pty_k: llvmlite.LLVMType | t.CPtr = llvmlite.param_get_ty(cur_param_k) if pty_k is not None: arg_val_k = llvmlite.ConstZero(pool, pty_k) @@ -1403,6 +1414,13 @@ class CallGenCall(CallGenBase.CallGenBase): for ai in range(can): arg_node: ast.AST | t.CPtr = cargs.get(ai) if arg_node is None: + # (a) 实参节点缺失(AST 参数列表空洞)→ fail-fast,禁止静默跳过 + # (跳过会减少 arg_count_eff,随后被"缺参默认填充"用零值补齐)。 + self.Gen.TypeGenErr.fatal_error( + cl, + f"方法调用 '{class_name}.{method_name}' 第 {ai + 1} 个实参" + f"节点缺失(AST 参数列表空洞,无法翻译)", + "TypeError") continue # 先取形参类型:单引号单字符字面量(CONST_CHAR)传给 str/指针 # 形参时需物化 1 字符字符串(translate_constant 依据 target_ty 判定), @@ -1422,6 +1440,16 @@ class CallGenCall(CallGenBase.CallGenBase): arg_val: llvmlite.Value | t.CPtr = self.Gen.ExprGen.translate_value( builder, pool, mod, arg_node, None, 0, trans, pty) if arg_val is None: + # (a) 实参表达式翻译失败(translate_value 返回 None)→ fail-fast。 + # 禁止 continue 跳过:跳过会减少 arg_count_eff,随后"缺参默认 + # 填充"用类型零值补齐该形参 —— 正是把未解析实参(如跨模块同名 + # 冲突的 Phase1.SHA1_LEN)静默变成 alloc(..., 0) 的兜底通路。 + # 用实参节点带出行号,报出被调用者与实参序号。 + self.Gen.TypeGenErr.fatal_error( + arg_node, + f"方法调用 '{class_name}.{method_name}' 第 {ai + 1} 个实参" + f"表达式翻译失败(返回 None,禁止按类型零值兜底)", + "TypeError") continue # 按形参类型 coerce(self 是形参 0,第 ai 个实参对应形参 ai+1) # 修复: CDefine/i32 常量传给 i64 形参(如 MemBuddy.__init__ 的 size: sizet) @@ -1552,10 +1580,23 @@ class CallGenCall(CallGenBase.CallGenBase): total_count += 1 mdf_pi += 1 continue - # 无默认值时用零值兜底(需要 found_func 获取参数类型)。 - # 默认参数表按裸名注册(如 "__init__"),跨模块 "ClassName.method" - # 查找常查不到——查不到 ≠ 无默认值(如 Buf.__init__(data, capacity) - # 缺的 length=0/owned=False 恰为零值),零值兜底语义一致。 + # (c) 已知默认值区间(DefaultCount>0)且该形参序号落在区间之前 + # ⇒ 该形参确无默认值 ⇒ 调用点漏传必需实参。禁止零值兜底(历史 + # 兜底会把漏传实参静默变成 0),fail-fast 报出被调用者与形参序号。 + if mdf_default_count > 0 and mdf_pi < mdf_offset: + self.Gen.TypeGenErr.fatal_error( + cl, + f"方法调用 '{lookup_name}' 漏传必需实参:第 {mdf_pi + 1} 个形参" + f"无默认值(调用点仅提供 {arg_count_eff} 个实参," + f"该函数前 {mdf_offset} 个形参为必需)", + "TypeError") + mdf_pi += 1 + continue + # (b) 其余缺参只能来自"默认值信息缺失"(默认参数表按裸名注册、 + # 跨模块 "ClassName.method" 查找常查不到,vpi 也可能未登记:查不到 + # ≠ 无默认值,故无法判定)。此时按声明形参类型补零,仅为对齐 + # call/declare 的形参数量、填补尾随的声明可选参数;这**不是** + # "表达式翻译失败"的兜底 —— 那条通路已在实参装配处 fail-fast。 if found_func is not None: param_node_z: llvmlite.Param | t.CPtr = llvmlite.function_get_param_head(found_func) skip_z: int = 0 diff --git a/App/lib/core/Gen/ImportsGenMixin.vp b/App/lib/core/Gen/ImportsGenMixin.vp index 67aae7f..ad1bec7 100644 --- a/App/lib/core/Gen/ImportsGenMixin.vp +++ b/App/lib/core/Gen/ImportsGenMixin.vp @@ -3,6 +3,7 @@ import t, c from stdint import * import ast import memhub +import hashtable import string import stdlib import viperlib @@ -48,11 +49,132 @@ class ImportsGenMixin: ReexportSrcs: bytes ReexportCount: int + # --- 导入查找索引(把 O(串长) 的逐字符扫描降为 O(名字长) 哈希查找) --- + # imported_modules/_from_imports 是空格拼接的扁平串("a b c" / "n:m[:orig]"), + # 长度可达数千字符,而每个 Name 节点都会多次查询。索引与「当前串指针」绑定: + # 调用方查询的 trans._imported_modules/_from_imports 总是 add_* 刚生成的当前串, + # 故命中索引快路径;其余来源(stub 期 stdlib malloc 缓冲等)指针不匹配 → 回退 + # 原逐字符扫描,语义完全不变。索引表用 PermPool 分配(跨文件/Phase 存活),由 + # 对应 add_* 增量维护(非每文件重建);key/value 均是指向该扁平串内部的指针, + # 串在文件级 reset 前始终有效。 + CurImportedModulesPtr: str + CurImportedModulesNames: hashtable.HashTable | t.CPtr + CurFromImportsPtr: str + CurFromImportsMods: hashtable.HashTable | t.CPtr + CurFromImportsOrigs: hashtable.HashTable | t.CPtr + # 注意:不声明 CurrentFile 字段——TypeGenMixin 已声明(Generator 展平块 # 只有一份,跨父类不去重规则下重复声明会导致字段错位)。本 Mixin 的 # set_current_file 与 CurrentFile 读取统一经 HandlesType 委托(延迟 import # 规避 HandlesType↔HandlesImports 循环依赖)。 + # ============================================================ + # _index_imported_module - 增量维护 imported_modules 查找索引 + # + # old_ptr 为追加前的串指针(None = 首个模块),new_ptr 为追加后的新串, + # name_ptr 指向新串内的新模块名(key 直接引用串内部,无需复制)。 + # 仅当 old_ptr 恰为当前索引绑定的串时复用其索引(正常路径:add_* 始终以 + # trans 的当前串为输入);否则放弃索引绑定并回退逐字符扫描(语义不变)。 + # ============================================================ + def _index_imported_module(self, old_ptr: str, new_ptr: str, + name_ptr: str) -> None: + """增量更新 imported_modules 索引(复用旧索引或新建,失败则解除绑定)""" + if new_ptr is None or name_ptr is None: + return + perm: rowptr[memhub.MemBuddy] = CompilerFactory.factory.PermPool + if perm is None: + return + idx: hashtable.HashTable | t.CPtr = None + if old_ptr is None: + # 新文件/新链首:建全新索引(旧索引随旧串一起废弃) + idx = hashtable.HashTable(perm) + elif old_ptr == self.CurImportedModulesPtr and self.CurImportedModulesNames is not None: + # 正常路径:复用当前串的索引并追加(不重建,内存 O(导入数)) + idx = self.CurImportedModulesNames + else: + # 旧串非当前索引串(异常来源):解除绑定,回退逐字符扫描 + self.CurImportedModulesPtr = None + self.CurImportedModulesNames = None + return + if idx is None: + self.CurImportedModulesPtr = None + self.CurImportedModulesNames = None + return + # 同名已在(重复导入)则跳过;否则写入(key/value 均指向 new_ptr 内部) + if idx.__contains__(name_ptr) == 0: + idx.set_str(name_ptr, name_ptr) + self.CurImportedModulesPtr = new_ptr + self.CurImportedModulesNames = idx + + # ============================================================ + # _index_from_import - 增量维护 from_imports 查找索引 + # + # entry_off 为新增 entry("local:module[:orig]")在新串内相对首字节的偏移。 + # 模块表 key=local_name → 指向模块名切片;orig 表 key=local_name → 指向原始名 + # 切片(无别名时写入 None,以"名存在但无原始名"区别于未命中)。 + # 语义对齐逐字符扫描:精确名首次命中为准(不覆盖),star 模块名以最后一条为准。 + # ============================================================ + def _index_from_import(self, old_ptr: str, new_ptr: str, entry_off: sizet, + local_name: str, module_name: str, + original_name: str) -> None: + """增量更新 from_imports 索引(复用旧索引或新建,失败则解除绑定)""" + if new_ptr is None or local_name is None or module_name is None: + return + perm: rowptr[memhub.MemBuddy] = CompilerFactory.factory.PermPool + if perm is None: + return + idx_m: hashtable.HashTable | t.CPtr = None + idx_o: hashtable.HashTable | t.CPtr = None + if old_ptr is None: + idx_m = hashtable.HashTable(perm) + idx_o = hashtable.HashTable(perm) + elif old_ptr == self.CurFromImportsPtr \ + and self.CurFromImportsMods is not None \ + and self.CurFromImportsOrigs is not None: + idx_m = self.CurFromImportsMods + idx_o = self.CurFromImportsOrigs + else: + self.CurFromImportsPtr = None + self.CurFromImportsMods = None + self.CurFromImportsOrigs = None + return + if idx_m is None or idx_o is None: + self.CurFromImportsPtr = None + self.CurFromImportsMods = None + self.CurFromImportsOrigs = None + return + local_len: sizet = string.strlen(local_name) + mod_len: sizet = string.strlen(module_name) + # 哈希键必须是 NUL 结尾串:扁平串内 local 段以 ':' 结尾(非 NUL),若直接把 + # 串内 local 段指针当键,FNV 会哈希到 "local:module..." 整段,与查询名 + # "local" 永不相等 → 索引恒 miss(_op_head NameError 根因)。故用 PermPool + # 分配 NUL 结尾的稳定副本作键(与索引表同寿命),值仍指向扁平串内模块名切片。 + key_ptr: str = perm.alloc(local_len + 1) + if key_ptr is None: + self.CurFromImportsPtr = None + self.CurFromImportsMods = None + self.CurFromImportsOrigs = None + return + string.strncpy(key_ptr, local_name, local_len) + key_ptr[local_len] = '\0' + mod_ptr: str = new_ptr + entry_off + local_len + 1 + if local_len == 1 and local_name[0] == '*': + # star import:模块名以最后一条为准(覆盖旧值,对齐 star_mod 语义) + idx_m.set_str(key_ptr, mod_ptr) + else: + if idx_m.__contains__(key_ptr) == 0: + idx_m.set_str(key_ptr, mod_ptr) + if idx_o.__contains__(key_ptr) == 0: + if original_name is not None and string.strcmp(original_name, local_name) != 0 \ + and string.strlen(original_name) > 0: + idx_o.set_str(key_ptr, mod_ptr + mod_len + 1) + else: + # 首次出现即无别名(或别名为空)→ 该名查询返回 None(与逐字符扫描一致) + idx_o.set_str(key_ptr, None) + self.CurFromImportsPtr = new_ptr + self.CurFromImportsMods = idx_m + self.CurFromImportsOrigs = idx_o + # ============================================================ # 添加已导入模块名 # ============================================================ @@ -67,6 +189,8 @@ class ImportsGenMixin: buf: str = pool.alloc(nlen + 1) if buf is not None: string.strcpy(buf, name) + # 索引:首个模块名(key 指向 buf 内部) + self._index_imported_module(None, buf, buf) return buf return None else: @@ -78,6 +202,8 @@ class ImportsGenMixin: string.strcpy(buf2, imported_modules) buf2[old_len] = ' ' string.strcpy(buf2 + old_len + 1, name) + # 索引:复用旧串索引并追加新名字(key 指向 buf2 内的 name 段) + self._index_imported_module(imported_modules, buf2, buf2 + old_len + 1) return buf2 return imported_modules @@ -88,6 +214,14 @@ class ImportsGenMixin: """检查模块是否已导入(词边界精确匹配,避免子串误匹配)""" if name is None or imported_modules is None: return 0 + # [索引快路径] 传入串即当前索引绑定的串(trans 字段的常态)→ O(名字长) 哈希查; + # 指针不匹配(stub 期 malloc 缓冲等)→ 回退下方逐字符扫描,语义不变。 + if imported_modules == self.CurImportedModulesPtr \ + and self.CurImportedModulesNames is not None: + if self.CurImportedModulesNames.__contains__(name) != 0: + return 1 + # 索引未命中:绝不据此返回 0(索引不完整会误判"未导入"), + # 落到下方原逐字符扫描按真实串内容判定(正确性优先 | 性能次之)。 name_len: sizet = string.strlen(name) cur: str = imported_modules total_len: sizet = string.strlen(imported_modules) @@ -175,6 +309,8 @@ class ImportsGenMixin: else: viperlib.snprintf(entry, 256, "%s:%s", local_name, module_name) if from_imports is None: + # 索引:首条 entry(local 从 entry 首字节开始,entry_off=0) + self._index_from_import(None, entry, 0, local_name, module_name, original_name) return entry else: old_len: sizet = string.strlen(from_imports) @@ -185,6 +321,9 @@ class ImportsGenMixin: string.strcpy(buf, from_imports) buf[old_len] = ' ' string.strcpy(buf + old_len + 1, entry) + # 索引:复用旧串索引并追加(entry 从 buf[old_len+1] 开始) + self._index_from_import(from_imports, buf, old_len + 1, + local_name, module_name, original_name) return buf return from_imports @@ -199,6 +338,15 @@ class ImportsGenMixin: """查找 from-import 别名对应的原始函数名,返回 None=无别名或未找到""" if local_name is None or from_imports is None: return None + # [索引快路径] 传入串即当前索引绑定的串 → O(名字长) 哈希查;否则回退扫描。 + # orig 表:名存在但存 None = 首次出现即无别名 → 返回 None(与扫描一致)。 + if from_imports == self.CurFromImportsPtr and self.CurFromImportsOrigs is not None: + if self.CurFromImportsOrigs.__contains__(local_name) != 0: + v_o: t.CPtr = self.CurFromImportsOrigs.__getitem__(local_name) + if v_o is not None: + return (str | t.CPtr)(v_o) + # 索引未命中 | 值为 None(首个即无别名):绝不据此直接返回 None—— + # 真实串可能仍含该名,落到下方原逐字符扫描按串内容判定(正确性优先)。 name_len: sizet = string.strlen(local_name) cur: str = from_imports ci: sizet = 0 @@ -273,6 +421,22 @@ class ImportsGenMixin: """ if name is None or from_imports is None: return None + # [索引快路径] 传入串即当前索引绑定的串 → O(名字长) 哈希查;否则回退扫描。 + # name 恰为 "*" 时与扫描一致:不做精确匹配,直接走 star 回退。 + if from_imports == self.CurFromImportsPtr and self.CurFromImportsMods is not None: + name_is_star: int = 0 + if string.strlen(name) == 1 and name[0] == '*': + name_is_star = 1 + if name_is_star == 0: + v_f: t.CPtr = self.CurFromImportsMods.__getitem__(name) + if v_f is not None: + return (str | t.CPtr)(v_f) + if allow_star_fallback != 0: + sv_f: t.CPtr = self.CurFromImportsMods.__getitem__("*") + if sv_f is not None: + return (str | t.CPtr)(sv_f) + # 索引精确名 | star 均未命中:绝不据此直接返回 None(索引不完整会漏报 + # "名已 import",_op_head NameError 即此类),落到下方原逐字符扫描按串内容判定。 name_len: sizet = string.strlen(name) cur: str = from_imports ci: sizet = 0 @@ -527,6 +691,14 @@ class ImportsGenMixin: # ============================================================ def set_current_file(self, file_path: str) -> None: """设置当前正在编译的文件名(与 HandlesType.set_current_file 同步调用)""" + # 文件级失效:索引绑定的是上一文件 arena 内的串指针,本文件 reset 后该 + # 地址可能被新分配复用(如 _merge_template_imports 的合并串)→ 必须解绑, + # 否则可能命中陈旧索引返回错误结果。本文件的索引由后续 add_* 重新建立。 + self.CurImportedModulesPtr = None + self.CurImportedModulesNames = None + self.CurFromImportsPtr = None + self.CurFromImportsMods = None + self.CurFromImportsOrigs = None self.Gen.TypeGenResolve.set_current_file(file_path) # ============================================================ @@ -834,4 +1006,10 @@ class ImportsGenMixin: self.ReexportFuncs = None self.ReexportSrcs = None self.ReexportCount = 0 + # 导入查找索引字段(索引表由 add_* 增量创建,PermPool 分配) + self.CurImportedModulesPtr = None + self.CurImportedModulesNames = None + self.CurFromImportsPtr = None + self.CurFromImportsMods = None + self.CurFromImportsOrigs = None return 0 diff --git a/App/lib/core/Gen/MatchGenMixin.vp b/App/lib/core/Gen/MatchGenMixin.vp index d4a7f8a..a477a0f 100644 --- a/App/lib/core/Gen/MatchGenMixin.vp +++ b/App/lib/core/Gen/MatchGenMixin.vp @@ -845,6 +845,18 @@ class MatchGenMixin: first_check: int = -1 i: int = 0 while i < case_count: + # 容量防线(与 _translate_match_simple / match-list 路径的 + # `collected < MAX_MATCH_CASES` 守卫对齐):默认 case(case _) + # 分支此前**不校验** collected 就写 check_arr/body_arr/def_arr/ + # nb_arr/guard_arr[collected],case 数超过 MAX_MATCH_CASES 时 + # 越界写穿 pool 相邻块(可破坏相邻空闲块的 free-list next 指针)。 + # 不静默丢弃 case(会产出漏 case 的错误代码),直接 fail-fast。 + if collected >= MAX_MATCH_CASES: + self.Gen.TypeGenErr.fatal_error( + node, + f"match case 数量超过上限 MAX_MATCH_CASES={MAX_MATCH_CASES}", + "OverflowError") + break case_node: ast.AST | t.CPtr = cases_list.get(i) if case_node is None or case_node.kind() != ast.ASTKind.MatchCase: i = i + 1 diff --git a/App/lib/core/Gen/Type/TypeGenBase.vp b/App/lib/core/Gen/Type/TypeGenBase.vp index a8e359f..307c759 100644 --- a/App/lib/core/Gen/Type/TypeGenBase.vp +++ b/App/lib/core/Gen/Type/TypeGenBase.vp @@ -6,6 +6,7 @@ from stdint import * import ast import llvmlite import memhub +import hashtable import string import viperlib import stdio @@ -93,7 +94,17 @@ class TypeGenBase: # 用 malloc 而非 pool list——pool 每文件 reset_to 后 list 悬空(clear_ctypedefs # 未覆盖的路径读到已释放内存 → strcmp 崩溃,1.log 根因);malloc 跨 pool # 生命周期存活,clear_ctypedefs 释放置空。 - ImportAliasBuf: bytes + ImportAliasBuf: bytes + + # --- 库 typedef 按模块一次性门禁(_lookup_library_typedef 性能优化) --- + # 库模块 typedef 缓存(PermPool 永久分配,跨文件/Phase 存活): + # LibTypedefScannedMods: key=库模块名 → 已扫描标记(命中=该模块源已读并入缓存); + # LibTypedefTypeMap: key="模块名|typedef 名" → 源中原始类型表达式。 + # 原实现每次查询都读 64KB 库源全文扫描(每个 Name 注解兜底连试 stdint/ + # w32.win32base 两次)→ 单文件 translate 7–12s 根因。缓存按模块名(内容) + # 索引,库源单次编译内不变,故命中结果与逐次读源等价。| 不用全局裸表变量。 + LibTypedefScannedMods: hashtable.HashTable | t.CPtr + LibTypedefTypeMap: hashtable.HashTable | t.CPtr # --- 常量整数表达式计算成功标志(原 _g_const_eval_ok) --- ConstEvalOk: int @@ -137,7 +148,10 @@ class TypeGenBase: self.LastBuiltinsTypedefResult = None self.LastBuiltinsTypedefNameBuf = None self.LastBuiltinsTypedefResultBuf = None - self.ImportAliasBuf = None + self.ImportAliasBuf = None + # 库 typedef 持久缓存字段(不随 clear_ctypedefs 复位 → 跨文件/Phase 存活) + self.LibTypedefScannedMods = None + self.LibTypedefTypeMap = None self.ConstEvalOk = 0 self.CurrentFile = None self.Specs = None diff --git a/App/lib/core/Gen/Type/TypeGenCDef.vp b/App/lib/core/Gen/Type/TypeGenCDef.vp index e7eae11..e9f543d 100644 --- a/App/lib/core/Gen/Type/TypeGenCDef.vp +++ b/App/lib/core/Gen/Type/TypeGenCDef.vp @@ -34,13 +34,27 @@ class TypeGenCDef(TypeGenBase.TypeGenBase): sys.maxsize = 9223372036854775807)必须全精度保存,否则 %d 32 位截断 成 -1(0xFFFFFFFF)→ 读回 4294967295(SysLibTest maxsize FAIL 根因)。 0xFFFFFFFF 仍存为 "4294967295",常量生成端 const_int32 内部截断回 -1。 + + 名称深拷贝到 pool(与 register_xmod_cdefine 对 key 深拷贝同一教训): + 调用方传入的 name 可能指向临时缓冲——VpiReader 注入 .vpi 时 Name.id 是 + 零拷贝指向 vpi_buf(见 ParseVpiIntoModule 的 token 零拷贝注释),而 + vpi_buf 在注入函数末尾即 stdlib.free。只存指针会让 CDefineNames 持有 + 悬空名:之后 lookup 的 strcmp 若命中被回收/复用内存里恰好出现的同名文本, + 就会返回别的条目的值(Phase1.SHA1_LEN=17 被读成 0 的根因)。 """ + if name is None: + return if self.CDefineNames is None: self.CDefineNames = list[str](pool, 64) self.CDefineValues = list[str](pool, 64) self.CDefineDoubleFlags = list[int](pool, 64) self.CDefineStrFlags = list[int](pool, 64) - self.CDefineNames.append(name) + name_copy: str = pool.alloc(string.strlen(name) + 1) + if name_copy is None: + self.Gen.TypeGenErr.fatal_error(None, f"CDefine '{name}' 名称缓冲区分配失败", "MemoryError") + return + string.strcpy(name_copy, name) + self.CDefineNames.append(name_copy) val_buf: str = pool.alloc(32) if val_buf is not None: viperlib.snprintf(val_buf, 32, "%lld", value) @@ -60,13 +74,23 @@ class TypeGenCDef(TypeGenBase.TypeGenBase): """注册浮点 CDefine 编译期常量(如 U_M_PI: t.CDefine = 3.14159...) 值以 %f 文本存储并标记浮点,供 translate_value 返回浮点常量。 + + 名称同样深拷贝到 pool(理由见 register_cdefine_constant:注入 .vpi 时 + Name.id 指向的 vpi_buf 会被释放,只存指针会留下悬空名)。 """ + if name is None: + return if self.CDefineNames is None: self.CDefineNames = list[str](pool, 64) self.CDefineValues = list[str](pool, 64) self.CDefineDoubleFlags = list[int](pool, 64) self.CDefineStrFlags = list[int](pool, 64) - self.CDefineNames.append(name) + name_copy_d: str = pool.alloc(string.strlen(name) + 1) + if name_copy_d is None: + self.Gen.TypeGenErr.fatal_error(None, f"CDefine 浮点 '{name}' 名称缓冲区分配失败", "MemoryError") + return + string.strcpy(name_copy_d, name) + self.CDefineNames.append(name_copy_d) val_buf: str = pool.alloc(32) if val_buf is not None: # 用 CRT stdio.snprintf 而非 viperlib.snprintf:viperlib 自实现格式化器的 @@ -89,7 +113,8 @@ class TypeGenCDef(TypeGenBase.TypeGenBase): """注册字符串 CDefine 编译期常量(如 ZLIB_VERSION: t.CDefine = "1.3.2") 字符串值深拷贝到 pool(AST 节点的 str_val 可能被 pool 操作覆写, - 与浮点 CDefine 的恢复同理)。 + 与浮点 CDefine 的恢复同理)。名称同样深拷贝(理由见 + register_cdefine_constant:注入 .vpi 的 Name.id 指向会被释放的 vpi_buf)。 """ if pool is None or name is None: return @@ -101,7 +126,12 @@ class TypeGenCDef(TypeGenBase.TypeGenBase): self.CDefineValues = list[str](pool, 64) self.CDefineDoubleFlags = list[int](pool, 64) self.CDefineStrFlags = list[int](pool, 64) - self.CDefineNames.append(name) + ncopy_s: str = pool.alloc(string.strlen(name) + 1) + if ncopy_s is None: + self.Gen.TypeGenErr.fatal_error(None, f"CDefine 字符串 '{name}' 名称缓冲区分配失败", "MemoryError") + return + string.strcpy(ncopy_s, name) + self.CDefineNames.append(ncopy_s) vlen: sizet = string.strlen(value) vcopy: str = pool.alloc(vlen + 1) if vcopy is None: diff --git a/App/lib/core/Gen/Type/TypeGenResolve.vp b/App/lib/core/Gen/Type/TypeGenResolve.vp index 3cfeb10..e1ae1a6 100644 --- a/App/lib/core/Gen/Type/TypeGenResolve.vp +++ b/App/lib/core/Gen/Type/TypeGenResolve.vp @@ -1,4 +1,5 @@ import lib.core.Gen.GenTypes as GenTypes +import lib.core.Gen.GenApi as GenApi import lib.core.Gen.Generator as Generator import lib.core.Gen.Type.TypeGenCType as TypeGenCType import lib.core.Gen.Type.TypeGenMixin as TypeGenMixin @@ -7,6 +8,7 @@ from stdint import * import ast import llvmlite import memhub +import hashtable import string import viperlib import stdio @@ -1054,6 +1056,83 @@ class TypeGenResolve(TypeGenBase.TypeGenBase): if b_final_r is not None: return b_final_r return None + def _register_lib_typedef_cache(self, mod: str, name_src: str, + name_len: sizet, type_str: str) -> None: + """把库模块源中的一条 typedef 写入持久缓存(PermPool,同名首次为准) + + key = "模块名|typedef 名"(模块名/typedef 名均为标识符,不含 '|')。 + 首次扫描某模块时对该模块全部 typedef 调用一次;结果跨文件/Phase 存活, + 后续同模块查询只查表,不再读库源。 + """ + if mod is None or name_src is None or type_str is None or name_len == 0: + return + perm: rowptr[memhub.MemBuddy] = CompilerFactory.factory.PermPool + if perm is None: + return + if self.LibTypedefTypeMap is None: + self.LibTypedefTypeMap = hashtable.HashTable(perm) + if self.LibTypedefTypeMap is None: + return + mod_len: sizet = string.strlen(mod) + key_len: sizet = mod_len + 1 + name_len + key_buf: str = perm.alloc(key_len + 1) + if key_buf is None: + return + string.strncpy(key_buf, mod, mod_len) + key_buf[mod_len] = '|' + string.strncpy(key_buf + mod_len + 1, name_src, name_len) + key_buf[key_len] = '\0' + # 同名首次为准(与逐次读源命中第一处声明的语义一致) + if self.LibTypedefTypeMap.__contains__(key_buf) != 0: + return + tlen: sizet = string.strlen(type_str) + type_copy: str = perm.alloc(tlen + 1) + if type_copy is None: + return + string.strcpy(type_copy, type_str) + self.LibTypedefTypeMap.set_str(key_buf, type_copy) + def _mark_lib_module_scanned(self, mod: str) -> None: + """置库模块「已扫描」标记(PermPool 持久):其后同模块查询只查缓存表""" + if mod is None: + return + # 缓存表未建成(PermPool 分配失败)时不得置标记——否则后续查询只查空表 + # 会误返回 None(改变语义);此时不标记、留待下次重试读源。 + if self.LibTypedefTypeMap is None: + return + perm: rowptr[memhub.MemBuddy] = CompilerFactory.factory.PermPool + if perm is None: + return + if self.LibTypedefScannedMods is None: + self.LibTypedefScannedMods = hashtable.HashTable(perm) + if self.LibTypedefScannedMods is None: + return + if self.LibTypedefScannedMods.__contains__(mod) != 0: + return + mlen: sizet = string.strlen(mod) + mcopy: str = perm.alloc(mlen + 1) + if mcopy is None: + return + string.strcpy(mcopy, mod) + self.LibTypedefScannedMods.set_str(mcopy, mcopy) + def _lookup_lib_typedef_cache(self, mod: str, attr: str) -> str: + """查持久缓存表:命中返回该模块源中的原始类型表达式,未命中返回 None""" + if self.LibTypedefTypeMap is None or mod is None or attr is None: + return None + mod_len: sizet = string.strlen(mod) + attr_len: sizet = string.strlen(attr) + key_len: sizet = mod_len + 1 + attr_len + key_buf: bytes = stdlib.malloc(key_len + 1) + if key_buf is None: + return None + string.strncpy(key_buf, mod, mod_len) + key_buf[mod_len] = '|' + string.strncpy(key_buf + mod_len + 1, attr, attr_len) + key_buf[key_len] = '\0' + v: t.CPtr = self.LibTypedefTypeMap.__getitem__(key_buf) + stdlib.free(key_buf) + if v is None: + return None + return (str | t.CPtr)(v) def _lookup_library_typedef(self, pool: rowptr[memhub.MemBuddy], mod: str, attr: str) -> str: """查库模块源文件中 "attr: t.CTypedef = TYPE" 并注册,返回 TYPE 或 None @@ -1062,6 +1141,11 @@ class TypeGenResolve(TypeGenBase.TypeGenBase): `size_t: t.CTypedef = stdint.size_t` 的 stdint.size_t 部分): 直接读模块源扫描 typedef(权威定义),miss 不查表——表内可能是 builtins 的间接引用(stdint.size_t → 自身)会自引用死循环。 + + [按模块一次性门禁] 首次遇到某库模块:读一次源、把该模块全部 typedef + 写入持久缓存(PermPool)并置「已扫描」标记;之后同一模块的查询只查 + 缓存表,不再做文件 I/O 与全文扫描。缓存按模块名(内容)索引,库源在 + 单次编译内不变,故命中值与逐次读源等价。| miss 仍返回 None。 """ if mod is None or attr is None: return None @@ -1071,6 +1155,14 @@ class TypeGenResolve(TypeGenBase.TypeGenBase): m_real: str = self._resolve_import_alias(mod) if m_real is None: m_real = mod + # [门禁命中] 该库模块源已扫描过 → 只查持久缓存表,不再读文件; + # 命中值仍注册进当前文件 CTypedef 表(保持原实现每次读源命中的副作用)。 + if self.LibTypedefScannedMods is not None: + if self.LibTypedefScannedMods.__contains__(m_real) != 0: + ts_hit: str = self._lookup_lib_typedef_cache(m_real, attr) + if ts_hit is not None: + self.Gen.TypeGenCType.register_ctypedef(pool, attr, ts_hit) + return ts_hit m_sha1: str = StubMergerStore.LookupSha1ByModName(m_real) m_path: str = self._build_lib_source_path(m_real, m_sha1) if m_path is None: @@ -1097,7 +1189,9 @@ class TypeGenResolve(TypeGenBase.TypeGenBase): m_i: sizet = 0 m_ls: sizet = 0 m_res: str = None - while m_i <= m_total and m_res is None: + # 全文件扫描(不再在首次命中处提前退出):既为该模块建立完整持久缓存, + # 又用与原实现相同的比较取 attr 的首次命中值(保证首次调用返回值不变)。 + while m_i <= m_total: if m_i == m_total or m_buf[m_i] == '\n': m_line_len: sizet = m_i - m_ls if m_line_len > 0: @@ -1115,39 +1209,46 @@ class TypeGenResolve(TypeGenBase.TypeGenBase): m_nl: sizet = m_j while m_nl > 0 and m_buf[m_ls + m_nl - 1] == ' ': m_nl -= 1 - if m_nl == attr_len: - m_nm_ok: int = 1 - m_ki: sizet = 0 - while m_ki < attr_len: - if m_buf[m_ls + m_ki] != attr[m_ki]: - m_nm_ok = 0 - break - m_ki += 1 - if m_nm_ok == 1: - m_rest: str = m_buf + m_ls + m_j + 1 - if string.strstr(m_rest, "t.CTypedef") is not None: - m_eq: str = string.strstr(m_rest, "=") - if m_eq is not None: - # '=' 相对行首偏移;值段严格限定在行尾 - # (m_line_len)内,防止跨行吞换行+后续内容 - m_eq_off: sizet = sizet(uint64(m_eq) - uint64(m_buf)) - m_ls - m_vs: sizet = m_eq_off + 1 - while m_vs < m_line_len and m_buf[m_ls + m_vs] == ' ': - m_vs += 1 - m_ve: sizet = m_line_len - while m_ve > m_vs and (m_buf[m_ls + m_ve - 1] == ' ' - or m_buf[m_ls + m_ve - 1] == '\r'): - m_ve -= 1 - if m_ve > m_vs: - m_vlen: sizet = m_ve - m_vs - m_tb: str = pool.alloc(m_vlen + 1) - if m_tb is not None: - string.strncpy(m_tb, m_buf + m_ls + m_vs, m_vlen) - m_tb[m_vlen] = '\0' - self.Gen.TypeGenCType.register_ctypedef(pool, attr, m_tb) + m_rest: str = m_buf + m_ls + m_j + 1 + if string.strstr(m_rest, "t.CTypedef") is not None: + m_eq: str = string.strstr(m_rest, "=") + if m_eq is not None: + # '=' 相对行首偏移;值段严格限定在行尾 + # (m_line_len)内,防止跨行吞换行+后续内容 + m_eq_off: sizet = sizet(uint64(m_eq) - uint64(m_buf)) - m_ls + m_vs: sizet = m_eq_off + 1 + while m_vs < m_line_len and m_buf[m_ls + m_vs] == ' ': + m_vs += 1 + m_ve: sizet = m_line_len + while m_ve > m_vs and (m_buf[m_ls + m_ve - 1] == ' ' + or m_buf[m_ls + m_ve - 1] == '\r'): + m_ve -= 1 + if m_ve > m_vs: + m_vlen: sizet = m_ve - m_vs + m_tb: str = pool.alloc(m_vlen + 1) + if m_tb is not None: + string.strncpy(m_tb, m_buf + m_ls + m_vs, m_vlen) + m_tb[m_vlen] = '\0' + # 写入持久缓存(首次遇到的名字为准) + self._register_lib_typedef_cache( + m_real, m_buf + m_ls, m_nl, m_tb) + # 原实现语义:attr 的首次匹配值 + if m_res is None and m_nl == attr_len: + m_nm_ok: int = 1 + m_ki: sizet = 0 + while m_ki < attr_len: + if m_buf[m_ls + m_ki] != attr[m_ki]: + m_nm_ok = 0 + break + m_ki += 1 + if m_nm_ok == 1: m_res = m_tb m_ls = m_i + 1 m_i += 1 + # 置该模块已扫描标记:同模块后续查询只查持久缓存表,不再重复读源 + self._mark_lib_module_scanned(m_real) + if m_res is not None: + self.Gen.TypeGenCType.register_ctypedef(pool, attr, m_res) stdlib.free(m_buf) return m_res def _expand_typedef_ref(self, pool: rowptr[memhub.MemBuddy], @@ -1625,15 +1726,15 @@ class TypeGenResolve(TypeGenBase.TypeGenBase): return self.extract_class_name_from_annotation( sub.slice, imported_modules, pool, from_imports) return None - # [D25 根因修复] 递归取 base 时必须把 `from_imports` 一并下传: - # 此前这里丢掉了第 4 个参数,导致同一份注解在「派发名(本函数)/ - # 查表键(_resolve_generic_subscript_ty)/ 注册键(_specialize_generic_class)」 - # 三处算出的 mangled 名可能不同 —— 例如 `list[ast.AST | t.CPtr]` 的 - # 元素段在带/不带 from_imports 时规范化结果不一致,进而出现 - # 「alloca 是裸 list*、pass1 按 list[ast.ASTptr] 解释、pass2 按 - # list[ast.ClassDefptr] 解释」的分裂(MatchGenMixin._handle_renum_match - # 崩溃根因,见 Debugger/_Type.md D25)。其余调用点(1625、1650 等)均已传。 - base_name: str = self.extract_class_name_from_annotation( + # [D25 根因修复] 递归取 base 时必须把 `from_imports` 一并下传: + # 此前这里丢掉了第 4 个参数,导致同一份注解在「派发名(本函数)/ + # 查表键(_resolve_generic_subscript_ty)/ 注册键(_specialize_generic_class)」 + # 三处算出的 mangled 名可能不同 —— 例如 `list[ast.AST | t.CPtr]` 的 + # 元素段在带/不带 from_imports 时规范化结果不一致,进而出现 + # 「alloca 是裸 list*、pass1 按 list[ast.ASTptr] 解释、pass2 按 + # list[ast.ClassDefptr] 解释」的分裂(MatchGenMixin._handle_renum_match + # 崩溃根因,见 Debugger/_Type.md D25)。其余调用点(1625、1650 等)均已传。 + base_name: str = self.extract_class_name_from_annotation( sub.value, imported_modules, pool, from_imports) if base_name is None: return None @@ -2202,8 +2303,8 @@ class TypeGenResolve(TypeGenBase.TypeGenBase): # SHA1 前缀形态优先用完整名 "{sha1}.{Class}" 精确命中定义侧 # struct(get_struct_type 类名回退可能命中跨模块同名类) full_attr_ty: llvmlite.LLVMType | t.CPtr = None - # val_name.id 是模块名段:恰为 16 位小写 hex 才是 SHA1 别名 - #(判定走唯一真源 GenApi.is_sha1_hex16,C10 收敛)。 + # val_name.id 是模块名段:恰为 16 位小写 hex 才是 SHA1 别名 + #(判定走唯一真源 GenApi.is_sha1_hex16,C10 收敛)。 if is_builtin_attr == 0 and GenApi.is_sha1_hex16(val_name.id) != 0: full_q: str = self._concat_sha1_class(pool, val_name.id, at.attr) if full_q is not None: