;;; 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)))