diff --git a/lib/Projectrans/Config.py b/lib/Projectrans/Config.py index 0f7bee0..c9efbb7 100644 --- a/lib/Projectrans/Config.py +++ b/lib/Projectrans/Config.py @@ -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 各模块以追加方式把计数写入 (含 main 的模块在启动时清空一次,保证每次运行一份结果)(可用 python Projectrans.py --project ... --func-hotspot )') + parser.add_argument('--hotspot-sites', nargs='?', const='', default=None, help='调用点热点统计:为每个匹配白名单的直接 call 点插入计数器,运行时 Ctrl+C 各模块按真实次数降序写入 (默认为 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'): diff --git a/lib/Projectrans/Phase2Translator.py b/lib/Projectrans/Phase2Translator.py index 8b104e5..cdb402d 100644 --- a/lib/Projectrans/Phase2Translator.py +++ b/lib/Projectrans/Phase2Translator.py @@ -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 时各模块处理器统一追加写最终 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):批内每文件的处理器统一追加写最终 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() diff --git a/lib/Shell/TransPyC.py b/lib/Shell/TransPyC.py index ac50148..451c0df 100644 --- a/lib/Shell/TransPyC.py +++ b/lib/Shell/TransPyC.py @@ -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'] diff --git a/lib/constants/config.py b/lib/constants/config.py index 57de922..511bc32 100644 --- a/lib/constants/config.py +++ b/lib/constants/config.py @@ -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" diff --git a/lib/core/Handles/HandlesAsyncFunctions.py b/lib/core/Handles/HandlesAsyncFunctions.py index bce6107..157868b 100644 --- a/lib/core/Handles/HandlesAsyncFunctions.py +++ b/lib/core/Handles/HandlesAsyncFunctions.py @@ -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) diff --git a/lib/core/Handles/HandlesClassDef.py b/lib/core/Handles/HandlesClassDef.py index 73940f9..e015b5f 100644 --- a/lib/core/Handles/HandlesClassDef.py +++ b/lib/core/Handles/HandlesClassDef.py @@ -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 diff --git a/lib/core/Handles/HandlesExprLambda.py b/lib/core/Handles/HandlesExprLambda.py index db37c70..b60cad3 100644 --- a/lib/core/Handles/HandlesExprLambda.py +++ b/lib/core/Handles/HandlesExprLambda.py @@ -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): diff --git a/lib/core/Handles/HandlesFunctions.py b/lib/core/Handles/HandlesFunctions.py index 5ac351d..bb6cf0f 100644 --- a/lib/core/Handles/HandlesFunctions.py +++ b/lib/core/Handles/HandlesFunctions.py @@ -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 = {} diff --git a/lib/core/LLVMCG/BaseGen.py b/lib/core/LLVMCG/BaseGen.py index 403ddc9..847ce76 100644 --- a/lib/core/LLVMCG/BaseGen.py +++ b/lib/core/LLVMCG/BaseGen.py @@ -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")方式写 ,若不清空会累积历史运行的 - 数据。清空必须满足两点: + --func-hotspot/--hotspot-sites 的处理器以追加("a")方式写 ,若不清空会 + 累积历史运行的数据。清空必须满足两点: - 只在含 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) \ No newline at end of file + 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_ 的顺序。源码位置来自生成期挂在 + 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 处理器:按调用次数降序把各调用点追加写入 。 + + 落盘格式(追加): + # sites \\n + -> :\\n + module_id 为空时省略行首 sha1 与头部 sha1(留空,不伪造)。 + + - 排序:落盘前用选择排序(两层运行期循环,IR 规模 O(N)、运行期 O(N^2) 仅 + 在 Ctrl+C 触发一次)按计数降序输出,只输出 count>0 的调用点;计数相同时 + 按计数器编号 K 升序(稳定、可复现)。 + - 源码位置:作为可选尾字段,格式 ":"(含前置单个空格);该位置 + 来自生成期挂在 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) + ]) + # 源码位置全局数组:每项形如 " :"(含前置空格);取不到位置则为空串 + 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" 打开一次 清空历史计数,再注册 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 处理器(统一追加写最终 ), + 并各自通过 @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') \ No newline at end of file diff --git a/lib/core/Translator/BaseTranslator.py b/lib/core/Translator/BaseTranslator.py index 1835bb4..59dd21c 100644 --- a/lib/core/Translator/BaseTranslator.py +++ b/lib/core/Translator/BaseTranslator.py @@ -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='') diff --git a/lib/core/Translator/LlvmGenerator.py b/lib/core/Translator/LlvmGenerator.py index e79716a..e5a6c3c 100644 --- a/lib/core/Translator/LlvmGenerator.py +++ b/lib/core/Translator/LlvmGenerator.py @@ -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)