Implementing a virtual machine in C (VM C language Implementation)

Source: Internet
Author: User

Introduced

GitHub shows what we're going to do, you can compare the code in the project in case you encounter any errors GitHub Repository
This is an article about using C language to build your own virtual machine. I like to study the underlying applications such as compilers, interpreters, editors, virtual machines and so on.

Pre-knowledge and reminders

Before we go on, there's something you need to know:

    • A compiler-I'm using clang3.4, but you can use any compiler that supports C99/C11
    • Editor-I would suggest you use a text editor instead of the IDE, I will use Emacs
    • BASIC programming knowledge-just basic, such as variables, controls, functions, structs, etc.
    • make-a compilation system so that we don't have to write duplicate commands in the console to compile our code
Why you should write a virtual machine

1
Here are some reasons why you should write a virtual machine:

    • You want to have a more in-depth understanding of your computer's work. This article will help you to understand how your computer works at the bottom, a virtual machine that provides a simple and graceful abstraction. And writing a virtual machine is the best way to learn it, isn't it?
    • You learn a virtual machine because you think it's interesting.
    • You want to know how some programming languages work. Today, different languages have their own virtual machines. For example Jvm,lua's VM, Facebook's hip-hop vm (Php/hack) and so on. These are great concepts, assuming that from C + + programs to assembly to machine execution, when you think about them, you take for granted all the features of OOP programming, automatic garbage collection, and so on.
Instruction Set

We will implement our own instruction set, it is very simple.
I'll simply mention some of the instructions, such as moving the values from the register, or jumping to other instructions, but hopefully you'll figure them out after you've spent the passage.

Our virtual opportunity has a series of registers A, B, C, D, E, and F. These are the purpose registers that you can use to store anything. A program is a read-only sequence of instructions. This is a stack-based virtual machine, which means we have a stack to stack and stack, and a small number of registers for us to use. Stack-based virtual machines are easier to implement than register-based virtual machines.

No more, here's the set of instructions we'll be using. The semicolon is followed by a description of the effect on each row.

PSH 5; Put 5 into the stack
PSH 10; Put 10 into the stack
ADD; Add two values to the top of the stack and then stack the results
POP; The stack top element is stacked and will be used for debugging
SET A 0; Reset register A to zero
HLT; Stop Program

This is our instruction set, note that the pop instruction prints the value of the stack, which makes it easier to debug (the Add command will put the result into the stack, so we use pop to determine if the operation is correct)
You can also try to implement similar mov a, B. The HLT directive indicates the end of our program.

How do I make a virtual machine work?

Virtual machines are simpler than you think, they follow a simple pattern: take instructions, parse, execute. Some advanced virtual machines may have additional steps, but that's the core. We take the next instruction from the code or instruction set, then parse the instruction and execute the parsed instruction. For the sake of simplicity, we will not parse the instructions, a typical virtual opportunity to package an instruction to a value and then parse it.

Project structure

Before we start programming, we create a virtual project. First, you need a C editor. We need a folder to place your project, I like to put the project under ~/dev. We create the project in the destination folder. This assumes that you already have the ~/dev/directory, but you can also create your project from anywhere.

$cd ~/Dev/mkdir mac  cd mac  mkdir

In the destination folder, we create a folder (called the VM "Mac"). Then we CD into this folder, create the SRC folder, here is where we put the code.

Makefile

We don't have any sub-files and don't contain anything, so we just need makefile to compile

= main.c  =-Wall-Wextra-g-std=c11  = clangall:      -o mac

That's enough for now, we'll improve it later, but as long as it can do the job well.

Program Instructions (code)

Now is the virtual machine code section. First we need to define the instruction set for the program. To do this, we use the enum because our instruction set is simply from the digital 0-x. In fact, when you assemble a file, the assembly converts instructions like mov into. \. For example, we can use 0, 5来 instead of psh,5, but this is very poor readability, so we used enumerations.

typedef enum {      PSH,    ADD,    POP,    SET,    HLT} InstructionSet

Now we are storing the program as data. For testing, we will write a simple program that adds 5 and 6 and then prints it out (using the pop command). If you want, you can create a command that prints the top element of the stack.

The instruction stores the array form, and I'll define it at the beginning of the document, but you can put it in the header file. Here is our test procedure:

constint program[] = {      5,    6,    ADD,    POP,    HLT};

