【AI 使用案例】如何使用 AI 进行代码调试

背景说明

在编译器中端构图开发中,需要处理字节码之间的关系。字节码在进入构图阶段前已进行 RPO(Reverse Post Order)排序。当遇到 fall through(顺序执行到下一个字节码)或跳转时,需要正确设置当前基本块(CurrentBlock)。

如果 CurrentBlock 未被正确设置,会导致没有联系的两条字节码,使用到同一个块。


案例一:RPO 排序后 Fall Through 路径错误

案例背景

测试代码(switch 表达式)

function switch_expr_test(x) {
    let result = 0;
    switch (true) {
        case x > 0 && x < 3:
            result += 100;
            break;
        case x >= 3 && x < 5:
            result += 200;
            break;
        default:
            result += 0;
            break;
    }
    return result;
}

崩溃信息

ASSERTION FAILED: index < GetInputCount()
IN ../../arkcompiler/ets_runtime/ecmascript/arksteed/arksteed_vertex.h:800: SetInput
Backtrace [tid=2076632]:

Thread 7 "OS_JIT_Thread" received signal SIGABRT, Aborted.
[Switching to Thread 0x7fffd173a640 (LWP 2076632)]
__pthread_kill_implementation (no_tid=0, signo=6, threadid=140736707405376) at ./nptl/pthread_kill.c:44
44      ./nptl/pthread_kill.c: No such file or directory.
(gdb) bt
#0  __pthread_kill_implementation (no_tid=0, signo=6, threadid=140736707405376) at ./nptl/pthread_kill.c:44
#1  __pthread_kill_internal (signo=6, threadid=140736707405376) at ./nptl/pthread_kill.c:78
#2  __GI___pthread_kill (signo=6, threadid=140736707405376) at ./nptl/pthread_kill.c:89
#3  0x00007ffff3982476 in __GI_raise (sig=sig@entry=6) at ../sysdeps/posix/raise.c:26
#4  0x00007ffff39687f3 in __GI_abort () at ./stdlib/abort.c:79
#5  0x00007ffff6e20365 in panda::debug::AssertionFail (expr=0x7fffc7371851 "index < GetInputCount()", file=<optimized out>, line=800, function=0x7fffc73511c8 "SetInput") at ../../arkcompiler/runtime_core/libpandabase/utils/debug.cpp:31
#6  0x00007fffcb4753b5 in panda::ecmascript::arksteed::Vertex::SetInput (this=0x7fff7c011c80, index=1, vertex=0x7fff7c00ce50) at ../../arkcompiler/ets_runtime/ecmascript/arksteed/arksteed_vertex.h:800
#7  0x00007fffcb485771 in panda::ecmascript::arksteed::MergePointInterpreterFrameState::MergeValue (this=0x7fff7c010bf0, reg=..., merged=0x7fff7c011b08, unmerged=0x7fff7c00ce50)
    at ../../arkcompiler/ets_runtime/ecmascript/arksteed/arksteed_framestate.cpp:188
#8  0x00007fffcb48763d in panda::ecmascript::arksteed::MergePointInterpreterFrameState::MergePhis(panda::ecmascript::arksteed::InterpreterFrameState&, panda::ecmascript::arksteed::BB*)::$_3::operator()(panda::ecmascript::arksteed::ValueVertex*&, panda::ecmascript::kungfu::VirtualRegister) const (this=0x7fffd1736ab8, value=@0x7fff7c011078: 0x7fff7c011b08, reg=...) at ../../arkcompiler/ets_runtime/ecmascript/arksteed/arksteed_framestate.cpp:199
#9  0x00007fffcb4876a8 in panda::ecmascript::arksteed::CompactInterpreterFrameState::ForEachParameter<panda::ecmascript::arksteed::MergePointInterpreterFrameState::MergePhis(panda::ecmascript::arksteed::InterpreterFrameState&, panda::ecmascript::arksteed::BB*)::$_3&>(unsigned long, panda::ecmascript::arksteed::MergePointInterpreterFrameState::MergePhis(panda::ecmascript::arksteed::InterpreterFrameState&, panda::ecmascript::arksteed::BB*)::$_3&) const (this=0x7fff7c010c40,
    numParams=7, f=...) at ../../arkcompiler/ets_runtime/ecmascript/arksteed/arksteed_framestate.h:193
