Next: , Previous: , Up: compiler topics   [Index]


17.3.2 Handling of lexical local bindings

Lexical local bindings are defined in the core language by the forms: let, letrec, letrec*, lambda, case-lambda, annotated-case-lambda. These forms have syntax:

(let     ((?lex ?rhs) ...) ?body)
(letrec  ((?lex ?rhs) ...) ?body)
(letrec* ((?lex ?rhs) ...) ?body)
(case-lambda (?formals ?body) ...)
(annotated-case-lambda ?annotation (?formals ?body) ...)
(lambda ?formals ?body)

in which: ?lex is a lexical gensym that uniquely identifies the binding in the core language form; ?rhs is the initialisation expression; ?formals is a proper or improper list of lex gensyms uniquely identifying arguments to function.

References to local bindings are represented by standalone ?lex gensyms; assignments to local bindings are represented by set! forms:

(set! ?lex ?rhs)

where the right–hand side expression ?rhs will, at run–time, evaluate to the new binding’s value.

Recordised language representation

The let, letrec and letrec* forms are recordised into nested hierarchies of bind, recbind and rec*bind structs:

(bind     ((?prel ?rhs) ...) ?body)
(recbind  ((?prel ?rhs) ...) ?body)
(rec*bind ((?prel ?rhs) ...) ?body)

in which ?prel is a prelex struct holding the ?lex gensym.

The lambda, case-lambda and annotated-case-lambda forms are recordised as clambda structs:

(clambda (?prel-formals ?body) ...)

in which ?prel-formals is a a proper or improper list of prel structs representing arguments to each clambda clause.

References to local bindings are represented by standalone prelex structs; assignments to local bindings are represented by assign structs:

(assign ?prel ?rhs)

Implementation of references and assignments

We distinguish between bindings that are only referenced (unassigned, read–only) and bindings that are also assigned (assigned, read–write). Example of code in which the binding X is only referenced:

(let ((X 123)) (display X))

example of code in which the binding X is assigned and referenced:

(let ((X 123)) (set! X 456) (display X))

The implementation technique for continuations used by Vicare mandates that: once an immediate Scheme object or reference to non–immediate Scheme object is stored in a machine word on the Scheme stack, such machine word must not be mutated; this allows the run–time system to copy stack frames at will. As a consequence the following implementation techniques for local bindings are adopted:

References to unassigned bindings must be implemented as Scheme stack operations; references and assignments to assigned bindings must be substituted with appropriate vector operations.

Recordisation and transformation of unassigned bindings

After the letrec optimiser has processed recbind and rec*bind forms, unassigned local bindings always end up defined as:

(bind ((?prel ?rhs)) ?body)

and references are represented by standalone prelex structs. Whenever a ?rhs expression is recognised to be of type clambda, the corresponding bind is transformed into:

(fix ((?prel ?rhs)) ?body)

No further transformations are needed for recordised code.

Recordisation and transformation of assigned bindings

After the letrec optimiser has processed recbind and rec*bind forms, assigned local bindings always end up defined as:

(bind ((?prel ?rhs)) ?body)

references are represented by standalone prelex structs and assignments are represented by assign structs.

NOTE No matter if a ?rhs expression is of type clambda: its associated bind struct is not transformed into a fix struct; only unassigned clambda bindings can be defined by a fix struct.

Definitions, references and assignments are transformed by introducing the appropriate vector operations; such operation are equivalent to the following:

(let ((x 123))
  (set! x 456)
  x)

is transformed into:

(let ((t 123))
  (let ((x (vector t)))
    ($vector-set! x 0 456)
    ($vector-ref  x 0)))

where the temporary binding t is generated by the compiler; the code always only access the first and single slot of the vector. Using recordised code:

(bind ((x_0 123))
  (seq
    (assign x_0 456)
    x))

is transformed into:

(bind ((x_0 123))
  (bind ((x_1 (funcall (primref vector) x_0)))
    (seq
      (funcall (primref $vector-set!)
               x_1
               (constant 0)
               (constant 456))
      (funcall (primref $vector-ref)
               x_1
               (constant 0)))))

Formal bindings in clambda structs are also processed this way; for example:

(lambda (a)
  (display a)
  (set! a 1)
  a)

is transformed into:

(lambda (a_0)
  (bind ((a_1 (funcall (primref vector) a_0)))
    (seq
      (funcall (primref display)
               (funcall (primref $vector-ref)
                        a_1 (constant 0)))
      (funcall (primref $vector-set!)
               a_1 (constant 0) (constant 1))
      (funcall (primref $vector-ref)
               a_1 (constant 0)))))

NOTE Assigned local bindings whose RHS expression is a clambda struct are also transformed by introducing a vector. After this transformation: there are no more bind struct whose RHS is a clambda struct.


Next: , Previous: , Up: compiler topics   [Index]