Files
TransPyC/debug-stmtgen-split-crash.md
T
2026-08-22 02:35:30 +08:00

7.7 KiB
Raw Blame History

Debug: StmtGenMixin 拆分后 FuncTest 运行时崩溃

Session ID: stmtgen-split-crash Status: [OPEN] Created: 2026-08-17

症状

  • 拆分 StmtGenMixin.vp → 3 文件(顶层 763 / StmtGenDeclMixin 512 / StmtGenCifMixin 520)后
  • PhaseC SUCCESS(FuncTest.exe 编译链接成功)
  • 运行时输出:
    === FuncTest: 函数调用基准测试 ===
    --- Test 1: basic function call ---
    PASS: add(10,20)=30
    FAIL: add(10,20) expect 30
    然后崩溃
    

回归窗口

  • 拆分前(git restore 的原始 StmtGenMixin.vp 1715 行)FuncTest 应正常
  • 拆分改动:类结构 StmtGenMixin → StmtGenDeclMixin → StmtGenCifMixin 三级继承;字段全留最底层 Cif;方法按功能域分 3 文件

根因(已确认,证据充分)

崩溃根因:FuncGenMixin/ClassGenMixin 对"末句控制流"无条件生成 unreachable(追加89),而 llc O2 将 br label %merge(merge: unreachable)优化为 fall-through → 双分支执行。

  • 证据链:
    1. FuncTest check 机器码:testl %ecx; je fail; callq ok; (fall-through) callq fail —— ok 后无跳转直接执行 fail → PASS+FAIL 双打印 + int3 崩溃
    2. check IR:br label %if.end.4 + if.end.4: unreachable(merge 可达却被判 unreachable)
    3. [HandlesIf.vp L302-315]:分支未 return 时 br 到 merge(merge 可达);全 return 时不 br(不可达)
    4. FuncGenMixin/ClassGenMixin 判定 last_is_ctrl → build_unreachable 不区分 merge 可达性 → bug
  • 与 StmtGenMixin 拆分逻辑无关(HandlesIf/FuncGenMixin/ClassGenMixin 均未拆分);拆分暴露了该 bug(FuncTest check 是"末句 if + 无注解 + merge 可达"函数)

修复(追加90,7 文件)

  1. HandlesTranslator.vp:Translator 加字段 _ctrl_merge_reachable(控制流 merge 可达性)
  2. HandlesIf.vp:结尾按 then/else 分支 br 情况设置(无 else 恒 1;有 else 任一 br → 1;全 return → 0)
  3. HandlesWhile.vp:cond_br 恒引用 end_bb → 设 1
  4. HandlesFor.vp:7 个迭代器 end_bb 处设 1
  5. HandlesMatch.vp:Handle 开头重置 0;br end_bb 处设 1
  6. FuncGenMixin.vp L1140:last_is_ctrl 且 _ctrl_merge_reachable == 0 才 unreachable;可达 → 正常收尾(ConstZero/fail-fast)
  7. ClassGenMixin.vp L4536:方法收尾路径同步

验证要点:check(merge 可达)→ ConstZero ret;get_struct_name(全 case return)→ unreachable 保持

修正(追加90 v2:避免循环依赖)

  • 初版缺陷:把 _ctrl_merge_reachable 放 Translator 实例字段 → HandlesMatch/If/While/For 方法体引用 trans._ctrl_merge_reachable 需 Translator 字段表,而 HandlesTranslator 依赖 HandlesMatch.NewMatchHandle → HandlesMatch ↔ HandlesTranslator 循环依赖 → TPC 声明顺序死锁 → MatchHandle struct opaque、方法/工厂符号全缺失 → 连锁报错(1.LOG 证据)
  • 修正 v2:改放 HandlesBase 模块级全局 + SetCtrlMergeReachable(val) / GetCtrlMergeReachable()(无 struct 依赖,无循环;跨模块函数调用仅依赖 Phase1 stub)
    • HandlesTranslator.vp:字段回滚(已删)
    • HandlesBase.vp:加 _ctrl_merge_reachable + Set/Get 函数
    • HandlesIf/While/For/Match:调用 HandlesBase.SetCtrlMergeReachable(1/0)
    • FuncGenMixin/ClassGenMixin:HandlesBase.GetCtrlMergeReachable() == 0 + import HandlesBase
  • 已知残余风险:模块级全局在多线程并行编译(Phase2Translator._parallel_translate_worker)下有竞争(_g_cif_eval_ok 先例同样存在且工作正常,暂接受)

