;;; -*- Mode:Lisp; Package:(NC LISP); Readtable:CL; Base:10 -*- ;;; Copyright (c) 1985 Yale University ;;; Authors: N Adams, R Kelsey, D Kranz, J Philbin, J Rees. ;;; This material was developed by the T Project at the Yale University Computer ;;; Science Department. Permission to copy this software, to redistribute it, ;;; and to use it for any purpose is granted, subject to the following restric- ;;; tions and understandings. ;;; 1. Any copy made of this software must include this copyright notice in full. ;;; 2. Users of this software agree to make their best efforts (a) to return ;;; to the T Project at Yale any improvements or extensions that they make, ;;; so that these may be included in future releases; and (b) to inform ;;; the T Project of noteworthy uses of this software. ;;; 3. All materials developed as a consequence of the use of this software ;;; shall duly acknowledge such use, in accordance with the usual standards ;;; of acknowledging credit in academic research. ;;; 4. Yale has made no warrantee or representation that the operation of ;;; this software will be error-free, and Yale is under no obligation to ;;; provide any services, by way of maintenance, update, or otherwise. ;;; 5. In conjunction with products arising from the use of this material, ;;; there shall be no use of the name of the Yale University nor of any ;;; adaptation thereof in any advertising, promotional, or sales literature ;;; without prior written consent from Yale in each case. ;;; ;;; Compilators ;;;=========================================================================== ;;; The compiler knows about several special forms. Some of them are part ;;; of Lisp, the others are internal to the compiler. ;;; Handlers for the special forms that are of interest to the compiler ;;; are defined with the following forms. (defvar *compilator-table* (make-table '*compilator-table*)) ;;; Define a compilator for a special form. (defmacro define-compilator (pattern &body body) (let ((name (car pattern)) (sym (concatenate-symbol 'compilator/ (car pattern))) (exp (gensym 'exp))) `(progn (defun ,sym (,exp) (destructure ((,(cdr pattern) (cdr ,exp))) ,@body)) (setf (table-entry *compilator-table* ',name) #',sym)))) ;;; (QUOTE . blah) ;;; Return a literal node. (define-compilator (QUOTE value) (values (create-literal-node (copy-tree value)) nil nil)) ;;;; (FUNCTION var) ;(define-compilator (FUNCTION var) ; (make-call `(,primop/function-ref ,var))) ;;; (LAMBDA vars . body) (define-compilator (LAMBDA name vars body) (let ((node (make-lambda name (copy-list vars) body))) (mapc #'(lambda (var) (if var (unused-variable-check var))) vars) (values node nil nil))) (define-compilator (optional-init) (make-call `(,primop/noop))) (define-compilator (optional-setup first-label nargs supplied-p-vars init-labels) (let* ((call (create-call-node (+ 5 (length init-labels)) 2)) (top (cps-args call `(,primop/optional-setup ,empty ,first-label ,nargs ,supplied-p-vars ,@init-labels)))) (values top call (call-arg 1)))) ;;; make the lambdas be exit args (define-compilator (dispatch-special-form . args) (let ((lambda-args (do ((n 0 (1+ n)) (args args (cdr args))) ((not (and (consp (car args)) (eq (caar args) 'LAMBDA))) n)))) ;; would be nice if make-call-with-exits was a little better ;; about returning top/node/role (let ((call (create-call-node (+ 2 (length args)) (1+ lambda-args)))) (values (cps-args call `(,primop/%dispatch ,empty ,@args)) call (call-arg 1))))) (define-compilator (SETQ-LEXICAL var value) (multiple-value-bind (node c-parent c-role) (make-call `(,primop/setq-lexical ,var ,value)) (push c-parent (variable-setqs var)) (values node c-parent c-role))) (define-compilator (the value-type form) (multiple-value-bind (node c-parent c-role) (->node form) (cond ((literal-node? node) (unless (typep (literal-value node) value-type) (warn "~s is not of type ~s as declared by THE." (literal-value node) value-type)) (values node c-parent c-role)) ((reference-node? node) (setf (leaf-type node) value-type) (values node c-parent c-role)) ;; somehow we want to get the type of the cont-var set I think... (t (values node c-parent c-role))))) ;;; (PRIMOP id formals . clauses) ;;; The special form for introducing primitive operations. The name should ;;; be changed to stop all the warning messages for PRIMOP variables. ;(define-compilator (primop primop) ; (make-call `(,primop/*primop ,primop))) ;;; (IF p c a) ;;; ==> ;;; ((lambda (join) ;;; (primop/conditional ;;; (lambda () (join c)) ;;; (lambda () (join a)) ;;; primop/true ;;; p)) ;;; ) (define-compilator (IF tested con alt) (let* ((j-var (create-variable "J")) (j-lambda (create-lambda-node "P" (list nil j-var))) (j-call (create-call-node 2 0)) (c-call (make-call-with-exits 2 (list primop/conditional (thunkify con j-var) (thunkify alt j-var) primop/true? tested)))) (relate call-proc j-call j-lambda) (relate lambda-body j-lambda c-call) (values j-call j-call (call-arg 1)))) (defun thunkify (exp cont-var) (let ((l-node (create-lambda-node "C" (list nil)))) (multiple-value-bind (call c-parent c-role) (make-block (list exp)) (relate lambda-body l-node call) (relate c-role c-parent (create-reference-node cont-var)) l-node))) ;;; (LABELS ((v1 e1) (v2 e2) ... (vn en)) . body) ;;; ==> (exits 1 primop/Y ;;; ;;; (lambda (c v1 ... vn) ;;; (exits 0 c ;;; (lambda (c) . body) ;;; (lambda () e1) ;;; ... ;;; (lambda () en)))) ;;; The code generator depends on call-exits being one for PRIMOP/Y (define-compilator (LABELS vars vals body) (let* ((call (create-call-node 2 1)) (c-var (create-variable "C")) (b-lambda (create-lambda-node "C" (list nil '()))) (y-lambda (create-lambda-node "Y" (list* nil c-var (copy-list vars))))) (let ((y-call (make-call-with-exits 0 `(,(create-reference-node c-var) ,b-lambda . ,vals)))) ;(mapcar #'value->thunk vals))))) (multiple-value-bind (b-call c-parent c-role) (make-block body) (relate call-proc call (create-primop-node primop/y)) (relate (call-arg 1) call y-lambda) (relate lambda-body y-lambda y-call) (relate lambda-body b-lambda b-call) (values call c-parent c-role))))) ;(defun value->thunk (val) ; (->value-node `(LAMBDA T (nil ,(create-variable "C")) (,val)))) ;#f (define-compilator (PROGN forms) (make-block forms)) ;;; BLOCK turns into a call to a lambda ;;; which binds the blocks continuation ;;; RETURN-FROM will be a call to the bound var (define-compilator (BLOCK var forms) (let ((block-lambda (make-lambda 'block (list nil var) forms)) (block-call (create-call-node 2 1))) (relate call-proc block-call block-lambda) (values block-call block-call (call-arg 1)))) ;what do we do with the (ignored) continuation to return-from? ; (block xxx ... (return-from xxx v) ...) ; ; ((block_n nil xxx_n) ... ; (xxx_n v)) ; ; how about: ; (^RF #cont#) ; ((RF IGNORE) (xxx_n v)) ; (define-compilator (RETURN-FROM block-cont value) ;; *** unwind the stack *** ; (let ((call (create-call-node 2 0)) ; (value (->value-node value))) ; (relate call-proc call (create-reference-node block-cont)) ; (relate (call-arg 1) call value) (multiple-value-bind (call c-node c-role) ;; deal with possible multiple values here (->node value) (if (call-node? call) (relate c-role c-node (create-reference-node block-cont)) (progn (setq call (make-call-with-exits 0 (list block-cont call))))) (let ((crock-lambda (create-lambda-node "RF" (list (create-variable "IGNORE")))) (crock-call (create-call-node 2 1))) (relate lambda-body crock-lambda call) (relate call-proc crock-call crock-lambda) (values crock-call crock-call (call-arg 1))))) ;;; tagbody is just like labels but we also bind ;;; the continuation to the entire tagbody, go's ;;; take this as a continuation and pass it to ;;; the labels, the last label will call its continuation (define-compilator (TAGBODY tagbody-cont . label-args) (let ((let (create-lambda-node "TAGBODY" (list nil tagbody-cont))) (call (create-call-node 2 1))) (multiple-value-bind (label-call c-node c-role) (compilator/labels (cons 'LABELS label-args)) (relate c-role c-node (create-reference-node tagbody-cont)) (relate lambda-body let label-call) (relate call-proc call let) (values call call (call-arg 1))))) ;;; go ignores its own contination ;;; but takes the continuation of the entire tagbody (define-compilator (GO-INTERNAL tag tagbody-cont) (let ((call (make-call-with-exits 2 (list primop/%go tagbody-cont tag)))) (let ((crock-lambda (create-lambda-node "GO" ;; ignored continuation hopefully optimized out (list (create-variable "IGNORE")))) (crock-call (create-call-node 2 1))) (relate lambda-body crock-lambda call) (relate call-proc crock-call crock-lambda) (values crock-call crock-call (call-arg 1))))) (define-compilator (MULTIPLE-VALUE-BIND vars exp body) (multiple-value-bind (e-node e-parent e-role) (->call-node exp) (multiple-value-bind (b-node b-parent b-role) (make-block body) (let ((l-node (create-lambda-node "R" (cons nil (copy-list vars))))) (relate lambda-body l-node b-node) (relate e-role e-parent l-node) (values e-node b-parent b-role))))) (define-compilator (VALUES vals) (let* ((call (create-call-node (1+ (length vals)) 0)) (top (cps-args call `(,empty ,@vals)))) (values top call call-proc))) (define-compilator (%CATCH-OPEN cont tag) (let ((call (make-call-with-exits 2 `(,primop/%catch-open ,empty ,cont ,tag)))) (values call call (call-arg 1)))) ;;; %CATCH-BODY is wrapped around a CATCH body ;;; It inserts a call to the primop %CATCH-BODY-VALUES ;;; around every place where the body produces a value. ;;; %CATCH-BODY-VALUES will look ahead to the continuation ;;; of the body to see where the values will be needed. (define-compilator (%CATCH-BODY body) (wrap-body-values primop/%catch-body-values nil body)) (define-compilator (THROW-INTERNAL tag result) (wrap-body-values primop/%throw-internal (list tag) result)) (defun wrap-body-values (primop extra-args body) (multiple-value-bind (node c-parent c-role) (->node body) (wrap-body-values-1 primop extra-args node c-parent c-role))) (defun wrap-body-values-1 (primop extra-args node c-parent c-role) (cond ;; ;; '259 => ( 1 '259) ;; ((or (literal-node? node) (reference-node? node)) (make-call `(,primop ,@extra-args ,node))) ;; ;; ( 0 '3 V_1 V_2) => ( 1 '3 V_1 V_2) ;; ((eq call-proc c-role) (let ((args (copy-list (call-args c-parent)))) (mapc #'detach args) (let ((new-call (make-call `(,primop ,@extra-args . ,args)))) (unless (empty? (node-parent c-parent)) (replace-node c-parent new-call)) (values (if (eq node c-parent) new-call node) new-call (call-arg 1))))) ;; ;; (( ... ) (^P_1 0 )) ;; ((P_1 NIL J_3) ... ;; ... (J_3 0 X_6) => ( 1 J_3 X_6) ;; ... (BAR 1 J_3 V_7) => ... (BAR 1 ^C_22 V_7) ;; ((C_22 V_23) ( 1 J_3 V_23)) ;; ((lambda-node? (call-proc c-parent)) (let ((jvar (car (lambda-variables (call-proc c-parent))))) (when jvar (dolist (ref (variable-refs jvar)) (let ((ref-role (node-role ref)) (ref-parent (node-parent ref))) (detach ref) (multiple-value-bind (nil parent role) (wrap-body-values-1 primop extra-args node ref-parent ref-role) (relate role parent ref)))))) (values node c-parent c-role)) ;; ;; (BA:R 1 X_1) => ... (BAR 1 ^C_22 X_1) ;; ((C_22 V_23) ( 1 V_23)) ;; ((and (eq c-role (call-arg 1)) (= 1 (call-exits c-parent))) (values node (insert-primop-call-as-cont primop extra-args c-parent) (call-arg 1))) (t (bug "something funny in %values")))) (defun insert-primop-call-as-cont (primop extra-args parent) (let* ((var (create-variable 'v)) (lambda (create-lambda-node "C" (list nil var))) (call (make-call `(,primop ,@extra-args ,var)))) (relate lambda-body lambda call) (relate (call-arg 1) parent lambda) call))