审计并修复 23 个文件:类型注解中的 T1 | T2 一律 fail-fast;typedef/define/export/extern/static/const/volatile/attribute 优先读取 Markers/Attributes/ByteOrder/BitWidth;指针统一 CPtr/rawptr/CRef/CTempRef/CArrayPtr/CAutoPtr Subscript;stub 输出统一新语法。
2668 lines
151 KiB
Python
2668 lines
151 KiB
Python
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_funcs and not module_name:
|
||
return name
|
||
if name in self._export_extern_funcs:
|
||
target_sha1: str = self._export_extern_funcs[name]
|
||
if module_name:
|
||
# 显式带 module_name 调用(如 stdlib.system):即使当前模块定义了同名函数,
|
||
# 也应按模块匹配 extern,返回裸名。snprintf 之所以工作而 system 不工作,
|
||
# 就是因为 os._win32 定义了自己的 system 包装,导致旧逻辑跳过了 extern 匹配。
|
||
if module_name == target_sha1:
|
||
return name
|
||
if module_name in self.ModuleSha1Map and self.ModuleSha1Map[module_name] == target_sha1:
|
||
return name
|
||
init_name: str = f"{module_name}.__init__"
|
||
if init_name in self.ModuleSha1Map and self.ModuleSha1Map[init_name] == target_sha1:
|
||
return name
|
||
# 兜底:module_name 不在 ModuleSha1Map 中或 sha1 不匹配,
|
||
# 但函数在 _export_extern_funcs 中(C extern 不应混淆),
|
||
# 仅当当前模块未定义同名函数时才返回裸名。
|
||
if name not in self._current_module_func_names:
|
||
return name
|
||
elif name not in self._current_module_func_names:
|
||
# 无 module_name 调用(如 from stdlib import system; system()):
|
||
# 仅当当前模块未定义同名函数时才返回 extern 裸名,
|
||
# 否则让本地定义走正常 mangle 路径(本地 def 遮蔽 import 语义)。
|
||
return name
|
||
# main 函数作为程序入口点,永远不混淆
|
||
if name == 'main':
|
||
return name
|
||
# Try class_sha1_map first for class methods (e.g., 'md5.__init__' -> class 'md5')
|
||
class_sha1_map: dict[str, str] = self.class_sha1_map
|
||
if class_sha1_map:
|
||
if '.' in name:
|
||
base_class: str = name.split('.')[0]
|
||
if base_class in class_sha1_map:
|
||
sha1: str = class_sha1_map[base_class]
|
||
return f"{sha1}.{name}"
|
||
elif name in class_sha1_map:
|
||
sha1: str = class_sha1_map[name]
|
||
return f"{sha1}.{name}"
|
||
# If module_name is already a SHA1 hash (16 hex chars), use it directly
|
||
if module_name and len(module_name) == 16 and all(c in '0123456789abcdef' for c in module_name):
|
||
return f"{module_name}.{name}"
|
||
if module_name and module_name in self.ModuleSha1Map:
|
||
sha1: str = self.ModuleSha1Map[module_name]
|
||
return f"{sha1}.{name}"
|
||
# Try parent package name (e.g., 'hashlib' -> 'hashlib.__init__')
|
||
if module_name:
|
||
init_name: str = f"{module_name}.__init__"
|
||
if init_name in self.ModuleSha1Map:
|
||
sha1: str = self.ModuleSha1Map[init_name]
|
||
return f"{sha1}.{name}"
|
||
if self._skip_mangle(name):
|
||
return name
|
||
if self.module_sha1:
|
||
result = f"{self.module_sha1}.{name}"
|
||
else:
|
||
result = name
|
||
return result
|
||
|
||
def _get_source_line(self, lineno: int) -> str | None:
|
||
if 0 < lineno <= len(self._current_source_lines):
|
||
return self._current_source_lines[lineno - 1].rstrip('\r\n')
|
||
return None
|
||
|
||
def _FindConcreteTwin(self, StructType: Any) -> Any:
|
||
"""按**同短名**在 `structs` 里找一份**已具化**(elements 非 None)的 struct 实例。
|
||
|
||
为什么需要:同一类名常有两份 struct 实例(短名 'Keyword' / sha1 全名
|
||
'<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') |