Warning: This is the manual of the legacy Guile 2.2 series. You may want to read the manual of the current stable series instead.
Next: Object File Format, Previous: Variables and the VM, Up: A Virtual Machine for Guile [Contents][Index]
By default, when you enter in expressions at Guile’s REPL, they are first compiled to bytecode. Then that bytecode 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 and
a reference to any captured lexical variables. In addition, when a
procedure is compiled, it has associated metadata written to side
tables, for instance a line number mapping, or its docstring. You can
pick apart these pieces with the accessors in (system vm
program)
. See Compiled Procedures, for a full API reference.
A procedure may reference data that was statically allocated when the procedure was compiled. For example, a pair of immediate objects (see Immediate objects) can be allocated directly in the memory segment that contains the compiled bytecode, and accessed directly by the bytecode.
Another use for statically allocated data is to serve as a cache for a
bytecode. Top-level variable lookups are handled in this way. If the
toplevel-box
instruction finds that it does not have a cached
variable for a top-level reference, it accesses other static data to
resolve the reference, and fills in the cache slot. Thereafter all
access to the variable goes through the cache cell. The variable’s
value may change in the future, but the variable itself will not.
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 Disassembly of #<procedure foo (a)> at #xea4ce4: 0 (assert-nargs-ee/locals 2 0) ;; 2 slots (1 arg) at (unknown file):1:0 1 (make-closure 1 7 1) ;; anonymous procedure at #xea4d04 (1 free var) 4 (free-set! 1 0 0) ;; free var 0 6 (mov 0 1) 7 (return-values 2) ;; 1 value ---------------------------------------- Disassembly of anonymous procedure at #xea4d04: 0 (assert-nargs-ee/locals 2 2) ;; 4 slots (1 arg) at (unknown file):1:16 1 (toplevel-box 1 74 58 68 #t) ;; `foo' 6 (box-ref 1 1) 7 (make-short-immediate 0 772) ;; () at (unknown file):1:28 8 (cons 2 2 0) 9 (free-ref 3 3 0) ;; free var 0 11 (cons 3 3 2) 12 (cons 2 1 3) 13 (return-values 2) ;; 1 value
First there’s some prelude, where foo
checks that it was called
with only 1 argument. Then at ip
1, we allocate a new closure
and store it in slot 1, relative to the sp
.
At run-time, local variables in Guile are usually addressed relative to
the stack pointer, which leads to a pleasantly efficient
sp[n]
access. However it can make the disassembly hard to
read, because the sp
can change during the function, and because
incoming arguments are relative to the fp
, not the sp
.
To know what fp
-relative slot corresponds to an
sp
-relative reference, scan up in the disassembly until you get
to a “n slots” annotation; in our case, 2, indicating that the
frame has space for 2 slots. Thus a zero-indexed sp
-relative
slot of 1 corresponds to the fp
-relative slot of 0, which
initially held the value of the closure being called. This means that
Guile doesn’t need the value of the closure to compute its result, and
so slot 0 was free for re-use, in this case for the result of making a
new closure.
A closure is code with data. The 6
in the (make-closure 1
6 1)
is a relative offset from the instruction pointer of the code for
the closure, and the final 1
indicates that the closure has space
for 1 free variable. Ip
4 initializes free variable 0 in the new
closure with the value from sp
-relative slot 0, which corresponds
to fp
-relative slot 1, the first argument of foo
:
a
. Finally we return the closure.
The second stanza disassembles the code for the closure. After the
prelude, we load the variable for the toplevel variable foo
into
slot 1. This lookup occurs lazily, 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. The box-ref
dereferences the
variable cell, replacing the contents of slot 1.
What follows is a sequence of conses to build up the result list.
Ip
7 makes the tail of the list. Ip
8 conses on the value
in slot 2, corresponding to the first argument to the closure: b
.
Ip
9 loads free variable 0 of slot 3 – the procedure being
called, in fp
-relative slot 0 – into slot 3, then ip
11
conses it onto the list. Finally we cons the value in slot 1,
containing the foo
toplevel, onto the front of the list, and we
return it.
Next: Object File Format, Previous: Variables and the VM, Up: A Virtual Machine for Guile [Contents][Index]