git.fiddlerwoaroof.com
Raw Blame History
;;; codegen.scm -- compile flic code to Lisp
;;;
;;; Author :  Sandra Loosemore
;;; Date   :  29 Apr 1992
;;;
;;; to do:  check completeness of special cases for constructors
;;;         constants still need work
;;;         optimized entry points
;;;
;;; The code generated here uses the following helper functions:
;;; (make-curried-fn opt-fn strictness)
;;;   make a curried function that calls opt-fn after collecting the
;;;   arguments and processing them according to strictness.  Both
;;;   the arguments are evaluated.
;;; (make-tuple-constructor arity)
;;;   return a function that makes an untagged data structure with "arity" 
;;;   slots.  "arity" is a constant.
;;; (make-tuple . args)
;;;   uncurried version of the above
;;; (make-tagged-data-constructor n arity)
;;;   return a function that makes a data structure with tag "n" and
;;;   "arity" slots.
;;; (make-tagged-data n . args)
;;;   uncurried version of the above
;;; (tuple-select arity i object)
;;;   extract component "i" from untagged "object"
;;; (tagged-data-select arity i object)
;;;   extract component "i" from tagged "object"
;;; (constructor-number object)
;;;   return the tag from "object"
;;; (delay form)
;;;   returns a delay object with unevaluated "form".
;;; (box form)
;;;   returns a delay object with evaluated "form".
;;; (force delay)
;;;   return the value of the delay object.
;;; (make-haskell-string string)
;;;   Converts a Lisp string lazily to a haskell string (using a magic
;;;   delay function).  Returns an unboxed result.



;;;======================================================================
;;; Code walker
;;;======================================================================


;;; Here is the main entry point.

