Hello world

I’ve been working on a virtual machine under 9front implementing the LC3 educational ISA. This has been a lot of fun, and I realise that probably sounds weird to a bunch of people who did CompSci at university and were made to do this sort of thing. In that sense I’m like a mature student - I’m actually doing it because I care.

Initially I started out when I was on a 7 week trip through Asia (I know, I never update this thing, but then nobody reads it either). I was in Kuala Lumpur and taking a much needed rest, and for some reason that I can’t quite remember I just decided to search “how to write a virtual machine” and a blog post came up which essentially lined up the problem of how you’d go about implementing LC3. So I just started.

And I continued in Nepal, where I finished off the majority of the VM and a rudimentary assembler and patched vexed hex editor for binary input to help with my hand-hacked test machine code.

And I continued on a beach in Thailand, where I wrote the beginnings of an assembler.

Yesterday I reached an important milestone - I was able to assemble and run the following program:

    .ORIG   x3000
    NOP
    ADD R2,R2,x6    ; we need 6 loops to get to 'h'
    NOT R2,R2       ; we want to be able to subtract this number so we'll 2's complements it
    ADD R2,R2,x1
LOOPH
  ADD   R0,R0,xf
    ADD R1,R1,x1
    ADD R3,R1,R2    ; compare if we're at 6 loops yet
    BRn LOOPH
    ADD R0,R0,xe
    OUT
E   .FILL   x65
L   .FILL   x6c
O   .FILL   x6f
    LD  R0,E
    OUT
    LD  R0,L
    OUT
    OUT
    LD  R0,O
    OUT
    .BLKW   x5
    LEA R0,WRLD
    PUTSP
    HALT
WRLD
  .STRINGZ  x20776f726c64210a
    .END

Which runs thusly:

cpu% lc3/lc3 -f out.hex
hello world!
Program complete, exiting

I think it’s quite a fun “hello world” because I wrote it as I was implementing features of the assembler…

Raw data directives

Up until the first OUT (which was a TRAP 0x21 at the time) I only just had assembling into a binary format working, with none of the raw data loading directives so I had to ADD the largest positive value supported by ADD’s immediate mode (which only supports a 5 bit signed operand) 6 times and then add the remainder to get to 0x68 or h.

To get the rest of ello I then implemented .FILL and (now that I think about it) just let the machine blast through trying to interpret those instructions (I should probalby fix that hahah), but this means that we’re then able to LD those memory locations into R0 which then means we can use the OUT trap to print each one in turn.

Then we have .BLKW which I didn’t have much use for in this program but it was easy to implement after .FILL so my implementation also initializes to 0x0 which maps to NOP (or more strictly, unconditional branch to PC).

Finally, .STRINGZ. As you can see, I’ve not implemented any data literals other than hex yet, so I still have string literals and decimal to implement but I don’t need those to demo that my VM is working.

Future work

I have some plans for this VM which I’m not going to talk about, because I’ve found in my life that talking about an idea to people often exhausts the need to actually follow through on it - needless to say it’s weird dumb bullshit like you’d expect from me.

The plans which I am going to talk about here are the 9p debugging features I’d like to implement.

Essentially, I’d like to write a filesystem which has something like the following layout:

reg/
  pc
  cond
  r0
  r1
  r2
  r3
  r4
  r5
  r6
mem/
  full
  0/
    0/
      0/
        0
        1
        ...
    1/
    ...
  1/
  2/
  3/
  ...
clk
cons

The idea here is that you can read and update the state of any given memory address or register at any point, and you can step the clock simply by reading clk. You can follow the program along by reading reg/pc and then reading the corresponding address.

In fact, beyond that I doubt it would be that hard to also have a disasm/ tree as well which will allow you to follow along with a disassembled version of the program.

This then means that your debugger is a simple filesystem API which you can then instrument and script around using rc, awk, whatever. In that sense, I’m kind of surprised that acid was never designed in this way, because I’d say one of the huge advantages of Plan 9 is that you don’t have to learn someone’s implementation of a given API or language when you can simply explore a filesystem and write tools to manipulate it.

Anyway, that’s what I’ve been up to.