X-Date: 2024-10-06T23:01:40Z X-Note-Id: 710a16d0-f850-4d9b-b054-0e6b1c94b1c1 Subject: Disassembling functions X-Slug: disassembling_functions In the latest patch to [Valeri](https://git.knazarov.com/knazarov/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.