手搓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

if 和 while 像岔路和回环:

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 从直线执行升级到控制流执行:if 和 while 被降级成 label 加 jump,最终都落在 pc 的跳转上。

下一篇将继续讲:

plaintext 复制代码
第 8 篇:函数调用:参数、返回值与调用帧
相关推荐
段一凡-华北理工大学15 分钟前
高炉能源系统智能化:热风炉、煤气与极致能效~系列文章07:高炉煤气:产生规律与全厂用户平衡
java·前端·能源
牧瀬クリスだ43 分钟前
Redis Hash实战:高效存储对象与购物车
java·数据库·redis·算法·哈希算法
程序员大阳43 分钟前
副队长JavaScript教程(55)-DOM节点增删改查
javascript·dom·增删改查·节点·克隆
木井巳1 小时前
【动态规划: 路径问题】下降路径最小和
java·算法·leetcode·动态规划·推荐算法
前端架构师-老李1 小时前
HTML与CSS基础第四课—strong、em、b、i 各表示什么
前端·css·html
king王一帅1 小时前
AI 时代一群人在卷笔记软件,我想先把 Markdown 编辑器的底层做好
前端·ai编程·markdown
用户11481867894841 小时前
盲水印:DCT-QIM 从原理到踩坑
前端
程序0071 小时前
天地图实现收货地址定位
前端·javascript·css
deepseek231 小时前
OpenAI三项科学发现拆解:3-SAT亚指数算法挑战ETH,AI科学声明的验证流水线怎么搭
人工智能·算法·openai