Sometimes a thought comes to mind which is very difficult to get rid of. This happened to me.
I decided to create a virtual machine (VM), considering that at that time I had no ideas, it seemed to me that this is a great idea. If you are interested, go ahead under the cat!
Theory
First, a little theory. What is a virtual machine? This is a program or a set of programs that allows you to emulate any hardware platform, in other words, a computer emulator.
')
By themselves, virtual machines are different, for example Virtual Box - this is a classic virtual machine that allows you to emulate a real computer, but for example, the JVM (Java Virtual Machine) cannot do this.
My version of VM will be somewhat similar to JVM simply because it is a more educational project than to create a powerful VM.
Memory
So now let's deal with the memory. To create memory, I decided to use an unsigned int array. We define the size of the array using a macro, in my version the memory size is 4096 bytes (in an array of 1024 elements, and since on most platforms, 4 bytes are allocated for unsigned int data, then 1024 * 4 = 4096), among other things, we define 8 registers for 8 cells in each it will be 256 bytes (8 * 8 * 4 = 256). It looks like this:
#define MEMSIZE 1024 unsigned int memory[MEMSIZE]; unsigned int reg[8][8];
Programming
We have the memory, and now how to write the code for our VM? Now we will deal with this issue, to begin with, we will define the commands that our car will execute:
enum commands { CRG = 1, CRC, PRG, PRC };
Each team has its own flag, defining some additional parameters.
describe the flags:
enum flags { STDI = 1, STDA };
The standard command has the form: [command] [flag] [data] (the appearance of some commands may differ), based on this, we will write a simple interpreter:
if (memory[cell] == CRG && memory[cell + 1] == STDI) { indxX = memory[cell + 2]; cell++; } else if (memory[cell] == CRC && memory[cell + 1] == STDI) { indxY = memory[cell + 2]; cell++; } else if (memory[cell] == PRG && memory[cell + 1] == STDI) { reg[indxX][0] = memory[cell + 2]; cell++; } else if (memory[cell] == PRC && memory[cell + 1] == STDI) { reg[indxX][indxY] = memory[cell + 2]; cell++; }
indxX & indxY are variables storing the current cursor position in the reg register.
cell is a variable storing the current cursor position in the memory array.
But programming with numbers is not very convenient; therefore, using the C preprocessor, we will describe our assembler. I understand that writing asm using macros is not very good, but this solution is temporary.
Our asm code looks like this:
#define $CRG {memory[memIndx++] = CRG;} #define $CRC {memory[memIndx++] = CRC;} #define $PRG {memory[memIndx++] = PRG;} #define $PRC {memory[memIndx++] = PRC;} #define _$STDI {memory[memIndx++] = STDI;} #define _$STDA {memory[memIndx++] = STDA;} #define _$DATA memory[memIndx++] =
memIndx is a variable storing the current cursor position in the memory array.
And here is the code on our asm that puts 123 in the register at [1] [0] (the first register, the zero cell):
$CRG _$STDI _$DATA 1; $CRC _$STDI _$DATA 0; $PRC _$STDI _$DATA 123;
Congratulations, now we have a similarity asm for our car!
Running programs
We managed to force our machine to execute programs, but the code lacks portability from one machine to another, so now we are going to create machine code generator from asm (and I remind you that, unlike real computers, our machine has machine code presented not in the form of binary, and decimal numbers), in principle, it is not so difficult, but first, let's think over the implementation.
First we have asm code, now we need to translate it into numbers, then write the resulting machine code into a .ncp file (numeric code program, in fact it is a text file, but in order to distinguish it from everything else I came up with my own extension), after that we need to run a .ncp file, to do it simply, since we wrote earlier, the interpreter recognizes exactly the numbers; you only need to extract data from the file and turn them into numbers using atoi ().
Let's move from words to deeds:
Reading the code and writing it to the file:
if (memory[i] == CRG && memory[i + 1] == STDI) { fprintf(code, "%d %d ", CRG, STDI); i++; } else if (memory[i] == CRC && memory[i + 1] == STDI) { fprintf(code, "%d %d ", CRC, STDI); i++; } else if (memory[i] == PRG && memory[i + 1] == STDI) { fprintf(code, "%d %d ", PRG, STDI); i++; } else if (memory[i] == PRC && memory[i + 1] == STDI) { fprintf(code, "%d %d ", PRC, STDI); i++; }
The code is part of the body of the ncpGen () function.
Reading a file and its execution:
if (prog != NULL) { fread(txt, 1, len, prog); tok = strtok(txt, " "); while (tok != NULL) { memory[i] = atoi(tok); tok = strtok(NULL, " "); if (argc == 3 && strcmp(argv[2], "-m") == 0) { printf("%d\n", memory[i]); } i++; } memInter(); } else { perror("Fail"); }
And now we define a macro so that instead of interpreting asm, the code turns into .ncp:
#define _toNCP(name) {strcpy(filename, name);} {ncpGen();}
If anything, then the article presents not all the code, but only a small part of it!
The full code is in
the project
repository .
Thank you very much for reading!