Initial commit: implementation of the virtual machine
This commit is contained in:
commit
87a815ac27
6 changed files with 262 additions and 0 deletions
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
*\~
|
||||||
|
.gdb_history
|
||||||
|
vm
|
||||||
|
synacor-challenge.tgz
|
11
Makefile
Normal file
11
Makefile
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
CC = gcc
|
||||||
|
CFLAGS = -Wall -Werror -pedantic -O3
|
||||||
|
|
||||||
|
.PHONY: all
|
||||||
|
all: vm
|
||||||
|
|
||||||
|
vm: src/vm.c
|
||||||
|
$(CC) $^ -o $@ $(CFLAGS)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f vm
|
10
README.org
Normal file
10
README.org
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
* Synacor Challenge
|
||||||
|
|
||||||
|
Solutions to the [[https://challenge.synacor.com/][Synacor Challenge]]
|
||||||
|
|
||||||
|
The virtual machine is implemented in [[src/vm.c]]. Compile it with =make=
|
||||||
|
and run it on the challenge program:
|
||||||
|
|
||||||
|
#+begin_src sh
|
||||||
|
./vm challenge.bin
|
||||||
|
#+end_src
|
79
arch-spec
Normal file
79
arch-spec
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
== Synacor Challenge ==
|
||||||
|
In this challenge, your job is to use this architecture spec to create a
|
||||||
|
virtual machine capable of running the included binary. Along the way,
|
||||||
|
you will find codes; submit these to the challenge website to track
|
||||||
|
your progress. Good luck!
|
||||||
|
|
||||||
|
|
||||||
|
== architecture ==
|
||||||
|
- three storage regions
|
||||||
|
- memory with 15-bit address space storing 16-bit values
|
||||||
|
- eight registers
|
||||||
|
- an unbounded stack which holds individual 16-bit values
|
||||||
|
- all numbers are unsigned integers 0..32767 (15-bit)
|
||||||
|
- all math is modulo 32768; 32758 + 15 => 5
|
||||||
|
|
||||||
|
== binary format ==
|
||||||
|
- each number is stored as a 16-bit little-endian pair (low byte, high byte)
|
||||||
|
- numbers 0..32767 mean a literal value
|
||||||
|
- numbers 32768..32775 instead mean registers 0..7
|
||||||
|
- numbers 32776..65535 are invalid
|
||||||
|
- programs are loaded into memory starting at address 0
|
||||||
|
- address 0 is the first 16-bit value, address 1 is the second 16-bit value, etc
|
||||||
|
|
||||||
|
== execution ==
|
||||||
|
- After an operation is executed, the next instruction to read is immediately after the last argument of the current operation. If a jump was performed, the next operation is instead the exact destination of the jump.
|
||||||
|
- Encountering a register as an operation argument should be taken as reading from the register or setting into the register as appropriate.
|
||||||
|
|
||||||
|
== hints ==
|
||||||
|
- Start with operations 0, 19, and 21.
|
||||||
|
- Here's a code for the challenge website: SJIDydAvKFEH
|
||||||
|
- The program "9,32768,32769,4,19,32768" occupies six memory addresses and should:
|
||||||
|
- Store into register 0 the sum of 4 and the value contained in register 1.
|
||||||
|
- Output to the terminal the character with the ascii code contained in register 0.
|
||||||
|
|
||||||
|
== opcode listing ==
|
||||||
|
halt: 0
|
||||||
|
stop execution and terminate the program
|
||||||
|
set: 1 a b
|
||||||
|
set register <a> to the value of <b>
|
||||||
|
push: 2 a
|
||||||
|
push <a> onto the stack
|
||||||
|
pop: 3 a
|
||||||
|
remove the top element from the stack and write it into <a>; empty stack = error
|
||||||
|
eq: 4 a b c
|
||||||
|
set <a> to 1 if <b> is equal to <c>; set it to 0 otherwise
|
||||||
|
gt: 5 a b c
|
||||||
|
set <a> to 1 if <b> is greater than <c>; set it to 0 otherwise
|
||||||
|
jmp: 6 a
|
||||||
|
jump to <a>
|
||||||
|
jt: 7 a b
|
||||||
|
if <a> is nonzero, jump to <b>
|
||||||
|
jf: 8 a b
|
||||||
|
if <a> is zero, jump to <b>
|
||||||
|
add: 9 a b c
|
||||||
|
assign into <a> the sum of <b> and <c> (modulo 32768)
|
||||||
|
mult: 10 a b c
|
||||||
|
store into <a> the product of <b> and <c> (modulo 32768)
|
||||||
|
mod: 11 a b c
|
||||||
|
store into <a> the remainder of <b> divided by <c>
|
||||||
|
and: 12 a b c
|
||||||
|
stores into <a> the bitwise and of <b> and <c>
|
||||||
|
or: 13 a b c
|
||||||
|
stores into <a> the bitwise or of <b> and <c>
|
||||||
|
not: 14 a b
|
||||||
|
stores 15-bit bitwise inverse of <b> in <a>
|
||||||
|
rmem: 15 a b
|
||||||
|
read memory at address <b> and write it to <a>
|
||||||
|
wmem: 16 a b
|
||||||
|
write the value from <b> into memory at address <a>
|
||||||
|
call: 17 a
|
||||||
|
write the address of the next instruction to the stack and jump to <a>
|
||||||
|
ret: 18
|
||||||
|
remove the top element from the stack and jump to it; empty stack = halt
|
||||||
|
out: 19 a
|
||||||
|
write the character represented by ascii code <a> to the terminal
|
||||||
|
in: 20 a
|
||||||
|
read a character from the terminal and write its ascii code to <a>; it can be assumed that once input starts, it will continue until a newline is encountered; this means that you can safely read whole lines from the keyboard and trust that they will be fully read
|
||||||
|
noop: 21
|
||||||
|
no operation
|
BIN
challenge.bin
Normal file
BIN
challenge.bin
Normal file
Binary file not shown.
158
src/vm.c
Normal file
158
src/vm.c
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#define MEM_SIZE (2 << 15)
|
||||||
|
#define STACK_SIZE 10000
|
||||||
|
|
||||||
|
#define val(r) ((r >> 15) ? reg[r & 7] : r)
|
||||||
|
|
||||||
|
size_t read_program(uint16_t mem[static 1], char *filename) {
|
||||||
|
FILE *fp = fopen(filename, "rb");
|
||||||
|
if (fp == NULL) {
|
||||||
|
fprintf(stderr, "Could not open file %s\n", filename);
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
int c = 0;
|
||||||
|
size_t n = -1;
|
||||||
|
int i = 0;
|
||||||
|
while ((c = fgetc(fp)) != EOF) {
|
||||||
|
if (i == 0) {
|
||||||
|
n++;
|
||||||
|
mem[n] = c;
|
||||||
|
} else {
|
||||||
|
mem[n] = (c << 8) | mem[n];
|
||||||
|
}
|
||||||
|
i = (i + 1) % 2;
|
||||||
|
}
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
return n + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void run(uint16_t mem[static 1]) {
|
||||||
|
uint16_t reg[8] = {0}; // registers
|
||||||
|
uint16_t stack[STACK_SIZE] = {0}; // stack
|
||||||
|
size_t stack_top = 0;
|
||||||
|
|
||||||
|
size_t pc = 0;
|
||||||
|
for (;;) {
|
||||||
|
uint16_t opcode = mem[pc];
|
||||||
|
uint16_t a = mem[pc + 1];
|
||||||
|
uint16_t b = mem[pc + 2];
|
||||||
|
uint16_t c = mem[pc + 3];
|
||||||
|
|
||||||
|
switch (opcode) {
|
||||||
|
case 0:
|
||||||
|
return;
|
||||||
|
case 1:
|
||||||
|
reg[a & 7] = val(b);
|
||||||
|
pc += 3;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
stack[stack_top] = val(a);
|
||||||
|
stack_top++;
|
||||||
|
pc += 2;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
stack_top--;
|
||||||
|
reg[a & 7] = stack[stack_top];
|
||||||
|
pc += 2;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
reg[a & 7] = val(b) == val(c);
|
||||||
|
pc += 4;
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
reg[a & 7] = val(b) > val(c);
|
||||||
|
pc += 4;
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
pc = val(a);
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
if (val(a)) {
|
||||||
|
pc = val(b);
|
||||||
|
} else {
|
||||||
|
pc += 3;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
if (!val(a)) {
|
||||||
|
pc = val(b);
|
||||||
|
} else {
|
||||||
|
pc += 3;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 9:
|
||||||
|
reg[a & 7] = (val(b) + val(c)) % 32768;
|
||||||
|
pc += 4;
|
||||||
|
break;
|
||||||
|
case 10:
|
||||||
|
reg[a & 7] = (val(b) * val(c)) % 32768;
|
||||||
|
pc += 4;
|
||||||
|
break;
|
||||||
|
case 11:
|
||||||
|
reg[a & 7] = val(b) % val(c);
|
||||||
|
pc += 4;
|
||||||
|
break;
|
||||||
|
case 12:
|
||||||
|
reg[a & 7] = val(b) & val(c);
|
||||||
|
pc += 4;
|
||||||
|
break;
|
||||||
|
case 13:
|
||||||
|
reg[a & 7] = val(b) | val(c);
|
||||||
|
pc += 4;
|
||||||
|
break;
|
||||||
|
case 14:
|
||||||
|
reg[a & 7] = ~val(b) & 0x7FFF;
|
||||||
|
pc += 3;
|
||||||
|
break;
|
||||||
|
case 15:
|
||||||
|
reg[a & 7] = mem[val(b)];
|
||||||
|
pc += 3;
|
||||||
|
break;
|
||||||
|
case 16:
|
||||||
|
mem[val(a)] = val(b);
|
||||||
|
pc += 3;
|
||||||
|
break;
|
||||||
|
case 17:
|
||||||
|
stack[stack_top] = pc + 2;
|
||||||
|
stack_top++;
|
||||||
|
pc = val(a);
|
||||||
|
break;
|
||||||
|
case 18:
|
||||||
|
if (!stack_top) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
stack_top--;
|
||||||
|
pc = stack[stack_top];
|
||||||
|
break;
|
||||||
|
case 19:
|
||||||
|
putchar(val(a));
|
||||||
|
pc += 2;
|
||||||
|
break;
|
||||||
|
case 20:
|
||||||
|
reg[a & 7] = getchar();
|
||||||
|
pc += 2;
|
||||||
|
break;
|
||||||
|
case 21:
|
||||||
|
pc++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
if (argc < 2) {
|
||||||
|
printf("Usage: %s <program>\n", argv[0]);
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t mem[MEM_SIZE] = {0};
|
||||||
|
read_program(mem, argv[1]);
|
||||||
|
run(mem);
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue