Commit Graph
76 Commits
Author SHA1 Message Date
TermiNexus 2aa55f36b9 fix(LLVMCG/Handles): GEP 基址 opaque 时按同短名找已具化孪生体并 bitcast;守卫报文补实例清单
现场:includes/ast/astaux.vp:165 `self.arg = _copy_str(pool, arg)` 的基址 pointee 是
opaque 的 `%"<sha1>.Keyword"*` —— 同一类名常有两份 struct 实例(短名 'Keyword' /
sha1 全名 '<sha1>.Keyword'),"函数签名里的 self 用哪一份"与"StructGen 具化的是哪一份"
并不保证一致 ⇒ GEP 无法在编译期算元素类型(llvmlite 抛裸 TypeError),
_GuardIrBuilder 的 opaque 守卫先行 fail-fast。

改动:
1) 新增 BaseGen._FindConcreteTwin:按**同短名**(IR 全名相同者优先)在 structs 里取一份
   已具化实例(元素最多者),与既有"同名实例取字段最多版本"(HandlesAssign:1156-1163)
   同一判据;返回 None 表示确无孪生体(真因 = "body 从未 set_body")。
2) 写入路径 HandlesAssign(`self.字段 = ...`):pointee opaque 时 bitcast 到孪生体,
   并把它同时用作 struct_type 与 `_get_member_offset` 的 hint ⇒ 偏移与 GEP 同源。
3) 读取路径 HandlesExprAttr._gep_and_Load_member 共用同一工具函数(读写两路同判据,
   避免"写修了读没修"再吃一轮)。
4) opaque-GEP 守卫报文补"同短名 struct 实例(key(IR 名)=槽数)"清单:清单含已具化项
   ⇒ 属"偏移与 GEP 未同源";全是 opaque ⇒ 属"body 从未 set_body"——一次报文即可判定是哪一侧。

找不到孪生体时**不兜底**:仍由守卫 fail-fast 并报出实例清单(本仓"不许静默兜底"约定)。

验证:py_compile 通过(BaseGen / HandlesAssign / HandlesExprAttr 三文件)。
2026-09-30 11:57:07 +08:00
TermiNexus a43513c9a7 fix(FuncGen): 成员表与 body 等长时不再做类型对齐校准(base 恒为 0),修 TypeGenMixin 字段偏移平移
判据:_member_base 的 delta 长度启发式在 _Delta == 0 时给出 base=0(FuncGen.py:441),
而 base=2 **只可能**来自其后的"类型一致性校准"(:446-480,对 b∈{0,1,2} 逐项比类型取高分)。
但该校准用的是**严格类型相等**:声明侧的联合类型(如 `list[str] | t.CPtr`)可能被记成
折叠后的 i8*,而 body 槽仍是具体指针(`%list[str]*`)⇒ b=0 丢掉这些匹配,某个平移 b
却能靠指针槽互相撞分拿到更高分。
_Delta == 0(成员表项数 == body 槽数)本身就是"逐位对齐"(定义端 body 由成员表生成;
跨模块端 body(.stub.ll) 与 .vpi 同一声明顺序)⇒ base 必须为 0,此时不应再校准。

现场(本次):TypeGenMixin 41 成员 / 41 槽(@t.NoVTable,_Delta=0),base 被抬到 2 ⇒
Generator.vp:136 `SubTypeGen.Gen = self`(Gen 声明 `%"47382dc8277c9d28.Generator"*`、
正确槽为 0)写到槽 40(`%"6fbe68e8fca81223.TypeSpecArena"*`)⇒
"指针隐式转换已禁止(非基本类型(结构体/类/联合)):不能把指针类型 … 隐式转为 …"。
(该报文本身是上一提交 909d8ff0 补的"成员槽来源明细",这次正是靠它直接读出了
base/下标/声明槽类型/布局槽类型四项事实。)

附带:成员槽失败报文再补"名序"(_DescribeMemberSlot 打印 index:name 全表)——偏移算错时
第一现场就是"名序与 body 布局不同源",逐项列出即可直接判定是哪一侧漂移,不必再猜。

验证:py_compile 通过。_Delta != 0 的既有校准现场(ast.Constant 的 `cnst.lineno` 落到
pool 槽等)不受影响(本次只加前置条件,未改校准逻辑本身)。
2026-09-30 11:15:58 +08:00
TermiNexus 909d8ff0eb fix(诊断): _get_node_info 还原自动注入 import 的行偏移;属性写入失败补成员槽来源明细
1) 行号还原(b 机制缺陷,实测可证):Phase2Translator._inject_auto_imports 在源码头
   注入 import _list/_dict/json,AST 节点行号整体下移 N
   (translator._line_offset = N),而 Gen._current_source_lines 是**磁盘**原文。
   BaseGen._get_node_info / _DescribeValueNode 此前直接用 AST 行号读源码 ⇒ 同一次
   fail-fast 里出现两套互相矛盾的行号:_ErrorStack 帧印磁盘行 1140/1141,
   _get_node_info 印 1142,并把源码显示成 1142 行的 `return "t.CPtr"`
   (真实错误行是 1141 的 `...LastBuiltinsTypedefResult = "t.CPtr"`,与被指语句无关)。
   新增 BaseGen._DiskLineno,判据与 HandlesBase._FailFastFixedArrayKeyword:687、
   HandlesAssign._RaiseGlobalAttrError:2051、LlvmGenerator/HandlesBody 的
   _ErrorStack 各站点逐字一致;标签行号(_current_node_info)与源码行一并还原。

2) 成员槽来源明细:HandlesAssign 的「对象.字段 = 值」写入路径(:1182)此前
   _StoreWithCoerce 失败时只报隐式转换禁令,不报目标槽来源,报文与被指语句、
   偏移对不上。现与 `self.x = ...` 路径(:897-905)同口径补 _DescribeMemberSlot:
   附上 class_members 声明侧(项数/base/表内下标/声明槽类型)与布局侧
   (struct name/元素数/elements[offset])两侧数据,可直接判定是声明侧退化
   还是布局侧退化(本次现场即"表内下标被同名继承项过滤移位"一类)。
2026-09-30 10:46:42 +08:00
TermiNexus c86ecb0c89 refactor(LLVMCG/Handles): 直建 ir.Function 点全部收敛到幂等入口,3 处不再吞 fail-fast
类别:a 编译器缺陷(同一根因的其它变体收口:函数声明创建的唯一入口 + fail-fast 不被吞)

背景(前几轮已修两处):llvmlite 的 `Function.__init__` 不是事务性的(名字在 `_set_name`
就进 module.scope,之后建 Argument/ReturnValue 才断言类型,最后才 add_global),中途失败会
留下悬空名字;本仓已加两道防线:全局守卫 `InstallFuncCreateGuard()`(回滚名字 + 带调用点的
FailFastError)与唯一幂等入口 `_CreateOrReuseFuncDecl`(创建前自检、同名同类型复用、异类型
带两侧签名 fail-fast)。但仍有 20+ 处**绕过入口直建**(同名时抛裸 DuplicatedNameError、且
名字被占/半注册时只能看到没有上下文的名字),以及若干处 `except Exception` 会把这颗
FailFastError 吞掉(声明静默缺失、真因丢失)。

改动:
1. 创建点收敛(21 处,行为等价或更严)——`X = ir.Function(<gen>.module, <fty>, name=<N>)`
   → `X = <gen>._CreateOrReuseFuncDecl(<N>, <fty>)`:
   · Handles:HandlesAnnAssign[llvm.memcpy]、HandlesAssign[llvm.memcpy.p0i8.p0i8.i64 ×2]、
     HandlesBase[GetOrCreateFuncDecl / GetOrCreateStubFuncDecl]、
     HandlesExpr[malloc]、HandlesExprBuiltin[strlen / llvm.memcpy]、
     HandlesExprCall[llvm.* 与普通 / win32 API / strlen / llvm.memcpy]、
     HandlesAsyncFunctions[malloc / create_* / resume_* / CRT 声明 / free]、
     HandlesExprLambda[lambda 包装]、HandlesFunctions[_replace_function_decl 的 old_func=None 分支]、
     HandlesImports[output stub 按需加载]
   · LLVMCG:BaseGen[_DeclareExternalFunc 与 6 处 --hotspot-sites 内部符号]、
     ExprGen[llvm.pow.f64]、VaArg[llvm.va_start / llvm.va_end]
   原有 globals/functions 预检查与随后的 `Gen.functions[...] = X` 赋值一律保留。
   注:`_DeclareExternalFunc` 收敛后外部声明会同时登记进 `self.functions`(幂等入口固有行为,
   与既有 `get_or_declare_c_func` 一致;该路径仅 --hotspot-sites 调试特性使用)。
2. fail-fast 不再被吞(3 处):在包裹创建的 `except Exception` 首部加
   `isinstance(_e, FailFastError) → raise`:
   · HandlesExpr[malloc 的 null 回退分支]、HandlesExprBuiltin[strlen]、HandlesExprCall[strlen]
   (其余异常维持原行为,本轮不扩大 fatal 面。)
   另有 HandlesFunctions / HandlesImports 的同类 handler 前几轮已具备 FFE 重抛,本次未重复改。

验证:13 个改动文件 `py_compile` 全部 EXIT=0;冒烟 4 例通过——同名同类型复用同一对象、
异签名 → FailFastError(报文含两侧签名 i64/i32)、名字被非函数全局占用 → FailFastError、
`llvm.va_start` 幂等。另:`ir.Function(` 在 lib 内仅剩 `BaseGen._CreateOrReuseFuncDecl` 内部
这一处(豁免点);`DecoratorPass.py` 的 `module` 为外部传入(非 Gen 的模块)故其两处保持原样。
2026-09-30 09:53:41 +08:00
TermiNexus 4d14334bd2 fix(LLVMCG): 成员表对齐改为「前缀锚 vs 尾部锚」按类型可写入性打分,修 Generator.vp:164 误报
类别:a 编译器缺陷(TPC 侧;修正 1776dfa6 / b06dac1d 的过宽判据)

现象:翻译 TransPyV/App/lib/core/Gen/Generator.vp 时 :164 `SubTypeGenErr.Gen = self` 报
「指针隐式转换已禁止:不能把 %"47382dc8277c9d28.Generator"* 隐式转为
%"6fbe68e8fca81223.TypeSpecArena"*」—— 目标类型是槽 41 的 Specs,而 Gen 的真身在槽 0。

判据:TypeGenErr 继承 TypeGenBase(41 槽)且只有 1 个自有字段 ⇒ body 42 槽;其成员表只含
['Gen'] 一项(**残缺前缀**,真身在 body 头部)。上一轮"尾部对齐"的前置校验(前缀数 == 父类
struct 槽数)在本例**巧合成立**:42 - 1 == 41 ⇒ 该表被锚到槽 41(Specs)⇒ 取到错误槽类型。
同族上一例为 Generator.vp:136(TypeGenMixin,已由 1776dfa6 挡住,但同类巧合仍会复现)。

