手搓JSVM第 7 篇:控制流:if、while 与 jump

源码仓库

1. 本文目标

前面我们已经能编译和执行直线代码:

javascript 复制代码
let a = 1 + 2;
a;

但真实程序会有分支和循环:

javascript 复制代码
let x = 1;
if (x) {
  x = 2;
}
x;

本篇目标是让 VM 支持控制流:

  1. label

  2. jump

  3. jump_if_false

  4. if

  5. while

2. 为什么控制流需要 jump

直线代码像一条直路:

plaintext 复制代码
1 -> 2 -> 3 -> 4

ifwhile 像岔路和回环:

plaintext 复制代码
如果条件成立,走 A 路;否则跳到 B 路。

形象化比喻:游戏地图传送门

程序计数器 pc 像玩家在地图上的位置,普通指令让玩家向前走一步。jump 像传送门:

plaintext 复制代码
不走下一格,直接传送到某个 label。

jump_if_false 像条件传送门:

plaintext 复制代码
如果钥匙不对,就传送到出口。

3. 前置知识

概念 说明
Label 给 bytecode 某个位置起名
Jump 无条件跳转
Conditional Jump 条件不满足时跳转
Basic Block 一段顺序执行的指令
Control Flow Graph 基本块之间的跳转关系

4. 源码中的对应位置

概念 源码位置 说明
jump / jump_if_false src/compiler/ir.ts IR 控制流指令
label / fixup src/compiler/emit.ts label 记录地址,fixup 回填跳转目标
runtime jump src/compiler/runtime-gen.ts 修改 pc 完成跳转
lowering if/while src/compiler/lowering.ts 把高级语句降级成 label 和 jump

5. 核心数据结构

Label

它解决什么问题

跳转目标先用名字表示,emit 阶段再统一变成数字地址。

教学版简化实现

javascript 复制代码
{ op: 'label', name: 'end_if' }

Jump

它解决什么问题

让 VM 不再顺序执行下一条指令,而是直接把 pc 改成目标地址。

教学版简化实现

javascript 复制代码
{ op: 'jump', target: 'loop_start' }

Jump If False

它解决什么问题

根据条件的真假决定是否跳转:条件为假时跳走,为真时继续往下执行。

教学版简化实现

javascript 复制代码
{ op: 'jump_if_false', condition: 0, target: 'end_if' }

6. Mermaid 图解

if 控制流

flowchart TD A["计算 condition"] --> B{"condition truthy?"} B -->|true| C["执行 then block"] B -->|false| D["跳到 end_if"] C --> D D["end_if"]

while 控制流

flowchart TD A["loop_start"] --> B["计算 condition"] B --> C{"condition truthy?"} C -->|false| D["loop_end"] C -->|true| E["执行 body"] E --> A

7. 伪代码

编译 if

plaintext 复制代码
compileIf(node):
    endLabel = newLabel('if_end')
    conditionReg = compileExpression(node.test)
    emit jump_if_false conditionReg, endLabel
    compileStatement(node.consequent)
    emit label endLabel

编译 while

plaintext 复制代码
compileWhile(node):
    startLabel = newLabel('while_start')
    endLabel = newLabel('while_end')

    emit label startLabel
    conditionReg = compileExpression(node.test)
    emit jump_if_false conditionReg, endLabel
    compileStatement(node.body)
    emit jump startLabel
    emit label endLabel

8. 教学版实现代码

javascript 复制代码
function compileIf(node, builder) {
  const end = builder.label('if_end');
  const condition = compileExpression(node.test, builder);
  builder.emit({ op: 'jump_if_false', condition, target: end });
  compileStatement(node.consequent, builder);
  builder.emit({ op: 'label', name: end });
}

function compileWhile(node, builder) {
  const start = builder.label('while_start');
  const end = builder.label('while_end');

  builder.emit({ op: 'label', name: start });
  const condition = compileExpression(node.test, builder);
  builder.emit({ op: 'jump_if_false', condition, target: end });
  compileStatement(node.body, builder);
  builder.emit({ op: 'jump', target: start });
  builder.emit({ op: 'label', name: end });
}

Runtime 执行 jump

javascript 复制代码
case OPCODES.JUMP:
  pc = bytecode[pc];
  break;

case OPCODES.JUMP_IF_FALSE: {
  const conditionReg = bytecode[pc++];
  const target = bytecode[pc++];
  if (!regs[conditionReg]) pc = target;
  break;
}

9. 示例输入与输出

javascript 复制代码
let x = 1;
if (x) {
  x = 2;
}
x;

简化 IR:

plaintext 复制代码
load_const r0, 1
init_slot slot0, r0
load_slot r1, slot0
jump_if_false r1, if_end
load_const r2, 2
store_slot slot0, r2
label if_end
load_slot r3, slot0
return r3

输出:

javascript 复制代码
2

10. 执行过程拆解

Step Instruction x 说明
1 load_const 1 uninit 准备初始值
2 init_slot x 1 初始化 x
3 load_slot x 1 读取条件
4 jump_if_false 1 条件为真,不跳
5 store_slot x = 2 2 更新 x
6 return x 2 返回 2

11. 与原始源码的差异

主题 教学版 正式源码
if 只支持 then 支持 alternate
while 基本循环 还处理 break/continue/label
label 字符串 label 带函数 ID 的 label 命名
jump fixup 简化 emit 阶段统一回填
truthy !value 使用 JS truthiness

12. 常见问题

Q1:为什么不能直接把 if 编成一个 JS if?

因为 VM 执行的是 bytecode,不是源码。源码里的 if 到了 bytecode 层面,只能表达成 pc 的变化。

Q2:label 是 runtime 真的存在的指令吗?

不是。label 只是编译期标记,emit 阶段会被移除,最终 bytecode 里只留下数字地址。

13. 本文小结

本文让 VM 从直线执行升级到控制流执行:ifwhile 被降级成 label 加 jump,最终都落在 pc 的跳转上。

下一篇将继续讲:

plaintext 复制代码
第 8 篇:函数调用:参数、返回值与调用帧
相关推荐
萧西待水1 小时前
奥赛一本通 1447 靶形数独
算法·深度优先
泯泷3 小时前
那段文字是谁删的?Yjs 14 正式版之前,一套删除归属方案的实现与边界
前端·javascript·算法
hold?fish:palm4 小时前
34 合并K个升序链表
javascript·算法·链表
Navigator_Z6 小时前
LeetCode //C - 1252. Cells with Odd Values in a Matrix
c语言·算法·leetcode
野槐6 小时前
前端项目优化(有了我,会发生...)
前端
幸运小圣6 小时前
PointerEvent 常用属性与事件整理【JavaScript】
开发语言·javascript·ecmascript
aa小小7 小时前
【无标题】
前端
语戚7 小时前
力扣 1621. 大小为K的不重叠线段的数目:动态规划(Java 实现)
java·算法·leetcode·动态规划·力扣·dp
青山木7 小时前
Hot 100 --- 最长有效括号
java·数据结构·算法·leetcode·动态规划
计算机魔术师8 小时前
还在单线程跟 AI 聊天?Claude Code 并行多会话让你一个人干三个人的活
前端