We write the stack virtual machine on Rust'e

Hello, Habr! For several weeks I have been developing my own programming language in Rust. I would like to tell you about what a newcomer in this business may face and what he should know about.


Brief prehistory


It all started with fork ein , I forked it in order to learn how programming languages ​​are built. So, as ein is interpreted from and to, then its execution speed was not the highest, and after I started to understand at least something, I decided to start writing my own interpreter, which I eventually abandoned.


But it's too early to despair! I read a couple of articles about VM and what they are about and decided to write a simple stackable VM.


What is a "stack virtual machine" and how does it work?


On Habré there is a detached article about it, but that would not drive through the links I will briefly outline the meaning of this thing.


A stack VM performs all operations on data that is stored as a stack, each operation retrieves the necessary amount of data for the operation, and after executing it can “send” a new number to the stack.


Getting started


First you need to create a new project using cargo:


cargo new habr_vm 

First we need to create some basic operations for our VM:


 enum Opcode { Push(i32), Add, AddAssign(i32), Sub, SubAssign(i32), } 

These are our basic operations, the Push command will add a new number to the stack, Add and Sub will take two numbers from the stack and perform actions with them (addition and subtraction, respectively), I don’t need to explain AddAssign and SubAssign.


The next task is to create the virtual machine itself, for this we will create a non-complex structure:


 struct Vm { pub stack: Vec<i32>, } 

And implement it:


 impl Vm { //       pub fn pop(&mut self) -> i32 { self.stack.pop().unwrap() } //      pub fn run(&mut self,program: Vec<Opcode>) { for opcode in program { //      match opcode { Opcode::Push(n) => { //      self.stack.push(n); } Opcode::Add => { //        ,       let value = self.pop() + self.pop(); self.stack.push(value); } Opcode::Sub => { //           let value = self.pop() - self.pop(); self.stack.push(value); } //        Opcode::AddAssign(n) => { let mut value = self.pop(); value += n; self.stack.push(value); } Opcode::SubAssign(n) => { let mut value = self.pop(); value -= n; self.stack.push(value); } } } } } 

We and our structure, what's next? Next you need to create our "program".


Here’s how it should look:


 let program = vec![ Opcode::Push(2),// 2    Opcode::Push(4),//  4    Opcode::Sub,//  4 - 2 ]; 

It's simple, isn't it? If so, then let's run our program!


 let mut vm = Vm {stack: Vec::new()}; vm.run(program); //     ,       2 for i in vm.stack() { println!("{}", i); } //  2 

It's very simple as for me, so you can add enough opcodes for the operation you need.


Conclusion


I think that I quite clearly explained how to write all this on a plant and how it works.


I would like to add that you can easily write your own YAP thanks to such a VM, all you have to do is write a parser, lexer and "compiler", and if you want to look at a ready-made project, you can follow this link .


All code from the article is available in this repository.


Good luck Habr!

Source: https://habr.com/ru/post/416505/


All Articles