(defun baz (x) (let ((y 600)) (setq y (* y 2)) (+ x y))) baz (baz 3) 1203 (disassemble-internal 'baz 2 nil) doc: ... TOS S[1] S[2] args: (arg1) ; [аrg1] 0 constant 600 ; [600 arg1] 1 constant 1200 ; [1200 600 arg1] 2 stack-set 1 ; [1200 arg1] 4 stack-ref 1 ; [arg1 1200 arg1] 5 stack-ref 1 ; [1200 arg1 1200 arg1] 6 plus ; [1200+arg1 1200 arg1] 7 return nil ;;; Working through simple bytecode examples (defun f1 (x) (1+ x)) f1 (disassemble 'f1) nil ;;; Disassemble drops its output into a separate buffer. While it's typically ;;; convenient, I actually want it pasted into this buffer. What do I do? ;;; ;;; M-x find-function or (find-function 'disassemble) gets me the source code of disassemble. ;; At the end of the source I see the following: (save-excursion (if (or interactive-p (null buffer)) (with-output-to-temp-buffer "*Disassemble*" (set-buffer "*Disassemble*") (disassemble-internal object indent (not interactive-p))) (set-buffer buffer) (disassemble-internal object indent nil))) nil) ;;; So it seems that using disassemble-internal will do the trick! (disassemble-internal 'f1 2 nil) doc: ... args: (arg1) 0 dup 1 add1 2 return nil ;;; In fact, I think I want to type even less. So I will create a macro. ;; (see OnLisp Chapter 7 about macros. They are really cut-and-paste on Lisp lists, no more). (defmacro dis (f) `(disassemble-internal ,f 2 nil)) dis ;;; OK, now we are cooking :) ;;; There is one key observation for reading disassembly: ;;; -- function arguments are pushed onto the stack ;;; -- the initial copies of the arguments are preserved at the bottom of the stack, as "master copies" ;;; -- whenever the argument's value is needed for an operation, it is typically duplicated ;;; or copied on top of the stack by "dup" or "stack-set" and then operated on. The original ;;; copy of the argument is kept unchanged. (dis 'f1) doc: ... args: (arg1) ; <<-- on top of the stack 0 dup ; <<-- after this we have two copies of arg on the stack 1 add1 ; <<-- and consume the one at the top, replacing it with (arg+1) at the top 2 return ; <<-- .. which is the intended return value nil (setq y 100) 100 ;;; Example from class: (defun baz (x) (let ((y 600)) (setq y (* y 2)) (+ x y))) baz (dis 'baz) doc: ... args: (arg1) ; <<-- on top of the stack (TOS); the stack is [arg] 0 constant 600 ; <<-- now top of the stack; the stack is [600 arg] (TOS on the left) 1 constant 1200 ; <<-- the stack after this is [1200 600 arg] ;; note that "(y 600)" gets its notional storage slot thanks to "let" ;; that slot is also going to be kept as a "master copy" of y 2 stack-set 1 ; <<-- places 1200 in the first slot of the stack, i.e., "y"'s slot, ; overwriting 600 there, and pops TOS. The stack is then [1200 arg] 4 stack-ref 1 ; <<-- pushes arg on top of the stack: [arg 1200 arg] 5 stack-ref 1 ; <<-- pushed 1200 on top of the stack: [1200 arg 1200 arg] 6 plus ; <<-- consumes the top two slots, pushes the sum: [(1200+arg) 1200 arg] 7 return nil ;; Note that although the stack shifted up and down, the lower slots that hold the arguments and ;; the let-created variables are kept consistently addressed despite their slot numbers changing. ;; (setq bar (let ((y 500)) (lambda (x) (+ x y)))) (closure ((y . 500) t) (x) (+ x y)) ; <-- the value of let is its last expression, i.e., (lambda ..) ;; (lambda ..) gets set as the symbol-value of bar bar (closure ((y . 500) t) (x) (+ x y)) (symbol-value 'bar) (closure ((y . 500) t) (x) (+ x y)) ;; For this reason, calling bar as function will fail: (bar 1) ; fails ;; .. and it needs to be called via funcall or apply (funcall bar 1 ) 501 (apply bar '(1)) 501 (dis bar) ;; note that (dis 'bar) will not work, because disassemble expects function value, not symbol-value to be set. So we need to get the actual closure by evaluating bar. doc: ... args: (arg1) 0 dup ; <<-- stack after this: [arg arg] 1 constant 500 ; <<-- push 500. Stack after: [500 arg arg] 2 plus ; <<-- [(500+arg) arg] 3 return nil ;; This is what the bytecode object looks like. Read about this in ;; https://nullprogram.com/blog/2014/01/04/ . Note that the constant 500 is a part of the ;; "constants vector" [500], and the stack will have a pointer to it pushed and acted upon. Recall ;; that numbers in Lisp are "bignums", not fixed-sized integers as in C. ;; Also that "3" is the maximum size of the stack. Indeed, see line 1. (byte-compile bar) #[257 "\211\300\\\207" [500] 3 " (fn X)"] ;; Let's try some cons cell/list arguments (defun foo (x) (+ (car x) y)) foo (defvar y 300) ; without this, calling foo will fail, as it needs as global "y" to exist y (foo '(1 2 3)) 301 (dis 'foo) doc: ... args: (arg1) ; <<-- stack: [arg] 0 dup ; <<-- [arg arg] 1 car ; <<-- replace arg at TOS with its car. Stack: [1 arg] 2 varref y ; <<-- lookup y _by name_ in obarray and push its value. Stack: [300 1 arg] 3 plus ; <<-- [301 arg] 4 return nil