TinyVM 1.0 released; adds 16 lines of code, registers, a VM stack and more
github.com
github.com
EDIT: I'm not trolling, I'm seriously wondering. There's plenty of literature out there on the advantages of three-operand instructions, large register spaces, and RISC-type instructions. Was there a reason that x86 was used as a basis?
1. Familiarity with x86 assembly. It's probably the assembly language that the majority of people who write assembly language know. 6502 probably being 2nd place
2. Easier to port small x86 assembly programs
3. Even if it's stuck in the 80's it's the most widely used assembly language and there are dedicated online communities of people still writing x86 asm today in 2011.
I seriously doubt that people will do it. If people write assembly, they mostly do it either because of speed, or because it's impossible to do in any other way. None of the reason is relevant in context of VM assembly, which is usually meant to be a compiler's target.
This VM doesn't have a particularly compact in-memory encoding - it uses an entire `int` for each argument! They could double the number of registers and the entire register set would fit in a cache line (except for EIP) at 4 bytes apiece. I guess it is nice that all 9 fit in one line now, but I doubt that was considered.
A stack-based virtual machine architecture has compact code representation (only bytecodes) where operands are pushed onto and popped of the corresponding argument stack. Register based VM-architectures require you to encode source and destination registers into the operations. IIRC, for Java bytecode, going from a stack-based representation to a register-based virtual machine grew the code size by more than 40%. But on the other hand interpretation got more efficient, since you have less instructions overall and thus fewer instruction dispatches. If you want more details I'll gladly point you to the excellent and canonical reference for this: Shi, Casey, Ertl and Gregg: "Virtual machine showdown: Stack versus registers." TACO http://dl.acm.org/citation.cfm?id=1328195.1328197 (There is also the journal article's predecessor from VEE05, https://www.usenix.org/events/vee05/full_papers/p153-yunhe.p...)
It's a stack-oriented VM, similarly designed to be as simple as possible, but my goal was to create something like an idealized game console. Thus, I have basic graphics support and gamepad input. The project includes a compiler for a dialect of Forth and a big pile of example code.
It is an almost complete .NET2.0 CIL interpreter, so it can run your exe's compiled from C#/VB/etc...
I'm not sure it has much practical application, but it was certainly fun to write, and I learnt a huge amount about interpreters and how .NET works.
PS Lots of bugs, a memory leak, an incomplete base class library, etc... so don't try to use this for anything serious!
I'm still as impressed as when I first saw it. Small, simple and elegant. Neat little project.
Instruction dispatching and malloc handling were discussed here last month in http://news.ycombinator.com/item?id=2722383 (Instruction dispatch is now a switch† instead of chain of if statements, malloc() results are still unchecked.)
␄
† The switch was slower in my benchmarks last time, I don't have time to check this time.
You have to implement a VM and run the provided program. This will give you access to the actual problem set.
While the doubled register count makes a big difference - especially with the new x86-64 calling convention - register renaming and hidden registers do a lot on x86 to mitigate the lack of general purpose registers. You don't need more than 8 registers to get the benefits of having more than 8.
But to my knowledge, compatibility with legacy compiled software is not a practical issue for VM's. And even if it were, this isn't a compatible implementation of x86 anyway. So what then is the reasoning for this choice?
I mentioned function calls because that's a boundary when an optimizer - either in-processor or in-compiler - is often forced to use memory and not registers. The big-N-register "register window" of RISC processors kicks this problem's ass. But with aggressive inlining already common, these boundaries don't come up as often as you'd think. So the x86-64 calling convention does a pretty damn good job too.
Edit: by the way, I guess I haven't really commenting on TinyVM one way or the other when I responded to you. You're absolutely right and the compatibility is not necessary at all. I think the register count limitation especially is just silly. I pointed out in a different thread that they could double their register count and they'd still fit in a single cache line... they even already have a naming convention (r08-r15) they can hijack.
If you have more VM registers than CPU registers, you have to write code to load and store your VM registers.
If you have more CPU registers than VM registers, you will have to write code to undo some of the register spilling done by the compiler (if you want your VM to use the hardware to the fullest.)
So, the optimum (from a viewpoint of 'makes implementation easier') depends on the target architecture.
Of course, a generic VM cannot know how many registers (and what kinds; uniform register files are not universally present) the target CPU will have.
That is a reason that choosing either zero (i.e. a stack machine) or infinitely many (i.e. SSA) are popular choices fo VMs: for the first, you know for sure your target will not have fewer registers than your VM, for the second, that it will not have more.
If you choose any other number of VM registers, a fast generic VM will have to handle both cases.
Alternatively, of course, one can target a specific architecture, and have the VM be fairly close to the target; Google's NaCl is (was? I think they changed things a bit) an extreme example. I have not checked the code, but this, I think, is similar.