By default, when you enter in expressions at Guile’s REPL, they are first compiled to VM object code, then that VM object code is executed to produce a value. If the expression evaluates to a procedure, the result of this process is a compiled procedure.
A compiled procedure is a compound object, consisting of its bytecode,
a reference to any captured lexical variables, an object array, and
some metadata such as the procedure’s arity, name, and documentation.
You can pick apart these pieces with the accessors in
program). See Compiled Procedures, for a full API reference.
The object array of a compiled procedure, also known as the
object table, holds all Scheme objects whose values are known
not to change across invocations of the procedure: constant strings,
symbols, etc. The object table of a program is initialized right
before a program is loaded with
See Loading Instructions, for more information.
Variable objects are one such type of constant object: when a global
binding is defined, a variable object is associated to it and that
object will remain constant over time, even if the value bound to it
changes. Therefore, toplevel bindings only need to be looked up once.
Thereafter, references to the corresponding toplevel variables from
within the program are then performed via the
instruction, which uses the object vector, and are almost as fast as
local variable references.
We can see how these concepts tie together by disassembling the
foo function we defined earlier to see what is going on:
scheme@(guile-user)> (define (foo a) (lambda (b) (list foo a b))) scheme@(guile-user)> ,x foo 0 (assert-nargs-ee/locals 1) 2 (object-ref 1) ;; #<procedure 8ebec20 at <current input>:0:17 (b)> 4 (local-ref 0) ;; `a' 6 (make-closure 0 1) 9 (return) ---------------------------------------- Disassembly of #<procedure 8ebec20 at <current input>:0:17 (b)>: 0 (assert-nargs-ee/locals 1) 2 (toplevel-ref 1) ;; `foo' 4 (free-ref 0) ;; (closure variable) 6 (local-ref 0) ;; `b' 8 (list 0 3) ;; 3 elements at (unknown file):0:29 11 (return)
First there’s some prelude, where
foo checks that it was called with only
1 argument. Then at
ip 2, we load up the compiled lambda.
loads up ‘a’, so that it can be captured into a closure by at
6—binding code (from the compiled lambda) with data (the free-variable
vector). Finally we return the closure.
The second stanza disassembles the compiled lambda. After the prelude, we note that toplevel variables are resolved relative to the module that was current when the procedure was created. This lookup occurs lazily, at the first time the variable is actually referenced, and the location of the lookup is cached so that future references are very cheap. See Top-Level Environment Instructions, for more details.
Then we see a reference to a free variable, corresponding to
disassembler doesn’t have enough information to give a name to that variable, so
it just marks it as being a “closure variable”. Finally we see the reference
b, then the
list opcode, an inline implementation of the
list scheme routine.