git.fiddlerwoaroof.com
Raw Blame History
;;; compiler-driver.scm -- compilation unit management
;;;
;;; author :  John & Sandra
;;;
;;;


;;; Flags for controlling various low-level behaviors of the compiler.
;;; You might want to tweak these in the system-building scripts for
;;; different Lisps, but users don't normally need to mess with them.

(define *compile-interface* '#f)
(define *interface-code-quality* 2)
(define *interface-chunk-size* '#f)
(define *default-code-quality* 2)
(define *optimized-code-quality* 3)
(define *code-chunk-size* 300)



;;;=====================================================================
;;; Main entry point
;;;=====================================================================

;;; This is the top level driver for the compiler.  It takes a file name
;;; and output controls.  It returns '#f if compilation fails.

(define *codefile-cache* '())

(define (haskell-compile filename cflags)
  (initialize-haskell-system)
  (let/cc abort-compile
    (dynamic-let ((*abort-compilation*
		   (lambda () (funcall abort-compile '#f))))
     (initialize-compilation)
     (let ((unit (find-cunit-name filename)))
       (let ((res (load-compilation-unit unit cflags)))
	 (map (lambda (x) (module-name x)) (ucache-modules res)))))))

;;; this is the initialization code that occurs at the start of compilation.

(define (initialize-compilation)
  (initialize-module-table)
  (for-each-unit
   (lambda (u)
     (setf (ucache-status u) 'available))))



;;;=====================================================================
;;; Filename utilities
;;;=====================================================================

;;; File extensions

(define *source-file-extensions* '(".hs" ".lhs"))
(define *unit-file-extension* ".hu")
(define *interface-file-extension* ".hi")
(define *lisp-file-extensions* '(".lisp" ".scm"))

(define (source-extension? x)
  (mem-string x *source-file-extensions*))

(define (unit-extension? x)
  (string=? x *unit-file-extension*))

(define (interface-extension? x)
  (string=? x *interface-file-extension*))

(define (lisp-extension? x)
  (mem-string x *lisp-file-extensions*))


;;; Build file names.

(define (make-cifilename filename)
  (let ((place  (filename-place filename))
	(name   (string-append (filename-name filename) "-hci")))
    (assemble-filename place name binary-file-type)))

(define (make-sifilename filename)
  (let ((place  (filename-place filename))
	(name   (string-append (filename-name filename) "-hci")))
    (assemble-filename place name source-file-type)))

(define (make-cfilename filename)
  (add-extension filename binary-file-type))

(define (make-sfilename filename)
  (add-extension filename source-file-type))


;;; This take a file name (extension ignored) & searches for a unit file.

(define (locate-existing-cunit name)
  (locate-extension name (list *unit-file-extension*)))

;;; This take a file name (extension ignored) & searches for a source file.

(define (locate-existing-source-file name)
  (locate-extension name *source-file-extensions*))

(define (locate-extension name extensions)
  (if (null? extensions)
      '#f
      (let ((name-1 (add-extension name (car extensions))))
	(if (file-exists? name-1)
	    name-1
	    (locate-extension name (cdr extensions))))))


;;; This delivers the name of a compilation unit.  The extension of the name
;;; is ignored & a test for the presence of a compilation unit with 
;;; the same name is done.  If none is found, signal an error.

(define (find-cunit-name name)
  (or (locate-existing-cunit name)
      (locate-existing-source-file name)
      (signal-file-not-found name)))



;;;=====================================================================
;;; Compilation unit file parsing
;;;=====================================================================

;;; This parses a unit file.  The file simply contains a list of file names.
;;; The files are sorted into two catagories: other compilation units and
;;; source files in the current unit.  When a file has no extension, the system
;;; checks for a unit file first and then a source file.

(define (parse-compilation-unit filename)
 (let ((unit-type (filename-type filename)))
  (if (or (source-extension? unit-type) (interface-extension? unit-type))
      (create-ucache filename filename (list filename) '() '() '#f '#t
		     '#f '() '#f '() '#f)
      (parse-compilation-unit-aux
        filename
	(call-with-input-file filename (function gather-file-names))))))

(define (create-ucache filename output-filename
		       source-files imports lisp-files
		       stable? load-prelude?
		       printers-set? printers optimizers-set? optimizers
		       chunk-size)
  (let* ((cifilename
	  (make-cifilename output-filename))
	 (sifilename
	  (make-sifilename output-filename))
	 (all-imports
	  (if load-prelude?
	      (cons *prelude-unit-filename* imports)
	      imports))
	 (cache-entry
	  (make ucache
		(ufile filename)
		(sifile sifilename)
		(cifile cifilename)
		(sfile (make-sfilename output-filename))
		(cfile (make-cfilename output-filename))
		(udate (current-date))
		(idate (get-latest-ifiledate cifilename sifilename))
		(stable? stable?)
		(load-prelude? load-prelude?)
		(status 'loading)
		(ifile-loaded '#f)
		(code-loaded '#f)
		(source-files source-files)
		(imported-units all-imports)
		(lisp-files lisp-files)
		(modules '())
		(printers-set? printers-set?)
		(printers printers)
		(optimizers-set? optimizers-set?)
		(optimizers optimizers)
		(chunk-size chunk-size))))
    (install-compilation-unit filename cache-entry)
    cache-entry))

(define (get-latest-ifiledate cifilename sifilename)
  (max (or (and (file-exists? cifilename)
		(file-write-date cifilename))
	   0)
       (or (and (file-exists? sifilename)
		(file-write-date sifilename))
	   0)))


;;; This returns a list of strings.  Blank lines and lines starting in -
;;; are ignored.

(define (gather-file-names port)
  (let ((char (peek-char port)))
    (cond ((eof-object? char)
	   '())
	  ((or (char=? char '#\newline) (char=? char '#\-))
	   (read-line port)
	   (gather-file-names port))
	  (else
	   (let ((line (read-line port)))
	     (cons line (gather-file-names port)))))))


;;; Actually parse contents of the unit file.

;;; These are in the command-interface stuff.
(predefine (set-printers args mode))
(predefine (set-optimizers args mode))
(predefine (parse-command-args string start next end))

(define (parse-compilation-unit-aux filename strings)
  (let ((input-defaults   filename)
	(output-defaults  filename)
	(import-defaults  filename)
	(stable?          '#f)
	(load-prelude?    '#t)
	(filenames        '())
	(imports          '())
	(sources          '())
	(lisp-files       '())
	(printers         '())
	(printers-set?    '#f)
	(optimizers       '())
	(optimizers-set?  '#f)
	(chunk-size       '#f)
	(temp             '#f))
    ;;; First look for magic flags.
    (dolist (s strings)
      (cond ((setf temp (string-match-prefix ":input" s))
	     (setf input-defaults (merge-file-defaults temp filename)))
	    ((setf temp (string-match-prefix ":output" s))
	     (setf output-defaults (merge-file-defaults temp filename)))
	    ((setf temp (string-match-prefix ":import" s))
	     (setf import-defaults (merge-file-defaults temp filename)))
	    ((string=? ":stable" s)
	     (setf stable? '#t))
	    ((string=? ":prelude" s)
	     (setf load-prelude? '#f))
	    ((setf temp (string-match-prefix ":p=" s))
	     (setf printers-set? '#t)
	     (setf printers
		   (set-printers
		      (parse-command-args temp 0 0 (string-length temp))
		      '=)))
	    ((setf temp (string-match-prefix ":o=" s))
	     (setf optimizers-set? '#t)
	     (setf optimizers
		   (set-optimizers
                      (parse-command-args temp 0 0 (string-length temp))
		      '=)))
	    ((setf temp (string-match-prefix ":chunk-size" s))
	     (setf chunk-size (string->number temp)))
	    (else
	     (push s filenames))))
    ;;; Next sort filenames into imports and source files.
    (dolist (s filenames)
      (let ((type    (filename-type s))
	    (fname   '#f))
	(cond ((string=? type "")  ; punt for now on this issue
	       (signal-extension-needed s))
;	      ((cond ((setf fname 
;			    (locate-existing-cunit
;			      (merge-file-defaults s import-defaults)))
;		      (push fname imports))
;		     ((setf fname
;			    (locate-existing-source-file
;			      (merge-file-defaults s input-defaults)))
;		      (push fname sources))
;		     (else
;		      (signal-unit-not-found s))))
	      ((unit-extension? type)
	       (setf fname  (merge-file-defaults s import-defaults))
	       (if (file-exists? fname)
		   (push fname imports)
		   (signal-unit-not-found fname)))
	      ((or (source-extension? type) (interface-extension? type))
	       (setf fname  (merge-file-defaults s input-defaults))
	       (if (file-exists? fname)
		   (push fname sources)
		   (signal-unit-not-found fname)))
	      ((lisp-extension? type)
	       (setf fname (merge-file-defaults s input-defaults))
	       (if (file-exists? fname)
		   (push (cons fname
			       (add-extension
			         (merge-file-defaults s output-defaults)
				 binary-file-type))
			 lisp-files)
		   (signal-unit-not-found fname)))
	      (else
	       (signal-unknown-file-type s)))))
    ;; Finally create the unit object.
    (create-ucache filename output-defaults
		   sources imports lisp-files
		   stable? load-prelude?
		   printers-set? printers optimizers-set? optimizers
		   chunk-size)))


;;; Helper functions for the above.

(define (string-match-prefix prefix s)
  (let ((prefix-length  (string-length prefix))
	(s-length       (string-length s)))
    (if (>= s-length prefix-length)
	(string-match-prefix-aux prefix s prefix-length s-length 0)
	'#f)))

(define (string-match-prefix-aux prefix s prefix-length s-length i)
  (cond ((eqv? i prefix-length)
	 (string-match-prefix-aux-aux s s-length i))
	((not (char=? (string-ref s i) (string-ref prefix i)))
	 '#f)
	(else
	 (string-match-prefix-aux prefix s prefix-length s-length (1+ i)))))

(define (string-match-prefix-aux-aux s s-length i)
  (cond ((eqv? i s-length)
	 "")
	((let ((ch  (string-ref s i)))
	   (or (char=? ch '#\space) (char=? ch #\tab)))
	 (string-match-prefix-aux-aux s s-length (1+ i)))
	(else
	 (substring s i s-length))))

(define (merge-file-defaults filename defaults)
  (let ((place  (filename-place filename))
	(name   (filename-name filename))
	(type   (filename-type filename)))
    (assemble-filename
      (if (string=? place "") defaults place)
      (if (string=? name "") defaults name)
      (if (string=? type "") defaults type))))
    
    
;;;=====================================================================
;;; Guts
;;;=====================================================================


;;; This is the main entry to the compilation system.  This causes a
;;; unit to be compiled and/or loaded.

(define (load-compilation-unit filename cflags)
  (let ((cunit (lookup-compilation-unit filename)))
    (cond ((eq? cunit '#f)
	   ;; Unit not found in cache.
	   (load-compilation-unit-aux
	     (parse-compilation-unit filename) cflags))
	  ((eq? (ucache-status cunit) 'loaded)
	   ;; Already loaded earlier in this compile.
	   cunit)
	  ((eq? (ucache-status cunit) 'loading)
	   (signal-circular-unit filename))
	  (else
	   (load-compilation-unit-aux cunit cflags))
	  )))


(define (load-compilation-unit-aux c cflags)
  (setf (ucache-status c) 'loading)
  (load-imported-units c cflags)
  (if (unit-valid? c cflags)
      (load-compiled-unit c (cflags-load-code? cflags))
      (locally-compile c cflags))
  (setf (ucache-status c) 'loaded)
  ;; Hack, hack.  When loading the prelude, make sure magic symbol
  ;; table stuff is initialized.
  (when (string=? (ucache-ufile c) *prelude-unit-filename*)
    (init-prelude-globals))
  c)

(define (load-compiled-unit c load-code?)
  (when (and load-code? (not (ucache-code-loaded c)))
    (when (memq 'loading *printers*)
      (format '#t "~&Loading unit ~s.~%" (ucache-ufile c))
      (force-output))
    (load-lisp-files (ucache-lisp-files c))
    (load-more-recent-file (ucache-cfile c) (ucache-sfile c))
    (setf (ucache-code-loaded c) '#t))
  (when (not (ucache-ifile-loaded c))
     (read-binary-interface c))
  (dolist (m (ucache-modules c))
      (add-module-to-symbol-table m))
  (link-instances (ucache-modules c)))


;;; These globals save the Prelude symbol table to avoid copying it
;;; into all modules which use the Prelude.

;;; Danger!  This assumes that every local symbol in the Prelude is
;;; exported.

(define *prelude-initialized* '#f)

(define (init-prelude-globals)
  (when (not *prelude-initialized*)
    (let ((pmod (locate-module '|Prelude|)))
      (setf *prelude-symbol-table* (module-symbol-table pmod))
      (setf *prelude-fixity-table* (module-fixity-table pmod))
      (when (eq? (module-inverted-symbol-table pmod) '#f)
	(let ((table (make-table)))
	  (table-for-each (lambda (name def)
			    (setf (table-entry table def) name))
			  *prelude-symbol-table*)
	  (setf (module-inverted-symbol-table pmod) table)))
      (setf *prelude-inverted-symbol-table*
	    (module-inverted-symbol-table pmod)))
    (setf *prelude-initialized* '#t)))


;;; This recursively loads all units imported by a given unit.

(define (load-imported-units c cflags)
  (dolist (filename (ucache-imported-units c))
    (load-compilation-unit filename cflags)))



;;; Load or compile lisp files.

(define (load-lisp-files lisp-files)
  (dolist (f lisp-files)
    (load-more-recent-file (cdr f) (car f))))

(define (compile-lisp-files lisp-files)
  (dolist (f lisp-files)
    (let ((source  (car f))
	  (binary  (cdr f)))
      (when (not (lisp-binary-current source binary))
	(compile-file source binary))
      (load binary))))



;;; This determines whether a unit is valid.

(define (unit-valid? c cflags)
  (and (or (ucache-stable? c)
	   ;; If the unit is not stable, make sure its source files
	   ;; haven't changed.
	   (and (all-imports-current (ucache-imported-units c)
				     (ucache-idate c))
		(all-sources-current (ucache-source-files c)
				     (ucache-idate c))
		(all-lisp-sources-current (ucache-lisp-files c)
					  (ucache-idate c))))
       (or (ucache-ifile-loaded c)
	   ;; If the interface hasn't been loaded already, make sure
	   ;; that the interface file exists.
	   (file-exists? (ucache-cifile c))
	   (file-exists? (ucache-sifile c)))
       (or (not (cflags-load-code? cflags))
	   ;; If we're going to load code, make sure that the code file
	   ;; exists.
	   (ucache-code-loaded c)
	   (file-exists? (ucache-cfile c))
	   (file-exists? (ucache-sfile c)))
       (or (not (cflags-write-code? cflags))
	   ;; If we need to produce a code file, make sure this has
	   ;; already been done.
	   ;; Don't write files for stable units which have already
	   ;; been loaded, regardless of whether or not the file exists.
	   (and (ucache-stable? c) (ucache-code-loaded c))
	   (file-exists? (ucache-cfile c))
	   (and (not (cflags-compile-code? cflags))
		(file-exists? (ucache-sfile c))))
       (or (not (cflags-compile-code? cflags))
	   ;; If we need to compile the lisp files, make sure this has
	   ;; already been done.
	   ;; Don't do this for stable units which have already
	   ;; been loaded.
	   (and (ucache-stable? c) (ucache-code-loaded c))
	   (all-lisp-binaries-current (ucache-lisp-files c)))
       (or (not (cflags-write-interface? cflags))
	   ;; If we need to produce an interface file, make sure this has
	   ;; already been done.
	   ;; Don't write files for stable units which have already
	   ;; been loaded, regardless of whether or not the file exists.
	   (and (ucache-stable? c) (ucache-ifile-loaded c))
	   (file-exists? (ucache-cifile c))
	   (and (not *compile-interface*)
		(file-exists? (ucache-sifile c))))
       ))

(define (all-sources-current sources unit-write-date)
  (every (lambda (s)
	   (let ((d  (file-write-date s)))
	     (and d (> unit-write-date d))))
	 sources))

(define (all-imports-current imports unit-write-date)
  (every (lambda (s) (> unit-write-date
			(ucache-idate (lookup-compilation-unit s))))
	 imports))

(define (all-lisp-sources-current lisp-files unit-write-date)
  (every (lambda (s)
	   (let ((d  (file-write-date (car s))))
	     (and d (> unit-write-date d))))
	 lisp-files))

(define (all-lisp-binaries-current lisp-files)
  (every (lambda (s)
	   (lisp-binary-current (car s) (cdr s)))
	 lisp-files))

(define (lisp-binary-current source binary)
  (and (file-exists? binary)
       (let ((sd  (file-write-date source))
	     (bd  (file-write-date binary)))
	 (and sd bd (> bd sd)))))


;;; This does the actual job of compilation.

(define (locally-compile c cflags)
  (dynamic-let ((*printers*
		  (if (ucache-printers-set? c)
		      (ucache-printers c)
		      (dynamic *printers*)))
		(*optimizers*
		  (if (ucache-optimizers-set? c)
		      (ucache-optimizers c)
		      (dynamic *optimizers*))))
    (when (memq 'compiling *printers*)
       (format '#t "~&Compiling unit ~s.~%Optimizers: ~A~%"
	       (ucache-ufile c)
	       *optimizers*)
	       (force-output))
    (if (cflags-compile-code? cflags)
	(compile-lisp-files (ucache-lisp-files c))
	(load-lisp-files (ucache-lisp-files c)))
    (multiple-value-bind (mods code)
	(compile-haskell-files (ucache-source-files c))
      ;; General bookkeeping to update module interface in cache.
      (setf (ucache-modules c) mods)
      (setf (ucache-idate c) (current-date))
      (setf (ucache-ifile-loaded c) '#t)
      ;; Write interface file if necessary.
      (when (cflags-write-interface? cflags)
	(let ((phase-start-time (get-run-time))
	      (icode  (create-dump-code c mods (ucache-load-prelude? c))))
	  (if (dynamic *compile-interface*)
	      (write-compiled-code-file
	        (ucache-cifile c)
		icode
		(dynamic *interface-code-quality*)
		(dynamic *interface-chunk-size*))
	      (write-interpreted-code-file (ucache-sifile c) icode '#f))
	  (when (memq 'phase-time *printers*)
	    (let* ((current-time (get-run-time))
		   (elapsed-time (- current-time phase-start-time)))
	      (format '#t "Interface complete: ~A seconds~%" elapsed-time)
	      (force-output)))))
      ;; Write code file if necessary.
      (when (cflags-write-code? cflags)
	(if (cflags-compile-code? cflags)
	    (write-compiled-code-file
	      (ucache-cfile c)
	      code
	      (if (memq 'lisp (dynamic *optimizers*))
		  (dynamic *optimized-code-quality*)
		  (dynamic *default-code-quality*))
	      (or (ucache-chunk-size c) (dynamic *code-chunk-size*)))
	    (write-interpreted-code-file (ucache-sfile c) code '#t)))
      ;; Load or evaluate code if necessary.
      ;; If we just wrote a compiled code file, load that; otherwise
      ;; do eval or in-core compilation.
      (when (cflags-load-code? cflags)
	(if (and (cflags-write-code? cflags)
		 (cflags-compile-code? cflags))
	    (load (ucache-cfile c))
	    (eval code (cflags-compile-code? cflags)))
	(setf (ucache-code-loaded c) '#t))
      )))



;;;=====================================================================
;;; Cache manager
;;;=====================================================================

;;; This is the cache manager for compilation units.  We use an alist at
;;; the moment.

(define *unit-cache* '())

(define (reset-unit-cache)
  (setf *unit-cache* '()))


;;; This checks to make sure that the compilation unit it finds
;;; in the cache has not been made out-of-date by updates to the unit file.

(define (lookup-compilation-unit name)
  (let ((r (ass-string name *unit-cache*)))
    (if r
	(let ((c  (cdr r)))
	 (if (or (ucache-stable? c)
		 (> (ucache-udate c)
		    (or (file-write-date (ucache-ufile c)) 0)))
	     c
	     '#f))
	'#f)))

(define (install-compilation-unit name c)
  (let ((r (ass-string name *unit-cache*)))
    (if (eq? r '#f)
	(push (cons name c) *unit-cache*)
	(setf (cdr r) c))))

(define (for-each-unit proc)
  (dolist (c *unit-cache*)
     (funcall proc (cdr c))))


;;;=====================================================================
;;; Error utilities
;;;=====================================================================

(define (signal-circular-unit filename)
  (fatal-error 'circular-unit
    "The compilation unit ~a has a circular dependency."
    filename))

(define (signal-unit-not-found filename)
  (fatal-error 'unit-not-found
    "The compilation unit file ~a was not found."
    filename))

(define (signal-extension-needed filename)
  (fatal-error 'extension-needed
    "You must provide an extension on the filename ~a in the .hu file."
     filename))