feat(hotspot): 新增 --hotspot-sites 逐调用点计数并带源码 file:line
为匹配白名单的直接 call 点插桩 internal i64 __fhs_site_<K>(call 前 +1),计数天然包含循环内放大的真实执行次数,可把热点定位到具体调用点。 新增 --hotspot-callees 覆盖白名单、--hotspot-sites-all 全量;按去 16 位 sha1 前缀后的基名匹配。Ctrl+C 落盘独立文件 hotspot_sites.txt(# sites 分段,含调用方/被调方/计数/源码位置),按计数降序;不破坏既有 hotspot.txt 格式。 在 AST->IR 生成期由 _HotspotSiteBuilder 为每条新生成的 call 指令轻量挂载 _src_file/_src_line,插桩时作为可选尾字段输出;取不到则留空不伪造。开关关闭时改用原生 ir.IRBuilder,零影响。
This commit is contained in:
1 parent
0ffaadc95e
commit
2021c0edb7
11 files changed
+567
-25
No files matched your search
@@ -150,6 +150,9 @@ def main() -> None:
|
||||
parser.add_argument('--asan', action='store_true', help='ASan 模式:对全部 .ll 做 opt -passes=asan 插桩,并显式链接 clang64 的 ASan 运行库(mingw64 的 clang 没有 x86_64-w64-windows-gnu 目标的 ASan runtime,不能用 -fsanitize=address)')
|
||||
parser.add_argument('--nowarn-register', action='store_true', help='抑制"外部函数返回类型未登记"类 WARN 输出(透传给自举编译的 TransPyV.exe)')
|
||||
parser.add_argument('--func-hotspot', help='函数热点统计:为每个模块的函数定义插入计数器,运行时 Ctrl+C 各模块以追加方式把计数写入 <path>(含 main 的模块在启动时清空一次,保证每次运行一份结果)(可用 python Projectrans.py --project ... --func-hotspot <path>)')
|
||||
parser.add_argument('--hotspot-sites', nargs='?', const='', default=None, help='调用点热点统计:为每个匹配白名单的直接 call 点插入计数器,运行时 Ctrl+C 各模块按真实次数降序写入 <path>(默认为 hotspot_sites.txt;可独立于 --func-hotspot 使用)')
|
||||
parser.add_argument('--hotspot-callees', default=None, help='--hotspot-sites 的白名单被调函数基名(逗号分隔),默认内存/字符串族 strcmp,strncmp,strchr,strlen,strstr,memcpy,memset,memmove,strcpy,strcat')
|
||||
parser.add_argument('--hotspot-sites-all', action='store_true', help='--hotspot-sites 对全部直接 call 点插桩(忽略白名单;体积/开销大,用于深度排查)')
|
||||
|
||||
args: argparse.Namespace = parser.parse_args()
|
||||
|
||||
@@ -199,6 +202,18 @@ def main() -> None:
|
||||
func_hotspot_file: str | None = args.func_hotspot
|
||||
if func_hotspot_file:
|
||||
func_hotspot_file = os.path.abspath(func_hotspot_file)
|
||||
# 调用点热点统计(--hotspot-sites):None=未启用;''=启用但未给路径(默认
|
||||
# hotspot_sites.txt);其余为显式路径。一律绝对化,保证运行时(exe 工作目录为
|
||||
# output_dir)各模块处理器追加写入的路径与预期一致。
|
||||
hotspot_sites_file: str | None = args.hotspot_sites
|
||||
if hotspot_sites_file is not None:
|
||||
if hotspot_sites_file == '':
|
||||
hotspot_sites_file = 'hotspot_sites.txt'
|
||||
hotspot_sites_file = os.path.abspath(hotspot_sites_file)
|
||||
# 白名单覆盖:逗号分隔 → 去空白后的基名列表(None=用 BaseGen 默认白名单)
|
||||
hotspot_callees: list[str] | None = None
|
||||
if args.hotspot_callees:
|
||||
hotspot_callees = [c.strip() for c in args.hotspot_callees.split(',') if c.strip()]
|
||||
|
||||
if isinstance(src, list):
|
||||
if len(src) == 1 and os.path.isdir(src[0]):
|
||||
@@ -217,6 +232,8 @@ def main() -> None:
|
||||
_vlog().info(f"数据布局: {target_datalayout}")
|
||||
if func_hotspot_file:
|
||||
_vlog().info(f"函数热点统计: {func_hotspot_file}(各模块运行时以追加方式写入,含 main 的模块启动时清空一次)")
|
||||
if hotspot_sites_file:
|
||||
_vlog().info(f"调用点热点统计: {hotspot_sites_file}(按真实调用次数降序落盘,含 main 的模块启动时清空一次)")
|
||||
|
||||
# 每次编译自动清理子进程性能剖析输出(PROF 目录 *.prof,硬编码路径)
|
||||
from lib.Projectrans.Phase2Translator import _CleanProfFiles, PROF_DIR
|
||||
@@ -324,6 +341,10 @@ def main() -> None:
|
||||
trans.slice_level = slice_level
|
||||
# 函数热点统计(--func-hotspot):None=未启用(零影响)
|
||||
trans.func_hotspot_file = func_hotspot_file
|
||||
# 调用点热点统计(--hotspot-sites):None=未启用(零影响)
|
||||
trans.hotspot_sites_file = hotspot_sites_file
|
||||
trans.hotspot_sites_all = bool(args.hotspot_sites_all)
|
||||
trans.hotspot_callees = hotspot_callees
|
||||
trans.run()
|
||||
|
||||
if args.run and linker_output and not linker_output.endswith('.bin'):
|
||||
|
||||
@@ -162,7 +162,10 @@ def _parallel_translate_worker(src_path: str, out_path: str, src_root: str, temp
|
||||
stub_files: dict[str, str], include_py_map: dict[str, str], slice_level: int,
|
||||
shared_sym_pickle_path: str, struct_sha1_map: dict[str, str],
|
||||
prebuilt_ModuleSha1Map: dict[str, str] | None = None,
|
||||
func_hotspot_file: str | None = None) -> tuple[str, str, str]:
|
||||
func_hotspot_file: str | None = None,
|
||||
hotspot_sites_file: str | None = None,
|
||||
hotspot_sites_all: bool = False,
|
||||
hotspot_callees: list[str] | None = None) -> tuple[str, str, str]:
|
||||
# 注意:不可跨任务复用 Phase2Translator/共享符号表!_translate_file 编译过程中
|
||||
# 会向共享 SymbolTable 写入符号(import insert/类注册等),复用导致前文件的
|
||||
# 符号污染后文件 → 跨模块 struct 类型前缀错位(如 7cadfe07e983fd90.CompilerFactory
|
||||
@@ -183,6 +186,10 @@ def _parallel_translate_worker(src_path: str, out_path: str, src_root: str, temp
|
||||
# 函数热点统计(--func-hotspot):worker 子进程无父进程属性,须显式透传
|
||||
# (None=未启用,零影响);生成 IR 时各模块处理器统一追加写最终 <path>
|
||||
trans.func_hotspot_file = func_hotspot_file
|
||||
# 调用点热点统计(--hotspot-sites):同样须显式透传(None=未启用,零影响)
|
||||
trans.hotspot_sites_file = hotspot_sites_file
|
||||
trans.hotspot_sites_all = hotspot_sites_all
|
||||
trans.hotspot_callees = hotspot_callees
|
||||
|
||||
if shared_sym_pickle_path and os.path.exists(shared_sym_pickle_path):
|
||||
# 使用 pickle.load(f) 流式加载,避免 pickle.loads(f.read()) 一次性
|
||||
@@ -227,7 +234,10 @@ def _parallel_translate_batch(batch: list, src_root: str, temp_dir: str, output_
|
||||
sha1_map: dict[str, str], sig_files: dict[str, str],
|
||||
stub_files: dict[str, str], include_py_map: dict[str, str], slice_level: int,
|
||||
shared_sym_pickle_path: str, struct_sha1_map: dict[str, str],
|
||||
func_hotspot_file: str | None = None) -> tuple[list, list]:
|
||||
func_hotspot_file: str | None = None,
|
||||
hotspot_sites_file: str | None = None,
|
||||
hotspot_sites_all: bool = False,
|
||||
hotspot_callees: list[str] | None = None) -> tuple[list, list]:
|
||||
"""批次子任务:worker 内创建一个 Phase2Translator + pickle.load 一次,顺序翻译批内全部文件。
|
||||
|
||||
batch 每项: (i, src_path, out_path, sha1, prebuilt_ModuleSha1Map)
|
||||
@@ -250,6 +260,10 @@ def _parallel_translate_batch(batch: list, src_root: str, temp_dir: str, output_
|
||||
trans.struct_sha1_map = struct_sha1_map
|
||||
# 函数热点统计(--func-hotspot):批内每文件的处理器统一追加写最终 <path>
|
||||
trans.func_hotspot_file = func_hotspot_file
|
||||
# 调用点热点统计(--hotspot-sites):批内每文件同样透传
|
||||
trans.hotspot_sites_file = hotspot_sites_file
|
||||
trans.hotspot_sites_all = hotspot_sites_all
|
||||
trans.hotspot_callees = hotspot_callees
|
||||
|
||||
symbol_table_bytes: bytes | None = None
|
||||
if shared_sym_pickle_path and os.path.exists(shared_sym_pickle_path):
|
||||
@@ -651,7 +665,14 @@ class Phase2Translator:
|
||||
# 函数热点统计(--func-hotspot):None=未启用(零影响);str=计数器输出
|
||||
# 文件路径。各模块运行时自带 Ctrl+C 处理器、统一以追加方式写该路径;
|
||||
# 含 main 的模块在 main 入口清空一次 → 每次运行一份结果(无构建后合并步骤)。
|
||||
self.func_hotspot_file: str | None = None
|
||||
self.func_hotspot_file: str | None = None
|
||||
# 调用点热点统计(--hotspot-sites):None=未启用(零影响);str=调用点计数
|
||||
# 落盘文件路径。各模块运行时自带 Ctrl+C 处理器、统一以追加方式写该路径;
|
||||
# 含 main 的模块在 main 入口清空一次 → 每次运行一份结果。
|
||||
self.hotspot_sites_file: str | None = None
|
||||
# 是否对全部直接 call 点插桩(忽略白名单);白名单被调函数基名列表(None=默认)
|
||||
self.hotspot_sites_all: bool = False
|
||||
self.hotspot_callees: list[str] | None = None
|
||||
self.sha1_map: dict[str, str] = {}
|
||||
self.sig_files: dict[str, str] = {}
|
||||
self.stub_files: dict[str, str] = {}
|
||||
@@ -855,10 +876,11 @@ class Phase2Translator:
|
||||
self.struct_sha1_map = self._build_struct_sha1_map()
|
||||
|
||||
need_translate = []
|
||||
# --func-hotspot:缓存 .ll / .stub.ll+.text.ll 可能不含热点计数 IR,启用时
|
||||
# 一律重译,保证每个模块都嵌入 internal 计数器与 @llvm.global_ctors 自注册。
|
||||
# 未启用时 hotspot_on=False,缓存与重组分支行为与改动前完全一致(零影响)。
|
||||
hotspot_on: bool = bool(self.func_hotspot_file)
|
||||
# --func-hotspot / --hotspot-sites:缓存 .ll / .stub.ll+.text.ll 可能不含
|
||||
# 热点计数 IR,启用任一功能时一律重译,保证每个模块都嵌入 internal 计数器
|
||||
# 与 @llvm.global_ctors 自注册。未启用时 hotspot_on=False,缓存与重组分支
|
||||
# 行为与改动前完全一致(零影响)。
|
||||
hotspot_on: bool = bool(self.func_hotspot_file or self.hotspot_sites_file)
|
||||
for i, src_path in enumerate(py_files, 1):
|
||||
rel = os.path.relpath(src_path, self.src_root)
|
||||
# 复用 parse_python_file 的 AST 缓存(get_file_dependencies 已 parse 过)
|
||||
@@ -1018,6 +1040,9 @@ class Phase2Translator:
|
||||
self.include_py_map, self.slice_level,
|
||||
shared_pickle_path, self.struct_sha1_map,
|
||||
func_hotspot_file=self.func_hotspot_file,
|
||||
hotspot_sites_file=self.hotspot_sites_file,
|
||||
hotspot_sites_all=self.hotspot_sites_all,
|
||||
hotspot_callees=self.hotspot_callees,
|
||||
)
|
||||
futures[future] = (bidx, rel_first, len(batch))
|
||||
|
||||
@@ -1565,6 +1590,14 @@ class Phase2Translator:
|
||||
if self.func_hotspot_file:
|
||||
trans.translator.FuncHotspotFile = self.func_hotspot_file
|
||||
trans.translator.FuncHotspotModuleLocal = True
|
||||
# 调用点热点统计(--hotspot-sites,Projectrans 多模块):同上,注册策略为
|
||||
# 本模块 ctor 自注册(见 BaseGen._InstallHotspotSitesModuleLocal);
|
||||
# self.hotspot_sites_file 为 None 时不设置任何属性 → 与未启用时逐字节一致。
|
||||
if self.hotspot_sites_file:
|
||||
trans.translator.HotspotSitesFile = self.hotspot_sites_file
|
||||
trans.translator.HotspotSitesAll = self.hotspot_sites_all
|
||||
trans.translator.HotspotCallees = self.hotspot_callees
|
||||
trans.translator.HotspotSitesModuleLocal = True
|
||||
|
||||
if self._shared_symbol_table is not None:
|
||||
trans.translator.SymbolTable = self._shared_symbol_table
|
||||
@@ -2845,7 +2878,10 @@ class Phase2Translator:
|
||||
self.include_py_map, self.slice_level,
|
||||
shared_pickle_path, self.struct_sha1_map,
|
||||
include_ModuleSha1Map,
|
||||
func_hotspot_file=self.func_hotspot_file
|
||||
func_hotspot_file=self.func_hotspot_file,
|
||||
hotspot_sites_file=self.hotspot_sites_file,
|
||||
hotspot_sites_all=self.hotspot_sites_all,
|
||||
hotspot_callees=self.hotspot_callees
|
||||
)
|
||||
in_progress[future] = item
|
||||
|
||||
@@ -2862,7 +2898,10 @@ class Phase2Translator:
|
||||
self.include_py_map, self.slice_level,
|
||||
shared_pickle_path, self.struct_sha1_map,
|
||||
include_ModuleSha1Map,
|
||||
func_hotspot_file=self.func_hotspot_file
|
||||
func_hotspot_file=self.func_hotspot_file,
|
||||
hotspot_sites_file=self.hotspot_sites_file,
|
||||
hotspot_sites_all=self.hotspot_sites_all,
|
||||
hotspot_callees=self.hotspot_callees
|
||||
)
|
||||
in_progress[future] = item
|
||||
remaining.clear()
|
||||
|
||||
@@ -340,6 +340,30 @@ class TransPyC:
|
||||
else:
|
||||
error('--func-hotspot requires an argument')
|
||||
sys.exit(1)
|
||||
elif sys.argv[I] == '--hotspot-sites':
|
||||
# 调用点热点统计:可选路径(省略时默认 hotspot_sites.txt,运行期由
|
||||
# 处理器按计数降序写入)。空串表示"已启用但未给路径"。
|
||||
if I + 1 < len(sys.argv) and not sys.argv[I + 1].startswith('-'):
|
||||
self.Args['HotspotSites'] = sys.argv[I + 1]
|
||||
I += 2
|
||||
else:
|
||||
self.Args['HotspotSites'] = ''
|
||||
I += 1
|
||||
elif sys.argv[I].startswith('--hotspot-callees='):
|
||||
# --hotspot-sites 白名单覆盖(逗号分隔的基名)
|
||||
self.Args['HotspotCallees'] = sys.argv[I][len('--hotspot-callees='):]
|
||||
I += 1
|
||||
elif sys.argv[I] == '--hotspot-callees':
|
||||
if I + 1 < len(sys.argv):
|
||||
self.Args['HotspotCallees'] = sys.argv[I + 1]
|
||||
I += 2
|
||||
else:
|
||||
error('--hotspot-callees requires an argument')
|
||||
sys.exit(1)
|
||||
elif sys.argv[I] == '--hotspot-sites-all':
|
||||
# --hotspot-sites 对全部直接 call 点插桩(忽略白名单)
|
||||
self.Args['HotspotSitesAll'] = True
|
||||
I += 1
|
||||
else:
|
||||
error(f'Unknown argument {sys.argv[I]}')
|
||||
sys.exit(1)
|
||||
@@ -607,6 +631,18 @@ class TransPyC:
|
||||
# 函数热点统计(--func-hotspot):绑定到翻译器;未传时为 None(零影响)
|
||||
self.translator.FuncHotspotFile = self.Args.get('FuncHotspot')
|
||||
|
||||
# 调用点热点统计(--hotspot-sites):None=未启用(零影响);''=启用但未给
|
||||
# 路径(默认 hotspot_sites.txt)。注册走种子单模块路径(main 入口注册)。
|
||||
HotspotSites: str | None = self.Args.get('HotspotSites')
|
||||
if HotspotSites is not None:
|
||||
if HotspotSites == '':
|
||||
HotspotSites = 'hotspot_sites.txt'
|
||||
self.translator.HotspotSitesFile = os.path.abspath(HotspotSites)
|
||||
self.translator.HotspotSitesAll = bool(self.Args.get('HotspotSitesAll'))
|
||||
HotspotCallees: str | None = self.Args.get('HotspotCallees')
|
||||
if HotspotCallees:
|
||||
self.translator.HotspotCallees = [c.strip() for c in HotspotCallees.split(',') if c.strip()]
|
||||
|
||||
# 检查是否有预处理符号文件的请求
|
||||
if 'PreSym' in self.Args:
|
||||
PresymConfig: dict[str, Any] = self.Args['PreSym']
|
||||
|
||||
+12
-1
@@ -26,6 +26,13 @@ DEFAULT_METADATA: dict[str, str] = {
|
||||
'COPYRIGHT_HOLDER': 'Galactic Vorpal Software Development Studio (e.g. GVSDS Inc.)'
|
||||
}
|
||||
|
||||
# 调用点热点统计(--hotspot-sites)默认白名单:内存/字符串族被调函数基名
|
||||
# (基名 = 去掉 16 位 sha1 模块前缀后的名字,如 "23499a69276862b2.strcmp" -> "strcmp")
|
||||
DEFAULT_HOTSPOT_CALLEES: tuple[str, ...] = (
|
||||
'strcmp', 'strncmp', 'strchr', 'strlen', 'strstr',
|
||||
'memcpy', 'memset', 'memmove', 'strcpy', 'strcat',
|
||||
)
|
||||
|
||||
ERROR_MESSAGES: dict[str, str] = {
|
||||
'MISSING_ARGS': 'Missing required arguments -f and/or -o',
|
||||
'UNKNOWN_ARG': 'Unknown argument',
|
||||
@@ -37,9 +44,13 @@ ERROR_MESSAGES: dict[str, str] = {
|
||||
HELP_MESSAGE: str = '''
|
||||
Usage: python TransPyC.py -f InputFile -o OutputFile [-wh header_files] [-debug DebugFile]
|
||||
[-cc compile_command] [-cflags CompileFlags] [-run] [-args RunArgs] [-h HelperFiles]
|
||||
[-func-hotspot HotFile]
|
||||
[-func-hotspot HotFile] [--hotspot-sites [SitesFile]] [--hotspot-callees a,b,c] [--hotspot-sites-all]
|
||||
-h: Specify helper files (C or Python) to help identify structs, functions, variables, and ptrs
|
||||
-func-hotspot: 统计各函数调用次数,Ctrl+C 退出时写入 HotFile(每行 "函数名 计数")
|
||||
--hotspot-sites: 统计各调用点真实运行次数,Ctrl+C 退出时写入 SitesFile(默认 hotspot_sites.txt,
|
||||
按计数降序;可独立于 -func-hotspot 使用)
|
||||
--hotspot-callees: --hotspot-sites 的白名单被调函数基名(逗号分隔,默认内存/字符串族)
|
||||
--hotspot-sites-all: --hotspot-sites 对全部直接 call 点插桩(忽略白名单,体积/开销大)
|
||||
'''
|
||||
|
||||
mode: str = "strict"
|
||||
@@ -376,7 +376,7 @@ class AsyncFunctionHandle(BaseHandle):
|
||||
self._RegisterCoroFunction(Gen, f'create_{name}', create_fn)
|
||||
create_fn.attributes.add('noredzone')
|
||||
entry: ir.Block = create_fn.append_basic_block('entry')
|
||||
Gen.builder = ir.IRBuilder(entry)
|
||||
Gen.builder = Gen._NewIrBuilder(entry)
|
||||
Gen.func = create_fn
|
||||
zero32: ir.Constant = ir.Constant(ir.IntType(32), 0)
|
||||
null_i8p: ir.Constant = ir.Constant(ir.PointerType(ir.IntType(8)), None)
|
||||
@@ -431,7 +431,7 @@ class AsyncFunctionHandle(BaseHandle):
|
||||
resume_fn.attributes.add('noredzone')
|
||||
Gen.func = resume_fn
|
||||
entry: ir.Block = resume_fn.append_basic_block('entry')
|
||||
Gen.builder = ir.IRBuilder(entry)
|
||||
Gen.builder = Gen._NewIrBuilder(entry)
|
||||
frame_arg: ir.Argument = resume_fn.args[0]
|
||||
frame_arg.name = 'frame'
|
||||
zero32: ir.Constant = ir.Constant(ir.IntType(32), 0)
|
||||
|
||||
@@ -1068,7 +1068,7 @@ class ClassHandle(BaseHandle):
|
||||
entry_block: ir.Block = child_func.append_basic_block(name="entry")
|
||||
saved_builder: ir.IRBuilder = Gen.builder
|
||||
saved_func: ir.Function = Gen.func
|
||||
Gen.builder = ir.IRBuilder(entry_block)
|
||||
Gen.builder = Gen._NewIrBuilder(entry_block)
|
||||
Gen.func = child_func
|
||||
|
||||
# 准备参数
|
||||
@@ -1529,7 +1529,7 @@ class ClassHandle(BaseHandle):
|
||||
EntryBlock: ir.Block = func.append_basic_block(name="entry")
|
||||
saved_builder: ir.IRBuilder = Gen.builder
|
||||
saved_func: ir.Function = Gen.func
|
||||
Gen.builder = ir.IRBuilder(EntryBlock)
|
||||
Gen.builder = Gen._NewIrBuilder(EntryBlock)
|
||||
Gen.func = func
|
||||
StructPtr: ir.Argument = func.args[0]
|
||||
base: int = 1 if IsCVTable else 0
|
||||
|
||||
@@ -35,7 +35,7 @@ class ExprLambdaHandle(BaseHandle):
|
||||
prev_builder: Any = Gen.builder
|
||||
prev_func: Any = Gen.func
|
||||
prev_vars: dict[str, Any] = dict(Gen.variables)
|
||||
Gen.builder = ir.IRBuilder(entry_block)
|
||||
Gen.builder = Gen._NewIrBuilder(entry_block)
|
||||
Gen.func = fn
|
||||
Gen.variables = {}
|
||||
for i, arg in enumerate(fn.args):
|
||||
|
||||
@@ -1707,7 +1707,7 @@ class FunctionHandle(BaseHandle):
|
||||
if existing_blocks:
|
||||
return func
|
||||
EntryBlock = func.append_basic_block(name="entry")
|
||||
Gen.builder = ir.IRBuilder(EntryBlock)
|
||||
Gen.builder = Gen._NewIrBuilder(EntryBlock)
|
||||
Gen.func = func
|
||||
Gen.variables = {}
|
||||
Gen._reg_values = {}
|
||||
|
||||
+417
-4
@@ -6,6 +6,7 @@ import llvmlite.ir as ir
|
||||
from lib.includes import t as _t
|
||||
from lib.includes.t import CTypeRegistry
|
||||
from lib.core.Handles.HandlesBase import CTypeInfo as _CTypeInfo, BuiltinTypeMap
|
||||
from lib.constants.config import DEFAULT_HOTSPOT_CALLEES
|
||||
|
||||
|
||||
class VaArgInstruction(ir.Instruction):
|
||||
@@ -15,6 +16,35 @@ class VaArgInstruction(ir.Instruction):
|
||||
ptr.type, ptr.get_reference(), self.type))
|
||||
|
||||
|
||||
class _HotspotSiteBuilder(ir.IRBuilder):
|
||||
"""--hotspot-sites 专用 IRBuilder:为每条新生成的 call 指令挂源码位置。
|
||||
|
||||
仅当调用点热点统计开关打开时才被实例化(见 BaseGenMixin._NewIrBuilder);
|
||||
未启用时一律使用原生 ir.IRBuilder,不产生任何额外属性、行为与既有完全一致。
|
||||
|
||||
重写 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)
|
||||
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
|
||||
@@ -178,6 +208,14 @@ class BaseGenMixin:
|
||||
# 注册策略:False=种子 CLI 单模块(在 main 入口注册);True=Projectrans
|
||||
# 多模块(每模块用 @llvm.global_ctors 自注册,不依赖 main 所在模块)
|
||||
self.func_hotspot_module_local: bool = False
|
||||
# 调用点热点统计(--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
|
||||
# 注册策略:与 func_hotspot_module_local 同理
|
||||
self.hotspot_sites_module_local: bool = False
|
||||
pi: dict[str, bool | int] = self._platform_info
|
||||
# 根据目标平台设置宏(声明式,从三元组推导)
|
||||
if pi['is_windows']:
|
||||
@@ -1014,17 +1052,20 @@ class BaseGenMixin:
|
||||
defs: list[ir.Function],
|
||||
path: str,
|
||||
require_main: bool = False,
|
||||
flag: str = '--func-hotspot',
|
||||
) -> ir.Function | None:
|
||||
"""在含 main 的模块的 main 入口插入一次 "w" 打开→关闭,清空计数文件。
|
||||
|
||||
--func-hotspot 的处理器以追加("a")方式写 <path>,若不清空会累积历史运行的
|
||||
数据。清空必须满足两点:
|
||||
--func-hotspot/--hotspot-sites 的处理器以追加("a")方式写 <path>,若不清空会
|
||||
累积历史运行的数据。清空必须满足两点:
|
||||
- 只在含 main 的模块执行一次(其它模块无 main,返回 None 且不截断);
|
||||
- 必须在 main 入口而非构造器:@llvm.global_ctors 的跨模块执行顺序无保证,
|
||||
放构造器里可能在其它模块已写入后清空 → 丢数据。
|
||||
main 入口在程序启动、任何 Ctrl+C 处理器写入之前执行,故每次运行都以空文件
|
||||
开始,配合追加写保证"一次运行一份结果"。
|
||||
|
||||
flag 仅用于报错信息,调用方可传 '--hotspot-sites'。
|
||||
|
||||
返回找到的 main 定义(供调用方继续注册处理器);未找到时返回 None。
|
||||
require_main=True(种子 CLI 单模块)时未找到 main 直接报错(不静默降级)。
|
||||
"""
|
||||
@@ -1036,7 +1077,7 @@ class BaseGenMixin:
|
||||
if main_func is None:
|
||||
if require_main:
|
||||
raise RuntimeError(
|
||||
'--func-hotspot: 找不到 main 函数定义,无法清空计数文件')
|
||||
f'{flag}: 找不到 main 函数定义,无法清空计数文件')
|
||||
return None
|
||||
i32: ir.IntType = ir.IntType(32)
|
||||
i8p: ir.PointerType = ir.PointerType(ir.IntType(8))
|
||||
@@ -1103,4 +1144,376 @@ class BaseGenMixin:
|
||||
self._RegisterFuncHotspotViaCtor(handler)
|
||||
# 非 main 模块无 main、不需要截断(返回 None 不报错):其它模块的写入
|
||||
# 依赖含 main 模块在启动期清空,不会互相覆盖。
|
||||
self._InstallFuncHotspotTruncate(defs, path)
|
||||
self._InstallFuncHotspotTruncate(defs, path)
|
||||
|
||||
# ========== 调用点热点统计(--hotspot-sites) ==========
|
||||
|
||||
def _NewIrBuilder(self, block: ir.Block) -> ir.IRBuilder:
|
||||
"""创建 IR 构建器:仅在 --hotspot-sites 启用时挂钩源码位置。
|
||||
|
||||
未启用(hotspot_sites_file 为 None)时返回原生 ir.IRBuilder,行为与既有
|
||||
完全一致(零影响,不挂任何属性);启用时返回 _HotspotSiteBuilder,仅在
|
||||
每条新生成的 call 指令对象上追加 _src_file/_src_line 两个普通属性,供
|
||||
调用点热点插桩读取源码位置。
|
||||
"""
|
||||
if self.hotspot_sites_file:
|
||||
return _HotspotSiteBuilder(block, self)
|
||||
return ir.IRBuilder(block)
|
||||
|
||||
@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_ 前缀的辅助函数自身,避免在插桩生成的代码里计数
|
||||
(--func-hotspot 的处理器/构造器也在此列)。
|
||||
- 只在 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
|
||||
|
||||
def _CreateHotspotSitesHandler(
|
||||
self,
|
||||
path: str,
|
||||
records: list[tuple[ir.GlobalVariable, str, str, str, int]],
|
||||
module_id: str | None = None,
|
||||
) -> ir.Function:
|
||||
"""生成 Win32 控制台 Ctrl+C 处理器:按调用次数降序把各调用点追加写入 <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) 仅
|
||||
在 Ctrl+C 触发一次)按计数降序输出,只输出 count>0 的调用点;计数相同时
|
||||
按计数器编号 K 升序(稳定、可复现)。
|
||||
- 源码位置:作为可选尾字段,格式 "<file>:<line>"(含前置单个空格);该位置
|
||||
来自生成期挂在 call 指令对象上的 _src_file/_src_line(见 _HotspotSiteBuilder)。
|
||||
生成期无法关联到 AST 的合成调用点取不到位置,则此字段整体省略(不伪造、不填默认值)。
|
||||
- 处理器签名为 Win32 PHANDLER_ROUTINE(i32 (i32) -> BOOL),用 CRT
|
||||
fopen("a")/fprintf/fclose 落盘(沿用既有做法),返回 FALSE(0) 让默认
|
||||
处理继续,使 Ctrl+C 仍能终止程序。
|
||||
"""
|
||||
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]))
|
||||
handler: ir.Function = ir.Function(
|
||||
self.module, ir.FunctionType(i32, [i32]),
|
||||
name=self._UniqueGlobalName('__fhs_sites_handler'))
|
||||
handler.linkage = 'internal'
|
||||
b: ir.IRBuilder = ir.IRBuilder(handler.append_basic_block(name='entry'))
|
||||
b.position_at_start(handler.entry_basic_block)
|
||||
# 无匹配调用点:生成空处理器(不落盘、不建数组),仅占位以保持注册链路一致
|
||||
if n == 0:
|
||||
b.ret(ir.Constant(i32, 0))
|
||||
return handler
|
||||
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 = handler.append_basic_block(name='fhs_sites_sort_k')
|
||||
body_k: ir.Block = handler.append_basic_block(name='fhs_sites_sort_k_body')
|
||||
loop_i: ir.Block = handler.append_basic_block(name='fhs_sites_sort_i')
|
||||
body_i: ir.Block = handler.append_basic_block(name='fhs_sites_sort_i_body')
|
||||
scan_i: ir.Block = handler.append_basic_block(name='fhs_sites_sort_i_scan')
|
||||
upd_i: ir.Block = handler.append_basic_block(name='fhs_sites_sort_i_upd')
|
||||
next_i: ir.Block = handler.append_basic_block(name='fhs_sites_sort_i_next')
|
||||
end_i: ir.Block = handler.append_basic_block(name='fhs_sites_sort_i_end')
|
||||
print_k: ir.Block = handler.append_basic_block(name='fhs_sites_print_k')
|
||||
after_k: ir.Block = handler.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; return 0
|
||||
b.position_at_end(after_k)
|
||||
b.call(fclose, [fp])
|
||||
b.ret(ir.Constant(i32, 0))
|
||||
return handler
|
||||
|
||||
def _RegisterHotspotSitesViaCtor(self, handler: ir.Function) -> None:
|
||||
"""为本模块的调用点热点处理器注册 Ctrl+C(复用/追加 @llvm.global_ctors)。
|
||||
|
||||
生成 internal void @__fhs_sites_ctor()(内部调用 SetConsoleCtrlHandler),
|
||||
并写入 appending linkage 的 @llvm.global_ctors:
|
||||
- 模块内尚无该全局时新建(单模块/纯 --hotspot-sites 场景);
|
||||
- 已存在且为本功能兼容的 appending 结构数组时追加一条(常见于同时启用
|
||||
--func-hotspot:其构造器已建好 @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(), []))
|
||||
set_handler: ir.Function = self._DeclareExternalFunc(
|
||||
'SetConsoleCtrlHandler', ir.FunctionType(i32, [i8p, i32]))
|
||||
ctor: ir.Function = ir.Function(
|
||||
self.module, ir.FunctionType(ir.VoidType(), []),
|
||||
name=self._UniqueGlobalName('__fhs_sites_ctor'))
|
||||
ctor.linkage = 'internal'
|
||||
cb: ir.IRBuilder = ir.IRBuilder(ctor.append_basic_block(name='entry'))
|
||||
cb.call(set_handler, [cb.bitcast(handler, i8p), ir.Constant(i32, 1)])
|
||||
cb.ret_void()
|
||||
elem_ty: ir.LiteralStructType = ir.LiteralStructType([i32, void_fnptr, i8p])
|
||||
entry_val: ir.Constant = ir.Constant(elem_ty, [
|
||||
ir.Constant(i32, 65535),
|
||||
ir.Constant(void_fnptr, ctor.get_reference()),
|
||||
ir.Constant(i8p, None),
|
||||
])
|
||||
existing: Any = self.module.globals.get('llvm.global_ctors')
|
||||
if existing is None:
|
||||
arr_ty: ir.ArrayType = ir.ArrayType(elem_ty, 1)
|
||||
gv: ir.GlobalVariable = ir.GlobalVariable(self.module, arr_ty, name='llvm.global_ctors')
|
||||
gv.linkage = 'appending'
|
||||
gv.initializer = ir.Constant(arr_ty, [entry_val])
|
||||
return
|
||||
value_ty: Any = getattr(existing, 'value_type', None)
|
||||
if (not isinstance(existing, ir.GlobalVariable)
|
||||
or existing.linkage != 'appending'
|
||||
or not isinstance(value_ty, ir.ArrayType)
|
||||
or value_ty.element != elem_ty
|
||||
or existing.initializer is None):
|
||||
raise RuntimeError(
|
||||
'--hotspot-sites: 模块内已存在不兼容的 @llvm.global_ctors,'
|
||||
'无法注册调用点热点处理器(不静默降级)')
|
||||
old_entries: list = list(existing.initializer.constant)
|
||||
new_ty: ir.ArrayType = ir.ArrayType(elem_ty, len(old_entries) + 1)
|
||||
existing.type = ir.PointerType(new_ty)
|
||||
existing.value_type = new_ty
|
||||
existing.initializer = ir.Constant(new_ty, old_entries + [entry_val])
|
||||
|
||||
def _InstallHotspotSites(self) -> None:
|
||||
"""安装调用点热点统计(--hotspot-sites):种子 CLI 单模块路径。
|
||||
|
||||
在 main 入口最前先用 "w" 打开一次 <path> 清空历史计数,再注册 Ctrl+C
|
||||
处理器(程序启动期各执行一次),与 _InstallFuncHotspot 同法。仅在
|
||||
hotspot_sites_file 非 None(用户传入 --hotspot-sites)时被调用;未启用时
|
||||
该方法不会执行,IR 输出与既有行为一致(零影响)。
|
||||
"""
|
||||
path: str | None = self.hotspot_sites_file
|
||||
if not path:
|
||||
raise RuntimeError('--hotspot-sites: 需要提供调用点计数输出文件路径')
|
||||
i32: ir.IntType = ir.IntType(32)
|
||||
i8p: ir.PointerType = ir.PointerType(ir.IntType(8))
|
||||
defs, records = self._EmitHotspotSiteCounters()
|
||||
handler: ir.Function = self._CreateHotspotSitesHandler(
|
||||
path, records, module_id=self.module_sha1 or None)
|
||||
main_func: ir.Function | None = self._InstallFuncHotspotTruncate(
|
||||
defs, path, require_main=True, flag='--hotspot-sites')
|
||||
if main_func is None:
|
||||
raise RuntimeError(
|
||||
'--hotspot-sites: 找不到 main 函数定义,无法注册 Ctrl+C 处理器')
|
||||
mb: ir.IRBuilder = ir.IRBuilder(main_func.entry_basic_block)
|
||||
mb.position_at_start(main_func.entry_basic_block)
|
||||
set_handler: ir.Function = self._DeclareExternalFunc(
|
||||
'SetConsoleCtrlHandler', ir.FunctionType(i32, [i8p, i32]))
|
||||
mb.call(set_handler, [mb.bitcast(handler, i8p), ir.Constant(i32, 1)])
|
||||
|
||||
def _InstallHotspotSitesModuleLocal(self) -> None:
|
||||
"""安装调用点热点统计(--hotspot-sites):Projectrans 多模块路径。
|
||||
|
||||
每模块自带 internal 调用点计数器 + internal 处理器(统一追加写最终 <path>),
|
||||
并各自通过 @llvm.global_ctors 在进程启动期注册 SetConsoleCtrlHandler
|
||||
(见 _RegisterHotspotSitesViaCtor;若 --func-hotspot 已建该全局则追加复用)。
|
||||
仅含 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()
|
||||
handler: ir.Function = self._CreateHotspotSitesHandler(
|
||||
path, records, module_id=module_id or None)
|
||||
self._RegisterHotspotSitesViaCtor(handler)
|
||||
# 非 main 模块无 main、不需要截断(返回 None 不报错):其它模块的写入
|
||||
# 依赖含 main 模块在启动期清空,不会互相覆盖。
|
||||
self._InstallFuncHotspotTruncate(defs, path, flag='--hotspot-sites')
|
||||
@@ -76,11 +76,19 @@ class BaseTranslatorMixin:
|
||||
self.config: Any = None
|
||||
self._ImportedModules: set[str] | None = None
|
||||
self._t_c_imported_names: dict = {}
|
||||
# 函数热点统计(--func-hotspot):None=未启用(零影响),否则为计数器落盘文件路径
|
||||
self.FuncHotspotFile: str | None = None
|
||||
# 注册策略:False=种子 CLI 单模块(main 入口注册);True=Projectrans 多模块
|
||||
# (每模块 @llvm.global_ctors 自注册,见 BaseGen._InstallFuncHotspotModuleLocal)
|
||||
self.FuncHotspotModuleLocal: bool = False
|
||||
# 函数热点统计(--func-hotspot):None=未启用(零影响),否则为计数器落盘文件路径
|
||||
self.FuncHotspotFile: str | None = None
|
||||
# 注册策略:False=种子 CLI 单模块(在 main 入口注册);True=Projectrans 多模块
|
||||
# (每模块 @llvm.global_ctors 自注册,见 BaseGen._InstallFuncHotspotModuleLocal)
|
||||
self.FuncHotspotModuleLocal: bool = False
|
||||
# 调用点热点统计(--hotspot-sites):None=未启用(零影响),否则为调用点计数落盘文件路径
|
||||
self.HotspotSitesFile: str | None = None
|
||||
# 是否对全部直接 call 点插桩(忽略白名单;体积/开销大)
|
||||
self.HotspotSitesAll: bool = False
|
||||
# 白名单被调函数基名列表;None=使用默认内存/字符串族白名单
|
||||
self.HotspotCallees: list[str] | None = None
|
||||
# 注册策略:与 FuncHotspotModuleLocal 同理(True=Projectrans 多模块 ctor 自注册)
|
||||
self.HotspotSitesModuleLocal: bool = False
|
||||
|
||||
# 导出表
|
||||
self.Exportable = Exportable(filename='')
|
||||
|
||||
@@ -237,6 +237,15 @@ class LlvmGeneratorMixin:
|
||||
Gen._InstallFuncHotspotModuleLocal()
|
||||
else:
|
||||
Gen._InstallFuncHotspot()
|
||||
# 调用点热点统计(--hotspot-sites):在函数热点之后装,保证本功能不会把
|
||||
# --func-hotspot 生成的 fopen/fprintf/fclose/SetConsoleCtrlHandler 调用点
|
||||
# 计入(再加上本功能自身的处理器/构造器晚于扫描生成,天然不自计数);
|
||||
# 多模块路径下 --func-hotspot 已建的 @llvm.global_ctors 由本功能追加复用。
|
||||
if Gen.hotspot_sites_file:
|
||||
if Gen.hotspot_sites_module_local:
|
||||
Gen._InstallHotspotSitesModuleLocal()
|
||||
else:
|
||||
Gen._InstallHotspotSites()
|
||||
ir_code: str = Gen.finalize()
|
||||
return ir_code
|
||||
|
||||
@@ -659,6 +668,11 @@ class LlvmGeneratorMixin:
|
||||
# 函数热点统计(--func-hotspot):透传落盘路径与注册策略;未启用时不产生任何 IR 改动
|
||||
self.LlvmGen.func_hotspot_file = getattr(self, 'FuncHotspotFile', None)
|
||||
self.LlvmGen.func_hotspot_module_local = getattr(self, 'FuncHotspotModuleLocal', False)
|
||||
# 调用点热点统计(--hotspot-sites):同上;未启用时不产生任何 IR 改动
|
||||
self.LlvmGen.hotspot_sites_file = getattr(self, 'HotspotSitesFile', None)
|
||||
self.LlvmGen.hotspot_sites_all = getattr(self, 'HotspotSitesAll', False)
|
||||
self.LlvmGen.hotspot_callees = getattr(self, 'HotspotCallees', None)
|
||||
self.LlvmGen.hotspot_sites_module_local = getattr(self, 'HotspotSitesModuleLocal', False)
|
||||
self.LlvmGen.setup_from_symbol_table(self.SymbolTable)
|
||||
if hasattr(self, 'CurrentFile') and self.CurrentFile:
|
||||
self.LlvmGen._set_source_info(self.CurrentFile)
|
||||
|
||||
Reference in new issue
Block a user