修法:不再用"长度关系 / 父类槽数"去断言表的对齐方式(那是猜),改为**比较两个候选锚**的
逐项得分(判据与建 body / 存储侧同一套 `_MemberSlotAccepts`):
  · 前缀锚 `_BaseNow()` —— 展平表 / 残缺前缀;
  · 尾部锚 `len(body) - len(表)` —— 仅自有字段(父类前缀未补齐,真身在 body 尾部)。
只有尾部锚**严格更高分**才采用它;同分/更低一律保持原行为(配对 → 下标法)⇒ 对既有正确
布局零行为变化,同时消除"巧合成立"的假阳性。命中尾部锚时每类留一次告警(报出两锚得分)。

验证:5 例冒烟全部通过 ——
A TypeGenErr(声明类型分别为通用指针 / Generator*,body 42 槽).Gen → 0(修前 41);
C AnnAssign 仅自有字段表 → target/annotation/value/simple = 8/9/10/11(尾部锚仍生效);
D ExceptHandler 重名表 + 联合类型 children → name = 9(布局配对仍生效);
E 自洽展平表 .value → 10(行为不变);FuncGen.py py_compile 通过。
2026-09-30 00:39:06 +08:00
TermiNexus 1776dfa60a fix(LLVMCG): 尾部对齐加「前缀数 == 父类 struct 槽数」前置校验,修正 Generator.vp:136 误报
类别:a 编译器缺陷(TPC 侧;修正 b06dac1d 引入的过宽判据)

现象:翻译 TransPyV/App/lib/core/Gen/Generator.vp 时 `SubTypeGen.Gen = self`(:136)报
「指针隐式转换已禁止(非基本类型):不能把 %"47382dc8277c9d28.Generator"* 隐式转为
%"6fbe68e8fca81223.TypeSpecArena"*」。

判据(事实链):
· SubTypeGen 静态类型 = rawptr[TypeGenMixin.TypeGenMixin],Gen 字段声明在基类 TypeGenBase;
  该 struct 41 槽:槽 0 = Generator*(Gen),槽 40 = TypeSpecArena*(Specs)。
· 抛错点 `_StoreWithCoerce` 用的是 **GEP 出来的槽类型**(TargetType = Ptr.type.pointee)
  ⇒ 报错里的 TypeSpecArena* 说明 "Gen 的偏移被算成了 40"(Specs 那一槽),而不是 0。
· 成因:b06dac1d 的「尾部对齐」在"成员表短于 body 字段区"时按 `len(elements) - len(表)`
  求前缀数;TypeGenMixin 的表只有 ['Gen'] 一项(且其声明类型已退化成通用指针 ⇒
  类型自校验被放行),于是被锚到 body 末尾 = 槽 40。

修法:尾部对齐加**必需的前缀校验**——前缀数必须等于**父类 struct 的槽数**(展平前缀就是
父类整份布局,含其继承链);父类 struct 不可得(跨模块未加载 / opaque)时**不介入**。
TypeGenMixin:40 ≠ len(TypeGenBase)=41 ⇒ 不介入,落回下标法得 0(Generator*,赋值合法)。
(正面场景仍生效:AnnAssign 表仅 4 项自有字段、body 12 槽、父类 Stmt 8 槽 ⇒ 8 == 8。)

验证:冒烟两例——反例 TypeGenMixin.Gen → 0(修前 40);正例 AnnAssign 仅自有字段表仍得
target/annotation/value/simple = 8/9/10/11;FuncGen py_compile 通过。
2026-09-29 23:58:09 +08:00
TermiNexus bd92dfdadf fix(LLVMCG): 成员表重名/联合类型字段不再打断布局配对,ExceptHandler.name 取到槽 9
类别:a 编译器缺陷(TPC 侧)

现象:翻译 TransPyV/App/lib/core/Gen/AsyncGenMixin.vp 时 `self._add_local(trans, hd.name)`
(:667)的第 2 个实参求值失败并终止编译。基址诊断给出的事实链:类 ExceptHandler
(%"831ce86a2fd82c0..." 即 astaux.vp)成员表 10 项 =
['parent','pool','lineno','col_offset','end_lineno','end_col_offset','children','children','type','name']
('children' 重复一次),struct body 10 槽 =
[i8*, AST*, MemManager*, i32×4, list*, AST*(type), i8*(name)],字段 'name' 计算偏移 = 10
⇒ 越界(正确槽 = 9)。

判据与修法(两条,均已写进代码注释与告警报文):
1) 布局配对断链:成员表里 'children' 的声明类型是**联合类型折叠后的通用指针**
   (`list[rawptr[AST]] | t.CPtr` ⇒ i8*),而 body 槽是具体指针(%"...list[...]"*)⇒
   原配对只比 `is` / `str()`,在这一条断链,其后所有条目不再消费槽位 ⇒ 目标字段退化为
   "base + 表内下标"('name' ⇒ 1 + 9 = 10)。修:两侧都是指针时复用存储/建 body 侧同一
   判据 `_MemberSlotAccepts`(通用指针 i8* 放行、具体指针按被指类型),标量仍走严格比较
   ——不引入新的过配对。修后 type = 8、name = 9 ✓。
2) 重名条目使长度判据失真:`_expected_count_pre = len(class_members) + vtable` 把重复项
   也算一槽 ⇒ 误判"body 不完整"而用**带重复项的表**重建 body(本模块布局比其它模块多一
   槽 ⇒ 跨模块 ABI 撕裂)。新增判据:表有重名**且** body 槽数恰等于「去重名数 + vtable」
   ⇒ 认定 body 已是权威布局(来自定义模块 stub),只报事实、不重建。
   另在"未被 body 配对命中"的回退路径上,把"成员表含重名条目 + 表项数/去重项数 + 该
   返回值取自回退路径可能失准"作为事实告警报出(每类一次)。

验证:冒烟复刻本现场(表含重名 'children' 且其类型为联合 i8*;body 10 槽)——逐字段
1..9 全部命中(parent=1 … children=7、type=8、name=9),无重名的自洽表行为不变;
FuncGen / StructGen 两文件 py_compile 通过。
2026-09-29 23:48:52 +08:00
TermiNexus 8f1ec5b715 fix(LLVMCG): llvmlite Function 创建事务化 + 不再吞 fail-fast,悬空名字不再复现
类别:a 编译器缺陷(TPC 侧)

现象:编译 StubMergerIR.vp 时 `_EmitExternalFuncDeclLlvm` 报「函数声明名字悬空占用:
'50741ad4b4b0934d.LLVMTypeCore.__init__' 已进 module.scope,但 module.globals 中无对应
对象」,且该 FailFastError 被 `_LoadModuleDeclarationsFromFile` 的节点级 `except Exception`
吞成一行 WARN「忽略节点 ClassDef LLVMTypeCore 异常」后 continue。

机制(判据已写进代码注释与报文字段):
1) llvmlite 的 `Function.__init__` 不是事务性的:名字在 `_set_name` 即进 module.scope,
   之后逐个建 Argument/ReturnValue 才做类型断言,最后才 add_global;中间任一步抛错都不
   回滚 scope ⇒ 留下「globals 查无、scope 已占用」的悬空名字,后续同名创建只能报裸
   DuplicatedNameError(或本仓的「名字悬空占用」),而真因——上一处被吞的创建失败——已丢失。
   修法:BaseGen 新增 `InstallFuncCreateGuard()`(随模块导入装一次),在**失败路径**上
   回滚名字(补 llvmlite 缺的那半截事务,模块状态可重试)并抛 FailFastError,报文含
   函数名 / 本次签名 / 原始异常 / 创建调用点(栈上首个非 llvmlite、非包装函数的帧);
   `DuplicatedNameError` 原样上抛——那是各调用点有意处理的正常分支(如「已存在则复用」)。
2) 首建失败与该 FailFastError 被 5 处兜底吞掉,使真因退化成一行 WARN:
   · HandlesImports._LoadModuleDeclarationsFromFile 节点级 except(本现场的直接来源)
   · HandlesImports stub 声明加载循环的两层 except
   · HandlesImports._TryLoadFuncDeclsFromOutputStub 的裸 `except Exception: pass`
   · HandlesFunctions._replace_function_decl 的 `except Exception: return old_func`
     (此处 old_func 必为 None ⇒ 调用点把 None 当函数声明用)
   修法:一律 `except FailFastError: raise`;其余异常维持原有口径(严格模式上抛 / 其余告警)。

验证:4 例冒烟(非法签名 → FailFastError 且报文含调用点;名字已回滚 ⇒ 同名可重试成功;
重复创建仍是 DuplicatedNameError 且旧名未被回滚;合法创建零副作用)全部通过;
BaseGen / HandlesImports / HandlesFunctions 三文件 py_compile 通过。
2026-09-29 23:31:57 +08:00
TermiNexus b06dac1d6b fix(LLVMCG): 字段偏移改用「冻结表有效性锚点 + 尾部对齐」,AnnAssign.value 不再读到 i32
类别:a 编译器缺陷(TPC 侧)

现象:编译 TranslatorGenMixin.vp 时 fail-fast —— getelementptr(GEP) 的指针操作数不是
指针类型:操作数类型=i32、操作数名='value.9'、GEP 名='vtable_slot_AST'。即
`aa.value.kind()`(TranslatorGenMixin.vp:788)的接收者读成了整数:AnnAssign.value 落到
槽 3(lineno, i32),随后虚分派取 vtable 时把 i32 当指针用。

根因(两条独立判据,均写进代码注释与告警报文):
1) 冻结表陈旧:StructGen 建 body 时把「成员名→槽位」挂在 struct 实例上,而 body 之后会被
   「不完整即清除」(StructGen 预检 existing_st.elements = None)与 stub 加载
   (HandlesImports._TryLoadStructFromStub 的 set_body)清空后重设,重设路径不更新该表
   ⇒ 陈旧表按旧布局取槽(AnnAssign 在「自有 4 字段 + vtable」时冻结 {'value': 3},body
   被重设为 12 槽正确布局后仍取槽 3)。修:建表时同时记下 body 对象身份
   (_tpc_member_body,set_body 每次赋新 tuple),查询侧用 `is` 判同一性,失效即不再采用。
2) 未展平成员表:表只含自有字段(父类前缀未补齐)时,「base(0/1) + 表内下标」会把整类字段
   整体前移(target/annotation 落到指针槽 ⇒ 类型巧合、静默读错对象;value 落到 i32 槽 ⇒
   本次崩溃)。修:表短于 body 字段区时按**尾部对齐**取槽(展平规则 = 父类前缀 + 自有字段
   ⇒ 自有字段恒为 body 尾部 len(表) 项),并用 _MemberSlotAccepts 逐项类型比对自校验;
   表自洽(长度 == 字段区)时本路径不介入 ⇒ 对既有正确行为零变化;REnum 是 max-variant
   镜像布局,明确排除。命中时每类留一次告警,报出表长/字段区/取槽结果。

