Disassembling functions
In the latest patch to Valeri, I've added support for disassembling arbitrary functions. It can be done both in the REPL and in file execution mode.
When you disassemble a function, you get back the human-readable virtual machine instructions that can be used to see if code generation has been done correctly. This of course can't compare with a fully-featured debugger, but is enough to iron out the basics.
Anyway, here's an example:
;; Function that calculates n!
(fn fact (n)
(if (<= n 0)
1
(* n (fact (- n 1)))))
;; Output the VM bytecode of the function
(println (disassemble fact))
When executed, this program outputs:
constants:
c0: true
c1: false
c2: 0
c3: 1
c4: nil
code:
mov r1 c0
mov r2 r0
mov r3 c2
less-equal r2 r3 0
mov r1 c1
equal r1 c0 0
jump 3
mov r2 c3
jump 8
mov r2 r0
mov r3 c4
mov r4 r0
mov r5 c3
sub r4 r4 r5
selfcall r3 r5
mul r2 r2 r3
mov r1 r2
mov r0 r1
ret r0
As you can see, there are constants
and code
sections. This is because every function in Valeri is self-contained and the code is not "glued" together with other functions.
Most of the opcodes have their first parameter as the accumulator (destination) and the rest as parameters. For example, mov r1 c0
moves the value of constant c0
into register r1
. And sub r4 r4 r5
subtracts value of r5
from r4
and puts the result back to r4
.
If you carefully study the opcodes, you'll notice that there are many redundant operations that can be eliminated. This is true, and mostly due to the code generator being naive. In the future when I'll get to optimizations it should be possible to reduce the size of the bytecode.