1

This question already has an answer here:

I'm working on a simple bytecode interpreter to learn how virtual machines work. I've read about VMs and it seems that all of them are either stack based or register based. At the time it made sense, but then I started working on my interpreter and realized what am I doing? (I decided on a register machine.) I'm just loading all these values from pointers into these fake registers, doing operations, then putting the result back into the register and copying to a pointer. I thought, why not just do this directly? Why create fake registers that just serve as an overhead. Maybe I don't know enough about VMs yet to fully understand, but it would seem to me like my way would be much faster.

marked as duplicate by gnat, Kilian Foth, Bart van Ingen Schenau, Dan Pichelman, GlenH7 Oct 20 '14 at 14:32

This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.

  • 1
    What do you mean by "doing this directly"? Do you want to interpret, say, a DAG with phis instead of a flat bytecode? I'm affraid, there is no easy way to optimise such an interpreter, while for the flat ones you can do a lot of nice tricks (like threaded code, simple template JITs, etc.). – SK-logic Oct 20 '14 at 9:25
  • there is also third VM: memory-to-memory so if you work a lot with pointers it's your choice: there are no registers, all operations use 2 or 3 memory addresses as parameters, and you can map some traditional registers like PC, SP to fixed VM memory addresses – Dmitry Ponyatov Oct 25 '17 at 9:42
3

The job of a VM is to execute arbitrary code. (Put aside thoughts of the JIT for a second, we are just talking the pure VM). How do you expect to compile arbitrary programs down to bytecode for your VM? You have to have an instruction set. That intruction set has to work on something. If you are talking about directly operating variables from a high level language, well sure that is possible, but it isn't really a VM. It is just an interpreter. An interpreter can be symbolic (ie. it can work on names), and it can be high level. It can operate on AST nodes, or on pure text. But a VM generally is lower level, emulates a simple CPU, and the bytecode itself is a form of intermediate language. So you can distribute your programs in bytecode form which gives you some level of protection (though in Java and .NET world not as much as it could were the VM a bit lower level).

The downside to high level intepreters is they do a lot of symbolic work every time you execute the program. The strength of a VM comes when combined with the compiler. Take C# for example. Sure you can write a program to interpreter C# at a high level (I did this for the Parrot VM years ago), but the execution is much more efficient if you do some of the work ahead of time like compile variables down to registers or stack operands. Then your program becomes much denser than ASTs, and is again, conveniently represented as dense bytecode.

You define virtual operations that are simpler, or closer to real instructions to be compiled on the fly to real native executable by the JITter, also known as Just In Time Compilation. The simpler, more low level the ops are, the more efficient the JITter implementation is.

Also, in non-JIT mode (pure VM operation) if you compile the symbolic accesses down to registers or stack operands, you can improve the performance of your VM by making sure the core register area is cacheable. If your variables are all over the heap, and you opt to operate on them directly, you lose cache efficiency. With a register VM we might design it with fixed registers, or unlimited virtual registers, but in any case, an allocator will map them to fixed registers that are packed into a contiguous segment of RAM that we hope will fit into less L1 cache lines and gain performance for spatial locality.

But a high level symbolic VM that operates on variables directly is possible. Here are a couple of alternatives.

VM A - Symbolic:

newglobal "count"
add "count", 1

VM B - Register (memory transfer):

mkglobal "count"
move $R1, "count"
inc $R1

The benefit of VM B is the opcodes are very close to or easily translatable to real live processors, because most processors don't allow operations on memory variables and require you to load the variable into a register or onto a stack. The JIT will have much less to do. With VM A, the add operation is really a fat, aggregate operation which has to be compiled into individual native ops.

For a simple comparison to the two approaches study Perl5 and study C#/.NET bytecode. While it is possible to compile Perl5 to a bytecode, it isn't the default form of it. Perl really does more symbolic interpretation of an in-memory AST. That is why it was a major architectural change when we wrote Parrot for Perl6, though I'm not convinced it was the right decision now for that language.

1

There is a third type of virtual machine which you may want to investigate. This type uses an execution model called Static Single Assignment, which does away with registers and stack in favor of a more abstract notion of a result that is calculated once and then used one or more times, but the bytecode says nothing about how it is stored.

The best known implementation of this idea is LLVM, so you may want to look at how they did it.

  • SSA isn't typically used for execution, only as an IR for easier analysis (and mapped to registers and/or stack slots before execution). LLVM is not a VM in the sense of this question. – user7043 Oct 20 '14 at 8:45
  • LLVM's primary purpose may not be as a VM, but it does nevertheless include a working vm implementation, showing that it is feasible to use SSA for direct execution. – Jules Oct 20 '14 at 8:56
  • It can certainly be executed, but nearly anything can. It's not something anyone should use when direct execution is at all interesting. There is no advantage, and the downside of having to implement phi nodes. You can pre-process the SSA code to remove them, but then you're essentially doing register allocation and your actual VM becomes a register VM. – user7043 Oct 20 '14 at 9:12

Not the answer you're looking for? Browse other questions tagged or ask your own question.