TermiNexus
|
b6d6d5ac6b
|
fix(struct): 修复 _Undefined 检查中的未定义变量 constant
列表初始化器中应检查元素 c.constant,原代码引用裸名 constant 导致 NameError。
|
2026-10-18 20:56:21 +08:00 |
|
TermiNexus
|
0bded58999
|
refactor(type): 全面清理旧联合与 t/c 标记判断
审计并修复 23 个文件:类型注解中的 T1 | T2 一律 fail-fast;typedef/define/export/extern/static/const/volatile/attribute 优先读取 Markers/Attributes/ByteOrder/BitWidth;指针统一 CPtr/rawptr/CRef/CTempRef/CArrayPtr/CAutoPtr Subscript;stub 输出统一新语法。
|
2026-10-18 18:01:20 +08:00 |
|
TermiNexus
|
f19d25782f
|
refactor(tpc): 退役 t/c 模块并将定宽类型升级为内建
删除 lib/includes/t.py、c.py,类型模型迁入 lib/core/TypeSystem.py;移除 IsTModule/_T_MODULE_NAMES 等名称判据,语法糖与内部标记统一为裸名;builtins 的 sizet/intN/uintN/fast/least/uchar/short/long 等改为编译器内建短名。
|
2026-10-18 13:00:58 +08:00 |
|
TermiNexus
|
2b4679073e
|
维护基线
|
2026-10-17 23:27:00 +08:00 |
|
TermiNexus
|
55a6856c4e
|
fix(tpc): >> 符号性只按左操作数判定,去掉“残留变量名”误判(负数右移发成 lshr)
- 现场:`b: int64 = -196608; b >> 1` 发成 lshr ⇒ 负数变大正数;而同样注解的 `c: int64 = 0 - 196608; c >> 1` 却正确 ⇒ 与 RHS/位置相关(IntWidthTest Test 5/6)
- 根因:emit_binary_op 在 i32/i64 分支又用 self._last_var_name 重判符号性,而它只是“最近见过的变量名”,可能是上一条表达式残留的别的(无符号)变量 ⇒ 误判无符号
- 改动:① ExprGen.emit_binary_op 删除 i32/i64 的 _last_var_name 分支(符号性只信调用方;i8/i16 仍按宽度定无符号);② HandlesExprOps 的 `>>` 改用 _check_node_unsigned(Node.left),覆盖 Name 之外的形态
- 验收:Test/IntWidthTest(Test 5/6),目标 TPC 侧 9/9 PASS
|
2026-10-14 20:18:26 +08:00 |
|
TermiNexus
|
15f77da9d3
|
chore: 改名同步 TransPyV -> ViperC(源码/配置内的纯替换)
|
2026-10-12 15:06:48 +08:00 |
|
TermiNexus
|
3b16334f5d
|
feat(types): 禁止隐式窄化(整数 高→低)——_coerce_value 与 pointee 判据同步
与 TransPyV 同口径:_coerce_value 的 int→int 窄化分支改为 fail-fast(隐式窄化禁止),仅豁免 ① 目标 i1 真值归一化 ② 编译期常量且值落进目标位宽;_ScalarConvertLossless 的 int↔int 改为 Dst.width>=Src.width。
|
2026-10-10 21:57:25 +08:00 |
|
TermiNexus
|
5a0eecc22f
|
feat(core): 栈地址逃逸检查(LifetimeError)与标量→聚合 fail-fast
- MemoryOps._IsFrameStackAddr 判据 + HandlesReturn/HandlesAssign 汇点挂钩:禁止栈地址逃逸(&local 与数组退让);新增 LifetimeError(FailFastError) 承载该错误类。 - MemoryOps._coerce_value:标量(整数/浮点)不得初始化/赋值给聚合类型,改为 FailFastError(旧行为让 _store 抛 TypeError,非 strict 下被静默吞掉,聚合未初始化)。
|
2026-10-08 12:10:24 +08:00 |
|
TermiNexus
|
9656bb161a
|
feat(codegen): 值引用自动取址收口到 _adjust_args,补齐 vtable/方法/构造等路径
查表键统一为真实发射出的函数名:FuncGen._adjust_args 开头以 func.name 调用 _apply_auto_addr,于是 direct/vtable 虚分派/构造/静态/__call__/下标赋值/c.Addr 构造/闭包/泛型/方法兜底全部自动取址,不再依赖每个调用点手写;同时删除 16 处冗余显式取址(t.CAutoPtr 的 always 模式不幂等,重复应用会多包一层指针)。登记侧:HandlesImports 把模式登记提前到 FuncName-in-Gen.functions 早退之前并按 func.name 主键写表,_TryLoadStructFromStub 改为无条件调用;HandlesFunctions 在函数对象就绪后按真实名登记,并在 _EmitFunctionForwardDeclLlvm 把逻辑名下的模式播种到 MangledName,覆盖同模块先调用后定义的场景。
|
2026-10-07 19:59:36 +08:00 |
|
TermiNexus
|
2514cee4c9
|
feat(lang): 同步 cif/celif/celse、值引用注解与 assert 改译 testcheck.check
vast 双路径(Python Parser/ExprParser 与 C 扩展 vast_c.c)新增 cif-celif-celse 语句族与 &T / &temp T 参数注解解析,_parser.pyd 已同步重编。HandlesIf 改认 @CIf 标记并做编译期常量求值,含运行时变量一律 fail-fast,同时删除 c.CIf 家族(c.py)。HandlesAssert 合成 testcheck.check(cond, None, msg) 调用,非字符串字面量消息报 TypeError。FuncGen 调用点按形参模式自动取址(ref 仅接受左值,右值 fail-fast;temp 物化临时栈),SymbolUtils/TypeAnnotationResolver 提供模式识别;t.py 删除 CNeedPtr。
|
2026-10-07 19:03:29 +08:00 |
|
TermiNexus
|
74cfaeb917
|
feat(switch): 新增 switch 语句并禁止 match 内 fallthrough
- vast/Nodes.py:Match 增加 is_switch 字段(含默认值)
- vast/Parser.py:新增 _ParseSwitch(case <常量|枚举变体> / default:,desugar 为 Match(is_switch=True));match / struct / fallthrough 改为硬关键字
- vast/Unparser.py:round-trip 忠实还原 switch 与 default:
- HandlesMatch / HandlesBody:新增 in_switch_case 门控,fallthrough 只允许出现在 switch 的 case 体内
- HandlesFor:补齐嵌套函数的 saved_loop_break/continue_targets 定义(修 NameError),并隔离 in_switch_case
- LLVMCG/BaseGen:新增 in_switch_case 状态
|
2026-10-02 23:15:42 +08:00 |
|
TermiNexus
|
b18d57fa38
|
perf(全仓库/正则): 字面量正则全部改模块级预编译常量(15 文件 / 115 个调用点)
背景(prof 基线 PROF/20260930_224857,主进程 433.69s):正则这一族合计 ≈45s ——
`re.match`(未预编译入口)被调 1970 万次 / 自身 10.70s、它内部 `Pattern.match` 占 8.06s,
`re._compile`(缓存查找)1975 万次 / 7.19s,`re._make`(真正编译)274 万次 / 7.62s
(说明 re 的 512 条缓存已被打穿,每 7 次调用就有 1 次重新编译)。
每次 `re.match(字面量, s)` 都要做"模式串哈希 → 缓存查找";预编译后这层全省掉。
改动:把 15 个文件里 97 处"字面量当模式"的 `re.match/search/sub/subn/fullmatch/
findall/finditer/split(...)` 提升为模块级 `_RE_*: re.Pattern[str] = re.compile(...)`,
共 115 个调用点(含同字面量跨处共用)。模式串与 flags 一字未改(flags 从调用处
搬进 re.compile,含原先以**位置参数**传入的 flags)。4 处拼接型动态模式按设计不动
(FuncGen 2 处、HandlesImports 2 处)。
核验(不靠肉眼):写了 AST 指纹对拍脚本,对每个文件取 HEAD 版与新工作区版,把
"旧 re.<m>(字面量, 其余实参, 关键字) + flags" 与 "新 _RE_X.<m>(实参, 关键字) +
常量 (模式, flags)" 做多重集比对 —— 15 个文件、115 个调用点全部逐一等价(0 失败),
另查无重名常量、无遗留字面量调用、GetDiagnostics 全空。
对拍过程中查出一处子代理抄写错误:`_RE_GLOBAL_DSOLOCAL_MULTILINE` 丢了行首锚 `^`
(原文注释明确"使用 ^ + MULTILINE 避免匹配字符串常量内容中的 define/declare"),已修正。
同轮经查证后**未做改动**的两项(避免把假热点当真事修):
- VLogger 的 `re.sub` 6.43s:profile 当时的 VLogger 版本里根本没有 `import re`
(`git show 3658ab06:lib/core/VLogger.py` 已确认),属 C 帧错挂,不做。
- tokenize 274 万次:那是 `_generate_tokens_from_c_tokenizer` 的 **token 产出次数**
(≈350 个整文件的词法量),SymbolUtils 的 4 个 tokenize 调用点 nc 仅数百,且已有
`_EXPAND_CACHE` 去重;再加一层 token 缓存无收益,不做。
|
2026-09-30 23:47:36 +08:00 |
|
TermiNexus
|
3658ab0637
|
perf(Phase2Translator/IRMetadataFix): 削逐行热点——跳过无谓 strip、预编译逐行正则、C 级数字扫描
prof 基线(PROF/20260930_224857/main_time.txt=438.40s,7.28 亿次调用)显示全流程最大的 Python 侧热点是逐行扫描产生的内置字符串/正则调用:str.startswith 2.372 亿次(32.1s)、str.strip 1.791 亿次(22.8s)、re.Pattern.match 3130 万次(11.4s)、re.match 模块级 1971 万次(10.7s)+re._compile 1975 万次(7.2s)。四处等价改动:
1) _split_ll 第二遍:in_define/in_global_def 续行(.ll 里占比最高)不再 str.strip——这两支只用行内容与花括号计数,前导/尾随空白不含花括号,_count_braces_outside_strings 结果逐字不变;
2) _split_ll 第一遍:加 "define" not in line 的 C 级快路径,绝大多数行连 strip 都不做;
3) Phase2Translator 逐行正则全部预编译为模块级 _RE_* 常量(11 处 re.match + 4 处 re.sub,模式串一字未改)——消掉每次调用的模式串哈希+缓存查找;
4) IRMetadataFix:_RewritePlain 的前导数字扫描由逐字符 Python 循环改为 str.lstrip 的 C 级扫描(该函数 prof 5.8M 次调用,是项目内 self time 最大者);SanitizeIRMetadata 的逐行 startswith("!") 改为 [:1] 索引切片。
离线自测:数字扫描与旧实现逐例一致;SanitizeIRMetadata 幂等性保持;_split_ll 冒烟输出与预期语义一致(global/多行初始化器/define→declare/constant/metadata 分流正确)。
|
2026-09-30 23:04:25 +08:00 |
|
TermiNexus
|
b0aa9f6d76
|
fix(Handles): 注解声明的类是权威类型,赋值不得改掉它
新增 var_annotated_class 台账(AnnAssign 声明完成时登记真实类名,随 var_struct_class 在函数作用域清空),HandlesAssign 三处「右值推出类」登记点命中该台账时不再覆盖声明类型。否则 node: rawptr[AST] = Name(...) 会把 node 记成 Name,后续 node = <AST*> 被判成被禁止的向下转型(includes/ast/parser.vp:1919)。
|
2026-09-30 19:09:37 +08:00 |
|
TermiNexus
|
ecd336771f
|
fix(FuncGen): base/字段偏移一律按"重名条目不占槽位"算,修 MatchCase 偏移 6(应为 7)
类别:a(编译器缺陷)
现象(早前现场,本轮定位):
TypeError: [fast-fail] len(mcc_pre.children) 分派 'list[….AST*].__len__' 失败:
类='MatchCase' 偏移=6 接收者类型=i32
判据(磁盘实证):
· MatchCase 的 body 在 temp 与**全部** output stub 中都是 **10 槽**
(vtable + AST 的 7 字段 + pattern/guard)⇒ `children` 的正确偏移 = base(1) + 表内下标(6) = **7**。
· 观测到 6 ⇒ base 被算成 0 ⇒ `_member_base` 的 `_Delta = 槽数 - 表长` 为 0,
即**表长 = 10**(9 个不同字段 + 1 条重名条目),而不是 9。
· 重名条目是**产物**:源码层面的字段名遮蔽已由 HandlesClassDef 的
"字段名遮蔽 fail-fast"(:1083)拦下 ⇒ 表里的重名只可能来自"展平残留
(半截祖先块,已由 2e7bac18 在插入点剥除)/ 同一字段被两条收集路径各登记一次"。
修复(与 `_get_member_offset` 配对路径已有的"重名条目不占槽位"同口径,全线统一):
· `_member_base` 的长度校准改用**去重名数**(`_Delta` 不再被重名条目冲掉 ⇒ base 正确);
· 新增 `_MemberSlotIndex(Members, FieldName)`:取"首个同名的**去重**下标"
(重名条目不计槽位),并把 `_get_member_offset` 中**全部 12 处** "base + 表内下标"
站点改为调用它(含继承链回退、REnum 变体搜索、stub 重载后、_PreRegisterClassMembers
之后等分支);"最后回退"里的 `_num_members_final` 同步改为去重计数。
· 对无重名的表,取值与改前**逐字相同**(去重下标 == 原始下标)。
核验:py_compile + compileall 全 lib 通过;离线冒烟(真实方法、未绑定实例调用):
含 1 条重复 'children' 的表上 `children -> 6`、`guard -> 8`、缺名 -> -1;
去重名数 9 ⇒ `_Delta = 10 - 9 = 1` ⇒ base=1 ⇒ `children` 偏移 7(与 body 10 槽一致)。
未放宽任何 fail-fast。
|
2026-09-30 18:44:48 +08:00 |
|
TermiNexus
|
2e7bac1816
|
fix(HandlesImports/HandlesClassDef): 剥掉"祖先块前缀的陈旧半截副本",消除成员表重名条目
类别:a(编译器缺陷)
现场(用户构建日志):
WARN[StructGen]: [结构体布局] 类 'ClassGenDef' 的成员表含**重名条目**:
表 50 项 / 去重 42 项,现有 body 42 槽恰等于「去重名数 + vtable」
判据(磁盘实证 + 结构推导,非猜测):
· ClassGenDef 自身 **0** 个字段(全是方法),父类 ClassGenBase 恰 **42** 个字段;
temp / output 两份 stub 的 ClassGenDef body 都是 **42** 槽,且槽序 = ClassGenBase
的字段声明序 ⇒ body 正确、**表多出 8 条**(50 = 42 + 8)。
· ClassGenBase 自己的 Phase-1 stub 就是 42 槽(不是 8)⇒ 多出的 8 条并非"父类 stub 短",
只能来自**内存里父类表尚未收集完就被展平**(写入了半截祖先块 Block[:k],k=8)——
正是 PrependInheritedBlock 文档里写明的"已知残留"。
· 重名条目不是布局槽位(见 FuncGen._get_member_offset 的重名段),却让
_member_base 的 _Delta = 42 - 50 = -8 走进类型校准,并让回退路径的
base + 表内下标 整体偏斜(同族现场:MatchCase / TypeGenResolve 的偏移算错)。
修复(判据全部为正向事实,且与插入方向无关):
· 新增 NormalizeAncestralCopies(Table, Block, InsertAtHead) -> (新表, 是否已含完整块):
① 完整块在**头**、尾部挂前缀副本 ⇒ 剥尾(副本是被后来的整块前置推过去的);
② 完整块在**尾**、头部挂前缀副本 ⇒ 剥头(副本先写入、整块随后追加);
③ 完整块尚未出现 ⇒ 剥插入边(仅当这些名字已另有正本,见下)。
剥掉的每一项在(表里已有的 / 随后插入的)完整块里同名同型 ⇒ **不丢布局信息**,
与 PrependInheritedBlock 性质①"不删块内条目"不冲突(剥的是副本)。
第一版曾"两端都剥",离线冒烟立刻暴露会把**正本**当副本剥掉([A,B,C,D,A,B] →
[C,D,A,B],再前置整块反而多出 C,D 两份)⇒ 改为先定位完整块在哪一端、再剥另一端。
· TrimStalePrefixCopy 判据:插入边连续游程与 Block[:j] 逐项同 name 同 IR 类型,
1 ≤ j < len(Block),且这 j 个名字在表里**各另有一份**(计数 ≥ 2)——最后一条把
"子类自有字段恰好与祖先块前缀同名同型"的误判排除。
· 两个插入点(HandlesImports.PrependInheritedBlock 前置 / HandlesClassDef 展平追加)
统一走该函数,判据对称;真正剥除时各留一条 WARN(含条数与前后表长)。
· PrependInheritedBlock 原有两条幂等提前返回("表头等于块" / InheritedBlockAlreadyPresent)
合并为 _HasFull 一条(语义等价),并更新其"已知残留"段落。
· StructGen 的重名 WARN 追加**逐条明细**(名@表内下标):单看"表 50 / 去重 42"无法区分
"祖先块前缀的陈旧副本"与"两条收集路径各登记一次"(两种成因修法完全不同)。
核验:py_compile + compileall 全 lib 通过;离线冒烟(调用真实函数,8 组用例 × 两个插入方向):
①/② 均 6→4 正确剥除,"无重复 / 带自有字段 / 类型不同 / 无正本 / 空表"均不改动。
未放宽任何 fail-fast。
|
2026-09-30 18:41:24 +08:00 |
|
TermiNexus
|
7ee6ebe6b0
|
fix(类型转换): int→i1 目标改为真值归一化 != 0,与 bool(x)/if 条件同判据
类别:a(编译器缺陷)。TPC 侧此前对 i1 目标一律 trunc(取最低位):运行时值 2 →
最低位 0 → false,而同一值在 `if 2:` / `bool(2)`(icmp ne 0)为真 ⇒ 条件上下文与
落存储/强转两处结论相反(静默语义分裂)。
- MemoryOps._CoerceIntToI1(新增,唯一判据源):非 i1 整数 → `icmp ne x, 0`。
- MemoryOps._coerce_value:窄化且目标位宽 == 1 时走 _CoerceIntToI1。
- HandlesAnnAssign._StoreWithCoerce:同上(self.字段 / obj.字段 落存储)。
- HandlesExprCall._HandleTypeCastLlvm:显式强转目标 i1 时同上。
与 TransPyV 侧同批修复(coerce_to_type / _translate_t_type_cast)对齐。
注:TPC 的 `bool` 注解仍是 i8(t.CBool.Size=8),故 `b: bool = 2` 仍按 i8 保位存储
(int(b)=2);本次只覆盖**真 i1 目标**,双端 bool 位宽差异见
SymbolUtils.py:1218-1223 已记录的全局语义分裂。
|
2026-09-30 15:14:31 +08:00 |
|
TermiNexus
|
3efe83f1ea
|
fix(编译期名称解析): 修 StructGen 缺 _vlog 导入与 HandlesImports 的 node/Node 误用
两处均为 a(编译器缺陷),且都被 HandlesImports.py:1055 的
`except Exception -> WARN 忽略异常` 吞掉,表现为模块声明静默退化
(本次现场:VpiReader.vp 阶段 WARN + 结构体声明路径中断)。非源码违规、
非机制漏豁免。
1) lib/core/LLVMCG/StructGen.py
- `_generate_structs` 的「成员表含重名条目、body 已是权威布局」提示调用
`_vlog().warning(...)`,但本文件从未定义/导入 `_vlog` ⇒ NameError。
该分支一旦命中(astaux.ExceptHandler 一类),`_generate_structs` 整体中断。
- 补 `from lib.core.VLogger import get_logger as _vlog`,与同目录
FuncGen.py / TypeConvert.py 同口径。
2) lib/core/Handles/HandlesImports.py:1317
- `_EmitExternalGlobalDeclLlvm` 形参名为 `Node`,该 elif 分支误写成小写 `node`
⇒ 只要传入的是**非带注解**的跨模块全局量(`ast.Assign`)就恒抛 NameError,
分支整体不可达:外部全局变量从未声明 external,消费侧表现为
unknown global / 类型占位。
- 判据改回 `isinstance(Node, ast.Assign)`。
静态核验:对 TransPyC 全部 .py 做 symtable 未定义名扫描,结果 0 条;
`python -m py_compile` 通过。
|
2026-09-30 14:55:40 +08:00 |
|
TermiNexus
|
7aa12263f5
|
fix(HandlesExprAttr/BaseGen): _get_attr_ptr 的 self 分支补 opaque 基址收口(孪生体 bitcast)+ GEP 守卫报出 TPC 发射点
【a 编译器缺陷】现场:Phase2 翻译 includes/ast/astaux.vp:165
`self.arg = _copy_str(pool, arg)`,守卫报
"getelementptr(GEP) 的基址指向**未具化**的具名结构体:基址类型=%"8312ce86a2fd82c0.Keyword"*,
GEP 名='arg'",而同短名实例清单显示 'Keyword' 键上那份已具化(10 槽)。
根因:`_get_attr_ptr` 的 **self 分支**是全仓"self 字段取址"家族里唯一**没有**做 opaque
收口的一处 —— 同函数下方各分支(变量分支 / 属性链分支 / i8* 分支 / 强转基座分支)都先
`_TryLoadStructFromStub` 补 body、再 bitcast 到已具化实例;`HandlesAssign` 的 `self.X = v`
写路径与读路径 `_gep_and_Load_member` 也都有同一套孪生体搜索(`_FindConcreteTwin`)。
本分支直接 `gep(SelfPtr, …)`,typed pointer 下 llvmlite 必须在编译期从 pointee 算元素类型
⇒ 撞 opaque 即抛裸 TypeError(被 IR 边界守卫拦下)。
修法(与同函数其余分支同口径,不引入新判据):
· 基址 pointee 为 opaque 时,先在类键(`Gen.structs[ClassName]`)上 `_TryLoadStructFromStub`
补 body;类键那份仍不可用则退回 `Gen._FindConcreteTwin`(同短名、取槽数最多);
· 命中即 bitcast 到该实例,并把该实例作为 `StructHint` 传给 `_get_member_offset`
—— 偏移与 GEP 必须同源(与 a43513c9 的"hint 只接受同类"口径一致);
· 顺带补 `offset is None ⇒ return None`(原实现会把 None 塞进 `ir.Constant(i32, None)`,
属同一函数其余分支已有的判断)。
【a 编译器缺陷·取证】`RequirePointerOperand` 的 opaque 分支新增一行
「TPC 发射点(由内向外)」:取 Python 栈中位于本仓 `lib/`、且不在 BaseGen.py(守卫自身)
的最内 3 帧,报出 `文件:行(函数)`。理由:该守卫的报文只能指到**语句**,而同一语句里常有
多个候选 GEP 点(本例 astaux.vp:165 在读写两侧共有 4 个 self 字段 GEP 点),逐个读代码
无法判定是哪一个 —— 本次即靠该取证能力闭环(先按"唯一漏收口的分支"修正,下一次构建若
仍失败会直接指出真实发射点)。只在失败路径执行,热路径零开销。
冒烟(临时脚本已删):
· `_get_attr_ptr` 复刻现场(self 槽持有 opaque `%8312…Keyword*`、'Keyword' 键那份 10 槽)
⇒ 输出 GEP 基址类型 `%"fef783da4d24330d.Keyword"*`(经 `cast_self_Keyword`),PASS;
· 守卫取证:伪造 lib/ 下的发射帧 ⇒ 报文含
`TPC 发射点(由内向外): core/Handles/_FakeProbe.py:2(_LibEmit)`,PASS。
|
2026-09-30 14:35:50 +08:00 |
|
TermiNexus
|
49930c56c7
|
fix(LLVMCG/HandlesExprAttr): REnum 变体字段按唯一变体键取槽 + 修 _PointeeMatchesStruct 的「同模块前缀」误判
【a 编译器缺陷】两处根因,同一现场(CallGenDispatch.vp:787
`len_ap: llvmlite.LLVMType* = len_val.Ty.Pointee`):
1) `_PointeeMatchesStruct` 只比 sha1 前缀 ⇒ 把**同模块的兄弟类**当成同一个类
(`50741ad4b4b0934d.ParamNode` 与 `50741ad4b4b0934d.LLVMType` 前缀相同,同出
includes/llvmlite/__types.vp)。结构体分支迭代 Gen.structs 时先命中 ParamNode
(它在 __types.vp 里声明更早)⇒ `_get_member_offset('Pointee','ParamNode')`
直接 fast-fail,报出与真因无关的类名。判据收紧为:**同一对象** 或 **IR 全名逐字相同**
(前缀标识的是模块,不是类)。同短名不同前缀(同一类的另一模块实例)仍由调用点的
`_UniqShortCN`(同短名候选唯一)放行,不受影响。
2) REnum 的变体字段在旧路径上无处可取:'Pointee' 只属变体键 'LLVMType_Ptr',
而主类 'LLVMType' 的成员表为空(HandlesClassDef:716)⇒ 即使接收者判对类名也必 fail-fast。
新增 `FuncGen._RenumVariantFieldOwner`,在 `_get_member_offset` 里当"主类不声明该字段"时改按
变体键取偏移。判据(逐条可查):① 主类经 `_IsRenumLayoutClass`(唯一判据源)判为 REnum;
② 变体键固定 `<主类短名>_<变体名>` 且成员表**首项恒为 `__tag`**(唯一产源:
HandlesClassDef._EmitREnumLlvm 与 HandlesImports 的 REnum 跨模块注册);
③ 恰有**一个**变体声明该字段(多个 ⇒ 不猜,照常 fail-fast);④ 变体 struct 已注册。
槽位正确的依据:变体 struct 的 body 是**主 REnum 镜像** `{i32 __tag, <max_variant_payload>}`,
变体成员表 `[__tag] + 载荷` 与主 REnum 槽位一一对应。
3) 载荷槽按**最大变体**宽度物化('Pointee' 槽是 i64),而字段本身声明为指针 ⇒
新增 `HandlesExprAttr._RenumVariantPayloadType` / `_CoerceVariantPayload`,在
`_gep_and_Load_member` 载入后按字段声明类型收口(int→ptr: inttoptr;ptr→ptr: bitcast;
int→int: trunc/zext;不可转换原样返回,交下游照常 fail-fast)。非 REnum 变体键逐字不变。
冒烟(临时脚本已删):_PointeeMatchesStruct 5 例、_RenumVariantFieldOwner 7 例、
载荷类型与转换 6 例、_get_member_offset 钩子(绑定真实方法)5 例 —— 全部通过。
|
2026-09-30 12:46:37 +08:00 |
|
TermiNexus
|
a1c43a7b3b
|
chore(诊断): MemberLookupFailFast 补取证——字段所有者、类键/实例名、登记为该类的变量与实际 pointee
现场:CallGenDispatch.vp:787 `len_ap: llvmlite.LLVMType* = len_val.Ty.Pointee`
报 "[fast-fail] 属性 'Pointee' 在类 'ParamNode' 中找不到(class_members=2 个:
['Next','Ty'])"。'Pointee' 实际属 LLVMType 的 REnum 变体镜像字段(LLVMType.Ptr.Pointee),
而 ParamNode 只有 Next/Ty ⇒ 说明**读取时的接收者类名被判成了 ParamNode**
(`len_val` 的注解与 RHS 都写的是 `llvmlite.Value*`,Value.Ty 才是 rawptr[LLVMType];
ParamNode.Ty 是裸 rawptr = i8*)。
本轮只补取证、不改行为,让下一轮能一次定位"是谁把接收者记成了 ParamNode":
① 本类键 → struct 实例 IR 名(键/实例名不一致 ⇒ 同一类名两份实例);
② 其它**声明了该字段**的类键列表(唯一者即"本该用的类");
③ var_struct_class 中登记为**本类**的变量(封顶 6 个)及其实际 pointee IR 名
—— 记录类名与实际 IR 类型不一致时,直接圈定记录点。
验证:py_compile 通过。
|
2026-09-30 12:26:05 +08:00 |
|
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 |
|