Q:

Register virtual machine and load/store architecture?

As a learning exercise, I would like to implement a virtual machine in C (a register machine, not a stack machine).
My knowledge of assembly language is limited to subsets of ARM and MIPS. I was planning to take inspiration from these for the vm's instruction set. They both use a load/store architecture, where arithmetic operations only occur on registers and there are load and store operations which allow moving data between memory and registers.
Is this kind of load/store architecture a natural fit for a virtual machine or no? If not, what are some alternatives? A small finite number of registers would not seem to work, as a program would just need to have n+1 live variables at a time to make it fail to work. I have read about some virtual machines that use infinitely many logical registers, but what are some other ways to solve this?.
What are the advantages/disadvantages of these alternatives (and load/store if feasible), and what are some suggestions on implementing these?
EDIT: For example, I notice that the Lua VM seems to have a finite number of registers but I can't find a memory load/store like in ARM or MIPS (though I do not mean to have things like an explicit call instruction as in Lua, since this could be implemented by just modifying a register as a stack pointer, storing parameters on this memory stack, and using jump instructions).
Also, when I say suggestions for implementation, I mean specifically on how to implement load/store or alternative in C, not how I should encode the instruction set (e.g. how wide to make the instructions, etc.).

A:

A RISC architecture would be a decent fit, because reduced instruction set means there are fewer instructions to implement.  Using a real architecture could work out for you because you’ll have a base of real code you can execute on it to test whether the emulator works as it should.  A good existing design to emulate, if I can make a suggestion, might be MMIX by Donald Knuth.  It’s the language of all the examples in the new edition of the famous The Art of Computer Programming.
If you want to design your own, keep in mind that real CPUs (almost all) have finite registers and use (at least) one of them as a stack register.  When there are more local variables than registers, the rest spill over onto the stack.
An instruction set in frequent real-world use that was originally designed to run in a software VM efficiently, but has been etched into silicon, is the Java Virtual Machine bytecode.  This is a stack-based language with no explicit registers.
Since you don’t want to do a stack-based CPU, your major decision is where on the spectrum from RISC to CISC you want to target your instruction set.  In many ways, CISC might work better on an emulator than on transistors, and indeed most modern implementations use RISC internally and emulate CISC in microcode.  But for example, if you add many different addressing modes to your instruction set, the opposite of load/store, you probably only have to implement them once in software, as opposed to duplicating them on a chip.  Variable-length instructions do not slow an emulator down as much as they slow down a hardware decoder pipeline.
Another important decision you’d want to make is whether to store the result of your operations in the source register (like x86 mostly does), or an arbitrary register (like SPARC).  The size of your instructions, the number of them, and the number of registers each instruction needs to specify, determines how many bits can use to specify each register, and therefore how many registers you can support.  For example, if you want instructions to be 32 bits long, and you want the add instruction to specify three registers, two operands and a destination, sixteen general-purpose registers would take four bits each, twelve bits to specify all three, whereas if addition always overwrites the first operand, you would only need two and could specify 64 registers in two six-bit slots.  But you could alternatively get around this problem by making instructions variable-length, so maybe the first byte of each instruction tells the CPU whether the operands are a register, an address in memory, an index from a pointer, or so on.
In order to run programs, even simple test programs, in your VM, you’ll want them to be able to do simple input and output.  Is this with special instructions to write to I/O ports, or is it memory-mapped?  If it’s memory-mapped, then to where?  (The IBM PC had serious problems for more than a decade because IBM mapped video memory to address 0xA000, AKA the 640K barrier.)  Is the memory a flat address space, or segmented, or does each process see a different flat address space?  How are address spaces and tasks started?  Does it have support for pages, memory-mapped files, no-execute, memory protection and copy-on-write?  Does it check for input by receiving an interrupt (How does it register the interrupt handler?) or listening on an input port? (How does it know which?)  If this is all abstracted and it calls an operating system—how does it do that?  Is the operating system part of the emulator or running under it?  Is the CPU going to need a set of instructions to support an OS, and will those instructions be privileged?
At some point, you’ll want to add support for SIMD and vectors; that’s an important aspect you’ll want to cover in an exercise like this.
You might consider taking a look at an undergrad computer architecture textbook, for example Structured Computer Organization by Andrew Tanenbaum, to get a better overview of the kinds of architectures out there.