#10 0x00007fffcb48759f in panda::ecmascript::arksteed::CompactInterpreterFrameState::ForEachRegister<panda::ecmascript::arksteed::MergePointInterpreterFrameState::MergePhis(panda::ecmascript::arksteed::InterpreterFrameState&, panda::ecmascript::arksteed::BB*)::$_3&>(unsigned long, unsigned long, panda::ecmascript::arksteed::MergePointInterpreterFrameState::MergePhis(panda::ecmascript::arksteed::InterpreterFrameState&, panda::ecmascript::arksteed::BB*)::$_3&) (
    this=0x7fff7c010c40, numVregs=8, f=...) at ../../arkcompiler/ets_runtime/ecmascript/arksteed/arksteed_framestate.h:217
#11 0x00007fffcb4859f3 in panda::ecmascript::arksteed::CompactInterpreterFrameState::ForEachValue<panda::ecmascript::arksteed::MergePointInterpreterFrameState::MergePhis(panda::ecmascript::arksteed::InterpreterFrameState&, panda::ecmascript::arksteed::BB*)::$_3>(unsigned long, unsigned long, panda::ecmascript::arksteed::MergePointInterpreterFrameState::MergePhis(panda::ecmascript::arksteed::InterpreterFrameState&, panda::ecmascript::arksteed::BB*)::$_3&&) (this=0x7fff7c010c40,
    numParams=7, numVregs=8, f=...) at ../../arkcompiler/ets_runtime/ecmascript/arksteed/arksteed_framestate.h:231
#12 0x00007fffcb48562b in panda::ecmascript::arksteed::MergePointInterpreterFrameState::MergePhis (this=0x7fff7c010bf0, unmerged=..., predecessor=0x7fff7c011b08)
    at ../../arkcompiler/ets_runtime/ecmascript/arksteed/arksteed_framestate.cpp:197
