;; This session shows how a simple function byte-compiles and executes ;; under _dynamic_ bindings (default for Emacs' Elisp). For lexical ;; bindings, see elisp-bytecode-lexical-binding-log.txt ;; See elisp-bytecode-lexical-binding-log.txt for links & docs about bytecode. (setq lexical-binding nil) nil (defun len (lst) (cond ((null lst) 0) (t (+ 1 (len (cdr lst)))))) len (len '()) 0 (len '(1 2 3)) 3 (byte-compile #'len) #[(lst) "\204\301\207\302A!T\207" [lst 0 len] 2] ;; Note the explicit list of arguments (gone in the lexical bindings!), ;; and also the name(s) of arguments in the constants vector. ;; Under dynamic bindings, all variables are looked up by name ;; (e.g., via the varref bytecode below, which pushes the looked up ;; value onto the stack). (disassemble (byte-compile #'len)) nil ; pasted from other buffer byte code: args: (lst) 0 varref lst 1 goto-if-not-nil 1 4 constant 0 5 return 6:1 constant len 7 varref lst 8 cdr 9 call 1 10 add1 11 return (type-of (symbol-function 'len)) compiled-function ; From http://nullprogram.com/blog/2014/01/04/ on dynamic scope calling convention: ; [..] dynamically scoped functions keep track of all its argument ; names. Before executing a function the interpreter examines the ; lambda list and binds (varbind) every variable globally to an ; argument. ; ; If the caller was byte-compiled, each argument started on the stack, ; was popped and bound to a variable, and, to be accessed by the ; function, will be pushed back right onto the stack (varref). There’s ; a lot of argument indirection for each function call. ; Let's say l is '(a b), so that cdr_of_l is (b) and cddr_of_l is nil. ; Let's step through the function call (len l), bytecode by bytecode, showing the data stack ; *after* each bytecode's execution. on entry: a new binding for lst is created. It gets pushed on top of any existing bindings (like a stack of bindings hanging off of the "lst" symbol entry in the global symbol table). It will remain in effect until return pops it off. Until popped off, it's what looking up "lst" will yield, shadowing any previous bindings (if any). the data stack is empty: [) 0 (varref lst): [l) ; the newly created binding is looked up and pushed onto the stack 1: [) ; goto-if-not-nil consumes lst, finds is non-nil, and jumps to "6" 6: [len) ; constant len (its symbol) is pushed onto the stack 7: [len l) ; lst is looked up again and pushed onto the stack 8: [len cdr_of_l) ; cdr pops lst, and pushes its result, cdr_of_lst 9: [) ; "call 1" finds len in stack's slot 1 off the top; len is bound ; to a compiled function, so it can proceed. call gets the function's ; list of arguments---that's (lst) in the compiled object #[(lst) ...]--- ; and pops as many values off the stack, creating new bindings for them, ; and overshadowing previous ones. ; Then we push "10" onto the separate control stack; it's now ["10">. And jump to "0" 0: [cdr_of_l) ; the new binding of lst. It's previous binding, l, is shadowed. lst's stack of bindings is [l cdr_of_l} 1: [) ; still not nill, goto "6" 6: [len) 7: [len cdr_of_l) ; another lookup of "lst", the new binding is pushed onto the stack 8: [len nil) ; cdr_of_l is popped by cdr bytecode, and it pushes nil 9: [) ; call pops the arguments, and creates another new binding for "lst". lst's bindings are now ; [l cdr_of_l nil}, control stack is new ["10" "10"> 0: [nil) ; the new binding of "lst" is looked up & pushed 1: [) ; goto-if-not-nil now falls through to "4" 4: [0) 5 (return): [0) ; this pops "10" off control stack, it's now ["10"> ; this also pops the top binding of "lst", it's back to [l cdr_of_l} ; the return value is whatever's on top of the stack, i.e., 0 10 (add1): [1) ; pops off 0, replaces with 0+1==1 11 (return): [1) ; pops "10" off the control stack, it's now empty [> ; pops the binding of lst, it's now back to [l} 10: [2) ; add1 11: [2) ; return pops off the last binding of lst; it's now back to whatever it was before (len l), if anything. ; 2 is the return value of the overall function call Note that the arguments are passed by creating new bindings for them by name, not stored on the stack; the binding operation pops them off. They must be looked up by name and pushed back onto the stack in order to get used.