from __future__ import annotations from typing import TYPE_CHECKING if TYPE_CHECKING: from lib.core.translator import Translator from lib.core.Translator.LlvmGenerator import LlvmGeneratorMixin import ast import inspect import os import re import hashlib import llvmlite.ir as ir from lib.core.Handles.HandlesBase import BaseHandle, CTypeInfo, FuncMeta from lib.core.VLogger import get_logger as _vlog from lib.constants.config import mode as _config_mode from lib.includes import t from lib.includes.t import CTypeRegistry from lib.core.SymbolUtils import IsTModule, ParseListAnnotation, ExtractTypeNameFromBinOp, AnnotationContainsName class ImportHandle(BaseHandle): _stub_cache = {} _stub_cache_dir = None _struct_Load_cache = {} _pyi_cache = {} _pyi_cache_dir = None _project_root_cache = None def _reset_file_cache(self) -> None: self._struct_Load_cache.clear() def _find_project_root(self) -> str: if self._project_root_cache is not None: return self._project_root_cache # 优先使用 translator 上显式设置的 _temp_dir(由 Phase2Translator._translate_file 传入), # 这样可以正确处理 include 文件——其源文件目录不在项目根目录下, # 从源文件目录向上搜索 temp/output 会失败并回退到错误的路径。 temp_dir_attr = getattr(self.Trans, '_temp_dir', None) if temp_dir_attr and os.path.isdir(temp_dir_attr): project_root = os.path.dirname(os.path.abspath(temp_dir_attr)) if os.path.isdir(os.path.join(project_root, 'temp')) and os.path.isdir(os.path.join(project_root, 'output')): self._project_root_cache = project_root return project_root current_file = getattr(self.Trans, 'CurrentFile', '') or '' if current_file: current_dir = os.path.dirname(os.path.abspath(current_file)) search_dir = current_dir for _ in range(10): if os.path.isdir(os.path.join(search_dir, 'temp')) and os.path.isdir(os.path.join(search_dir, 'output')): self._project_root_cache = search_dir return search_dir parent = os.path.dirname(search_dir) if parent == search_dir: break search_dir = parent # 回退到当前工作目录(编译运行时的项目根目录) cwd = os.getcwd() if os.path.isdir(os.path.join(cwd, 'temp')) and os.path.isdir(os.path.join(cwd, 'output')): self._project_root_cache = cwd return cwd if current_file: self._project_root_cache = current_dir return current_dir self._project_root_cache = cwd return cwd def _EmitImportDeclarationsLlvm(self, Node: ast.Import, Gen: LlvmGeneratorMixin) -> None: if not getattr(self.Trans, '_ImportedModules', None): self.Trans._ImportedModules = set() for alias in Node.names: name = alias.name if name in ('c', 't'): if alias.asname: self.Trans.SymbolTable.import_aliases[alias.asname] = name AliasInfo = CTypeInfo() AliasInfo.IsModuleAlias = True AliasInfo.ResolvedModule = name self.Trans.SymbolTable.insert(alias.asname, AliasInfo) continue self.Trans._ImportedModules.add(name) if alias.asname: self.Trans.SymbolTable.import_aliases[alias.asname] = name # 同时在符号表中注册模块别名,以便 CTypeInfo.FromNode 能解析 AliasInfo = CTypeInfo() AliasInfo.IsModuleAlias = True AliasInfo.ResolvedModule = name self.Trans.SymbolTable.insert(alias.asname, AliasInfo) current_module = self._get_current_module_name() self._EmitModuleDeclarationsLlvm(name, Gen, register_module_name=current_module) def _RegisterFromImportAliases(self, Node: ast.ImportFrom, Gen: LlvmGeneratorMixin, module: str) -> None: """Register 'from module import y as z' aliases after module declarations are Loaded""" if not Node.names: return for alias in Node.names: name = alias.name asname = alias.asname # For 'from X import Y' (no asname), register CDefine constants # into _define_constants so they can be found by _HandleNameLlvm if not asname or asname == name: # Check if this name is a CDefine constant in SymbolTable sym_info = self.Trans.SymbolTable.lookup(name) if sym_info and sym_info.IsDefine and sym_info.DefineValue is not None: define_constants = vars(Gen).setdefault('_define_constants', {}) if name not in define_constants: define_constants[name] = sym_info.DefineValue # Also check with module prefix for prefix_key in [f"{module}.{name}", name]: sym_info2 = self.Trans.SymbolTable.lookup(prefix_key) if sym_info2 and sym_info2.IsDefine and sym_info2.DefineValue is not None: define_constants = vars(Gen).setdefault('_define_constants', {}) if name not in define_constants: define_constants[name] = sym_info2.DefineValue break continue if not hasattr(self.Trans, '_t_c_imported_names'): self.Trans._t_c_imported_names = {} self.Trans._t_c_imported_names[asname] = (module, name) if name in Gen.functions: Gen.functions[asname] = Gen.functions[name] if name in Gen.structs: Gen.structs[asname] = Gen.structs[name] info = self.Trans.SymbolTable.lookup(name) if info: self.Trans.SymbolTable.insert(asname, info) if name in Gen.variables: Gen.variables[asname] = Gen.variables[name] if name in Gen.global_vars: Gen.global_vars[asname] = Gen.global_vars[name] for func_name in list(Gen.functions.keys()): if func_name.startswith(f'{name}.'): method_suffix = func_name[len(name):] Gen.functions[f'{asname}{method_suffix}'] = Gen.functions[func_name] for key in list(self.Trans.SymbolTable.keys()): if key.startswith(f'{name}.'): suffix = key[len(name):] self.Trans.SymbolTable.insert(f'{asname}{suffix}', self.Trans.SymbolTable[key]) for meta_dict in (Gen.class_members, Gen.class_member_defaults, Gen.class_member_signeds, Gen.class_member_bitfields, Gen.class_member_byteorders, Gen.class_member_bitoffsets, Gen.class_methods): if name in meta_dict: meta_dict[asname] = meta_dict[name] if name in Gen.class_vtable: Gen.class_vtable.add(asname) if name in Gen.class_packed: Gen.class_packed.add(asname) struct_sha1_map = getattr(Gen, '_struct_sha1_map', {}) if name in struct_sha1_map: struct_sha1_map[asname] = struct_sha1_map[name] def _EmitImportFromDeclarationsLlvm(self, Node: ast.ImportFrom, Gen: LlvmGeneratorMixin) -> None: module = Node.module if Node.module else '' if module in ('c', 't'): if not getattr(self.Trans, '_ImportedModules', None): self.Trans._ImportedModules = set() self.Trans._ImportedModules.add(module) if not hasattr(self.Trans, '_t_c_imported_names'): self.Trans._t_c_imported_names = {} # 注册 t/c 类型别名到 _t_type_symbols(CType 子类) t_type_syms = self.Trans.SymbolTable._t_type_symbols for alias in Node.names: name = alias.name asname = alias.asname or name self.Trans._t_c_imported_names[asname] = (module, name) # 仅注册 CType 子类(CEnum/CUnion/CStruct/REnum 等) if module == 't': cls = getattr(t, name, None) if isinstance(cls, type) and issubclass(cls, t.CType): t_type_syms[asname] = cls return if not getattr(self.Trans, '_ImportedModules', None): self.Trans._ImportedModules = set() current_module = self._get_current_module_name() if Node.level and Node.level > 0: current_file = getattr(self.Trans, 'CurrentFile', '') or '' current_dir = os.path.dirname(os.path.abspath(current_file)) parent_dir = current_dir for _ in range(Node.level - 1): parent_dir = os.path.dirname(parent_dir) if module: SearchExtensions = ['.pyi', '.py'] sub_path_base = os.path.join(parent_dir, module) for ext in SearchExtensions: candidate = sub_path_base + ext if os.path.isfile(candidate): self.Trans._ImportedModules.add(module) self._LoadModuleDeclarationsFromFile(candidate, Gen, module_name=module, register_module_name=current_module) self._RegisterFromImportAliases(Node, Gen, module) return ModuleSha1Map = getattr(Gen, 'ModuleSha1Map', {}) # 尝试完整模块名和短模块名 full_mod_name = f"{current_module}.{module}" if current_module else module target_sha1 = ModuleSha1Map.get(full_mod_name) or ModuleSha1Map.get(module) if target_sha1: temp_dir = getattr(Gen, '_temp_dir', None) if temp_dir: sha1_pyi = os.path.join(temp_dir, f"{target_sha1}.pyi") if os.path.isfile(sha1_pyi): self.Trans._ImportedModules.add(module) self._LoadModuleDeclarationsFromFile(sha1_pyi, Gen, module_name=module, register_module_name=current_module) self._RegisterFromImportAliases(Node, Gen, module) return else: mod_dir = parent_dir SearchExtensions = ['.pyi', '.py'] pkg_name = '' if not module: # When 'from . import name' is used, Load the package's __init__ # to make package-level names (functions, classes, constants) available pkg_name = self._LoadPackageInitForRelativeImport(mod_dir, Gen, current_module) # Also try to Load each alias as a submodule for alias in Node.names: found_sub = False for ext in SearchExtensions: sub_path = os.path.join(mod_dir, alias.name + ext) if os.path.isfile(sub_path): self.Trans._ImportedModules.add(alias.name) self._LoadModuleDeclarationsFromFile(sub_path, Gen, module_name=alias.name, register_module_name=current_module) if alias.asname: self.Trans.SymbolTable.import_aliases[alias.asname] = alias.name found_sub = True break if not found_sub: # SHA1 map fallback for submodule ModuleSha1Map = getattr(Gen, 'ModuleSha1Map', {}) sub_ModulePath = f"{pkg_name}.{alias.name}" if pkg_name else alias.name target_sha1 = ModuleSha1Map.get(sub_ModulePath) or ModuleSha1Map.get(alias.name) if target_sha1: temp_dir = getattr(Gen, '_temp_dir', None) if temp_dir: sha1_pyi = os.path.join(temp_dir, f"{target_sha1}.pyi") if os.path.isfile(sha1_pyi): self.Trans._ImportedModules.add(sub_ModulePath) self._LoadModuleDeclarationsFromFile(sha1_pyi, Gen, module_name=alias.name, register_module_name=current_module) if alias.asname: self.Trans.SymbolTable.import_aliases[alias.asname] = alias.name self._RegisterFromImportAliases(Node, Gen, module or pkg_name) return self.Trans._ImportedModules.add(module) self._EmitModuleDeclarationsLlvm(module, Gen, register_module_name=current_module) self._RegisterFromImportAliases(Node, Gen, module) def _LoadModuleDeclarationsFromFile(self, pyi_path: str, Gen: LlvmGeneratorMixin, module_name: str | None = None, register_module_name: str | None = None, actual_module_name: str | None = None, reexport_package: str | None = None) -> None: if not pyi_path or not os.path.isfile(pyi_path): return try: with open(pyi_path, 'r', encoding='utf-8') as f: ModuleCode = f.read() ModuleTree = ast.parse(ModuleCode) for node in ModuleTree.body: if isinstance(node, ast.FunctionDef): if node.name in ('va_start', 'va_arg', 'va_end'): continue file_module_name = os.path.splitext(os.path.basename(pyi_path))[0] effective_register = register_module_name or module_name reexport_names = [] if module_name and module_name != file_module_name and module_name != effective_register: reexport_names.append(module_name) # When Loading a submodule for 'from .xxx import yyy' inside a package's __init__.py, # re-export functions under the package name so 'pkg.func()' resolves correctly if reexport_package and reexport_package != file_module_name and reexport_package not in reexport_names: reexport_names.append(reexport_package) self._EmitExternalFuncDeclLlvm(node, Gen, source_module_name=file_module_name, register_module_name=effective_register, reexport_module_names=reexport_names if reexport_names else None) elif isinstance(node, ast.AnnAssign): self._EmitExternalGlobalDeclLlvm(node, Gen, module_name=module_name, ModulePath=pyi_path) elif isinstance(node, ast.Assign): self._EmitExternalGlobalDeclLlvm(node, Gen, module_name=module_name, ModulePath=pyi_path) elif isinstance(node, ast.ClassDef): if hasattr(node, 'type_params') and node.type_params: if not hasattr(self.Trans.ClassHandler, '_generic_class_templates'): self.Trans.ClassHandler._generic_class_templates = {} type_params = [tp.name for tp in node.type_params] self.Trans.ClassHandler._generic_class_templates[node.name] = { 'node': node, 'type_params': type_params, } self._EmitExternalClassDeclLlvm(node, Gen, module_name=module_name, actual_module_name=actual_module_name) elif isinstance(node, ast.ImportFrom): if node.level > 0: current_dir = os.path.dirname(os.path.abspath(pyi_path)) parent_dir = current_dir for _ in range(node.level - 1): parent_dir = os.path.dirname(parent_dir) if node.module: SearchExtensions = ['.pyi', '.py'] sub_path_base = os.path.join(parent_dir, node.module) found_sub = False for ext in SearchExtensions: candidate = sub_path_base + ext if os.path.isfile(candidate): actual_module = f"{register_module_name}.{node.module}" if register_module_name else node.module self._RegisterSubModuleSha1(candidate, actual_module, node.module, Gen) if not getattr(self.Trans, '_ImportedModules', None): self.Trans._ImportedModules = set() self.Trans._ImportedModules.add(actual_module) for alias in node.names: if alias.name == '*': self._LoadModuleDeclarationsFromFile(candidate, Gen, module_name=module_name, register_module_name=register_module_name, reexport_package=reexport_package or module_name) else: self._LoadModuleDeclarationsFromFile(candidate, Gen, module_name=alias.name, register_module_name=register_module_name, actual_module_name=actual_module, reexport_package=reexport_package or module_name) found_sub = True break # SHA1 map 回退 if not found_sub: actual_module = f"{register_module_name}.{node.module}" if register_module_name else node.module ModuleSha1Map = getattr(Gen, 'ModuleSha1Map', {}) target_sha1 = ModuleSha1Map.get(actual_module) or ModuleSha1Map.get(node.module) if target_sha1: temp_dir = getattr(Gen, '_temp_dir', None) if temp_dir: sha1_pyi = os.path.join(temp_dir, f"{target_sha1}.pyi") if os.path.isfile(sha1_pyi): if not getattr(self.Trans, '_ImportedModules', None): self.Trans._ImportedModules = set() self.Trans._ImportedModules.add(actual_module) for alias in node.names: if alias.name == '*': self._LoadModuleDeclarationsFromFile(sha1_pyi, Gen, module_name=module_name, register_module_name=register_module_name, reexport_package=reexport_package or module_name) else: self._LoadModuleDeclarationsFromFile(sha1_pyi, Gen, module_name=alias.name, register_module_name=register_module_name, actual_module_name=actual_module, reexport_package=reexport_package or module_name) else: mod_dir = parent_dir SearchExtensions = ['.pyi', '.py'] # Load __init__.py to make package-level names available self._LoadPackageInitForRelativeImport(mod_dir, Gen, register_module_name) for alias in node.names: found_alias = False for ext in SearchExtensions: sub_path = os.path.join(mod_dir, alias.name + ext) if os.path.isfile(sub_path): sub_ModulePath = f"{register_module_name}.{alias.name}" if register_module_name else alias.name if not getattr(self.Trans, '_ImportedModules', None): self.Trans._ImportedModules = set() self.Trans._ImportedModules.add(sub_ModulePath) if alias.name == '*': self._LoadModuleDeclarationsFromFile(sub_path, Gen, module_name=module_name, register_module_name=register_module_name, reexport_package=reexport_package or module_name) else: self._LoadModuleDeclarationsFromFile(sub_path, Gen, module_name=alias.name, register_module_name=register_module_name, reexport_package=reexport_package or module_name) found_alias = True break # SHA1 map 回退 if not found_alias: sub_ModulePath = f"{register_module_name}.{alias.name}" if register_module_name else alias.name ModuleSha1Map = getattr(Gen, 'ModuleSha1Map', {}) target_sha1 = ModuleSha1Map.get(sub_ModulePath) or ModuleSha1Map.get(alias.name) if target_sha1: temp_dir = getattr(Gen, '_temp_dir', None) if temp_dir: sha1_pyi = os.path.join(temp_dir, f"{target_sha1}.pyi") if os.path.isfile(sha1_pyi): if not getattr(self.Trans, '_ImportedModules', None): self.Trans._ImportedModules = set() self.Trans._ImportedModules.add(sub_ModulePath) if alias.name == '*': self._LoadModuleDeclarationsFromFile(sha1_pyi, Gen, module_name=module_name, register_module_name=register_module_name, reexport_package=reexport_package or module_name) else: self._LoadModuleDeclarationsFromFile(sha1_pyi, Gen, module_name=alias.name, register_module_name=register_module_name, reexport_package=reexport_package or module_name) except Exception as _e: if _config_mode == "strict": self.Trans.LogWarning(f"异常被忽略: {_e}") def _get_current_module_name(self) -> str | None: current_file = getattr(self.Trans, 'CurrentFile', '') or '' if current_file: return os.path.splitext(os.path.basename(current_file))[0] return None def _RegisterSubModuleSha1(self, candidate_path: str, actual_module: str, short_module: str, Gen: LlvmGeneratorMixin) -> None: """Register a submodule's SHA1 in ModuleSha1Map so cross-module class references work""" try: with open(candidate_path, 'r', encoding='utf-8') as f: sub_content = f.read() sub_sha1 = hashlib.sha1(sub_content.encode('utf-8')).hexdigest()[:16] if hasattr(Gen, 'ModuleSha1Map'): Gen.ModuleSha1Map[actual_module] = sub_sha1 if short_module and short_module not in Gen.ModuleSha1Map: Gen.ModuleSha1Map[short_module] = sub_sha1 except Exception as _e: if _config_mode == "strict": raise _vlog().warning(f"处理导入失败: {_e}", "Exception") def _LoadPackageInitForRelativeImport(self, mod_dir: str, Gen: LlvmGeneratorMixin, register_module_name: str | None) -> str: """Load __init__.py from the package directory for 'from . import name' resolution. This makes package-level names (functions, classes, constants) available. Returns the package name.""" pkg_name = os.path.basename(mod_dir) init_Loaded = False # 跳过当前正在编译的文件,避免重复加载导致 class_members 重复 current_sha1 = getattr(Gen, 'module_sha1', None) ModuleSha1Map = getattr(Gen, 'ModuleSha1Map', {}) pkg_sha1 = ModuleSha1Map.get(pkg_name) if pkg_sha1 and pkg_sha1 == current_sha1: return pkg_name # Try SHA1 map first (correct mangled names from stub) if pkg_sha1: temp_dir = getattr(Gen, '_temp_dir', None) if temp_dir: sha1_pyi = os.path.join(temp_dir, f"{pkg_sha1}.pyi") if os.path.isfile(sha1_pyi): self._LoadModuleDeclarationsFromFile(sha1_pyi, Gen, module_name=pkg_name, register_module_name=register_module_name) init_Loaded = True # Fallback: try Loading __init__.py directly from the package directory if not init_Loaded: for ext in ['.pyi', '.py']: init_path = os.path.join(mod_dir, '__init__' + ext) if os.path.isfile(init_path): self._LoadModuleDeclarationsFromFile(init_path, Gen, module_name=pkg_name, register_module_name=register_module_name) init_Loaded = True break return pkg_name def _EmitModuleDeclarationsLlvm(self, module_name: str, Gen: LlvmGeneratorMixin, register_module_name: str | None = None) -> None: if module_name in ('pyzlib',): source_sig_files = getattr(self.Trans, '_source_module_sig_files', None) ModulePath = module_name.replace('.', os.sep) + '.pyi' PackageInitPath = module_name.replace('.', os.sep) + os.sep + '__init__.pyi' ProjectRoot = os.path.dirname(os.path.abspath(getattr(self.Trans, 'CurrentFile', '') or '')) or os.getcwd() HandlesFile = os.path.abspath(inspect.getfile(self.__class__)) TransPyCRoot = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(HandlesFile)))) FullModulePath = None SearchDirs = [os.path.join(TransPyCRoot, 'includes'), os.path.join(TransPyCRoot, 'lib', 'includes')] + list(self.Trans.LibraryPaths) for LibPath in SearchDirs: SearchPath = os.path.join(ProjectRoot, LibPath) if not os.path.isabs(LibPath) else LibPath CandidatePath = os.path.join(SearchPath, ModulePath) if os.path.isfile(CandidatePath): FullModulePath = CandidatePath break CandidatePath = os.path.join(SearchPath, PackageInitPath) if os.path.isfile(CandidatePath): FullModulePath = CandidatePath break ModuleParts = ModulePath.split(os.sep) if len(ModuleParts) > 1: LibPathBasename = os.path.basename(LibPath.rstrip('/\\')) if LibPathBasename == ModuleParts[0]: RemainingPath = os.sep.join(ModuleParts[1:]) CandidatePath = os.path.join(SearchPath, RemainingPath) if os.path.isfile(CandidatePath): FullModulePath = CandidatePath break if not FullModulePath: ModulePathPy = module_name.replace('.', os.sep) + '.py' PackageInitPathPy = module_name.replace('.', os.sep) + os.sep + '__init__.py' SearchDirsForPy = [os.path.join(TransPyCRoot, 'includes'), os.path.join(TransPyCRoot, 'lib', 'includes')] + list(self.Trans.LibraryPaths) for LibPath in SearchDirsForPy: SearchPath = os.path.join(ProjectRoot, LibPath) if not os.path.isabs(LibPath) else LibPath CandidatePath = os.path.join(SearchPath, ModulePathPy) if os.path.isfile(CandidatePath): FullModulePath = CandidatePath break CandidatePath = os.path.join(SearchPath, PackageInitPathPy) if os.path.isfile(CandidatePath): FullModulePath = CandidatePath break if not FullModulePath or not os.path.isfile(FullModulePath): source_sig_files = getattr(self.Trans, '_source_module_sig_files', None) if source_sig_files: sig_path = source_sig_files.get(module_name) if sig_path and os.path.isfile(sig_path): FullModulePath = sig_path else: for key, val in source_sig_files.items(): if key.endswith('.' + module_name) or key == module_name: FullModulePath = val break if not FullModulePath or not os.path.isfile(FullModulePath): if module_name in ('pyzlib', 'zdeflate', 'zinflate', 'zchecksum', 'zdef', 'zhuff'): pass if not FullModulePath: CurrentFile = getattr(self.Trans, 'CurrentFile', None) or '' if CurrentFile: CurrentFileDir = os.path.dirname(os.path.abspath(CurrentFile)) SearchFromDirs = [CurrentFileDir, os.getcwd()] else: SearchFromDirs = [os.getcwd()] SearchDirsForSubmodule = SearchFromDirs + [os.path.join(TransPyCRoot, 'includes'), os.path.join(TransPyCRoot, 'lib', 'includes')] + list(self.Trans.LibraryPaths) for SearchPath in SearchDirsForSubmodule: if os.path.isdir(SearchPath): for root, dirs, files in os.walk(SearchPath): if '__pycache__' in root: continue if module_name + '.py' in files: FullModulePath = os.path.join(root, module_name + '.py') break if module_name + '.pyi' in files: FullModulePath = os.path.join(root, module_name + '.pyi') break if FullModulePath: break self._LoadDeclarationsFromStubLlvm(module_name, Gen) if module_name == 'pyzlib': pass try: with open(FullModulePath, 'r', encoding='utf-8') as f: ModuleCode = f.read() ModuleTree = ast.parse(ModuleCode) if module_name == 'pyzlib': func_names = [n.name for n in ModuleTree.body if isinstance(n, ast.FunctionDef)] has_functions_or_classes = False for node in ModuleTree.body: if module_name == 'pyzlib': node_type = type(node).__name__ name = getattr(node, 'name', getattr(node, 'attr', '?')) try: if isinstance(node, ast.FunctionDef): if node.name in ('va_start', 'va_arg', 'va_end'): continue effective_register = register_module_name or module_name self._EmitExternalFuncDeclLlvm(node, Gen, source_module_name=module_name, register_module_name=effective_register) has_functions_or_classes = True elif isinstance(node, ast.AnnAssign): self._EmitExternalGlobalDeclLlvm(node, Gen, module_name=module_name, ModulePath=FullModulePath) has_functions_or_classes = True elif isinstance(node, ast.Assign): self._EmitExternalGlobalDeclLlvm(node, Gen, module_name=module_name, ModulePath=FullModulePath) has_functions_or_classes = True elif isinstance(node, ast.ClassDef): if hasattr(node, 'type_params') and node.type_params: if not hasattr(self.Trans.ClassHandler, '_generic_class_templates'): self.Trans.ClassHandler._generic_class_templates = {} type_params = [tp.name for tp in node.type_params] self.Trans.ClassHandler._generic_class_templates[node.name] = { 'node': node, 'type_params': type_params, } self._EmitExternalClassDeclLlvm(node, Gen, module_name=module_name) has_functions_or_classes = True elif isinstance(node, ast.ImportFrom): if node.level > 0: current_dir = os.path.dirname(os.path.abspath(FullModulePath)) parent_dir = current_dir for _ in range(node.level - 1): parent_dir = os.path.dirname(parent_dir) if node.module: SearchExtensions = ['.pyi', '.py'] sub_path_base = os.path.join(parent_dir, node.module) found_sub = False for ext in SearchExtensions: candidate = sub_path_base + ext if os.path.isfile(candidate): actual_module = f"{module_name}.{node.module}" if module_name else node.module self._RegisterSubModuleSha1(candidate, actual_module, node.module, Gen) if not getattr(self.Trans, '_ImportedModules', None): self.Trans._ImportedModules = set() self.Trans._ImportedModules.add(actual_module) for alias in node.names: if alias.name == '*': self._LoadModuleDeclarationsFromFile(candidate, Gen, module_name=module_name, register_module_name=register_module_name, reexport_package=module_name) else: self._LoadModuleDeclarationsFromFile(candidate, Gen, module_name=alias.name, register_module_name=register_module_name, actual_module_name=actual_module, reexport_package=module_name) has_functions_or_classes = True found_sub = True break # SHA1 map 回退:当文件查找失败时,从 SHA1 映射中查找子模块 stub if not found_sub: actual_module = f"{module_name}.{node.module}" if module_name else node.module ModuleSha1Map = getattr(Gen, 'ModuleSha1Map', {}) # 尝试完整模块名和短模块名 target_sha1 = ModuleSha1Map.get(actual_module) or ModuleSha1Map.get(node.module) if target_sha1: temp_dir = getattr(Gen, '_temp_dir', None) if temp_dir: sha1_pyi = os.path.join(temp_dir, f"{target_sha1}.pyi") if os.path.isfile(sha1_pyi): if not getattr(self.Trans, '_ImportedModules', None): self.Trans._ImportedModules = set() self.Trans._ImportedModules.add(actual_module) for alias in node.names: if alias.name == '*': self._LoadModuleDeclarationsFromFile(sha1_pyi, Gen, module_name=module_name, register_module_name=register_module_name, reexport_package=module_name) else: self._LoadModuleDeclarationsFromFile(sha1_pyi, Gen, module_name=alias.name, register_module_name=register_module_name, actual_module_name=actual_module, reexport_package=module_name) has_functions_or_classes = True else: mod_dir = parent_dir SearchExtensions = ['.pyi', '.py'] # Load __init__.py to make package-level names available self._LoadPackageInitForRelativeImport(mod_dir, Gen, register_module_name or module_name) for alias in node.names: found_alias = False for ext in SearchExtensions: sub_path = os.path.join(mod_dir, alias.name + ext) if os.path.isfile(sub_path): sub_ModulePath = f"{module_name}.{alias.name}" if module_name else alias.name if not getattr(self.Trans, '_ImportedModules', None): self.Trans._ImportedModules = set() self.Trans._ImportedModules.add(sub_ModulePath) if alias.name == '*': self._LoadModuleDeclarationsFromFile(sub_path, Gen, module_name=module_name, register_module_name=register_module_name, reexport_package=module_name) else: self._LoadModuleDeclarationsFromFile(sub_path, Gen, module_name=alias.name, register_module_name=register_module_name, reexport_package=module_name) has_functions_or_classes = True found_alias = True break # SHA1 map 回退 if not found_alias: sub_ModulePath = f"{module_name}.{alias.name}" if module_name else alias.name ModuleSha1Map = getattr(Gen, 'ModuleSha1Map', {}) target_sha1 = ModuleSha1Map.get(sub_ModulePath) or ModuleSha1Map.get(alias.name) if target_sha1: temp_dir = getattr(Gen, '_temp_dir', None) if temp_dir: sha1_pyi = os.path.join(temp_dir, f"{target_sha1}.pyi") if os.path.isfile(sha1_pyi): if not getattr(self.Trans, '_ImportedModules', None): self.Trans._ImportedModules = set() self.Trans._ImportedModules.add(sub_ModulePath) if alias.name == '*': self._LoadModuleDeclarationsFromFile(sha1_pyi, Gen, module_name=module_name, register_module_name=register_module_name, reexport_package=module_name) else: self._LoadModuleDeclarationsFromFile(sha1_pyi, Gen, module_name=alias.name, register_module_name=register_module_name, reexport_package=module_name) has_functions_or_classes = True except Exception as e: if module_name == 'pyzlib': node_type = type(node).__name__ name = getattr(node, 'name', getattr(node, 'attr', '?')) _vlog().warning(f"HandlesImports: 忽略异常 {e}", exc_info=e) continue if not has_functions_or_classes and module_name: self._LoadDeclarationsFromStubLlvm(module_name, Gen) elif 'includes' in FullModulePath and module_name: self._LoadDeclarationsFromStubLlvm(module_name, Gen) except Exception as e: if module_name: self._LoadDeclarationsFromStubLlvm(module_name, Gen) def _EmitExternalGlobalDeclLlvm(self, Node: ast.AnnAssign | ast.Assign, Gen: LlvmGeneratorMixin, module_name: str | None = None, ModulePath: str | None = None) -> None: """Emit external global variable declaration from imported module""" if isinstance(Node, ast.AnnAssign) and isinstance(Node.target, ast.Name): VarName = Node.target.id # 不再跳过 _ 开头的全局变量,跨模块引用需要声明它们 var_type = ir.IntType(32) IsPtr = False is_string_val = False TypeInfo = None if VarName in Gen.module.globals: existing = Gen.module.globals[VarName] if isinstance(existing, ir.GlobalVariable): return if Node.annotation: parse_result = ParseListAnnotation(Node.annotation) if parse_result and not parse_result.is_pointer: # list[type, count] 数组声明 elem_type_node = parse_result.elem_type_node count_node = parse_result.count_node ElemTypeInfo = CTypeInfo.FromNode(elem_type_node, self.Trans.SymbolTable) if ElemTypeInfo: elem_type = Gen._ctype_to_llvm(ElemTypeInfo) if isinstance(elem_type, ir.VoidType): elem_type = ir.IntType(8) if isinstance(count_node, ast.Constant) and isinstance(count_node.value, int): var_type = ir.ArrayType(elem_type, count_node.value) elif isinstance(count_node, ast.Constant) and count_node.value is None: var_type = ir.ArrayType(elem_type, 0) else: var_type = Gen._ctype_to_llvm(ElemTypeInfo) IsPtr = False else: TypeInfo = CTypeInfo.FromNode(Node.annotation, self.Trans.SymbolTable) if TypeInfo: var_type = Gen._ctype_to_llvm(TypeInfo) IsPtr = TypeInfo.IsPtr if isinstance(var_type, ir.IdentifiedStructType) and (var_type.elements is None or len(var_type.elements) == 0): var_type = ir.IntType(32) if Node.value and ModulePath and IsPtr and isinstance(Node.value, ast.Constant) and isinstance(Node.value.value, str): str_val = Node.value.value + '\x00' str_bytes = str_val.encode('utf-8') arr_type = ir.ArrayType(ir.IntType(8), len(str_bytes)) str_gv_name = VarName + '_str' if str_gv_name not in Gen.module.globals: str_gv = ir.GlobalVariable(Gen.module, arr_type, name=str_gv_name) str_gv.initializer = ir.Constant(arr_type, bytearray(str_bytes)) str_gv.linkage = 'internal' ptr_type = ir.PointerType(ir.IntType(8)) if VarName not in Gen.module.globals: ptr_gv = ir.GlobalVariable(Gen.module, ptr_type, name=VarName) str_gv = Gen.module.globals[str_gv_name] ptr_gv.initializer = ir.Constant.gep(str_gv, [ir.Constant(ir.IntType(32), 0), ir.Constant(ir.IntType(32), 0)]) ptr_gv.linkage = 'internal' Gen.variables[VarName] = None return # 检查是否是 CDefine 常量或 CTypedef 类型别名 IsCDefine = False IsCTypedef = False if Node.annotation: if isinstance(Node.annotation, ast.Attribute): if getattr(Node.annotation.value, 'id', None) == 't' and Node.annotation.attr == 'CDefine': IsCDefine = True elif getattr(Node.annotation.value, 'id', None) == 't' and Node.annotation.attr == 'CTypedef': IsCTypedef = True elif isinstance(Node.annotation, ast.Name): if Node.annotation.id == 'CDefine': IsCDefine = True elif Node.annotation.id == 'CTypedef': IsCTypedef = True if IsCTypedef: # 检查符号是否已存在且已正确解析(OriginalType 已设置),避免覆盖 existing_sym = self.Trans.SymbolTable.lookup(VarName) if hasattr(self.Trans.SymbolTable, 'lookup') else None if existing_sym and isinstance(existing_sym, CTypeInfo) and existing_sym.IsTypedef and existing_sym.OriginalType: return if Node.value: ValueTypeInfo = None if isinstance(Node.value, ast.BinOp) and isinstance(Node.value.op, ast.BitOr): ValueTypeInfo = self.Trans.TypeMergeHandler.MergeTypes(Node.value) else: ValueTypeInfo = self.Trans.TypeMergeHandler.GetCTypeInfo(Node.value) if isinstance(ValueTypeInfo, CTypeInfo) and (ValueTypeInfo.BaseType or ValueTypeInfo.PtrCount > 0): ValueTypeInfo.IsTypedef = True ValueTypeInfo.Name = VarName self.Trans.SymbolTable.insert(VarName, ValueTypeInfo) if module_name: FullName = f"{module_name}.{VarName}" self.Trans.SymbolTable.insert(FullName, ValueTypeInfo) return TTypeInfo = self.Trans.TypeMergeHandler.GetCTypeInfo(Node.annotation) if TTypeInfo: TTypeInfo.IsTypedef = True TTypeInfo.Name = VarName self.Trans.SymbolTable.insert(VarName, TTypeInfo) if module_name: FullName = f"{module_name}.{VarName}" self.Trans.SymbolTable.insert(FullName, TTypeInfo) return if IsCDefine: # CDefine 宏不生成 LLVM 全局变量,只注册到 SymbolTable # 引用时会直接替换为立即数 DefineValue = None if Node.value: DefineValue = self._ExtractConstValue(Node.value) # 注册不带前缀的键名(例如 Z_NO_COMPRESSION) self._RegisterCDefineSymbol(VarName, DefineValue, Node.lineno, ModulePath or 'unknown') # 如果提供了模块名,也注册带模块前缀的键名 if module_name: FullName = f"{module_name}.{VarName}" self._RegisterCDefineSymbol(FullName, DefineValue, Node.lineno, ModulePath or 'unknown') return # 不再继续生成 LLVM 全局变量 # t.State 标记的全局变量:只声明不定义(external linkage),避免 DSO/non-DSO 重定义 if TypeInfo and TypeInfo.IsState: if VarName not in Gen.module.globals: gv = ir.GlobalVariable(Gen.module, var_type, name=VarName) gv.linkage = 'external' Gen._export_funcs.add(VarName) return gv = ir.GlobalVariable(Gen.module, var_type, name=VarName) # 外部全局变量声明:只声明不定义(external linkage),避免多重定义 gv.linkage = 'external' elif isinstance(node, ast.Assign) and Node.targets and isinstance(Node.targets[0], ast.Name): VarName = Node.targets[0].id if VarName.startswith('_'): return if VarName in Gen.module.globals: return gv = ir.GlobalVariable(Gen.module, ir.IntType(32), name=VarName) gv.linkage = 'external' def _ExtractValue(self, value_node: ast.AST, target_type: ir.Type, var_name: str | None = None) -> ir.Constant | None: """Extract constant value from AST node for global variable initializer""" # 处理负号 if isinstance(value_node, ast.UnaryOp) and isinstance(value_node.op, ast.USub): if isinstance(value_node.operand, ast.Constant) and isinstance(value_node.operand.value, int): neg_val = -value_node.operand.value if isinstance(target_type, ir.IntType): return ir.Constant(target_type, neg_val) return ir.Constant(ir.IntType(32), neg_val) # 处理正号 elif isinstance(value_node, ast.UnaryOp) and isinstance(value_node.op, ast.UAdd): return self._ExtractValue(value_node.operand, target_type) if isinstance(value_node, ast.Constant): if isinstance(value_node.value, bool): if isinstance(target_type, ir.IntType): return ir.Constant(target_type, 1 if value_node.value else 0) return ir.Constant(ir.IntType(32), 1 if value_node.value else 0) elif isinstance(value_node.value, int): if isinstance(target_type, ir.IntType): return ir.Constant(target_type, value_node.value) return ir.Constant(ir.IntType(32), value_node.value) elif isinstance(value_node.value, str): str_val = value_node.value + '\x00' str_bytes = str_val.encode('utf-8') arr_type = ir.ArrayType(ir.IntType(8), len(str_bytes)) Gen = self.Trans.LlvmGen gv_name = var_name + '_str' if var_name else f"str_global_{id(value_node)}" if gv_name in Gen.module.globals: gv = Gen.module.globals[gv_name] else: gv = ir.GlobalVariable(Gen.module, arr_type, name=gv_name) gv.initializer = ir.Constant(arr_type, bytearray(str_bytes)) gv.linkage = 'internal' ptr_type = ir.PointerType(ir.IntType(8)) return ir.Constant(ptr_type, gv.reference) elif isinstance(value_node.value, bool): return ir.Constant(ir.IntType(32), 1 if value_node.value else 0) elif value_node.value is None: if isinstance(target_type, ir.BaseStructType): return ir.Constant(target_type, None) if isinstance(target_type, ir.PointerType): return ir.Constant(target_type, None) return ir.Constant(target_type, 0) elif isinstance(value_node, ast.Name): if value_node.id == 'None': if isinstance(target_type, ir.BaseStructType): return ir.Constant(target_type, None) if isinstance(target_type, ir.PointerType): return ir.Constant(target_type, None) return ir.Constant(target_type, 0) return None def _ExtractConstValue(self, value_node: ast.AST) -> object: """Extract constant value from AST node for SymbolTable registration 支持常量、一元运算符、二元运算符和常量引用 """ if isinstance(value_node, ast.Constant): return value_node.value elif isinstance(value_node, ast.BinOp): left = self._ExtractConstValue(value_node.left) right = self._ExtractConstValue(value_node.right) if left is None or right is None: return None if isinstance(value_node.op, ast.Add): return left + right elif isinstance(value_node.op, ast.Sub): return left - right elif isinstance(value_node.op, ast.Mult): return left * right elif isinstance(value_node.op, ast.Div): return left // right if isinstance(left, int) and isinstance(right, int) else left / right elif isinstance(value_node.op, ast.FloorDiv): return left // right elif isinstance(value_node.op, ast.Mod): return left % right elif isinstance(value_node.op, ast.Pow): return left ** right elif isinstance(value_node.op, ast.LShift): return left << right elif isinstance(value_node.op, ast.RShift): return left >> right elif isinstance(value_node.op, ast.BitOr): return left | right elif isinstance(value_node.op, ast.BitXor): return left ^ right elif isinstance(value_node.op, ast.BitAnd): return left & right elif isinstance(value_node, ast.UnaryOp): operand = self._ExtractConstValue(value_node.operand) if operand is None: return None if isinstance(value_node.op, ast.USub): return -operand elif isinstance(value_node.op, ast.UAdd): return +operand elif isinstance(value_node.op, ast.Invert): return ~operand elif isinstance(value_node, ast.Name): if value_node.id in getattr(self.Trans, 'SymbolTable', {}): info = self.Trans.SymbolTable[value_node.id] if info.IsDefine and info.DefineValue is not None: return info.DefineValue elif isinstance(value_node, ast.Call): if isinstance(value_node.func, ast.Attribute): if getattr(value_node.func.value, 'id', None) == 't': if value_node.args: return self._ExtractConstValue(value_node.args[0]) elif isinstance(value_node.func, ast.Name): if value_node.args: return self._ExtractConstValue(value_node.args[0]) return None def _ExtractTypedefOriginalType(self, value_node: ast.AST) -> str | None: if isinstance(value_node, ast.Attribute): if getattr(value_node.value, 'id', None) == 't': llvm_str = CTypeRegistry.NameToLLVM(value_node.attr) if llvm_str: return llvm_str elif isinstance(value_node, ast.Name): llvm_str = CTypeRegistry.NameToLLVM(value_node.id) if llvm_str: return llvm_str elif isinstance(value_node, ast.BinOp) and isinstance(value_node.op, ast.BitOr): left = self._ExtractTypedefOriginalType(value_node.left) right = self._ExtractTypedefOriginalType(value_node.right) parts = [] if left: parts.append(left) if right: parts.append(right) if parts: return '|'.join(parts) elif isinstance(value_node, ast.Call): if isinstance(value_node.func, ast.Attribute): if getattr(value_node.func.value, 'id', None) == 't': llvm_str = CTypeRegistry.NameToLLVM(value_node.func.attr) if llvm_str: return llvm_str elif isinstance(value_node.func, ast.Name): llvm_str = CTypeRegistry.NameToLLVM(value_node.func.id) if llvm_str: return llvm_str return None def _RegisterCDefineSymbol(self, FullName: str, DefineValue: object, lineno: int, FilePath: str) -> None: """Register CDefine constant to SymbolTable""" info = CTypeInfo() info.IsDefine = True info.DefineValue = DefineValue info.Lineno = lineno info.file = FilePath # 直接添加到 SymbolTable 字典 self.Trans.SymbolTable.insert(FullName, info) def _check_annotation_for_state(self, annotation: ast.AST) -> bool: return (AnnotationContainsName(annotation, 'CExport') or AnnotationContainsName(annotation, 'CExtern') or AnnotationContainsName(annotation, 'State')) def _EmitExternalFuncDeclLlvm(self, Node: ast.FunctionDef, Gen: LlvmGeneratorMixin, is_class_method: bool = False, source_module_name: str | None = None, register_module_name: str | None = None, reexport_module_names: list[str] | None = None) -> None: FuncName = Node.name if Node.returns and self._check_annotation_for_state(Node.returns): Gen._export_funcs.add(FuncName) # t.State 标记的外部 C 函数必须保持原始名称,以便链接器解析 if FuncName in Gen.functions: return CReturnTypes = [] if Node.decorator_list: for decorator in Node.decorator_list: if isinstance(decorator, ast.Call) and isinstance(decorator.func, ast.Attribute): if decorator.func.attr == 'CReturn': for arg in decorator.args: CReturnTypes.append(arg) IsPtr = False ReturnTypeInfo = None if Node.returns: try: ReturnTypeInfo = self.ResolveAnnotationType(Node.returns) if ReturnTypeInfo and ReturnTypeInfo.IsStr: ReturnTypeInfo = CTypeInfo() ReturnTypeInfo.BaseType = t.CChar() ReturnTypeInfo.PtrCount = 1 if ReturnTypeInfo is None: ReturnTypeInfo = CTypeInfo() ReturnTypeInfo.BaseType = t.CInt() IsPtr = ReturnTypeInfo.IsPtr except Exception as e: # 回退:返回类型解析失败时使用默认 CInt _vlog().warning(f"HandlesImports: 忽略异常 {e}", exc_info=e) ReturnTypeInfo = CTypeInfo() ReturnTypeInfo.BaseType = t.CInt() else: ReturnTypeInfo = CTypeInfo() ReturnTypeInfo.BaseType = t.CInt() ReturnType = Gen._ctype_to_llvm(ReturnTypeInfo) if (isinstance(ReturnType, ir.VoidType) or (isinstance(ReturnType, ir.PointerType) and isinstance(ReturnType.pointee, ir.VoidType))) and Node.returns: RetAnnName = None if isinstance(Node.returns, ast.Name): RetAnnName = Node.returns.id elif isinstance(Node.returns, ast.Attribute): RetAnnName = Node.returns.attr elif isinstance(Node.returns, ast.BinOp) and isinstance(Node.returns.op, ast.BitOr): left = Node.returns.left if isinstance(left, ast.Name): RetAnnName = left.id elif isinstance(left, ast.Attribute): RetAnnName = left.attr if RetAnnName and RetAnnName in Gen.structs: ReturnType = ir.PointerType(Gen.structs[RetAnnName]) elif IsPtr or RetAnnName: ReturnType = ir.PointerType(ir.IntType(8)) ParamTypes = [] is_method = is_class_method or '.__' in FuncName class_name_for_method = FuncName.split('.')[0] if '.' in FuncName else None class_is_cpython = class_name_for_method and self.Trans.SymbolTable.is_struct(class_name_for_method) for i, Arg in enumerate(Node.args.args): if i == 0 and is_method: # self parameter of a method should always be a pointer to the struct ParamType = ir.PointerType(Gen.structs.get(class_name_for_method or FuncName.split('.')[0], ir.IntType(8))) elif Arg.annotation: try: ParamTypeInfo = self.ResolveAnnotationType(Arg.annotation) if ParamTypeInfo is None: ParamTypeInfo = CTypeInfo() ParamTypeInfo.BaseType = t.CInt() ParamIsPtr = ParamTypeInfo.IsPtr if i == 0 and is_method and Arg.arg == 'self' and class_is_cpython: ParamIsPtr = True ParamTypeInfo.PtrCount = max(ParamTypeInfo.PtrCount, 1) if ParamTypeInfo.IsStr: ParamTypeInfo = CTypeInfo() ParamTypeInfo.BaseType = t.CChar() ParamTypeInfo.PtrCount = 1 ParamIsPtr = True # C 语言中数组参数退化为指针:list[type, N] → type* if ParamTypeInfo.ArrayDims: ParamTypeInfo.ArrayDims = [] ParamTypeInfo.PtrCount = max(ParamTypeInfo.PtrCount, 1) ParamType = Gen._ctype_to_llvm(ParamTypeInfo) except Exception as e: # 回退:参数类型解析失败时使用默认 i32 _vlog().warning(f"HandlesImports: 忽略异常 {e}", exc_info=e) ParamType = ir.IntType(32) if isinstance(ParamType, ir.VoidType) or (isinstance(ParamType, ir.PointerType) and isinstance(ParamType.pointee, ir.VoidType)): AnnName = None if isinstance(Arg.annotation, ast.Name): AnnName = Arg.annotation.id elif isinstance(Arg.annotation, ast.Attribute): AnnName = Arg.annotation.attr elif isinstance(Arg.annotation, ast.BinOp) and isinstance(Arg.annotation.op, ast.BitOr): AnnName = ExtractTypeNameFromBinOp(Arg.annotation) if AnnName and AnnName in Gen.structs: ParamType = ir.PointerType(Gen.structs[AnnName]) else: ParamType = ir.PointerType(ir.IntType(8)) else: ParamType = ir.IntType(32) ParamTypes.append(ParamType) if CReturnTypes: for _ in CReturnTypes: ParamTypes.append(ir.PointerType(ir.IntType(8))) IsVariadic = Node.args.vararg is not None FuncType = ir.FunctionType(ReturnType, ParamTypes, var_arg=IsVariadic) _is_export = False if isinstance(ReturnTypeInfo, CTypeInfo) and ReturnTypeInfo.IsState: _is_export = True if isinstance(ReturnTypeInfo, CTypeInfo) and ReturnTypeInfo.Storage and isinstance(ReturnTypeInfo.Storage, t.CExport) and not ReturnTypeInfo.IsState: Gen._export_funcs.add(FuncName) _is_export = True if isinstance(ReturnTypeInfo, CTypeInfo) and ReturnTypeInfo.Storage and isinstance(ReturnTypeInfo.Storage, t.CExtern): Gen._export_funcs.add(FuncName) _is_export = True if not _is_export and Node.returns: _is_export = self._check_annotation_for_state(Node.returns) MangledName = Gen._mangle_func_name(FuncName, module_name=source_module_name) func = ir.Function(Gen.module, FuncType, name=MangledName) Gen.functions[MangledName] = func Gen.functions[FuncName] = func func_meta = FuncMeta.NONE if Node.decorator_list: for d in Node.decorator_list: if isinstance(d, ast.Name): if d.id == 'staticmethod': func_meta |= FuncMeta.STATIC_METHOD elif d.id == 'property': func_meta |= FuncMeta.PROPERTY_GETTER elif d.id == 'classmethod': func_meta |= FuncMeta.CLASS_METHOD elif isinstance(d, ast.Attribute): if isinstance(d.value, ast.Name): # 支持 @property.setter 和 @propname.setter 两种形式 if d.value.id == 'property' or d.attr in ('setter', 'getter', 'deleter'): if d.attr == 'setter': func_meta |= FuncMeta.PROPERTY_SETTER elif d.attr == 'getter': func_meta |= FuncMeta.PROPERTY_GETTER elif d.attr == 'deleter': func_meta |= FuncMeta.PROPERTY_DELETER if not self.Trans.SymbolTable.has(FuncName): FuncInfo = CTypeInfo() FuncInfo.Name = FuncName FuncInfo.IsFunction = True FuncInfo.MetaList = func_meta self.Trans.SymbolTable.insert(FuncName, FuncInfo) else: existing = self.Trans.SymbolTable[FuncName] if existing.MetaList == FuncMeta.NONE and func_meta != FuncMeta.NONE: existing.MetaList = func_meta # 同时注册带模块前缀的符号,以便 module.func_name 查找能命中 if source_module_name and source_module_name not in ('c', 't'): FullSymKey = f"{source_module_name}.{FuncName}" if not self.Trans.SymbolTable.has(FullSymKey): FullFuncInfo = CTypeInfo() FullFuncInfo.Name = FullSymKey FullFuncInfo.IsFunction = True FullFuncInfo.MetaList = func_meta self.Trans.SymbolTable.insert(FullSymKey, FullFuncInfo) # property setter/deleter: 在原始 PropKey(不带后缀)下注册 MetaList if FuncMeta.PROPERTY_SETTER in func_meta or FuncMeta.PROPERTY_DELETER in func_meta: BasePropKey = FuncName.replace('$set', '').replace('$del', '') base_existing = self.Trans.SymbolTable.lookup(BasePropKey) if base_existing: if func_meta != FuncMeta.NONE: base_existing.MetaList = base_existing.MetaList | func_meta else: PropInfo = CTypeInfo() PropInfo.Name = BasePropKey PropInfo.IsFunction = True PropInfo.MetaList = func_meta self.Trans.SymbolTable.insert(BasePropKey, PropInfo) if register_module_name and register_module_name != source_module_name: ReexportName = Gen._mangle_func_name(FuncName, module_name=register_module_name) Gen.functions[ReexportName] = func if reexport_module_names: for reexport_mod in reexport_module_names: if reexport_mod and reexport_mod != source_module_name: ReexportName2 = Gen._mangle_func_name(FuncName, module_name=reexport_mod) if ReexportName2 not in Gen.functions: Gen.functions[ReexportName2] = func def _EmitExternalClassDeclLlvm(self, Node: ast.ClassDef, Gen: LlvmGeneratorMixin, module_name: str | None = None, actual_module_name: str | None = None) -> None: ClassName = Node.name if hasattr(Node, 'type_params') and Node.type_params: return IsCenum = False IsCpythonObject = False IsCVTable = False if Node.bases: for base in Node.bases: if getattr(base, 'attr', None): if base.attr == 'CEnum' or base.attr == 'Enum' or base.attr == 'REnum': IsCenum = True break elif getattr(base, 'id', None): if base.id == 'CEnum' or base.id == 'Enum' or base.id == 'REnum': IsCenum = True break if not IsCenum and getattr(Node, 'decorator_list', None): for decorator in Node.decorator_list: if isinstance(decorator, ast.Attribute): if getattr(decorator.value, 'id', None) == 't': if decorator.attr == 'Object': IsCpythonObject = True elif decorator.attr == 'CVTable': IsCVTable = True elif isinstance(decorator, ast.Call): if isinstance(decorator.func, ast.Attribute): if getattr(decorator.func.value, 'id', None) == 't': if decorator.func.attr == 'Object': IsCpythonObject = True elif decorator.func.attr == 'CVTable': IsCVTable = True elif isinstance(decorator.func, ast.Name): if decorator.func.id == 'Object': IsCpythonObject = True elif decorator.func.id == 'CVTable': IsCVTable = True if ClassName.startswith('__') and not IsCpythonObject and not IsCenum: return IsPacked = False if getattr(Node, 'decorator_list', None): for decorator in Node.decorator_list: if isinstance(decorator, ast.Call): if isinstance(decorator.func, ast.Attribute): if getattr(decorator.func.value, 'id', None) == 'c' and decorator.func.attr == 'Attribute': for arg in decorator.args: if isinstance(arg, ast.Attribute): if isinstance(arg.value, ast.Attribute): if getattr(arg.value.value, 'id', None) == 't' and arg.value.attr == 'attr' and arg.attr == 'packed': IsPacked = True if IsPacked: Gen.class_packed.add(ClassName) if IsCpythonObject: CInfo = CTypeInfo() CInfo.Name = ClassName CInfo.IsCpythonObject = True CInfo.IsStruct = True if module_name: FullName = f"{module_name}.{ClassName}" self.Trans.SymbolTable.insert(FullName, CInfo) self.Trans.SymbolTable.insert(ClassName, CInfo) if IsCenum: EnumTypeNode = CTypeInfo() EnumTypeNode.Name = ClassName EnumTypeNode.BaseType = t.CEnum(ClassName) EnumTypeNode.IsEnum = True self.Trans.SymbolTable.insert(ClassName, EnumTypeNode) for item in Node.body: VarName = None if isinstance(item, ast.AnnAssign) and isinstance(item.target, ast.Name): VarName = item.target.id elif isinstance(item, ast.Assign): for target in item.targets: if isinstance(target, ast.Name): VarName = target.id break if VarName is None: continue value = 0 if isinstance(item, ast.Assign) and isinstance(item.value, ast.Constant) and isinstance(item.value.value, int): value = item.value.value MemberNode = CTypeInfo() MemberNode.Name = VarName MemberNode.BaseType = t.CEnum(ClassName) MemberNode.value = value MemberNode.EnumName = ClassName MemberNode.Lineno = item.lineno MemberNode.IsEnumMember = True self.Trans.SymbolTable.insert(VarName, MemberNode) self.Trans.SymbolTable.insert(f"{ClassName}.{VarName}", MemberNode) self.Trans.SymbolTable.insert(f"{ClassName}_{VarName}", MemberNode) return self._TryLoadStructFromStub(ClassName, Gen) source_sha1 = None if actual_module_name and hasattr(Gen, 'ModuleSha1Map') and actual_module_name in Gen.ModuleSha1Map: source_sha1 = Gen.ModuleSha1Map[actual_module_name] elif module_name and hasattr(Gen, 'ModuleSha1Map') and module_name in Gen.ModuleSha1Map: source_sha1 = Gen.ModuleSha1Map[module_name] # Track which module defines this class (for cross-module name mangling) if source_sha1: if not hasattr(Gen, 'class_sha1_map'): Gen.class_sha1_map = {} Gen.class_sha1_map[ClassName] = source_sha1 Gen._get_or_create_struct(ClassName, source_sha1=source_sha1, packed=IsPacked) if ClassName not in Gen.class_members: Gen.class_members[ClassName] = [] if ClassName not in Gen.class_member_defaults: Gen.class_member_defaults[ClassName] = {} if ClassName not in Gen.class_member_signeds: Gen.class_member_signeds[ClassName] = {} if ClassName not in Gen.class_member_bitfields: Gen.class_member_bitfields[ClassName] = {} if ClassName not in Gen.class_member_byteorders: Gen.class_member_byteorders[ClassName] = {} if ClassName not in Gen.class_member_bitoffsets: Gen.class_member_bitoffsets[ClassName] = {} has_methods = False # 解析 __provides__/__requires__/__require_must__ 编译期元数据(跨模块加载) for item in Node.body: if isinstance(item, ast.AnnAssign) and isinstance(item.target, ast.Name): field_name: str = item.target.id if field_name in ('__provides__', '__requires__', '__require_must__') and item.value: values: list[str] = [] if isinstance(item.value, ast.List): for elt in item.value.elts: if isinstance(elt, ast.Constant) and isinstance(elt.value, str): values.append(elt.value) if field_name == '__provides__': Gen.class_provides[ClassName] = values elif field_name == '__requires__': Gen.class_requires[ClassName] = values elif field_name == '__require_must__': Gen.class_require_must[ClassName] = values _existing_member_names = {m[0] for m in Gen.class_members[ClassName]} for item in Node.body: # 跳过编译期元数据字段(__provides__/__requires__/__require_must__) if isinstance(item, ast.AnnAssign) and isinstance(item.target, ast.Name): if item.target.id in ('__provides__', '__requires__', '__require_must__'): continue parsed = self.CollectAnnAssignMember(item, Gen) if parsed: VarName, MemberType, TypeInfo = parsed if TypeInfo is None: if VarName not in _existing_member_names: Gen.class_members[ClassName].append((VarName, MemberType)) _existing_member_names.add(VarName) Gen.class_member_signeds[ClassName][VarName] = None Gen.class_member_bitfields[ClassName][VarName] = 0 else: if TypeInfo.IsBitField: Gen.class_member_bitfields[ClassName][VarName] = TypeInfo.BitWidth else: if VarName not in _existing_member_names: Gen.class_members[ClassName].append((VarName, MemberType)) _existing_member_names.add(VarName) Gen.class_member_bitfields[ClassName][VarName] = 0 if TypeInfo.ByteOrder: Gen.class_member_byteorders[ClassName][VarName] = TypeInfo.ByteOrder else: Gen.class_member_byteorders[ClassName][VarName] = "" if item.value: const = self._BuildScalarConstant(item.value, MemberType, Gen) if const: Gen.class_member_defaults[ClassName][VarName] = const elif isinstance(item, ast.FunctionDef): has_methods = True MethodName = item.name FuncFullName = f"{ClassName}.__init__" if MethodName == "__init__" else f"{ClassName}.__call__" if MethodName == "__call__" else f"{ClassName}.{MethodName}" is_item_static = any(isinstance(d, ast.Name) and d.id == 'staticmethod' for d in item.decorator_list) is_item_property = any(isinstance(d, ast.Name) and d.id == 'property' for d in item.decorator_list) is_item_classmethod = any(isinstance(d, ast.Name) and d.id == 'classmethod' for d in item.decorator_list) is_item_prop_setter = any(isinstance(d, ast.Attribute) and d.attr == 'setter' and isinstance(d.value, ast.Name) and d.value.id == 'property' for d in item.decorator_list) is_item_prop_getter = any(isinstance(d, ast.Attribute) and d.attr == 'getter' and isinstance(d.value, ast.Name) and d.value.id == 'property' for d in item.decorator_list) is_item_prop_deleter = any(isinstance(d, ast.Attribute) and d.attr == 'deleter' and isinstance(d.value, ast.Name) and d.value.id == 'property' for d in item.decorator_list) # setter/deleter 使用不同的函数名后缀 DeclFuncName = FuncFullName if is_item_prop_setter: DeclFuncName = FuncFullName + '$set' elif is_item_prop_deleter: DeclFuncName = FuncFullName + '$del' if DeclFuncName not in Gen.functions: FuncDeclNode = ast.FunctionDef( name=DeclFuncName, args=item.args, body=item.body, decorator_list=item.decorator_list, returns=item.returns ) self._EmitExternalFuncDeclLlvm(FuncDeclNode, Gen, is_class_method=not is_item_static and not is_item_classmethod, source_module_name=module_name) func_meta = FuncMeta.NONE if is_item_static: func_meta |= FuncMeta.STATIC_METHOD if is_item_property: func_meta |= FuncMeta.PROPERTY_GETTER if is_item_classmethod: func_meta |= FuncMeta.CLASS_METHOD if is_item_prop_setter: func_meta |= FuncMeta.PROPERTY_SETTER if is_item_prop_getter: func_meta |= FuncMeta.PROPERTY_GETTER if is_item_prop_deleter: func_meta |= FuncMeta.PROPERTY_DELETER # setter/deleter 的 SymKey 使用带后缀的函数名 SymKey = DeclFuncName if not self.Trans.SymbolTable.has(SymKey): FuncInfo = CTypeInfo() FuncInfo.Name = SymKey FuncInfo.IsFunction = True FuncInfo.MetaList = func_meta self.Trans.SymbolTable.insert(SymKey, FuncInfo) else: existing = self.Trans.SymbolTable[SymKey] if existing.MetaList == FuncMeta.NONE and func_meta != FuncMeta.NONE: existing.MetaList = func_meta # setter/deleter: 同时在原始 PropKey(不带后缀)下注册 MetaList if is_item_prop_setter or is_item_prop_deleter: base_existing = self.Trans.SymbolTable.lookup(FuncFullName) if base_existing: if func_meta != FuncMeta.NONE: base_existing.MetaList = base_existing.MetaList | func_meta else: PropInfo = CTypeInfo() PropInfo.Name = FuncFullName PropInfo.IsFunction = True PropInfo.MetaList = func_meta self.Trans.SymbolTable.insert(FuncFullName, PropInfo) if has_methods: if IsCVTable: Gen._cross_module_vtable_classes.add(ClassName) Gen.class_vtable.add(ClassName) NewFuncName = f'{ClassName}.__before_init__' if not Gen._has_function(NewFuncName) and (IsCpythonObject or IsCVTable): source_sha1 = None if module_name and hasattr(Gen, 'ModuleSha1Map') and module_name in Gen.ModuleSha1Map: source_sha1 = Gen.ModuleSha1Map[module_name] MangledName = Gen._mangle_name(NewFuncName) if not source_sha1 else f"{source_sha1}.{NewFuncName}" StructType = Gen.structs.get(ClassName) if StructType: StructPtrType = ir.PointerType(StructType) NewFuncType = ir.FunctionType(ir.VoidType(), [StructPtrType]) NewFunc = ir.Function(Gen.module, NewFuncType, name=MangledName) Gen.functions[NewFuncName] = NewFunc bitfields = Gen.class_member_bitfields.get(ClassName, {}) if any(v > 0 for v in bitfields.values()): current_bit_offset = 0 for name, _ in Gen.class_members.get(ClassName, []): bw = bitfields.get(name, 0) Gen.class_member_bitoffsets[ClassName][name] = current_bit_offset current_bit_offset += bw def _TryLoadStructFromStub(self, class_name: str, Gen: LlvmGeneratorMixin) -> None: # 缓存键包含 Gen 实例标识,确保每个模块的 Gen 都能独立加载 struct 定义, # 同时仍能防止同一 Gen 内的递归重入。原先使用纯 class_name 作为键会导致 # 跨模块 struct 类型对象无法分别 set_body(每个模块有独立的 self.structs)。 cache_key = (id(Gen), class_name) if cache_key in self._struct_Load_cache: return # 先设置缓存防止递归重入,但如果加载失败则清除缓存允许后续重试 self._struct_Load_cache[cache_key] = True ProjectRoot = self._find_project_root() temp_dir = os.path.join(ProjectRoot, 'temp') if not os.path.isdir(temp_dir): del self._struct_Load_cache[cache_key] return if self._stub_cache_dir != temp_dir: self._stub_cache.clear() self._stub_cache_dir = temp_dir for filename in os.listdir(temp_dir): if not filename.endswith('.stub.ll'): continue stub_path = os.path.join(temp_dir, filename) source_sha1 = filename.replace('.stub.ll', '') try: with open(stub_path, 'r', encoding='utf-8') as f: content = f.read() for line in content.splitlines(): stripped = line.strip() match = re.match(r'%"?((?:[a-f0-9]+\.)?(\w+))"?\s*=\s*type\s*(.*)', stripped) if match: full_name = match.group(1) short_name = match.group(2) struct_body = match.group(3).strip() self._stub_cache.setdefault(short_name, []).append((full_name, struct_body, source_sha1)) if '.' in full_name: self._stub_cache.setdefault(full_name, []).append((full_name, struct_body, source_sha1)) except Exception as _e: if _config_mode == "strict": raise _vlog().warning(f"处理导入失败: {_e}", "Exception") if class_name not in self._stub_cache: del self._struct_Load_cache[cache_key] return for full_name, struct_body, source_sha1 in self._stub_cache[class_name]: is_packed = struct_body.startswith('<{') and struct_body.endswith('}>') if struct_body and struct_body != 'opaque': elem_types = [] if is_packed: inner = struct_body[2:-1].strip() else: inner = struct_body.strip('{}') if inner: for elem in inner.split(','): elem = elem.strip() if elem: et = self._parse_llvm_type(elem, Gen, source_sha1=source_sha1) elem_types.append(et if et else ir.IntType(32)) try: st = Gen._get_or_create_struct(class_name, source_sha1=source_sha1, packed=is_packed) if isinstance(st, ir.IdentifiedStructType) and st.is_opaque: st.set_body(*elem_types) if is_packed: Gen.class_packed.add(class_name) except Exception as _e: if _config_mode == "strict": raise _vlog().warning(f"处理导入失败: {_e}", "Exception") stub_filename = source_sha1 + '.stub.ll' self._TryLoadClassMembersFromPyi(class_name, stub_filename, Gen) return def _TryLoadClassMembersFromPyi(self, class_name: str, stub_filename: str, Gen: LlvmGeneratorMixin) -> None: ProjectRoot = self._find_project_root() temp_dir = os.path.join(ProjectRoot, 'temp') if class_name in Gen.class_members and len(Gen.class_members[class_name]) > 0: return pyi_basename = stub_filename.replace('.stub.ll', '.pyi') pyi_path = os.path.join(temp_dir, pyi_basename) if self._pyi_cache_dir != temp_dir: self._pyi_cache.clear() self._pyi_cache_dir = temp_dir if pyi_path not in self._pyi_cache: if not os.path.isfile(pyi_path): self._pyi_cache[pyi_path] = None return try: with open(pyi_path, 'r', encoding='utf-8') as f: self._pyi_cache[pyi_path] = ast.parse(f.read()) except Exception as e: _vlog().warning(f"HandlesImports: 忽略异常 {e}", exc_info=e) self._pyi_cache[pyi_path] = None return pyi_tree = self._pyi_cache[pyi_path] if pyi_tree is None: return source_sha1 = stub_filename.replace('.stub.ll', '') module_name = None if hasattr(Gen, 'ModuleSha1Map'): for mod_name, mod_sha1 in Gen.ModuleSha1Map.items(): if mod_sha1 == source_sha1 and '.' not in mod_name: module_name = mod_name break if module_name is None: for mod_name, mod_sha1 in Gen.ModuleSha1Map.items(): if mod_sha1 == source_sha1: module_name = mod_name break if module_name is None and hasattr(Gen, 'module_sha1') and Gen.module_sha1 == source_sha1: module_name = '__self__' found = False for node in pyi_tree.body: if isinstance(node, ast.ClassDef) and node.name == class_name: self._EmitExternalClassDeclLlvm(node, Gen, module_name=module_name) found = True break if not found: return if class_name in Gen.class_members and len(Gen.class_members[class_name]) > 0: return return def _BuildScalarConstant(self, value_node: ast.AST, target_type: ir.Type, Gen: LlvmGeneratorMixin) -> ir.Constant | None: """Build a scalar constant value from AST node, delegated to unified method.""" return self.Trans._BuildScalarConstantUnified(value_node, target_type, Gen) def _LookupStubFuncType(self, func_name: str, Gen: LlvmGeneratorMixin) -> ir.FunctionType | None: """从 stub.ll 文件中查找指定函数的 LLVM 类型""" if not getattr(self, '_stub_func_cache', None): self._stub_func_cache = {} ProjectRoot = self._find_project_root() temp_dir = os.path.join(ProjectRoot, 'temp') if not os.path.isdir(temp_dir): return None if func_name not in self._stub_func_cache: for filename in os.listdir(temp_dir): if not filename.endswith('.stub.ll'): continue stub_path = os.path.join(temp_dir, filename) source_sha1 = filename.replace('.stub.ll', '') try: with open(stub_path, 'r', encoding='utf-8') as f: content = f.read() for line in content.splitlines(): stripped = line.strip() if not stripped.startswith('declare '): continue name_match = stripped.split('@', 1) if len(name_match) < 2: continue stub_func_name = name_match[1].split('(', 1)[0].strip().strip('"') match = re.match(r'declare\s+(.+?)\s+@("?[\w.]+"?)\s*\((.*)\)$', stripped) if match: stub_ret = match.group(1).strip() stub_params = match.group(3).strip() self._stub_func_cache[stub_func_name] = (stub_ret, stub_params, '...' in stub_params, source_sha1) except Exception as _e: if _config_mode == "strict": raise _vlog().warning(f"处理导入失败: {_e}", "Exception") if func_name not in self._stub_func_cache: return None ret_str, params_str, is_variadic, source_sha1 = self._stub_func_cache[func_name] ret_type = self._parse_llvm_type(ret_str, Gen, source_sha1=source_sha1, create_structs=False) if ret_type is None: ret_type = ir.IntType(32) param_types = [] if params_str and params_str != 'void': depth = 0 current = '' for ch in params_str: if ch == ',' and depth == 0: p = self._strip_llvm_param_name(current.strip()) if p and p != '...': pt = self._parse_llvm_type(p, Gen, source_sha1=source_sha1, create_structs=False) if pt is not None: param_types.append(pt) else: param_types.append(ir.PointerType(ir.IntType(8))) current = '' else: if ch == '{': depth += 1 elif ch == '}': depth -= 1 current += ch p = self._strip_llvm_param_name(current.strip()) if p and p != '...': pt = self._parse_llvm_type(p, Gen, source_sha1=source_sha1, create_structs=False) if pt is not None: param_types.append(pt) else: param_types.append(ir.PointerType(ir.IntType(8))) return ir.FunctionType(ret_type, param_types, var_arg=is_variadic) def _strip_llvm_param_name(self, param_str: str) -> str: """剥离 LLVM IR 参数字符串中的参数名,仅保留类型部分""" param_str = param_str.strip() result = re.sub(r'\s+%[\w.]+\s*$', '', param_str) return result.strip() def _parse_simple_llvm_type(self, type_str: str) -> ir.Type | None: """解析简单的 LLVM 类型字符串(不创建结构体,避免副作用)""" type_str = type_str.strip() type_map = { 'void': ir.VoidType(), 'i1': ir.IntType(1), 'i8': ir.IntType(8), 'i16': ir.IntType(16), 'i32': ir.IntType(32), 'i64': ir.IntType(64), 'float': ir.FloatType(), 'double': ir.DoubleType(), } if type_str in type_map: return type_map[type_str] if type_str.endswith('*'): pointee = type_str[:-1].strip() pointee_type = self._parse_simple_llvm_type(pointee) if pointee_type: return ir.PointerType(pointee_type) return ir.PointerType(ir.IntType(8)) if type_str.startswith('[') and ']' in type_str: arr_match = re.match(r'\[(\d+)\s*x\s+(.+)\]', type_str) if arr_match: size = int(arr_match.group(1)) elem_type = self._parse_simple_llvm_type(arr_match.group(2).strip()) if elem_type: return ir.ArrayType(elem_type, size) if type_str.startswith('%'): return ir.PointerType(ir.IntType(8)) return None def _LoadDeclarationsFromStubLlvm(self, module_name: str, Gen: LlvmGeneratorMixin) -> None: """从 stub.ll 文件加载函数声明和结构体定义到 Gen""" ProjectRoot = self._find_project_root() temp_dir = os.path.join(ProjectRoot, 'temp') if not os.path.isdir(temp_dir): return all_stub_files = [f for f in os.listdir(temp_dir) if f.endswith('.stub.ll')] source_sig_files = getattr(self.Trans, '_source_module_sig_files', {}) target_sha1 = None if module_name: for key, val in source_sig_files.items(): if key == module_name or key.endswith('.' + module_name): target_sha1 = os.path.basename(val).replace('.pyi', '') break if not target_sha1: ModuleSha1Map = getattr(Gen, 'ModuleSha1Map', {}) target_sha1 = ModuleSha1Map.get(module_name) if target_sha1: target_stub = f"{target_sha1}.stub.ll" if target_stub in all_stub_files: stub_files = [target_stub] else: stub_files = [] else: stub_files = [] needed_sha1s = set() for filename in stub_files: stub_path = os.path.join(temp_dir, filename) try: with open(stub_path, 'r', encoding='utf-8') as f: content = f.read() for line in content.splitlines(): stripped = line.strip() if stripped.startswith('%') and '=' in stripped and 'type' in stripped: for m in re.finditer(r'%\"?([a-f0-9]{16})\.', stripped): needed_sha1s.add(m.group(1)) elif stripped.startswith('declare '): for m in re.finditer(r'%\"?([a-f0-9]{16})\.', stripped): needed_sha1s.add(m.group(1)) except Exception as _e: if _config_mode == "strict": raise _vlog().warning(f"处理导入失败: {_e}", "Exception") preLoad_files = [] for sha1 in needed_sha1s: dep_stub = f"{sha1}.stub.ll" if dep_stub in all_stub_files and dep_stub not in stub_files: preLoad_files.append(dep_stub) Load_order = preLoad_files + stub_files for filename in Load_order: stub_path = os.path.join(temp_dir, filename) source_sha1 = filename.replace('.stub.ll', '') is_preLoad = filename in preLoad_files try: with open(stub_path, 'r', encoding='utf-8') as f: content = f.read() for line in content.splitlines(): stripped = line.strip() if stripped.startswith('%') and '=' in stripped and 'type' in stripped: struct_def_match = re.match(r'%"?((?:[a-f0-9]+\.)?[\w.]+)"?\s*=\s*type\s*(.*)', stripped) if struct_def_match: raw_name = struct_def_match.group(1) if '.' in raw_name: clean_name = raw_name.split('.', 1)[1] else: clean_name = raw_name struct_body = struct_def_match.group(2).strip() if struct_body and struct_body != 'opaque': elem_types = [] is_packed = struct_body.startswith('<{') and struct_body.endswith('}>') if is_packed: inner = struct_body[2:-1].strip() else: inner = struct_body.strip('{}') if inner: for elem in inner.split(','): elem = elem.strip() if elem: elem_type = self._parse_llvm_type(elem, Gen, source_sha1=source_sha1) if elem_type is not None: elem_types.append(elem_type) else: elem_types.append(ir.IntType(32)) st = Gen._get_or_create_struct(clean_name, source_sha1=source_sha1, packed=is_packed) if isinstance(st, ir.IdentifiedStructType) and st.elements is None: try: st.set_body(*elem_types) except Exception as _e: if _config_mode == "strict": raise _vlog().warning(f"处理导入失败: {_e}", "Exception") continue if not stripped.startswith('declare '): continue if is_preLoad: continue func_name_match = stripped.split('@', 1) if len(func_name_match) < 2: continue func_name = func_name_match[1].split('(', 1)[0].strip().strip('"') if not module_name: continue try: func_type = self._parse_llvm_declare(stripped, Gen) if func_type is not None: if func_name in Gen.functions: existing_func = Gen.functions[func_name] existing_ftype = getattr(existing_func, 'ftype', None) or existing_func.type.pointee if str(existing_ftype) != str(func_type): func = ir.Function(Gen.module, func_type, name=func_name) Gen.functions[func_name] = func if '.' in func_name: short_name = func_name.split('.', 1)[1] Gen.functions[short_name] = func else: func = ir.Function(Gen.module, func_type, name=func_name) Gen.functions[func_name] = func if '.' in func_name: short_name = func_name.split('.', 1)[1] Gen.functions[short_name] = func except Exception as _e: if _config_mode == "strict": raise _vlog().warning(f"处理导入失败: {_e}", "Exception") except Exception as _e: if _config_mode == "strict": raise _vlog().warning(f"处理导入失败: {_e}", "Exception") def _TryLoadFuncDeclsFromOutputStub(self, struct_name: str, Gen: LlvmGeneratorMixin) -> bool: """按需从 output 目录的 stub.ll 加载泛型特化方法声明。 当 temp stub 不包含泛型特化(如 list[str].__iter__)时, 从 output stub(由 _split_ll 从完整 IR 生成)中按需加载。 struct_name: 结构体名,如 '9163064cf3eb88f4.list[str]' 或 'list[str]' 返回 True 如果找到了并加载了新函数声明。 """ ProjectRoot = self._find_project_root() output_dir = os.path.join(ProjectRoot, 'output') if not os.path.isdir(output_dir): return False # 提取短名称和 SHA1 前缀 sha1_prefix: str | None = None if '.' in struct_name: parts: list[str] = struct_name.split('.', 1) if len(parts[0]) == 16 and all(c in '0123456789abcdef' for c in parts[0]): sha1_prefix = parts[0] short_name: str = parts[1] else: short_name = struct_name else: short_name = struct_name # 确定要搜索的 stub 文件列表 stub_files_to_search: list[str] = [] if sha1_prefix: target_stub = f'{sha1_prefix}.stub.ll' target_path = os.path.join(output_dir, target_stub) if os.path.isfile(target_path): stub_files_to_search.append(target_stub) else: # 没有SHA1前缀,搜索所有 output stub for fname in os.listdir(output_dir): if fname.endswith('.stub.ll'): stub_files_to_search.append(fname) loaded_any: bool = False for fname in stub_files_to_search: stub_path = os.path.join(output_dir, fname) try: with open(stub_path, 'r', encoding='utf-8') as f: content = f.read() for line in content.splitlines(): stripped = line.strip() if not stripped.startswith('declare '): continue # 检查是否是目标结构体的方法 if f'.{short_name}.' not in stripped: continue try: func_type = self._parse_llvm_declare(stripped, Gen) if func_type is None: continue # 提取函数名:最后一个 @ 到第一个 ( 之间的内容 paren_pos: int = stripped.index('(') at_pos: int = stripped.rindex('@', 0, paren_pos) func_name: str = stripped[at_pos + 1:paren_pos].strip().strip('"') if func_name not in Gen.functions: func = ir.Function(Gen.module, func_type, name=func_name) Gen.functions[func_name] = func if '.' in func_name: short_fn = func_name.split('.', 1)[1] Gen.functions[short_fn] = func loaded_any = True except Exception: pass except Exception as _e: if _config_mode == "strict": raise _vlog().warning(f"按需加载 output stub 失败: {_e}", "Exception") return loaded_any def _parse_llvm_declare(self, declare_line: str, Gen: LlvmGeneratorMixin) -> ir.FunctionType | None: """解析 LLVM declare 语句""" match = re.match(r'declare\s+(.+?)\s+@("?[^()]+"?)\s*\((.*)\)$', declare_line) if not match: return None ret_type_str = match.group(1).strip() params_str = match.group(3).strip() ret_type = self._parse_llvm_type(ret_type_str, Gen) if ret_type is None: return None param_types = [] if params_str and params_str != 'void': depth = 0 current = '' for ch in params_str: if ch == ',' and depth == 0: p = self._strip_llvm_param_name(current.strip()) if p and p != '...': pt = self._parse_llvm_type(p, Gen) if pt is not None: param_types.append(pt) current = '' else: if ch == '{': depth += 1 elif ch == '}': depth -= 1 current += ch p = self._strip_llvm_param_name(current.strip()) if p and p != '...': pt = self._parse_llvm_type(p, Gen) if pt is not None: param_types.append(pt) is_variadic = '...' in params_str return ir.FunctionType(ret_type, param_types, var_arg=is_variadic) def _parse_llvm_type(self, type_str: str, Gen: LlvmGeneratorMixin, source_sha1: str | None = None, create_structs: bool = True) -> ir.Type | None: """解析 LLVM 类型字符串""" type_str = type_str.strip() type_map = { 'void': ir.VoidType(), 'i1': ir.IntType(1), 'i8': ir.IntType(8), 'i16': ir.IntType(16), 'i32': ir.IntType(32), 'i64': ir.IntType(64), 'float': ir.FloatType(), 'double': ir.DoubleType(), } if type_str in type_map: return type_map[type_str] if type_str.startswith('{') and type_str.endswith('}'): inner = type_str[1:-1].strip() field_types = [] depth = 0 current = '' for ch in inner: if ch == ',' and depth == 0: field_types.append(current.strip()) current = '' else: if ch == '{': depth += 1 elif ch == '}': depth -= 1 current += ch if current.strip(): field_types.append(current.strip()) llvm_fields = [] for ft in field_types: parsed = self._parse_llvm_type(ft, Gen, source_sha1=source_sha1, create_structs=create_structs) if parsed is not None: llvm_fields.append(parsed) else: return None if llvm_fields: return ir.LiteralStructType(llvm_fields) return None if type_str.endswith('*'): pointee = type_str[:-1].strip() pointee_type = self._parse_llvm_type(pointee, Gen, source_sha1=source_sha1, create_structs=create_structs) if pointee_type is not None: return ir.PointerType(pointee_type) return ir.PointerType(ir.IntType(8)) if type_str.startswith('[') and ']' in type_str: arr_match = re.match(r'\[(\d+)\s*x\s+(.+)\]', type_str) if arr_match: size = int(arr_match.group(1)) elem_type = self._parse_llvm_type(arr_match.group(2).strip(), Gen, source_sha1=source_sha1, create_structs=create_structs) if elem_type is not None: return ir.ArrayType(elem_type, size) typedef_names = {'typedef', 'CType', 'CVolatile', 'CEnum', 'CUnion', 'CStruct', 'enum', 'REnum', 'renum'} struct_match = re.match(r'%"?((?:[a-f0-9]+\.)?[\w.\[\]]+)"?', type_str) if struct_match: raw_name = struct_match.group(1) actual_source_sha1 = source_sha1 if '.' in raw_name: parts = raw_name.split('.', 1) if len(parts[0]) == 16 and all(c in '0123456789abcdef' for c in parts[0]): actual_source_sha1 = parts[0] struct_name = parts[1] else: struct_name = raw_name if struct_name not in typedef_names: # 泛型类特化(如 list[str]):触发特化创建结构体和方法声明 # 使用 declare_only=True 避免在引用模块中生成方法体(定义由定义模块提供) if '[' in struct_name and actual_source_sha1: if struct_name in Gen.structs: return Gen.structs[struct_name] gc_base: str = struct_name.split('[')[0] gc_args_raw: str = struct_name[len(gc_base):] gc_type_args: list[str] = [] for gc_arg_part in gc_args_raw.split(']['): gc_arg_part = gc_arg_part.strip('[]') if gc_arg_part: gc_type_args.append(gc_arg_part) if (gc_type_args and hasattr(self.Trans, 'ClassHandler') and hasattr(self.Trans.ClassHandler, '_generic_class_templates') and gc_base in self.Trans.ClassHandler._generic_class_templates): gc_type_names: list[str] = [] for gc_ta in gc_type_args: gc_tn: str = gc_ta if gc_ta == 'int': gc_tn = 'CInt' elif gc_ta == 'double': gc_tn = 'CDouble' elif gc_ta == 'float': gc_tn = 'CFloat' elif gc_ta == 'char': gc_tn = 'CChar' elif gc_ta == 'bool': gc_tn = 'CBool' gc_type_names.append(gc_tn) saved_module_sha1: str | None = getattr(Gen, 'module_sha1', None) saved_trans_sha1: str | None = getattr(self.Trans, '_module_sha1', None) Gen.module_sha1 = actual_source_sha1 self.Trans._module_sha1 = actual_source_sha1 try: gc_spec: str | None = self.Trans.ClassHandler._specialize_generic_class( gc_base, gc_type_args, Gen, type_names=gc_type_names, declare_only=True) except Exception: gc_spec = None Gen.module_sha1 = saved_module_sha1 self.Trans._module_sha1 = saved_trans_sha1 if gc_spec and gc_spec in Gen.structs: return Gen.structs[gc_spec] if create_structs: return Gen._get_or_create_struct(struct_name, source_sha1=actual_source_sha1) else: existing = Gen.structs.get(struct_name) if existing is not None: return existing return None return ir.IntType(32) return None def _strip_param_name(self, param_str: str) -> str: """从 LLVM 参数字符串中剥离参数名,只保留类型部分 例如: '%"sha1.struct_name"* %param_name' -> '%"sha1.struct_name"*' 'i8* %path' -> 'i8*' 'i32' -> 'i32' """ param_str = param_str.strip() stripped = re.sub(r'\s+%[\w.]+\s*$', '', param_str) return stripped.strip()