验证:8 例冒烟(冻结表新鲜;陈旧×展平表 / ×自有表;无冻结×自有表 / ×展平表;无 vtable;
整类逐字段 = 8/9/10/11;尾部类型不符时落回旧路径)全部通过;两文件 py_compile 通过。
2026-09-29 23:18:37 +08:00
TermiNexus 90d81df4f8 fix(tpc): 自指针指向 opaque 同名实例时归一为具化实例;GEP 边界拦下 opaque pointee
类别 a(编译器缺陷):llvmlite 的 typed pointer GEP 必须在编译期算出元素类型——
`GEPInstr` → `ptr.type.gep(i)` → `IdentifiedStructType.gep` → `self.elements[i]`,
对 **opaque**(elements is None)pointee 抛裸 TypeError('NoneType' object is not
subscriptable)。现场:includes/ast/astaux.vp `Keyword.__init__` 的
`self.arg = _copy_str(pool, arg)`,经 HandlesAssign._HandleAttributeStoreLlvm:869
的 GEP 崩溃。成因:同名 struct 存在**两份实例**——函数签名/变量里那份仍 opaque,
类键 `Gen.structs[ClassName]` 上那份已具化;偏移取自类键那份、GEP 却用 self 那份。

- HandlesAssign._HandleAttributeStoreLlvm:GEP 前把 self 指针 bitcast 到**有 body 的
  那份实例**(仅当 self 的 pointee opaque 且类键实例已具化),并把该实例作为
  StructHint 传给 _get_member_offset —— 偏移与 GEP 必须同源(与属性读取路径
  _gep_and_Load_member 的 415916b3 同口径);max_offset 的取值也补上 elements 为
  None 的判据(原实现会对 opaque 实例 len() 触发断言)。
- BaseGen.RequirePointerOperand 新增 RequireConcretePointee(GEP 专用):基址指向
  opaque 具名结构体时 fail-fast,报文给出基址类型、结构体名、GEP 名与当前节点位置,
  不再落到 llvmlite 的裸 TypeError(typed pointer 下"GEP 指向 opaque 结构体"无任何
  合法场景:元素类型必须编译期可知)。

本地冒烟:opaque pointee 被拦下(FailFastError,而非 llvmlite 的 TypeError);
具化 pointee 正常通过。
2026-09-29 22:10:11 +08:00
TermiNexus 4734a54aed fix(tpc): 函数声明创建前自检签名与半注册名字,DuplicatedNameError 不再裸抛
类别 a(编译器缺陷):llvmlite 的 Function.__init__ 顺序是
  NamedValue.__init__ → _set_name → module.scope.register(name)      【名字占用】
  → 逐形参 Argument(...) / ReturnValue(...)(内部 assert isinstance(typ, types.Type))
  → module.add_global(self)                                        【进入 globals】
中间任一步抛错,Python 都不会回滚 scope ⇒ 留下"半注册"名字:module.globals 查无、
module.scope 已占用。而 _EmitFunctionForwardDeclLlvm 的 `except Exception` 在非严格
模式**静默 pass**(严格模式也只 LogWarning)⇒ 真因(返回类型或形参不是 llvmlite
Type,典型 None=注解未解析出来)被吞掉,直到定义阶段 _EmitFunctionLlvm →
_CreateOrReuseFuncDecl 才以 DuplicatedNameError 暴露,报文只有名字、没有两侧信息
(现场:972cf2b7756765d5.ImportsGenMixin._index_imported_module,本次构建 723s 后失败)。

- BaseGen._CreateOrReuseFuncDecl(唯一幂等入口)新增两条**创建前**自检:
  ① 签名自检:return_type 与 args 逐项必须是 ir.Type,否则在名字注册之前就报出
     "哪个位置 / 什么类型 / 完整签名 / 当前节点位置";
  ② 悬空名字自检:name 已进 module.scope 而 module.globals 无对象 ⇒ 直接报出
     "此前同名创建中途失败且异常被吞",不再落到裸 DuplicatedNameError。
- HandlesFunctions._EmitFunctionForwardDeclLlvm:该 except 不再吞非 FailFastError
  异常(原:非严格静默 pass / 严格仅 warning)⇒ 改抛 FailFastError,附函数名、
  本次签名、原始异常类型与消息、当前节点位置。

本地冒烟(5 例全过):合法签名建成功;None 返回类型拦下且不留半注册名字;None 形参
拦下;悬空名字报状态事实;同签名二次调用仍幂等复用。
2026-09-29 20:45:26 +08:00
TermiNexus f28ff1b414 fix(tpc): 字段偏移改为「布局真源优先」,成员表漂移不再整体平移
类别 a(编译器缺陷):_get_member_offset 返回 `base + 成员表下标`,但 class_members
是**可变**表(继承展平会在父类就绪后前置前缀、跨模块可按需重载/重跑),而 struct
body 一旦 set_body 即**永久冻结** ⇒ 表与布局的序位一旦漂移,整类字段偏移整体平移。
现场(IR 实证):Converter 模块读 `Alias.name` 落在 struct 槽 9(= asname),而定义
模块 Alias.__init__ 写入槽 8;`import t` 无 as 子句 ⇒ asname=NULL ⇒ TransPyV StubGen
误报「import 别名 name 为空(_copy_str 返回 None)」并终止 includes/stdint.vp 的 vpi
生成。同族旧现场:ast.Constant.lineno 取到 pool 槽(已由成员偏移同源修复过)。

- StructGen._generate_structs:set_body 时把「成员名 → 槽位」映射冻结到 struct
  实例上(_tpc_member_slots / _tpc_member_base)。用**实例属性**而非全局表:同一
  类名可能存在不止一个 struct 实例,偏移必须与被 GEP 的那一份布局同源;仅当
  member_types 恰为 `[vtable?] + 全部成员` 时记录(位域类折叠为单一 storage 槽,
  其字段位置另由 class_member_bitoffsets 表达;stub 已置 body 的情形不记录)。
- FuncGen._get_member_offset:新增两条「只看布局」的路径,优先于原下标法——
  ① 冻结映射命中即返回;
  ② 用**类型**把成员表逐项配对到 elements(类型不匹配、重名的条目视为方法/占位/
     漂移项,不占槽位),配对中类型相等的槽位即字段真实槽位。
  表与布局自洽时两者给出与旧下标法逐字相同的结果;都覆盖不到才落回下标法(对既有
  正确行为零变化)。另在「配对结果 ≠ 下标法」时每类每字段告警一次,报出成员表项数
  与 struct 槽数,把漂移来源变成可见事实。

本地冒烟(三类,均通过):自洽表 8 / 漂移表(类型不匹配)8(旧下标法 9)/
全同型漂移且命中冻结映射 8。
2026-09-29 19:57:57 +08:00
TermiNexus d305d3369b fix(tpc): IR 边界补「指针操作数类型守卫」,非指针值不再抛裸 AttributeError
类别 a(编译器缺陷/诊断缺失):llvmlite 的 IRBuilder.gep 不做任何类型校验,
GEPInstr.__init__ 直接读 ptr.type.is_opaque;LoadInstr 同族亦同(instructions.py
:440/:558)。上游一旦把非指针值(典型 i32)当指针传入,抛出的就是裸
`AttributeError: 'IntType' object has no attribute 'is_opaque'` —— 报文里既没有
指令名、没有操作数、也没有当前语句,无法定位发射点(现场:Phase2 翻译
TransPyV/App/lib/core/Gen/TranslatorGenMixin.vp 时只带 713/787/788 三条语句位置)。

- BaseGen:新增 RequirePointerOperand(IR 边界先行判定,不做兜底转换)与
  _GuardIrBuilder(无条件覆盖 gep/load/store,合法 IR 与原生 ir.IRBuilder 一致);
  _HotspotSiteBuilder 改继承 _GuardIrBuilder;_NewIrBuilder 未启用热点统计时
  由原生 ir.IRBuilder 改为返回 _GuardIrBuilder。
- MemoryOps:_EmitLoad/_EmitStore 的 volatile 分支直接构造 LoadInstr/StoreInstr,
  绕过了 builder.load/store 的类型守卫,补同一判定(非指针 ptr 的 store 会静默
  生成非法 IR 文本)。

报文含:指令名、操作数类型、操作数名、GEP 的 name=(发射点标识)+ 当前节点位置。
2026-09-29 19:24:27 +08:00
TermiNexus 415916b3dd fix(tpc): 成员偏移以「实际 GEP 用的 struct 实例」为准,并报出双实例不一致
类别:a(编译器缺陷 —— 偏移与 GEP 不同源,整类字段偏移平移)

现场:TransPyV/App/lib/core/Gen/Class/ClassGenGeneric.vp:346
    return ast.Constant(pool, cnst.const_kind, cnst.int_val, cnst.float_val,
                        cnst.str_val, cnst.lineno, cnst.col_offset)
报「调用参数类型不匹配:指针类型 %"c772afbbfd495aca.MemManager"* 不能隐式转换为
整数参数 i32」,逐项列表显示实参 [6](= cnst.lineno)求值成 MemManager*。

取证(TransPyV/temp/5df907d3a02a0bac.stub.ll:21/:23):
  %"…Constant" = { [0]i8*(vtable) [1]AST*(parent) [2]MemManager*(pool)
                   [3]i32(lineno) [4]i32(col_offset) … }
  __new__(Constant*, MemManager*, i32, i64, double, i8*, i32, i32)
成员表扁平序为 [parent, pool, lineno, col_offset, …] ⇒ base=1 时 lineno 偏移 3
(正确),base=0 时偏移 2 = pool,正是报出的那个指针类型;而 col_offset 偏移
3 落到 lineno 仍是 i32,所以**只有 [6] 报错** —— 与现象逐位吻合。

根因:FuncGen._member_base 的布局取自**类键** self.structs[cls],而 GEP 用的是
**取值处** ObjVal.type.pointee 那一份实例。同名类双注册(或一份已从 stub 加载完整、
另一份仍 opaque)时两份布局不同 ⇒ 用一份算偏移、用另一份读,整类字段偏移平移。
_class_has_vtable 对跨模块 AST 类恒为 False(其 docstring 已记录该已知静默路径),
base 只能靠 delta + 类型校准救回,两实例不同源时即失守。

改动:
1. FuncGen._member_base(ClassName, StructHint=None):hint 为**同类**(短名相同)的
   struct 实例时,以它计算 delta / 首元素 vtable 判据 / 类型校准;当它与类键上的
   实例不是同一对象时,每类一次 vlog().warning 报出两侧名字与元素数(opaque 或
   元素数),使"双注册"这一事实不再静默。
2. FuncGen._get_member_offset(..., StructHint=None) → _BaseNow() 透传(base 仍现算,
   不缓存)。
3. HandlesExprAttr._handle_attr_name:把 ObjVal.type.pointee 作为 hint 传入。

不传 hint 时行为与改前逐字一致;其余偏移消费点(字段写入 / 构造初始化等)本轮未动。
2026-09-29 18:18:49 +08:00
TermiNexus aa1b536517 perf(tpc): _generate_structs 去掉两处"纯 no-op 重建",不改任何判定语义
类别:b(机制漏缓存)—— 都是"结果等价却重算"的浪费,不是判定逻辑改动。