#13 0x00007fffcb485466 in panda::ecmascript::arksteed::MergePointInterpreterFrameState::Merge (this=0x7fff7c010bf0, unmerged=..., predecessor=0x7fff7c011b08) at ../../arkcompiler/ets_runtime/ecmascript/arksteed/arksteed_framestate.cpp:155
#14 0x00007fffcb46ac26 in panda::ecmascript::arksteed::ArkSteedGraphBuilder::BuildBody (this=0x7fffd1736d40) at ../../arkcompiler/ets_runtime/ecmascript/arksteed/arksteed_graph_builder.cpp:172
#15 0x00007fffcb46a4b6 in panda::ecmascript::arksteed::ArkSteedGraphBuilder::Build (this=0x7fffd1736d40) at ../../arkcompiler/ets_runtime/ecmascript/arksteed/arksteed_graph_builder.cpp:40
#16 0x00007fffcb4655fb in panda::ecmascript::arksteed::ArkSteedCompilerTask::Compile (this=0x5555557d4820) at ../../arkcompiler/ets_runtime/ecmascript/arksteed/arksteed_compiler.cpp:62
#17 0x00007fffcb465b85 in ArkSteedCompile (compilerTask=0x5555557d4820, arkSteedTask=0x5555557d4670) at ../../arkcompiler/ets_runtime/ecmascript/arksteed/arksteed_compiler.cpp:120
#18 0x00007ffff75a3599 in panda::ecmascript::JitResources::Compile (this=0x55555566aa80, compiler=0x5555557d4820, jitTask=0x5555557d4670) at ../../arkcompiler/ets_runtime/ecmascript/jit/jit_resources.h:47
#19 0x00007ffff75a3554 in panda::ecmascript::Jit::JitCompile (this=0x7ffff7f92f78 <panda::ecmascript::Jit::GetInstance()::instance_>, compiler=0x5555557d4820, jitTask=0x5555557d4670) at ../../arkcompiler/ets_runtime/ecmascript/jit/jit.cpp:369
#20 0x00007ffff75b5781 in panda::ecmascript::JitTask::Optimize (this=0x5555557d4670) at ../../arkcompiler/ets_runtime/ecmascript/jit/jit_task.cpp:75
#21 0x00007ffff75b8584 in panda::ecmascript::JitTask::AsyncTask::Run (this=0x5555557d4c80, threadIndex=1) at ../../arkcompiler/ets_runtime/ecmascript/jit/jit_task.cpp:471
#22 0x00007ffff7c0a7ee in common::Runner::Run (this=0x5555556ceb10, threadId=1) at ../../arkcompiler/ets_runtime/common_components/taskpool/runner.cpp:135
#23 0x00007ffff7c0b47e in common::Runner::Runner(unsigned int, std::function<void (unsigned long)>, std::function<void (unsigned long)>)::$_0::operator()() const (this=0x55555579dce8) at ../../arkcompiler/ets_runtime/common_components/taskpool/runner.cpp:32
#24 0x00007ffff7c0b455 in std::__invoke_impl<void, common::Runner::Runner(unsigned int, std::function<void (unsigned long)>, std::function<void (unsigned long)>)::$_0>(std::__invoke_other, common::Runner::Runner(unsigned int, std::function<void (unsigned long)>, std::function<void (unsigned long)>)::$_0&&) (__f=...) at /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/invoke.h:61
#25 0x00007ffff7c0b415 in std::__invoke<common::Runner::Runner(unsigned int, std::function<void (unsigned long)>, std::function<void (unsigned long)>)::$_0>(std::__invoke_other, common::Runner::Runner(unsigned int, std::function<void (unsigned long)>, std::function<void (unsigned long)>)::$_0&&) (__fn=...) at /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/invoke.h:96
#26 0x00007ffff7c0b3ed in std::thread::_Invoker<std::tuple<common::Runner::Runner(unsigned int, std::function<void (unsigned long)>, std::function<void (unsigned long)>)::$_0> >::M_invoke<0ul>(std::_Index_tuple<0ul>) (this=0x55555579dce8) at /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/std_thread.h:259
#27 0x00007ffff7c0b3c5 in std::thread::_Invoker<std::tuple<common::Runner::Runner(unsigned int, std::function<void (unsigned long)>, std::function<void (unsigned long)>)::$_0> >::operator()() (this=0x55555579dce8) at /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/std_thread.h:266
#28 0x00007ffff7c0b319 in std::thread::_State_impl<std::thread::_Invoker<std::tuple<common::Runner::Runner(unsigned int, std::function<void (unsigned long)>, std::function<void (unsigned long)>)::$_0> > >::_M_run() (this=0x55555579dce0) at /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/std_thread.h:211
#29 0x00007ffff3c65253 in ?? () from /lib/x86_64-linux-gnu/libstdc++.so.6
#30 0x00007ffff39d4ac3 in start_thread (arg=<optimized out>) at ./nptl/pthread_create.c:442
#31 0x00007ffff3a668d0 in clone3 () at ../sysdeps/unix/sysv/linux/x86_64/clone3.S:81
(gdb) f 14
#14 0x00007fffcb46ac26 in panda::ecmascript::arksteed::ArkSteedGraphBuilder::BuildBody (this=0x7fffd1736d40) at ../../arkcompiler/ets_runtime/ecmascript/arksteed/arksteed_graph_builder.cpp:172
172                     mergeStates_[index]->Merge(*currentFrameState_, prevBlock);
(gdb) p index
$1 = 32

问题描述
在 ArkSteed 字节码解释器中,字节码在进入构图阶段前会进行 RPO(Reverse Post Order)排序。RPO 后,字节码的执行顺序会发生变化,导致原来的物理顺序(相邻字节码)不再是 RPO 顺序中的相邻位置。

现象

  • 字节码 35 (lda v1) → 36 (add2) → 37 (return) 是物理顺序
  • 但 RPO 后,字节码 36 可能会 fallthrough 到一个不相关的块(如 32)

根本原因
nextRPOIndex != fallThroughIndex 时,说明 RPO 顺序中当前块的"下一个块"不是物理顺序上的下一个字节码,需要通过 Jump 来处理这个跳转。


编译后的字节码结构

.function any test.#*#switch_expr_test(any a0, any a1, any a2, any a3) {
label_13:
label_0:
        ldai 0x3
        sta v0
        ldai 0x0
        sta v1
        ldtrue
        sta v2
        lda v1
        greater 0x0, a3
        sta v3
        callruntime.isfalse 0x1
        jnez label_3                -- 10
label_2:
        lda v0
        less 0x2, a3
        sta v3
label_3:
        lda v3
        strictnoteq 0x3, v2
        jeqz label_4
