I have implemented a Forth, a BASIC, and a formula interpreter for a spreadsheet. The idea of a virtual instruction set keeps cropping up from time-to-time with me.
In this post, I shall be looking into my github repo bennett . It contains source code from the book “Introduction to Compiling Techniques: A First Course using ANSI C, LEX and YACC”, by JP Bennett. I have made my own modifications to improve compilation on modern hardware, as well as re-implementing the assembler and disassembler in Raku (formerly Perl6, of course).
The purpose of the book is to implement a Basic-like language which supports recursion. The assembler is therefore a perfectly capable one. The assembly language consists of 15 instructions: HALT (to stop the machine), NOP (does nothing), TRAP (outputs a byte), ADD/SUB/MUL/DIV (arithmetic), STI/LDI/LDA/LDR (storing/retrieving values) and BZE/BNZ/BRA/BAL (branching).
The major limitations is that it does not take in keyboard input, and doesn’t have a facility for incorporating foreign functions. These shouldn’t be too difficult to add, especially the former.
The repo does have a C implementation of the VSL (Very Simple Language, i.e. a form of BASIC, which compiles to assembler), VAS (assembler, which compiles to machine instructions), VAM (a virtual machine executer).
Raku enthusiasts may be more interested in the stuff that I’ve implemented for Raku, though. It is available in the myvm directory . asm.p6 is the assembler, and disasm.p6 is the disassembler. It is satisfying to note that the assembler takes only slightly more than 100 lines to implement using Raku’s grammar facilities. The line count could be reduced further if offsets were stored in little-endian format rather than big-endian. I chose to stick with Bennett’s way of implementing things, though, for compatibility.
If you want to test things out, then compile the top level directory:
autoreconf -iv ./configure make cd examples make
The examples will compile to VAS and VAM using Bennett’s implementations. You can write your own assembly programs, but you’ll probably want to use the examples in the first instance. If you are in the examples directory, and have already issued a make in accordance with the instructions above, then you can prove that the Raku assembler works by issuing a command like:
This will create a file called out.vam, which should be identical to fact.vam. You can run the compiled code by issuing the command:
The next step might be to implement the virtual machine in Raku, and a little more ambitiously, the parser for VSL.
Some directions I personally might want to take:
- see if the assembly language is a good base implementation for a Schorre Meta-II compiler. That would be an interesting project.
- see if I can implement co-routines or continuations in an easy fashion.
If there’s significant interest, I’ll post some details on Bennett’s instruction set, and what’s going on under the hood of the compiler. The repo does contain some info in vam.txt for those interested in writing some assembler.
That’ll do for now, though.
I’ve made a few exciting updates to the assembler and VM. Surprisingly perhaps, Bennett’s VM didn’t have an N flag, to test for negativity. There were no branch instructions based on this. So you could only branch unconditionally, on zero, or not-zero.
Consequently, I have added BLTZ (branch on less than zero) and BGTZ (branch on greater than 0).
I have also added a SYS instruction, in order to make system calls (“interrupts”). The format for it is:
where n is the call number. Currently there is only one system call you can make: 0 (for getchar()). You can extend the VM to make other calls, though.
examples/echo.vsl , which copies
stdout. Here is its implementation:
\ added by mcarter 2020-06-30 \ test of syscalls \ this just echos stdin to stdout loop: SYS 0 \ getchar into R15 LDR R15, R15 \ check for EOF BLTZ fin \ if EOF then finished TRAP BRA loop fin: HALT
You can read more about the VM instructions here.