-
Notifications
You must be signed in to change notification settings - Fork 0
2. Initial VM
I started the project as most sane programmers would: as a console project. The result was less than 400 lines long, and that's with plenty of whitespace and comments.
It consisted of the following pieces:
Global storage:
static const size_t c_dwAddressSpace = 1 << 15;
static const size_t c_dwNumRegisters = 8;
std::vector<uint16_t> memory(c_dwAddressSpace);
std::vector<uint16_t> registers(c_dwNumRegisters);
std::vector<uint16_t> stack;
Some useful helpers:
uint16_t Translate(uint16_t value)
{
if (value <= 32767)
{
return value;
}
if (value <= 32775)
{
return registers[value - 32768];
}
assert(0 && "Invalid value");
return 0xFFFF;
}
void Write(uint16_t address, uint16_t value)
{
if (address <= 32767)
{
memory[address] = value;
}
else if (address <= 32775)
{
registers[address - 32768] = value;
}
else
{
assert(0 && "Invalid address");
}
}
Reading in the binary file from commandline:
std::ifstream in(argv[1], std::ifstream::in | std::ifstream::binary);
if (!in.good())
{
std::cout << "Failed to open file: " << argv[1] << std::endl;
return 1;
}
size_t dwCurOffset = 0;
while (in.good())
{
in.read((char *)&memory[dwCurOffset++], sizeof(uint16_t));
}
And finally, a giant loop with a switch statement:
uint16_t inst = 0;
bool halted = false;
while (!halted)
{
const uint16_t op = memory[inst++];
switch (op)
{
// All the cases
}
}
The source for this console VM is available on my own github, at this link: https://github.com/dclamage/SynacorVM
Some notable events:
-
The documentation was not always clear whether a parameter should be treated as an immediate value, or whether it should be dereferenced as a memory address. This was particularly hairy for me with the RMEM and WMEM instructions. I went back and forth a few times on how they should work. In fact, at one point I had RMEM correct, but WMEM incorrect. Then I fixed WMEM and then applied that fix to RMEM, breaking it in the process.
-
When running the challenge.bin, even with just a couple ops implemented, it will print out code #2.
-
After printing the code, it runs a self test. This was the first time I started grasping how clever this challenge was. The self test essentially bootstraps the opcodes, in that they are tested in a specific order such that one is verified to work properly before being used as a dependency to verify other opcodes. Without the self test, there would have been much more headache involved in getting the opcodes exactly correct.
-
After passing all self tests, you get code #3.
How is this only 3/8?
Oh, and after passing the tests, you end up playing a Zork-like adventure game! I figured this was the victory lap... boy was I wrong.