from __future__ import annotations from typing import Any import vast as ast import os import re import llvmlite.ir as ir from lib.includes import t as _t from lib.includes.t import CTypeRegistry from lib.core.Handles.HandlesBase import CTypeInfo as _CTypeInfo, BuiltinTypeMap from lib.core.SymbolUtils import EnumIntWidth, _ENUM_DEFAULT_INT_WIDTH from lib.constants.config import DEFAULT_HOTSPOT_CALLEES # `_current_node_info` 的形态:"line N[, col C][ (额外)]"(由 _set_node_info 生成)。 # _get_node_info 用它把标签行号还原成磁盘行号(见 _DiskLineno)。 _LINE_INFO_RE: re.Pattern[str] = re.compile(r'line (\d+)(.*)\Z', re.S) class VaArgInstruction(ir.Instruction): def descr(self, buf: list[str]) -> None: ptr: ir.Value = self.operands[0] buf.append("va_arg {0} {1}, {2}\n".format( ptr.type, ptr.get_reference(), self.type)) def RequirePointerOperand(Value: Any, OpName: str, Gen: Any = None, Detail: str = '', RequireConcretePointee: bool = False) -> None: """IR 边界守卫:指针类指令(GEP/load/store)的**指针操作数**必须是指针类型。 为什么需要本守卫:llvmlite 的 `IRBuilder.gep` **不做任何类型校验**, `GEPInstr.__init__` 直接读 `ptr.type.is_opaque`;`LoadInstr.__init__` 同族 亦同(见 llvmlite/ir/instructions.py:440/558,builder.py:777)。因此上游一旦 把一个**非指针值**(典型:i32)当指针传进来,抛出的就是裸 `AttributeError: 'IntType' object has no attribute 'is_opaque'` —— 报文里既没有指令名、没有操作数、也没有当前语句,无法定位发射点 (现场:Phase2 翻译 ViperC/App/lib/core/Gen/TranslatorGenMixin.vp 时报错 只带 713/787/788 三条语句位置,剩下的全被 llvmlite 内部吞掉)。 本函数在 IR 边界先行判定,**不做任何兜底转换**:操作数不是指针立即抛 FailFastError,报文给出指令名、操作数类型、操作数名、GEP 的 `name=`(即发射 点标识)与当前节点位置,让"上游把非指针值当指针使用"的根因一眼可见。 合法路径下(操作数确实是指针)零副作用,只多一次 isinstance 判定。 """ if isinstance(getattr(Value, 'type', None), ir.PointerType): if RequireConcretePointee: # GEP 的第二层守卫:被指结构体**未具化**(opaque,elements is None)。 # llvmlite 的 GEPInstr 走 `ptr.type.gep(i)` → `IdentifiedStructType.gep` # → `self.elements[i.constant]`,对 opaque pointee 抛裸 TypeError # ('NoneType' object is not subscriptable;现场:astaux.vp `Keyword.__init__` # 的 `self.arg = ...`)。typed pointer 下 GEP 必须能在编译期算出元素类型, # 故"指向 opaque 结构体的 GEP"**无任何合法场景** ⇒ 直接 fail-fast 报出 # 结构体名与 GEP 名,暴露"同名 struct 两份实例(一份已具化、一份仍 opaque) # / body 从未 set_body"的根因。 _Pointee: Any = getattr(Value.type, 'pointee', None) if isinstance(_Pointee, ir.IdentifiedStructType) and _Pointee.elements is None: from lib.core.LLVMCG.FuncGen import FailFastError _MsgP: str = ( f"{OpName} 的基址指向**未具化**的具名结构体:基址类型={Value.type}," f"被指结构体={getattr(_Pointee, 'name', '?')!r}(opaque,elements 未设置)" + (f",{Detail}" if Detail else "") + "\n llvmlite 会在此处内部对 opaque pointee 计算元素类型并抛裸 TypeError" "('NoneType' object is not subscriptable);本守卫在 IR 边界 fail-fast," "暴露\"该类存在另一份已具化实例(偏移与 GEP 未同源)\"或" "\"该类 body 从未 set_body\"的根因(不做兜底)") if Gen is not None and hasattr(Gen, '_get_node_info'): _MsgP += "\n " + Gen._get_node_info() # 同短名 struct 实例清单(key(IR 名)=槽数):**直接**区分两种根因—— # 清单里存在已具化项 ⇒ "偏移与 GEP 未同源"(应 bitcast 到该项,见 # HandlesAssign 的孪生体搜索);全是 opaque ⇒ "该类 body 从未 set_body" # (往上查 StructGen 的 set_body 为何没跑)。不必再靠猜。 if Gen is not None and isinstance(getattr(Gen, 'structs', None), dict): _PShort: str = '' try: _PShort = Gen._extract_short_name(getattr(_Pointee, 'name', '') or '') except Exception: _PShort = '' _Kin: list[str] = [] for _k_n, _k_v in list(Gen.structs.items()): if not isinstance(_k_v, ir.IdentifiedStructType): continue try: _k_s: str = Gen._extract_short_name(getattr(_k_v, 'name', '') or '') except Exception: _k_s = '' if _PShort and _k_s != _PShort: continue _k_cnt: Any = (len(_k_v.elements) if _k_v.elements is not None else 'opaque') _Kin.append(f"{_k_n}({getattr(_k_v, 'name', '?')})={_k_cnt}") if _Kin: _MsgP += "\n 同短名 struct 实例(key(IR 名)=槽数): " + ", ".join(_Kin) # TPC 发射点(由内向外,最内 3 帧):行号只能指到**语句**,无法区分 # 同一语句里是哪个 GEP 点(现场 astaux.vp:165 `self.arg = _copy_str(...)` # 在读写两侧共有多个候选发射点,逐个读代码无法判定)。取栈中位于本仓 # lib/、且不在本文件(守卫自身)的最内 3 帧,报出 文件:行(函数)。 try: import traceback as _tb _Sites: list[str] = [] for _fr in reversed(_tb.extract_stack()): _fn: str = (getattr(_fr, 'filename', '') or '').replace('\\', '/') if '/lib/' not in _fn or _fn.endswith('/LLVMCG/BaseGen.py'): continue _Sites.append(f"{_fn.split('/lib/', 1)[-1]}:{getattr(_fr, 'lineno', 0)}" f"({getattr(_fr, 'name', '?')})") if len(_Sites) >= 3: break if _Sites: _MsgP += "\n TPC 发射点(由内向外): " + " ← ".join(_Sites) except Exception: pass raise FailFastError(_MsgP) return # 失败路径才导入:热路径(每次 gep/load/store)不承担导入开销,也避免循环导入。 from lib.core.LLVMCG.FuncGen import FailFastError _TypeStr: str = str(getattr(Value, 'type', None)) _ValName: Any = getattr(Value, 'name', '') _Msg: str = f"{OpName} 的指针操作数不是指针类型:操作数类型={_TypeStr}" if _ValName: _Msg += f",操作数名='{_ValName}'" if Detail: _Msg += f",{Detail}" _Msg += ("\n llvmlite 只会在此处内部访问 `ptr.type.is_opaque`,抛裸 AttributeError" "('IntType' object has no attribute 'is_opaque');" "本守卫在 IR 边界 fail-fast,暴露上游把非指针值当指针使用的根因" "(不做兜底转换)") if Gen is not None and hasattr(Gen, '_get_node_info'): _Msg += "\n " + Gen._get_node_info() raise FailFastError(_Msg) _TPC_FuncInitGuardInstalled: bool = False def InstallFuncCreateGuard() -> None: """给 llvmlite 的 `ir.Function.__init__` 装一次守卫:创建中途失败**不留悬空名字**。 背景(llvmlite 的构造不是事务性的):`Function.__init__` 的顺序是 super().__init__ → `_set_name` → `module.scope.register(name)` 【名字占用】 → 逐形参 `Argument(...)` / `ReturnValue(...)`(内部 `assert isinstance(typ, types.Type)`) → `parent.add_global(self)` 【进入 globals】 中间任一步抛错都不会回滚 scope ⇒ globals 查无、scope 却已占用的"悬空名字"。此后同名 创建只会得到 llvmlite 的裸 DuplicatedNameError(报文只有名字),或本仓 `_CreateOrReuseFuncDecl` 的"名字悬空占用"fail-fast —— 两者都只能指向"上一处失败被 吞了",而**那一处**的调用点与异常早已丢失(现场:50741ad4b4b0934d.LLVMTypeCore.__init__, 上游首建失败被 HandlesImports 的节点级 `except Exception` 吞成一行 WARN)。 本守卫做两件事(均在**失败路径**上,合法创建零额外开销): 1. 回滚本次注册的名字(`scope._useset.discard`)——补上 llvmlite 缺的那半截事务, 模块状态可重试,不再积累悬空名字; 2. 抛 FailFastError,报文给出函数名、签名、原始异常,以及**发起创建的 TPC 调用点** (栈上第一个不在 llvmlite 包内、也不是本包装函数的帧)。 `DuplicatedNameError`(名字已被占用)原样上抛:那是各调用点**有意**处理的正常分支 (如 `_replace_function_decl` 的"已存在则复用"),不属于"创建中途失败"语义。 """ global _TPC_FuncInitGuardInstalled if _TPC_FuncInitGuardInstalled: return _TPC_FuncInitGuardInstalled = True _OrigInit: Any = ir.Function.__init__ def _GuardedFuncInit(self: Any, module: Any, ftype: Any, name: Any) -> None: try: _OrigInit(self, module, ftype, name) return except Exception as _e: from llvmlite.ir._utils import DuplicatedNameError as _Dup if isinstance(_e, _Dup): raise # 名字已在 `_set_name` 阶段注册(进入本分支即证明 register 成功)⇒ 回滚 _scope: Any = getattr(module, 'scope', None) _useset: Any = getattr(_scope, '_useset', None) if _useset is not None: _useset.discard(name) _useset.discard(str(name)) import traceback as _tb _site: str = '(栈中未找到非 llvmlite 帧)' for _fr in reversed(_tb.extract_stack()): _fn: str = _fr.filename.replace('\\', '/') if 'site-packages/llvmlite' in _fn or _fr.name == '_GuardedFuncInit': continue _site = f"{_fr.filename}:{_fr.lineno} (in {_fr.name})" break from lib.core.LLVMCG.FuncGen import FailFastError raise FailFastError( f"函数创建失败(已回滚悬空名字):'{name}'" f"\n · 本次签名: {ftype}" f"\n · 原始异常: {type(_e).__name__}: {_e}" f"\n · 创建调用点: {_site}" f"\n 成因:llvmlite 的 Function 构造不是事务性的——名字在 `_set_name` 就进了" f" module.scope,之后逐个建 Argument/ReturnValue 时才做类型断言,最后才" f" add_global。任何形参 / 返回类型不是 llvmlite Type(典型 None:注解或 stub" f" declare 的类型没解析出来)都会在「已注册、未进 globals」之间失败 ⇒ 留下" f"悬空名字,后续同名创建只报裸 DuplicatedNameError 或「名字悬空占用」。" f"\n 本守卫已回滚名字(模块状态可重试);请修上面那一行的签名来源。" ) from _e ir.Function.__init__ = _GuardedFuncInit InstallFuncCreateGuard() class _GuardIrBuilder(ir.IRBuilder): """默认 IR 构建器:在 GEP/load/store 处对**指针操作数**做类型守卫。 与 `_HotspotSiteBuilder`(--hotspot-sites 专用)不同,本类**无条件启用** (BaseGenMixin._NewIrBuilder 返回它):只做"操作数不是指针 → fail-fast" 一件事,合法 IR 的生成结果与原生 ir.IRBuilder 完全一致。 为什么必须无条件启用:`IRBuilder.gep` 无类型校验(见 RequirePointerOperand 的说明),而 TPC 侧全部 GEP 都走 `Gen.builder.gep`(无一处传 source_etype, 故必走 GEPInstr 的 `ptr.type.is_opaque` 分支);load/store 虽在 llvmlite 内 有 isinstance 守卫,但报错不带源码位置。此处在唯一入口补上判定与位置信息。 """ def __init__(self, block: ir.Block | None = None, gen: Any = None) -> None: super().__init__(block) self._GuardGen: Any = gen def gep(self, ptr: Any, indices: Any, inbounds: bool = False, name: str = '', source_etype: Any = None) -> Any: RequirePointerOperand(ptr, 'getelementptr(GEP)', self._GuardGen, f"GEP 名='{name}',索引 {len(indices)} 项", RequireConcretePointee=True) return super().gep(ptr, indices, inbounds=inbounds, name=name, source_etype=source_etype) def load(self, ptr: Any, name: str = '', align: int | None = None, typ: Any = None) -> Any: RequirePointerOperand(ptr, 'load', self._GuardGen, f"load 名='{name}'") return super().load(ptr, name=name, align=align, typ=typ) def store(self, value: Any, ptr: Any, align: int | None = None) -> Any: RequirePointerOperand(ptr, 'store', self._GuardGen, f"store 值名='{getattr(value, 'name', '')}'") return super().store(value, ptr, align=align) class _HotspotSiteBuilder(_GuardIrBuilder): """--hotspot-sites 专用 IRBuilder:为每条新生成的 call 指令挂源码位置。 仅当调用点热点统计开关打开时才被实例化(见 BaseGenMixin._NewIrBuilder); 未启用时使用 _GuardIrBuilder(同为 IR 边界守卫,见其类注释)。 重写 call() 只在返回的 ir.CallInstr 对象上追加两个普通 Python 属性 (_src_file / _src_line),不改变指令语义、操作数,也不参与 IR 渲染 (llvmlite 仅按指令自身类型输出文本,忽略这些自定义属性)。取不到位置时 留空(None / 0),不伪造。 """ def __init__(self, block: ir.Block | None, gen: Any) -> None: super().__init__(block, gen) self._HotspotGen: Any = gen def call(self, fn: Any, args: Any, name: str = '', cconv: Any = None, tail: Any = False, fastmath: Any = (), attrs: Any = (), arg_attrs: Any = None) -> ir.CallInstr: instr: ir.CallInstr = super().call( fn, args, name=name, cconv=cconv, tail=tail, fastmath=fastmath, attrs=attrs, arg_attrs=arg_attrs) gen: Any = self._HotspotGen # 记录生成期当前源码位置(语句级 lineno + 当前源文件名);取不到则留空 instr._src_file = getattr(gen, '_current_source_file', None) instr._src_line = getattr(gen, '_current_lineno', 0) or 0 return instr class BaseGenMixin: @staticmethod def _parse_triple(triple: str | None) -> dict[str, bool | int]: """解析目标三元组,返回平台信息字典""" info: dict[str, bool | int] = { 'is_windows': False, 'is_linux': False, 'is_macos': False, 'is_x86_64': False, 'is_arm64': False, 'is_32bit': False, 'ptr_size': 8, 'long_size': 8, 'wchar_t_size': 32, } if not triple: return info triple_lower: str = triple.lower() # 检测操作系统 if 'windows' in triple_lower or 'win32' in triple_lower or 'mingw' in triple_lower or 'msvc' in triple_lower or 'cygwin' in triple_lower: info['is_windows'] = True # Windows LLP64: long = 32 位, wchar_t = 16 位 info['long_size'] = 32 info['wchar_t_size'] = 16 elif 'linux' in triple_lower: info['is_linux'] = True elif 'darwin' in triple_lower or 'macos' in triple_lower or 'apple' in triple_lower: info['is_macos'] = True # 检测架构 if 'x86_64' in triple_lower or 'amd64' in triple_lower or 'x64' in triple_lower: info['is_x86_64'] = True elif 'aarch64' in triple_lower or 'arm64' in triple_lower: info['is_arm64'] = True elif 'i386' in triple_lower or 'i686' in triple_lower or 'arm' in triple_lower: info['is_32bit'] = True info['ptr_size'] = 4 info['long_size'] = 32 if info['is_windows']: info['wchar_t_size'] = 16 else: info['wchar_t_size'] = 32 return info def __init__(self, triple: str | None = None, datalayout: str | None = None) -> None: self.module: ir.Module = ir.Module(name="transpyc_module") if triple: self.module.triple = triple else: self.module.triple = "x86_64-none-elf" if datalayout: self.module.data_layout = datalayout else: self.module.data_layout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" # 解析目标平台信息 self._platform_info: dict[str, bool | int] = self._parse_triple(self.module.triple) self.ptr_size: int = self._platform_info['ptr_size'] # 根据目标平台配置 t 模块中的平台相关类型大小 _t.configure_platform(self.module.triple) self.builder: ir.IRBuilder | None = None self.func: ir.Function | None = None self.functions: dict[str, ir.Function] = {} self.variables: dict[str, ir.Value | None] = {} self._direct_values: dict[str, ir.Value] = {} self._reg_values: dict[str, ir.Value] = {} self.var_signedness: dict[str, bool] = {} self._unsigned_results: set[int] = {} self.string_const_counter: int = 0 self.Vtables: dict[str, ir.GlobalVariable] = {} self.class_methods: dict[str, list[str]] = {} self.class_members: dict[str, list[tuple[str, ir.Type]]] = {} self.class_member_defaults: dict[str, dict[str, Any]] = {} self.class_member_signeds: dict[str, dict[str, bool]] = {} self.class_member_bitfields: dict[str, dict[str, int]] = {} self.class_member_byteorders: dict[str, dict[str, str]] = {} self.local_var_byteorders: dict[str, str] = {} self.class_member_element_class: dict[str, dict[str, str]] = {} self.class_member_bitoffsets: dict[str, dict[str, int]] = {} self.structs: dict[str, ir.IdentifiedStructType] = {} self.typedef_int_widths: dict[str, int] = {} # typedef名称 -> 底层整数位宽 (如 'UINT' -> 32, 'BYTE' -> 8) self._cross_module_vtable_classes: set[str] = set() self._cross_module_novtable: set[str] = set() self.class_vtable: set[str] = set() self._class_graph: dict[str, Any] = {} self.class_parent: dict[str, str] = {} # 多继承:父类名数组(声明顺序)。class_parent 兼容保留为第一个父类。 self.class_parents: dict[str, list[str]] = {} # Store member annotations for deferred type resolution (cross-module class references) self.class_member_annotations: dict[str, dict[str, Any]] = {} self.loop_break_targets: list[ir.Block] = [] self.loop_continue_targets: list[ir.Block] = [] # 当前是否正处于 switch 的 case 语句体内(True=是)。供 `fallthrough` # 标记语句(vast 脱糖为 c.NoBreak())判定合法性:只在 switch 的 case 体内 # 合法;写进 match(完全禁止 fallthrough)或 switch 之外一律 fail-fast。 self.in_switch_case: bool = False self.eh_jmp_buf_stack: list[ir.Value] = [] self.eh_except_block_stack: list[ir.Block] = [] self.eh_finally_stack: list[Any] = [] self.global_vars: set[str] = set() self.nonlocal_params: dict[str, Any] = {} self._variable_scope_stack: list[dict[str, Any]] = [] # Stack of outer scope variables for nested function nonlocal lookup self.var_struct_class: dict[str, str] = {} # 【注解声明类台账(2026-09-30)】带类型注解声明过的变量 → 其**权威**类名。 # 由 HandlesAnnAssign 在声明完成时登记;HandlesAssign 的"右值推出类"登记点 # 见 var_annotated_class 命中即**不覆盖** —— 声明决定变量类型,赋值只能把右值 # 转换进该类型。否则 `node: rawptr[AST] = Name(...)` 会把 node 记成 Name, # 后续 `node = ` 就被判成被禁止的向下转型 # (现场 includes/ast/parser.vp:1919:%AST* → %Name*)。 self.var_annotated_class: dict[str, str] = {} self.var_ptr_element: dict[str, bool] = {} # 全局变量的指针元素标志(持久,不受函数重置影响) # 用于全局变量 _argv: bytes | t.CPtr 等,使 _argv[index] 使用 8 字节指针步长 self.global_var_ptr_element: dict[str, bool] = {} self.class_provides: dict[str, list[str]] = {} self.class_requires: dict[str, list[str]] = {} self.class_require_must: dict[str, list[str]] = {} # 编译期 with 上下文栈:记录当前嵌套 with 链中所有可用的 provides 字段名 # 用于 __require_must__ 的纯编译期静态可达性检查 self._with_provides_stack: list[list[str]] = [] # 函数参数自动取地址模式: func_name -> [None, None, 'ref', 'temp', 'always', ...] # 'ref': &T — 实参非指针左值取址,右值 fail-fast # 'temp': &temp T — 实参非指针时在调用方建同型临时再取址 # 'always': t.CAutoPtr — 无条件取地址 self._auto_addr_params: dict[str, list[str | None]] = {} self.global_struct_class: dict[str, str] = {} self.var_type_info: dict[str, Any] = {} self.var_const_flags: dict[str, bool] = {} # ---- const / volatile 登记(与 ViperC 侧 VarEntry/FieldEntry 逐字段对应, # 对照表见 Debugger/type-descriptors.md「两侧语义对照表」)---- # 分两层,因为 TPC 的函数内语句与模块级语句走不同生命周期: # 函数作用域层(随函数出栈清空,清零点 HandlesFunctions 函数出口): # var_const_flags 变量(局部/形参)自身 const # var_volatile_flags 变量自身 volatile # var_pointee_volatile_flags 变量所指物 volatile(rawptr[t.CVolatile|T]) # 模块级层(模块存活期持久): # global_* 模块级同名三类 # 类字段层(模块存活期持久): # field_const_flags '类名.字段名' -> True # field_volatile_flags '类名.字段名' -> True # field_*_names 裸字段名集合(IR 侧按 GEP 指令名回查, # GEP 以字段名命名,见 HandlesExprAttr:36 等) self.var_volatile_flags: dict[str, bool] = {} self.var_pointee_volatile_flags: dict[str, bool] = {} self.global_const_flags: dict[str, bool] = {} self.global_volatile_flags: dict[str, bool] = {} self.global_pointee_volatile_flags: dict[str, bool] = {} self.field_const_flags: dict[str, bool] = {} self.field_const_names: set[str] = set() self.field_volatile_flags: dict[str, bool] = {} self.field_volatile_names: set[str] = set() self.field_pointee_volatile_flags: dict[str, bool] = {} self.field_pointee_volatile_names: set[str] = set() # const 变量名 -> 声明处类型文本(仅用于报文的「声明处类型(含 const)」) self.const_decl_types: dict[str, str] = {} self._current_source_file: str | None = None self._current_source_lines: list[str] = [] self._ns_cache: dict[str, str] = {} self._type_cache: dict[tuple[str, bool], ir.Type] = {} self._typedef_cache: dict[str, Any] = {} self._func_sig_cache: dict[str, Any] = {} self.var_type_assignments: dict[str, Any] = {} self._temp_struct_ptrs: list[ir.Value] = [] self._stop_iter_flag_param: ir.Value | None = None self._local_heap_ptrs: list[tuple[ir.Value, str, str | None]] = [] self._var_to_heap_ptr: dict[str, ir.Value] = {} self.known_return_types: dict[str, ir.Type] = {} self._current_lineno: int = 0 self._current_node_info: str = "" self.module_sha1: str | None = None self.ModuleSha1Map: dict[str, str] = {} self._temp_dir: str | None = None self._export_funcs: set[str] = set() self._export_extern_funcs: dict[str, str] = {} self._current_module_func_names: set[str] = set() # __doc__ 魔法字符串注册表:symbol_name -> docstring 文本(None 表示空/优化剥离) self._doc_strings: dict[str, str | None] = {} self.class_packed: set[str] = set() self._define_constants: dict[str, int] = {} # 以下属性原先在外部懒注入,此处显式初始化以避免 hasattr/getattr 动态反射 self._struct_sha1_map: dict[str, str] = {} self.SymbolTable: Any = None self._Trans: Any = None self.class_sha1_map: dict[str, str] = {} # 以下属性原先懒注入,此处显式初始化以消除 hasattr/getattr 动态反射 self._import_handler_ref: Any = None self._classes_in_progress: set[str] = set() # 字段收集期间添加当前类名,阻止 CollectAnnAssignMember 触发的嵌套 # _generate_structs 用不完整 class_members 调用 set_body(set_body 只能调用一次) # 用集合而非单值,因为嵌套特化类 _EmitClassLlvm 会清除单值,导致外层类被错误设置 body self._collecting_members_set: set[str] = set() # 已按父类展平的类成员表登记(HandlesImports._EmitExternalClassDeclLlvm / # HandlesClassDef._EmitClassLlvm 在展平完成时登记)。两处用途: # ① _TryLoadClassMembersFromPyi 的 guard 只在"已展平"时短路 —— # 停在自有字段的冻结表允许后续补齐(展平与加载顺序无关); # ② FuncGen._member_base 查询前据此判定是否需要重跑展平。 self._members_flattened: set[str] = set() # 展平补齐重入保护:避免 _member_base → _TryLoadClassMembersFromPyi → # _EmitExternalClassDeclLlvm → _member_base 的同类递归。 self._members_flatten_in_progress: set[str] = set() # 展平补齐的"依赖状态签名"(类名 → (struct 元素数, 成员表长度)): # 同一签名下补齐无效时不再重复尝试(HandlesImports.EnsureClassMembersFlattened)。 self._members_flatten_repair_sig: dict[str, tuple[int, int]] = {} self._last_var_name: str | None = None self._variadic_info: dict | None = None self._vtable_copy_counter: int = 0 self._decorated_funcs: dict = {} self._current_tree: Any = None self._DefineConstants: dict = {} self._all_define_constants: dict = {} # 闭包追踪:记录返回闭包的函数和变量 # _closure_return_types: func_name -> (fn_return_type, nonlocal_arg_types) self._closure_return_types: dict[str, tuple[Any, list[Any]]] = {} # _closure_var_types: var_name -> (fn_return_type, nonlocal_arg_types) self._closure_var_types: dict[str, tuple[Any, list[Any]]] = {} # 调用点热点统计(--hotspot-sites):None=未启用(零影响);str=调用点计数落盘文件路径 self.hotspot_sites_file: str | None = None # 是否对全部直接 call 点插桩(忽略白名单;体积/开销大) self.hotspot_sites_all: bool = False # 白名单被调函数基名列表;None=使用默认内存/字符串族白名单 self.hotspot_callees: list[str] | None = None # 注册策略:False=种子 CLI 单模块(在 main 入口注册);True=Projectrans # 多模块(每模块用 @llvm.global_ctors 自注册,不依赖 main 所在模块) self.hotspot_sites_module_local: bool = False # 函数级 DWARF 调试元数据(--dwarf / --asan 自动开启): # False=未启用(零影响,不构造任何 DI 元数据、不挂 !dbg); # True=每个 define 发 DISubprogram + 入口首条指令发 DILocation。 self.dwarf_enabled: bool = False self.DebugInfo: Any = None # Ctrl+C 调用栈(--traceback):仅在翻译 includes/traceback.vp 这一个模块时 # 置 True —— 落成 .ll 文本前为本模块注入 @llvm.global_ctors 构造器调用其 # Install()(见 _RegisterTracebackInstallViaCtor)。默认 False(零影响)。 self.traceback_ctor: bool = False pi: dict[str, bool | int] = self._platform_info # 根据目标平台设置宏(声明式,从三元组推导) if pi['is_windows']: self._define_constants['_WIN32'] = 1 if not pi['is_32bit']: self._define_constants['_WIN64'] = 1 self._define_constants['WIN64'] = 1 self._define_constants['WIN32'] = 1 if pi['is_linux']: self._define_constants['__linux__'] = 1 if pi['is_macos']: self._define_constants['__APPLE__'] = 1 self._define_constants['__MACH__'] = 1 if pi['is_x86_64']: self._define_constants['__x86_64__'] = 1 self._define_constants['__x86_64'] = 1 self._define_constants['_M_X64'] = 1 elif pi['is_arm64']: self._define_constants['__aarch64__'] = 1 self._define_constants['_M_ARM64'] = 1 if not pi['is_32bit'] and pi['is_linux']: self._define_constants['__LP64__'] = 1 elif pi['is_32bit']: self._define_constants['_ILP32'] = 1 self._define_constants['SIZEOF_VOID_P'] = pi['ptr_size'] self._define_constants['__SIZEOF_POINTER__'] = pi['ptr_size'] def find_struct_by_pointee(self, pointee: ir.Type) -> tuple[str, ir.IdentifiedStructType] | None: """根据 pointee 类型查找匹配的结构体,返回 (class_name, struct_type) 或 None""" for class_name, struct_type in self.structs.items(): if pointee == struct_type: return (class_name, struct_type) return None @staticmethod def _extract_short_name(name: str) -> str: """从结构体类型名中提取短名(去掉 sha1 前缀)。 如 "6bc91356c652f8ca.Function" → "Function" 如 "Function" → "Function"(无 sha1 前缀时原样返回) """ if '.' in name: prefix: str = name.split('.', 1)[0] if len(prefix) == 16 and all(c in '0123456789abcdef' for c in prefix): return name.split('.', 1)[1] return name @staticmethod def _extract_struct_sha1(st: ir.IdentifiedStructType) -> str: """从 IdentifiedStructType 的 name 中提取 sha1 前缀(16 位十六进制)。 返回空字符串表示无 sha1 前缀。用于同名类冲突检测。 """ if not isinstance(st, ir.IdentifiedStructType): return '' try: name: str = st.name except (AssertionError, AttributeError): return '' if '.' in name: prefix: str = name.split('.', 1)[0] if len(prefix) == 16 and all(c in '0123456789abcdef' for c in prefix): return prefix return '' def _FindStructByIrName(self, ns_name: str) -> ir.IdentifiedStructType | None: """按 **IR 类型名**(`IdentifiedStructType.name`)查已注册结构体。 必要性:`self.structs` 的键有"裸短名 / sha1 前缀全名 / import 别名"多种, 同一个 IR 名字可被多个键指向。若只按"键"判存在性,对同一 IR 名字会再建 第二个 `IdentifiedStructType` 对象 ⇒ 单模块 .ll 里出现**两条同名 type 定义** (Body 逐字相同)。`llc` 容忍这种重复,但 `llvm-link` 报 "error: redefinition of type" ⇒ 跨模块 alwaysinline 合并整体回退 (现场:includes/ast/parser.vp 的 8312ce86a2fd82c0.Keyword —— 裸名 'Keyword' 先被 .tokens 的导入占用,astaux 的同名类只剩 'KwNode' 键, 随后按全名 '8312ce86a2fd82c0.Keyword' 请求时又建了一个同名新对象)。 IR 里类型的身份**就是名字**(不是 Python 对象),故存在性必须按名字判定: 命中即复用同一对象(需要 body 时由既有 set_body 流程补,与"opaque 占位 → set_body"同语义)。名字不同的同名类冲突由 sha1 前缀命名负责区分,不靠 "同名两个对象"。 """ if not ns_name: return None for _st in self.structs.values(): if isinstance(_st, ir.IdentifiedStructType) and _st.name == ns_name: return _st return None @staticmethod def _CleanStructName(name: str) -> str: """结构体名清洗:去掉指针标记/空格/修饰符(clean_name 既有行为)。 但**保留**泛型特化名 '[...]' 段内的原样文本:特化实参里的 '*' 是类型 身份的一部分('list[.AST*]' 表示元素为指向 AST 的指针),若一并 移除会让声明端引用名('list[.AST*]')与定义端注册名 ('list[.AST]')分裂成两个 struct。头部(首个 '[' 之前)行为不变; '[' 开头(LLVM 数组串 '[4 x i8*]')走原清洗,保持既有语义。 """ lb: int = name.find('[') if lb > 0: head, tail = name[:lb], name[lb:] else: head, tail = name, '' head = (head.replace(' *', '').replace('*', '').replace(' ', '_') .replace('const', 'const_').replace('volatile', 'volatile_')) return head + tail def _enum_int_type(self, EnumName: str | None, MemberInfo: Any = None) -> ir.IntType: """枚举运行时常量 / 枚举类型注解的整数位宽类型(两侧 ABI 同点)。 宽度 = "最宽成员类型",在枚举定义处推导并登记 (HandlesClassDef._RegisterEnumMembers → enum_width),此处读取。 优先用调用点手头的成员条目;退回按枚举名查符号表;再退回默认 i32 (跨模块 stub 序列化不携带 enum_width、旧产物 → 默认 32,与改前逐字一致)。 """ Width: int = EnumIntWidth(MemberInfo, 0) if Width > 0: return ir.IntType(Width) if EnumName: Table: Any = getattr(self, 'SymbolTable', None) if Table is not None: return ir.IntType(EnumIntWidth(Table.lookup(EnumName))) return ir.IntType(_ENUM_DEFAULT_INT_WIDTH) def _get_or_create_struct(self, name: str, source_sha1: str | None = None, packed: bool = False) -> ir.IdentifiedStructType: clean_name: str = self._CleanStructName(name) # 如果名称包含模块前缀(如 "a9fa0f6200c09e65.zbit_writer"), # 先尝试用短名称(如 "zbit_writer")查找已注册的 struct, # 避免为同一 struct 创建多个不同的类型对象导致跨模块引用类型不一致 if '.' in clean_name and clean_name not in self.structs: parts: list[str] = clean_name.split('.', 1) if len(parts[0]) == 16 and all(c in '0123456789abcdef' for c in parts[0]): short_name: str = parts[1] if short_name in self.structs: existing_short: ir.IdentifiedStructType = self.structs[short_name] # 治本修复:同名类 sha1 冲突检测。 # 当请求的 sha1 前缀与现有 struct 的 sha1 前缀不一致时 # (如 stmts.py:Module 与 __module.py:Module 同名冲突), # 不能复用已有 struct(set_body 已锁定为错误布局), # 需创建独立 struct 并以 sha1 前缀全名存储。 _existing_sha1: str = self._extract_struct_sha1(existing_short) if _existing_sha1 and _existing_sha1 != parts[0]: if clean_name in self.structs: return self.structs[clean_name] # 按 IR 名去重:该 IR 名字的 struct 可能已由**别的键** # (裸名/别名)注册 ⇒ 复用,绝不建第二个同名对象 # (否则单模块 .ll 出现两条同名 type 定义 ⇒ llvm-link # "redefinition of type" ⇒ 跨模块内联整体回退)。 _st_same: ir.IdentifiedStructType | None = self._FindStructByIrName(clean_name) if _st_same is not None: self.structs[clean_name] = _st_same if packed and not _st_same.packed: _st_same.packed = True return _st_same st_new: ir.IdentifiedStructType = ir.IdentifiedStructType(self.module, name=clean_name, packed=packed) self.structs[clean_name] = st_new return st_new return existing_short # 优先解析 typedef 整数类型(如 INT/UINT/BYTE),避免将其注册为 opaque struct。 # 必须在 `if clean_name in self.structs` 缓存检查之前执行, # 否则首次调用(SymbolTable 未就绪时)会将 typedef 名缓存为 opaque struct, # 后续调用即使 SymbolTable 就绪也只会返回缓存的错误类型。 # 此修复同时解决两个问题: # 1. alloca unsized type:变量声明 `argc: INT = 6` 不再生成 `alloca %"...INT"` # 2. 下标访问:FindStructNameInAnnotation 不再匹配 typedef 名(因不在 Gen.structs) td_width: int | None = self._resolve_typedef_int_width(clean_name) if td_width is not None: if td_width > 0: self.typedef_int_widths[clean_name] = td_width return ir.IntType(td_width) # td_width == 0: 特殊 typedef 名称(str/bytes/list 等),返回 i8 self.typedef_int_widths[clean_name] = 0 return ir.IntType(8) if clean_name in self.structs: existing: ir.IdentifiedStructType = self.structs[clean_name] # 治本修复:同名类 sha1 冲突检测(短名调用路径)。 # 当 source_sha1 与现有 struct 的 sha1 前缀不一致时(跨模块同名类冲突), # 不能复用已有 struct(set_body 已锁定为错误布局), # 需创建独立 struct 并以 sha1 前缀全名存储,不覆盖短名条目。 if source_sha1: _existing_sha1: str = self._extract_struct_sha1(existing) if _existing_sha1 and _existing_sha1 != source_sha1: full_key: str = f"{source_sha1}.{clean_name}" if full_key in self.structs: st_full: ir.IdentifiedStructType = self.structs[full_key] if packed and isinstance(st_full, ir.IdentifiedStructType) and not st_full.packed: st_full.packed = True return st_full ns_name: str = f"{source_sha1}.{clean_name}" # 按 IR 名去重(见 _FindStructByIrName):同一 IR 名字已有对象 # ⇒ 复用同一对象,只补注册本键,绝不建第二个同名对象。 _st_same2: ir.IdentifiedStructType | None = self._FindStructByIrName(ns_name) if _st_same2 is not None: self.structs[full_key] = _st_same2 if packed and not _st_same2.packed: _st_same2.packed = True return _st_same2 st_new: ir.IdentifiedStructType = ir.IdentifiedStructType(self.module, name=ns_name, packed=packed) self.structs[full_key] = st_new return st_new if packed and isinstance(existing, ir.IdentifiedStructType) and not existing.packed: existing.packed = True return existing if clean_name in {'typedef', 'CType', 'CVolatile', 'CEnum', 'CUnion', 'CStruct', 'enum', 'REnum', 'renum', 'Void', 'void'}: return ir.IntType(8) # typing.Any 是"任意类型"标记,应解析为 i8*(通用指针), # 而非创建为 opaque 结构体(否则包含 Any 字段的结构体会变成 unsized)。 if clean_name == 'Any': return ir.IntType(8).as_pointer() # t.CType 的基本类型子类(CInt/CChar/CSizeT/CLong/CFloat/CDouble 等, # position 含 BASE 且 Size>0)有具体 LLVM 类型,应直接返回对应的 # ir.IntType/ir.FloatType/ir.DoubleType,而非创建结构体。 # _is_ctype_marker_name 对这些类型返回 False(不跳过),需要在此处拦截。 _basic_llvm_type: ir.Type | None = self._try_resolve_ctype_basic(clean_name) if _basic_llvm_type is not None: return _basic_llvm_type # t.CType 及其子类是"类型强转"标记,不应被编译为真实结构体。 # 动态检查 t 模块中所有 CType 子类名(排除 CStruct/CEnum/CUnion/REnum 子类, # 因为用户定义的结构体类继承自它们,需要被创建为结构体)。 if self._is_ctype_marker_name(clean_name): return ir.IntType(8) if self.SymbolTable: Entry: _CTypeInfo | None = self.SymbolTable.lookup(clean_name) # 「有类定义 ⇒ 是类」的唯一判据(与 TypeConvert._type_str_to_llvm_base # 的 `type_str in self.structs or type_str in self._struct_sha1_map`、 # HandlesTypeMerge 的 Name/Attribute 分支、DeclarationGenerator 的 # `attr_name in struct_names` 同一条规则源): # _struct_sha1_map / class_sha1_map 已登记该名为**类**时,即便平坦符号表 # 条目是**同名枚举成员**(如 ast/base.vp 的 `Constant: t.State` 与 # ast/exprs.vp 的 `class Constant` 同名;谁后插入谁占住裸名),也必须建成 # 结构体。否则此处返回 i32 ⇒ 上游 PtrCount=1 叠加后变成 i32*, # 与调用点实参 %".Constant"* 分裂(现场:constant_get_str_val 的 # 形参 i32*)。类型注解语境下枚举**成员**从不是类型,判据只能是"是否有类定义"。 # 必须排除**枚举类型本身**(如 `class ASTKind(t.CEnum)`):_struct_sha1_map # 是按 .vpi 里全部 ClassDef 建的,枚举类也在其中 —— 若一并当结构体,枚举 # 注解(kind() 返回值等)会被建为 opaque struct,引发 i32 ↔ 结构体错位 # ('IntType' object has no attribute 'is_opaque' 现场)。 # 判据必须按 **IsEnumMember** 区分(现场实测标志组合): # 枚举**成员** = IsEnum=True + IsEnumMember=True + BaseType=CEnum # 枚举**类型** = IsEnum=True + IsEnumMember=False # 只判 IsEnum 会把成员误当枚举类型 ⇒ 返回 i32 ⇒ 上游 PtrCount=1 ⇒ i32*。 _known_class_name: bool = ( (clean_name in getattr(self, '_struct_sha1_map', {}) or clean_name in getattr(self, 'class_sha1_map', {})) and not (Entry and Entry.IsEnum and not getattr(Entry, 'IsEnumMember', False))) if Entry and Entry.IsTypedef: resolved_str: str = self._resolve_typedef(clean_name) if resolved_str != clean_name: return self._type_str_to_llvm(resolved_str) if Entry.BaseType: if not isinstance(Entry.BaseType, (_t._CTypedef,)) or Entry.PtrCount > 0: resolved: str = self._resolve_ctype_to_str(Entry) if resolved: return self._type_str_to_llvm(resolved) if Entry and Entry.IsRenum: if clean_name in self.structs: return self.structs[clean_name] # REnum 结构体尚未创建时不能 fall through 到 IsEnum(会返回 i32), # 需要继续到下方正常结构体创建路径,创建 opaque 结构体。 # 这对自引用 REnum(如 LLVMType 包含 LLVMType*)至关重要: # _parse_llvm_type 先通过此路径创建 opaque 结构体,_TryLoadStructFromStub 再 set_body。 elif (Entry and Entry.IsEnum and clean_name not in self.class_methods and not _known_class_name): # CEnum 存储宽度 = 最宽成员类型(登记在 Entry.enum_width;未登记 # → 默认 i32)。此前此处恒 i32,与同文件 _enum_int_type 的常量/ # 注解宽度分裂(宽成员枚举的类型与常量不同宽)。 return self._enum_int_type(clean_name, Entry) # 治本修复:当 clean_name 是已注册的类(在 class_methods 或 class_members 中)时, # 不应走 IsExceptionClass/IsFunction/IsVariable 分支返回非结构体类型。 # 否则 _get_or_create_struct 返回指针类型(如 _TypedPointerType)而非 IdentifiedStructType, # 导致 _TryLoadStructFromStub 的 set_body 被跳过(isinstance 检查失败), # struct 保持不完整字段数,引发跨模块 GEP 越界崩溃。 _is_class_name: bool = clean_name in self.class_methods or clean_name in self.class_members if Entry and Entry.IsExceptionClass and not _is_class_name: return ir.IntType(32) if Entry and Entry.IsFunction and not _is_class_name: return ir.PointerType(ir.IntType(8)) if Entry and Entry.IsVariable and not _is_class_name: return ir.PointerType(ir.IntType(8)) if Entry and Entry.BaseType and not _known_class_name: if isinstance(Entry.BaseType, type) and issubclass(Entry.BaseType, _t.CEnum): return self._enum_int_type(clean_name, Entry) if Entry.BaseType is _t.CEnum: return self._enum_int_type(clean_name, Entry) ns_name: str # 泛型特化名('list[str]' / 'dict[str][int]'):类→模块 sha1 表是按**基名** # 登记的 ClassDef('list' 来自 includes/_list.vp),特化名本身不在表里 ⇒ # 若不先按基名取 sha1,_mangle_name 会拼出 '<使用方模块sha1>.list[str]', # 与 declare 端(DeclarationGenerator `gc_sha1 = struct_sha1_map.get(gc_name, # module_sha1)` → '%"{模板sha1}.list[str]"*')及定义端 # (HandlesClassDef 特化时 Gen.module_sha1 = 模板模块)**三处分裂**。 _tmpl_name: str = clean_name.split('[', 1)[0] if '[' in clean_name else clean_name _tmpl_sha1: str | None = self._struct_sha1_map.get(_tmpl_name, '') or None if source_sha1: ns_name = f"{source_sha1}.{clean_name}" elif _tmpl_sha1: ns_name = f"{_tmpl_sha1}.{clean_name}" else: ns_name = self._mangle_name(clean_name) # 按 IR 名去重(见 _FindStructByIrName):该 IR 名字的对象可能已由**别的键** # (裸名 / sha1 全名 / import 别名)注册 ⇒ 复用同一对象并补注册本键。 # 否则单模块 .ll 会出现两条同名 type 定义 ⇒ llvm-link # "error: redefinition of type" ⇒ 跨模块 alwaysinline 合并整体回退 # (现场:includes/ast/parser.vp 的 8312ce86a2fd82c0.Keyword 定义两次)。 _st_same3: ir.IdentifiedStructType | None = self._FindStructByIrName(ns_name) if _st_same3 is not None: self.structs[clean_name] = _st_same3 if packed and not _st_same3.packed: _st_same3.packed = True return _st_same3 st: ir.IdentifiedStructType = ir.IdentifiedStructType(self.module, name=ns_name, packed=packed) self.structs[clean_name] = st # 尝试解析 typedef 的底层整数位宽 width: int | None = self._resolve_typedef_int_width(clean_name) if width is not None: self.typedef_int_widths[clean_name] = width return st def _resolve_typedef_int_width(self, name: str) -> int | None: """解析 typedef 名称的底层整数位宽 通过 CTypeRegistry 和 BuiltinTypeMap 动态解析, 替代硬编码的 TYPEDEF_NAMES 映射表。 返回 int 位宽或 None(无法解析)。 """ # 1. 先查 BuiltinTypeMap(覆盖 BYTEPTR 等指针别名) bm: Any = BuiltinTypeMap.Get(name) if bm: ctype_cls: Any ptr_count: int ctype_cls, ptr_count = bm if ptr_count > 0: return None # 指针类型,不是整数 typedef llvm_str: str = CTypeRegistry.CTypeToLLVM(ctype_cls) if llvm_str and llvm_str.startswith('i') and llvm_str[1:].isdigit(): return int(llvm_str[1:]) # 2. 再查 SymbolTable(用户定义的 typedef) if self.SymbolTable: Entry: _CTypeInfo | None = self.SymbolTable.lookup(name) if Entry and Entry.IsTypedef: if Entry.BaseType: if not isinstance(Entry.BaseType, (_t._CTypedef,)) and Entry.PtrCount == 0: llvm_str: str = CTypeRegistry.CTypeToLLVM(type(Entry.BaseType) if isinstance(Entry.BaseType, _t.CType) else Entry.BaseType) if llvm_str and llvm_str.startswith('i') and llvm_str[1:].isdigit(): return int(llvm_str[1:]) if Entry.OriginalType: if isinstance(Entry.OriginalType, _CTypeInfo) and Entry.OriginalType.BaseType: if not isinstance(Entry.OriginalType.BaseType, (_t._CTypedef,)) and Entry.OriginalType.PtrCount == 0: llvm_str: str = CTypeRegistry.CTypeToLLVM(type(Entry.OriginalType.BaseType) if isinstance(Entry.OriginalType.BaseType, _t.CType) else Entry.OriginalType.BaseType) if llvm_str and llvm_str.startswith('i') and llvm_str[1:].isdigit(): return int(llvm_str[1:]) elif isinstance(Entry.OriginalType, str): # OriginalType 是类型名字符串(如 'CInt', 'CUnsignedInt'), # 通过 CTypeRegistry 解析为整数位宽 cls: Any = CTypeRegistry.GetClassByName(Entry.OriginalType) if cls: llvm_str: str = CTypeRegistry.CTypeToLLVM(cls) if llvm_str and llvm_str.startswith('i') and llvm_str[1:].isdigit(): return int(llvm_str[1:]) # 3. 尝试 CTypeRegistry 直接按名称查找 ctype_cls: Any = CTypeRegistry.GetClassByName(name) if ctype_cls: llvm_str: str = CTypeRegistry.CTypeToLLVM(ctype_cls) if llvm_str and llvm_str.startswith('i') and llvm_str[1:].isdigit(): return int(llvm_str[1:]) # 4. 特殊名称(非整数类型,但需要标记为 typedef 以跳过 opaque 定义) # 注意:如果 name 已注册为 struct(用户定义了同名 class,如 includes/_dict.py 中的 dict), # 则不应视为 typedef,应返回 None 让调用方走 struct 创建路径。 # 解决时序问题:_parse_llvm_declare 从 stub.ll 解析时 dict 可能尚未注册到 Gen.structs, # 但 SymbolTable 在 .pyi 解析阶段已就绪,可通过 is_struct 检查区分 class 和 typedef。 _SPECIAL_TYPEDEFS: set[str] = { 'typedef', 'CType', 'CVolatile', 'CEnum', 'CUnion', 'CStruct', 'enum', 'REnum', 'renum', 'list', 'dict', 'tuple', 'set', 'str', 'bytes', 'array', 'type', 'CArray', } if name in _SPECIAL_TYPEDEFS: if name in self.structs: return None # 已注册为 struct,不视为 typedef if self.SymbolTable is not None: try: if self.SymbolTable.is_struct(name): return None # SymbolTable 中标记为 struct,不视为 typedef except Exception: pass return 0 # 标记为 typedef,但位宽为 0(非整数) # t.CType 及其子类是"类型强转"标记,不应被创建为结构体。 # 动态检查 t 模块中所有 CType 子类名(排除 CStruct/CEnum/CUnion/REnum 子类)。 # 返回 0 标记为 typedef,使 _get_or_create_struct 走 typedef 路径返回 i8。 if self._is_ctype_marker_name(name): return 0 return None @staticmethod def _is_ctype_marker_name(name: str) -> bool: """检查名称是否是 t.CType 的"类型强转"标记子类名。 只有 position 不含 BASE 的纯修饰符/标记类型(CPtr/CVolatile/CConst/ CInline/CStatic/CExtern/CExport/CRegister/CAuto/_CTypedef/CTypeDefault 等) 才应该跳过结构体创建。有 BASE position 的基本类型(CInt/CChar/CSizeT/ CLong/CFloat/CDouble 等)有具体大小,应被正常解析为对应宽度的整数/浮点。 CStruct/CEnum/CUnion/REnum 的用户子类是合法结构体,需要被创建。 注意:CTypeRegistry.GetClassByName 会跳过 CType 本身和 CTypeDefault, 所以这里直接用 getattr(t, name) 检查 t 模块属性。 Returns: True 如果 name 是 CType 标记子类名(应跳过结构体创建) """ if not name: return False # 去除可能的 SHA1 前缀(如 "abc123.CPtr" -> "CPtr") short_name: str = name.split('.')[-1] if '.' in name else name # 直接查 t 模块属性(CTypeRegistry._build 会跳过 CType/CTypeDefault/BigEndian/LittleEndian) t_cls: Any = getattr(_t, short_name, None) if not isinstance(t_cls, type) or not issubclass(t_cls, _t.CType): return False # 排除 CStruct/CEnum/CUnion/REnum 子类(它们是命名类型的基类, # 用户定义的结构体继承自它们,需要被创建为结构体) if issubclass(t_cls, (_t.CStruct, _t.CEnum, _t.REnum, _t.CUnion)): return False # 基本类型(position 含 BASE 且 Size > 0)有具体大小,不应跳过 # (CInt→i32, CChar→i8, CSizeT→i64, CLong→i64, CFloat→f32 等) # CVoid 虽含 BASE 但 Size=0,CType Size=None,仍需跳过 if _t.CType.BASE in t_cls.position: # 检查实例的 Size(类属性可能未设置,用实例获取) try: inst = t_cls() size_val = getattr(inst, 'Size', None) if size_val is not None and size_val > 0: return False except Exception: pass return True def _try_resolve_ctype_basic(self, name: str) -> ir.Type | None: """尝试将 t.CType 的基本类型子类名解析为对应的 LLVM 基本类型。 仅对 t 模块内的 CType 基本类型子类(CInt/CChar/CSizeT/CLong/CFloat/ CDouble 等,即 position 含 BASE 且 Size>0)生效,返回对应的 ir.IntType/ir.FloatType/ir.DoubleType。 CStruct/CEnum/CUnion/REnum 子类不在此处理(用户自定义结构体需要被 创建为结构体)。CVoid/CType/CPtr 等无 Size 或 Size=0 的类型也不在此 处理(应通过 _is_ctype_marker_name 跳过结构体创建)。 Returns: 对应的 ir.Type,或 None 表示不是基本类型,需要继续走结构体创建路径。 """ if not name: return None short_name: str = name.split('.')[-1] if '.' in name else name cls: Any = getattr(_t, short_name, None) if not isinstance(cls, type) or not issubclass(cls, _t.CType): return None if issubclass(cls, (_t.CStruct, _t.CEnum, _t.REnum, _t.CUnion)): return None if _t.CType.BASE not in getattr(cls, 'position', frozenset()): return None try: inst = cls() size_val = getattr(inst, 'Size', None) if size_val is None or size_val <= 0: return None except Exception: return None llvm_str: str | None = CTypeRegistry.NameToLLVM(short_name) if not llvm_str: return None return self._type_str_to_llvm(llvm_str) def _set_node_info(self, node: ast.AST, extra_info: str = "") -> None: if hasattr(node, 'lineno'): self._current_lineno = node.lineno else: self._current_lineno = 0 if hasattr(node, 'col_offset'): self._current_node_info = f"line {self._current_lineno}, col {node.col_offset}" else: self._current_node_info = f"line {self._current_lineno}" if extra_info: self._current_node_info += f" ({extra_info})" # 语句/指令级 DWARF 位置(--dwarf / --asan):每进入一条语句就把当前 # builder 的默认调试位置切到该 AST 节点的行/列(col_offset 是 0-based, # +1 转 1-based;无位置时为 0 → 回退函数入口 DILocation)。此后该语句生成 # 的所有指令(含 call)自动带本函数 scope 的 !dbg,llc 不再因缺 !dbg 丢弃 # 整份 DI。未启用时 dwarf_enabled=False,一次都不触碰 builder.debug_metadata # (零影响:_GetDebugInfo() 也不会被调用)。 if self.dwarf_enabled and self.builder is not None: _DbgInfo = self._GetDebugInfo() if _DbgInfo is not None: _DbgInfo.SetStatement( self.builder, self._current_lineno, getattr(node, 'col_offset', -1) + 1) def _set_source_info(self, filepath: str) -> None: self._current_source_file = filepath try: with open(filepath, 'r', encoding='utf-8') as f: self._current_source_lines = f.readlines() except Exception: # 文件读取失败时回退为空行列表 self._current_source_lines = [] def _GetDebugInfo(self) -> Any: """返回本模块的 DebugInfoEmitter;未启用 --dwarf 或无源文件时返回 None。 未启用(dwarf_enabled=False)时不构造任何对象、不产生任何 IR 差异, 行为与改动前逐字节一致。源文件取 _current_source_file(由 GenerateCCode 在生成 IR 前设定,模块内恒定),每个模块一份 DICompileUnit + DIFile。 """ if not self.dwarf_enabled: return None if not self._current_source_file: return None if self.DebugInfo is None: from lib.core.LLVMCG.DebugInfo import DebugInfoEmitter self.DebugInfo = DebugInfoEmitter(self.module, self._current_source_file) return self.DebugInfo def _skip_mangle(self, name: str) -> bool: skip_prefixes: tuple[str, ...] = ('llvm.', 'str_const_', 'printf', 'memset_pattern') if any(name.startswith(p) for p in skip_prefixes): return True if '.' in name: parts: list[str] = name.split('.', 1) if len(parts[0]) >= 8 and all(c in '0123456789abcdef' for c in parts[0]): return True return False def _mangle_name(self, name: str) -> str: if self._skip_mangle(name): return name if name in self._export_funcs: return name if self.module_sha1: return f"{self.module_sha1}.{name}" return name def _find_function(self, name: str) -> ir.Function | None: if name in self.functions: return self.functions[name] g: Any = self.module.globals.get(name) if g and isinstance(g, ir.Function): self.functions[name] = g return g mangled: str = self._mangle_func_name(name) if mangled != name: if mangled in self.functions: self.functions[name] = self.functions[mangled] return self.functions[mangled] g = self.module.globals.get(mangled) if g and isinstance(g, ir.Function): self.functions[mangled] = g self.functions[name] = g return g for sha1 in self.ModuleSha1Map.values(): prefixed: str = f"{sha1}.{name}" if prefixed in self.functions: self.functions[name] = self.functions[prefixed] return self.functions[prefixed] g = self.module.globals.get(prefixed) if g and isinstance(g, ir.Function): self.functions[prefixed] = g self.functions[name] = g return g return None def _has_function(self, name: str) -> bool: return self._find_function(name) is not None def _get_mro_chain(self, ClassName: str) -> list[str]: """MRO 线性化类名列表(深度优先 + 声明顺序 + 去重,含自身)。 顺序: 自身 → 父类1 及祖先 → 父类2 及祖先(先声明父类优先,菱形去重)。 与 VPC HandlesStruct.get_mro_chain 的语义一致。 支持带 sha1 前缀的类名(如 "c9b44d17cd621a75.CallGenLib",跨模块 structs 表键):查 class_parents 前剥离前缀,否则表键(裸类名)不匹配 → MRO 链只剩自身 → 继承方法调用 fail-fast "Undefined method 'find_func_in_module' in class '...CallGenLib'"(CallGenLib 根因)。 """ chain: list[str] = [] visited: set[str] = set() stack: list[str] = [ClassName] parents_map: dict[str, list[str]] = getattr(self, 'class_parents', {}) while stack: cur: str = stack.pop() if not cur or cur in visited: continue visited.add(cur) chain.append(cur) # 归一化:剥离 "16位hex." 前缀后用裸类名查父类表 lookup: str = cur if len(cur) > 17 and cur[16] == '.': lookup = cur[17:] parents: list[str] = parents_map.get(lookup, parents_map.get(cur, [])) # 父类逆序压栈,保证声明顺序弹出(先访问父类1 的整条链) for p in reversed(parents): if p and p not in visited: stack.append(p) return chain def _get_parent_classes(self, ClassName: str) -> list[str]: """获取类的所有父类名(声明顺序),兼容旧 class_parent 单链。""" parents_map: dict[str, list[str]] = getattr(self, 'class_parents', {}) parents: list[str] = parents_map.get(ClassName) if parents: return parents p: str | None = self.class_parent.get(ClassName) if p: return [p] return [] def _CreateOrReuseFuncDecl(self, name: str, func_type: ir.FunctionType) -> ir.Function: """函数声明创建的唯一幂等入口(所有"新建 ir.Function"的路径都应走这里)。 llvmlite 的 NameScope 按名字去重(GlobalValue.deduplicate_name=False): 同名 global 再建一次必抛 llvmlite.ir._utils.DuplicatedNameError(只给名字、 不给两侧签名,现场:5d84ea579d83ef73.CallGenFd._translate_fakeduck_call)。 故此处: · module.globals 已有同名函数且函数类型(按字符串比较,FunctionType 无 __eq__ ⇒ 只能比结构串)相同 ⇒ **复用**并补登记 Gen.functions; · 已有但类型不同 ⇒ fail-fast 报出两侧签名 —— 这是 declare / define 分裂的 真因,绝不静默复用错签名(会让调用点按错签名传参)。 """ # ---- 创建前自检(两条,都是"进了 llvmlite 就不可救"的状态)---- # 背景:llvmlite 的 `Function.__init__` 顺序是 # NamedValue.__init__ → _set_name → module.scope.register(name) 【名字占用】 # ... 逐形参 `Argument(...)`(内部 assert isinstance(typ, types.Type))... # module.add_global(self) 【进入 globals】 # 中间任一步抛错,Python 都不会回滚 scope ⇒ 留下"半注册"的名字:globals 查无、 # scope 却已占用。此后同一名字再建必抛 DuplicatedNameError(报文只有名字、 # 没有两侧信息)。现场:972cf2b7756765d5.ImportsGenMixin._index_imported_module # —— 上游首建失败被 `except Exception` 静默吞掉,只剩这个裸报错。 # ① 签名自检:返回类型/任一形参不是 llvmlite Type(典型 None=注解未解析出来) # 会在 `Argument(...)` / `ReturnValue(...)` 处 assert 失败,而失败点在名字 # 注册之后(见上)⇒ 在此先行判定,给出可定位报文。 _bad_desc: str | None = None _bad_ty: Any = None _ret_ty: Any = getattr(func_type, 'return_type', None) if not isinstance(_ret_ty, ir.Type): _bad_desc = '返回类型' _bad_ty = _ret_ty else: for _ai, _at in enumerate(getattr(func_type, 'args', ()) or ()): if not isinstance(_at, ir.Type): _bad_desc = f'第 {_ai} 个形参类型' _bad_ty = _at break if _bad_desc is not None: from lib.core.LLVMCG.FuncGen import FailFastError raise FailFastError( f"函数声明签名非法:'{name}' 的{_bad_desc}是 " f"{type(_bad_ty).__name__}({str(_bad_ty)[:80]}),不是 llvmlite 类型" f"\n · 本次签名: {func_type}" f"\n 成因:该处注解未解析出 llvmlite Type(解析失败返回 None / 拿到的是" f"符号表条目等非类型对象)。llvmlite 会在 Argument(...)/ReturnValue(...) " f"处 assert 失败,而失败点落在「名字已进 module.scope、尚未 add_global」" f"之间 ⇒ 留下半注册名字,后续同名创建只报 DuplicatedNameError。" f"\n 修法:核对上面这个位置在声明侧(源码注解 / .vpi stub)为何解析不出类型。" + self._get_node_info()) # ② 悬空名字自检:scope 已占用而 globals 无对象 ⇒ 上一次同名创建中途失败 # (异常被吞/未走完 add_global)。此处若继续新建只会得到裸 DuplicatedNameError, # 故直接报出状态事实(不静默清理:真因在"上一处被吞的创建失败",清理会掩盖它)。 _mod_scope: Any = getattr(self.module, 'scope', None) if (name not in self.module.globals and _mod_scope is not None and getattr(_mod_scope, 'is_used', None) is not None and _mod_scope.is_used(name)): from lib.core.LLVMCG.FuncGen import FailFastError raise FailFastError( f"函数声明名字悬空占用:'{name}' 已进 module.scope,但 module.globals " f"中无对应对象——llvmlite 的 Function 在「名字注册」与 add_global 之间" f"失败时不会回滚 scope。" f"\n ⇒ 说明**此前对同名函数的创建中途失败且异常被吞**(真因在那一处," f"不在本次调用)。本次继续新建只会得到裸 DuplicatedNameError。" f"\n 修法:先修掉上一处被吞的创建失败(形参类型非法 / 签名解析失败)," f"半注册名字随之消失。" + self._get_node_info()) existing: Any = self.module.globals.get(name) if existing is not None and not isinstance(existing, ir.Function): # 名字被非函数 global 占住(全局变量 / vtable / 字符串常量…):llvmlite 的 # NameScope 按名字去重,再建同名 ir.Function 必抛 DuplicatedNameError # (报文只有名字,不含冲突类型)⇒ 在此把冲突双方摊开,避免裸抛。 from lib.core.LLVMCG.FuncGen import FailFastError raise FailFastError( f"同名全局符号冲突:'{name}' 已在 module.globals 中登记,但不是函数" f"({type(existing).__name__}):{existing!r}" f"\n 函数声明无法复用该符号(llvmlite NameScope 按名字去重);" f"请核对该名字是否同时被用作全局变量 / vtable / 字符串常量。") if isinstance(existing, ir.Function): _old_type: Any = getattr(existing, 'ftype', None) if _old_type is None or str(_old_type) == str(func_type): self.functions[name] = existing return existing from lib.core.LLVMCG.FuncGen import FailFastError raise FailFastError( f"同名函数声明签名冲突(declare/define 分裂):'{name}'" f"\n · 已存在声明: {_old_type}" f"\n · 本次要创建: {func_type}" f"\n 拒绝静默复用(调用点会按错签名传参);请核对同一函数在声明侧" f"(stub / .vpi)与定义侧的注解解析结果是否一致。") func: ir.Function = ir.Function(self.module, func_type, name=name) self.functions[name] = func return func def _get_or_declare_function(self, mangled_name: str, func_type: ir.FunctionType) -> ir.Function: return self._CreateOrReuseFuncDecl(mangled_name, func_type) def _get_function(self, name: str) -> ir.Function | None: func: ir.Function | None = self._find_function(name) return func def _mangle_func_name(self, name: str, module_name: str | None = None) -> str: if name in self._export_funcs and not module_name: return name if name in self._export_extern_funcs: target_sha1: str = self._export_extern_funcs[name] if module_name: # 显式带 module_name 调用(如 stdlib.system):即使当前模块定义了同名函数, # 也应按模块匹配 extern,返回裸名。snprintf 之所以工作而 system 不工作, # 就是因为 os._win32 定义了自己的 system 包装,导致旧逻辑跳过了 extern 匹配。 if module_name == target_sha1: return name if module_name in self.ModuleSha1Map and self.ModuleSha1Map[module_name] == target_sha1: return name init_name: str = f"{module_name}.__init__" if init_name in self.ModuleSha1Map and self.ModuleSha1Map[init_name] == target_sha1: return name # 兜底:module_name 不在 ModuleSha1Map 中或 sha1 不匹配, # 但函数在 _export_extern_funcs 中(C extern 不应混淆), # 仅当当前模块未定义同名函数时才返回裸名。 if name not in self._current_module_func_names: return name elif name not in self._current_module_func_names: # 无 module_name 调用(如 from stdlib import system; system()): # 仅当当前模块未定义同名函数时才返回 extern 裸名, # 否则让本地定义走正常 mangle 路径(本地 def 遮蔽 import 语义)。 return name # main 函数作为程序入口点,永远不混淆 if name == 'main': return name # Try class_sha1_map first for class methods (e.g., 'md5.__init__' -> class 'md5') class_sha1_map: dict[str, str] = self.class_sha1_map if class_sha1_map: if '.' in name: base_class: str = name.split('.')[0] if base_class in class_sha1_map: sha1: str = class_sha1_map[base_class] return f"{sha1}.{name}" elif name in class_sha1_map: sha1: str = class_sha1_map[name] return f"{sha1}.{name}" # If module_name is already a SHA1 hash (16 hex chars), use it directly if module_name and len(module_name) == 16 and all(c in '0123456789abcdef' for c in module_name): return f"{module_name}.{name}" if module_name and module_name in self.ModuleSha1Map: sha1: str = self.ModuleSha1Map[module_name] return f"{sha1}.{name}" # Try parent package name (e.g., 'hashlib' -> 'hashlib.__init__') if module_name: init_name: str = f"{module_name}.__init__" if init_name in self.ModuleSha1Map: sha1: str = self.ModuleSha1Map[init_name] return f"{sha1}.{name}" if self._skip_mangle(name): return name if self.module_sha1: result = f"{self.module_sha1}.{name}" else: result = name return result def _get_source_line(self, lineno: int) -> str | None: if 0 < lineno <= len(self._current_source_lines): return self._current_source_lines[lineno - 1].rstrip('\r\n') return None def _FindConcreteTwin(self, StructType: Any) -> Any: """按**同短名**在 `structs` 里找一份**已具化**(elements 非 None)的 struct 实例。 为什么需要:同一类名常有两份 struct 实例(短名 'Keyword' / sha1 全名 '.Keyword'),"函数签名里的 self 用哪一份"与"StructGen 具化的是哪一份" 并不保证一致;一旦被 GEP 的那份是 opaque,llvmlite 在编译期算元素类型即抛裸 TypeError(现场:includes/ast/astaux.vp:165 `self.arg = _copy_str(pool, arg)` 的基址 pointee 是 opaque 的 `%".Keyword"*`)。调用方把指针 bitcast 到 本函数返回的实例即可让"偏移与 GEP 同源"——与 HandlesAssign 里 "同短名取字段最多版本"是同一判据。 返回 None 表示**没有**已具化孪生体(真因是"body 从未 set_body")⇒ 调用方不得 兜底,交由 RequirePointerOperand 的 opaque 守卫 fail-fast 并报出实例清单。 """ if not isinstance(StructType, ir.IdentifiedStructType): return None _Short: str = '' try: _Short = self._extract_short_name(getattr(StructType, 'name', '') or '') except Exception: _Short = '' if not _Short: return None # 第一优先:**IR 全名相同**的实例(同类名两份实例的情形,必为同一类); # 第二优先:同短名里元素最多的一份(跨模块同名类;与 HandlesAssign # "同短名取字段最多版本"同判据)。全名相同优先可避免"短名相同但实为另一个 # 模块的同名类"被误选而算出错的偏移。 _Full: str = getattr(StructType, 'name', '') or '' _Best: Any = None _BestN: int = -1 for _v in list(getattr(self, 'structs', {}).values()): if not isinstance(_v, ir.IdentifiedStructType) or _v.elements is None: continue if _Full and getattr(_v, 'name', '') == _Full: return _v try: _s: str = self._extract_short_name(getattr(_v, 'name', '') or '') except Exception: _s = '' if _s != _Short: continue if len(_v.elements) > _BestN: _BestN = len(_v.elements) _Best = _v return _Best def _DiskLineno(self, Lineno: int) -> int: """AST 行号 → 磁盘真实行号(减掉文件头自动注入 import 的行数)。 判据与其余各处**逐字一致**(HandlesBase._FailFastFixedArrayKeyword:687、 HandlesAssign._RaiseGlobalAttrError:2051、LlvmGenerator/HandlesBody 的 _ErrorStack 各站点):Phase2Translator 在解析前用 _inject_auto_imports 往源码头注入 `import _list/_dict/json`,AST 全部节点行号因此整体下移 N (translator._line_offset = N);而 `Gen._current_source_lines` 是**磁盘** 原文 ⇒ 任何"AST 行号 + 源码行"的输出都必须先减 N,否则印出的源码是下一行。 本渲染器此前漏还原,于是同一次 fail-fast 里出现两套矛盾行号 (现场:真实错误在 TypeGenResolve.vp:1141 `= "t.CPtr"`, _get_node_info 印 1142 且源码取到 1142 行的 `return "t.CPtr"`, 而 _ErrorStack 帧印的是正确的 1141/1140)。 """ if Lineno <= 0: return Lineno _offset: int = int(getattr(self._Trans, '_line_offset', 0) or 0) if _offset > 0 and Lineno > _offset: return Lineno - _offset return Lineno def _get_node_info(self) -> str: info: str = "" if self._current_node_info: # 标签里的行号同样是 AST 行号:必须与下面的源码行**一起**还原, # 否则标签(1142)与源码(1141)互相矛盾。 _m = _LINE_INFO_RE.match(self._current_node_info) _info: str = (f"line {self._DiskLineno(int(_m.group(1)))}{_m.group(2)}" if _m is not None else self._current_node_info) info = f" [{_info}]" if self._current_source_file: info += f" in {self._current_source_file}" lineno: int = self._DiskLineno(self._current_lineno) if lineno > 0: info += f", line {lineno}:" source_line: str | None = self._get_source_line(lineno) if source_line: info += f"\n → {source_line}" return info def _DescribeValueNode(self, Node: Any = None) -> str: """把「真正被转换的值节点」渲染成一段诊断文本,供隐式转换禁令引用。 报文风格照本仓既有「表达式文本 + 位置 + 源码片段」: 表达式 ''(<节点形态>,位置 文件:行:列),源码: <该行> 关键 ①:**列号按有符号打印**——未知(无属性 / <0)时打印 '-',绝不把 col_offset 的 -1 当无符号打成 4294967295(此前 VPC 侧诊断错位的根因)。取不到节点时 回退当前语句上下文(_get_node_info 风格),不再固定打印外层 Assign。 关键 ②:**行号必须先还原为磁盘行号**(_DiskLineno)——节点行号含文件头 自动注入 import 的偏移,直接用来读源码会读到下一行(现场:真实错误行 1141,这里印成 1142 且源码显示出 1142 行的 `return "t.CPtr"`)。 """ if Node is None: return self._get_node_info() try: Expr: str = ast.unparse(Node) except Exception: Expr = getattr(Node, 'attr', '') or getattr(Node, 'id', '') or '' RawLine: int = getattr(Node, 'lineno', 0) or 0 Col: Any = getattr(Node, 'col_offset', -1) Line: int = self._DiskLineno(RawLine or self._current_lineno or 0) Loc: str = self._current_source_file or '' if Line > 0: Loc += f":{Line}" Loc += f":{Col}" if isinstance(Col, int) and Col >= 0 else ":-" Code: str = '' if Line > 0: try: Code = (self._get_source_line(Line) or '').strip() except Exception: Code = '' Text: str = f";出错表达式: 表达式 '{Expr}'({type(Node).__name__} 形态,位置 {Loc})" if Code: Text += f",源码: {Code}" return Text # ========== const / volatile:判定、登记与报文(两侧同口径) ========== # 语义唯一来源见 Debugger/type-descriptors.md。TPC 与 ViperC 必须**同判据、 # 同报文**;任何未消除的差异都列在该文档的「已知差异」一节。 @staticmethod def _IsConstObject(Info: Any) -> bool: """注解解析结果是否为「变量自身 const」的对象(C 语义下的 const 对象)。 判据(两侧统一):`(DataConst or VarConst)` 且 `PtrCount == 0`。 `t.CConst` 在本仓库解析为 **DataConst**(t 模块分支:HandlesTypeMerge.py:192-196、 TypeAnnotationResolver.py:382-389),其字段语义是「指向的数据 const」 (HandlesBase.py:76-77)。因此: - 非指针类型(PtrCount==0):该限定退化为「该对象自身 const」⇒ `a: const int` / `a: t.CConst | int` 的 a 禁止赋值/增量赋值/元素赋值; - 指针类型(PtrCount>=1):限定只作用于 pointee ⇒ `const T *p` 的 p 仍可重新赋值(C 语义),**不**登记为 const 变量。 后者就是全仓既有两处 `t.CConst | T | t.CPtr` 局部变量(fat32_types.vp:1591 `buf_ptr`、elf.vp:509 `cmp_name`)在其作用域内被重新赋值却合法的原因。 """ if Info is None: return False if not (getattr(Info, 'DataConst', False) or getattr(Info, 'VarConst', False)): return False return getattr(Info, 'PtrCount', 0) == 0 @staticmethod def _AnnotationDeclText(Annotation: Any) -> str: """注解节点的源码文本(仅用于报文;取不到时给占位,不伪造)。""" if Annotation is None: return '(无注解)' try: return ast.unparse(Annotation) except Exception: return '<无法还原注解文本>' @staticmethod def _AnnotationHasQualifier(Annotation: Any, AttrName: str) -> bool: """注解子树里是否出现 `t.`(与 ViperC TypeGenErr.has_decorator_marker 同口径)。""" if Annotation is None: return False for n in ast.walk(Annotation): if isinstance(n, ast.Attribute) and n.attr == AttrName: return True return False @staticmethod def _AnnotationHasPointeeVolatile(Annotation: Any) -> bool: """注解是否为 `rawptr[t.CVolatile | T]`(volatile 指针指向物,等价 C 的 volatile T*)。 与 ViperC TypeGenErr.has_pointee_volatile_marker 同口径:只看 `rawptr[...]` 的下标实参子树里有没有 CVolatile;多级指针(slice 仍是 Subscript)明确不支持。 """ if not isinstance(Annotation, ast.Subscript): return False base: Any = Annotation.value if not isinstance(base, ast.Name) or base.id != 'rawptr': return False return BaseGenMixin._AnnotationHasQualifier(Annotation.slice, 'CVolatile') def RegisterVarQualifiers(self, Name: str, Info: Any, Annotation: Any, IsGlobal: bool = False, IsParam: bool = False) -> None: """登记一个变量(局部/形参/模块级全局)的 const / volatile 限定。 统一的登记入口(TPC 的形参、局部变量、模块级全局三处声明点都调本函数), 保证「登记点」只有一份判据。 IsParam=True 时 **const 判据沿用形参的历史条件** `DataConst or VarConst` (不看指针层数):这是「形参保持现状 + 不得放宽任何既有 fail-fast」两条 硬约束下的唯一选择——把形参也改成指针感知判据虽然更合 C 语义,但会**放宽** 既有形参检查(`fmt: t.CConst | str` 这类指针形参不再登记),从约束上不允许。 该历史条件对 `const T*` 形参过严,但全仓 462 个 .vp 普查(见 Debugger/type-descriptors.md)表明**零个形参在其函数体内被重新赋值**, 故这处差异在既有代码上不可观测。非形参的三种声明一律用指针感知判据 `_IsConstObject`。 """ if not Name: return if IsParam: IsConst: bool = bool(Info) and ( getattr(Info, 'DataConst', False) or getattr(Info, 'VarConst', False)) else: IsConst = self._IsConstObject(Info) if IsConst: if IsGlobal: self.global_const_flags[Name] = True else: self.var_const_flags[Name] = True self.const_decl_types[Name] = self._AnnotationDeclText(Annotation) if self._AnnotationHasQualifier(Annotation, 'CVolatile'): if IsGlobal: self.global_volatile_flags[Name] = True else: self.var_volatile_flags[Name] = True if self._AnnotationHasPointeeVolatile(Annotation): if IsGlobal: self.global_pointee_volatile_flags[Name] = True else: self.var_pointee_volatile_flags[Name] = True def RegisterFieldQualifiers(self, ClassName: str | None, FieldName: str, Info: Any, Annotation: Any) -> None: """登记一个类字段的 const / volatile 限定(键 `类名.字段名` + 裸字段名)。""" if not FieldName: return if self._IsConstObject(Info): if ClassName: self.field_const_flags[f'{ClassName}.{FieldName}'] = True self.field_const_names.add(FieldName) self.const_decl_types[FieldName] = self._AnnotationDeclText(Annotation) if self._AnnotationHasQualifier(Annotation, 'CVolatile'): if ClassName: self.field_volatile_flags[f'{ClassName}.{FieldName}'] = True self.field_volatile_names.add(FieldName) if self._AnnotationHasPointeeVolatile(Annotation): if ClassName: self.field_pointee_volatile_flags[f'{ClassName}.{FieldName}'] = True self.field_pointee_volatile_names.add(FieldName) def _IsVarConst(self, Name: str) -> bool: """变量(局部/形参/模块级全局)自身是否 const。""" return bool(Name) and (Name in self.var_const_flags or Name in self.global_const_flags) def _IsVarVolatile(self, Name: str) -> bool: """变量自身是否 volatile。""" return bool(Name) and (Name in self.var_volatile_flags or Name in self.global_volatile_flags) def _IsVarPointeeVolatile(self, Name: str) -> bool: """变量所指物是否 volatile(等价 C 的 volatile T* 解引用)。""" return bool(Name) and (Name in self.var_pointee_volatile_flags or Name in self.global_pointee_volatile_flags) def _IsFieldConst(self, ClassName: str | None, FieldName: str) -> bool: """类字段自身是否 const。 类名可解析时按精确键 `类名.字段名`(**不**回退裸名,避免"另一个同名 const 字段"造成跨类误伤);类名解析不到时退化为裸名集合(保守报错)。 """ if not FieldName: return False if ClassName: return f'{ClassName}.{FieldName}' in self.field_const_flags return FieldName in self.field_const_names def _IsFieldVolatile(self, FieldName: str) -> bool: return bool(FieldName) and FieldName in self.field_volatile_names def _IsFieldPointeeVolatile(self, FieldName: str) -> bool: return bool(FieldName) and FieldName in self.field_pointee_volatile_names @staticmethod def _StripDedupSuffix(Name: str) -> list[str]: """IR 名 -> 候选登记名(llvmlite 去重把重复名改成 `名.1` / `名.2`)。""" Cands: list[str] = [Name] Head, _, Tail = Name.rpartition('.') if Head and Tail.isdigit(): Cands.append(Head) return Cands def _IsPtrVolatile(self, ptr: Any) -> bool: """按 IR 指针对应的**登记名**判定该次访问是否带 volatile。 alloca / GlobalVariable 均以变量名命名,GEP 以字段名命名 (见 HandlesExprAttr 的 `gep(..., name=AttrName)`),故变量自身 volatile、 变量所指物 volatile 与字段 volatile 都在此命中。名字取不到时返回 False (不做任何猜测/兜底)。 下标 GEP 额外带基址名(`subscript_<基址名>` / `subscript_ptr_<基址名>`, 见 HandlesExprAttr/HandlesAssign 的下标改写):基址是 pointee-volatile 变量/字段时,经该下标的 load/store 也要带 volatile(等价 C 的 `volatile T* p` 的 `p[i]`)。**基址名后缀为空时名字与改造前逐字相同** ⇒ 无 pointee-volatile 的代码 IR 逐字节不变。 """ nm: Any = getattr(ptr, 'name', None) if not nm or not isinstance(nm, str): return False for C in self._StripDedupSuffix(nm): if self._IsVarVolatile(C) or self._IsVarPointeeVolatile(C): return True if C in self.field_volatile_names or C in self.field_pointee_volatile_names: return True BaseNames: list[str] = list(self.var_pointee_volatile_flags) BaseNames += list(self.global_pointee_volatile_flags) BaseNames += list(self.field_pointee_volatile_names) for B in BaseNames: if (nm == f'subscript_{B}' or nm.startswith(f'subscript_{B}.') or nm == f'subscript_ptr_{B}' or nm.startswith(f'subscript_ptr_{B}.')): return True return False @staticmethod def _SubscriptSuffix(BaseNode: Any) -> str: """下标基址名后缀(供 pointee-volatile 按 IR 名回查,见 `_IsPtrVolatile`)。 基址是 `Name`(变量)→ `_变量名`;是 `obj.字段` → `_字段名`;其余形态返回 `''`。**返回 `''` 时下标 GEP 名与改造前逐字相同**(既有代码 IR 零变更)。 """ if isinstance(BaseNode, ast.Name): return f'_{BaseNode.id}' if BaseNode.id else '' if isinstance(BaseNode, ast.Attribute): return f'_{BaseNode.attr}' if BaseNode.attr else '' return '' def _ConstAssignMessage(self, Kind: str, Name: str) -> str: """const 变量被赋值时的 fail-fast 报文(两侧同口径)。 含:文件/行/列 + 源码片段(`_get_node_info`)+ 变量名 + 声明处类型(含 const) + 修法。 """ DeclType: str = self.const_decl_types.get(Name, '(声明处类型未登记)') return ( f"不能对 const 变量 '{Name}' {Kind}{self._get_node_info()}" f"(声明处类型(含 const):{DeclType};" f"修法:删掉该声明里的 `const` 描述符 / `t.CConst`,或改为声明时一次赋值、" f"之后只用非 const 的副本/中间量;const 对象在其作用域内禁止赋值、" f"增量赋值与元素赋值,且不会降级为警告)" ) # ========== 统一工具方法 ========== def _create_string_global(self, s: str, name: str | None = None) -> ir.Constant: """创建字符串全局变量并返回 i8* 指针。 统一字符串全局变量创建模式:编码为 utf-8 + '\\x00' 终止符, 创建 internal linkage 的 GlobalVariable,返回 i8* 指针常量。 """ str_val: str = s + '\x00' str_bytes: bytes = str_val.encode('utf-8') arr_type: ir.ArrayType = ir.ArrayType(ir.IntType(8), len(str_bytes)) gv_name: str = name or f"str_const_{self.string_const_counter}" self.string_const_counter += 1 gv: ir.GlobalVariable = ir.GlobalVariable(self.module, arr_type, name=gv_name) gv.initializer = ir.Constant(arr_type, bytearray(str_bytes)) gv.linkage = 'internal' return ir.Constant(ir.PointerType(ir.IntType(8)), gv.get_reference()) def _create_doc_global(self, symbol_name: str, docstring: str | None) -> None: """为符号创建 __doc__ 全局常量。 - docstring 为 None 或空字符串时,不创建常量(访问时内联空指针) - slice_level != 0(优化开启)时,内容视为空 - 非空时创建 internal linkage、global_constant=True 的只读字符串常量 - 常量名格式:{module_sha1}.{symbol_name}.__doc__ """ slice_level: int = getattr(self._Trans, 'SliceLevel', 3) if slice_level != 0: docstring = None if docstring is None or docstring == '': self._doc_strings[symbol_name] = None return self._doc_strings[symbol_name] = docstring mangled: str = self._mangle_name(f"{symbol_name}.__doc__") self._create_string_global(docstring, name=mangled) gv: ir.GlobalVariable | None = self.module.globals.get(mangled) if gv is not None: gv.global_constant = True def _get_doc_ptr(self, symbol_name: str) -> ir.Value | None: """获取符号 __doc__ 的 i8* 指针。 - 若符号未注册 __doc__,返回 None(调用方回退到其他解析) - 若 __doc__ 为空(无 docstring 或优化剥离),返回 i8* null 内联空指针 - 若 __doc__ 非空,返回指向字符串全局常量的 i8* 指针 - 跨模块 docstring(在 _doc_strings 中有文本但 module.globals 无对应常量)首次访问时创建本地 internal linkage 只读副本 """ if symbol_name not in self._doc_strings: return None docstring: str | None = self._doc_strings[symbol_name] if docstring is None or docstring == '': return ir.Constant(ir.PointerType(ir.IntType(8)), None) mangled: str = self._mangle_name(f"{symbol_name}.__doc__") gv: ir.GlobalVariable | None = self.module.globals.get(mangled) if gv is None: # 跨模块 docstring:创建本地 internal linkage 只读字符串常量副本 self._create_string_global(docstring, name=mangled) gv = self.module.globals.get(mangled) if gv is None: return ir.Constant(ir.PointerType(ir.IntType(8)), None) gv.global_constant = True zero: ir.Constant = ir.Constant(ir.IntType(32), 0) return self.builder.gep(gv, [zero, zero], name=f"{symbol_name}_doc_ptr") def _apply_bswap_if_big(self, val: ir.Value, byte_order: str, name: str) -> ir.Value: """大端字节序时对整数应用 bswap,否则原值返回。 仅对 16/32/64 位整数生效,统一 bswap 逻辑。 """ if byte_order == 'big' and isinstance(val.type, ir.IntType) and val.type.width in (16, 32, 64): return self.builder.bswap(val, name=name) return val def _zero_value_for_type(self, llvm_type: ir.Type) -> ir.Constant | None: """为指定 LLVM 类型生成零值常量。 合并原 HandlesAssign._zero_value 与 StructGen._zero_value_for_type 的逻辑。 返回 ir.Constant 或 None(无法生成零值的类型,如 opaque struct)。 """ if isinstance(llvm_type, ir.IntType): return ir.Constant(llvm_type, 0) elif isinstance(llvm_type, (ir.FloatType, ir.DoubleType)): return ir.Constant(llvm_type, 0.0) elif isinstance(llvm_type, ir.PointerType): return ir.Constant(llvm_type, None) elif isinstance(llvm_type, ir.ArrayType): elem_zv: ir.Constant | None = self._zero_value_for_type(llvm_type.element) if elem_zv is None: return None return ir.Constant(llvm_type, [elem_zv] * llvm_type.count) elif isinstance(llvm_type, ir.BaseStructType): if llvm_type.elements is not None and len(llvm_type.elements) > 0: elem_zvs: list[ir.Constant | None] = [self._zero_value_for_type(m) for m in llvm_type.elements] if any(zv is None for zv in elem_zvs): return None return ir.Constant(llvm_type, elem_zvs) return None return None # ========== 调用点热点统计基础设施(--hotspot-sites) ========== def _UniqueGlobalName(self, base: str) -> str: """返回本模块内未被占用的全局符号名(追加下划线直到不冲突)。 仅用于 --hotspot-sites 自动生成的内部符号,避免与用户全局/函数重名。 """ name: str = base while name in self.module.globals: name += '_' return name def _DeclareExternalFunc(self, name: str, ftype: ir.FunctionType) -> ir.Function: """获取或声明外部 C 函数(用于 --hotspot-sites 所需的 Win32/CRT 接口)。 已存在同名函数时直接复用;若已存在符号类型不一致则显式报错(不静默降级)。 """ existing: Any = self.module.globals.get(name) if existing is not None: if not isinstance(existing, ir.Function): raise RuntimeError( f'--hotspot-sites: 全局符号 {name} 已存在且不是函数,无法声明外部接口') if existing.ftype != ftype: raise RuntimeError( f'--hotspot-sites: 外部函数 {name} 已存在但签名不一致' f'(期望 {ftype},实际 {existing.ftype})') return existing return self._CreateOrReuseFuncDecl(name, ftype) def _InstallHotspotTruncate( self, defs: list[ir.Function], path: str, require_main: bool = False, flag: str = '--hotspot-sites', ) -> ir.Function | None: """在含 main 的模块的 main 入口插入一次 "w" 打开→关闭,清空计数文件。 --hotspot-sites 的各处理器(调用点计数、每函数计时)以追加("a")方式写 ,若不清空会累积历史运行的数据。清空必须满足两点: - 只在含 main 的模块执行一次(其它模块无 main,返回 None 且不截断); - 必须在 main 入口而非构造器:@llvm.global_ctors 的跨模块执行顺序无保证, 放构造器里可能在其它模块已写入后清空 → 丢数据。 main 入口在程序启动、任何 Ctrl+C 处理器写入之前执行,故每次运行都以空文件 开始,配合追加写保证"一次运行一份结果"。 flag 仅用于报错信息(调用点计数与每函数计时各自调用一次,传入对应开关名)。 返回找到的 main 定义(供调用方继续注册处理器);未找到时返回 None。 require_main=True(种子 CLI 单模块)时未找到 main 直接报错(不静默降级)。 """ main_func: ir.Function | None = None for func in defs: if func.name == 'main': main_func = func break if main_func is None: if require_main: raise RuntimeError( f'{flag}: 找不到 main 函数定义,无法清空计数文件') return None i32: ir.IntType = ir.IntType(32) i8p: ir.PointerType = ir.PointerType(ir.IntType(8)) fopen: ir.Function = self._DeclareExternalFunc('fopen', ir.FunctionType(i8p, [i8p, i8p])) fclose: ir.Function = self._DeclareExternalFunc('fclose', ir.FunctionType(i32, [i8p])) path_ptr: ir.Constant = self._create_string_global( path, name=self._UniqueGlobalName('__fhs_trunc_path')) mode_ptr: ir.Constant = self._create_string_global( 'w', name=self._UniqueGlobalName('__fhs_trunc_mode')) b: ir.IRBuilder = ir.IRBuilder(main_func.entry_basic_block) b.position_at_start(main_func.entry_basic_block) fp: ir.Value = b.call(fopen, [path_ptr, mode_ptr], name='fhs_trunc_fp') b.call(fclose, [fp]) return main_func # ========== 调用点热点统计(--hotspot-sites) ========== def _NewIrBuilder(self, block: ir.Block) -> ir.IRBuilder: """创建 IR 构建器:IR 边界守卫(无条件)+ --hotspot-sites 源码位置挂钩。 未启用热点统计(hotspot_sites_file 为 None)时返回 _GuardIrBuilder: 合法 IR 与原生 ir.IRBuilder 完全一致,只在 GEP/load/store 的指针操作数 不是指针时 fail-fast(否则 llvmlite 会抛无位置信息的裸 AttributeError 'IntType' object has no attribute 'is_opaque',见 RequirePointerOperand)。 启用时返回 _HotspotSiteBuilder(同为 _GuardIrBuilder 子类,守卫不变), 仅在每条新生成的 call 指令对象上追加 _src_file/_src_line 两个普通属性, 供调用点热点插桩读取源码位置。 """ if self.hotspot_sites_file: return _HotspotSiteBuilder(block, self) return _GuardIrBuilder(block, self) @staticmethod def _HotspotCalleeBase(name: str) -> str: """返回被调函数名的基名:去掉 16 位 sha1 模块前缀。 仅当形如 "<16位十六进制>.xxx" 时才剥离,避免误伤普通函数名; 例如 "23499a69276862b2.strcmp" -> "strcmp","strlen" -> "strlen"。 """ if len(name) > 17 and name[16] == '.': prefix: str = name[:16] if all(ch in '0123456789abcdefABCDEF' for ch in prefix): return name[17:] return name def _EmitHotspotSiteCounters( self, ) -> tuple[list[ir.Function], list[tuple[ir.GlobalVariable, str, str, str, int]]]: """为匹配白名单的每个直接 call 调用点安装 internal i64 计数器并在 call 之前递增。 返回 (模块内函数定义列表, 记录列表);记录为 (计数器全局, 调用方函数名, 被调方基名, 源码文件名, 源码行号), 记录顺序即计数器编号 __fhs_site_ 的顺序。源码位置来自生成期挂在 call 指令对象上的 _src_file/_src_line(见 _HotspotSiteBuilder);生成期 无法关联到 AST 的合成调用点取不到位置,则文件名留空、行号记 0。 插桩规则: - 白名单:hotspot_sites_all=True 时不过滤(全部直接调用点);否则仅对 "被调名去 16 位 sha1 前缀后的基名" 命中白名单的调用点插桩。 - 跳过间接调用(callee 不是具名 ir.Function):无法归属被调方,故不插桩。 - 跳过 __fhs_ 前缀的辅助函数自身,避免在插桩生成的代码里计数 (本功能稍后生成的处理器/构造器/计时辅助亦在此列)。 - 只在 call 之前插入 load/add/store,不改变返回值/参数/控制流。 - 计数器一律 internal,跨模块(独立翻译单元)链接不产生重复符号,初值 0。 """ i64: ir.IntType = ir.IntType(64) if self.hotspot_sites_all: whitelist: set[str] | None = None else: names: list[str] = ( list(self.hotspot_callees) if self.hotspot_callees is not None else list(DEFAULT_HOTSPOT_CALLEES) ) whitelist = {self._HotspotCalleeBase(n) for n in names} defs: list[ir.Function] = [f for f in self.module.functions if getattr(f, 'blocks', None)] records: list[tuple[ir.GlobalVariable, str, str, str, int]] = [] for func in defs: if func.name.startswith('__fhs_'): continue for block in list(func.blocks): for instr in list(block.instructions): if not isinstance(instr, ir.CallInstr): continue callee: Any = instr.callee if not isinstance(callee, ir.Function) or not callee.name: continue base: str = self._HotspotCalleeBase(callee.name) if whitelist is not None and base not in whitelist: continue idx: int = len(records) counter: ir.GlobalVariable = ir.GlobalVariable( self.module, i64, name=self._UniqueGlobalName(f'__fhs_site_{idx}')) counter.linkage = 'internal' counter.initializer = ir.Constant(i64, 0) # 在 call 之前递增计数:load -> add 1 -> store(不改变 call 本身) b: ir.IRBuilder = ir.IRBuilder(block) b.position_before(instr) cur: ir.Value = b.load(counter, name=f'fhs_site_load_{idx}') inc: ir.Value = b.add(cur, ir.Constant(i64, 1), name=f'fhs_site_inc_{idx}') b.store(inc, counter) # 源码位置:生成期 _HotspotSiteBuilder 挂在 call 指令对象上;缺失则留空 src_file: str = getattr(instr, '_src_file', None) or '' src_line: int = getattr(instr, '_src_line', 0) or 0 records.append((counter, func.name, base, src_file, src_line)) return defs, records @staticmethod def _HotspotTimePath(sites_path: str) -> str: """由调用点计数文件路径推导每函数计时输出文件路径。 取同目录下的固定名 hotspot_time.txt(不覆盖、不解析既有 hotspot_sites.txt 格式),便于下游分别解析两类结果。 """ return os.path.join(os.path.dirname(sites_path), 'hotspot_time.txt') # 深度栈容量:512。超出该深度的调用不记账(self/inc 均不计),但仍在 # __fhs_ftime_enter_ 里计入进入次数——溢出是"拿不到数值",明确跳过而非伪造。 _HotspotDepth: int = 512 def _EmitReadTimestamp(self, b: ir.IRBuilder, name: str = '') -> ir.Value: """发射一次**串行化**时间戳读取,返回 i64 tick(入口与每个 ret 前共用)。 串行化方案:在 x86_64 目标上先取一次内存屏障 @llvm.x86.sse2.lfence 再读 @llvm.readcyclecounter()(该 intrinsic 在 x86_64 后端降为 rdtsc)。理由: - lfence 在 Intel/AMD 上对取指与访存有序列化效果,能阻止 rdtsc 与前后访存 乱序(裸 rdtsc 只保证自身 edx:eax 原子,不保证与其它指令的顺序),这是 "lfence; rdtsc" 这一业界通行的序列化读法; - 用 LLVM intrinsic 组合而非 ir.InlineAsm:无内联汇编约束串的转义/渲染风险, 也不依赖 Clang 风格 asm 约束是否被 llvmlite 正确渲染,可移植性更好。 非 x86_64 目标(如 aarch64/arm/i386)降级为**裸** @llvm.readcyclecounter(): lfence 是 x86(SSE2) 专有 intrinsic,在非 x86 目标上不存在、编译期即不可用, 故此处按目标三元组在**编译期**决定是否发射 lfence(不在运行期静默退化)。 """ i64: ir.IntType = ir.IntType(64) if bool(self._platform_info.get('is_x86_64')): lfence: ir.Function = self._DeclareExternalFunc( 'llvm.x86.sse2.lfence', ir.FunctionType(ir.VoidType(), [])) b.call(lfence, []) rcc: ir.Function = self._DeclareExternalFunc( 'llvm.readcyclecounter', ir.FunctionType(i64, [])) return b.call(rcc, [], name=name) def _EmitHotspotTimeCounters( self, ) -> list[tuple[ir.GlobalVariable, ir.GlobalVariable, ir.GlobalVariable, str]]: """为模块内每个函数定义安装"自身耗时(self) + 包含式耗时(inc) + 进入次数"计时器。 计时原语(串行化读):见 _EmitReadTimestamp(x86_64 上 lfence; rdtsc, 其它目标降级为裸 rdtsc)。无 libc/Win32 依赖、无需额外链接;精度为 1 个 tick(x86_64 上即一个 CPU 周期,~0.3ns 量级),故耗时单位记为 tick。 exclusive(自身耗时)算法——深度栈: - 全局 common i64 __fhs_tdepth:当前插桩深度,初值 0;common linkage 让每个 翻译单元各自发射同名 tentative 定义、由链接器合并为**全程序唯一一份**, 故跨模块调用也共用同一张深度栈(独立翻译单元链接不产生重复符号)。 - 全局 common [DEPTH x i64] __fhs_tstart / __fhs_tchild(零初始化, DEPTH = _HotspotDepth = 512):按深度索引的"本帧起始 tick"与"本帧内被 插桩子调用 inclusive 之和";另有一个 internal i64 抖动槽 __fhs_tscratch, 用于把"不应记账"的写入导向一个永不读取的位置(等价于不写,但不分支)。 - 入口(每个被插桩函数 F,索引 N): t0 = 串行化读时间戳() d = load __fhs_tdepth ok = d < DEPTH __fhs_tstart[ok ? d : 0] = t0 (溢出时写抖动槽) __fhs_tchild[ok ? d : 0] = 0 (溢出时写抖动槽) __fhs_tdepth = ok ? d + 1 : d (溢出时不推进深度) slot = ok ? d : -1 存进该次调用的 alloca(每调用一个独立栈槽) __fhs_ftime_enter_N += 1 (溢出也计进入次数) 说明:slot 是"本帧独占的深度索引",递归/重入时各帧各占一层,天然正确; 因为本帧在其返回前独享 slot,故 ret 时用 __fhs_tstart[slot] 即本帧 t0 (无需再单独保存 t0 到 alloca,避免出现无人读取的死存储)。 - 每个 ret 之前(slot = load): inok = slot >= 0;idx = inok ? slot : 0 t1 = 串行化读时间戳() inc = t1 - __fhs_tstart[idx] (本次 inclusive) self = inc - __fhs_tchild[idx] (减去本次内被插桩子调用的 inc 之和) __fhs_fself_N += inok ? self : 0 (新增:自身耗时) __fhs_finc_N += inok ? inc : 0 (保留:包含式耗时) __fhs_tdepth = inok ? slot : load __fhs_tdepth (回到本帧深度) if slot > 0(含用抖动槽等价实现): __fhs_tchild[slot-1] += inc (把本次 inc 记到父帧的 child 累加器) 溢出帧(slot=-1)不记账、也不写入父帧 child;其墙钟时间仍落在父帧的 inc 里,因而会计入父帧的 **self**(见下方说明)。 返回记录列表 (自身耗时全局, 包含式耗时全局, 进入次数全局, 函数名),顺序即 N 的编号顺序。落盘由 _CreateHotspotTimeDump 完成(按 self 降序)。 固有性质(务必据此解读数值):**未插桩的被调方**(间接调用、无定义体的外部 函数如 strcmp/memcpy、名字以 __fhs_ 开头的辅助函数、深度溢出而跳过的调用) 其耗时不会被本方法从调用者 self 中扣除,一律计入**调用者自身 self**。故 self 是"该函数自身指令 + 未插桩子调用"的墙钟时间,只是比 inc 更贴近自身成本。 深度栈为 common(全程序一份)后,跨模块被调方只要被插桩即能正确扣除;修复前 各模块各持一份 internal 深度栈,跨模块子调用的 inc 记不进调用方 tchild, 表现为调用方 self ≈ inc 的假象(见 common linkage 说明)。 插桩规则与跳过条件: - 只处理有基本块(即已定义)且名字不以 __fhs_ 开头的函数;每函数计数器一律 internal(同名索引跨模块并存,不可合并);深度栈用 common 由链接器合并为 全程序唯一一份,初值 0。 - 入口代码置于入口块最前(无终结指令,不改变 CFG);条件写入用 select 实现, 不新增基本块、不修改任何既有分支/终结指令。 - 只在该函数的 ret 终结指令之前插桩;以 unreachable 等其它终结指令结尾的 块(如 noreturn 调用后的代码)没有"返回点",明确跳过、不伪造数值(数量见 报告,如需可另行统计)。 - 累加只做 load/sub/add/store/select 与读取计时原语,不改变返回值/参数/控制流。 """ depth: int = self._HotspotDepth i32: ir.IntType = ir.IntType(32) i64: ir.IntType = ir.IntType(64) c0_32: ir.Constant = ir.Constant(i32, 0) c0_64: ir.Constant = ir.Constant(i64, 0) c1_64: ir.Constant = ir.Constant(i64, 1) cneg1_64: ir.Constant = ir.Constant(i64, -1) # 深度栈三件套用 common linkage:每模块各自发射一份同名 tentative 定义, # 由链接器合并成"全程序唯一一份"(与 HandlesAssign 中多模块同名全局同法, # 避免 ld.lld duplicate symbol)。跨模块调用链必须共用同一张深度栈,否则 # 被调方在自模块栈里 slot=0(或 slot 落在错误父帧)→ 其 inc 永远记不到 # 调用方 __fhs_tchild[slot-1] → 调用方 self ≈ inc(子时间一分不扣)。 # 抖动槽 __fhs_tscratch 只写不读,留 internal 即可(各模块独立无影响)。 depth_gv: ir.GlobalVariable = ir.GlobalVariable( self.module, i64, name=self._UniqueGlobalName('__fhs_tdepth')) depth_gv.linkage = 'common' depth_gv.initializer = c0_64 arr_ty: ir.ArrayType = ir.ArrayType(i64, depth) tstart_gv: ir.GlobalVariable = ir.GlobalVariable( self.module, arr_ty, name=self._UniqueGlobalName('__fhs_tstart')) tstart_gv.linkage = 'common' tstart_gv.initializer = ir.Constant(arr_ty, None) tchild_gv: ir.GlobalVariable = ir.GlobalVariable( self.module, arr_ty, name=self._UniqueGlobalName('__fhs_tchild')) tchild_gv.linkage = 'common' tchild_gv.initializer = ir.Constant(arr_ty, None) scratch_gv: ir.GlobalVariable = ir.GlobalVariable( self.module, i64, name=self._UniqueGlobalName('__fhs_tscratch')) scratch_gv.linkage = 'internal' scratch_gv.initializer = c0_64 records: list[tuple[ir.GlobalVariable, ir.GlobalVariable, ir.GlobalVariable, str]] = [] for func in [f for f in self.module.functions if getattr(f, 'blocks', None)]: if func.name.startswith('__fhs_'): continue idx: int = len(records) self_gv: ir.GlobalVariable = ir.GlobalVariable( self.module, i64, name=self._UniqueGlobalName(f'__fhs_fself_{idx}')) self_gv.linkage = 'internal' self_gv.initializer = c0_64 inc_gv: ir.GlobalVariable = ir.GlobalVariable( self.module, i64, name=self._UniqueGlobalName(f'__fhs_finc_{idx}')) inc_gv.linkage = 'internal' inc_gv.initializer = c0_64 enter_gv: ir.GlobalVariable = ir.GlobalVariable( self.module, i64, name=self._UniqueGlobalName(f'__fhs_ftime_enter_{idx}')) enter_gv.linkage = 'internal' enter_gv.initializer = c0_64 # 入口:取 t0 → 压深度栈 → 记录本帧 slot → 进入次数 +1(置于入口块最前) entry: ir.Block = func.entry_basic_block eb: ir.IRBuilder = ir.IRBuilder(entry) eb.position_at_start(entry) slot_ptr: ir.Value = eb.alloca(i64, name=f'fhs_t_slot_{idx}') t0: ir.Value = self._EmitReadTimestamp(eb, name=f'fhs_t_t0_{idx}') d: ir.Value = eb.load(depth_gv, name=f'fhs_t_depth_{idx}') ok: ir.Value = eb.icmp_unsigned('<', d, ir.Constant(i64, depth), name=f'fhs_t_ok_{idx}') safe_idx: ir.Value = eb.select(ok, d, c0_64, name=f'fhs_t_idx_{idx}') eb.store(t0, eb.select( ok, eb.gep(tstart_gv, [c0_32, safe_idx]), scratch_gv)) eb.store(c0_64, eb.select( ok, eb.gep(tchild_gv, [c0_32, safe_idx]), scratch_gv)) eb.store(eb.select(ok, eb.add(d, c1_64), d), depth_gv) eb.store(eb.select(ok, d, cneg1_64), slot_ptr) eb.store(eb.add(eb.load(enter_gv), c1_64), enter_gv) # 每个 ret 之前:算 self/inc,回退深度,累加父帧 child for block in func.blocks: term: Any = block.terminator if not isinstance(term, ir.Ret): continue rb: ir.IRBuilder = ir.IRBuilder(block) rb.position_before(term) slot: ir.Value = rb.load(slot_ptr, name=f'fhs_t_slotv_{idx}') inok: ir.Value = rb.icmp_signed('>=', slot, c0_64, name=f'fhs_t_inok_{idx}') t1: ir.Value = self._EmitReadTimestamp(rb, name=f'fhs_t_t1_{idx}') safe_slot: ir.Value = rb.select(inok, slot, c0_64, name=f'fhs_t_slotidx_{idx}') inc_v: ir.Value = rb.sub( t1, rb.load(rb.gep(tstart_gv, [c0_32, safe_slot])), name=f'fhs_t_inc_{idx}') child_v: ir.Value = rb.load( rb.gep(tchild_gv, [c0_32, safe_slot]), name=f'fhs_t_child_{idx}') self_v: ir.Value = rb.sub(inc_v, child_v, name=f'fhs_t_self_{idx}') rb.store(rb.add(rb.load(self_gv), rb.select(inok, self_v, c0_64)), self_gv) rb.store(rb.add(rb.load(inc_gv), rb.select(inok, inc_v, c0_64)), inc_gv) rb.store(rb.select(inok, slot, rb.load(depth_gv)), depth_gv) pok: ir.Value = rb.icmp_signed('>', slot, c0_64, name=f'fhs_t_pok_{idx}') p_idx: ir.Value = rb.select(pok, rb.sub(slot, c1_64), c0_64) p_ptr: ir.Value = rb.gep(tchild_gv, [c0_32, p_idx]) p_target: ir.Value = rb.select(pok, p_ptr, scratch_gv) rb.store(rb.add(rb.load(p_target), inc_v), p_target) records.append((self_gv, inc_gv, enter_gv, func.name)) return records def _CreateHotspotSitesDump( self, path: str, records: list[tuple[ir.GlobalVariable, str, str, str, int]], module_id: str | None = None, ) -> ir.Function: """生成 void() 落盘函数:按调用次数降序把各调用点追加写入 。 落盘格式(追加): # sites \\n -> :\\n module_id 为空时省略行首 sha1 与头部 sha1(留空,不伪造)。 - 排序:落盘前用选择排序(两层运行期循环,IR 规模 O(N)、运行期 O(N^2) 仅 在落盘时触发一次)按计数降序输出,只输出 count>0 的调用点;计数相同时 按计数器编号 K 升序(稳定、可复现)。 - 源码位置:作为可选尾字段,格式 ":"(含前置单个空格);该位置 来自生成期挂在 call 指令对象上的 _src_file/_src_line(见 _HotspotSiteBuilder)。 生成期无法关联到 AST 的合成调用点取不到位置,则此字段整体省略(不伪造、不填默认值)。 - 函数签名为 void(),由 __fhs_dump_all(见 _CreateHotspotDumpAll)统一带守卫 调用,本函数自身不检查守卫。用 CRT fopen("a")/fprintf/fclose 落盘(沿用既有做法)。 """ n: int = len(records) i8: ir.IntType = ir.IntType(8) i32: ir.IntType = ir.IntType(32) i64: ir.IntType = ir.IntType(64) i8p: ir.PointerType = ir.PointerType(i8) i64p: ir.PointerType = ir.PointerType(i64) c0_8: ir.Constant = ir.Constant(i8, 0) c1_8: ir.Constant = ir.Constant(i8, 1) c0_32: ir.Constant = ir.Constant(i32, 0) c0_64: ir.Constant = ir.Constant(i64, 0) c1_64: ir.Constant = ir.Constant(i64, 1) fopen: ir.Function = self._DeclareExternalFunc('fopen', ir.FunctionType(i8p, [i8p, i8p])) fprintf: ir.Function = self._DeclareExternalFunc( 'fprintf', ir.FunctionType(i32, [i8p, i8p], var_arg=True)) fclose: ir.Function = self._DeclareExternalFunc('fclose', ir.FunctionType(i32, [i8p])) dump_fn: ir.Function = self._CreateOrReuseFuncDecl( self._UniqueGlobalName('__fhs_dump_sites'), ir.FunctionType(ir.VoidType(), [])) dump_fn.linkage = 'internal' b: ir.IRBuilder = ir.IRBuilder(dump_fn.append_basic_block(name='entry')) b.position_at_start(dump_fn.entry_basic_block) # 无匹配调用点:生成空落盘函数(不落盘、不建数组),保持调用链一致 if n == 0: b.ret_void() return dump_fn path_ptr: ir.Constant = self._create_string_global( path, name=self._UniqueGlobalName('__fhs_sites_path')) mode_ptr: ir.Constant = self._create_string_global( 'a', name=self._UniqueGlobalName('__fhs_sites_mode')) mid_ptr: ir.Constant | None = None hdr_fmt_ptr: ir.Constant | None = None if module_id: fmt_ptr: ir.Constant = self._create_string_global( '%s %s -> %s %llu%s\n', name=self._UniqueGlobalName('__fhs_sites_fmt')) hdr_fmt_ptr = self._create_string_global( '# sites %s\n', name=self._UniqueGlobalName('__fhs_sites_hdr_fmt')) mid_ptr = self._create_string_global( module_id, name=self._UniqueGlobalName('__fhs_sites_module_id')) else: fmt_ptr = self._create_string_global( '%s -> %s %llu%s\n', name=self._UniqueGlobalName('__fhs_sites_fmt')) fp: ir.Value = b.call(fopen, [path_ptr, mode_ptr], name='fhs_sites_fp') if module_id and hdr_fmt_ptr is not None: b.call(fprintf, [fp, hdr_fmt_ptr, mid_ptr], name='fhs_sites_write_hdr') # 归属元数据全局数组:运行期按索引取 "调用方名 / 被调方基名 / 计数器地址" caller_ty: ir.ArrayType = ir.ArrayType(i8p, n) callee_ty: ir.ArrayType = ir.ArrayType(i8p, n) cntptr_ty: ir.ArrayType = ir.ArrayType(i64p, n) caller_gv: ir.GlobalVariable = ir.GlobalVariable( self.module, caller_ty, name=self._UniqueGlobalName('__fhs_sites_caller')) caller_gv.linkage = 'internal' caller_gv.global_constant = True caller_gv.initializer = ir.Constant(caller_ty, [ self._create_string_global(rec[1], name=self._UniqueGlobalName(f'__fhs_sites_caller_{k}')) for k, rec in enumerate(records) ]) callee_gv: ir.GlobalVariable = ir.GlobalVariable( self.module, callee_ty, name=self._UniqueGlobalName('__fhs_sites_callee')) callee_gv.linkage = 'internal' callee_gv.global_constant = True callee_gv.initializer = ir.Constant(callee_ty, [ self._create_string_global(rec[2], name=self._UniqueGlobalName(f'__fhs_sites_callee_{k}')) for k, rec in enumerate(records) ]) # 源码位置全局数组:每项形如 " :"(含前置空格);取不到位置则为空串 loc_ty: ir.ArrayType = ir.ArrayType(i8p, n) loc_gv: ir.GlobalVariable = ir.GlobalVariable( self.module, loc_ty, name=self._UniqueGlobalName('__fhs_sites_loc')) loc_gv.linkage = 'internal' loc_gv.global_constant = True loc_gv.initializer = ir.Constant(loc_ty, [ self._create_string_global( f' {rec[3]}:{rec[4]}' if (rec[3] and rec[4] > 0) else '', name=self._UniqueGlobalName(f'__fhs_sites_loc_{k}')) for k, rec in enumerate(records) ]) cnt_gv: ir.GlobalVariable = ir.GlobalVariable( self.module, cntptr_ty, name=self._UniqueGlobalName('__fhs_sites_cnt_ptrs')) cnt_gv.linkage = 'internal' cnt_gv.global_constant = True cnt_gv.initializer = ir.Constant(cntptr_ty, [ ir.Constant(i64p, rec[0].get_reference()) for rec in records ]) # 全部 alloca 置于 entry 块:避免在循环体内 alloca 导致每次迭代爆栈 used: ir.Value = b.alloca(ir.ArrayType(i8, n), name='fhs_sites_used') for i in range(n): b.store(c0_8, b.gep(used, [c0_32, ir.Constant(i32, i)])) k_ptr: ir.Value = b.alloca(i64, name='fhs_sites_k') best_ptr: ir.Value = b.alloca(i64, name='fhs_sites_best') bestv_ptr: ir.Value = b.alloca(i64, name='fhs_sites_bestv') i_ptr: ir.Value = b.alloca(i64, name='fhs_sites_i') b.store(c0_64, k_ptr) # 选择排序:外层 k 走 0..n-1,每轮在未输出项里选计数最大者(计数相同取 K 小者) loop_k: ir.Block = dump_fn.append_basic_block(name='fhs_sites_sort_k') body_k: ir.Block = dump_fn.append_basic_block(name='fhs_sites_sort_k_body') loop_i: ir.Block = dump_fn.append_basic_block(name='fhs_sites_sort_i') body_i: ir.Block = dump_fn.append_basic_block(name='fhs_sites_sort_i_body') scan_i: ir.Block = dump_fn.append_basic_block(name='fhs_sites_sort_i_scan') upd_i: ir.Block = dump_fn.append_basic_block(name='fhs_sites_sort_i_upd') next_i: ir.Block = dump_fn.append_basic_block(name='fhs_sites_sort_i_next') end_i: ir.Block = dump_fn.append_basic_block(name='fhs_sites_sort_i_end') print_k: ir.Block = dump_fn.append_basic_block(name='fhs_sites_print_k') after_k: ir.Block = dump_fn.append_basic_block(name='fhs_sites_sort_end') b.branch(loop_k) # loop_k: if k < n -> body_k else after_k b.position_at_end(loop_k) k_val: ir.Value = b.load(k_ptr) b.cbranch(b.icmp_signed('<', k_val, ir.Constant(i64, n)), body_k, after_k) # body_k: best=-1, bestv=0, i=0; -> loop_i b.position_at_end(body_k) b.store(ir.Constant(i64, -1), best_ptr) b.store(c0_64, bestv_ptr) b.store(c0_64, i_ptr) b.branch(loop_i) # loop_i: if i < n -> body_i else end_i b.position_at_end(loop_i) i_val: ir.Value = b.load(i_ptr) b.cbranch(b.icmp_signed('<', i_val, ir.Constant(i64, n)), body_i, end_i) # body_i: if used[i]==0 -> scan_i else next_i b.position_at_end(body_i) used_i: ir.Value = b.load(b.gep(used, [c0_32, i_val])) b.cbranch(b.icmp_signed('==', used_i, c0_8), scan_i, next_i) # scan_i: v = *cnt_ptrs[i]; if v > bestv -> upd_i else next_i b.position_at_end(scan_i) cnt_p: ir.Value = b.load(b.gep(cnt_gv, [c0_32, i_val])) v_val: ir.Value = b.load(cnt_p) b.cbranch(b.icmp_signed('>', v_val, b.load(bestv_ptr)), upd_i, next_i) # upd_i: bestv=v, best=i; -> next_i b.position_at_end(upd_i) b.store(v_val, bestv_ptr) b.store(i_val, best_ptr) b.branch(next_i) # next_i: i++; -> loop_i b.position_at_end(next_i) b.store(b.add(b.load(i_ptr), c1_64), i_ptr) b.branch(loop_i) # end_i: best<0(剩余全为 0 计数)-> after_k;否则 print_k b.position_at_end(end_i) best_val: ir.Value = b.load(best_ptr) b.cbranch(b.icmp_signed('<', best_val, c0_64), after_k, print_k) # print_k: used[best]=1; fprintf(...); k++; -> loop_k b.position_at_end(print_k) idx: ir.Value = b.load(best_ptr) b.store(c1_8, b.gep(used, [c0_32, idx])) caller_ptr: ir.Value = b.load(b.gep(caller_gv, [c0_32, idx])) callee_ptr: ir.Value = b.load(b.gep(callee_gv, [c0_32, idx])) loc_ptr: ir.Value = b.load(b.gep(loc_gv, [c0_32, idx])) if module_id and mid_ptr is not None: b.call(fprintf, [fp, fmt_ptr, mid_ptr, caller_ptr, callee_ptr, b.load(bestv_ptr), loc_ptr], name='fhs_sites_write') else: b.call(fprintf, [fp, fmt_ptr, caller_ptr, callee_ptr, b.load(bestv_ptr), loc_ptr], name='fhs_sites_write') b.store(b.add(b.load(k_ptr), c1_64), k_ptr) b.branch(loop_k) # after_k: fclose; ret_void b.position_at_end(after_k) b.call(fclose, [fp]) b.ret_void() return dump_fn def _CreateHotspotTimeDump( self, path: str, records: list[tuple[ir.GlobalVariable, ir.GlobalVariable, ir.GlobalVariable, str]], module_id: str | None = None, ) -> ir.Function: """生成 void() 落盘函数:按**自身耗时**降序写入每函数计时文件。 落盘格式(追加): # time \\n \\n module_id 为空时省略头部 sha1 行(留空,不伪造)。列含义(单位均为 tick): - self_tick:自身耗时(exclusive,见 _EmitHotspotTimeCounters),已扣除本 函数内**被插桩**子调用的 inclusive 之和;但**未插桩**的被调方(间接调用、 无定义体的外部函数如 strcmp/memcpy、深度溢出跳过的调用)仍计入其中。 - inc_tick:包含式耗时(inclusive,含全部被调方)。 - enter_count:被进入次数(含递归/重入),用于交叉校验。 - 排序:与调用点计数同法,落盘前用选择排序按 self_tick 降序输出,只输出 进入次数 >0 的函数;self_tick 相同时按编号 N 升序(稳定、可复现)。 - 函数签名为 void(),由 __fhs_dump_all(见 _CreateHotspotDumpAll)统一带守卫 调用,本函数自身不检查守卫。用 CRT fopen("a")/fprintf/fclose 落盘(沿用既有做法)。 """ n: int = len(records) i8: ir.IntType = ir.IntType(8) i32: ir.IntType = ir.IntType(32) i64: ir.IntType = ir.IntType(64) i8p: ir.PointerType = ir.PointerType(i8) i64p: ir.PointerType = ir.PointerType(i64) c0_8: ir.Constant = ir.Constant(i8, 0) c1_8: ir.Constant = ir.Constant(i8, 1) c0_32: ir.Constant = ir.Constant(i32, 0) c0_64: ir.Constant = ir.Constant(i64, 0) c1_64: ir.Constant = ir.Constant(i64, 1) fopen: ir.Function = self._DeclareExternalFunc('fopen', ir.FunctionType(i8p, [i8p, i8p])) fprintf: ir.Function = self._DeclareExternalFunc( 'fprintf', ir.FunctionType(i32, [i8p, i8p], var_arg=True)) fclose: ir.Function = self._DeclareExternalFunc('fclose', ir.FunctionType(i32, [i8p])) dump_fn: ir.Function = self._CreateOrReuseFuncDecl( self._UniqueGlobalName('__fhs_dump_time'), ir.FunctionType(ir.VoidType(), [])) dump_fn.linkage = 'internal' b: ir.IRBuilder = ir.IRBuilder(dump_fn.append_basic_block(name='entry')) b.position_at_start(dump_fn.entry_basic_block) # 无计时对象(模块内无用户函数定义):生成空落盘函数占位,保持调用链一致 if n == 0: b.ret_void() return dump_fn path_ptr: ir.Constant = self._create_string_global( path, name=self._UniqueGlobalName('__fhs_ftime_path')) mode_ptr: ir.Constant = self._create_string_global( 'a', name=self._UniqueGlobalName('__fhs_ftime_mode')) fmt_ptr: ir.Constant = self._create_string_global( '%s %llu %llu %llu\n', name=self._UniqueGlobalName('__fhs_ftime_fmt')) fp: ir.Value = b.call(fopen, [path_ptr, mode_ptr], name='fhs_ftime_fp') if module_id: hdr_fmt_ptr: ir.Constant = self._create_string_global( '# time %s name self inc enter (tick; self excludes instrumented callees)\n', name=self._UniqueGlobalName('__fhs_ftime_hdr_fmt')) mid_ptr: ir.Constant = self._create_string_global( module_id, name=self._UniqueGlobalName('__fhs_ftime_module_id')) b.call(fprintf, [fp, hdr_fmt_ptr, mid_ptr], name='fhs_ftime_write_hdr') # 归属元数据全局数组:运行期按索引取 "函数名 / 自身耗时地址 / 包含式地址 / 进入次数地址" name_ty: ir.ArrayType = ir.ArrayType(i8p, n) selfptr_ty: ir.ArrayType = ir.ArrayType(i64p, n) incptr_ty: ir.ArrayType = ir.ArrayType(i64p, n) enterptr_ty: ir.ArrayType = ir.ArrayType(i64p, n) name_gv: ir.GlobalVariable = ir.GlobalVariable( self.module, name_ty, name=self._UniqueGlobalName('__fhs_ftime_name')) name_gv.linkage = 'internal' name_gv.global_constant = True name_gv.initializer = ir.Constant(name_ty, [ self._create_string_global(rec[3], name=self._UniqueGlobalName(f'__fhs_ftime_name_{k}')) for k, rec in enumerate(records) ]) self_gv: ir.GlobalVariable = ir.GlobalVariable( self.module, selfptr_ty, name=self._UniqueGlobalName('__fhs_ftime_self_ptrs')) self_gv.linkage = 'internal' self_gv.global_constant = True self_gv.initializer = ir.Constant(selfptr_ty, [ ir.Constant(i64p, rec[0].get_reference()) for rec in records ]) inc_gv: ir.GlobalVariable = ir.GlobalVariable( self.module, incptr_ty, name=self._UniqueGlobalName('__fhs_ftime_inc_ptrs')) inc_gv.linkage = 'internal' inc_gv.global_constant = True inc_gv.initializer = ir.Constant(incptr_ty, [ ir.Constant(i64p, rec[1].get_reference()) for rec in records ]) enter_gv: ir.GlobalVariable = ir.GlobalVariable( self.module, enterptr_ty, name=self._UniqueGlobalName('__fhs_ftime_enter_ptrs')) enter_gv.linkage = 'internal' enter_gv.global_constant = True enter_gv.initializer = ir.Constant(enterptr_ty, [ ir.Constant(i64p, rec[2].get_reference()) for rec in records ]) # 全部 alloca 置于 entry 块:避免在循环体内 alloca 导致每次迭代爆栈 used: ir.Value = b.alloca(ir.ArrayType(i8, n), name='fhs_ftime_used') for i in range(n): b.store(c0_8, b.gep(used, [c0_32, ir.Constant(i32, i)])) k_ptr: ir.Value = b.alloca(i64, name='fhs_ftime_k') best_ptr: ir.Value = b.alloca(i64, name='fhs_ftime_best') bestv_ptr: ir.Value = b.alloca(i64, name='fhs_ftime_bestv') i_ptr: ir.Value = b.alloca(i64, name='fhs_ftime_i') b.store(c0_64, k_ptr) # 选择排序:外层 k 走 0..n-1,每轮在未输出项里选自身耗时(self)最大者(相同取 N 小者) loop_k: ir.Block = dump_fn.append_basic_block(name='fhs_ftime_sort_k') body_k: ir.Block = dump_fn.append_basic_block(name='fhs_ftime_sort_k_body') loop_i: ir.Block = dump_fn.append_basic_block(name='fhs_ftime_sort_i') body_i: ir.Block = dump_fn.append_basic_block(name='fhs_ftime_sort_i_body') scan_i: ir.Block = dump_fn.append_basic_block(name='fhs_ftime_sort_i_scan') upd_i: ir.Block = dump_fn.append_basic_block(name='fhs_ftime_sort_i_upd') next_i: ir.Block = dump_fn.append_basic_block(name='fhs_ftime_sort_i_next') end_i: ir.Block = dump_fn.append_basic_block(name='fhs_ftime_sort_i_end') print_k: ir.Block = dump_fn.append_basic_block(name='fhs_ftime_print_k') after_k: ir.Block = dump_fn.append_basic_block(name='fhs_ftime_sort_end') b.branch(loop_k) b.position_at_end(loop_k) k_val: ir.Value = b.load(k_ptr) b.cbranch(b.icmp_signed('<', k_val, ir.Constant(i64, n)), body_k, after_k) b.position_at_end(body_k) b.store(ir.Constant(i64, -1), best_ptr) b.store(c0_64, bestv_ptr) b.store(c0_64, i_ptr) b.branch(loop_i) b.position_at_end(loop_i) i_val: ir.Value = b.load(i_ptr) b.cbranch(b.icmp_signed('<', i_val, ir.Constant(i64, n)), body_i, end_i) b.position_at_end(body_i) used_i: ir.Value = b.load(b.gep(used, [c0_32, i_val])) b.cbranch(b.icmp_signed('==', used_i, c0_8), scan_i, next_i) # scan_i: v = *self_ptrs[i](自身耗时);同时把进入次数非 0 作为"有样本"判据 b.position_at_end(scan_i) self_p: ir.Value = b.load(b.gep(self_gv, [c0_32, i_val])) v_val: ir.Value = b.load(self_p) enter_p: ir.Value = b.load(b.gep(enter_gv, [c0_32, i_val])) entered: ir.Value = b.load(enter_p) has: ir.Value = b.icmp_signed('>', entered, c0_64) better: ir.Value = b.icmp_signed('>', v_val, b.load(bestv_ptr)) b.cbranch(b.and_(has, better), upd_i, next_i) b.position_at_end(upd_i) b.store(v_val, bestv_ptr) b.store(i_val, best_ptr) b.branch(next_i) b.position_at_end(next_i) b.store(b.add(b.load(i_ptr), c1_64), i_ptr) b.branch(loop_i) # end_i: best<0(剩余均为 0 或未被进入)-> after_k;否则 print_k b.position_at_end(end_i) best_val: ir.Value = b.load(best_ptr) b.cbranch(b.icmp_signed('<', best_val, c0_64), after_k, print_k) b.position_at_end(print_k) idx: ir.Value = b.load(best_ptr) b.store(c1_8, b.gep(used, [c0_32, idx])) name_ptr: ir.Value = b.load(b.gep(name_gv, [c0_32, idx])) sp: ir.Value = b.load(b.gep(self_gv, [c0_32, idx])) ip: ir.Value = b.load(b.gep(inc_gv, [c0_32, idx])) ep: ir.Value = b.load(b.gep(enter_gv, [c0_32, idx])) b.call(fprintf, [fp, fmt_ptr, name_ptr, b.load(sp), b.load(ip), b.load(ep)], name='fhs_ftime_write') b.store(b.add(b.load(k_ptr), c1_64), k_ptr) b.branch(loop_k) # after_k: fclose; ret_void b.position_at_end(after_k) b.call(fclose, [fp]) b.ret_void() return dump_fn def _CreateHotspotDumpAll( self, sites_dump: ir.Function, time_dump: ir.Function, ) -> tuple[ir.Function, ir.Function]: """生成模块级"总落盘"链路:幂等守卫 + void() 总落盘函数 + Ctrl+C 处理器。 同一模块**只落盘一次**:Ctrl+C 与 atexit 可能先后都触发(例如处理器返回后 进程继续退出),故用每模块一份的 internal i32 @__fhs_dumped(初值 0)作幂等 守卫: - void @__fhs_dump_all():先读守卫,非 0 直接返回;否则置 1 后依次调用 sites_dump(调用点计数)与 time_dump(每函数计时)——两个落盘函数本身 不检查守卫; - i32 @__fhs_ctl_handler(i32):直接调用 __fhs_dump_all() 后返回 FALSE(0), 供 SetConsoleCtrlHandler 注册(保持"仍让进程终止"的既有行为)。 atexit 与 Ctrl+C 共用同一守卫,先到者落盘、后到者直接返回,避免重复段/行。 返回 (Ctrl+C 处理器, 总落盘函数)。 """ i32: ir.IntType = ir.IntType(32) guard: ir.GlobalVariable = ir.GlobalVariable( self.module, i32, name=self._UniqueGlobalName('__fhs_dumped')) guard.linkage = 'internal' guard.initializer = ir.Constant(i32, 0) dump_all: ir.Function = self._CreateOrReuseFuncDecl( self._UniqueGlobalName('__fhs_dump_all'), ir.FunctionType(ir.VoidType(), [])) dump_all.linkage = 'internal' b: ir.IRBuilder = ir.IRBuilder(dump_all.append_basic_block(name='entry')) do_bb: ir.Block = dump_all.append_basic_block(name='fhs_dump_do') done_bb: ir.Block = dump_all.append_basic_block(name='fhs_dump_done') b.position_at_start(dump_all.entry_basic_block) cur: ir.Value = b.load(guard, name='fhs_dumped_load') b.cbranch(b.icmp_signed('!=', cur, ir.Constant(i32, 0)), done_bb, do_bb) b.position_at_end(do_bb) b.store(ir.Constant(i32, 1), guard) b.call(sites_dump, []) b.call(time_dump, []) b.branch(done_bb) b.position_at_end(done_bb) b.ret_void() ctl_handler: ir.Function = self._CreateOrReuseFuncDecl( self._UniqueGlobalName('__fhs_ctl_handler'), ir.FunctionType(i32, [i32])) ctl_handler.linkage = 'internal' cb: ir.IRBuilder = ir.IRBuilder(ctl_handler.append_basic_block(name='entry')) cb.call(dump_all, []) cb.ret(ir.Constant(i32, 0)) return ctl_handler, dump_all def _EmitHotspotDumpRegistration( self, builder: ir.IRBuilder, ctl_handler: ir.Function, dump_all: ir.Function, ) -> None: """向给定构建器发射两处落盘触发注册: - SetConsoleCtrlHandler(bitcast @__fhs_ctl_handler, i8*), TRUE):Ctrl+C 路径; - atexit(bitcast @__fhs_dump_all, i8*)):正常返回 / exit() 退出路径。 二者共用 __fhs_dumped 幂等守卫(见 _CreateHotspotDumpAll),任一先触发即落盘, 后触发者直接返回,不重复写段/行。atexit 声明沿用本文件既有外部函数写法(见 fopen/SetConsoleCtrlHandler:ir.FunctionType + ir.PointerType(ir.IntType(8))); 本仓库 IR 为 typed pointer(函数指针统一以 i8* 传递),故以 bitcast 取函数地址。 返回值为 int,此处有意忽略(拿不到 atexit 会在链接期报错,不静默降级)。 覆盖 libc exit()(含正常 return 与 sys.exit 的 fail-fast);不覆盖 _exit()/ abort()/TerminateProcess/崩溃信号等不经过 exit() 的终止路径。 """ i32: ir.IntType = ir.IntType(32) i8p: ir.PointerType = ir.PointerType(ir.IntType(8)) set_handler: ir.Function = self._DeclareExternalFunc( 'SetConsoleCtrlHandler', ir.FunctionType(i32, [i8p, i32])) atexit_fn: ir.Function = self._DeclareExternalFunc( 'atexit', ir.FunctionType(i32, [i8p])) builder.call(set_handler, [builder.bitcast(ctl_handler, i8p), ir.Constant(i32, 1)]) builder.call(atexit_fn, [builder.bitcast(dump_all, i8p)]) def _RegisterHotspotSitesViaCtor(self, ctl_handler: ir.Function, dump_all: ir.Function) -> None: """用 @llvm.global_ctors 在进程启动期为本模块注册热点落盘触发。 生成 internal void @__fhs_sites_ctor()(内部调用 SetConsoleCtrlHandler 注册 Ctrl+C 处理器 __fhs_ctl_handler,并调用 atexit 注册退出处理器 __fhs_dump_all), 并写入 appending linkage 的 @llvm.global_ctors —— 每个模块一份,链接器按 appending 语义把各模块的构造器拼接进 .init_array,进程启动时各执行一次。相比 "在 main 入口注册",本方案不依赖 main 所在模块,适配 Projectrans 的"每源文件 独立翻译单元"模型。 模块内若已存在 @llvm.global_ctors 视为冲突,显式报错(不静默降级)。 """ i32: ir.IntType = ir.IntType(32) i8p: ir.PointerType = ir.PointerType(ir.IntType(8)) void_fnptr: ir.PointerType = ir.PointerType(ir.FunctionType(ir.VoidType(), [])) ctor: ir.Function = self._CreateOrReuseFuncDecl( self._UniqueGlobalName('__fhs_sites_ctor'), ir.FunctionType(ir.VoidType(), [])) ctor.linkage = 'internal' cb: ir.IRBuilder = ir.IRBuilder(ctor.append_basic_block(name='entry')) self._EmitHotspotDumpRegistration(cb, ctl_handler, dump_all) cb.ret_void() if 'llvm.global_ctors' in self.module.globals: raise RuntimeError( '--hotspot-sites: 模块内已存在 @llvm.global_ctors,无法注册热点处理器(不静默降级)') elem_ty: ir.LiteralStructType = ir.LiteralStructType([i32, void_fnptr, i8p]) arr_ty: ir.ArrayType = ir.ArrayType(elem_ty, 1) entry_val: ir.Constant = ir.Constant(elem_ty, [ ir.Constant(i32, 65535), # 传函数引用字符串(非 Function 对象):与 _create_string_global 同法, # 否则 llvmlite 会以 str(Function) 渲染整个 define 体 ir.Constant(void_fnptr, ctor.get_reference()), ir.Constant(i8p, None), ]) gv: ir.GlobalVariable = ir.GlobalVariable(self.module, arr_ty, name='llvm.global_ctors') gv.linkage = 'appending' gv.initializer = ir.Constant(arr_ty, [entry_val]) def _RegisterTracebackInstallViaCtor(self) -> None: """用 @llvm.global_ctors 在进程启动期调用本模块的 Install()(--traceback)。 仅在"本翻译单元就是 includes/traceback.vp"时由 StructGen.finalize() 调用 (traceback_ctor 由 Phase2Translator._translate_file 按源文件名置位)。 生成 internal void @__traceback_install_ctor()(entry 块直接 call 本模块的 Install() 并忽略其 int 返回值),再写入 appending linkage 的 @llvm.global_ctors —— 每模块一份,链接器按 appending 语义拼进 .init_array, 进程启动时执行一次。 为什么用构造器而不是"在 main 入口调用":适配 Projectrans 的"每源文件独立 翻译单元"模型(不依赖哪个模块含 main),且 CRT 初始化期即执行,能覆盖 "main 之前就卡死"的场景 —— 这正是本功能的用途。 同模块内取 Install 符号(不走跨模块声明),避免跨模块符号名/mangling 耦合。 fail-fast:找不到 Install 符号、或模块内已存在 @llvm.global_ctors 时显式 报错(不静默降级 —— 否则产物缺注册、功能静默失效)。 """ install_fn: ir.Function | None = self._find_function('Install') if install_fn is None: raise RuntimeError( '--traceback: 在 traceback 模块内找不到 Install() 函数,' '无法注入启动期构造器(不静默降级)') i32: ir.IntType = ir.IntType(32) i8p: ir.PointerType = ir.PointerType(ir.IntType(8)) void_fnptr: ir.PointerType = ir.PointerType(ir.FunctionType(ir.VoidType(), [])) ctor: ir.Function = self._CreateOrReuseFuncDecl( self._UniqueGlobalName('__traceback_install_ctor'), ir.FunctionType(ir.VoidType(), [])) ctor.linkage = 'internal' cb: ir.IRBuilder = ir.IRBuilder(ctor.append_basic_block(name='entry')) cb.call(install_fn, []) # Install() 无参;返回值(int)有意忽略 cb.ret_void() if 'llvm.global_ctors' in self.module.globals: raise RuntimeError( '--traceback: 模块内已存在 @llvm.global_ctors,' '无法注入 traceback 启动期构造器(不静默降级)') elem_ty: ir.LiteralStructType = ir.LiteralStructType([i32, void_fnptr, i8p]) arr_ty: ir.ArrayType = ir.ArrayType(elem_ty, 1) entry_val: ir.Constant = ir.Constant(elem_ty, [ ir.Constant(i32, 65535), # 传函数引用字符串(非 Function 对象):与 _RegisterHotspotSitesViaCtor 同法, # 否则 llvmlite 会以 str(Function) 渲染整个 define 体 ir.Constant(void_fnptr, ctor.get_reference()), ir.Constant(i8p, None), ]) gv: ir.GlobalVariable = ir.GlobalVariable(self.module, arr_ty, name='llvm.global_ctors') gv.linkage = 'appending' gv.initializer = ir.Constant(arr_ty, [entry_val]) def _InstallHotspotSites(self) -> None: """安装调用点热点统计(--hotspot-sites):种子 CLI 单模块路径。 顺序:先装调用点计数(其扫描只针对用户代码,不会把随后插入的计时原语 调用计入),再装每函数计时;随后在 main 入口用 "w" 各清空一次调用点计数 文件与计时文件,并注册落盘触发(Ctrl+C 处理器 SetConsoleCtrlHandler + 退出处理器 atexit,程序启动期各执行一次)。仅在 hotspot_sites_file 非 None (用户传入 --hotspot-sites)时被调用;未启用时该方法不会执行,IR 输出与 既有行为一致(零影响)。 """ path: str | None = self.hotspot_sites_file if not path: raise RuntimeError('--hotspot-sites: 需要提供调用点计数输出文件路径') module_id: str | None = self.module_sha1 or None defs, records = self._EmitHotspotSiteCounters() time_records = self._EmitHotspotTimeCounters() time_path: str = self._HotspotTimePath(path) sites_dump: ir.Function = self._CreateHotspotSitesDump( path, records, module_id=module_id) time_dump: ir.Function = self._CreateHotspotTimeDump( time_path, time_records, module_id=module_id) ctl_handler, dump_all = self._CreateHotspotDumpAll(sites_dump, time_dump) # main 入口各清空一次历史文件(require_main=True → 无 main 显式报错) main_func: ir.Function | None = self._InstallHotspotTruncate( defs, path, require_main=True, flag='--hotspot-sites') if main_func is None: raise RuntimeError( '--hotspot-sites: 找不到 main 函数定义,无法注册热点落盘触发') self._InstallHotspotTruncate( defs, time_path, require_main=True, flag='--hotspot-sites') # 在 main 入口注册落盘触发(清空/截断之后、程序 body 之前执行) mb: ir.IRBuilder = ir.IRBuilder(main_func.entry_basic_block) mb.position_at_start(main_func.entry_basic_block) self._EmitHotspotDumpRegistration(mb, ctl_handler, dump_all) def _InstallHotspotSitesModuleLocal(self) -> None: """安装调用点热点统计(--hotspot-sites):Projectrans 多模块路径。 每模块自带 internal 调用点计数器 + internal 每函数计时器 + internal 落盘函数 (调用点计数追加写最终 ,计时追加写同目录 hotspot_time.txt),并各自 通过 @llvm.global_ctors 在进程启动期注册落盘触发:Ctrl+C 处理器 (SetConsoleCtrlHandler) 与退出处理器 (atexit)(见 _RegisterHotspotSitesViaCtor)。 仅含 main 的模块额外在 main 入口以 "w" 各清空一次文件,保证"一次运行一份结果"。 未启用(hotspot_sites_file 为 None)时本方法不会被调用。 """ path: str | None = self.hotspot_sites_file if not path: raise RuntimeError('--hotspot-sites: 需要提供调用点计数输出文件路径') module_id: str = getattr(self, 'module_sha1', None) or '' defs, records = self._EmitHotspotSiteCounters() time_records = self._EmitHotspotTimeCounters() time_path: str = self._HotspotTimePath(path) sites_dump: ir.Function = self._CreateHotspotSitesDump( path, records, module_id=module_id or None) time_dump: ir.Function = self._CreateHotspotTimeDump( time_path, time_records, module_id=module_id or None) ctl_handler, dump_all = self._CreateHotspotDumpAll(sites_dump, time_dump) self._RegisterHotspotSitesViaCtor(ctl_handler, dump_all) # 非 main 模块无 main、不需要截断(返回 None 不报错):其它模块的写入 # 依赖含 main 模块在启动期清空,不会互相覆盖。 self._InstallHotspotTruncate(defs, path, flag='--hotspot-sites') self._InstallHotspotTruncate(defs, time_path, flag='--hotspot-sites')