;; Assembler test cases / examples ;; -------------- ;; First example: a simple if-then statement. Notice the order in ;; which we retrieve operands from the stack. (setf *if-test* '(start ; Start label (pushi 1) ; Push two operands on the stack (pushi 2) (<) ; Pop 1, 2, push 2 < 1 (jumpz else) ; If 2 < 1 then go to else (pushi 3) ; Print 3 (print) (jump endif) else ; Else label (pushi 4) ; Print 4 (print) endif (exit 0))) ;; To run the program, we make a new virtual machine, install the assembled ;; code into the machine's code segment, and then run. (run-vm (make-vm-state :code (assemble *if-test*))) ;; -------------- ;; Second example: compute 3! recursively. ;; Notice that we reserve two empty spaces before the arguments ;; when the caller sets up the stack frame. This is so that ;; the call instruction has a place to save the frame pointer and ;; the program counter of the caller before setting the new frame ;; and program counter for the callee. (setf *factorial-test* '(start (pushi 0) ; Reserve space for saved fp, pc (pushi 0) (pushi 3) ; Push the argument 5 (call fact 1) ; Compute 5! (print) ; Print the result (exit 0) fact (lvar 0) ; Get the argument n-1 (jumpz basecase) ; If zero, go to base case (pushi 0) ; Reserve space for saved fp, pc (pushi 0) (pushi 1) ; Compute n-1 (lvar 0) (-) (call fact 1) ; Compute (n-1)! (lvar 0) ; Multiply by n (*) (return) basecase (pushi 1) ; 0! = 1 (return))) ;; This code segment might not be obvious at first. One thing we ;; can do to make it easier to read is to step through it one ;; instruction at a time. The virtual machine has a debugging hook, ;; which is called just before each instruction is executed; if we install ;; the vm-tracer program on that hook, we can step through the execution ;; one instruction at a time, and see the state of the stack at each ;; step. Just hit enter to advance to the next instruction (or q to quit). (run-vm (make-vm-state :code (assemble *factorial-test*) :debug-hook #'vm-tracer)) ;; -------------- ;; Third example: More debugging. ;; Tracing through the entire program execution can be laborious ;; for anything longer than a few instructions. A better alternative ;; is to selectively trace through the code. We can use the debugging ;; hooks for that, too. To make it a little easier, our machine has ;; a "debug" pseudo-instruction. It's a no-op, but it's a no-op that ;; can be used to send instructions to a debugging program. In the ;; example below, we use the debugging hook to print out text messages ;; at different places in the execution. (setf *debug-test* '(start (debug "~%Start of program") (pushi 1) (pushi 2) (<) (jumpz else) (debug "~%Took if") (pushi 3) (print) (jump endif) else (debug "~%Took else") (pushi 4) (print) endif (exit 0))) (run-vm (make-vm-state :code (assemble *debug-test*) :debug-hook #'(lambda (vm) (let ((i (vm-state-inst vm))) (when (eq (car i) 'debug) (apply #'format t (cdr i)))))))