Disclaimer: I am not experienced at this, so I might use the wrong terminology, or have some totally stupid ideas. The point is to learn by doing, and sometimes that involves failing and looking really stupid. Now having said that...
Last night I started working on a project that I've been toying with the idea of for a while.
I want to make a virtual machine that meets all the following critera:
- Portable, with no weird dependencies. Should just compile with my utilities library and standard C++. Should run on every platform supported by C++.
- Sandboxable. Should be able to run untrusted code. (Lua is not built for this.)
- Really fast. As fast as possible without just going and writing a JIT. (Don't want to deal with individual platform weirdness just yet.)
- Pause/resume capable. (Something DerpScript is not.)
- Can save and restore VM state. (Something Lua cannot do with any level of sanity.)
- A new LLVM backend targeted to it, or an assembler that can read LCC's intermediate assembly representation in a similar manner to q3asm.
And for these goals:
- Scripting for games, allowing fast iteration time and dynamic reloading of game logic. The execution time must be fast to be suitable for this.
- User-programmable game scripts that can safely be run on a multiplayer server, even though they're written by potentially hostile players. We need sandboxing, suspend/resume support, and the ability to take and restore state snapshots for this to work out.
- General purpose scripting. Maybe get it integrated with that texture generator tool I was working on. For this, it needs a sane API and the ability to hold references to data outside of the VM. I don't know how I'm going to handle the latter part, and the way I did it in DerpScript isn't going to cut it.
So after one night of work I have the start of the VM, a mostly complete assembler (not using LCC's assembly), and a disassembler. At the moment it's possible to write some very simple assembly programs and do basic flow control and memory access. Only the "add" instruction has been implemented on the arithmetic side.
The specs of the VM are (right now):
- No addressable general purpose registers. Everything is just direct memory access. There's a program counter and stack pointer, and that's it at the moment. So far there is no way to modify them directly. Push/pop instructions exist as a way to modify the stack pointer, with no direct access (yet). Jump and branch instructions modify the program counter, but there's no way to read it (yet).
- Three addressing modes: Immediate, direct memory, and indirect with an immediate value as an offset from the stack pointer. There are additional instructions for dereferencing pointers in memory. (This could all change, because I'd rather have the dereference-pointer ops be replaced with addressing modes built into the opcodes.)
- Memory is divided into 32-bit Words. Each instruction with encoded addressing modes takes up exactly one Word, plus one Word per parameter. This can change with a modification to a typedef to change the meaning of Word, but there's a minimum usable size. 8-bit Words won't work just because the encoded instructions won't fit in them.
That's all I have right now. I'll add more information as I piece it together. I haven't done a good job of making the code presentable, so no source code or example code yet. But maybe soon.