├── .gitignore ├── Makefile ├── README.md ├── mac-improved ├── Makefile ├── README.md ├── mac.c ├── sum200.mac └── test.mac └── mac.c /.gitignore: -------------------------------------------------------------------------------- 1 | #*# 2 | mac 3 | .o 4 | *.dSYM/ -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS = -Wall -Wextra -g -std=c11 2 | 3 | all: mac 4 | 5 | test: mac 6 | ./mac 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mac 2 | mac is a simple virtual machine created for educational purposes. Here is the accompanied [blog post](https://felixangell.com/blogs/virtual-machine-in-c). 3 | 4 | There is a slightly more extended example in the [mac-improved](mac-improved/) folder. 5 | 6 | In addition, there is a more complicated virtual machine written in C in the same style as mac [here](https://github.com/felixangell/krug/tree/master/vm) I wrote as a target for my 'krug' compiler. 7 | -------------------------------------------------------------------------------- /mac-improved/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS = -Wall -Wextra -g -std=c11 2 | 3 | all: 4 | ${CC} ${CFLAGS} mac.c -o mac 5 | -------------------------------------------------------------------------------- /mac-improved/README.md: -------------------------------------------------------------------------------- 1 | # MAC 2 | MAC is a really simple virtual machine. It's written in C and is [as of writing this] 3 | only one small C file. 4 | 5 | This is the more advanced version, it has more instructions and 6 | you can pass it files, for instance this file is `test.mac`: 7 | 8 | 1 5 1 1 9 | 3 11 1 9 10 | 1 0 11 | 12 | Which is passed as an argument to the program `./mac test.mac` and it will 13 | execute the program. Note that there could be more error checks on memory, 14 | etc. But these are not accounted for for simplicity and brevity. 15 | 16 | ## Registers 17 | 18 | register purpose 19 | ------------------------------- 20 | A general purpose 21 | B general purpose 22 | C general purpose 23 | D general purpose 24 | E general purpose 25 | F general purpose 26 | EX extra 27 | EXA extra 28 | IP instruction pointer 29 | SP stack pointer 30 | 31 | ## Instructions 32 | 33 | op val usage function 34 | --------------------------------------------------------------------- 35 | HLT 0 hlt halts program 36 | PSH 1 psh val pushes to stack 37 | POP 2 pop pops value from stack 38 | ADD 3 add adds top two vals on stack 39 | MUL 4 mul multiplies top two vals on stack 40 | DIV 5 div divides top two vals on stack 41 | SUB 6 sub subtracts top two vals on stack 42 | MOV 7 mov reg_a, reg_b movs the value in reg_a to reg_b 43 | SET 8 set reg, val sets the reg to value 44 | LOG 9 log a prints out a 45 | IF 10 if reg val ip if the register == val branch to the ip 46 | IFN 11 ifn reg val ip if the register != val branch to the ip 47 | GLD 10 gld reg loads a register to the stack 48 | GPT 11 gpt reg pushes top of stack to the given register 49 | NOP 12 nop nothing 50 | -------------------------------------------------------------------------------- /mac-improved/mac.c: -------------------------------------------------------------------------------- 1 | /* 2 | A more advanced version of the VM 3 | */ 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #define STACK_SIZE 256 10 | static int stack[STACK_SIZE]; 11 | 12 | /** Instructions */ 13 | typedef enum { 14 | HLT, // 0 -- hlt :: halts program 15 | PSH, // 1 -- psh val :: pushes to stack 16 | POP, // 2 -- pop :: pops value from stack 17 | ADD, // 3 -- add :: adds top two vals on stack 18 | MUL, // 4 -- mul :: multiplies top two vals on stack 19 | DIV, // 5 -- div :: divides top two vals on stack 20 | SUB, // 6 -- sub :: subtracts top two vals on stack 21 | SLT, // 7 -- slt reg_a, reg_b :: pushes (reg_a < reg_b) to stack 22 | MOV, // 8 -- mov reg_a, reg_b :: movs the value in reg_a to reg_b 23 | SET, // 9 -- set reg, val :: sets the reg to value 24 | LOG, // 10 -- log a :: prints out a 25 | IF, // 11 -- if reg val ip :: if the register == val branch to the ip 26 | IFN, // 12 -- ifn reg val ip :: if the register != val branch to the ip 27 | GLD, // 13 -- gld reg :: loads a register to the stack 28 | GPT, // 14 -- gpt reg :: pushes top of stack to the given register 29 | NOP // 15 -- nop :: nothing 30 | } Instructions; 31 | 32 | /** Registers */ 33 | typedef enum { 34 | A, B, C, D, E, F, I, J, // GENERAL PURPOSE 35 | EX, // EXCESS 36 | EXA, // MORE EXCESS 37 | IP, // INSTRUCTION POINTER 38 | SP, // STACK POINTER 39 | REGISTER_SIZE 40 | } Registers; 41 | 42 | static int registers[REGISTER_SIZE]; 43 | 44 | // instructions array 45 | int *instructions; 46 | 47 | // how many instructions to do 48 | int instruction_count = 0; 49 | 50 | // how much space is allocated for the instructions 51 | // 4 instructions by default 52 | int instruction_space = 4; 53 | 54 | /** if the program is running */ 55 | static bool running = true; 56 | 57 | /** if the IP is assigned by jmp instructions(such as IF,IFN),it should not increase 1 any more **/ 58 | bool is_jmp = false; 59 | 60 | /** quick ways to get SP and IP */ 61 | #define SP (registers[SP]) 62 | #define IP (registers[IP]) 63 | 64 | /** fetch current instruction set */ 65 | // #define FETCH (test_a[IP]) 66 | // #define FETCH (test_b[IP]) 67 | #define FETCH (instructions[IP]) 68 | 69 | /** prints the stack from A to B */ 70 | void print_stack() { 71 | for (int i = 0; i < SP; i++) { 72 | printf("0x%04d ", stack[i]); 73 | if ((i + 1) % 4 == 0) { printf("\n"); } 74 | } 75 | if (SP != 0) { printf("\n"); } 76 | } 77 | 78 | void print_registers() { 79 | printf("Register Dump:\n"); 80 | for (int i = 0; i < REGISTER_SIZE; i++) { 81 | printf("%04d ", registers[i]); 82 | if ((i + 1) % 4 == 0) { printf("\n"); } 83 | } 84 | } 85 | 86 | int find_empty_register() { 87 | for (int i = 0; i < REGISTER_SIZE; i++) { 88 | if (i != registers[EX] && i != registers[EXA]) { return i; } 89 | } 90 | return EX; 91 | } 92 | 93 | void eval(int instr) { 94 | is_jmp = false; 95 | switch (instr) { 96 | case HLT: { 97 | running = false; 98 | printf("Finished Execution\n"); 99 | // print_stack(0, 16); 100 | // print_registers(); 101 | break; 102 | } 103 | case PSH: { 104 | SP = SP + 1; 105 | IP = IP + 1; 106 | stack[SP] = instructions[IP]; 107 | break; 108 | } 109 | case POP: { 110 | SP = SP - 1; 111 | break; 112 | } 113 | case ADD: { 114 | registers[A] = stack[SP]; 115 | SP = SP - 1; 116 | 117 | registers[B] = stack[SP]; 118 | /* SP = SP - 1; */ 119 | 120 | registers[C] = registers[B] + registers[A]; 121 | 122 | /* SP = SP + 1; */ 123 | stack[SP] = registers[C]; 124 | printf("%d + %d = %d\n", registers[B], registers[A], registers[C]); 125 | break; 126 | } 127 | case MUL: { 128 | registers[A] = stack[SP]; 129 | SP = SP - 1; 130 | 131 | registers[B] = stack[SP]; 132 | /*SP = SP - 1;*/ 133 | 134 | registers[C] = registers[B] * registers[A]; 135 | 136 | /*SP = SP + 1;*/ 137 | stack[SP] = registers[C]; 138 | printf("%d * %d = %d\n", registers[B], registers[A], registers[C]); 139 | break; 140 | } 141 | case DIV: { 142 | registers[A] = stack[SP]; 143 | SP = SP - 1; 144 | 145 | registers[B] = stack[SP]; 146 | /* SP = SP - 1;*/ 147 | 148 | registers[C] = registers[B] / registers[A]; 149 | 150 | /* SP = SP + 1; */ 151 | stack[SP] = registers[C]; 152 | printf("%d / %d = %d\n", registers[B], registers[A], registers[C]); 153 | break; 154 | } 155 | case SUB: { 156 | registers[A] = stack[SP]; 157 | SP = SP - 1; 158 | 159 | registers[B] = stack[SP]; 160 | /* SP = SP - 1; */ 161 | 162 | registers[C] = registers[B] - registers[A]; 163 | 164 | /* SP = SP + 1; */ 165 | stack[SP] = registers[C]; 166 | printf("%d - %d = %d\n", registers[B], registers[A], registers[C]); 167 | break; 168 | } 169 | case SLT: { 170 | SP = SP - 1; 171 | stack[SP] = stack[SP+1] < stack[SP]; 172 | break; 173 | } 174 | case MOV: { 175 | registers[instructions[IP + 2]] = registers[instructions[IP + 1]]; 176 | IP = IP + 2; 177 | break; 178 | } 179 | case SET: { 180 | registers[instructions[IP + 1]] = instructions[IP + 2]; 181 | IP = IP + 2; 182 | break; 183 | } 184 | case LOG: { 185 | printf("%d\n", registers[instructions[IP + 1]]); 186 | IP = IP + 1; 187 | break; 188 | } 189 | case IF: { 190 | if (registers[instructions[IP + 1]] == instructions[IP + 2]) { 191 | IP = instructions[IP + 3]; 192 | is_jmp = true; 193 | } 194 | else{ 195 | IP = IP + 3; 196 | } 197 | break; 198 | } 199 | case IFN: { 200 | if (registers[instructions[IP + 1]] != instructions[IP + 2]) { 201 | IP = instructions[IP + 3]; 202 | is_jmp = true; 203 | } 204 | else { 205 | IP = IP + 3; 206 | } 207 | break; 208 | } 209 | case GLD: { 210 | SP = SP + 1; 211 | IP = IP + 1; 212 | stack[SP] = registers[instructions[IP]]; 213 | break; 214 | } 215 | case GPT: { 216 | registers[instructions[IP + 1]] = stack[SP]; 217 | IP = IP + 1; 218 | break; 219 | } 220 | case NOP: { 221 | printf("Do Nothing\n"); 222 | break; 223 | } 224 | default: { 225 | printf("Unknown Instruction %d\n", instr); 226 | break; 227 | } 228 | } 229 | } 230 | 231 | int main(int argc, char** argv) { 232 | if (argc <= 1) { 233 | printf("error: no input files\n"); 234 | return -1; 235 | } 236 | 237 | // filename 238 | char *filename = argv[1]; 239 | 240 | FILE *file = fopen(filename, "r"); 241 | if (!file) { 242 | printf("error: could not read file `%s`\n", filename); 243 | return -1; 244 | } 245 | 246 | // allocate space for instructions 247 | instructions = malloc(sizeof(*instructions) * instruction_space); // 4 instructions 248 | 249 | // read the "binary" file 250 | int num; 251 | int i = 0; 252 | while (fscanf(file, "%d", &num) > 0) { 253 | instructions[i] = num; 254 | printf("%d\n", instructions[i]); 255 | i++; 256 | if (i >= instruction_space) { 257 | instruction_space *= 2; 258 | instructions = realloc(instructions, sizeof(*instructions) * instruction_space); // double size 259 | } 260 | } 261 | 262 | // set 'instruction_count' to number of instructions read 263 | instruction_count = i; 264 | 265 | // close the file 266 | fclose(file); 267 | 268 | // initialize stack pointer 269 | SP = -1; 270 | 271 | // loop through program, but don't 272 | // go out of the programs bounds 273 | while (running && IP < instruction_count) { 274 | eval(FETCH); 275 | if(!is_jmp){ 276 | IP = IP + 1; 277 | } 278 | } 279 | 280 | // clean up instructions 281 | free(instructions); 282 | 283 | return 0; 284 | } 285 | -------------------------------------------------------------------------------- /mac-improved/sum200.mac: -------------------------------------------------------------------------------- 1 | 9 5 1 2 | 9 6 0 3 | 13 5 4 | 13 6 5 | 3 6 | 14 6 7 | 2 8 | 1 1 9 | 3 10 | 14 5 11 | 12 5 201 6 12 | 10 6 13 | 0 14 | -------------------------------------------------------------------------------- /mac-improved/test.mac: -------------------------------------------------------------------------------- 1 | 1 5 1 1 2 | 3 11 1 9 3 | 1 0 4 | -------------------------------------------------------------------------------- /mac.c: -------------------------------------------------------------------------------- 1 | /** 2 | 3 | This is almost identical to the articles 4 | VM 5 | 6 | **/ 7 | 8 | #include 9 | #include 10 | 11 | bool running = true; 12 | int ip = 0; 13 | int sp = -1; 14 | 15 | int stack[256]; 16 | 17 | typedef enum { 18 | PSH, 19 | ADD, 20 | POP, 21 | HLT 22 | } InstructionSet; 23 | 24 | const int program[] = { 25 | PSH, 5, 26 | PSH, 6, 27 | ADD, 28 | POP, 29 | HLT 30 | }; 31 | 32 | int fetch() { 33 | return program[ip]; 34 | } 35 | 36 | void eval(int instr) { 37 | switch (instr) { 38 | case HLT: { 39 | running = false; 40 | printf("done\n"); 41 | break; 42 | } 43 | case PSH: { 44 | sp++; 45 | stack[sp] = program[++ip]; 46 | break; 47 | } 48 | case POP: { 49 | int val_popped = stack[sp--]; 50 | printf("popped %d\n", val_popped); 51 | break; 52 | } 53 | case ADD: { 54 | // first we pop the stack and store it as a 55 | int a = stack[sp--]; 56 | 57 | // then we pop the top of the stack and store it as b 58 | int b = stack[sp--]; 59 | 60 | // we then add the result and push it to the stack 61 | int result = b + a; 62 | sp++; // increment stack pointer **before** 63 | stack[sp] = result; // set the value to the top of the stack 64 | 65 | // all done! 66 | break; 67 | } 68 | } 69 | } 70 | 71 | int main() { 72 | while (running) { 73 | eval(fetch()); 74 | ip++; // increment the ip every iteration 75 | } 76 | } --------------------------------------------------------------------------------