Next: , Previous: , Up: A Virtual Machine for Guile   [Contents][Index]

9.3.5 Compiled Procedures are VM Programs

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 #x203be34:

   0    (assert-nargs-ee/locals 2 1)    ;; 1 arg, 1 local     at (unknown file):1:0
   1    (make-closure 2 6 1)            ;; anonymous procedure at #x203be50 (1 free var)
   4    (free-set! 2 1 0)               ;; free var 0
   6    (return 2)

Disassembly of anonymous procedure at #x203be50:

   0    (assert-nargs-ee/locals 2 3)    ;; 1 arg, 3 locals    at (unknown file):1:0
   1    (toplevel-box 2 73 57 71 #t)    ;; `foo'
   6    (box-ref 2 2)
   7    (make-short-immediate 3 772)    ;; ()
   8    (cons 3 1 3)
   9    (free-ref 4 0 0)                ;; free var 0
  11    (cons 3 4 3)
  12    (cons 2 2 3)
  13    (return 2)

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 2. The ‘6’ in the (make-closure 2 6 1) is a relative offset from the instruction pointer of the code for the closure.

A closure is code with data. We already have the code part initialized; what remains is to set the data. Ip 4 initializes free variable 0 in the new closure with the value from local variable 1, which corresponds to 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 local variable 2. 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 local 2.

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 local 1, corresponding to the first argument to the closure: ‘b’. Ip 9 loads free variable 0 of local 0 – the procedure being called – into slot 4, then ip 11 conses it onto the list. Finally we cons local 2, containing the foo toplevel, onto the front of the list, and we return it.

Next: , Previous: , Up: A Virtual Machine for Guile   [Contents][Index]