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 VMs and how they happen 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:

enumOpcode {

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:

structVm {
    pub stack: Vec<i32>,

And implement it:

impl Vm {
    //Упрощаем себе жизнь тем что сокращаем писанинуpubfnpop(&mutself) -> i32 {
    //Здесь будет происходить все самое интересноеpubfnrun(&mutself,program: Vec<Opcode>) {
        for opcode in program { //Проверяем каждую команду в нашей программеmatch opcode {
                Opcode::Push(n) => {
                    //Просто добавляем число в наш стекself.stack.push(n);
                Opcode::Add => {
                    // Берем два числа из стека и складываем их, добавляем в стек уже новое числоlet value = self.pop() + self.pop();
                Opcode::Sub => {
                   // Тут то же самое что и с складыванием только наоборот let value = self.pop() - self.pop();
                // Думаю следующие две операции объяснять не нужно
                Opcode::AddAssign(n) => {
                    letmut value = self.pop();
                    value += n;
                Opcode::SubAssign(n) => {
                    letmut value = self.pop();
                    value -= n;

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!

letmut vm = Vm {stack: Vec::new()};
// Выводим все числа из стека, на данный момент это будет просто 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.


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!

Also popular now: