7.4

10 Refactoring the Compiler

We’ve now written half-a-dozen compilers, each of which has centered around a structurally recursive function over the syntax of Exprs.

In each case of this function, we emit the appropriate code for that kind of expression.

This works fine while our language is small and the concepts are simple enough that just a few instructions need to be produced.

But this leads to a monolithic and unwieldy function as the language and the complexity of its constructs grow.

Moving forward we will refactor this main compiler function to call out to a separate helper function for each kind of expression.

For example, here is the Fraud compiler written in the new style:

fraud/compile-refactor.rkt

  #lang racket
  (provide (all-defined-out))
   
  ;; type CEnv = [Listof Variable]
   
  (define imm-shift        1)
  (define imm-type-mask    (sub1 (arithmetic-shift 1 imm-shift)))
  (define imm-type-int     #b0)
  (define imm-type-true    #b11)
  (define imm-type-false   #b01)
   
  ;; Expr -> Asm
  (define (compile e)
    `(entry
      ,@(compile-e e '())
      ret
      err
      (push rbp)
      (call error)
      ret))
   
  ;; Expr CEnv -> Asm
  (define (compile-e e c)
    (match e
      [(? integer? i)        (compile-integer i)]
      [(? boolean? b)        (compile-boolean b)]
      [(? symbol? x)         (compile-variable x c)]
      [`(add1 ,e0)           (compile-add1 e0 c)]
      [`(sub1 ,e0)           (compile-sub1 e0 c)]
      [`(zero? ,e0)          (compile-zero? e0 c)]
      [`(if ,e0 ,e1 ,e2)     (compile-if e0 e1 e2 c)]
      [`(let ((,x ,e0)) ,e1) (compile-let x e0 e1 c)]))
   
  ;; Integer -> Asm
  (define (compile-integer i)
    `((mov rax ,(arithmetic-shift i imm-shift))))
   
  ;; Boolean -> Asm
  (define (compile-boolean b)
    `((mov rax ,(if b imm-type-true imm-type-false))))
   
  ;; Expr CEnv -> Asm
  (define (compile-add1 e0 c)
    (let ((c0 (compile-e e0 c)))
      `(,@c0
        ,@assert-integer
        (add rax ,(arithmetic-shift 1 imm-shift)))))
   
  ;; Expr CEnv -> Asm
  (define (compile-sub1 e0 c)
    (let ((c0 (compile-e e0 c)))
      `(,@c0
        ,@assert-integer
        (sub rax ,(arithmetic-shift 1 imm-shift)))))
   
  ;; Expr CEnv -> Asm
  (define (compile-zero? e0 c)
    (let ((c0 (compile-e e0 c))
          (l0 (gensym))
          (l1 (gensym)))
      `(,@c0
        ,@assert-integer
        (cmp rax 0)
        (mov rax ,imm-type-false)
        (jne ,l0)
        (mov rax ,imm-type-true)
        ,l0)))
   
  ;; Expr Expr Expr CEnv -> Asm
  (define (compile-if e0 e1 e2 c)
    (let ((c0 (compile-e e0 c))
          (c1 (compile-e e1 c))
          (c2 (compile-e e2 c))
          (l0 (gensym))
          (l1 (gensym)))
      `(,@c0
        (cmp rax ,imm-type-false)
        (je ,l0)       ; jump to c2 if #f
        ,@c1
        (jmp ,l1)      ; jump past c2
        ,l0
        ,@c2
        ,l1)))
   
  ;; Variable CEnv -> Asm
  (define (compile-variable x c)
    (let ((i (lookup x c)))
      `((mov rax (offset rsp ,(- (add1 i)))))))
   
  ;; Variable Expr Expr CEnv -> Asm
  (define (compile-let x e0 e1 c)
    (let ((c0 (compile-e e0 c))
          (c1 (compile-e e1 (cons x c))))
      `(,@c0
        (mov (offset rsp ,(- (add1 (length c)))) rax)
        ,@c1)))
   
  ;; Variable CEnv -> Natural
  (define (lookup x cenv)
    (match cenv
      ['() (error "undefined variable:" x)]
      [(cons y cenv)
       (match (eq? x y)
         [#t (length cenv)]
         [#f (lookup x cenv)])]))
   
  (define assert-integer
    `((mov rbx rax)
      (and rbx ,imm-type-mask)
      (cmp rbx ,imm-type-int)
      (jne err)))