真正的根因(1.LOG 定位,v3 修正)

  • 1.LOG L3:HandlesMatch.vp: invalid syntax (line 976)——批量脚本 _patch_ctrl.py 的 replace 把 HandlesBase.SetCtrlMergeReachable(1) 误插到 if nb_arr[ci]==0: br end_bb 与 else: 之间 → else 悬空语法错误
  • 语法错误使 TPC 无法解析 HandlesMatch.vp → MatchHandle struct opaque、Handle 方法无声明、NewMatchHandle 符号 undefined → 依赖它的 StmtGenCifMixin(NewMatchHandle 内 h.Trans = trans)/HandlesTranslator 连锁报错
  • v1(Translator 字段)/v2(HandlesBase 全局)都报同一 MatchHandle 错误的原因:真正的障碍一直是语法错误,不是依赖方案
  • 修正 v3:删除 HandlesMatch.vp L975 误插行,if-else 结构恢复;其余 8 处 SetCtrlMergeReachable 位置已验证合法

最终方案(v1 定稿,重建成功)

  • v1/v2 的 MatchHandle 错误其实是 L975 语法错误(误判为循环依赖)

  • v3(HandlesBase 模块级全局)编译通过但 TPV 编译器运行时崩(win32base Struct 收到全垃圾参数 → pool 悬空,插桩 DBG-ST 证实)

  • 定稿 v1:trans._ctrl_merge_reachable 字段(线程安全,per-trans 无全局共享)+ 控制流翻译器写 + FuncGen/ClassGen 判定读

  • 2026-08-17 用户验证:清缓存重建成功 ✓

  • 待验证:FuncTest 运行(check 不再 PASS+FAIL 双打印)、68 测试回归

  • H1 跨模块继承字段布局错位:Generator 组合布局中 StmtGenMixin 链字段块位置与各 Mixin 独立布局不一致 → self 字段访问偏移错误 → 函数表/状态错乱

  • H2 跨模块继承 struct 加载失败:TPV 编译 Generator.vp 时从 vpi/stub 加载 StmtGenCifMixin 字段定义失败(opaque)→ 组合布局损坏

  • H3 self.method 沿 MRO 的 this 调整错误:translate_stmt 调 Decl/Cif 方法时 this_adjust 偏移算错 → 方法内字段访问错乱

  • H4 模块级符号跨文件解析:顶层/Decl 方法引用的模块级常量(ENUM_MAX 等)或符号解析失败降级 → 编译期静默错误

  • H5 编译链未清缓存:tpv.exe 为旧产物(未含拆分逻辑)或新文件依赖顺序错误 → 生成代码错误

证据计划

  1. 确认用户验证方式(是否 --clean --rebuild-includes --clear-cache + Phase A 全量重建)
  2. 确认拆分前 FuncTest 是否正常(回归基线)
  3. 插桩定位崩溃点与字段偏移(TPV 侧或 FuncTest 侧)

状态日志

2026-08-17 证据收集 2(TPV 自身产物 + asan 发现)

  • TPV 自身编译产物(output/0d5dfc518e9dbf1c.ll 4:54:31):
    • 三 class struct 布局完全一致:{ i64, EnumEntry*, i32, i32 } ✓
    • translate_stmt 跨 Mixin 调用正确:bitcast StmtGenMixin* → StmtGenDeclMixin* + 调用 9df7bd403cb4eb0a.StmtGenDeclMixin.translate_expr_stmt(L4195-4196)✓
    • TPV 编译器自身(拆分后)编译正确
  • 新证据:FuncTest.exe 是 ASan 插桩版!
    • output/06/067f811511854c88.asan.ll 存在(4:54:45),内容是 LLVM bitcode(BC 头,非文本 IR)
    • FuncTest.exe 含 __asan_init/__asan_report 符号
    • project.json linker flags 无 asan 运行时库(只有 msvcrt/ucrt/pthread/mingwex/kernel32/user32)
  • 新假设 H6:崩溃是 --asan 编译模式导致(main 插桩 + asan 运行时未正确初始化/链接),与 StmtGenMixin 拆分无关。拆分前 68 测试全过可能不带 --asan
  • 待确认:用户编译 FuncTest 的命令是否带 --asan;拆分前测试是否带 --asan
  • 分析 Test/FuncTest/.tpv_build/temp 的 .ll(4:46 产物):
    • 067f811511854c88.ll L535:call i32 @add(i32 10, i32 20) ✓ 参数正确
    • 748d0424e8976fa0.ll L271-293(check):br i1 %4, if.then.4/if.else.4 ✓ 条件跳转正确;ok/fail 各打印一次 ✓
    • ok/fail 函数 L245-269:单次 printf ✓
  • 结论:FuncTest 生成代码完全正常。PASS+FAIL 双打印+崩溃非生成代码所致
  • 倾向 H5:编译链产物混用(tpv.exe 未含拆分逻辑的旧产物/缓存复用);用户正在重编译
  • 下一步:用户完成"清缓存 + Phase A 全量重建"后重跑 FuncTest 验证