在本章中,我们将设计字节码指令集,它是:
- AST 和机器执行之间的桥梁
- 比 AST 更接近机器,比机器码更抽象
- 易于解释执行和优化
字节码是一种中间表示,介于高级语言和机器码之间。它的特点是:
- 紧凑:比 AST 占用更少空间
- 快速:比解释 AST 更快
- 可移植:不依赖特定硬件
类比:
- Java 字节码 → JVM
- Python 字节码 → CPython
- JavaScript 字节码 → V8/SpiderMonkey
#[derive(Debug, Clone, PartialEq)]
pub enum Instruction {
LoadConst(usize),
LoadLocal(usize),
StoreLocal(usize),
Add,
Sub,
Mul,
Div,
Call(usize),
Return,
Jump(isize),
JumpIfFalse(isize),
}LoadConst(usize) // 加载常量
LoadLocal(usize) // 加载局部变量
StoreLocal(usize) // 存储局部变量例子:
let x = 10;字节码:
LoadConst 0 // 加载常量 10
StoreLocal 0 // 存储到变量 x
Add // 加法
Sub // 减法
Mul // 乘法
Div // 除法例子:
1 + 2 * 3字节码:
LoadConst 0 // 1
LoadConst 1 // 2
LoadConst 2 // 3
Mul // 2 * 3 = 6
Add // 1 + 6 = 7
Jump(isize) // 无条件跳转
JumpIfFalse(isize) // 条件跳转
Return // 返回
Call(usize) // 函数调用例子:
if (x > 0) {
return 1;
}
return -1;字节码:
LoadLocal 0 // x
LoadConst 0 // 0
Greater
JumpIfFalse 3 // 如果 false,跳过 3 条指令
LoadConst 1 // 1
Return
LoadConst 2 // -1
Return
#[derive(Debug, Clone)]
pub struct BytecodeChunk {
pub instructions: Vec<Instruction>,
pub constants: Vec<Value>,
pub local_count: usize,
}字段说明:
-
instructions:指令序列
- 按顺序执行的指令列表
-
constants:常量池
- 存储所有常量值
- 指令通过索引引用
-
local_count:局部变量数量
- 用于分配局部变量空间
impl BytecodeChunk {
pub fn new() -> Self {
Self {
instructions: Vec::new(),
constants: Vec::new(),
local_count: 0,
}
}
}pub fn emit(&mut self, instruction: Instruction) {
self.instructions.push(instruction);
}例子:
let mut chunk = BytecodeChunk::new();
chunk.emit(Instruction::LoadConst(0));
chunk.emit(Instruction::Add);pub fn add_constant(&mut self, value: Value) -> usize {
self.constants.push(value);
self.constants.len() - 1
}为什么返回索引?
- 指令需要通过索引引用常量
- 避免重复存储相同的常量
例子:
let mut chunk = BytecodeChunk::new();
let idx = chunk.add_constant(Value::Number(42.0));
chunk.emit(Instruction::LoadConst(idx));pub fn set_local_count(&mut self, count: usize) {
self.local_count = count;
}当前实现使用 Rust 枚举,占用空间较大:
enum Instruction {
Add, // 可能占用 16 字节
LoadConst(usize), // 可能占用 24 字节
}优化方案:
// 使用字节数组
type Bytecode = Vec<u8>;
// 指令编码
const OP_ADD: u8 = 0x01;
const OP_LOAD_CONST: u8 = 0x02;
// 编码 LoadConst(5)
bytecode.push(OP_LOAD_CONST);
bytecode.push(5);pub fn encode(&self) -> Vec<u8> {
let mut bytes = Vec::new();
for instruction in &self.instructions {
match instruction {
Instruction::Add => {
bytes.push(0x01);
}
Instruction::LoadConst(idx) => {
bytes.push(0x02);
bytes.extend_from_slice(&idx.to_le_bytes());
}
// ... 其他指令
}
}
bytes
}10 + 20字节码:
constants: [10.0, 20.0]
instructions:
0: LoadConst 0
1: LoadConst 1
2: Add
let x = 10;
let y = 20;
x + y字节码:
constants: [10.0, 20.0]
local_count: 2
instructions:
0: LoadConst 0
1: StoreLocal 0
2: LoadConst 1
3: StoreLocal 1
4: LoadLocal 0
5: LoadLocal 1
6: Add
if (x > 0) {
y = 1;
} else {
y = -1;
}字节码:
constants: [0.0, 1.0, -1.0]
instructions:
0: LoadLocal 0 // x
1: LoadConst 0 // 0
2: Greater
3: JumpIfFalse 3 // 跳到指令 7
4: LoadConst 1 // 1
5: StoreLocal 1 // y = 1
6: Jump 2 // 跳到指令 9
7: LoadConst 2 // -1
8: StoreLocal 1 // y = -1
9: ...
for (let i = 0; i < 10; i = i + 1) {
sum = sum + i;
}字节码:
constants: [0.0, 10.0, 1.0]
instructions:
0: LoadConst 0 // 0
1: StoreLocal 0 // i = 0
2: LoadLocal 0 // i (循环开始)
3: LoadConst 1 // 10
4: Less
5: JumpIfFalse 8 // 如果 i >= 10,跳出循环
6: LoadLocal 1 // sum
7: LoadLocal 0 // i
8: Add
9: StoreLocal 1 // sum = sum + i
10: LoadLocal 0 // i
11: LoadConst 2 // 1
12: Add
13: StoreLocal 0 // i = i + 1
14: Jump -13 // 跳回指令 2
15: ...
1 + 2未优化:
LoadConst 0 // 1
LoadConst 1 // 2
Add
优化后:
LoadConst 0 // 3
if (true) {
x = 1;
} else {
x = 2; // 永远不会执行
}优化后:
LoadConst 0
StoreLocal 0
LoadLocal 0
StoreLocal 0 // 无意义的操作
优化后:
// 删除
#[test]
fn test_instruction_types() {
let instructions = vec![
Instruction::LoadConst(0),
Instruction::Add,
Instruction::Return,
];
assert_eq!(instructions.len(), 3);
}#[test]
fn test_bytecode_chunk() {
let mut chunk = BytecodeChunk::new();
let idx = chunk.add_constant(Value::Number(42.0));
chunk.emit(Instruction::LoadConst(idx));
chunk.emit(Instruction::Return);
assert_eq!(chunk.instructions.len(), 2);
assert_eq!(chunk.constants.len(), 1);
}#[test]
fn test_constant_pool() {
let mut chunk = BytecodeChunk::new();
let idx1 = chunk.add_constant(Value::Number(10.0));
let idx2 = chunk.add_constant(Value::Number(20.0));
assert_eq!(idx1, 0);
assert_eq!(idx2, 1);
assert_eq!(chunk.constants[0], Value::Number(10.0));
assert_eq!(chunk.constants[1], Value::Number(20.0));
}- 抽象:不依赖硬件
- 可移植:跨平台
- 解释执行:需要虚拟机
- 较慢:有解释开销
- 具体:特定于 CPU
- 不可移植:平台相关
- 直接执行:CPU 直接运行
- 快速:无解释开销
答:
- 机器码生成复杂
- 需要支持多个平台
- 字节码更易于优化
- 可以先解释执行,热点代码再编译
答: 考虑因素:
- 简单性:易于实现和理解
- 紧凑性:占用更少空间
- 效率:执行速度快
- 可扩展性:易于添加新指令
栈式(我们的选择):
- 指令简单
- 代码紧凑
- 易于实现
寄存器式:
- 指令复杂
- 代码较大
- 执行更快
在下一章中,我们将实现字节码生成器,将 AST 转换为字节码。
- 添加比较运算指令(Less, Greater, Equal)
- 添加逻辑运算指令(And, Or, Not)
- 实现字节码的序列化和反序列化
- 设计一个反汇编器,将字节码转换为可读格式
本章的完整代码在 src/bytecode.rs 文件中。