; -*- Mode: Scheme; Syntax: Scheme; Package: Scheme; -*- ; Copyright (c) 1993, 1994 Richard Kelsey and Jonathan Rees. See file COPYING. ; This is file comp.scm. ;;;; The byte-code compiler ; This is a two-phase compiler. The first phase does macro expansion, ; variable resolution, and instruction selection, and computes the ; size of the code vector. The second phase (assembly) creates the ; code vector, "template" (literals vector), and debugging data ; structures. ; The output of the first phase (the COMPILE- and INSTRUCTION- ; routines) and the input to the second phase (SEGMENT->TEMPLATE) is a ; "segment." A segment is a pair (size . proc) where size is the size ; of the code segment in bytes, and proc is a procedure that during ; phase 2 will store the segment's bytes into the code vector. ; A "cenv" maps lexical variables to pairs. Level is ; the variable's distance from the root of the environment; 0 means ; outermost level, and higher numbers mean deeper lexical levels. The ; offset is the position of the variable within its level's ; environment vector. ; Optimizations are marked with +++, and may be flushed if desired. (define (compile-top exp cenv depth cont) (compile exp (initial-cenv cenv) depth cont)) ; Main dispatch for compiling a single expression. (define (compile exp cenv depth cont) (let ((node (type-check (classify exp cenv) cenv))) ((operator-table-ref compilators (node-operator-id node)) node cenv depth cont))) ; Specialists (define compilators (make-operator-table (lambda (node cenv depth cont) (generate-trap cont "not valid in expression context" (schemify node cenv))) (lambda (frob) ;for let-syntax, with-aliases, etc. (lambda (node cenv depth cont) (call-with-values (lambda () (frob node cenv)) (lambda (form cenv) (compile form cenv depth cont))))))) (define (define-compilator name type proc) (operator-define! compilators name type proc)) (define-compilator 'literal #f (lambda (node cenv depth cont) (let ((obj (node-form node))) (if (eq? obj #f) ;; +++ hack for bootstrap from Schemes that don't distinguish #f/() (deliver-value (instruction (enum op false)) cont) (compile-constant obj depth cont))))) (define-compilator 'quote syntax-type (lambda (node cenv depth cont) (let ((exp (node-form node))) cenv ;ignored (let ((obj (cadr exp))) (compile-constant obj depth cont))))) (define (compile-constant obj depth cont) (if (ignore-values-cont? cont) empty-segment ;+++ dead code (deliver-value (instruction-with-literal (enum op literal) obj) cont))) ; Variable reference (define-compilator 'name #f (lambda (node cenv depth cont) (let* ((binding (name-node-binding node cenv)) (name (node-form node))) (deliver-value (if (and (binding? binding) (pair? (binding-place binding))) (let* ((level+over (binding-place binding)) (back (- (environment-level cenv) (car level+over))) (over (cdr level+over))) (case back ((0) (instruction (enum op local0) over)) ;+++ ((1) (instruction (enum op local1) over)) ;+++ ((2) (instruction (enum op local2) over)) ;+++ (else (instruction (enum op local) back over)))) (instruction-with-location (enum op global) (get-location binding cenv name value-type))) cont)))) ; Assignment (define-compilator 'set! syntax-type (lambda (node cenv depth cont) (let* ((exp (node-form node)) (lhs-node (classify (cadr exp) cenv)) (name (node-form lhs-node)) ;; Error if not a name node... (binding (name-node-binding lhs-node cenv))) (sequentially (compile (caddr exp) cenv depth (named-cont name)) (deliver-value (if (and (binding? binding) (pair? (binding-place binding))) (let ((level+over (binding-place binding))) (instruction (enum op set-local!) (- (environment-level cenv) (car level+over)) (cdr level+over))) (instruction-with-location (enum op set-global!) (get-location binding cenv name usual-variable-type))) cont))))) ; Conditional (define-compilator 'if syntax-type (lambda (node cenv depth cont) (let ((exp (node-form node)) (alt-label (make-label)) (join-label (make-label))) (sequentially ;; Test (compile (cadr exp) cenv depth (fall-through-cont node 1)) (instruction-using-label (enum op jump-if-false) alt-label) ;; Consequent (compile (caddr exp) cenv depth cont) (if (fall-through-cont? cont) (instruction-using-label (enum op jump) join-label) empty-segment) ;; Alternate (attach-label alt-label (compile (cadddr exp) cenv depth cont)) (attach-label join-label empty-segment))))) (define-compilator 'begin syntax-type (lambda (node cenv depth cont) (let ((exp (node-form node))) (compile-begin (cdr exp) cenv depth cont)))) (define compile-begin (let ((operator/begin (get-operator 'begin))) (lambda (exp-list cenv depth cont) (if (null? exp-list) (generate-trap cont "null begin") (let ((dummy (make-node operator/begin ;For debugging database `(begin ,@exp-list)))) (let loop ((exp-list exp-list) (i 1)) (if (null? (cdr exp-list)) (compile (car exp-list) cenv depth cont) (careful-sequentially (compile (car exp-list) cenv depth (ignore-values-cont dummy i)) (loop (cdr exp-list) (+ i 1)) depth cont)))))))) ; Compile a call (define (compile-call node cenv depth cont) (if (node-ref node 'type-error) (compile-unknown-call node cenv depth cont) (let ((proc-node (classify (car (node-form node)) cenv))) (if (and (lambda-node? proc-node) (not (n-ary? (cadr (node-form proc-node))))) (compile-redex proc-node (cdr (node-form node)) cenv depth cont) (let ((new-node (maybe-transform-call proc-node node cenv))) (if (eq? new-node node) (compile-unknown-call node cenv depth cont) (compile new-node cenv depth cont))))))) (define-compilator 'call #f compile-call) ; A redex is a call of the form ((lambda (x1 ... xn) body ...) e1 ... en). (define lambda-node? (node-predicate 'lambda)) (define (compile-redex proc-node args cenv depth cont) (let* ((proc-exp (node-form proc-node)) (formals (cadr proc-exp)) (body (cddr proc-exp))) (if (null? formals) (compile-body body cenv depth cont) ;+++ (maybe-push-continuation (sequentially (push-all-with-names args formals cenv 0) (compile-lambda-code formals body cenv (cont-name cont))) depth cont)))) ; Compile a call to a computed procedure. (define (compile-unknown-call node cenv depth cont) (let ((exp (node-form node))) (let ((call (sequentially (push-arguments node cenv 0) (compile (car exp) cenv (length (cdr exp)) (fall-through-cont node 0)) (instruction (enum op call) (length (cdr exp)))))) (maybe-push-continuation call depth cont)))) (define (maybe-push-continuation code depth cont) (if (return-cont? cont) code (let ((label (make-label))) (sequentially (instruction-using-label (enum op make-cont) label depth) (note-source-code (cont-source-info cont) code) (attach-label label (cont-segment cont)))))) ; Continuation is implicitly fall-through. (define (push-arguments node cenv depth) (let recur ((args (cdr (node-form node))) (depth depth) (i 1)) (if (null? args) empty-segment (sequentially (compile (car args) cenv depth (fall-through-cont node i)) (instruction (enum op push)) (recur (cdr args) (+ depth 1) (+ i 1)))))) (define (push-all-with-names exp-list names cenv depth) (if (null? exp-list) empty-segment (sequentially (compile (car exp-list) cenv depth (named-cont (car names))) (instruction (enum op push)) (push-all-with-names (cdr exp-list) (cdr names) cenv (+ depth 1))))) ; OK, now that you've got all that under your belt, here's LAMBDA. (define-compilator 'lambda syntax-type (lambda (node cenv depth cont) (let ((exp (node-form node)) (name (cont-name cont))) (deliver-value (instruction-with-template (enum op closure) (compile-lambda exp cenv ;; Hack for constructors. ;; Cf. disclose method ;; (if name #t #f) #f) name) cont)))) (define (compile-lambda exp cenv body-name) (let* ((formals (cadr exp)) (nargs (number-of-required-args formals))) (sequentially ;; Check number of arguments (if (n-ary? formals) (if (pair? formals) (instruction (enum op check-nargs>=) nargs) empty-segment) ;+++ (lambda x ...) needs no check (instruction (enum op check-nargs=) nargs)) (compile-lambda-code formals (cddr exp) cenv body-name)))) ; name isn't the name of the procedure, it's the name to be given to ; the value that the procedure will return. (define (compile-lambda-code formals body cenv name) (if (null? formals) (compile-body body ;+++ Don't make null environment cenv 0 (return-cont name)) ;; (if (node-ref node 'no-inferior-lambdas) ...) (sequentially (let ((nargs (number-of-required-args formals))) (if (n-ary? formals) (sequentially (instruction (enum op make-rest-list) nargs) (instruction (enum op push)) (instruction (enum op make-env) (+ nargs 1))) (instruction (enum op make-env) nargs))) (let* ((vars (normalize-formals formals)) (cenv (bind-vars (reverse vars) cenv))) (note-environment vars (compile-body body cenv 0 (return-cont name))))))) (define compile-letrec (let ((operator/lambda (get-operator 'lambda syntax-type)) (operator/set! (get-operator 'set! syntax-type)) (operator/call (get-operator 'call)) (operator/unassigned (get-operator 'unassigned))) (lambda (node cenv depth cont) ;; (if (node-ref node 'pure-letrec) ...) (let* ((exp (node-form node)) (specs (cadr exp)) (body (cddr exp))) (compile-redex (make-node operator/lambda `(lambda ,(map car specs) ,@(map (lambda (spec) (make-node operator/set! `(set! ,@spec))) specs) ,(make-node operator/call `(,(make-node operator/lambda `(lambda () ,@body)))))) (map (lambda (spec) (make-node operator/unassigned `(unassigned))) specs) cenv depth cont))))) (define-compilator 'letrec syntax-type compile-letrec) ; -------------------- ; Deal with internal defines (ugh) (define (compile-body body cenv depth cont) (scan-body body cenv (lambda (defs exps) (if (null? defs) (compile-begin exps cenv depth cont) (compile-letrec (make-node operator/letrec `(letrec ,(map (lambda (node) (cdr (node-form node))) defs) ,@exps)) cenv depth cont))))) (define operator/letrec (get-operator 'letrec)) ; -------------------- ; Compile-time continuations ; ; A compile-time continuation is a pair (segment . name). Segment is ; one of the following: ; a return instruction - invoke the current full continuation. ; empty-segment - fall through to subsequent instructions. ; an ignore-values instruction - ignore values, then fall through. ; If name is non-#f, then the value delivered to subsequent ; instructions will be assigned to a variable. If the value being ; assigned is a lambda, we can give that lambda that name, for ; debugging purposes. (define (make-cont seg source-info) (cons seg source-info)) (define cont-segment car) (define cont-source-info cdr) ; Eventually we may be able to optimize jumps to jumps. Can't yet. ;(define (make-jump-cont jump cont) ; (if (fall-through-cont? cont) ; (make-cont jump (cont-name cont)) ; cont)) (define return-cont-segment (instruction (enum op return))) (define (return-cont name) (make-cont return-cont-segment name)) (define (return-cont? cont) (eq? (cont-segment cont) return-cont-segment)) ; Fall through into next instruction (define (fall-through-cont node i) (make-cont empty-segment (cons i node))) (define (fall-through-cont? cont) (not (return-cont? cont))) ; Ignore return value, then fall through (define ignore-values-segment (instruction (enum op ignore-values))) (define (ignore-values-cont node i) (make-cont ignore-values-segment (cons i node))) (define (ignore-values-cont? cont) (eq? (cont-segment cont) ignore-values-segment)) ; Value is in *val*; deliver it to its continuation. ; No need to generate an ignore-values instruction in this case. (define (deliver-value segment cont) (if (ignore-values-cont? cont) ;+++ segment (sequentially segment (cont-segment cont)))) ; For putting names to lambda expressions: (define (named-cont name) (make-cont empty-segment name)) (define (cont-name cont) (if (pair? (cont-source-info cont)) #f (cont-source-info cont))) ; -------------------- ; Compile-time environments (define (bind-vars names cenv) (let ((level (+ (environment-level cenv) 1))) (lambda (name) (if (eq? name funny-name/lexical-level) level (let loop ((over 1) (names names)) (cond ((null? names) (lookup cenv name)) ((eq? name (car names)) (make-binding usual-variable-type (cons level over) #f)) (else (loop (+ over 1) (cdr names))))))))) (define (initial-cenv cenv) (bind1 funny-name/lexical-level -1 cenv)) (define (environment-level cenv) (lookup cenv funny-name/lexical-level)) (define funny-name/lexical-level (string->symbol "Lexical nesting level")) ; Find lookup result that was cached by classifier (define (name-node-binding node cenv) (or (node-ref node 'binding) (node-form node))) ; = (lookup cenv (node-form node)) ; -------------------- ; Utilities ; Produce something for source code that contains a compile-time error. (define (generate-trap cont . stuff) (apply warn stuff) (sequentially (instruction-with-literal (enum op literal) (cons 'error stuff)) (deliver-value (instruction (enum op trap)) cont))) ; Make a segment smaller, if it seems necessary, by introducing an ; extra template. A segment is "too big" if it accesses more literals ; than the size of the operand in a literal-accessing instruction. ; The number of literals is unknowable given current representations, ; so we conservatively shrink the segment when its size exceeds 2 ; times the largest admissible operand value, figuring that it takes ; at least 2 instruction bytes to use a literal. (define (careful-sequentially seg1 seg2 depth cont) (if (and (= depth 0) (> (+ (segment-size seg1) (segment-size seg2)) large-segment-size)) (if (> (segment-size seg1) (segment-size seg2)) (sequentially (shrink-segment seg1 (fall-through-cont #f #f)) seg2) (sequentially seg1 (shrink-segment seg2 cont))) (sequentially seg1 seg2))) (define large-segment-size (* byte-limit 2)) (define (shrink-segment seg cont) (maybe-push-continuation (sequentially (instruction-with-template (enum op closure) (if (return-cont? cont) seg (sequentially seg (instruction (enum op return)))) #f) (instruction (enum op call) 0)) 0 cont)) ; -------------------- ; Type checking. This gets called on all nodes. (define (type-check node cenv) (if *type-check?* (let ((form (node-form node))) (if (pair? form) (let ((proc-node (car form))) (if (node? proc-node) (let ((proc-type (node-type proc-node cenv))) (cond ((procedure-type? proc-type) (if (restrictive? proc-type) (let* ((args (if (eq? *type-check?* 'heavy) (map (lambda (exp) (classify exp cenv)) (cdr form)) (cdr form))) (args-type (make-some-values-type (map (lambda (arg) (meet-type (node-type arg cenv) value-type)) args))) (node (make-similar-node node (cons proc-node args)))) (if (not (meet? args-type (procedure-type-domain proc-type))) (diagnose-call-error node proc-type cenv)) node) node)) ((not (meet? proc-type any-procedure-type)) ;; Could also check args for one-valuedness. (let ((message "non-procedure in operator position")) (warn message (schemify node cenv) `(procedure: ,proc-type)) (node-set! node 'type-error message)) node) (else node))) node)) node)) node)) (define (set-type-check?! check?) (set! *type-check?* check?)) (define *type-check?* 'heavy) (define (diagnose-call-error node proc-type cenv) (let ((message (cond ((not (fixed-arity-procedure-type? proc-type)) "invalid arguments") ((= (procedure-type-arity proc-type) (length (cdr (node-form node)))) "argument type error") (else "wrong number of arguments")))) (warn message (schemify node cenv) `(procedure wants: ,(rail-type->sexp (procedure-type-domain proc-type) #f)) `(arguments are: ,(map (lambda (arg) (type->sexp (node-type arg cenv) #t)) (cdr (node-form node))))) (node-set! node 'type-error message))) ; Type system loophole (define-compilator 'loophole syntax-type (lambda (node cenv depth cont) (compile (caddr (node-form node)) cenv depth cont)))