label_5:
        lda v0
        greatereq 0x4, a3
        sta v0
        callruntime.isfalse 0x5     -- 20
        jnez label_8
label_7:
        ldai 0x5
        less 0x6, a3
        sta v0
label_8:
        lda v0
        strictnoteq 0x7, v2
        jeqz label_9
        jmp label_11
label_4:
        ldai 0x64
        add2 0x8, v1            --- 30
        jmp label_12
label_9:
        ldai 0xc8               --- 32
        add2 0x9, v1
        jmp label_12
label_11:
        lda v1                  --- 35
        add2 0xa, v1            --- 36
label_12:
        return                  --- 37
label_14:
}

字节码与源码对应表(行号从0开始)

行号 字节码 源码对应
0 ldai 0x3 测试调用传入的参数 (switch_expr_test(3))
1 sta v0 v0 = x
2 ldai 0x0 let result = 0
3 sta v1 v1 = result = 0
4 ldtrue switch (true)
5 sta v2 v2 = true
6 lda v1 准备计算 x > 0
7 greater 0x0, a3 计算 x > 0
8 sta v3 v3 = (x > 0)
9 callruntime.isfalse 0x1 检查条件是否为false
10 jnez label_3 若不为false则跳到label_3继续判断 x < 3
11 lda v0 准备计算 x < 3
12 less 0x2, a3 计算 x < 3
13 sta v3 v3 = (x < 3),此时v3 = (x > 0) && (x < 3)
14 lda v3 准备判断整个case条件
15 strictnoteq 0x3, v2 比较 v3 !== true
16 jeqz label_4 若相等(条件为true),跳到label_4执行 result += 100
17 lda v0 准备计算第二个case x >= 3
18 greatereq 0x4, a3 计算 x >= 3
19 sta v0 临时存结果
20 callruntime.isfalse 0x5 检查是否为false
21 jnez label_8 若不为false则跳到label_8
22 ldai 0x5 准备计算 x < 5
23 less 0x6, a3 计算 x < 5
24 sta v0 存结果
25 lda v0 准备判断
26 strictjoteq 0x7, v2 比较 v0 === true
27 jeqz label_9 若相等,跳到label_9执行 result += 200
28 jmp label_11 跳到default分支
29 ldai 0x64 case 1: result += 100 (0x64=100)
30 add2 0x8, v1 v1 = v1 + 100
31 jmp label_12 break,跳出switch
32 ldai 0xc8 case 2: result += 200 (0xC8=200)
33 add2 0x9, v1 v1 = v1 + 200
34 jmp label_12 break
35 lda v1 default: 准备加0
36 add2 0xa, v1 v1 = v1 + 0
37 return return result

崩溃信息

ASSERTION FAILED: index < GetInputCount()
IN ../../arkcompiler/ets_runtime/ecmascript/arksteed/arksteed_vertex.h:736: SetInput

崩溃栈:
#14 BuildBody at arksteed_graph_builder.cpp:191
  -> mergeStates_[index]->Merge(*currentFrameState_, prevBlock);

含义:在设置 Vertex 输入时,index 超出了该 Vertex 的输入数量

问题代码

arksteed_graph_builder.cpp:178-187 - 原有代码没有处理 RPO 后的 fall through 情况:

ProcessBytecode();

// ❌ 缺失:RPO 后的 fall through 处理

问题根因深度分析

在 ArkSteed 字节码解释器中,字节码在进入构图阶段前会进行 RPO(Reverse Post Order)排序。RPO 后,字节码的执行顺序会发生变化。

核心问题

  • 字节码 35 → 36 → 37 是物理顺序
  • 但 RPO 后,字节码 36 可能会 fallthrough 到一个不相关的块
  • nextRPOIndex != fallThroughIndex 时,说明 RPO 顺序中当前块的"下一个块"不是物理顺序上的下一个字节码

修复方案

// arksteed_graph_builder.cpp:178-187
ProcessBytecode();

if (!iterator_.Done() && bytecodeInfo.needFallThrough()) {
    uint32_t nextRPOIndex = iterator_.NextRPOIndex();
    uint32_t fallThroughIndex = iterator_.NextIndex();
    if (nextRPOIndex != fallThroughIndex) { // Jump
        BB* block = FinishBlock<JumpVertex>({}, &jumpTargets_[fallThroughIndex]);
        MergeIntoFrameState(block, fallThroughIndex);
    }
}

