|
One of my favorite techniques for implementing VMs is the "computed goto": https://eli.thegreenplace.net/2012/07/12/computed-goto-for-e... Consider this example for dispatching instructions from the article: while (running)
{
uint16_t op = mem_read(reg[R_PC]++) >> 12;
switch (op)
{
case OP_ADD:
{ADD, 6}
break;
case OP_AND:
{AND, 7}
break;
case OP_NOT:
{NOT, 7}
break;
case OP_BR:
{BR, 7}
break;
...
}
}
That code has a lot of branching. The switch statement has to jump to the corresponding case, the break statement branches to the bottom, and then there is third branch to get back to the top of the while loop. Three branches just to hit one instruction.Now imagine we had written the above as: static void* dispatch_table[] = { &&OP_ADD, &&OP_AND, &&OP_NOT, &&OP_BR,
... };
#define DISPATCH() goto *dispatch_table[memory[reg[R_PC]++] >> 12]
DISPATCH();
OP_ADD:
{ADD, 6}
DISPATCH();
OP_AND:
{AND, 7}
DISPATCH();
OP_NOT:
{NOT, 7}
DISPATCH();
OP_BR:
{BR, 7}
DISPATCH();
...
Now there is only one branch per instruction. The handler for each instruction directly jumps to the next location via the goto. There is no need to be in an explicit loop because the interpreter runs until it hits a halting instruction.Many VMs now use this technique, including the canonical Ruby and Python interpreters. |
My latest project [1] simply calls into a struct via a function pointer for each instruction. With a twist. Since it returns the pointer to the next operation and uses longjmp to stop the program, which means I can get away without lookups and without a loop condition. And as an added bonus the code is much nicer to deal with and more extendable, since the set of operations isn't hardcoded any more.
Comparing language performance is tricky business, but it mostly runs faster than Python3 from my tests so far. My point is that computed goto is not the end of the story.
I'll just add that there are many different ways to write a VM; the one posted here is very low level, the one linked below reasonably high level. Using physical CPUs as inspiration is all good, but there's nothing gained from pretending in itself unless you're compiling to native code at some point.
[0] http://www.emulators.com/docs/nx25_nostradamus.htm
[1] https://gitlab.com/sifoo/snigl