commit 87a815ac27bd257b7c8bd67816119b8af1d92e4e Author: Dimitri Lozeve Date: Wed Jan 13 09:16:21 2021 +0100 Initial commit: implementation of the virtual machine diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..70b6a74 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*\~ +.gdb_history +vm +synacor-challenge.tgz diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a31b575 --- /dev/null +++ b/Makefile @@ -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 diff --git a/README.org b/README.org new file mode 100644 index 0000000..2fa75d9 --- /dev/null +++ b/README.org @@ -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 diff --git a/arch-spec b/arch-spec new file mode 100644 index 0000000..0ce07d6 --- /dev/null +++ b/arch-spec @@ -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 to the value of +push: 2 a + push onto the stack +pop: 3 a + remove the top element from the stack and write it into ; empty stack = error +eq: 4 a b c + set to 1 if is equal to ; set it to 0 otherwise +gt: 5 a b c + set to 1 if is greater than ; set it to 0 otherwise +jmp: 6 a + jump to +jt: 7 a b + if is nonzero, jump to +jf: 8 a b + if is zero, jump to +add: 9 a b c + assign into the sum of and (modulo 32768) +mult: 10 a b c + store into the product of and (modulo 32768) +mod: 11 a b c + store into the remainder of divided by +and: 12 a b c + stores into the bitwise and of and +or: 13 a b c + stores into the bitwise or of and +not: 14 a b + stores 15-bit bitwise inverse of in +rmem: 15 a b + read memory at address and write it to +wmem: 16 a b + write the value from into memory at address +call: 17 a + write the address of the next instruction to the stack and jump to +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 to the terminal +in: 20 a + read a character from the terminal and write its ascii code to ; 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 diff --git a/challenge.bin b/challenge.bin new file mode 100644 index 0000000..642b1ce Binary files /dev/null and b/challenge.bin differ diff --git a/src/vm.c b/src/vm.c new file mode 100644 index 0000000..9560df4 --- /dev/null +++ b/src/vm.c @@ -0,0 +1,158 @@ +#include +#include +#include + +#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 \n", argv[0]); + return EXIT_SUCCESS; + } + + uint16_t mem[MEM_SIZE] = {0}; + read_program(mem, argv[1]); + run(mem); + + return EXIT_SUCCESS; +}