I know it's been quite a while since I wrote anything. But I haven't been totally idle. For the last several weeks I've been working on something that should be of some benefit to both Retro and Parable.
Retro runs on a virtual machine (Ngaro) and Parable runs on an integreted virtual machine which isn't named. Though there are some similarities in the internal design, these are not compatible with each other, and both have some shortcomings.
Ngaro works, but the I/O model is a bit crufty and it's tied very closely to a traditional console I/O approach. This has been limiting as I work to move towards modern devices. Parable's core VM doesn't provide any I/O and works pretty well, but it's much more complex and needs a considerable amount of memory. Ngaro's design also suffers somewhat from excessive memory usage: each 32-bit cell can hold only one instruction, so 4 bytes per instruction adds up quickly, especially on devices with very small amounts of RAM.
I decided that it'd be nice to have a little VM like Ngaro, but with the I/O flexibility Parable provides. Nga is the result. It's based on my work with Ngaro:
- small, byte coded instruction set
- easy to implement
- simple linear memory model
- 32-bit cells
It draws a little from Parable:
- easy to add instructions for custom I/O
- sources intermixed with documentation and commentary
And adds some twists of its own:
- support for packing up to 4 instructions per cell
- reduced special cases
- slightly smaller instruction set overall
- easily embedded into larger application
Nga is small: the reference implementation in C is just 263 lines, including an optional standalone mode like the old pre interface for Parable (run code, display resulting stack). There are 27 instructions. Unlike Ngaro, only one requires a value in the following cell (LIT). Jumps, calls, etc take the address from the stack.
Tooling is better. I have a standalone assembler (with implementations in C and Python, an assembly preprocessor (in Python), a disassembler (in Python), and a PL/0 compiler (in Python). In the near future I'll have a simple debugger tool that integrates the VM, assembler, and disassembler allowing for step by step evaluation of code.
The support for packed instructions is really nice. In Ngaro a lot of space was wasted. As a simple example, here's a square function:
:square dup mul ret
In Ngaro this would be three cells. Nga allows the entire routine to fit into one. In my tests the overall image sizes are between 20-40% smaller than with unpacked instructions. This should be a bit benefit on targets with tight memory limits.
Overall it's pretty nice. My intention is:
- finish the tooling (debugger)
- implement an I/O layer
- write a Forth implementation (Retro 12 ?)
- implement an integer-based subset of Parable
- maybe do a C compiler