Files
TransPyC/lib/core/LLVMCG/BaseGen.py
T
TermiNexus 21c76541e5 fix(mangle): 仅 extern 函数保持裸名
删除 _export_funcs 裸名分支,普通 def/export def 恢复模块 SHA1 前缀,消除 os getcwd/system 等重复符号。
2026-10-19 12:54:16 +08:00

2646 lines
150 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from __future__ import annotations
from typing import Any
import vast as ast
import os
import re
import llvmlite.ir as ir
from lib.core import TypeSystem as _t
from lib.core.TypeSystem 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 脱糖为 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 = <AST*>` 就被判成被禁止的向下转型
# (现场 includes/ast/parser.vp:1919:%AST* → %Name*)。
self.var_annotated_class: dict[str, str] = {}
self.var_ptr_element: dict[str, bool] = {}
# 全局变量的指针元素标志(持久,不受函数重置影响)
# 用于全局变量 _argv: bytes | 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': 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[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[<sha1>.AST*]' 表示元素为指向 AST 的指针),若一并
移除会让声明端引用名('list[<sha1>.AST*]')与定义端注册名
('list[<sha1>.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', 'CTypedef', '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()
# 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
# 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: State` 与
# ast/exprs.vp 的 `class Constant` 同名;谁后插入谁占住裸名),也必须建成
# 结构体。否则此处返回 i32 ⇒ 上游 PtrCount=1 叠加后变成 i32*,
# 与调用点实参 %"<sha1>.Constant"* 分裂(现场:constant_get_str_val 的
# 形参 i32*)。类型注解语境下枚举**成员**从不是类型,判据只能是"是否有类定义"。
# 必须排除**枚举类型本身**(如 `class ASTKind(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', 'CTypedef', '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(非整数)
# 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:
"""检查名称是否是 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(TypeSystem, 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:
"""尝试将 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_extern_funcs:
# extern def 一律使用裸名(C/Win32 符号不加模块 SHA1 前缀)
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 全名
'<sha1>.Keyword'),"函数签名里的 self 用哪一份"与"StructGen 具化的是哪一份"
并不保证一致;一旦被 GEP 的那份是 opaque,llvmlite 在编译期算元素类型即抛裸
TypeError(现场:includes/ast/astaux.vp:165 `self.arg = _copy_str(pool, arg)`
的基址 pointee 是 opaque 的 `%"<sha1>.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 `= "CPtr"`,
_get_node_info 印 1142 且源码取到 1142 行的 `return "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:
"""把「真正被转换的值节点」渲染成一段诊断文本,供隐式转换禁令引用。
报文风格照本仓既有「表达式文本 + 位置 + 源码片段」:
表达式 '<unparse>'(<节点形态>,位置 文件:行:列),源码: <该行>
关键 ①:**列号按有符号打印**——未知(无属性 / <0)时打印 '-',绝不把 col_offset
的 -1 当无符号打成 4294967295(此前 VPC 侧诊断错位的根因)。取不到节点时
回退当前语句上下文(_get_node_info 风格),不再固定打印外层 Assign。
关键 ②:**行号必须先还原为磁盘行号**(_DiskLineno)——节点行号含文件头
自动注入 import 的偏移,直接用来读源码会读到下一行(现场:真实错误行
1141,这里印成 1142 且源码显示出 1142 行的 `return "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 '<expr>'
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`。
`CConst` 在本仓库解析为 **DataConst**(t 模块分支:HandlesTypeMerge.py:192-196、
TypeAnnotationResolver.py:382-389),其字段语义是「指向的数据 const」
(HandlesBase.py:76-77)。因此:
- 非指针类型(PtrCount==0):该限定退化为「该对象自身 const」⇒
`a: const int` / `a: CConst | int` 的 a 禁止赋值/增量赋值/元素赋值;
- 指针类型(PtrCount>=1):限定只作用于 pointee ⇒ `const T *p` 的 p
仍可重新赋值(C 语义),**不**登记为 const 变量。
后者就是全仓既有两处 `CConst | 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:
"""注解子树里是否带指定限定符(读取 AST 的 Markers 字段)。
AttrName 兼容旧书写名(CConst/CVolatile)与关键字名(const/volatile);
修饰信息一律来自 `Markers`,不再匹配裸名/Attribute 文本。
"""
if Annotation is None:
return False
from lib.core.SymbolUtils import RejectTypeUnionAnnotation
RejectTypeUnionAnnotation(Annotation)
MarkerName: str = {
'CConst': 'const', 'CVolatile': 'volatile',
'const': 'const', 'volatile': 'volatile',
}.get(AttrName, AttrName)
for n in ast.walk(Annotation):
if MarkerName in (getattr(n, 'Markers', None) or ()):
return True
return False
@staticmethod
def _AnnotationHasPointeeVolatile(Annotation: Any) -> bool:
"""注解是否为「指向物 volatile」(`volatile T*` / `volatile rawptr[T]`)。
读取 AST 字段:`rawptr[...]` / `CPtr[...]` 节点自身的 Markers 含
'volatile',或其下标子树的 Markers 含 'volatile'(多级指针不另行展开)。
"""
if not isinstance(Annotation, ast.Subscript):
return False
base: Any = Annotation.value
BaseName: str = ''
if isinstance(base, ast.Name):
BaseName = base.id
elif isinstance(base, ast.Attribute):
BaseName = base.attr
if BaseName not in ('rawptr', 'CPtr'):
return False
if 'volatile' in (getattr(Annotation, 'Markers', None) or ()):
return True
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: 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` 描述符 / `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")方式写
<path>,若不清空会累积历史运行的数据。清空必须满足两点:
- 只在含 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_<K> 的顺序。源码位置来自生成期挂在
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_<N> 里计入进入次数——溢出是"拿不到数值",明确跳过而非伪造。
_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() 落盘函数:按调用次数降序把各调用点追加写入 <path>。
落盘格式(追加):
# sites <module_sha1>\\n
<module_sha1> <caller_func> -> <callee_base> <count> <file>:<line>\\n
module_id 为空时省略行首 sha1 与头部 sha1(留空,不伪造)。
- 排序:落盘前用选择排序(两层运行期循环,IR 规模 O(N)、运行期 O(N^2) 仅
在落盘时触发一次)按计数降序输出,只输出 count>0 的调用点;计数相同时
按计数器编号 K 升序(稳定、可复现)。
- 源码位置:作为可选尾字段,格式 "<file>:<line>"(含前置单个空格);该位置
来自生成期挂在 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)
])
# 源码位置全局数组:每项形如 " <file>:<line>"(含前置空格);取不到位置则为空串
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 <module_sha1>\\n
<func_name> <self_tick> <inc_tick> <enter_count>\\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 落盘函数
(调用点计数追加写最终 <path>,计时追加写同目录 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')