Firth and Simplifying Software
As the x86_64 port of Firth nears usability, I'm already planning the ARMv6 port. With my Raspberry Pi looped back to my Mac, the port will be cross compiled from Firth. The reason for that is most of the Firth code right now is bootstrap code, meant to get the base system image running. Once the core image is complete with multiple threads of execution, multiple dictionaries, and a system wide lexicon, I will abandon much of the hand coded assembler in favor of Firth object code. The reason for the hand coded assembler at the start is that it allows me to grow the VM iteratively, without relying upon imperfect definitions. As I implement bits in assembler, I prove out the desIgn and then refactor into Firth instructions. When I encounter a set of instructions that are difficult to implement using the VM instructions, it provides an opportunity to rethink both the design of the VM and the application itself.
Rethinking The VM
One of the common problems facing every designer is knowing when a design is too simple. Some design decisions restrict what is easy to accomplish. If a significant number of opportunities for simplification across the code base exist by a slight increase in complexity of the VM, it can be a net win to extend the VM. Great examples of these sorts of instructions are stream oriented memory access instructions. Reading or writing from a series of adjacent locations can be done with a handful of instructions and a couple special purpose memory address reflgisters. Many processors even have streaming instructions designed specifically for these purposes. As a result, these sorts of changes can help the VM better exploit the underlying platform and simplify the application. Both are good justifications for added complexity in the base design.
Rethinking The Software
Sometimes, however, the problem being solved in software needs only be solved once. In these cases, attempting to expand the scope of the VM will not reduce the complexity, merely shift it. Similarly, the solution may not bare enough corresponce to the problem to qualify as a good fit. This is often the case when a more generalized solution is applied to a specific problem. The generalized solution is functional in that it may accomodate all of the edge cases for the specific problem at hand, but may also introduce complexity to solve edge cases which do not exist in the specific problem. Take for example strings of ASCII text as a source file format. Consider that to manage source code in this form you typically need:
- a parser, lexer, and tokenizer to generate
- an abstract syntax tree which is processed into
- an intermediary runtime language which is optimized by
- a term rewriting engine which feeds into
- a compiler that does machine translation
You end up with several representations of the same thing before getting something usable by the machine. The simpler approach is for the editor to directly manipulate a structured representation, which is directly compiled by the just in time environment. The net effect is skipping the AST and going straight to the IRL, and allowing the compiler to rely upon edit time macro expansions and refactoring to optimize the source. This is radically different from a traditional environment as the programmer's tools make explicit at edit time what optimizations are being performed. This allows the programmer to tune their software by understanding the underlying representation.