问题验证

修复后:

  • 当 RPO 顺序与物理顺序一致时:不创建额外跳转
  • 当 RPO 顺序发生跳转时:显式创建 JumpVertex 连接到目标块

修改的完整代码

diff --git a/ecmascript/arksteed/arksteed_bytecode_iterator.h b/ecmascript/arksteed/arksteed_bytecode_iterator.h
index 333e2bc39..6c903b4d0 100644
--- a/ecmascript/arksteed/arksteed_bytecode_iterator.h
+++ b/ecmascript/arksteed/arksteed_bytecode_iterator.h
@@ -89,6 +89,12 @@ public:
         return (*postOrderList_)[pos_] + 1;
     }

+    uint32_t NextRPOIndex() const
+    {
+        ASSERT(!Done());
+        return (*postOrderList_)[pos_ + 1];
+    }
+
     BytecodeInfo GetCurrentBytecodeInfo() const
     {
         uint32_t idx = Index();
diff --git a/ecmascript/arksteed/arksteed_graph_builder.cpp b/ecmascript/arksteed/arksteed_graph_builder.cpp
index 80f7ed3b9..fdf493f2c 100644
--- a/ecmascript/arksteed/arksteed_graph_builder.cpp
+++ b/ecmascript/arksteed/arksteed_graph_builder.cpp
@@ -161,6 +161,7 @@ bool ArkSteedGraphBuilder::BuildBody()
 {
     for (iterator_.GotoStart(); !iterator_.Done(); ++iterator_) {
         uint32_t index = iterator_.Index();
+        auto bytecodeInfo = iterator_.GetCurrentBytecodeInfo();

         MergePointInterpreterFrameState* mergeState = mergeStates_[index];
         if (mergeState != nullptr) {
@@ -175,6 +176,15 @@ bool ArkSteedGraphBuilder::BuildBody()
         }

         ProcessBytecode();
+
+        if (!iterator_.Done() && bytecodeInfo.needFallThrough()) {
+            uint32_t nextRPOIndex = iterator_.NextRPOIndex();
+            uint32_t fallThroughIndex = iterator_.NextIndex();
+            if (nextRPOIndex != fallThroughIndex) { // Jump
+                BB* block = FinishBlock<JumpVertex>({}, &jumpTargets_[fallThroughIndex]);
+                MergeIntoFrameState(block, fallThroughIndex);
+            }
+        }
     }
     return true;
 }

AI 辅助调试过程

第一步:AI 分析崩溃的原因

Q: 直接粘贴崩溃栈信息

A: 运行时Index=32和静态分析出来的前驱数量不同。

第二步:分析字节码和源码的对应关系

Q: 帮我分析一下字节码和源代码的对应关系(直接粘贴字节码和 test.ts 代码)

A: [AI 给出了字节码与源码对应表]

Q: 重新对应,行号从0开始,label 不算字节码,比如 add2 0x8, v1 是第 30 条字节码

A: [AI 给出了修正后的字节码与源码对应表]

第三步:修复代码

Q: 发现是 RPO 排序后,fall through 路径错误。请帮我看看当前的修改正确吗?(直接粘贴 git diff 出来的代码修改)

A: 分析了修改逻辑,确认是正确的。解释了 needFallThrough()、NextIndex()、NextRPOIndex() 的作用。


效率对比

调试方式 传统调试 AI 辅助调试
分析崩溃原因 需要阅读大量代码猜测 直接定位到前驱数量不匹配
理解字节码 手动逐行对应(约30分钟) AI 快速给出对应表(约1分钟)
代码修改 自己验证逻辑 AI 检查修改是否正确

时间节省估算

阶段 传统调试 AI 辅助调试 提升
原因分析 1 小时 20 分钟 3x
字节码对应 30 分钟 1 分钟 30x
方案验证 10 分钟 2 分钟 5x

思考

  1. 原因分析阶段,直接给出崩溃栈,AI 能快速分析出根因范围
  2. 字节码对应阶段,粘贴字节码和源码。AI 特别擅长做映射类工作,节省大量手动对应时间
  3. 方案验证阶段,写出发现的问题,请帮我看看当前的修改正确吗?AI 能快速理解改动并验证
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