diff --git a/lib/Projectrans/Config.py b/lib/Projectrans/Config.py index 7b3788a..0f7bee0 100644 --- a/lib/Projectrans/Config.py +++ b/lib/Projectrans/Config.py @@ -149,6 +149,7 @@ def main() -> None: parser.add_argument('--clear-cache', action='store_true', help='清除 .transpyc_cache 全局缓存(.vpi/.stub.ll/.doc.json)') 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 )') args: argparse.Namespace = parser.parse_args() @@ -193,6 +194,11 @@ def main() -> None: pass target_triple: str | None = resolved.get('target', {}).get('triple') target_datalayout: str | None = resolved.get('target', {}).get('datalayout') + # 函数热点统计(--func-hotspot):解析为绝对路径,保证运行时(exe 工作目录为 + # output_dir)各模块处理器追加写入的路径与预期完全一致。 + func_hotspot_file: str | None = args.func_hotspot + if func_hotspot_file: + func_hotspot_file = os.path.abspath(func_hotspot_file) if isinstance(src, list): if len(src) == 1 and os.path.isdir(src[0]): @@ -209,6 +215,8 @@ def main() -> None: _vlog().info(f"目标三元组: {target_triple}") if target_datalayout: _vlog().info(f"数据布局: {target_datalayout}") + if func_hotspot_file: + _vlog().info(f"函数热点统计: {func_hotspot_file}(各模块运行时以追加方式写入,含 main 的模块启动时清空一次)") # 每次编译自动清理子进程性能剖析输出(PROF 目录 *.prof,硬编码路径) from lib.Projectrans.Phase2Translator import _CleanProfFiles, PROF_DIR @@ -314,6 +322,8 @@ def main() -> None: trans: Phase2Translator = Phase2Translator(src, temp_dir, output_dir, compile_cmd=cc_cmd, compile_flags=cc_flags, linker_cmd=linker_cmd, linker_flags=linker_flags, linker_output=linker_output, static_libs=static_libs, dynamic_libs=dynamic_libs, include_dirs=project_include_dirs, target_triple=target_triple, target_datalayout=target_datalayout, force_recompile_includes=force_recompile_includes, asan_mode=args.asan) trans.sha1_map = Load_sha1_map(temp_dir) trans.slice_level = slice_level + # 函数热点统计(--func-hotspot):None=未启用(零影响) + trans.func_hotspot_file = func_hotspot_file 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 752b9da..8b104e5 100644 --- a/lib/Projectrans/Phase2Translator.py +++ b/lib/Projectrans/Phase2Translator.py @@ -26,9 +26,9 @@ from StubGen import PythonToStubConverter # PROF 热点:nt._path_isfile 2.6 万次 16.4s(Windows 磁盘 stat ~0.6ms/次)。 # 编译进程内文件状态稳定(obj/ll 一旦生成不删除、不被移动),用模块级缓存 # 避免重复 stat。仅用于"存在性检查"类调用(不用于内容变化的判断)。 -_FILE_STAT_CACHE: dict[str, bool] = {} - - +_FILE_STAT_CACHE: dict[str, bool] = {} + + def _CachedIsFile(path: str) -> bool: r: bool | None = _FILE_STAT_CACHE.get(path) if r is None: @@ -161,7 +161,8 @@ def _parallel_translate_worker(src_path: str, out_path: str, src_root: str, temp 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], - prebuilt_ModuleSha1Map: dict[str, str] | None = None) -> tuple[str, str, str]: + prebuilt_ModuleSha1Map: dict[str, str] | None = None, + func_hotspot_file: str | None = None) -> tuple[str, str, str]: # 注意:不可跨任务复用 Phase2Translator/共享符号表!_translate_file 编译过程中 # 会向共享 SymbolTable 写入符号(import insert/类注册等),复用导致前文件的 # 符号污染后文件 → 跨模块 struct 类型前缀错位(如 7cadfe07e983fd90.CompilerFactory @@ -179,6 +180,9 @@ def _parallel_translate_worker(src_path: str, out_path: str, src_root: str, temp trans.include_py_map = include_py_map trans.slice_level = slice_level trans.struct_sha1_map = struct_sha1_map + # 函数热点统计(--func-hotspot):worker 子进程无父进程属性,须显式透传 + # (None=未启用,零影响);生成 IR 时各模块处理器统一追加写最终 + trans.func_hotspot_file = func_hotspot_file if shared_sym_pickle_path and os.path.exists(shared_sym_pickle_path): # 使用 pickle.load(f) 流式加载,避免 pickle.loads(f.read()) 一次性 @@ -222,7 +226,8 @@ def _parallel_translate_batch(batch: list, src_root: str, temp_dir: str, output_ include_dirs: list[str], triple: str | None, datalayout: str | None, 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]) -> tuple[list, list]: + shared_sym_pickle_path: str, struct_sha1_map: dict[str, str], + func_hotspot_file: str | None = None) -> tuple[list, list]: """批次子任务:worker 内创建一个 Phase2Translator + pickle.load 一次,顺序翻译批内全部文件。 batch 每项: (i, src_path, out_path, sha1, prebuilt_ModuleSha1Map) @@ -243,6 +248,8 @@ def _parallel_translate_batch(batch: list, src_root: str, temp_dir: str, output_ trans.include_py_map = include_py_map trans.slice_level = slice_level trans.struct_sha1_map = struct_sha1_map + # 函数热点统计(--func-hotspot):批内每文件的处理器统一追加写最终 + trans.func_hotspot_file = func_hotspot_file symbol_table_bytes: bytes | None = None if shared_sym_pickle_path and os.path.exists(shared_sym_pickle_path): @@ -640,7 +647,11 @@ class Phase2Translator: if os.path.isdir(_clang64_bin): os.environ['PATH'] = _clang64_bin + os.pathsep + os.environ.get('PATH', '') self.asan_rt_dir = _FindAsanRuntimeDir() - self.slice_level = 3 + self.slice_level = 3 + # 函数热点统计(--func-hotspot):None=未启用(零影响);str=计数器输出 + # 文件路径。各模块运行时自带 Ctrl+C 处理器、统一以追加方式写该路径; + # 含 main 的模块在 main 入口清空一次 → 每次运行一份结果(无构建后合并步骤)。 + self.func_hotspot_file: str | None = None self.sha1_map: dict[str, str] = {} self.sig_files: dict[str, str] = {} self.stub_files: dict[str, str] = {} @@ -844,6 +855,10 @@ 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) 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 过) @@ -889,7 +904,7 @@ class Phase2Translator: raise _vlog().warning(f"收集源文件导入的 include 模块失败: {_e}", "Exception") - if _CachedIsFile(out_path): + if not hotspot_on and _CachedIsFile(out_path): deps_changed = self._check_deps_changed(sha1, current_ModuleSha1Map) if not deps_changed: _vlog().info(f"[{i}/{len(py_files)}] 跳过(缓存): {rel} -> {sha1}.ll") @@ -900,7 +915,7 @@ class Phase2Translator: continue _vlog().info(f"[{i}/{len(py_files)}] 重译(依赖变化): {rel} -> {sha1}.ll") - else: + elif not hotspot_on: stub_cache = os.path.join(self.output_dir, f"{sha1}.stub.ll") text_cache = os.path.join(self.output_dir, f"{sha1}.text.ll") if _CachedIsFile(stub_cache) and _CachedIsFile(text_cache): @@ -1002,6 +1017,7 @@ class Phase2Translator: self.sha1_map, self.sig_files, self.stub_files, self.include_py_map, self.slice_level, shared_pickle_path, self.struct_sha1_map, + func_hotspot_file=self.func_hotspot_file, ) futures[future] = (bidx, rel_first, len(batch)) @@ -1276,6 +1292,11 @@ class Phase2Translator: if stripped.startswith('@') and ' global ' in stripped and 'external global' not in stripped: text_lines.append(line) + # LLVM 保留全局(如 --func-hotspot 的 @llvm.global_ctors;名字含点故 + # 最终 IR 保留引号)是 appending 定义,禁止改写为 "external global" + # 存根(否则 .ll 重组后 llc 报错) + if re.match(r'@("?)llvm\.', stripped): + continue if ' internal ' not in stripped and ' private ' not in stripped: name_match = re.match(r'(@\S+)', stripped) if name_match: @@ -1537,6 +1558,14 @@ class Phase2Translator: trans.translator._shared_class_methods = self._shared_class_methods trans.translator._shared_cross_module_novtable = self._shared_cross_module_novtable + # 函数热点统计(--func-hotspot,Projectrans 多模块): + # 各模块处理器统一以追加方式写最终 ;注册策略为本模块 + # @llvm.global_ctors 自注册(见 BaseGen._InstallFuncHotspotModuleLocal)。 + # self.func_hotspot_file 为 None 时不设置任何属性 → 与未启用时逐字节一致(零影响)。 + if self.func_hotspot_file: + trans.translator.FuncHotspotFile = self.func_hotspot_file + trans.translator.FuncHotspotModuleLocal = True + if self._shared_symbol_table is not None: trans.translator.SymbolTable = self._shared_symbol_table # 确保共享符号表的 translator 指向当前 translator,以便 import 别名解析正确 @@ -1913,7 +1942,7 @@ class Phase2Translator: opt_content = re.sub(r'^(define|declare)\s+(?!private|internal|available_externally|linkonce|weak|common|appending|extern_weak|linkonce_odr|weak_odr|external|dso_local|@)', r'\1 dso_local ', opt_content, flags=re.MULTILINE) # LLVM 22+: external 不允许在带初值的全局定义上显式写出,改为 linkonce 保留多模块合并语义 opt_content = re.sub(r'(@[\w.]+)\s*=\s*external\s+global\s+(\w[\w*]*)\s+(-?\d+|null|zeroinitializer|true|false)$', r'\1 = linkonce global \2 \3', opt_content, flags=re.MULTILINE) - opt_content = re.sub(r'(@[\w.]+)\s*=\s*(private|internal|available_externally|linkonce|weak|common|appending|extern_weak|linkonce_odr|weak_odr)\s+(global|constant)', r'\1 = \2 dso_local \3', opt_content) + opt_content = re.sub(r'(@(?!llvm\.)[\w.]+)\s*=\s*(private|internal|available_externally|linkonce|weak|common|appending|extern_weak|linkonce_odr|weak_odr)\s+(global|constant)', r'\1 = \2 dso_local \3', opt_content) opt_content = re.sub(r'(@[\w.]+)\s*=\s+(?!private|internal|available_externally|linkonce|weak|common|appending|extern_weak|linkonce_odr|weak_odr|dso_local)(global|constant)', r'\1 = dso_local \2', opt_content) opt_content = _AddWeakComdat(opt_content) with open(optimized_ll, 'w', encoding='utf-8') as f: @@ -2147,7 +2176,7 @@ class Phase2Translator: ll_content = re.sub(r'^(define|declare)\s+(?!private|internal|available_externally|linkonce|weak|common|appending|extern_weak|linkonce_odr|weak_odr|external|dso_local|@)', r'\1 dso_local ', ll_content, flags=re.MULTILINE) # LLVM 22+: external 不允许在带初值的全局定义上显式写出,改为 linkonce 保留多模块合并语义 ll_content = re.sub(r'^(@[\w.]+)\s*=\s*external\s+global\s+(\w[\w*]*)\s+(-?\d+|null|zeroinitializer|true|false)$', r'\1 = linkonce global \2 \3', ll_content, flags=re.MULTILINE) - ll_content = re.sub(r'^(@[\w.]+)\s*=\s*(private|internal|available_externally|linkonce|weak|common|appending|extern_weak|linkonce_odr|weak_odr)\s+(global|constant)', r'\1 = \2 dso_local \3', ll_content, flags=re.MULTILINE) + ll_content = re.sub(r'(@(?!llvm\.)[\w.]+)\s*=\s*(private|internal|available_externally|linkonce|weak|common|appending|extern_weak|linkonce_odr|weak_odr)\s+(global|constant)', r'\1 = \2 dso_local \3', ll_content, flags=re.MULTILINE) ll_content = re.sub(r'^(@[\w.]+)\s*=\s+(?!private|internal|available_externally|linkonce|weak|common|appending|extern_weak|linkonce_odr|weak_odr|dso_local)(global|constant)', r'\1 = dso_local \2', ll_content, flags=re.MULTILINE) ll_content = _AddWeakComdat(ll_content) # LLVM 22+: 移除同一模块中已有 define 的 declare 语句(LLVM 22 将 declare+define 视为重复定义) @@ -2815,7 +2844,8 @@ class Phase2Translator: self.sha1_map, self.sig_files, self.stub_files, self.include_py_map, self.slice_level, shared_pickle_path, self.struct_sha1_map, - include_ModuleSha1Map + include_ModuleSha1Map, + func_hotspot_file=self.func_hotspot_file ) in_progress[future] = item @@ -2831,7 +2861,8 @@ class Phase2Translator: self.sha1_map, self.sig_files, self.stub_files, self.include_py_map, self.slice_level, shared_pickle_path, self.struct_sha1_map, - include_ModuleSha1Map + include_ModuleSha1Map, + func_hotspot_file=self.func_hotspot_file ) in_progress[future] = item remaining.clear() @@ -2898,7 +2929,7 @@ class Phase2Translator: ll_content = re.sub(r'^(define|declare)\s+(?!private|internal|available_externally|linkonce|weak|common|appending|extern_weak|linkonce_odr|weak_odr|external|dso_local)', r'\1 dso_local ', ll_content, flags=re.MULTILINE) # LLVM 22+: external 不允许在带初值的全局定义上显式写出,改为 linkonce 保留多模块合并语义 ll_content = re.sub(r'^(@[\w.]+)\s*=\s*external\s+global\s+(\w[\w*]*)\s+(-?\d+|null|zeroinitializer|true|false)$', r'\1 = linkonce global \2 \3', ll_content, flags=re.MULTILINE) - ll_content = re.sub(r'^(@[\w.]+)\s*=\s*(private|internal|available_externally|linkonce|weak|common|appending|extern_weak|linkonce_odr|weak_odr)\s+(global|constant)', r'\1 = \2 dso_local \3', ll_content, flags=re.MULTILINE) + ll_content = re.sub(r'(@(?!llvm\.)[\w.]+)\s*=\s*(private|internal|available_externally|linkonce|weak|common|appending|extern_weak|linkonce_odr|weak_odr)\s+(global|constant)', r'\1 = \2 dso_local \3', ll_content, flags=re.MULTILINE) ll_content = re.sub(r'^(@[\w.]+)\s*=\s+(?!private|internal|available_externally|linkonce|weak|common|appending|extern_weak|linkonce_odr|weak_odr|dso_local)(global|constant)', r'\1 = dso_local \2', ll_content, flags=re.MULTILINE) ll_content = _AddWeakComdat(ll_content) diff --git a/lib/Shell/TransPyC.py b/lib/Shell/TransPyC.py index 9872b3a..ac50148 100644 --- a/lib/Shell/TransPyC.py +++ b/lib/Shell/TransPyC.py @@ -332,6 +332,14 @@ class TransPyC: else: error('--datalayout requires an argument') sys.exit(1) + elif sys.argv[I] == '--func-hotspot': + # 函数热点统计:为每个函数定义配 u64 计数器,Ctrl+C 时写入指定文件 + if I + 1 < len(sys.argv): + self.Args['FuncHotspot'] = sys.argv[I + 1] + I += 2 + else: + error('--func-hotspot requires an argument') + sys.exit(1) else: error(f'Unknown argument {sys.argv[I]}') sys.exit(1) @@ -596,6 +604,9 @@ class TransPyC: # 有命令行参数,使用 ParseArgs 方法解析 self.ParseArgs() + # 函数热点统计(--func-hotspot):绑定到翻译器;未传时为 None(零影响) + self.translator.FuncHotspotFile = self.Args.get('FuncHotspot') + # 检查是否有预处理符号文件的请求 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 6cf9a7d..57de922 100644 --- a/lib/constants/config.py +++ b/lib/constants/config.py @@ -37,7 +37,9 @@ 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] -h: Specify helper files (C or Python) to help identify structs, functions, variables, and ptrs + -func-hotspot: 统计各函数调用次数,Ctrl+C 退出时写入 HotFile(每行 "函数名 计数") ''' mode: str = "strict" diff --git a/lib/core/LLVMCG/BaseGen.py b/lib/core/LLVMCG/BaseGen.py index 25c9931..403ddc9 100644 --- a/lib/core/LLVMCG/BaseGen.py +++ b/lib/core/LLVMCG/BaseGen.py @@ -173,6 +173,11 @@ class BaseGenMixin: 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]]] = {} + # 函数热点统计(--func-hotspot):None=未启用(零影响);str=计数器落盘文件路径 + self.func_hotspot_file: str | None = None + # 注册策略:False=种子 CLI 单模块(在 main 入口注册);True=Projectrans + # 多模块(每模块用 @llvm.global_ctors 自注册,不依赖 main 所在模块) + self.func_hotspot_module_local: bool = False pi: dict[str, bool | int] = self._platform_info # 根据目标平台设置宏(声明式,从三元组推导) if pi['is_windows']: @@ -849,4 +854,253 @@ class BaseGenMixin: return None return ir.Constant(llvm_type, elem_zvs) return None - return None \ No newline at end of file + return None + + # ========== 函数热点统计(--func-hotspot) ========== + + def _UniqueGlobalName(self, base: str) -> str: + """返回本模块内未被占用的全局符号名(追加下划线直到不冲突)。 + + 仅用于 --func-hotspot 自动生成的内部符号,避免与用户全局/函数重名。 + """ + name: str = base + while name in self.module.globals: + name += '_' + return name + + def _DeclareExternalFunc(self, name: str, ftype: ir.FunctionType) -> ir.Function: + """获取或声明外部 C 函数(用于 --func-hotspot 所需的 Win32/CRT 接口)。 + + 已存在同名函数时直接复用;若已存在符号类型不一致则显式报错(不静默降级)。 + """ + existing: Any = self.module.globals.get(name) + if existing is not None: + if not isinstance(existing, ir.Function): + raise RuntimeError( + f'--func-hotspot: 全局符号 {name} 已存在且不是函数,无法声明外部接口') + if existing.ftype != ftype: + raise RuntimeError( + f'--func-hotspot: 外部函数 {name} 已存在但签名不一致' + f'(期望 {ftype},实际 {existing.ftype})') + return existing + return ir.Function(self.module, ftype, name=name) + + def _EmitFuncHotspotCounters(self) -> tuple[list[ir.Function], list[ir.GlobalVariable], list[ir.Constant]]: + """为模块内每个函数定义安装 internal i64 计数器并在入口递增。 + + 返回 (被计数的函数定义列表, 计数器列表, 函数名常量指针列表)。 + + 抽出自原 _InstallFuncHotspot,供两种注册策略复用: + - 种子 CLI 单模块:在 main 入口注册处理器(原行为); + - Projectrans 多模块:每模块用 @llvm.global_ctors 自注册。 + 计数器一律 internal,跨模块(独立翻译单元)链接不产生重复符号。 + """ + i64: ir.IntType = ir.IntType(64) + # 收集模块内所有函数定义(只有声明没有基本块的属于 declare,跳过) + defs: list[ir.Function] = [f for f in self.module.functions if getattr(f, 'blocks', None)] + if not defs: + raise RuntimeError('--func-hotspot: 模块内没有任何函数定义,无法安装计数') + counters: list[ir.GlobalVariable] = [] + name_ptrs: list[ir.Constant] = [] + for idx, func in enumerate(defs): + # 计数器全局:唯一命名的 internal i64,初值 0 + counter: ir.GlobalVariable = ir.GlobalVariable( + self.module, i64, name=self._UniqueGlobalName(f'__fhs_cnt_{idx}')) + counter.linkage = 'internal' + counter.initializer = ir.Constant(i64, 0) + counters.append(counter) + # 可读函数名(IR 中真实符号名)写入只读字符串常量,供处理器打印 + name_ptrs.append(self._create_string_global( + func.name, name=self._UniqueGlobalName(f'__fhs_name_{idx}'))) + # 入口块最前递增:load -> add 1 -> store(置于最前,不破坏原有跳转) + entry: ir.Block = func.entry_basic_block + b: ir.IRBuilder = ir.IRBuilder(entry) + b.position_at_start(entry) + cur: ir.Value = b.load(counter, name=f'fhs_load_{idx}') + inc: ir.Value = b.add(cur, ir.Constant(i64, 1), name=f'fhs_inc_{idx}') + b.store(inc, counter) + return defs, counters, name_ptrs + + def _CreateFuncHotspotHandler( + self, + path: str, + counters: list[ir.GlobalVariable], + name_ptrs: list[ir.Constant], + module_id: str | None = None, + ) -> ir.Function: + """生成 Win32 控制台 Ctrl+C 处理器:把所有计数器追加写入文件。 + + - 处理器签名为 Win32 PHANDLER_ROUTINE(i32 (i32) -> BOOL) + - 用 CRT fopen("a")/fprintf/fclose 落盘:多模块各自持有处理器,运行时各 + 自以追加方式写同一个 ,互不覆盖(见 _InstallFuncHotspotModuleLocal) + - module_id 非空时首行先写 "# module \n",用于区分不同模块的计数段 + - 每行格式为 "函数名 计数\n" + - 返回 FALSE(0),让默认处理继续,使 Ctrl+C 仍能终止程序 + """ + 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])) + 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_handler')) + handler.linkage = 'internal' + b: ir.IRBuilder = ir.IRBuilder(handler.append_basic_block(name='entry')) + b.position_at_start(handler.entry_basic_block) + path_ptr: ir.Constant = self._create_string_global( + path, name=self._UniqueGlobalName('__fhs_path')) + # 追加模式 "a":多模块多处理器各自追加到同一个 ,互不覆盖 + mode_ptr: ir.Constant = self._create_string_global( + 'a', name=self._UniqueGlobalName('__fhs_mode')) + fmt_ptr: ir.Constant = self._create_string_global( + '%s %llu\n', name=self._UniqueGlobalName('__fhs_fmt')) + fp: ir.Value = b.call(fopen, [path_ptr, mode_ptr], name='fhs_fp') + if module_id: + hdr_fmt_ptr: ir.Constant = self._create_string_global( + '# module %s\n', name=self._UniqueGlobalName('__fhs_hdr_fmt')) + mid_ptr: ir.Constant = self._create_string_global( + module_id, name=self._UniqueGlobalName('__fhs_module_id')) + b.call(fprintf, [fp, hdr_fmt_ptr, mid_ptr], name='fhs_write_module') + for gv, name_ptr in zip(counters, name_ptrs): + b.call(fprintf, [fp, fmt_ptr, name_ptr, b.load(gv)], name='fhs_write') + b.call(fclose, [fp]) + b.ret(ir.Constant(i32, 0)) + return handler + + def _RegisterFuncHotspotViaCtor(self, handler: ir.Function) -> None: + """用 @llvm.global_ctors 在进程启动期为本模块注册 Ctrl+C 处理器。 + + 生成 internal void @__fhs_ctor()(内部调用 SetConsoleCtrlHandler), + 并把它写入 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)) + # 构造器元素类型 { i32, void ()*, i8* }:优先级、函数指针、关联数据(NULL) + 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_ctor')) + ctor.linkage = 'internal' + b: ir.IRBuilder = ir.IRBuilder(ctor.append_basic_block(name='entry')) + b.call(set_handler, [b.bitcast(handler, i8p), ir.Constant(i32, 1)]) + b.ret_void() + if 'llvm.global_ctors' in self.module.globals: + raise RuntimeError( + '--func-hotspot: 模块内已存在 @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 _InstallFuncHotspotTruncate( + self, + defs: list[ir.Function], + path: str, + require_main: bool = False, + ) -> ir.Function | None: + """在含 main 的模块的 main 入口插入一次 "w" 打开→关闭,清空计数文件。 + + --func-hotspot 的处理器以追加("a")方式写 ,若不清空会累积历史运行的 + 数据。清空必须满足两点: + - 只在含 main 的模块执行一次(其它模块无 main,返回 None 且不截断); + - 必须在 main 入口而非构造器:@llvm.global_ctors 的跨模块执行顺序无保证, + 放构造器里可能在其它模块已写入后清空 → 丢数据。 + main 入口在程序启动、任何 Ctrl+C 处理器写入之前执行,故每次运行都以空文件 + 开始,配合追加写保证"一次运行一份结果"。 + + 返回找到的 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( + '--func-hotspot: 找不到 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 + + def _InstallFuncHotspot(self) -> None: + """安装函数热点统计(--func-hotspot):种子 CLI 单模块路径。 + + 在 main 入口最前:先用 "w" 打开一次 清空历史计数,再注册 Ctrl+C + 处理器(程序启动期各执行一次)。仅在 func_hotspot_file 非 None(用户传入 + --func-hotspot)时被调用;未启用时该方法不会执行,IR 输出与既有行为一致。 + """ + path: str | None = self.func_hotspot_file + if not path: + raise RuntimeError('--func-hotspot: 需要提供计数器输出文件路径') + i32: ir.IntType = ir.IntType(32) + i8p: ir.PointerType = ir.PointerType(ir.IntType(8)) + defs, counters, name_ptrs = self._EmitFuncHotspotCounters() + handler: ir.Function = self._CreateFuncHotspotHandler( + path, counters, name_ptrs, module_id=self.module_sha1 or None) + # main 入口清空历史计数文件(require_main=True → 无 main 显式报错) + main_func: ir.Function | None = self._InstallFuncHotspotTruncate( + defs, path, require_main=True) + if main_func is None: + raise RuntimeError( + '--func-hotspot: 找不到 main 函数定义,无法注册 Ctrl+C 处理器') + # 在 main 入口注册处理器(清空之后、程序 body 之前执行) + 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])) + handler_ptr: ir.Value = mb.bitcast(handler, i8p) + mb.call(set_handler, [handler_ptr, ir.Constant(i32, 1)]) + + def _InstallFuncHotspotModuleLocal(self) -> None: + """安装函数热点统计(--func-hotspot):Projectrans 多模块路径。 + + 每个模块是独立翻译单元、各自 llc 后链接,无法跨模块共享同一处理器, + 故每模块自带 internal 计数器 + internal 处理器(统一追加写最终 ), + 并各自通过 @llvm.global_ctors 在进程启动期注册 SetConsoleCtrlHandler。 + 所有新增符号均 internal,跨模块不产生重复符号;多个处理器在 Ctrl+C 时 + 由系统 LIFO 依次调用、各自以追加("a")方式写同一文件,互不覆盖。 + 仅含 main 的模块额外在 main 入口以 "w" 清空一次文件(见 + _InstallFuncHotspotTruncate),保证"一次运行一份结果"。未启用 + (func_hotspot_file 为 None)时本方法不会被调用。 + """ + path: str | None = self.func_hotspot_file + if not path: + raise RuntimeError('--func-hotspot: 需要提供计数器输出文件路径') + module_id: str = getattr(self, 'module_sha1', None) or '' + defs, counters, name_ptrs = self._EmitFuncHotspotCounters() + handler: ir.Function = self._CreateFuncHotspotHandler( + path, counters, name_ptrs, module_id=module_id or None) + self._RegisterFuncHotspotViaCtor(handler) + # 非 main 模块无 main、不需要截断(返回 None 不报错):其它模块的写入 + # 依赖含 main 模块在启动期清空,不会互相覆盖。 + self._InstallFuncHotspotTruncate(defs, path) \ No newline at end of file diff --git a/lib/core/Translator/BaseTranslator.py b/lib/core/Translator/BaseTranslator.py index e48d963..1835bb4 100644 --- a/lib/core/Translator/BaseTranslator.py +++ b/lib/core/Translator/BaseTranslator.py @@ -76,6 +76,11 @@ 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 # 导出表 self.Exportable = Exportable(filename='') diff --git a/lib/core/Translator/LlvmGenerator.py b/lib/core/Translator/LlvmGenerator.py index 42ce967..e79716a 100644 --- a/lib/core/Translator/LlvmGenerator.py +++ b/lib/core/Translator/LlvmGenerator.py @@ -228,6 +228,15 @@ class LlvmGeneratorMixin: Gen._set_Vtable_initializers() # 执行 DecoratorPass:为带自定义装饰器的函数生成 wrapper _run_decorator_pass(Gen) + # 函数热点统计(--func-hotspot):在装饰器 wrapper 生成之后统一收口, + # 为模块内所有函数定义安装计数器与 Ctrl+C 处理器;未启用时不执行(零影响)。 + # func_hotspot_module_local=True 走 Projectrans 多模块路径(每模块 + # @llvm.global_ctors 自注册);否则走种子 CLI 单模块路径(main 入口注册)。 + if Gen.func_hotspot_file: + if Gen.func_hotspot_module_local: + Gen._InstallFuncHotspotModuleLocal() + else: + Gen._InstallFuncHotspot() ir_code: str = Gen.finalize() return ir_code @@ -647,6 +656,9 @@ class LlvmGeneratorMixin: self.LlvmGen._temp_dir = self._temp_dir if hasattr(self, '_export_extern_funcs'): self.LlvmGen._export_extern_funcs = self._export_extern_funcs + # 函数热点统计(--func-hotspot):透传落盘路径与注册策略;未启用时不产生任何 IR 改动 + self.LlvmGen.func_hotspot_file = getattr(self, 'FuncHotspotFile', None) + self.LlvmGen.func_hotspot_module_local = getattr(self, 'FuncHotspotModuleLocal', False) self.LlvmGen.setup_from_symbol_table(self.SymbolTable) if hasattr(self, 'CurrentFile') and self.CurrentFile: self.LlvmGen._set_source_info(self.CurrentFile)