The above program will put 5, 6 into the stack, call the add instruction, the two stack, and then add the result into the stack. We'll get the results out of the stack and print it, but you don't have to do it yourself. We just use it to test. Finally, the hlt instruction is called to indicate the end of the program.

Now we have the program. So now we implement the value, parse, and evaluate the virtual machine operation. But remember, we don't parse anything, because this is just the original instruction. This means that we only need to take care of the value, and the evaluation. We simplify two function fetch and evaluate

Remove current Instruction

Since we have stored the program in an array, it is easy to take out the instructions. A virtual machine has a counter, often called a program calculator, instruction pointer ... These names are all meant to be, depending on your personal preference. I abbreviate these into IP or PC because they are very common in VM code.

If you remember, I said we would treat the program counter as a register. We'll do it, but we'll have to wait a bit. Now we just start at the beginning of the program, create a variable called IP, and set its value to 0.

int ip = 0;

The IP represents the instruction pointer. Because we store the program as an array, we use the IP variable to point to the current index. For example, if we create a variable x, which is the program that the IP points to, it stores the first instruction of our program (plus the IP value is 0)

int0;int main() {      int instr = program[ip];    return0;}

We print the variable instr, which gives us PSH, which is 0, because this is the first value of our enum. We can write a function of this process

int fetch() {      return program[ip];}

This function will method the current execution of the instruction. What about the next instruction? We just increment the instruction pointer.

