diff --git a/App/lib/core/Gen/Class/ClassGenDef.vp b/App/lib/core/Gen/Class/ClassGenDef.vp index 90ca9d3..0a0b7c3 100644 --- a/App/lib/core/Gen/Class/ClassGenDef.vp +++ b/App/lib/core/Gen/Class/ClassGenDef.vp @@ -108,15 +108,23 @@ novirtual class ClassGenDef(ClassGenBase.ClassGenBase): # 标记 _deps_pending 延迟会让空 entry 持久存在、多遍死锁不收敛 ⇒ 改为中和旧 entry # (置空 Name + Ty=None,使 find_struct_by_module 不再命中),走下方完整注册路径重建。 needs_rereg: int = 0 + # 前向声明占位命中:本类此前被"指向未注册类的指针"注解按名字前向声明过 + # (StructGenMixin.ensure_forward_class 建了 {sha}.Class 不透明占位)。 + # 此处**不早退**,走下方"原位填充"分支——把字段体写进同一个 Ty 对象, + # 保住指针身份,使所有已发出的 Ptr(占位.Ty) 引用(如 CallGenBase.Gen) + # 在填充后自动指向完整定义(对齐 TPC 的 opaque 占位 + set_body 填充)。 + fill_fwd: int = 0 if existing is not None: - if existing.FieldCount == 0 and trans._declare_only == 0: + if existing.IsForwardDecl != 0: + fill_fwd = 1 + elif existing.FieldCount == 0 and trans._declare_only == 0: if self.Gen.ClassGenBase._class_body_has_field(cd) != 0: if existing.Name is not None: existing.Name[0] = '\0' existing.Ty = None existing.FieldCount = 0 needs_rereg = 1 - if existing is not None and needs_rereg == 0: + if existing is not None and needs_rereg == 0 and fill_fwd == 0: # 结构体已注册(Phase A-pre 多遍扫描或 Phase B 重新翻译),但仍需在当前模块中翻译方法 if existing.Ty is not None: # Phase 1b(declare_only=0):existing.Ty 是 Phase 1a 注册的 struct_ty, @@ -555,6 +563,20 @@ novirtual class ClassGenDef(ClassGenBase.ClassGenBase): struct_ty = st_copy type_name = llvmlite.get_struct_name(struct_ty) + # 前向声明占位命中:**原位填充**——保持 existing.Ty 指针身份不变,仅把刚构建 + # 的字段体写进同一个类型对象(名称沿用占位的 "{sha}.Class")。此后所有已发出的 + # Ptr(占位.Ty) 引用(如 CallGenBase.Gen)自动指向完整定义。这一步必须早于 + # module_add_named_type:避免同一名字先以占位名注册、又以新对象重复注册。 + if fill_fwd != 0 and existing is not None and existing.Ty is not None: + _fwd_name: str = llvmlite.get_struct_name(existing.Ty) + _fwd_fields: rawptr[llvmlite.ParamNode] = llvmlite.get_struct_fields(struct_ty) + _fwd_fc: int = llvmlite.get_struct_fcount(struct_ty) + *existing.Ty = llvmlite.LLVMType.Struct(_fwd_fields, _fwd_fc, _fwd_name) + struct_ty = existing.Ty + type_name = _fwd_name + existing.IsForwardDecl = 0 + existing.FieldCount = 0 + # 注册命名结构体到模块(输出 %"name" = type { ... } 定义行) llvmlite.module_add_named_type(trans.Module, pool, struct_ty) @@ -577,7 +599,13 @@ novirtual class ClassGenDef(ClassGenBase.ClassGenBase): # ============================================================ # 3. 注册到 HandlesStruct # ============================================================ - entry: GenTypes.StructEntry* = self.Gen.StructGen.register_struct(pool, class_name, struct_ty, trans.ModuleSha1) + entry: GenTypes.StructEntry* = None + if fill_fwd != 0 and existing is not None: + # 前向声明占位:字段体已原位写入 existing.Ty,登记条目沿用 existing + # (不新建条目/Ty,否则已发出的 Ptr(占位.Ty) 引用会指向旧空对象)。 + entry = existing + else: + entry = self.Gen.StructGen.register_struct(pool, class_name, struct_ty, trans.ModuleSha1) if entry is None: VLogger.error(f"failed to register {class_name}", "CLASS") return 0 diff --git a/App/lib/core/Gen/GenTypes.vp b/App/lib/core/Gen/GenTypes.vp index 3660f31..152f7ea 100644 --- a/App/lib/core/Gen/GenTypes.vp +++ b/App/lib/core/Gen/GenTypes.vp @@ -167,6 +167,9 @@ novirtual class StructEntry: # sha1(方法名带模板前缀),作废重译时 reset_module_methods_emitted # 按触发文件 sha1 精确重置 MethodsEmitted,防特化方法体残留 1 不补译 # → 链接 undefined symbol(list[ast.ClassDef|t.CPtr].* 根因) + IsForwardDecl: int # 1=前向声明占位条目(Ty=不透明具名结构体 {sha}.Class,FieldCount=0; + # 定义侧随后**原位填充**同一 Ty 对象并清零本标记)。 + # 语义对齐 TPC:未注册类 → 具名空 CStruct 占位 + 后续 set_body 填充。 # ============================================================ diff --git a/App/lib/core/Gen/StructGenMixin.vp b/App/lib/core/Gen/StructGenMixin.vp index af74d0c..89e8e79 100644 --- a/App/lib/core/Gen/StructGenMixin.vp +++ b/App/lib/core/Gen/StructGenMixin.vp @@ -665,6 +665,58 @@ novirtual class StructGenMixin: self._struct_idx_add(self.StructCount - 1, entry) return entry + # ============================================================ + # ensure_forward_class - 按"类名 + 定义模块 SHA1"前向声明一个类 + # + # 语义对齐 TPC(未注册类 → 具名空 CStruct 占位 + 后续 set_body 填充): + # 注解里出现"指向尚未注册的类的指针"时,返回 Ptr(本占位.Ty) 而不是匿名的 + # Ptr(i8)——后者会丢类身份、冻结成 i8* 且无法修复(Gen↔Generator 循环依赖 + # 使"按顺序先注册被指类"不可能)。定义侧命中该占位条目时**原位填充**同一 + # Ty 对象(保持 Ty 指针不变),所有已发出的 Ptr 引用继续有效。 + # + # 幂等:已存在 (class_name, sha1) 条目则直接返回,不重复登记。 + # 返回占位/既有条目;参数非法或表满时返回 None。 + # ============================================================ + def ensure_forward_class(self, pool: memhub.MemBuddy*, sha1: str, class_name: str) -> GenTypes.StructEntry*: + if pool is None or sha1 is None or class_name is None: + return None + if sha1[0] == '\0' or class_name[0] == '\0': + return None + existing: GenTypes.StructEntry* = self.find_struct_by_module(class_name, sha1) + if existing is not None: + return existing + if self.init_struct_table(pool) == 0: + return None + if self.StructCount >= STRUCT_MAX: + VLogger.error(f"table full, cannot forward-declare {class_name}", "STRUCT") + return None + # 具名不透明结构体 "%{sha}.{Class}"(fields=None, fcount=0):名称与定义侧 + # ClassGenDef 的 "sha1.ClassName" 逐字一致,故定义侧能按名定位并原位填充。 + # 名称与 Ty 必须按 StdlibAlloc 选**持久池**(与 register_struct 同口径): + # 占位条目不随本文件 reset 回收,Ty/Name 若在瞬时 pool 会悬空。 + fwd_pool: memhub.MemBuddy* = pool + if self.StdlibAlloc != 0: + fwd_pool = CompilerFactory.factory.PermPool + if fwd_pool is None: + fwd_pool = pool + sh_len: sizet = string.strlen(sha1) + cn_len: sizet = string.strlen(class_name) + name_buf: str = fwd_pool.alloc(sh_len + 1 + cn_len + 1) + if name_buf is None: + return None + string.strcpy(name_buf, sha1) + name_buf[sh_len] = '.' + string.strcpy(name_buf + sh_len + 1, class_name) + fwd_ty: llvmlite.LLVMType* = llvmlite.Struct(fwd_pool, None, 0, name_buf) + if fwd_ty is None: + return None + entry: GenTypes.StructEntry* = self.register_struct(pool, class_name, fwd_ty, sha1) + if entry is None: + return None + entry.IsForwardDecl = 1 + self.add_visible_struct(pool, class_name) + return entry + def register_struct_alias(self, pool: memhub.MemBuddy*, name: str, sha1: str, diff --git a/App/lib/core/Gen/Type/TypeGenResolve.vp b/App/lib/core/Gen/Type/TypeGenResolve.vp index 2fb568d..7109374 100644 --- a/App/lib/core/Gen/Type/TypeGenResolve.vp +++ b/App/lib/core/Gen/Type/TypeGenResolve.vp @@ -1946,6 +1946,68 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): return 1 return 0 + # ============================================================ + # _forward_declare_ptr_from_node - 对"指向未注册类"的注解做前向声明 + # + # 语义对齐 TPC(未注册类 → 具名空 CStruct 占位 + 后续 set_body 填充): + # 注解里出现"指向尚未注册的类的指针"(该类尚未 register_struct,常见于类间 + # 循环引用 / 跨文件注册顺序)时,不再退回匿名的 Ptr(i8)——它丢类身份、被冻结 + # 成 i8* 后无法修复(现场 CallGenBase.Gen: Generator.Generator* 先于 Generator + # 注册 ⇒ Gen 冻结 i8* ⇒ self.Gen.FuncGenBase 属性访问失败)。改为按"类名 + + # 定义模块 SHA1"登记具名不透明结构体 {sha}.Class 占位,返回 Ptr(占位.Ty); + # 定义侧命中占位时**原位填充**同一 Ty 对象(指针身份不变)。 + # + # 仅支持 Attribute(Mod.Class / 别名 Mod.Class)与 Name(from-import 裸类名)两形。 + # 返回 Ptr(占位.Ty);无法确定类名/模块 SHA1 时返回 None(调用方保持旧行为)。 + # ============================================================ + def _forward_declare_ptr_from_node(self, pool: memhub.MemBuddy*, + node: ast.AST*, + imported_modules: str, + from_imports: str) -> llvmlite.LLVMType*: + if node is None: + return None + cls_nm: str = None + sha1: str = None + k: int = node.kind() + if k == ast.ASTKind.Attribute: + at: ast.Attribute* = ast.Attribute*(node) + if at.attr is None or at.value is None: + return None + if at.value.kind() != ast.ASTKind.Name: + return None + mod_nm: ast.Name* = ast.Name*(at.value) + if mod_nm.id is None: + return None + cls_nm = at.attr + mod_buf: str = mod_nm.id + # 4a. mod 是 import 别名时先经 from_imports 反查完整模块名 + if from_imports is not None: + orig_mod: str = self.Gen.ImportsGen.lookup_from_import(from_imports, mod_buf, 0) + if orig_mod is not None: + sha1 = self.Gen.CallGenBase._lookup_sha1_from_modname_raw(pool, orig_mod) + # 4b. mod 本身是完整模块路径(无别名 import) + if sha1 is None: + sha1 = self.Gen.CallGenBase._lookup_sha1_from_modname_raw(pool, mod_buf) + elif k == ast.ASTKind.Name: + nm: ast.Name* = ast.Name*(node) + if nm.id is None: + return None + cls_nm = nm.id + if from_imports is not None: + sha1 = self.Gen.CallGenBase._lookup_mod_sha1(pool, from_imports, cls_nm, 0) + else: + return None + if cls_nm is None or sha1 is None or sha1[0] == '\0': + return None + # 校正为类的**真定义模块**(包重导出会给包 sha1) + sha1 = self._class_true_def_sha1(pool, cls_nm, sha1) + if sha1 is None or sha1[0] == '\0': + return None + entry: GenTypes.StructEntry* = self.Gen.StructGen.ensure_forward_class(pool, sha1, cls_nm) + if entry is None or entry.Ty is None: + return None + return llvmlite.Ptr(pool, entry.Ty) + def map_t_type(self, pool: memhub.MemBuddy*, type_name: str) -> llvmlite.LLVMType*: """将 t.CXxx / c.CXxx 类型名映射到 LLVMType @@ -3311,9 +3373,17 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): return llvmlite.Ptr(pool, other_cls_ty) # 类名未注册:可能是前向自我引用(如 json 的 # 'JsonValue' | t.CPtr 字段引用自身类,类体处理中 - # 尚未注册)或 Phase A-pre 依赖类延迟到下一遍注册。 - # 回退 Ptr(i8) 由 AnnotClassName 恢复 + 多遍注册收敛 - # 处理,禁止 fail-fast(误杀合法前向引用)。 + # 尚未注册)、或 Phase A-pre 依赖类延迟到下一遍注册、 + # 或是**循环依赖**(Gen↔Generator)下被指类必然晚于本类。 + # 首选**前向声明**:按类名+定义模块 SHA1 登记具名不透明 + # 结构体 {sha}.Class 占位并返回 Ptr(占位.Ty),保住类身份 + # (定义侧随后原位填充)。无法确定类名/模块时,才退回 + # Ptr(i8) 由 AnnotClassName 恢复 + 多遍注册收敛,禁止 + # fail-fast(误杀合法前向引用)。 + _fwd_ptr_ty: llvmlite.LLVMType* = self._forward_declare_ptr_from_node( + pool, other_node, imported_modules, from_imports) + if _fwd_ptr_ty is not None: + return _fwd_ptr_ty return llvmlite.Ptr(pool, llvmlite.Int8(pool)) # `X | t.CPtr` **恒追加恰好 1 层**(用户口径,与 rawptr[T]/t.CPtr[T] 下标形 # 以及 `_parse_type_string` 的 `T*` 反解析三处同源):