证据(PROF\main_prof_72472.prof 的 `_generate_structs` 子调用归属,self=39.07s/8,493 次):
  isinstance 6.195s(6,249 万次)· `HandlesBase.__get__` 3.596s · len 2.892s
  `_TypedPointerType.__init__` 2.600s(375 万次)· `_patched_new` 2.030s · dict.get 2.555s
  `_MemberSlotAccepts` 2.437s · SymbolTable.lookup 2.105s · rsplit 1.123s

改动(均为**恒等变换**,任意中断/交错下语义不变,不引入失效判定):
1. Step 3 成员类型重绑:新增 `if pointee is not struct:` / `if member_type is not struct:`
   短路。原实现对"pointee 本来就是该 struct 实例"的成员也**现建一个 ir.PointerType
   再覆盖写**——llvmlite 类型是值语义(`_TypedPointerType.__eq__` 按 pointee 比较、
   打印只取决于结构),覆盖写是纯 no-op。该分支每次调用遍历全部类的全部成员
   ⇒ 这是 375 万次指针构造(约占本函数 9s,含 _patched_new/_patched_init/_IsBadPointee)
   的主源。
2. T* 占位扫描改为 `_StructBodyHasPlaceholderT`(按 body 对象记忆):Step 4 的
   "全类×全元素"扫描与 set_body 前的复核扫描,每次调用都全量重扫,是本函数
   6,249 万次 isinstance 与 498 万次 rsplit 的主源。判据精确性:该扫描是
   `st.elements` 的纯函数,而 `elements` 在 llvmlite 里**整体替换**(set_body 赋新
   tuple、作废置 None,从不原地改元素)⇒ 用 `elements` 的对象身份当版本号即可精确
   判定"body 未变";缓存条目强引用 struct 与 elements,二者存活 ⇒ id 不会被回收
   复用 ⇒ 不会误判命中。
3. vtable 槽 `i8*` 改用模块级共享实例 `_I8_PTR`(原来每类每次现建)。

**刻意不做**:不加"全量状态签名 early-out"。理由(写在此处备查):签名只能覆盖
"每类成员个数"这类可 O(类数) 取得的量,覆盖不到"成员类型原地替换";而那次替换正是
本函数 Step 3 自己做的 ⇒ 一旦某轮在中途抛异常,签名与上一次成功轮相同,early-out
会跳过**必要的**重建。要做到完备就得给约 25 处 `class_members` 变更点埋版本号,
收益不确定、风险实在,故不做。

验证:py 层 import + GetDiagnostics 干净;未跑编译器。
2026-09-29 16:37:03 +08:00
TermiNexus ac0d0f2cc8 perf(tpc): _MemberSlotAccepts 身份短路 + _generate_structs 热路径局部化(profile 第二梯队)
依据 PROF/main_prof_10152.prof(tottime 2288.9s / 72 文件顺序翻译):

  _generate_structs        自身 268.6s   cum 1893.2s   15762 基本/35240 总调用
    └ 子调用:_TryLoadStructFromStub 1589.5s×418k、_MemberSlotAccepts 61.1s×54.2M
              isinstance 39.7s×300M、PointerType 构造 68.7s×15.9M、
              SymbolTable.lookup 16.7s×15.9M、__len__ 6.8s×15.9M

本次两处(均等价改写):
1. StructGen._MemberSlotAccepts:在 `Declared == Slot or str(Declared) == str(Slot)`
   之前加**身份短路** `Declared is Slot`。该函数被 _generate_structs 调 54.2M 次,
   其中 llvmlite `Type.__eq__` 合计 ~30s(types.py:178/303/550/720)+
   `str()` ~3.3s;同一对象(body 与 class_members 引用同一类型对象)是其常见形态。
2. StructGen._generate_structs:把 `self.SymbolTable` / `.lookup` 提升为函数级局部
   (真值语义逐字保持:SymbolTable 定义了 __len__,故先取 `bool(_symtab)`),
   消掉逐类重复的属性查找与 15.9M 次 `__len__`。

说明:本提交只做等价微优化;本函数的大头(35,240 次全量 O(类数×成员数) 扫描、
429k 次结构体 stub 加载、1407 次泛型特化整类发射)需要结构性改动,另行评估。
2026-09-29 14:41:48 +08:00
TermiNexus 4fc067e3bf perf(tpc): 按 profile 消掉两处主热点(stub 索引每 miss 重扫目录 / vtable 每类全扫函数)
依据 PROF/main_prof_10152.prof(main_time.txt: 失败 2362.46s,72 个文件全部走
顺序 _translate_file,tottime 合计 2288.9s):

  tottime      calls   函数
  491.7s      723782   nt._path_isdir            ← 总时长 21%
  268.6s       15762   StructGen._generate_structs
  182.9s      479819   nt.listdir                ← 8%
  87.2s    657640754   builtins.isinstance
  78.6s     41514263   ntpath.join
  75.9s     83047878   ntpath.splitroot
  75.7s         1631   StructGen._create_Vtable_globals   (46ms/次)
  66.6s       238007   HandlesImports._build_stub_index
  63.0s    317868145   str.startswith
  38.4s    175751378   str.endswith
  ...
  ── 仅 isdir + listdir + join/splitroot/endswith ≈ 850s ≈ 总时长的 37%

一、_build_stub_index 的零 syscall 快路径(HandlesImports + Phase2Translator)
    它由 _TryLoadStructFromStub 的**每次索引 miss** 调用(238k 次),每次都要
    2× os.path.isdir + 2× os.listdir + 全量 endswith/join 重拼文件集合 ——
    这正是上面 isdir 723k / listdir 479k 的主要来源。
    改为:写入方显式置脏(ImportHandle.MarkStubSetDirty),未置脏时直接返回。
    置脏点:① 每个文件翻译入口(_translate_file,顺序与 worker 模式皆按文件粒度
    覆盖)② 写 temp/<sha1>.stub.ll 之后 ③ 注册 include 写 output/<sha1>.stub.ll
    之后。索引建完即清脏。语义:增量重扫、output 覆盖 temp 全部不变,只是把
    "集合是否变化"由目录扫描改为写入方免费告知(编译进程内 stub 只增不改)。

二、_create_Vtable_globals 的 O(类数 × 函数数) → O(函数数)
    原实现对每个候选类都遍历一遍 self.functions 找 "{ClassName}." 前缀且非
    declare 的函数;改为一次性预计算"非 declare 函数名的全部点分前缀"集合,
    每类只做一次集合查询。判据等价(startswith(prefix) ⟺ prefix 在点分前缀集)。

重编后请再抓一次 profile 复核收益;下一梯队热点是 _generate_structs
(268.6s tottime / 35k 次调用)与 _parse_llvm_type(3.5M 次调用)。
2026-09-29 14:36:57 +08:00
TermiNexus f4c8c48748 fix(tpc): 函数声明创建全部收口到唯一幂等入口,消除 DuplicatedNameError 裸抛
类别 a(编译器缺陷)+ b(机制漏豁免)。报错:翻译 lib\core\Gen\Expr\ExprGenName.vp
失败,报文只有名字 dddc595175f00c66.ExprGenAttr.find_global_in_module
⇒ llvmlite DuplicatedNameError(NameScope.register 对 GlobalValue
deduplicate_name=False,同名 global 再建必抛,__str__ 只回名字)。

根因(已取证,非猜测):
- llvmlite Module 里 `scope`(NameScope,注册/去重)与 `globals`(OrderedDict)
  是两套结构;`add_global` 只在构造时写入 ===> 「名字已在 scope._useset、
  但 globals 查不到」这一状态一旦出现,任何"先查 globals 再建"的守卫都会失效。
- 该状态由**清理不完整**制造:删旧名时只删"对象真名 existing_func.name/old_name",
  而字典键/目标名(func_name / MangledName)仍留在 scope._useset。
  别名注册(Gen.functions[短名] = 名为全名的对象)恰好让二者不同名。
- 另有若干创建点只看 Gen.functions、完全不看 module.globals(裸建 ir.Function),
  只要 module.globals 已有同名声明就裸抛 DuplicatedNameError。

本次收口(函数声明创建的唯一入口 = BaseGen._CreateOrReuseFuncDecl):
1. BaseGen._CreateOrReuseFuncDecl:新增"名字被非函数 global 占住"分支,把冲突
   双方(类型 + repr)报出来,替代原 llvmlite 只给名字的裸抛。
2. 清理不完整处补全为「同时清 真名 + 目标名」:
   - HandlesImports.py 模板 declare 装载(clearing func_name 之外还清 existing_func.name)
   - HandlesFunctions._replace_function_decl(clearing {old_name, MangledName})
   - 其 except 对 FailFastError 改为上抛(不再回退成复用旧声明)
3. 裸建 ir.Function 的创建点改走唯一入口(module.globals 同名则复用、
   签名冲突/非函数冲突则 fail-fast):
   - HandlesExprCall._HandleStaticMethodCallLlvm({sha1}.Class.Method)  ← 本现场形态
   - HandlesExprCall._HandleMethodCallLlvm 的 class_sha1 回退分支
   - HandlesExprCall._try_declare_c_lib_func / HandlesExprBuiltin._try_declare_c_lib_func
   - HandlesAssign 模块级全局赋值处的函数声明
   - HandlesImports.__before_init__ 声明(原查 NewFuncName 却建 MangledName)
   - HandlesFunctions._EmitFunctionLlvm(补登记 module.globals 已有声明,
     使既有的签名修复路径生效,而不是裸建撞名)

行为等价性:上述各点原先"撞名"时必然抛 DuplicatedNameError(即必失败),
收口后要么复用同一符号、要么 fail-fast 报出两侧签名/冲突类型,无静默降级。
2026-09-29 13:50:44 +08:00
TermiNexus 25cda80995 fix(tpc): 补齐单/多参泛型类下标注解(list[str] → %"{模板sha1}.list[str]"*)
类别:a 编译器缺陷(机制缺失:两个注解解析器都只认双参定长 list[T, N])。

现场(前序报错):CallGenMixin.vp:149 调 ClassGenGeneric._specialize_generic_class
报「%\"650c1c3366d09e39.list[str]\"* 不能隐式转 i32」——实参侧正确、**形参侧
`type_args: list[str]*` 在符号表里成 i32**。同类:字段
`TypeGenBase.CTypedefNames: list[str] | t.CPtr` 在共享符号表里成 i8*(实测
temp/_shared_sym.json)。

真根因:单参泛型类下标在两处注解解析器里都无人处理,直接落到"未知类型"兜底:
  · TypeAnnotationResolver._from_node_subscript(IsListAnnotation 只认双参)
    → `return CTypeInfo()`(空 ⇒ i32 / 与 t.CPtr 合并成 i8*);
  · LLVMTypeMapper._resolve_subscript → warn + 'i32'。
而声明端 DeclarationGenerator._get_type_str 的泛型分支(L1393-1423)**一直是对的**
(`%"<模板sha1>.<特化名>"*`),三处规则分裂。