int main() {      int// PSH    // increment instruction pointer    int// 5}

So how do we automate that? We know that the program will always run, knowing that the hlt instruction is called. So we use a dead loop to keep the program running.

// INCLUDE <stdbool.h>!booltrue;int main() {      while (running) {       int x = fetch();       iffalse;       ip++;    }}

It works perfectly, but it's a little confusing. We traverse each instruction to check if it is hlt, if it is to stop the loop, otherwise execute the instruction and continue.

Execution instructions

Our goal here is to really execute the instructions and make it look clearer. Since this virtual machine is very simple, we can use enum to write a huge switch structure. Eval has a parameter that represents the instruction to execute. We do not increase the instruction pointer unless we consume the operand.

void eval(int instr) {      switch (instr) {        case HLT:            false;            break;    }}

Back in the main function, we can combine eval

booltrue;  int0;// instruction enum here// eval function here// fetch function hereint main() {      while (running) {        eval(fetch());        // increment the ip every iteration    }}
Stack!

We need a stack before we add other instructions. Fortunately, this is very simple and we only need an array. The array has a suitable size, here is the 256. We also need a stack pointer, usually abbreviated to SP. It points to the current index of our stack array.

In order to be more image, this is our stack (array)

[]//Empty

PSH 5//Put 5 on top of the stack
[5]

PSH 6
[5, 6]

POP
[5]

POP
[]//Empty

PSH 6
[6]

PSH 5
[6, 5]

So go ahead with our program.

PSH, 5,
PSH, 6,
ADD,
POP,
HLT

First put 5 into the stack

[5]

Then 6 into the stack

[5,6]

The add command then adds these two numbers out of the stack, adding the results into the stack

[5, 6]

Pop the top value, store it in a variable >called a
A = Pop; A contains 6
[5]//stack contents

Pop the top value, store it in a variable >called b
b = Pop; B contains 5
[]//Stack contents

Now we add B and a. Note we does it >backwards, in addition
This doesn ' t matter, but in other >potential instructions
For instance divide 5/6 are not the same >as 6/5
result = B + A;
Push result//Push the result to the stack
[One]//stack contents

So where are our stack pointers? The stack pointer, or SP, typically defaults to-1, which indicates that the stack is empty. The array starts at 0, so if the SP represents 0, then the C compiler can cause confusion.

Now if we put 3 numbers into the stack, the SP would be 2.

SP points here (sp = 2)
|
V
[1, 5, 9]
0 1 2 <-These is the array indices

When we want to see the top of the stack, we just see the value the SP points to. So now you should understand how the stack works. C language implementation is also very simple. In addition to the IP variables, we also define an SP variable whose default value is -1! now that the stack is just an array, we have the following definition:

int0;  int sp = -1;  intstack[256// use a define or something here preferably// other c code here...

Now if we want to put the element into the stack, we increment the stack pointer and then set the value to the location where the SP points. Note that the order is very important!

// pushing 5// sp = -1// sp = 0  stack5// top of stack is now [5] 

So we can add the push operation to the Eval function:

void eval(int instr) {      switch (instr) {        case HLT: {            false;            break;        }        case PSH: {            sp++;            stack[sp] = program[++ip];            break;        }    }}

Now, you might notice the difference between some of the previous eval functions. First, there are parentheses in each case. If you are unfamiliar with this technique, it gives you a scope so that you can define variables in the case. We don't need it now, but we'll use it later, so it's easy to keep all the case consistent.

In addition, Program[++ip] indicates a self-increment operation. Our program is stored in an array. We psh the instruction to consume an operand. An operand is basically a parameter, just like when you call a function you can pass a parameter. In this case, we say to put 5 into the stack. So we have to get this operand, to do this, we increase the instruction pointer. So our IP is zero, which means it points to PSH, but now we're in PSH's order and we want to get the next instruction. To do this, you need to add instructions. Note that the position of the increment is important, we get the instruction before we have to increase the instruction pointer otherwise we will only get PSH, and then skip the next instruction to cause some strange errors. I can abbreviate sp++ to STACK[++SP].

Now say the pop command, it's very simple. We only need to self-decrement the stack pointer, but I also want to print out the value of our stack. I omitted the other code directives and switch statements, which only include the case portion of the pop instruction:

// REMEMBER TO INCLUDE <stdio.h>!case POP: {      intstack[sp--];    printf("popped %d\n", val_popped);    break;}

So what we do is we save the top value of the stack to val_popped, and then we subtract the stack pointer. If we subtract the stack pointer first, you get some garbage, because the SP can be 0, then we'll subtract the stack pointer and set val_popped to Stack[-1] Basically, that's not good.

Finally, the add command. The case scope mentioned above is used here.

case ADD: {      // first we pop the stack and store it as a    intstack[sp--];    // then we pop the top of the stack and store it as b    intstack[sp--];    // we then add the result and push it to the stack    int result = b + a;    // increment stack pointer **before**    stack// set the value to the top of the stack    // all done!    break;}
Register

For virtual machines, registers are optional. Registers are easy to implement, and we mention that we have registers a, B, C, D, E, and F. We can define enumerations like this:

enum {     A, B, C, D, E, F,   NUM_OF_REGISTERS} Registers;

The last value num_of_registers is just a simple trick, so we get the number of registers. Now we need an array to store the registers.

int

We can do this by using register a

printf("%d\n"// prints the value at the register A
Instruction Pointers

What is a branch? I'll leave it to you. Remember that an instruction pointer points to the current instruction. Now, since this is the source code in the virtual machine, your best option is to have the instruction pointer register so that the program can be read and manipulated from the virtual machine.

enum {      A, B, C, D, E, F, PC, SP,    NUM_OF_REGISTERS} Registers;

Now we can actually use these instructions and stack pointers. A quick way to do this is to define the macro

#define sp (registers[SP])#define ip (registers[IP])

Should be a good solution, so you don't need to rewrite the code, and ensure that the functionality is normal. However, this may not be easy to extend and may cause code confusion, so I recommend not using this method, but it's enough for a simple toy virtual machine.

When it comes to the branch of our code, I give you a hint. With our new IP registers, we can write different values to this IP. Try this example to see what it does:

PSH 10
SET IP 0

Similar to the basic program that people are familiar with

PRINT "Hello, World"
10 GOTO

However, because I always have to put the value into the stack, it will eventually cause a stack overflow. In your VM, this is also a boundary condition that needs to be handled.

; These is the instructions
PSH 10; 0 1
PSH 20; 2 3
SET IP 0; 4 5 6

If you want to transfer to the second set instruction, we will set the IP register to 2 instead of 0.

At last

You can get the code here. If you want to see the version containing the Mov,set directive, you can download bettervm.c. If you are having problems, you can also compare your implementation to the above file. If you want the tutorial code, you can download main.c.

You can execute the make command under the project folder, and if it compiles correctly, you can execute the./mac file.

If you are interested in this topic and are expanding, there are a lot of resources on the Internet. Notch wrote DCPU-16, a 16-bit virtual machine. There are a lot of implementations on GitHub, you can imitate them. If you write a similar simulator, check its syntax rules to see if you can execute instructions and set registers.

Thank you for reading.

Original address

Implementing a virtual machine in C (VM C language Implementation)

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.