Skip to content

Commit 5c528b6

Browse files
authored
Chapter 15: A Virutal Machine (#12)
This implement [chapter 15](http://craftinginterpreters.com/a-virtual-machine.html). We implement a tiny stack based VM. The code is pretty similar to the C version. The only difference is the macro. We don't need the `do {...} while(false)` trick since Rust macros support proper blocks.
1 parent bcec748 commit 5c528b6

File tree

6 files changed

+130
-9
lines changed

6 files changed

+130
-9
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,5 @@ Each commit corresponds to one chapter in the book:
1919
* [Chapter 12: Classes](https://github.com/jeschkies/lox-rs/commit/337896b3dae4087ad889dca2f3cca32ed025134b)
2020
* [Chapter 13: Inheritance](https://github.com/jeschkies/lox-rs/commit/0207ecc8fca1af20667c69cefb4fa5f277330ca3)
2121

22-
## Part III: A Bytecode Virtual Machine
22+
## Part III: A Bytecode Virtual Machine
23+
* [Chapter 14: Chunks of Bytecode](https://github.com/jeschkies/lox-rs/commit/bcec748d59b568c3b6ce93d6d07b40b14f44caa0)

bytecode/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,7 @@ version = "0.1.0"
44
authors = ["Karsten Jeschkies <[email protected]>"]
55
edition = "2018"
66

7+
[features]
8+
debug_trace_execution = []
9+
710
[dependencies]

bytecode/src/chunk.rs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ use std::ptr;
77
#[derive(Debug)]
88
pub enum OpCode {
99
OpConstant(usize),
10+
OpAdd,
11+
OpSubtract,
12+
OpMultiply,
13+
OpDivide,
14+
OpNegate,
1015
OpReturn,
1116
}
1217

@@ -22,7 +27,7 @@ pub struct Chunk {
2227
// and `OpConstant` with the index as two bytes. Rust's enum will use two bytes for both. As this
2328
// implementation is for learning purposes this solution should suffice. A safe Rust
2429
// implementation should use `Vec<OpCode>` instead.
25-
code: *mut OpCode,
30+
pub code: *mut OpCode,
2631
}
2732

2833
impl Chunk {
@@ -99,7 +104,7 @@ impl Drop for Chunk {
99104
}
100105

101106
impl<'a> IntoIterator for &'a Chunk {
102-
type Item = OpCode;
107+
type Item = &'a OpCode;
103108
type IntoIter = ChunkIter<'a>;
104109

105110
fn into_iter(self) -> Self::IntoIter {
@@ -116,13 +121,18 @@ pub struct ChunkIter<'a> {
116121
}
117122

118123
impl<'a> Iterator for ChunkIter<'a> {
119-
type Item = OpCode;
124+
type Item = &'a OpCode;
120125

121126
fn next(&mut self) -> Option<Self::Item> {
122127
if self.offset < self.chunk.count {
123-
let result: OpCode;
128+
let result: &OpCode;
124129
unsafe {
125-
result = self.chunk.code.offset(self.offset as isize).read();
130+
result = self
131+
.chunk
132+
.code
133+
.offset(self.offset as isize)
134+
.as_ref()
135+
.expect("Could not read Chunk.");
126136
}
127137
self.offset += 1;
128138
Some(result)

bytecode/src/debug.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ fn simple_instruction(name: &str) {
1919
println!("{}", name)
2020
}
2121

22-
fn disassemble_instruction(chunk: &Chunk, op_code: OpCode, i: usize) {
22+
pub fn disassemble_instruction(chunk: &Chunk, op_code: &OpCode, i: usize) {
2323
// Note: The index is not really the offset if the op code has different sizes. For now all
2424
// op codes have the same size.
2525
print!("{:04} ", i);
@@ -30,8 +30,13 @@ fn disassemble_instruction(chunk: &Chunk, op_code: OpCode, i: usize) {
3030
}
3131
match op_code {
3232
OpCode::OpConstant(constant_index) => {
33-
constant_instruction("OP_CONSTANT", chunk, constant_index)
33+
constant_instruction("OP_CONSTANT", chunk, *constant_index)
3434
}
35+
OpCode::OpAdd => simple_instruction("OP_ADD"),
36+
OpCode::OpSubtract => simple_instruction("OP_SUBTRACT"),
37+
OpCode::OpMultiply => simple_instruction("OP_MULTIPLY"),
38+
OpCode::OpDivide => simple_instruction("OP_DIVIDE"),
39+
OpCode::OpNegate => simple_instruction("OP_NEGATE"),
3540
OpCode::OpReturn => simple_instruction("OP_RETURN"),
3641
_ => println!("Unknown opcode {:?}", op_code),
3742
}

bytecode/src/main.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,34 @@ mod chunk;
22
mod debug;
33
mod memory;
44
mod value;
5+
mod vm;
56

67
use chunk::{Chunk, OpCode};
78
use debug::disassemble_chunk;
9+
use vm::VM;
810

911
fn main() -> Result<(), Box<dyn std::error::Error + 'static>> {
1012
let mut chunk = Chunk::new();
11-
1213
let constant = chunk.add_constant(1.2);
1314
chunk.write_chunk(OpCode::OpConstant(constant), 123);
1415

16+
let constant_2 = chunk.add_constant(3.4);
17+
chunk.write_chunk(OpCode::OpConstant(constant_2), 123);
18+
19+
chunk.write_chunk(OpCode::OpAdd, 123);
20+
21+
let constant_3 = chunk.add_constant(5.6);
22+
chunk.write_chunk(OpCode::OpConstant(constant_3), 123);
23+
24+
chunk.write_chunk(OpCode::OpDivide, 123);
25+
chunk.write_chunk(OpCode::OpNegate, 123);
26+
1527
chunk.write_chunk(OpCode::OpReturn, 123);
1628

29+
let mut vm = VM::new(&chunk);
30+
1731
disassemble_chunk(&chunk, "test chunk");
32+
vm.interpret();
1833

1934
// No need to free chunk since we implemented `Drop`.
2035
Ok(())

bytecode/src/vm.rs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
use crate::chunk::{Chunk, OpCode};
2+
use crate::debug::disassemble_instruction;
3+
use crate::value::{print_value, Value};
4+
5+
macro_rules! binary_op{
6+
( $sel:ident, $op:tt ) => {
7+
{
8+
let b = $sel.stack.pop().expect("The stack was empty!");
9+
let a = $sel.stack.pop().expect("The stack was empty!");
10+
$sel.stack.push(a $op b);
11+
}
12+
};
13+
}
14+
15+
static STACK_MAX: usize = 245;
16+
17+
pub struct VM<'a> {
18+
chunk: &'a Chunk,
19+
ip: *const OpCode,
20+
stack: Vec<Value>,
21+
}
22+
23+
// TODO: replace with Result<_, Error>
24+
pub enum InterpretResult {
25+
Ok,
26+
CompileError,
27+
RuntimeError,
28+
}
29+
30+
impl<'a> VM<'a> {
31+
pub fn new(chunk: &'a Chunk) -> Self {
32+
VM {
33+
chunk: chunk,
34+
ip: chunk.code,
35+
stack: Vec::with_capacity(STACK_MAX),
36+
}
37+
}
38+
39+
pub fn interpret(mut self) -> InterpretResult {
40+
self.run()
41+
}
42+
43+
fn run(mut self) -> InterpretResult {
44+
let mut position: usize = 0; // TODO: infer position from self.ip.
45+
loop {
46+
let instruction: OpCode = unsafe {
47+
let r = self.ip.read();
48+
self.ip = self.ip.add(1);
49+
r
50+
};
51+
52+
if cfg!(feature = "debug_trace_execution") {
53+
print!(" ");
54+
for slot in &self.stack {
55+
print!("[{:?}]", slot);
56+
}
57+
println!();
58+
disassemble_instruction(self.chunk, &instruction, position);
59+
position += 1;
60+
}
61+
62+
match instruction {
63+
OpCode::OpConstant(index) => {
64+
let constant = self.read_constant(index);
65+
self.stack.push(constant);
66+
}
67+
OpCode::OpAdd => binary_op!(self, +),
68+
OpCode::OpSubtract => binary_op!(self, -),
69+
OpCode::OpMultiply => binary_op!(self, *),
70+
OpCode::OpDivide => binary_op!(self, /),
71+
OpCode::OpNegate => {
72+
let value = self.stack.pop().expect("The stack was empty!");
73+
self.stack.push(-value);
74+
}
75+
OpCode::OpReturn => {
76+
print_value(self.stack.pop().expect("The stack was empty!"));
77+
println!();
78+
return InterpretResult::Ok;
79+
}
80+
}
81+
}
82+
}
83+
84+
fn read_constant(&self, index: usize) -> Value {
85+
self.chunk.constants[index]
86+
}
87+
}

0 commit comments

Comments
 (0)