(define (codegen-top big-let)
  (do ((bindings (flic-let-bindings big-let) (cdr bindings))
       (result   '())
       (decls    '()))
      ((null? bindings) `(begin ,@(nreverse decls) ,@(nreverse result)))
    (let ((var  (car bindings)))
      (push `(predefine ,(fullname var)) decls)
      (push (codegen-definition var (var-value var)) result))))


;;; See box.scm for more information about this...

(define (do-codegen object)
  (let ((x               (codegen object))
	(unboxed?        (flic-exp-unboxed? object))
	(strict-result?  (flic-exp-strict-result? object))
	(cheap?          (flic-exp-cheap? object)))
    (if unboxed?
	(if strict-result?
	    x
	    (if cheap?
		`(unbox ,x)
		`(force ,x)))
	(if strict-result?
	    (if cheap?
		`(box ,x)
		`(delay ,x))
	    (if cheap?
		x
		`(delay (force ,x)))))))
    

(define (do-codegen-list list)
  (map (function do-codegen) list))


(define-flic-walker codegen (object))


;;; For top-level definitions bound to lambda expressions, make both
;;; a standard entry point (with possibly unboxed arguments) and
;;; a standard entry point.

(define (codegen-definition var exp)
  (let ((fullname  (fullname var)))
    (when (or (memq 'codegen (dynamic *printers*))
	      (memq 'codegen-flic (dynamic *printers*)))
;       (format '#t "~%Codegen of ~A [~A]  " (def-name var) (struct-hash var))
       (format '#t "~%Codegen of ~A  " (def-name var))
       (when (not (var-strict? var))
	   (format '#t "Nonstrict  "))
       (when (not (eq? (var-strictness var) '()))
	   (format '#t "Strictness: ")
	   (dolist (s (var-strictness var))
	       (format '#t (if s "S " "N "))))
       (when (var-simple? var)
	   (format '#t " Inline "))
       (format '#t "~%")
       (when (memq 'codegen-flic (dynamic *printers*))
          (pprint* exp)))
    (let ((lisp-code
	   (if (not (flic-lambda? exp))
	       `(define ,fullname ,(do-codegen exp))
	       (let* ((optname  (optname var))
		      (lambda   (codegen-lambda-aux exp))
		      (def      `(define (,optname ,@(cadr lambda))
				                   ,@(cddr lambda))))
		 (if (var-selector-fn? var)
   	             ;; Standard entry point for selectors is never used.
		     def
		     `(begin
			,def
			(define ,fullname
			  ,(maybe-make-box-value
			    (codegen-curried-fn
			     `(function ,optname) (var-strictness var))
			    (var-strict? var)))))))))
      (when (or (memq 'codegen (dynamic *printers*))
		(memq 'codegen-flic (dynamic *printers*)))
	    (pprint* lisp-code))
      lisp-code)))

(define (codegen-lambda-list vars)
  (map (function fullname) vars))

(define (codegen-curried-fn opt-fn strictness)
  (if (null? (cdr strictness))
      ;; one-argument special cases
      (if (car strictness)
	  `(make-curried-fn-1-strict ,opt-fn)
	  `(make-curried-fn-1-nonstrict ,opt-fn))
      ;; general case
      `(make-curried-fn ,opt-fn ',strictness)))


;;; Curry lambdas.  Functions always return an unboxed value.

(define-codegen flic-lambda (object)
  (codegen-curried-fn
    (codegen-lambda-aux object)
    (map (lambda (x) (var-strict? x)) (flic-lambda-vars object))))

(define (codegen-lambda-aux object)
  (let* ((vars    (flic-lambda-vars object))
	 (ignore  '())
	 (args    (codegen-lambda-list vars)))
    (dolist (v vars)
      (if (eqv? (var-referenced v) 0)
	  (push (fullname v) ignore)))
    `(lambda ,args
       ,@(if (not (null? ignore))
	     `((declare (ignore ,@ignore)))
	     '())
       ,(do-codegen (flic-lambda-body object)))))


;;; This is only for non-top-level lets.
;;; The boxing of the value of each of the bindings is controlled by its
;;; strict? property.

(define-codegen flic-let (object)
  (let ((bindings   (flic-let-bindings object))
	(body       (flic-let-body object))
	(recursive? (flic-let-recursive? object)))
    (if recursive?
	(codegen-letrec bindings body)
	(codegen-let*   bindings body))))


;;; For efficiency reasons, we want to make all the function bindings
;;; in the function namespace (some implementations do not do tail-recursion
;;; or other optimizations correctly otherwise).  This means we have
;;; to sort out the variable bindings from the function bindings here.

(define (codegen-letrec bindings body)
  (let ((let-bindings     '())
	(labels-bindings  '()))
    (dolist (var bindings)
      (let ((value    (var-value var))
	    (fullname (fullname var))
	    (strict?  (var-strict? var)))
	(if (flic-lambda? value)
	    ;; Some functions may need only the optimized or standard
	    ;; entry points, but not both.
	    (let ((optname     (optname var))
		  (lambda      (codegen-lambda-aux value))
		  (optimized?  (var-optimized-refs? var))
		  (standard?   (var-standard-refs? var)))
	      (when standard?
		(push (list fullname
			    (maybe-make-box-value
			      (codegen-curried-fn
			        (if optimized? `(function ,optname) lambda)
				(var-strictness var))
			      strict?))
		      let-bindings))
	      (when optimized?
		(push (cons optname (cdr lambda)) labels-bindings)))
	    (push (list fullname (do-codegen value)) let-bindings))))
    (setf let-bindings (nreverse let-bindings))
    (setf labels-bindings (nreverse labels-bindings))
    (cond ((null? let-bindings)
	   `(labels ,labels-bindings ,(do-codegen body)))
	  ((null? labels-bindings)
	   `(letrec ,let-bindings ,(do-codegen body)))
	  (t
	   `(let ,(map (lambda (b) `(,(car b) '#f)) let-bindings)
	      (labels ,labels-bindings
		      ,@(map (lambda (b) `(setf ,@b)) let-bindings)
		      ,(do-codegen body))))
	  )))

(define (codegen-let* bindings body)
  (if (null? bindings)
      (do-codegen body)
      (let* ((var       (car bindings))
	     (value     (var-value var))
	     (fullname  (fullname var))
	     (strict?   (var-strict? var))
	     (body      (codegen-let* (cdr bindings) body)))
	(if (flic-lambda? value)
	    ;; Some functions may need only the optimized or standard
	    ;; entry points, but not both.
	    (let ((optname     (optname var))
		  (lambda      (codegen-lambda-aux value))
		  (optimized?  (var-optimized-refs? var))
		  (standard?   (var-standard-refs? var)))
	      (when standard?
		(setf body
		      (add-let-binding
		        (list fullname
			      (maybe-make-box-value
			        (codegen-curried-fn
				  (if optimized? `(function ,optname) lambda)
				  (var-strictness var))
				strict?))
			body)))
	      (when optimized?
		(setf body `(flet ((,optname ,@(cdr lambda))) ,body)))
	      body)
	    (add-let-binding (list fullname (do-codegen value)) body)))))

(define (add-let-binding binding body)
  (if (and (pair? body) (eq? (car body) 'let*))
      `(let* (,binding ,@(cadr body)) ,@(cddr body))
      `(let* (,binding) ,body)))


(define-codegen flic-app (object)
  (let ((fn         (flic-app-fn object))
	(args       (flic-app-args object))
	(saturated? (flic-app-saturated? object)))
    (cond ((and saturated? (flic-pack? fn))
	   ;; Saturated call to constructor
	   (codegen-constructor-app-aux
	     (flic-pack-con fn)
	     (do-codegen-list args)))
	  ((and saturated? (flic-ref? fn))
	   ;; Saturated call to named function
	   (let* ((var     (flic-ref-var fn))
		  (optname (optname var))
		  (argcode (do-codegen-list args)))
	     `(,optname ,@argcode)))
	  (else
	   ;; Have to make a curried call to standard entry point.
	   (let ((fncode   (do-codegen fn))
		 (argcode  (do-codegen-list args)))
	     (if (and (pair? fncode)
		      (eq? (car fncode) 'force))
		 `(funcall-force ,(cadr fncode) ,@argcode)
		 `(funcall ,fncode ,@argcode))))
	  )))

(define (codegen-constructor-app-aux con argcode)
  (let ((alg  (con-alg con)))
    (cond ((eq? con (core-symbol ":"))
	   `(cons ,@argcode))
	  ((algdata-implemented-by-lisp? alg)
	   (apply-maybe-lambda (cadr (con-lisp-fns con)) argcode))
	  ((algdata-tuple? alg)
	   `(make-tuple ,@argcode))
	  (else
	   `(make-tagged-data ,(con-tag con) ,@argcode)))))


(define-codegen flic-ref (object)
  (fullname (flic-ref-var object)))


(define-codegen flic-const (object)
  (let ((value   (flic-const-value object)))
    (cond ((string? value)
	   `(make-haskell-string ,value))
	  ((char? value)
	   ;; *** I think the parser ought to convert characters to their
	   ;; *** ASCII codes instead of doing it here.  There are problems
	   ;; *** with valid Haskell characters that can't be represented
	   ;; *** portably as Lisp characters.
	   (char->integer value))
	  ((number? value)
	   value)
	  (else
	   ;; It must be a ratio.  This is a bit of a hack - this depends on
	   ;; the fact that 2 tuples are represented in the same manner as
	   ;; rationals.  Hacked for strict rationals - jcp
	   `(make-tuple ,(car value) ,(cadr value)))
	  )))


;;; Returns a function or constant, so doesn't need to delay result.
;;; See flic-app for handling of saturated constructor calls.

(define-codegen flic-pack (object)
  (let* ((con        (flic-pack-con object))
	 (arity      (con-arity con))
	 (alg        (con-alg con))
	 (tuple?     (algdata-tuple? alg))
	 (strictness (con-slot-strict? con))
	 (index      (con-tag con)))
    (cond ((eq? con (core-symbol "Nil"))
	   ''())
	  ((eq? con (core-symbol "True"))
	   ''#t)
	  ((eq? con (core-symbol "False"))
	   ''#f)
	  ((eq? con (core-symbol ":"))
	   '(function make-cons-constructor))
	  ((algdata-implemented-by-lisp? alg)
	   (let ((fn (cadr (con-lisp-fns con))))
	     (if (eqv? (con-arity con) 0)
		 fn
		 (codegen-curried-fn
		  (if (and (pair? fn) (eq? (car fn) 'lambda))
		      fn
		      `(function ,fn))
		  strictness))))
	  ((algdata-enum? alg)
	   ;; All constructors have 0 arity; represent them just
	   ;; by numbers.
	   index)
	  (tuple?
	   ;; Only a single constructor for this type.
	   (codegen-curried-fn
	     `(make-tuple-constructor ,arity)
	     strictness))
	  ((eqv? arity 0)
	   ;; No arguments to this constructor.
	   `(make-tagged-data ,index))
	  (else
	   ;; General case.
	   (codegen-curried-fn
	    `(make-tagged-data-constructor ,index ,arity)
	    strictness))
	  )))



;;; These expressions translate directly into their Lisp equivalents.

(define-codegen flic-case-block (object)
  `(block ,(flic-case-block-block-name object)
     ,@(do-codegen-list (flic-case-block-exps object))))

(define-codegen flic-return-from (object)
  `(return-from ,(flic-return-from-block-name object)
		,(do-codegen (flic-return-from-exp object))))

(define-codegen flic-and (object)
  `(and ,@(do-codegen-list (flic-and-exps object))))

(define-codegen flic-if (object)
  `(if ,(do-codegen (flic-if-test-exp object))
       ,(do-codegen (flic-if-then-exp object))
       ,(do-codegen (flic-if-else-exp object))))

(define-codegen flic-sel (object)
  (codegen-flic-sel-aux
    (flic-sel-con object)
    (flic-sel-i object)
    (do-codegen (flic-sel-exp object))))

(define (codegen-flic-sel-aux con index exp)
  (let* ((alg      (con-alg con))
	 (tuple?   (algdata-tuple? alg))
	 (arity    (con-arity con)))
    (cond ((eq? con (core-symbol ":"))
	   (if (eqv? index 0)
	       `(car ,exp)
	       `(cdr ,exp)))
	  ((algdata-implemented-by-lisp? alg)
	   (apply-maybe-lambda (list-ref (cddr (con-lisp-fns con)) index)
			       (list exp)))
	  (tuple?
	   `(tuple-select ,arity ,index ,exp))
	  (else
	   `(tagged-data-select ,arity ,index ,exp))
	  )))

(define-codegen flic-is-constructor (object)
  (codegen-flic-is-constructor-aux
    (flic-is-constructor-con object)
    (do-codegen (flic-is-constructor-exp object))))

(define (codegen-flic-is-constructor-aux con exp)
  (let ((type (con-alg con)))
    (cond ((eq? type (core-symbol "Bool"))
	   (if (eq? con (core-symbol "True"))
	       exp
	       `(not ,exp)))
	  ((eq? type (core-symbol "List"))
	   (if (eq? con (core-symbol ":"))
	       `(pair? ,exp)
	       `(null? ,exp)))
	  ((algdata-implemented-by-lisp? type)
	   (let ((fn (car (con-lisp-fns con))))
	     (apply-maybe-lambda fn (list exp))))
	  ((algdata-tuple? type)
	   ;; This should never happen.
	   ''#t)
	  ((algdata-enum? type)
	   `(eqv? (the fixnum ,exp) (the fixnum ,(con-tag con))))
	  (else
	   `(eqv? (the fixnum (constructor-number ,exp))
		  (the fixnum ,(con-tag con))))
	  )))


(define-codegen flic-con-number (object)
  (let ((type   (flic-con-number-type object))
	(exp    (do-codegen (flic-con-number-exp object))))
    `(the fixnum
	  ,(cond ((eq? type (core-symbol "Bool"))
		  `(if ,exp 1 0))
		 ((eq? type (core-symbol "List"))
		  `(if (pair? ,exp) 0 1))
		 ((algdata-tuple? type)
		  ;; This should never happen.
		  0)
		 ((algdata-implemented-by-lisp? type)
		  (let ((var (gensym)))
		    `(let ((,var ,exp))
		       (cond ,@(map (lambda (con)
				      `(,(apply-maybe-lambda
					  (car (con-lisp-fns con))
					  (list var))
					',(con-tag con)))
				    (algdata-constrs type))
			     (else (error "No constructor satisfies ~A.~%"
					  ',(def-name type)))))))
		 ((algdata-enum? type)
		  exp)
		 (else
		  `(constructor-number ,exp))
		 ))
    ))



;;;======================================================================
;;; Utility functions
;;;======================================================================

;;; Here are some helper functions for handing boxing and unboxing
;;; of values.
;;; maybe-make-box-delay is used to box forms that are "expensive" to
;;; compute; maybe-make-box-value is used to box forms like constants
;;; or functions that are "cheap" to compute eagerly.
;;; Maybe-unbox is used to unbox a form that returns a boxed result.

(define (maybe-make-box-delay form unboxed?)
  (if unboxed?
      form
      `(delay ,form)))

(define (maybe-make-box-value form unboxed?)
  (if unboxed?
      form
      `(box ,form)))

(define (maybe-unbox form unboxed?)
  (if unboxed?
      `(force ,form)
      form))


;;; These two var slots are filled in lazily by the code generator,
;;; since most vars generated don't need them.  You should always
;;; use these functions instead of accessing the structure slot
;;; directly.

(define (fullname var)
  (or (var-fullname var)
      (setf (var-fullname var)
	    (if (var-toplevel? var)
		;; For toplevel names, use module name glued onto base names.
		;; These are always interned symbols.
		(if (def-core? var)
		    (symbol-append '|*Core:| (def-name var))
		    (symbol-append (def-module var) '\: (def-name var)))
		;; Otherwise, make sure we have a gensym.
		;; The uniquification of interned symbols is required
		;; because there may be multiple nested bindings of the
		;; same name, and we want to be able to distinguish between
		;; the different bindings.
		(let ((name  (def-name var)))
		  (if (gensym? name)
		      name
		      (gensym (symbol->string name))))))
      ))

(define (optname var)
  (or (var-optimized-entry var)
      (let ((name  (string-append (symbol->string (fullname var)) "/OPT")))
	(setf (var-optimized-entry var)
	      (if (var-toplevel? var)
		  (string->symbol name)
		  (gensym name))))))



;;;======================================================================
;;; Exported functions
;;;======================================================================

;;; This handles types exported to lisp from Haskell
;;; *** Is this really supposed to create variable bindings as
;;; *** opposed to function bindings???
;;; *** I assume all of these functions want strict arguments and return
;;; *** strict results, even if the data structures contain boxed values.

(define (codegen-exported-types mods)
  (let ((defs '()))
    (dolist (m mods)
      (dolist (a (module-alg-defs m))
        (when (algdata-export-to-lisp? a)
	  (dolist (c (algdata-constrs a))
	    (setf defs (nconc (codegen-constr c) defs))))))
    `(begin ,@defs)))

(define (codegen-constr c)
  (let ((lisp-fns (con-lisp-fns c)))
    (if c
        (let ((res
	       `(,(codegen-lisp-predicate (car lisp-fns) c)
		 ,(codegen-lisp-constructor (cadr lisp-fns) c)
		 ,@(codegen-lisp-accessors
		    (cddr lisp-fns) (con-slot-strict? c) c 0))))
	  (when (memq 'codegen (dynamic *printers*))
	    (dolist (d res)
	      (pprint* d)))
	  res)
	'())))

(define (codegen-lisp-predicate name c)
  `(define (,name x)
     ,(codegen-flic-is-constructor-aux c 'x)))

(define (codegen-lisp-constructor name c)
  (let ((strictness (con-slot-strict? c))
	(args       '())
	(exps       '()))
    (dolist (s strictness)
      (let ((arg  (gensym)))
	(push arg args)
	(push (if s arg `(box ,arg)) exps)))
    `(define (,name ,@(nreverse args))
	 ,(codegen-constructor-app-aux c (nreverse exps)))))

(define (codegen-lisp-accessors names strictness c i)
  (declare (type fixnum i))
  (if (null? names)
      '()
      (let ((body  (codegen-flic-sel-aux c i 'x)))
	(when (not (car strictness))
	  (setf body `(force ,body)))
	(cons `(define (,(car names) x) ,body)
	      (codegen-lisp-accessors (cdr names) (cdr strictness) c (+ i 1))))
    ))



;;; This is a special hack needed due to brain-dead common lisp problems.
;;; This allows the user to place lambda defined functions in ImportLispType
;;; *** I'm not convinced this is necessary;  ((lambda ...) args)
;;; *** is perfectly valid Common Lisp syntax!

(define (apply-maybe-lambda fn args)
  (if (and (pair? fn)
	   (eq? (car fn) 'lambda))
      `(funcall ,fn ,@args)
      `(,fn ,@args)))