Publish a post about vm arrays and functions
This commit is contained in:
parent
f1b24ea7a5
commit
9c8c3f8739
1 changed files with 119 additions and 0 deletions
|
@ -0,0 +1,119 @@
|
||||||
|
X-Date: 2023-08-20T21:40:00Z
|
||||||
|
X-Note-Id: 906d96de-189c-4755-89b9-3cd43f6e9542
|
||||||
|
Subject: VM progress update: arrays and function calls
|
||||||
|
X-Slug: vm_progress_update_arrays_and_function_calls
|
||||||
|
|
||||||
|
The virtual machine I'm working on can now allocate and access arrays from the bytecode and assembly.
|
||||||
|
Arrays are one of the basic data structures in the VM, in addition to numeric types and pointers.
|
||||||
|
Until now, I was only able to operate with arrays from unit tests, because memory allocation and garbage
|
||||||
|
collection wasn't fully there.
|
||||||
|
|
||||||
|
Now, with garbage collection and memory allocation in place, the following is possible:
|
||||||
|
|
||||||
|
```
|
||||||
|
;; Allocate an array of 10240 untyped
|
||||||
|
;; elements and place a pointer to it
|
||||||
|
;; into register r1
|
||||||
|
arri r1, 10240
|
||||||
|
|
||||||
|
;; Load '42' into register r2
|
||||||
|
li r2, 42
|
||||||
|
|
||||||
|
;; Put the content of r2 into the array
|
||||||
|
;; pointed to by r1 with offset of 10000
|
||||||
|
asi r1, r2, 10000
|
||||||
|
|
||||||
|
;; Read the content of the array with
|
||||||
|
;; offset of 10000 to register r0
|
||||||
|
ali r0, r1, 10000
|
||||||
|
```
|
||||||
|
|
||||||
|
When saved as `array.asm`, you can byte-compile it and execute like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ cat array.asm | ./asm | ./vm
|
||||||
|
R0: 42
|
||||||
|
```
|
||||||
|
|
||||||
|
As of now, R0 is printed by default when the virtual machine executes the last
|
||||||
|
instruction.
|
||||||
|
|
||||||
|
Another interesting feature that is available now is "jump-and-link". It is based on the same
|
||||||
|
concept in the RISC-V architecture, pretty much like x86 "call". It jumps to a specific offset
|
||||||
|
and stores the address of the next instruction in the specified register. Here's an example:
|
||||||
|
|
||||||
|
```
|
||||||
|
begin:
|
||||||
|
;; Load '42' into register r2
|
||||||
|
li r2, 42
|
||||||
|
|
||||||
|
;; Jump to label 'fun' and store
|
||||||
|
;; the address of next instruction in
|
||||||
|
;; the r3 register
|
||||||
|
jal r3, fun
|
||||||
|
|
||||||
|
;; Continue execution after return from
|
||||||
|
;; 'fun'
|
||||||
|
;; Increment register r0
|
||||||
|
addi r0, r0, 1
|
||||||
|
|
||||||
|
;; Jump to label 'end' unconditionally
|
||||||
|
jmp end
|
||||||
|
|
||||||
|
fun:
|
||||||
|
;; Copy content of register r2 into
|
||||||
|
;; register r0
|
||||||
|
mov r0, r2
|
||||||
|
|
||||||
|
;; Jump to location saved in r3
|
||||||
|
jr r3
|
||||||
|
|
||||||
|
end:
|
||||||
|
;; End of program
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
And there are now also stack operation. They look and work pretty much like you would expect
|
||||||
|
on normal hardware, except that the stack is also accessible as an array.
|
||||||
|
|
||||||
|
```
|
||||||
|
;; Load '42' into register r2
|
||||||
|
li r2, 42
|
||||||
|
|
||||||
|
;; Load '43' into register r3
|
||||||
|
li r3, 43
|
||||||
|
|
||||||
|
;; Push register r2 onto the stack
|
||||||
|
push r2
|
||||||
|
|
||||||
|
;; Update the stack's top element
|
||||||
|
;; to be the content of register r3.
|
||||||
|
;; The 'sp' is a register that contains
|
||||||
|
;; stack pointer. Negative offsets
|
||||||
|
;; are supported as well.
|
||||||
|
asi sp, r3, 0
|
||||||
|
|
||||||
|
;; Pop the top element of the stack
|
||||||
|
;; into register r0
|
||||||
|
pop r0
|
||||||
|
```
|
||||||
|
|
||||||
|
For the stack, I also have a "base pointer" which is supposed to serve as an address of the
|
||||||
|
current call frame. It would be useful when the time comes to compile an actual language into
|
||||||
|
the bytecode.
|
||||||
|
|
||||||
|
In addition to the new instructions, the virtual machine now loads the bundle of both code and
|
||||||
|
data from the binary representation. Data section is needed for storing larger constant objects
|
||||||
|
that I would need when implementing the programming language on top of it. For example, 64-bit
|
||||||
|
integers, floating point constants, strings and others. The assembly compiler though currently
|
||||||
|
just emits empty data section because it can't properly evaluate constants yet. This would be
|
||||||
|
something I'll handle separately.
|
||||||
|
|
||||||
|
All in all, I'm quite happy with the progress. The code is a bit messy and ugly, but it is slowly
|
||||||
|
making it into a better state as I add more functionality and do refactoring bit-by-bit.
|
||||||
|
|
||||||
|
After ironing out the basics, I would probably work on the dynamic linking and interaction with
|
||||||
|
a C FFI. This is needed to give the code executing in the virtual machine ability to call
|
||||||
|
external functions (like filesystem access, network and others).
|
||||||
|
|
||||||
|
As usual, the experimental code can be found [here](https://git.sr.ht/~knazarov/lisp.experimental).
|
Loading…
Reference in a new issue