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