Skip to content

Latest commit

 

History

33 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

OHVM

OHVM(Only for Happy VM)是一个极其简单的字节码解释器。

它不是通用虚拟机,也不模拟真实 CPU。这个项目只保留理解解释器所需的最小闭环:读取字节码、维护运行状态、逐条取指并执行,直到程序停止。

字节码文件 -> 加载 -> 取指 -> 解码 -> 执行 -> HALT

设计目标

  • 足够小:核心只有一个 VM 结构和一个 switch 执行循环。
  • 足够完整:支持整数运算、变量、分支、循环、函数调用和输出。
  • 行为明确:所有整数与地址都使用小端编码,错误通过状态码返回。
  • 容易验证:没有第三方依赖,并为主要执行路径提供单元测试。

不包含图形、声音、垃圾回收、即时编译、高级语言或汇编器。这些功能会遮住项目真正想说明的事情。

机器模型

OHVM 是一台 32 位整数栈机:

资源 容量 用途
字节码 4096 字节 保存待执行指令
操作数栈 256 个 int32 传递参数和保存计算结果
全局槽位 256 个 int32 保存变量
返回地址栈 64 个地址 支持 CALL / RET

ip 指向下一条指令。除 JMP、成功跳转的 JZ、CALL 和 RET 外,执行指令后它自然向前移动。

指令集

栈效果使用 (执行前 -- 执行后) 表示。a 是较早入栈的值,b 是栈顶值。

编码 指令 操作数 栈效果 / 行为
00 HALT - 停止程序
01 PUSH i32 (-- value)
02 POP - (value --)
03 DUP - (value -- value value)
04 LOAD u8 slot (-- globals[slot])
05 STORE u8 slot (value --),写入全局槽位
06 ADD - (a b -- a+b)
07 SUB - (a b -- a-b)
08 MUL - (a b -- a*b)
09 DIV - (a b -- a/b)
0a MOD - (a b -- a%b)
0b EQ - (a b -- a==b)
0c LT - (a b -- a<b)
0d GT - (a b -- a>b)
0e JMP u16 address 无条件跳转
0f JZ u16 address (condition --),为零时跳转
10 CALL u16 address 保存返回地址并跳转
11 RET - 返回最近一次调用
12 PRINT - (value --),输出十进制整数

字节码格式

文件头固定为 8 字节,后面紧跟指令:

偏移 大小 内容
0 4 ASCII 魔数 OHVM
4 1 格式版本,当前为 1
5 1 保留,必须为 0
6 2 入口地址 u16,相对于指令区,小端
8 其余 指令与操作数

所有 u16 和 i32 操作数均为小端,与宿主机字节序无关。

下面的完整字节流会输出 42:

4f 48 56 4d 01 00 00 00   # 文件头
01 2a 00 00 00            # PUSH 42
12                        # PRINT
00                        # HALT

汇编示例

下面使用助记符和标签展示字节码逻辑,方便阅读。OHVM 当前没有汇编器;生成字节码时,需要将标签替换为相对于指令区起点的 u16 字节地址。

算术运算

计算 $(6 + 7) \times 3$ 并输出 39:

PUSH 6
PUSH 7
ADD
PUSH 3
MUL
PRINT
HALT

每条算术指令弹出栈顶的两个值,并把结果重新压栈。例如 SUB 执行的是 a - b,其中 b 是原来的栈顶值。

从 1 累加到 100

用全局槽位 0 保存总和,槽位 1 保存循环变量:

PUSH 0
STORE 0             ; sum = 0
PUSH 1
STORE 1             ; i = 1

loop:
LOAD 0
LOAD 1
ADD
STORE 0             ; sum += i

LOAD 1
PUSH 1
ADD
DUP
STORE 1             ; ++i,并保留一份用于比较
PUSH 101
LT                  ; i < 101
JZ done
JMP loop

done:
LOAD 0
PRINT                ; 5050
HALT

JZ 会弹出条件值,仅当它为 0 时跳转。因此这里在 i < 101 不成立时离开循环。

函数调用

调用 square 计算 $7^2$。参数和返回值都通过操作数栈传递:

PUSH 7
CALL square
PRINT                ; 49
HALT

square:
DUP                   ; (value -- value value)
MUL
RET

CALL 把下一条指令的地址压入返回地址栈,再跳转到目标地址;RET 弹出该地址并继续执行。

构建与运行

Linux、macOS:

make
./ohvm program.ohvmbc

Windows(MinGW):

mingw32-make
.\ohvm.exe program.ohvmbc

使用 --trace 可查看每一步的指令地址、操作码和栈深度;--max-steps N 可以限制执行步数,避免错误程序无限循环。

./ohvm --trace --max-steps 1000 program.ohvmbc

测试

make test
# Windows: mingw32-make test

测试覆盖算术、循环、函数调用、字节码文件加载以及典型错误。实现入口见 vm.c,指令和机器状态定义见 vm.h。

示例程序

生成四个可直接执行的字节码程序:

make examples
# Windows: mingw32-make examples
文件 演示内容 输出
examples/answer.ohvmbc 压栈与输出 42
examples/sum_1_to_100.ohvmbc 全局变量、条件和循环 5050
examples/factorial.ohvmbc 乘法与循环 120
examples/function_call.ohvmbc CALL、RET 与栈传参 49

例如:

./ohvm examples/sum_1_to_100.ohvmbc

示例的可读源码位于 examples/make_examples.c。它直接使用操作码构造字节流,没有隐藏的编译或汇编阶段。

为什么重写

早期 OHVM 同时设想了寄存器、图形、键盘、声音、汇编器和高级语言,但大部分功能停留在规范或空实现中。当前版本主动舍弃这些范围,只表达最初、也最重要的想法:亲手做一台能够可靠执行简单程序的小机器。

About

计划搞一个类似于JS的那种字节码虚拟机

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages