Looking for resources on simulating a VM in Rust

Hi, I started learning Rust about a month ago, and this is my second project. I’m working on the Corewar project, and I’m a bit confused about how to actually simulate a virtual machine in code.

I’ve done nand2tetris part one before, but I still don’t feel like I really understand the core ideas of building a VM. I’ve tried looking for resources, but most of what I find are either 1000-page books or full code dumps, and neither of those really click with me.

Are there any beginner-friendly resources (articles, videos, or short guides) that explain how to think about simulating a VM, preferably in a way that could help me apply it while learning Rust?

Thanks!

I suggest you search "CHIP-8 emulator"

3 Likes

Besides looking at CHIP-8 interpreters, the second part of the Crafting Interpreters book is about implementing a bytecode compiler and VM for a programming language. This part of the book is written in C, but there are many implementations in Rust.

There's also this book on writing interpreters in Rust, which has some chapters on virtual machines, but I haven't read it yet.

1 Like

thank you. i will take a look at it.

thank you. i will check that out.

At their core, VMs are really just a loop of "fetch and decode an instruction, then execute that instruction." Because of that, a good start is to first just decode all the instructions by writing a disassembler: together with an assembler (it looks like they provide you one?) this will let you validate the messy half by then reassembling the output and checking if they're byte-identical, or at least sensible. (This does depend on the VM and instructions being somewhat amenable to it though, otherwise it's hard to tell what's instructions to decode and what's just data. Use your best judgement!)

Then execution is generally pretty simple, depending on exactly what machine you're emulating and to what level: match the decoded instruction and do what it says. A lot of the time this is something like ld y, x for "load a byte from address x to register y" which you can just write as something like self.registers[y] = self. memory[x], or add y, x for "add register x to register y", which is self.register[y] += self.register[x] for example (be sure you get the arguments the right way around, this isn't consistent across different machines!)

The messy part here is just that in practice these instructions tend to actually be pretty subtle and require some care to do exactly right, for example loading from memory may actually perform effects on some machines (like block and wait for input to read from the keyboard!), and addition generally sets flags like "the result was too large so it was wrapped", so you should actually be using overflowing_add, and so on. Be sure to read the details carefully!

You'll probably want to have unit tests for everything, though this is much more effective if you have something "known good" to validate against, ideally provided test programs with their expected output for example. Otherwise you may be just reinforcing whatever incorrect assumption you made!

You might want a debugger for your VM. This can be as simple as reading a command in a loop, at least calling "vm.step()", and a bunch of inspection commands like printing memory ranges, showing register values, or disassembling. Unfortunately it's pretty tricky to do the classic "run until I hit a key" nicely, since you can't portably read stdin without blocking.

Once you have that then "real" VMs start getting really, really messy. It's common to have to simulate how the screen is being drawn at the instruction cycle level to handle the common trick of altering the things on screen as they're being drawn, for example, which can make that code really convoluted. Then there's emulating hardware bugs, or for more modern hardware JITing native code.

I'm happy to answer anything more specific you're having issues with!

3 Likes

Thanks for the pointers and detailed explanations. I think the best way forward now is just to start building and solve problems as they come up, since I haven’t found a “perfect” resource. I’ve already written an assembler and disassembler, and I’m making progress in the vm here: github.com/BenaliOssama/vm. I’ll keep implementing and documenting along the way so it might help others later, and I’ll come back with more specific questions once I run into real issues.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.