修复(一条规则源 + 两处消费 + 一处物化):
1. SymbolUtils 新增 ResolveGenericClassSpecName / IsGenericClassSubscriptBase ——
   与 DeclarationGenerator 泛型分支逐字同口径:
   spec = 基名 + '[' + ']['.join(ResolveGenericTypeArgStr(实参)) + ']';
   判据=该基名**有类定义**(符号表条目 IsStruct 或任一命名空间命中,实测 list
   条目 IsStruct=True,故等价覆盖声明端 non_struct_types ∪ _generic_class_names)。
   另加 GetStructSha1MapFromSymtab / GetTCAliasMapFromSymtab(从
   SymbolTable.translator 取 Phase2 已挂好的 _struct_sha1_map /
   _t_c_imported_names),供实参串跨模块类引用解析('<sha1>.AST')。
2. TypeAnnotationResolver._from_node_subscript 末尾(既有 rawptr/CPtr/CArray/
   list[T,N]/tuple 分支**之后**)接入:产出
   CTypeInfo(BaseType=CStruct(spec), IsStruct=True, Name=spec)(PtrCount 保持 0,
   与本文件既有结构体 promotion 同口径:`X | t.CPtr` 取 max→1 层、
   `rawptr[X]` +1→1 层,均与 declare 侧一致)。
3. LLVMTypeMapper._resolve_subscript 同样位置接入:产出 `%struct.<spec>*`
   (与 _resolve_name / _resolve_attribute 的类引用同一表示)。
4. BaseGen._get_or_create_struct:特化名(含 '[')先按**基名**查
   _struct_sha1_map 取模板 sha1 再拼 IR 名 —— 否则会用**使用方模块** sha1 拼出
   '<本模块sha1>.list[str]',与 declare/define 端 '<模板sha1>.list[str]' 分裂。

自测(vast AST):list[str]→'list[str]'、dict[str,int]→'dict[str][int]'、
list[rawptr[AST]]→'list[AST*]'、rawptr[int]/tuple[…]→None(仍由既有分支处理)。
2026-09-29 13:17:36 +08:00
TermiNexus a6c9003a19 fix(tpc): struct body 解冻改用 llvmlite 公开属性 elements(原 _elements 是空操作)
类别:a 编译器缺陷(机制漏豁免:错误的"解冻"字段名使所有 body 重建路径失效)。

现场(连续多轮复现):TransPyV Generator.vp:200
`self.ExprGenFstr = SubExprGenFstr` 报「指针隐式转换已禁止(层数不同):
%"a7b68c79f39cb4d3.ExprGenFstr"* → i8**」,[成员槽来源] 显示
class_members[Generator] 下标 11 声明槽类型**正确**,而布局
'47382dc8277c9d28.Generator' 的 elements[11]=i8**。

