前段时间写了一个栈式虚拟机(Stack-based VM),整理了一份设计文档。本文是对这份文档的解读和总结。

项目代码:https://github.com/xyanrch1024/VM

为什么选栈式架构

虚拟机架构主要有两种:

特性栈式寄存器式
指令长度短(1-3 字节)较长
编译器后端极简需要寄存器分配
解释执行直观,Bug 少指令数少约 30%
JIT 适配较难天然适合

这个项目选择栈式,原因很简单:纯解释执行场景下,栈式实现简单、代码生成方便、不易出错。

总体架构

VM 实例包含以下核心组件:

  • 操作数栈vector<Value>,同时存放局部变量和临时计算值
  • 调用栈vector<CallFrame>,管理函数调用
  • 字符串表 — intern 池,字符串比较退化为指针比较
  • 函数表 — 所有已编译函数的集合
  • PC / FP — 程序计数器和帧指针

值系统

每个 Value 占 8 字节,用 1 字节类型标签 + 7 字节值:

┌──────┬──────────────────────────────┐
│ type │           value               │
│ 1B   │           7B                  │
├──────┼──────────────────────────────┤
│ NIL  │  padding                      │
│ BOOL │  bool                         │
│ INT  │  int64_t                      │
│FLOAT │  double                       │
│STRING│  void* (→ string table)       │
└──────┴──────────────────────────────┘

类型提升规则:INT + FLOAT → FLOAT,字符串 + 走拼接。

指令集(40 条)

指令编码格式:1 字节 opcode + 变长操作数。

分为几大类:

类别数量示例
常量压栈5OP_CONSTANT, OP_NIL
栈操作5OP_DUP, OP_SWAP, OP_ROT
局部变量10OP_LOAD, OP_STORE_0~3
算术运算7OP_ADD, OP_SUB, OP_MUL
比较运算6OP_EQ, OP_LT, OP_GE
位运算6OP_BIT_AND, OP_SHL
逻辑运算1OP_NOT
控制流4OP_JMP, OP_JZ, OP_LOOP
函数调用2OP_CALL, OP_RET
其他4OP_PRINT, OP_HALT

执行模型

操作数栈分为两个区域:

┌───────────────────┐
│   局部变量区域      │  [fp, fp+numLocals)
│   slot 0,1,...,N  │  LOAD/STORE 访问
├───────────────────┤
│   表达式栈          │  [fp+numLocals, sp]
│   临时计算值        │  PUSH/POP 操作
└───────────────────┘

函数调用时:预分配局部变量 → 压入 CallFrame → 执行 → RET 弹出清栈。

内存管理:字符串驻留

所有字符串在加载时放入 intern 表。每次遇到字符串常量,先查 hash map:

已存在?→ 返回已有指针(O(1) 比较)
不存在?→ 创建新副本,加入表,返回稳定指针

编译示例

1 + 2 × 3 的字节码:

OP_CONSTANT  0   ; push 1
OP_CONSTANT  1   ; push 2
OP_CONSTANT  2   ; push 3
OP_MUL           ; 2 × 3 = 6
OP_ADD           ; 1 + 6 = 7
OP_PRINTLN       ; print 7
OP_HALT

递归阶乘 factorial(5):

与多数语言编译器的中间表示类似,函数体内通过 LOAD/STORE 访问参数和局部变量,CALL/RET 管理调用栈。

测试覆盖

13 个测试覆盖了:算术、局部变量、条件分支、循环、递归、浮点、字符串、比较、位运算、栈操作、双递归调用(fibonacci)。

优化方向(待做)

  • Top-of-stack caching — 栈顶值缓存到寄存器
  • Computed goto — switch 替换为跳转表
  • 内联缓存 — 频繁调用函数做内联
  • JIT 编译 — 热点字节码编译为本地机器码

结语

这份设计文档完整描述了一个可工作的栈式虚拟机。代码量不大(C++17,8 个文件),但涵盖了 VM 的核心设计范式。对编译器或语言实现感兴趣的同学可以参考。

完整源码:https://github.com/xyanrch1024/VM