Compiling Expressions

SICP > Computing with Register Machines > Compilation > Compiling Expressions
Previous: Structure of the Compiler Next: Compiling Combinations

    In this section and the next we implement the code generators to which the compile procedure dispatches.

    Compiling linkage code

    In general, the output of each code generator will end with instructions--generated by the procedure compile-linkage--that implement the required linkage. If the linkage is return then we must generate the instruction (goto (reg continue)). This needs the continue register and does not modify any registers. If the linkage is next, then we needn't include any additional instructions. Otherwise, the linkage is a label, and we generate a goto to that label, an instruction that does not need or modify any registers.[*]

    (define (compile-linkage linkage)
      (cond ((eq? linkage 'return)
             (make-instruction-sequence '(continue) '()
              '((goto (reg continue)))))
            ((eq? linkage 'next)
             (empty-instruction-sequence))
            (else
             (make-instruction-sequence '() '()
              `((goto (label ,linkage)))))))
    
    The linkage code is appended to an instruction sequence by preserving the continue register, since a return linkage will require the continue register: If the given instruction sequence modifies continue and the linkage code needs it, continue will be saved and restored.

    (define (end-with-linkage linkage instruction-sequence)
      (preserving '(continue)
       instruction-sequence
       (compile-linkage linkage)))
    

    Compiling simple expressions

    The code generators for self-evaluating expressions, quotations, and variables construct instruction sequences that assign the required value to the target register and then proceed as specified by the linkage descriptor.

    (define (compile-self-evaluating exp target linkage)
      (end-with-linkage linkage
       (make-instruction-sequence '() (list target)
        `((assign ,target (const ,exp))))))
    
    (define (compile-quoted exp target linkage)
      (end-with-linkage linkage
       (make-instruction-sequence '() (list target)
        `((assign ,target (const ,(text-of-quotation exp)))))))
    
    (define (compile-variable exp target linkage)
      (end-with-linkage linkage
       (make-instruction-sequence '(env) (list target)
        `((assign ,target
                  (op lookup-variable-value)
                  (const ,exp)
                  (reg env))))))
    
    All these assignment instructions modify the target register, and the one that looks up a variable needs the env register.

    Assignments and definitions are handled much as they are in the interpreter. We recursively generate code that computes the value to be assigned to the variable, and append to it a two-instruction sequence that actually sets or defines the variable and assigns the value of the whole expression (the symbol ok) to the target register. The recursive compilation has target val and linkage next so that the code will put its result into val and continue with the code that is appended after it. The appending is done preserving env, since the environment is needed for setting or defining the variable and the code for the variable value could be the compilation of a complex expression that might modify the registers in arbitrary ways.

    (define (compile-assignment exp target linkage)
      (let ((var (assignment-variable exp))
            (get-value-code
             (compile (assignment-value exp) 'val 'next)))
        (end-with-linkage linkage
         (preserving '(env)
          get-value-code
          (make-instruction-sequence '(env val) (list target)
           `((perform (op set-variable-value!)
                      (const ,var)
                      (reg val)
                      (reg env))
             (assign ,target (const ok))))))))
    
    (define (compile-definition exp target linkage)
      (let ((var (definition-variable exp))
            (get-value-code
             (compile (definition-value exp) 'val 'next)))
        (end-with-linkage linkage
         (preserving '(env)
          get-value-code
          (make-instruction-sequence '(env val) (list target)
           `((perform (op define-variable!)
                      (const ,var)
                      (reg val)
                      (reg env))
             (assign ,target (const ok))))))))
    
    The appended two-instruction sequence requires env and val and modifies the target. Note that although we preserve env for this sequence, we do not preserve val, because the get-value-code is designed to explicitly place its result in val for use by this sequence. (In fact, if we did preserve val, we would have a bug, because this would cause the previous contents of val to be restored right after the get-value-code is run.)

    Compiling conditional expressions

    The code for an if expression compiled with a given target and linkage has the form

     compilation of predicate, target val, linkage next
     (test (op false?) (reg val))
     (branch (label false-branch))
    true-branch
     compilation of consequent with given target and given linkage or after-if
    false-branch
     compilation of alternative with given target and linkage
    after-if
    

    To generate this code, we compile the predicate, consequent, and alternative, and combine the resulting code with instructions to test the predicate result and with newly generated labels to mark the true and false branches and the end of the conditional. [*] In this arrangement of code, we must branch around the true branch if the test is false. The only slight complication is in how the linkage for the true branch should be handled. If the linkage for the conditional is return or a label, then the true and false branches will both use this same linkage. If the linkage is next, the true branch ends with a jump around the code for the false branch to the label at the end of the conditional.

    (define (compile-if exp target linkage)
      (let ((t-branch (make-label 'true-branch))
            (f-branch (make-label 'false-branch))                    
            (after-if (make-label 'after-if)))
        (let ((consequent-linkage
               (if (eq? linkage 'next) after-if linkage)))
          (let ((p-code (compile (if-predicate exp) 'val 'next))
                (c-code
                 (compile
                  (if-consequent exp) target consequent-linkage))
                (a-code
                 (compile (if-alternative exp) target linkage)))
            (preserving '(env continue)
             p-code
             (append-instruction-sequences
              (make-instruction-sequence '(val) '()
               `((test (op false?) (reg val))
                 (branch (label ,f-branch))))
              (parallel-instruction-sequences
               (append-instruction-sequences t-branch c-code)
               (append-instruction-sequences f-branch a-code))
              after-if))))))
    
    Env is preserved around the predicate code because it could be needed by the true and false branches, and continue is preserved because it could be needed by the linkage code in those branches. The code for the true and false branches (which are not executed sequentially) is appended using a special combiner parallel-instruction-sequences described in section [*].

    Note that cond is a derived expression, so all that the compiler needs to do handle it is to apply the cond->if transformer (from section [*]) and compile the resulting if expression.

    Compiling sequences

    The compilation of sequences (from procedure bodies or explicit begin expressions) parallels their evaluation. Each expression of the sequence is compiled--the last expression with the linkage specified for the sequence, and the other expressions with linkage next (to execute the rest of the sequence). The instruction sequences for the individual expressions are appended to form a single instruction sequence, such that env (needed for the rest of the sequence) and continue (possibly needed for the linkage at the end of the sequence) are preserved.

    (define (compile-sequence seq target linkage)
      (if (last-exp? seq)
          (compile (first-exp seq) target linkage)
          (preserving '(env continue)
           (compile (first-exp seq) target 'next)
           (compile-sequence (rest-exps seq) target linkage))))
    

    Compiling lambda expressions

    Lambda expressions construct procedures. The object code for a lambda expression must have the form

    construct procedure object and assign it to target register
    linkage
    
    When we compile the lambda expression, we also generate the code for the procedure body. Although the body won't be executed at the time of procedure construction, it is convenient to insert it into the object code right after the code for the lambda. If the linkage for the lambda expression is a label or return, this is fine. But if the linkage is next, we will need to skip around the code for the procedure body by using a linkage that jumps to a label that is inserted after the body. The object code thus has the form

     construct procedure object and assign it to target register
     code for given linkageor (goto (label after-lambda))
     compilation of procedure body
    after-lambda
    

    Compile-lambda generates the code for constructing the procedure object followed by the code for the procedure body. The procedure object will be constructed at run time by combining the current environment (the environment at the point of definition) with the entry point to the compiled procedure body (a newly generated label). [*]

    (define (compile-lambda exp target linkage)
      (let ((proc-entry (make-label 'entry))
            (after-lambda (make-label 'after-lambda)))
        (let ((lambda-linkage
               (if (eq? linkage 'next) after-lambda linkage)))
          (append-instruction-sequences
           (tack-on-instruction-sequence
            (end-with-linkage lambda-linkage
             (make-instruction-sequence '(env) (list target)
              `((assign ,target
                        (op make-compiled-procedure)
                        (label ,proc-entry)
                        (reg env)))))
            (compile-lambda-body exp proc-entry))
           after-lambda))))
    
    Compile-lambda uses the special combiner tack-on-instructionsequence (section [*]) rather than append-instruction-sequences to append the procedure body to the lambda expression code, because the body is not part of the sequence of instructions that will be executed when the combined sequence is entered; rather, it is in the sequence only because that was a convenient place to put it.

    Compile-lambda-body constructs the code for the body of the procedure. This code begins with a label for the entry point. Next come instructions that will cause the run-time evaluation environment to switch to the correct environment for evaluating the procedure body--namely, the definition environment of the procedure, extended to include the bindings of the formal parameters to the arguments with which the procedure is called. After this comes the code for the sequence of expressions that makes up the procedure body. The sequence is compiled with linkage return and target val so that it will end by returning from the procedure with the procedure result in val.

    (define (compile-lambda-body exp proc-entry)
      (let ((formals (lambda-parameters exp)))
        (append-instruction-sequences
         (make-instruction-sequence '(env proc argl) '(env)
          `(,proc-entry
            (assign env (op compiled-procedure-env) (reg proc))
            (assign env
                    (op extend-environment)
                    (const ,formals)
                    (reg argl)
                    (reg env))))
         (compile-sequence (lambda-body exp) 'val 'return))))
    

    Previous: Structure of the Compiler Next: Compiling Combinations

      webmaster@arsdigita.org