取证(已核对全部产物与共享符号表,排除"来源侧退化"):
- TransPyV/output/*.stub.ll 与 temp/47382dc8277c9d28.stub.ll 里
  %"47382dc8277c9d28.Generator" 的 slot11 全部是 %"a7b68c79f39cb4d3.ExprGenFstr"*,
  **没有任何 stub 含 i8**;
- 共享符号表(temp/_shared_sym.json → symbol_table_bytes)里
  Generator.Members['ExprGenFstr'] = CTypeInfo(BaseType=CStruct('ExprGenFstr'),
  PtrCount=1, IsStruct=True),也**正确**。

真根因:llvmlite 本版本 IdentifiedStructType 的字段是**公开属性 `elements`**
(types.py: `self.elements = None`;`is_opaque` = `elements is None`;
`set_body` 仅在 `elements is not None` 时抛 "already defined")。而本仓所有
"清除 body 以便重建"的路径都写 `st._elements = None`(不存在的属性)⇒ **空操作** ⇒
⇒ `if struct_type.elements is None` 恒假 ⇒ set_body 永不执行 ⇒ 早期冻结的错误布局
(如 i8**)无法被任何后续权威重建覆盖:
  · StructGen Step 4 逐元素校验已判定不一致(_slot_types_ok=False),清空失败 ⇒
    既不能 skip 也不能重建 ⇒ 错布局被静默保留;
  · HandlesClassDef 的本地定义权威重建点(class_members 已重建为正确类型后)
    同样无法刷新 body。
两处叠加正是"class_members 正确、布局却是 i8**"的稳定现象,也解释了此前
TypeGenCDef(槽3) / ExprGenFstr(槽11) 在不同构建里交替出现(取决于谁先被冻结)。

修复:四处解冻改为写公开属性 `elements`:
  · StructGen.py Step 4 前置校验失败后的清空;
  · StructGen.py sha1 全名 key 的 struct 清空;
  · StructGen.py T* 占位清空;
  · HandlesClassDef.py @t.NoVTable 本地定义点的清空。
清空后 is_opaque 恢复为真 ⇒ _TryLoadStructFromStub(declare 侧权威 body)或
class_members 重建可真正 set_body。
2026-09-29 13:13:17 +08:00
TermiNexus 9d8aedec4a fix(tpc): 函数声明创建收口到幂等入口 _CreateOrReuseFuncDecl;清理 DDEBUG 探针
类别:a 编译器缺陷(根因修复)+ 调试探针移除。

现场:翻译 CallGenFd.vp 时抛 llvmlite.ir._utils.DuplicatedNameError:
'5d84ea579d83ef73.CallGenFd._translate_fakeduck_call'(llvmlite 的
NameScope 按名字去重,GlobalValue.deduplicate_name=False ⇒ 同名 global
建第二次必抛,且报文只给名字、不给两侧签名)。

根因:BaseGen._get_or_declare_function 建完 ir.Function 后**只加入
module.globals**(llvmlite 自动)而**未登记 Gen.functions**;而
HandlesFunctions._EmitFunctionLlvm 的 need_new 判据只看 Gen.functions
(`MangledName not in Gen.functions`)⇒ 同一名字再建一次 → DuplicatedNameError;
该处 except 又把它吞掉(非 strict 模式只 LogWarning)→ 函数始终没进
Gen.functions → 后续其它创建点(FuncGen._get_or_declare_func /
get_or_declare_c_func,同样只查 self.functions)再建 → 异常从别处逃逸到
Phase2Translator。

修复:
1. BaseGen 新增 _CreateOrReuseFuncDecl(name, func_type) —— 函数声明创建的
   唯一幂等入口:module.globals 已有同名函数且**函数类型结构串相同**则复用
   (并补登记 Gen.functions;FunctionType 无 __eq__,只能比 str);
   类型不同则 fail-fast 报出两侧签名(declare/define 分裂的真因),
   绝不静默复用错签名(调用点会按错签名传参)。
2. _get_or_declare_function 改走该入口(顺带补登记 Gen.functions);
   FuncGen._get_or_declare_func / get_or_declare_c_func 改走该入口;
   HandlesFunctions 定义路径改走该入口,且该处 except 对 FailFastError
   直接 re-raise(不再吞)。
3. 清理遗留调试探针(含 sys.exit(1)):BaseGen 三处 [DDEBUG]
   (_get_or_declare_function 双点 / _mangle_func_name 前导点与结果双点)、
   HandlesExprAsm 两处 [DDEBUG_ASM]、HandlesExprCall 一处
   [DDEBUG] _HandleClosureCallLlvm。
2026-09-29 13:00:11 +08:00
TermiNexus 7167703ca7 fix(tpc): 降级判据改用「注解形态 + 解析结果形状」,放在字段类型唯一漏斗
上一版拦截失效的原因(实测):`class_member_annotations` 全仓**只被声明与读取、从未被
写入**(Step 2 一直是空转),故依赖它的 `_HasDegradedMemberTypes` 恒为 False。改法:

1) SymbolUtils 新增两个**形状判据**(纯判定,不做类型解析):
   - IsI8DoublePtrType(T):是否 i8 系两层指针(`i8**`)= `T | t.CPtr` 在 T 类型丢失后
     退化成的形状;
   - IsClassRefPtrAnnotation(Ann):注解是否「指针包装的类引用」(rawptr[Mod.Class] /
     Mod.Class | t.CPtr;内层为非 t/c 模块的 Attribute 或首字母大写裸名)。
2) HandlesBase.CollectAnnAssignMember(字段类型的**唯一漏斗**,覆盖本地类 /
   跨模块 .vpi / 嵌套类三条写入链)新增硬判据:注解是类引用指针 **且** 解析结果正是
   `i8**` ⇒ 必属类型信息丢失,当场 fail-fast,并打出 CTypeInfo 全字段(BaseType/
   PtrCount/IsStruct/IsTypedef/IsEnum/Name)与内层名的符号表条目标志。此前的
   `_CheckPtrAnnotationKeepsClass` 只看"void 基座且无类身份",而现场这条链**带 Name/
   IsTypedef**(Attribute 分支的 typedef 出口 → BaseType=CVoid 但 Name=类名),正好漏过。
3) StructGen 删除依赖那张空表的 `_HasDegradedMemberTypes` / `_IsI8DoublePtr` /
   `_IsClassRefPtrAnnotation` 及 set_body 前的拦截(改由漏斗处兜住,不留死代码)。

现场:Generator.vp:200 `self.ExprGenFstr = SubExprGenFstr`
  → 布局槽 elements[11]=i8** 而声明 %"a7b68c79f39cb4d3.ExprGenFstr"*
2026-09-29 12:04:20 +08:00
TermiNexus 66de269ebf fix(tpc): 撤回失效的"事后修复",改为 set_body 前拦截降级布局(不冻结)
a 编译器缺陷。实测证据(07ea8e2f 的探针回执):对本版本 llvmlite,
`st._elements = None` 之后 `st.set_body(*elems)` 仍以
`RuntimeError: <name> is already defined` 拒绝 —— 说明 **`_elements` 不是 set_body 的
判定存储**,即"清 _elements 以便重载/重设 body"这一全仓惯用手段在本版本是**空操作**
(结构体 body 一经设定即不可改)。因此:

  - 撤回 7fb0e498 加的 `_SyncStructBodyTypes`(使用点就地修复)及其在
    `_get_member_offset` 的调用 —— 它在本版本永不可能成功,只会把编译卡死;
  - 改为**事前拦截**:`_generate_structs` 在 `set_body` 前调用
    `_HasDegradedMemberTypes`,若本次布局里存在「注解是类引用(`Mod.Class` /
    首字母大写裸名,排除 t/c 模块)**却**解析成 `i8**`」的成员,说明该 pass 类型尚未
    就绪 ⇒ **不设 body、保持 opaque**,等类型就绪的 pass 再设。这样从根上避免
    "降级布局被永久冻结"(现场:Generator 的槽 i8** 而声明 %"…ExprGenFstr"*,
    赋值时报「层数不同」)。始终不就绪则在使用点响亮失败(opaqueness / 层数不同),
    不再静默产出错布局。
  - 新增 `_IsI8DoublePtr` / `_IsClassRefPtrAnnotation` 两个判据助手(保守:只在
    "指针包装 + 类引用内层 + i8 系两层指针"三条同时成立时判降级)。
2026-09-29 11:56:08 +08:00
TermiNexus 07ea8e2fd9 fix(tpc): 布局校验/修复排除 REnum 镜像布局 + set_body 失败带回滚与上下文
a 编译器缺陷(承接 b8ed33a2 / 7fb0e498):REnum 的 struct body 是 **max-variant
镜像布局** —— class_members 里的名字是*变体*而非字段,变体嵌套结构体也必须逐字
镜像主结构体。此前两处按"声明类型 vs 槽类型"的校验/修复会把这类布局当成错位:
  - Step 4 前置校验可能据此清 _elements 重建;
  - _SyncStructBodyTypes 可能据此改写布局(跨模块 GEP 偏移随即与定义端失配)。
新增 _IsRenumLayoutClass(主 REnum 自身 / `<REnum>_<Variant>`,判据走 SymbolTable 的
IsRenum)并在两处排除。

另外:就地修复的 `_elements = None` + `set_body` 一旦被 llvmlite 拒绝
(`<name> is already defined`,现场 fd4cd0ec583f42b9.Generator),原来会抛出这句
毫无上下文的裸 RuntimeError。现改为:捕获后**直接写回原 _elements 回滚**,再抛
FailFastError 带上类名 / struct / 原异常 / 元素数前后值,既不留中间状态也不再含糊。
2026-09-29 11:34:17 +08:00
TermiNexus 7fb0e49854 fix(tpc): 成员偏移查询点就地修复错位 struct body(修正晚于冻结的时序)
a 编译器缺陷(承接 b8ed33a2):Step 4 的前置校验只在**当次** _generate_structs 生效,
漏掉"class_members 被 .vpi/stub 修正在后、struct body 冻结在前"的时序 —— 于是
一次只修好一部分槽(实测:第 3 槽修好、第 11 槽仍错),错误逐个前移。

证据链(成员槽来源明细 + 全盘 grep):
  class_members[Generator] 项数=47 base=0 下标=11
      声明槽类型=%"a7b68c79f39cb4d3.ExprGenFstr"*
  布局 struct name='fd4cd0ec583f42b9.Generator' 元素数=47 elements[11]=i8**
  且**没有任何** stub 文本里含 i8** ⇒ body 是从"当时已降级的 class_members"冻住的,
  后来重载只修 class_members,改不了已冻结的 body(llvmlite set_body 只能设一次)。

修法:新增 StructGen._SyncStructBodyTypes —— 在**使用点**(_get_member_offset,
即任何 GEP 之前、成员表已在上方补齐/重载之后)按 class_members 逐槽比对,
只把**存储时必被拒绝**的槽(判据同 _MemberSlotAccepts)改回声明类型,其余元素
(含通用指针 i8* 槽)原样保留;_elements 置空后 set_body 重设(命名类型对象不变,
故已发出的 getelementptr %"name"* 不受影响)。同名类 sha1 冲突路径
(_ResolvedClass != ClassName)跳过,避免用别的同名类成员表写坏布局。
2026-09-29 00:19:06 +08:00
TermiNexus b8ed33a200 fix(tpc): struct body 冻结前逐元素校验类型一致性(错位 body 不再被当"完整")
a 编译器缺陷:_generate_structs Step 4 判定既有 struct body 是否"完整"时只比
「元素个数 / 首元素是 vtable 指针 / 是否含 T 占位」—— **类型错位**的 body
(个数命中、元素名也全对,但某槽被冻结成通用多层指针)会被直接 continue;
而 llvmlite 的 set_body 只能设一次 ⇒ 错布局被永久冻结,后续 .vpi / .stub 重载
只修 class_members、改不了 body ⇒ 赋值时报「层数不同」。

现场(成员槽来源明细实锤:声明侧正确、布局侧坏):
  class_members[Generator] 项数=47 base=0 下标=3
      声明槽类型=%"d827f3045bd28467.TypeGenCDef"*
  布局 struct name='fd4cd0ec583f42b9.Generator' 元素数=47 elements[3]=i8**
  → self.TypeGenCDef = SubTypeGenCDef
    指针隐式转换已禁止(层数不同):不能把 %…TypeGenCDef"* 隐式转为 i8**

修法:新增 _MemberSlotAccepts(判据与存储侧同一套 _ClassifyPtrImplicitConvert),
把 class_members 声明类型与 elements[base+i] 逐项比对;只有**存储时必被拒绝**的
组合(层数不同 / 指针与非指针混位)判为不一致 ⇒ 走既有「清 _elements + 清 stub
缓存」路径重建布局(通用指针槽 i8*、继承上转、数组退让照旧放行,不误清)。
2026-09-29 00:06:11 +08:00
TermiNexus 49f0386c33 fix(tpc): 构造物化豁免按「与目标同一」匹配,覆盖 sha1 前缀命名
a 编译器缺陷(承接 fa6b3c2e,仍拦 `*槽 = <boxed 变体构造>`):上一版按 callee
候选名直查 `Gen.structs` 取聚合 —— 但跨模块命名隔离下,同一类型的键是
`sha1.ClassName` 全名(stub 里就是 `%"50741ad4b4b0934d.LLVMType"`),裸名键
往往另有一份 opaque 占位条目且**不是同一 IR 类型**。于是
`LLVMType.Struct(...)`(候选名 Struct / LLVMType)取到的是占位/取不到,
与 target_type(`%"50741ad4b4b0934d.LLVMType"`)比对失败 ⇒ 豁免不生效。

修法:不按裸名直查,改为「键等于候选名,或以 `.<候选名>` 结尾」扫描 structs,
并要求取到的聚合与 TargetType **同一**(对象同一或 IR 名相同)才命中。命中后
若源的被指类型与目标仍非同一对象/名字(构造走的是另一份条目),按 DerefAs 的
整对象拷贝语义落地:指针 bitcast 到目标被指类型再 load(指针→指针 bitcast 恒
合法),绝不把指针值写进值槽。

现场(TransPyV/App/lib/core/Gen/Class/ClassGenDef.vp:122):
  *existing.Ty = llvmlite.LLVMType.Struct(...)
  → 非基本类型的值/指针隐式转换已禁止(构造物化除外):不能把指针类型
    %"50741ad4b4b0934d.LLVMType"* 隐式转为值类型 %"50741ad4b4b0934d.LLVMType"
2026-09-28 23:32:23 +08:00
TermiNexus 78c5155de9 revert(tpc): 回滚 51de3b6d 的 [DIAG-decl] 临时诊断
「跨模块函数形参退化成 i32*」(constant_get_str_val / function_get_name 第 0 形参
i32*)一族已由 1c1f78e7 定位修复(同名枚举成员遮蔽类定义,判据按 IsEnumMember
区分),探针使命结束,整体移除:
  - FuncGen._RecordDeclFrom / _DescribeI32ParamDiag 及两处调用点
  - _adjust_args fail-fast 报文中的 _diag_extra 拼接
  - BaseGen._get_or_declare_function 入口记录点
  - HandlesBase.GetOrCreateFuncDecl 入口记录点
2026-09-28 23:23:02 +08:00
TermiNexus fa6b3c2e64 fix(tpc): 「构造物化」豁免覆盖 REnum 变体构造(*槽 = <boxed 变体> 被误拦)
a 编译器缺陷:规则③(非基本类型值 ↔ 指针隐式转换一律禁止——含 T* → T)
的唯一例外是「构造物化」,但 _CtorCallStructType 只按 callee 名在 Gen.structs
里取聚合类型:对 REnum 变体构造 Qual.Variant(...)(如
llvmlite.LLVMType.Struct(fields, fcount, name))取到的是**变体短名 Struct**,
structs 中不存在 ⇒ 恒 miss,于是 `*existing.Ty = <boxed 变体>` 被当作指针→值
隐式转换拦下。报文自己写着「构造物化除外」,实现却没覆盖这一形态。

修法:按 HandlesExprCall._HandleCallLlvm 的分派顺序补齐判据 —— 先按 callee 名
判类构造(Point()),miss 后再按**限定名 Qual**(REnum 类,如 LLVMType)取一次
聚合类型。REnum 变体构造返回 boxed 指针(_allocaEntry(RenumType)),其 pointee
恰为 structs[Qual],与 target_type 比对通过即放行,随后走既有的
「load + 整对象 store」物化路径(与 c.DerefAs 的整对象拷贝分支等价)。

现场:非基本类型的值/指针隐式转换已禁止(构造物化除外):不能把指针类型
%"50741ad4b4b0934d.LLVMType"* 隐式转为值类型 %"50741ad4b4b0934d.LLVMType"
(ClassGenDef.vp:117)。
2026-09-28 23:21:27 +08:00
TermiNexus 1c1f78e7b3 fix(tpc): 修正枚举判据——按 IsEnumMember 区分成员/类型(bb05a157 判反了)
a 编译器缺陷:bb05a157 用 `not Entry.IsEnum` 排除"枚举类型",但现场实测枚举
**成员**条目的标志组合是
    IsEnum=True + IsEnumMember=True + BaseType=CEnum
(成员的 IsEnum 也被置真)⇒ 成员被误判成枚举类型,`rawptr[Constant]` 落 i32*,
`constant_get_str_val` 形参再次退化成 i32*。

正确判据(两处同源):
  枚举**类型** = IsEnum and not IsEnumMember ⇒ 走枚举分支(宽度 i32 等)
  枚举**成员**(IsEnumMember)⇒ 不是类型 ⇒ 让位给类定义("有类定义 ⇒ 是类")

现场证据(51de3b6d 的 [DIAG-decl] 报文,随 Traceback 输出):
  实参类=5df907d3a02a0bac.Constant | structs=有 |
  _struct_sha1_map=5df907d3a02a0bac | class_sha1_map=5df907d3a02a0bac |
  symtab[IsStruct=False, IsEnum=True, IsEnumMember=True, Base=CEnum] |
  声明函数类型=i8* (i32*) | 声明来源=..._EmitExternalFuncDeclLlvm <- _get_or_declare_function

修后 `ResolveAnnotationType(rawptr[Constant])` 得 CStruct/1 ⇒ _ctype_to_llvm 取
structs['Constant'] ⇒ 声明变为 i8* (%"5df907d3a02a0bac.Constant"*),与实参同型。
2026-09-28 22:44:20 +08:00
TermiNexus 51de3b6dcf chore(tpc): [临时诊断] 声明来源 + i32* 形参判定依据并入 fail-fast 报文
只为定位"跨模块函数形参退化成 i32*"(现场 function_get_name 第 0 形参 i32*,
实参 %"46c7dc5230ea5a52.Function"*)。静态排查已穷尽:_ctype_to_llvm /
_type_str_to_llvm 产生 i32* 的出口只有「BaseType 退化成 i32 + PtrCount=1」
(含 CTypeInfo.ToString() 的 'CInt *' → i32*)与「IsEnum + Name + PtrCount=1」,
离线复现不出,需要运行时证据。

做法:把判定依据直接拼进 FailFastError 报文,使其随 Traceback 一起被看到:
  [DIAG-decl] 实参类 / structs / _struct_sha1_map / class_sha1_map /
              symtab 标志 / 声明函数类型 / 声明来源(创建者调用栈)
并在四个声明创建点记录首个创建者:
  FuncGen._get_or_declare_func / get_or_declare_c_func、
  BaseGen._get_or_declare_function、HandlesBase.GetOrCreateFuncDecl(会替换声明)。

定位完成后整体回滚(连同记录点)。
2026-09-28 22:20:09 +08:00
TermiNexus bb05a157a6 fix(tpc): 收紧「有类定义 ⇒ 是类」判据:排除枚举类型本身
a 编译器缺陷(我上一轮 61f074ff / 3ca46c01 引入的过宽判据):判据用的
_struct_sha1_map 是 _build_struct_sha1_map 按 .vpi/.pyi 里**全部 ClassDef** 建的
⇒ **枚举类**(class ASTKind(t.CEnum)、class LogLevel 等)也在其中。于是
`x: ASTKind` 这类枚举注解被当成结构体建成 opaque struct,与「枚举宽度 = i32」
错位,现场表现为 i32 ↔ 结构体互换:
  AttributeError: IntType object has no attribute is_opaque
(llvmlite builder.store / LoadInstr 把 i32 值当指针用)。

收紧为判据分工(两处同口径):
· HandlesTypeMerge._GetCTypeInfoInternal 的 Name 分支:Entry.IsEnum ⇒ 枚举类型,
  不进「类定义」分支(走下方 IsEnum 分支,宽度 i32 等);
· BaseGen._get_or_create_struct:_known_class_name 追加 and not Entry.IsEnum。

「裸名被枚举**成员**占住」(Entry.IsEnumMember,如 ast/base.vp 的
Constant: t.State vs ast/exprs.vp 的 class Constant)仍让位给类定义 —— 这正是
原修复要解决的那一类,不受影响。
2026-09-28 21:57:21 +08:00
TermiNexus 3ca46c01cf fix(tpc): 同名枚举成员不得遮蔽类定义(_get_or_create_struct 的 i32 出口)
a 编译器缺陷:类型注解语境下枚举**成员**从不是类型,判据只能是「该名是否有类
定义」。但 _get_or_create_struct 的两个枚举出口(Entry.IsEnum 分支、Entry.BaseType
为 CEnum 分支)没有这条判据:当平坦符号表条目被**同名枚举成员**占住时(ast/base.vp
的 `Constant: t.State` 与 ast/exprs.vp 的 `class Constant` 同名,谁后插入谁占住裸名),
返回 i32;上游 PtrCount=1 叠加后即 i32*,与调用点实参 %"<sha1>.Constant"* 分裂 ——
现场 `constant_get_str_val` 形参 i32*(上一轮 61f074ff 只修了上游 TypeMerge 的 Name
分支,这里是真正吐出 i32 的那一步)。

判据与既有规则源统一(_struct_sha1_map / class_sha1_map 已登记该名为类 ⇒ 是类):
与 TypeConvert._type_str_to_llvm_base 的 `type_str in self.structs or type_str in
self._struct_sha1_map`、HandlesTypeMerge 的 Name/Attribute 分支、
DeclarationGenerator 的 `attr_name in struct_names` 同一条。

修后落到下方正常结构体创建路径,按 _struct_sha1_map 拼出 `"<sha1>.Constant"`,
与实参/声明端同一结构体对象。
2026-09-28 21:46:15 +08:00
TermiNexus 96d8e13c5a fix(err): 成员查找 fail-fast 改用可读异常 + 报错语句栈不再重复长报文
① 新增 MemberLookupFailFast(KeyError):_get_member_offset 的 fail-fast 原用 raise KeyError(消息),而 KeyError.__str__ = repr(消息) ⇒ 报文被加上引号、换行被转义成字面 \n(含"源代码 (文件, 行号): 行号 | 源码"的多行报文彻底不可读)。新类继承 KeyError(保住 HandlesExprAttr 两处 except KeyError 兜底点)但 __str__ 返回裸消息。

② 顺带删除该处的 traceback.print_stack 调试转储(遗留调试语句,输出噪音)。

③ Phase2Translator 语句栈渲染:原格式把同一条异常消息贴在每一层帧后面 ⇒ 一句话被打印 N 次;改为外层→内层的调用路径逐帧印位置、异常消息只在最内层印一次。
2026-09-28 16:48:58 +08:00
TermiNexus 49ff1b995f fix(cast): 显式指针 cast 一律放行(按 bitcast 重解释)+ MemoryOps 改用 vast 解析器模块
① 新增 _IsExplicitPtrCastNode:值节点是写明的指针转换调用(t.CPtr(x)/CPtr(x)/c.CPtr(x)/rawptr[T](x)/t.CPtr[T](x)/(T*)x/T*(x))时,_ClassifyPtrImplicitConvert 直接放行(用户规则「显式转换都可以」),调用方按既有 bitcast 重解释;非 cast 调用(MemBuddy(x)/list[X](x)/foo(x))不受影响。

根因:includes/_list.vp:60 的 elem_ptr: T | t.CPtr = t.CPtr(地址) 在 list[X*] 特化下 T|t.CPtr = X**(T 本身是指针),而 t.CPtr(...) 的值是 i8* ⇒ 被判「层数不同」,与报错自己给的「如需转换请显式 cast(如 t.CPtr(...))」建议自相矛盾(ExprGenFstr.vp 编译失败的 2 条 elem_ptr 报错)。

② MemoryOps.py 的 import ast 是标准库 —— 但 TPC 全部源码由 vast 解析,节点类为 lib.vast.Nodes.*,与标准库 ast 互不兼容 ⇒ 该文件所有节点类型判据对 Viper 源码静默失效(_value_node_of 子表达式下沉、_CtorCallStructType 构造物化豁免、以及新增的 cast 判据都不生效)。改为 import vast as ast,一并修复。
2026-09-28 16:48:52 +08:00
TermiNexus 97c196bfc7 feat(cast): 放行数组退让([N]T* → T* 单向),与继承上转同处判据
在 _ClassifyPtrImplicitConvert 的“被指类型非基本”分支里、继承上转之后加数组退让分支:源的被指类型是 ir.ArrayType 且其 element 与目标被指类型相同 ⇒ 放行(C/C++ 同口径)

判据顺序(两侧一致):层数不同即禁 → 完全相同放行 → 通用指针通配 → 标量无损/有损 → 继承上转 → 数组退让 → 否则 DENY;反向 T*→[N]T*、多维只退一层([N][M]T*→[M]T* 放行、→T* 不放行)
2026-09-28 15:38:41 +08:00
TermiNexus 171b0e3eeb fix(cast): 补继承上转豁免(Derived*→Base* 允许隐式转换),修 MemBuddy*→MemManager* 被规则③误拦
_ClassifyPtrImplicitConvert 由 staticmethod 改实例方法(全仓 5 处调用均为 self./Gen.,无 unbound 调用);新增 _ClassNameOfPointee(复用 Gen.find_struct_by_pointee + 短名回退)与 _IsInheritanceUpcastPointee(复用 Gen._get_mro_chain);两指针皆非基本标量时先判上转再判禁

保持:Base*→Derived* 仍禁(下转须显式强转)、无关结构体互转仍禁、通用指针通配/层数/完全相同/None/构造物化/标量无损口径不变
2026-09-28 15:29:41 +08:00
TermiNexus 9f8190d58f fix(cast): 无损口径对齐 TPV + 规则③豁免构造物化(p: Point = Point() 放行,真实 Foo*→Foo 仍禁)
_ScalarConvertLossless 改为与 TPV 一致:int↔int 全向允许、float↔float 全向允许、int→float 需目标浮点位宽≥源整数位宽、float→int 不允许(此前把整数↔浮点一律判有损,两侧不一致)

新增 _ValueNodeOf / _CtorCallStructType:指针→非基本值分支先豁免构造物化(Call 被调方命中 Gen.structs 且类型同一),其余保持禁止;报文前缀与 TPV 逐字一致;诊断沿用 _DescribeValueNode

已知未覆盖:TPC 无对称的“值→指针”检查;泛型构造不豁免;别名导入的类名可能命不中(保守不豁免,只误报不漏放)
2026-09-28 15:17:48 +08:00
TermiNexus d7158bd336 feat(cast): 指针隐式转换新规则 + None 可隐式转任意类型 + 诊断错位修复(9 文件)
MemoryOps._ClassifyPtrImplicitConvert:同类型放行;层数不同禁;任一侧通用指针(i8*)放行;皆非通用按被指类型(非基本类型禁、基本标量按位宽无损);指针与非基本值互换禁

_IsNoneValue 收敛:AST None 常量 或 指针型 null 常量 → 放行任意目标(指针目标给 null,非指针目标给零值);整数 0 不误命中;短路口覆盖 9 处禁令

诊断:新增 BaseGen._DescribeValueNode(子表达式下沉 + 有符号列号 + 源码片段),9 处禁令统一使用,不再打印外层 Assign 与 4294967295 列号

注:TPC 侧规则③比 TPV 更严(指针→非基本值恒禁),可能与既有按值回读冲突;待实机编译确认后再统一两侧口径
2026-09-28 15:10:39 +08:00
TermiNexus 58050a1ab2 fix(member_base): base 改由声明类型与 elements 的一致度校准,修整类字段偏移整体差 1
delta 长度启发式只在成员表与布局恰好差 vtable 一项时成立;成员表多/少一个非 vtable 项时 base 判错,整个类字段偏移整体平移:ast.Constant 的 lineno 落到 pool(MemManager*),调用点报形参 lineno: i32 收到指针,把真因掩盖成指针/整数错配

改法:对 b 属于 {0,1,2} 逐项比较成员声明类型与 elements[b+i],取一致度最高者(完全相同 +2),仅在明显更优(至少多对齐 1 个成员)时覆盖启发式;指针槽区分度最高

同时移除上一轮临时加的运行时字段布局探针(_diag_member_layout 及其两处调用)
2026-09-26 21:29:28 +08:00
TermiNexus c83fc752c6 fix(sizeof,parse): 尺寸链去静默兜底,解析报错可定位,补 C 解析器缺口
StructGen: _get_struct_size/_get_align 去 opaque→8/ptr_size、未知→4 兜底并补 VectorType;LiteralStructType.fields→.elements(llvmlite 无 fields,必然 AttributeError)

HandlesClassDef: union 载荷尺寸经不存在的 ir.DataLayout 静默退化成 8 字节;HandlesExprCall/ExprBuiltin: sizeof 各静默 0 分支改 fail-fast,数组元素 sizeof 统一走 _get_struct_size

HandlesFor: nonlocal struct/array 堆分配不再退化成 malloc(8)

vast/vast_c: 补一元 * / & 解析,装饰器后 extern/export 分派修复,C 侧报错带 token 与行号;StubGen.Converter: 宏展开丢尾随空行修 .pyi 缩进非法

其余为 rawptr 迁移 / fail-fast 口径等历史改动一并入库
2026-09-26 19:27:22 +08:00
TermiNexus 53a345a59a feat(ctrlc-stack): 新增 --ctrlc-stack 编译期开关(按键打印 .vp 调用栈)
Config/Phase2Translator: --ctrlc-stack 自动开 DWARF;缓存失效;链接后 llvm-dwarfdump 导出 <exe>.stackmap(逐条归一化为 RVA——旧的整体判据会被 lld 去重产生的 0 地址行带偏,导致全表差一个 ImageBase);链接参数追加 -ldbghelp;include 也生成 DWARF 并隔离到 includes.binary_ctrlcstack(_asan);llc 追加 -frame-pointer=all。

BaseGen._RegisterDbgStackInstallViaCtor + StructGen.finalize 钩子: 注入 llvm.global_ctors(priority 65535) 在启动期调用 dbgstack.Install(),写盘前核对 global_ctors 否则 fail-fast。

FuncGen._adjust_args: 指针到整数的 fail-fast 补逐项类型诊断。
2026-09-25 13:11:13 +08:00
TermiNexus 2cc816ac13 修复了一些错误 2026-09-18 20:00:59 +08:00
TermiNexus 6af2dea917 修复了一些问题 2026-09-17 14:52:37 +08:00
TermiNexus 43e7cdd2cc feat(tpc): 函数级 DWARF 调试元数据 + 合并 IR 元数据规整
DWARF: 新增 LLVMCG/DebugInfo.py(DebugInfoEmitter)——每模块一个 DIFile/DICompileUnit、每个 define 一个 DISubprogram 并在函数入口首条指令挂 DILocation,模块落盘前补 !llvm.dbg.cu 与 !llvm.module.flags(Debug Info Version 3)。开关:新增 --dwarf(默认关闭),dwarf_enabled = args.dwarf or args.asan,沿 Phase2Translator → _translate_file → Translator.DwarfEnabled → LlvmCodeGenerator.dwarf_enabled 下传。define 之外的 declare 不挂。include 库刻意不给 DI(其 .obj 进跨项目共享的 includes.binary 缓存,注入会污染缓存)

关键约束(踩过的坑,已在探针中实测):DICompileUnit 与 DISubprogram 必须 is_distinct=True;emissionKind 用 FullDebug 而非 Full;spFlags 用 DISPFlagDefinition;DISubprogram 必带 type=DISubroutineType,缺了 llc 会崩 0xC0000005;模块级必须有 !llvm.dbg.cu + Debug Info Version 3

IRMetadataFix: 新增 SanitizeIRMetadata,在 _llc_compile 入口统一规整 IR——BuildCombinedIR 是多模块 IR 文本直接拼接,--dwarf 后每个片段自带 !0..!N 与两份命名元数据,22383 行样本里 !0/!1 各重复 67 次导致 opt/llc 报 Metadata id is already used。实现:按块重编号 !N 并改写 !dbg/命名元数据引用、!llvm.dbg.cu 全量聚合、其它命名元数据全同去重;无元数据文件逐字节 no-op、幂等(1.7MB/0.30s)

顺带修 _AddWeakComdat:函数头正则跨行吞换行,--dwarf 把 !dbg 放到 define 行尾后 comdat 被插到 ! 附着之后,llc 报 expected { in function body(现场 52 个 define 中 36 个中招);改为插在附着之前,无 !dbg 时产物逐字节不变

已自证:llc/opt RC=0、行表含 .vp 文件名与行号、opt -passes=forceattrs,asan 与 llvm-link/always-inline 均不丢 !dbg。已知待办:llc 现报 inlinable function call ... must have a !dbg location ⇒ 需补语句/调用点级 !dbg(否则警告 ignoring invalid debug info 会丢弃整份 DI)
2026-09-16 19:29:51 +08:00
TermiNexus 033c0a7771 fix(tpc): 修复 t.CVoid 清理暴露的三处类型解析缺口
SymbolTypeResolver: 裸 t.CPtr 作 typedef 值(无联合、无下标)时按 void* 展开。_find_base_type 把 attr==CPtr 归一为小写 ptr,而守卫 _PTR_MODIFIERS 写的是 CPtr 命不中,导致 ptr 被当成类型名存进 OriginalType,最终建成 %"<模块sha>.ptr" 幻影结构体(HANDLE/VOIDPTR/DIR 报 cannot store i8* to ...ptr*)。改前 t.CVoid | t.CPtr 走 BinOp 分支恰好绕过

HandlesExprAttr: 属性访问的成员查找补继承链回退(对齐 HandlesExprUtils._get_var_class 的口径)。字段声明在基类时元素类表的键是声明类,而收件人是子类,原实现只查收件人类自身;方法调用侧早已做 MRO 查询,故 150 处 self.Gen.X.method() 未暴露。修后 self.Gen.TypeGenCType.ImportAliasBuf 可解析(该字段在基类 TypeGenBase),且此前被静默丢弃的 _register_import_alias malloc 分支恢复生效

TypeConvert._base_ctype_to_llvm: 注册表给出完整类型串(CPtr/CArrayPtr/CNeedPtr/CAutoPtr -> i8*)时不再返回 None(_llvm_str_to_ir 只认标量名),改走 _type_str_to_llvm——与同文件 _basic_type_to_llvm 既有口径一致;否则 PtrCount 循环会构造 ir.PointerType(None)(llvmlite opaque 指针,无 .pointee),在 HandlesFunctions._GetFunctionSignatureLlvm 抛 AttributeError(opendir 的 FATAL)。触发集合经全注册表扫描恰好 4 个类

等价性验证: 31614 个符号 + 37527 个真实注解点位逐条对比改前/改后差异 0 条(崩溃输入除外,由 opaque ptr 变为 typed i8**)
2026-09-16 18:16:38 +08:00
TermiNexus bf4bab093a fix(hotspot): 深度栈改用 common linkage,修复跨模块 self 记账漏扣
症状:--hotspot-sites 下跨模块调用链的调用方 self 约等于 inc(子调用 inc 未被扣除),而同模块链正常,导致所有基于 self 的结论带偏差(实测 _translate_module_level 421.9e9/422.0e9、translate_children 164.5e9/164.9e9 等)。

根因:__fhs_tdepth/__fhs_tstart/__fhs_tchild 原为 internal linkage,Projectrans 多模块模式下每个 .vp 翻译单元各持一份独立深度栈;跨模块调用时被调方读到的深度为 0,slot=0,ret 时 slot>0 判定为假,其 inc 永远写不到调用方的 tchild[slot-1],于是调用方 self = inc - 0 = inc。

修复:三件套改 common linkage,由链接器合并为全程序唯一一份(与本仓库多模块同名全局的既有做法一致);__fhs_tscratch 与每函数计数器仍保持 internal。已用逐指令 Python 模拟验证:同模块各形态均正确、跨模块独立栈必然漏扣、共享栈后全部正确。

仅 --hotspot-sites 启用时生效,未启用路径逐字节不变。旧的 hotspot_time.txt 在跨模块链上的 self 列不可信,需重新测量。
2026-09-14 14:35:17 +08:00
TermiNexus 136e9bc33e feat(hotspot): 热点日志在进程退出时也落盘(atexit + 幂等守卫)
此前 --hotspot-sites 仅由 SetConsoleCtrlHandler 在 Ctrl+C 时落盘,导致正常退出与 fail-fast(VLogger.error 走 sys.exit(1),底层是 extern exit() 即 libc exit)都拿不到热点数据。

现把两个 handler 主体抽为 void() 落盘函数(__fhs_dump_sites/__fhs_dump_time),新增 __fhs_dump_all 与每模块 internal i32 __fhs_dumped 幂等守卫,并在 ctor/main 入口同时注册 SetConsoleCtrlHandler 与 atexit;Ctrl+C 处理器仍返回 FALSE(0) 保持原有终止行为。同模块只落盘一次,不产生重复段。

覆盖:正常 return、sys.exit(1) fail-fast、Ctrl+C。不覆盖(已在报告中列明):_exit、abort、崩溃信号、被强杀。不启用开关时 IR 逐字节不变;两个输出文件格式与排序不变。
2026-09-14 14:08:55 +08:00
TermiNexus 84d91d53e5 perf(hotspot): 计时串行化并改为自身耗时(exclusive)统计
--hotspot-sites 的每函数计时原为裸 rdtsc 的 inclusive 耗时,导致 _parse_* 一条调用链各项数值几乎相同、无法定位自身成本,且短函数测量不可信。

新增 _EmitReadTimestamp:x86_64 用 lfence 加 readcyclecounter 串行化读(业界通行的 lfence;rdtsc),非 x86_64 编译期降级为裸 readcyclecounter;入口与每个 ret 前共用。选 intrinsic 组合而非内联汇编,避免约束串转义与渲染风险。

用深度栈实现自身耗时:全局 __fhs_tdepth 与 [512 x i64] __fhs_tstart/__fhs_tchild,self = inc 减去本帧内被插桩子调用 inc 之和;递归靠每调用独立 slot 与深度索引天然正确;深度溢出或终结指令非 ret 的块明确跳过不记账(仅计进入次数),不伪造数值。入口段用 select 而非条件分支,避免改动 CFG。

hotspot_time.txt 行为 函数名 self inc 进入次数(仍按 self 降序),段头注明单位 tick 与 self 口径;hotspot_sites.txt 格式保持不变;帮助文本同步更新。未插桩的被调方(含 libc)耗时计入调用者 self,属该方法固有性质并已注明。
2026-09-14 13:42:14 +08:00
TermiNexus fc5a6134ca feat(hotspot)!: 下线 --func-hotspot 并为 --hotspot-sites 增加每函数计时器
移除 --func-hotspot 全链路(CLI 解析与绑定、Config argparse 与注入、Translator 属性、LlvmGenerator 收口与透传、BaseGen 专属实现、Phase2Translator 透传、帮助文本);全仓已无 FuncHotspot/func-hotspot 残留引用。

通用截断逻辑保留并改名为 _InstallHotspotTruncate,供 --hotspot-sites 使用(分别清空 hotspot_sites.txt 与 hotspot_time.txt);ctor 注册改为直接新建并接收处理器列表(sites 与 time 两个处理器在同一 ctor 内注册)。

--hotspot-sites 现附带每函数计时:入口读一次 @llvm.readcyclecounter() 存起始 tick,在函数所有 ret 之前再读一次并累加差值到 internal i64 __fhs_ftime_N,同时 __fhs_ftime_enter_N 记录进入次数;单位为 tick(x86_64 即 CPU 周期),语义为 inclusive(包含内层调用)。

计时结果按耗时降序写入同目录 hotspot_time.txt(# time <module_sha1> 分段 + 函数名/累计 tick/进入次数),不改变 hotspot_sites.txt 的既有格式与字段。不传 --hotspot-sites 时零影响。
2026-09-14 13:10:06 +08:00