;;; dump-interface.scm -- interface file writer/loader ;;; ;;; author : John & Sandra ;;; date : 8 Jul 1992 ;;; ;;; This writes binary interface files. A binary interface file is just ;;; a lisp (mumble) source file which directly builds the ast structure ;;; created by a compilation. These files could be stored in either ;;; source or binary (compiled lisp) form. ;;; An interface may reference entities defined in other interfaces. ;;; To ensure consistancy between when an interface is written and ;;; when it is read back in, a stamp is assigned to all interface files ;;; which serves as a unique id. The stamps of all imported units are ;;; saved and examined at load time. ;;;================================================================== ;;; Interface to compilation system ;;;================================================================== ;;; For compiled code, don't actually write out all the source code. ;;; Use a magic macro to memoize the form to be compiled. (define *form-to-compile* '#f) (define *magic-file-to-compile* "$HASKELL/bin/magic.scm") ;;; The output from compiling the prelude can completely overwhelm ;;; the Lisp compiler. If this variable is a number, it specifies ;;; a "reasonable" number of top-level forms which can be compiled ;;; and write-compiled-code-file will try to break up the input ;;; code automagically. (define *magic-chunk-size* '#f) ;;; This is called to write both the code file and the interface file. (define (write-compiled-code-file filename code code-quality chunk-size) (let ((phase-start-time (get-run-time)) (forms (flatten-forms code))) (dynamic-let ((*magic-chunk-size* (or chunk-size (dynamic *magic-chunk-size*))) (*code-quality* (or code-quality (dynamic *code-quality*)))) (if (or (not (dynamic *magic-chunk-size*)) (<= (the fixnum (length forms)) (the fixnum (dynamic *magic-chunk-size*)))) (write-compiled-code-file-aux filename `(begin ,@forms)) (with-compilation-unit () (write-compiled-code-file-aux filename `(begin ,@(map (lambda (f) `(load ,f)) (write-compiled-code-file-split filename forms))) )))) (when (memq 'phase-time *printers*) (let* ((current-time (get-run-time)) (elapsed-time (- current-time phase-start-time))) (format '#t "Lisp compilation complete: ~A seconds~%" elapsed-time))) )) (define (write-compiled-code-file-split filename forms) (let ((place (filename-place filename)) (name (filename-name filename)) (type (filename-type filename)) (result '())) (do ((i 0 (1+ i))) ((null? forms)) (multiple-value-bind (head tail) (split-list forms (dynamic *magic-chunk-size*)) (let ((fname (assemble-filename place (format '#f "~a-part~a" name i) type))) (push fname result) (write-compiled-code-file-aux fname `(begin ,@head)) (setf forms tail)))) (nreverse result))) (define (flatten-forms code) (if (and (pair? code) (eq? (car code) 'begin)) (nreverse (flatten-forms-aux (cdr code) '())) (list code))) (define (flatten-forms-aux forms result) (dolist (f forms) (if (and (pair? f) (eq? (car f) 'begin)) (setf result (flatten-forms-aux (cdr f) result)) (push f result))) result) (define (write-compiled-code-file-aux filename code) (dynamic-let ((*form-to-compile* code)) (compile-file (dynamic *magic-file-to-compile*) filename))) (define-syntax (magic-form-to-compile) (dynamic *form-to-compile*)) ;;; Writing source code is good for debugging purposes, but slow. ;;; The *print-circle* and *print-shared* flags have to be set because ;;; the code printed out may contain gensyms, and this will ensure ;;; that the code can be read in again. (define (write-interpreted-code-file filename code hairy?) (dynamic-let ((*print-circle* '#t) (*print-shared* '#t)) (call-with-output-file filename (lambda (port) (if hairy? (pprint-flatten code port) (print-flatten code port)))))) ;;; This attempts to read a compiled interface for a unit. This is ;;; done whenever the unit file is newer than the source file. If ;;; imported units have changed, the load will fail and recompilation ;;; will be attempted. ;;; The caller is responsible for making sure that the interface file exists ;;; and for making sure that the interface file is up-to-date with ;;; respect to imported modules and that all the imported modules are ;;; known. ;;; These variables are assigned by the code in the dump file. (define *modules-loaded* '()) (define *modules-imported* '()) (define *defs-referenced* '()) (define *saved-cse-values* '()) (define *writer-version* '()) (define (read-binary-interface unit) (dynamic-let ((*modules-loaded* '()) (*modules-imported* '()) (*defs-referenced* '()) (*saved-cse-values* '()) (*writer-version* '())) (let ((file-date (load-more-recent-file (ucache-cifile unit) (ucache-sifile unit)))) (cond ((string=? *writer-version* *haskell-compiler-version*) (setf (ucache-idate unit) file-date) (setf (ucache-modules unit) (vector->list *modules-loaded*)) (setf (ucache-ifile-loaded unit) '#t) '#t) (else (signal-incompatible-interface-file (ucache-cifile unit)) '#f))))) (define (signal-incompatible-interface-file filename) (fatal-error 'incompatible-interface-file "File ~A~%~ was written by a different version of the Haskell system.~%~ You must remove it and recompile." filename)) (define (load-more-recent-file cfile sfile) (cond ((file-exists? cfile) (if (or (not (file-exists? sfile)) (> (file-write-date cfile) (file-write-date sfile))) (load-compiled-interface-file cfile) (load-interpreted-interface-file sfile))) ((file-exists? sfile) (load-interpreted-interface-file sfile)) (else (signal-file-not-found cfile)))) (define (load-interpreted-interface-file file) (load file) (file-write-date file)) (define (load-compiled-interface-file file) (load file) (file-write-date file)) ;;;================================================================== ;;; Dump code generator ;;;================================================================== ;;; Globals (define *dump-defs* '()) (define *dump-slot-init-code* '()) (define *dump-def-counter* 0) (define *dump-def-code-table* (make-table)) (define *cse-objects* '()) (define *cse-value-num* 0) (define *cse-object-num* '()) (define *gtype-class-index* '()) (define *context-class-index* '()) (define *gtype-tycon-index* '()) (define *gtype-list-index* '()) (define *gtype-index* '()) (define *number-vars-dumped* 0) (define-syntax (def-dump-code def) `(table-entry *dump-def-code-table* ,def)) ;;; This saves slot initialization code. (define (add-dump-init code) (push code *dump-slot-init-code*)) ;;; Here is the top-level call. (define (create-dump-code unit modules load-prelude?) (dynamic-let ((*unit* (module-unit (car modules))) (*dump-defs* '()) (*dump-slot-init-code* '()) (*dump-def-counter* 0) (*dump-def-code-table* (make-table)) (*cse-objects* '()) (*cse-object-num* *num-saved-gtyvars*) (*gtype-class-index* '()) (*context-class-index* '()) (*gtype-tycon-index* '()) (*gtype-list-index* '()) (*gtype-index* '()) (*number-vars-dumped* 0) (*number-types-dumped* 0) (*number-classes-dumped* 0)) (let ((res (create-dump-code-aux unit modules load-prelude?))) (when (memq 'dumper (dynamic *printers*)) (pprint* res)) (when (memq 'dump-stat (dynamic *printers*)) (format '#t "~&Dumped ~A definitions, ~A type objects, and ~A classes.~%" *number-vars-dumped* *number-types-dumped* *number-classes-dumped*) (format '#t "Used ~A definitions and ~A type cells.~%" *dump-def-counter* (length *cse-objects*))) res))) ;;; This assumes all modules are in the same compilation unit and that ;;; *unit* is set to that unit. ;;; imod-code establishes local bindings for all the imported modules. ;;; dmod-code establishes local bindings for all the modules defined in ;;; this compilation unit. (define (create-dump-code-aux unit modules load-prelude?) (let* ((imod-counter 0) (imod-alist '()) (explicit-imports (collect-all-imported-modules unit)) (all-imports (if load-prelude? (append (collect-prelude-modules) explicit-imports) explicit-imports)) (imod-code (map (lambda (m) (push (cons (module-name m) imod-counter) imod-alist) (incf imod-counter) `(locate-module ',(module-name m))) all-imports)) (dmod-counter 0) (dmod-alist '()) (dmod-code (map (lambda (m) (push (cons (module-name m) dmod-counter) dmod-alist) (incf dmod-counter) `(make module (unit ',(module-unit m)) (name ',(module-name m)) (type ',(module-type m)))) modules))) ;; This actually does most of the work. It dumps the module asts by ;; placing inits for each slot into *dump-slot-init-code*. A list of ;; definitions referenced is maintained in *dump-defs*. (dolist (m modules) (dump-module m (cdr (assq (module-name m) dmod-alist)))) ;; This creates the final code `(begin (setf *writer-version* ',*haskell-compiler-version*) (setf *modules-imported* (vector ,@imod-code)) (setf *modules-loaded* (vector ,@dmod-code)) ;; This sets the elements individually instead of using the vector ;; function, because the vector may be longer than ;; call-arguments-limit. (setf *defs-referenced* (make-vector ,(dynamic *dump-def-counter*))) ,@(map (lambda (d) `(setf ,(def-dump-code d) ,(make-def-init-code d imod-alist dmod-alist))) *dump-defs*) ,@(cse-init-code) ,@(dynamic *dump-slot-init-code*) ) )) ;;; Runtime support (define-syntax (lookup-imported-mod i) `(vector-ref *modules-imported* ,i)) (define-syntax (lookup-defined-mod i) `(vector-ref *modules-loaded* ,i)) (define (set-export-from-def-vector table key index) (setf (table-entry table key) (list (cons key (vector-ref *defs-referenced* index))))) (define (set-export-from-def table key def) (setf (table-entry table key) (list (cons key def)))) (define (set-symtab-from-def-vector table key index) (setf (table-entry table key) (vector-ref *defs-referenced* index))) (define (init-variable-slots var exported? toplevel? type simple? strict?) (setf (def-exported? var) exported?) (setf (var-toplevel? var) toplevel?) (setf (var-type var) type) (setf (var-simple? var) simple?) (setf (var-strict? var) strict?) var) (define (init-function-slots var exported? toplevel? type simple? strict? arity strictness opt-entry) (setf (def-exported? var) exported?) (setf (var-toplevel? var) toplevel?) (setf (var-type var) type) (setf (var-simple? var) simple?) (setf (var-strict? var) strict?) (setf (var-arity var) arity) (setf (var-strictness var) strictness) (setf (var-optimized-entry var) opt-entry) var) (define (init-method-var-slots var class default method-signature) (setf (method-var-class var) class) (setf (method-var-default var) default) (setf (method-var-method-signature var) method-signature) var) (define (init-constructor-slots con arity types signature tag alg fixity infix?) (setf (con-arity con) arity) (setf (con-types con) types) (setf (con-signature con) signature) (setf (con-tag con) tag) (setf (con-alg con) alg) (setf (con-fixity con) fixity) (setf (con-infix? con) infix?) (dotimes (i arity) (push '#f (con-slot-strict? con))) con) (define (make-new-instance algdata tyvars class context gcontext dictionary m) (make instance (algdata algdata) (tyvars tyvars) (class class) (context context) (gcontext gcontext) (dictionary dictionary) (methods m) (ok? '#t))) ;;; This computes the transitive closure of all modules available to ;;; a unit. (define (collect-all-imported-modules unit) (collect-all-modules-1 (ucache-imported-units unit) '() '())) (define (collect-all-modules-1 units mods-so-far units-seen) (cond ((null? units) mods-so-far) ((mem-string (car units) units-seen) (collect-all-modules-1 (cdr units) mods-so-far units-seen)) (else (let ((u (lookup-compilation-unit (car units)))) (collect-all-modules-1 (append (ucache-imported-units u) (cdr units)) (append (ucache-modules u) mods-so-far) (cons (ucache-ufile u) units-seen)))) )) (define (collect-prelude-modules) (let ((prelude-unit (lookup-compilation-unit *prelude-unit-filename*))) (append (ucache-modules prelude-unit) (collect-all-imported-modules prelude-unit)))) (define (def->core-name-string def) (if (con? def) (remove-con-prefix (symbol->string (def-name def))) (symbol->string (def-name def)))) ;;; This code returns the load time definition for an object. When the ;;; object is a core symbol or in a different unit, previously ;;; created definitions are returned. Otherwise, a new definition is ;;; created. (define (make-def-init-code d imod-alist dmod-alist) (declare (ignore dmod-alist)) (cond ((def-core? d) `(core-symbol ,(def->core-name-string d))) ((eq? (def-unit d) *unit*) `(create-definition/inner ',(def-module d) ',(def-name d) ',(cond ((method-var? d) 'method-var) ((var? d) 'var) ((con? d) 'con) ((synonym? d) 'synonym) ((algdata? d) 'algdata) ((class? d) 'class)))) ((is-tuple-constructor? d) `(tuple-constructor ,(tuple-constructor-arity d))) ((is-tuple-tycon? d) `(tuple-tycon ,(tuple-constructor-arity (car (algdata-constrs d))))) (else (let ((m (assq (def-module d) imod-alist))) ;; This is a bogus error message. The problem is that nothing ;; so far ensures units are closed under import/export: some ;; modules may be referenced that are accidentally in the symbol ;; table. The unif file for the current module needs to be ;; updated when this happens. (when (eq? m '#f) (fatal-error 'symbol-not-in-unit "Reference to symbol ~A in module ~A: not in compilation unit.~%" (def-name d) (def-module d))) `(table-entry (module-symbol-table (lookup-imported-mod ,(tuple-2-2 m))) ',(def-name d)))) )) ;;; Once a module has been compiled, most of its slots are useless. ;;; All we really need to save are the identifying information, ;;; symbol table, and export table. ;;; Instances also need to be dumped here instead of with class objects; ;;; this is because links can go across compilation unit boundaries. ;;; They are fixed up when pulling units out of the cache. ;;; The identifying info is stored when the module variable is bound. (define (dump-module module index) (let ((mod-exp `(lookup-defined-mod ,index)) (save-all-symbols (or (eq? (module-type module) 'standard) (eq? (module-name module) '|Prelude|)))) ;; Dump symbol table entries only for defs for which this is ;; the "home" module. (In other words, ignore imported defs.) ;; The purpose of this is to allow references from other ;; interface files to be resolved; see make-def-init-code. ;; Jcp: we need to save the complete symbol table for incremental ;; compilation to work. (let ((code '())) (table-for-each (lambda (key val) (when (or save-all-symbols (eq? (def-module val) (module-name module))) (let ((def (dump-object val))) (push (if (and (pair? def) (eq? (car def) 'vector-ref) (eq? (cadr def) '*defs-referenced*)) `(set-symtab-from-def-vector table ',key ,(caddr def)) `(setf (table-entry table ',key) ,def)) code)))) (module-symbol-table module)) (add-dump-init `(setf (module-symbol-table ,mod-exp) (let ((table (make-table))) ,@code table)))) ;; dump the fixity table - needed by the incremental compiler (when save-all-symbols (let ((code '())) (table-for-each (lambda (key val) (push `(setf (table-entry table ',key) (make-fixity ',(fixity-associativity val) ',(fixity-precedence val))) code)) (module-fixity-table module)) (add-dump-init `(setf (module-fixity-table ,mod-exp) (let ((table (make-table))) ,@code table))))) ;; Dump all export table entries. This is used by the import/export ;; phase to resolve references. (let ((code '())) (table-for-each (lambda (key val) ;; val is an a-list of (sym . def) pairs. ;; Look for shortcut to reduce size of generated code. (push (if (and (null? (cdr val)) (eq? (car (car val)) key)) (let ((def (dump-object (cdr (car val))))) (if (and (pair? def) (eq? (car def) 'vector-ref) (eq? (cadr def) '*defs-referenced*)) `(set-export-from-def-vector table ',key ,(caddr def)) `(set-export-from-def table ',key ,def))) `(setf (table-entry table ',key) ,(dump-object val))) code)) (module-export-table module)) (add-dump-init `(setf (module-export-table ,mod-exp) (let ((table (make-table))) ,@code table)))) ;; Dump the instances. (add-dump-init `(setf (module-instance-defs ,mod-exp) ,(dump-object (module-instance-defs module)))) (add-dump-init `(setf (module-default ,mod-exp) ,(dump-object (module-default module)))) (add-dump-init `(setf (module-uses-standard-prelude? ,mod-exp) ,(dump-object (module-uses-standard-prelude? module)))) )) (define (make-fixity a p) (make fixity (associativity a) (precedence p))) ;;;================================================================== ;;; Dump structure traversal ;;;================================================================== ;;; This is the general object dumper. It recognizes the basic Lisp ;;; objects and dumps them. Given an object, this generates lisp code ;;; to recreate the object at load time. (define (dump-object x) (cond ((struct? x) (dump x)) ((or (symbol? x) (null? x)) ;; Symbols and lists must be quoted. `',x) ((or (number? x) (eq? x '#t) (eq? x '#f) (string? x) ; This makes dumped strings immutable. (char? x)) ;; These objects are self-evaluating. x) ((list? x) ;; True lists `(list ,@(map (function dump-object) x))) ((pair? x) `(cons ,(dump-object (car x)) ,(dump-object (cdr x)))) ((vector? x) `(vector ,@(map (function dump-object) (vector->list x)))) ((table? x) `(list->table ,@(dump-object (table->list x)))) (else (error "Don't know how to dump ~A." x)))) ;;; *** Should install the walker in the type descriptor. (define-walker dump) (define (dump x) (call-walker dump x)) ;;;================================================================== ;;; Dumpers for defs ;;;================================================================== ;;; All walkers for def structures should call this macro. The body ;;; is invoked only if the def belongs to the current compilation unit ;;; and hasn't already been traversed. Within the body, the ;;; variable "v" is bound to a form that will evaluate to the ;;; corresponding def structure at run time. This is also ;;; the return value from the macro. (define-local-syntax (with-new-def (v d stat-var) . body) (let ((temp (gensym)) (expvar (gensym))) `(let ((,temp ,d) (,expvar '#f)) (if (not (def-dump-code ,temp)) (begin (cond ((not (def-core? ,temp)) (setf ,expvar (list 'vector-ref '*defs-referenced* (dynamic *dump-def-counter*))) (incf (dynamic *dump-def-counter*)) (push ,temp *dump-defs*)) (else (setf ,expvar (make-core-symbol-name (def->core-name-string ,temp))))) (setf (def-dump-code ,temp) ,expvar) (when (eq? (def-unit ,temp) *unit*) (incf (dynamic ,stat-var)) (let ((,v ,expvar)) ,@body)) ,expvar) (def-dump-code ,temp))))) ;;; This macro is used to save the value of a structure slot in the ;;; initforms of the dump. (define-local-syntax (dump-def-slots obj-var type dexp slots) `(add-dump-init (list 'update-slots ',type ,dexp ,@(map (lambda (s) `(list ',s (dump-object (struct-slot ',type ',s ,obj-var)))) slots))) ) (define-walker-method dump var (var) (dump-var/n var)) (define (dump-var/n var) (with-new-def (dexp var *number-vars-dumped*) (do-dump-var dexp var '#f))) (define (do-dump-var dexp var method-var?) (let ((code '()) (exported? (def-exported? var)) (toplevel? (var-toplevel? var)) (type (var-type var)) (simple? (var-simple? var)) (strict? (var-strict? var)) (arity (var-arity var)) (strictness (var-strictness var)) (opt-entry (var-optimized-entry var)) (complexity (var-complexity var)) (fixity (var-fixity var)) (value (var-value var)) (inline-value (var-inline-value var)) (sel? (var-selector-fn? var))) ;; Some slots are useless for vars that don't name functions. (if (eqv? arity 0) (push `(init-variable-slots var ',exported? ',toplevel? ,(dump-object type) ',simple? ',strict?) code) (push `(init-function-slots var ',exported? ',toplevel? ,(dump-object type) ',simple? ',strict? ',arity ,(dump-strictness strictness) ',opt-entry) code)) ;; These slots rarely need to be tweaked from the default. (when sel? (push `(setf (var-selector-fn? var) '#t) code)) (when complexity (push `(setf (var-complexity var) ,complexity) code)) (when fixity (push `(setf (var-fixity var) ,(dump-object fixity)) code)) ;; Save values of simple variables to permit inlining. ;; Save values of structured constants to permit folding of flic-sel ;; operations -- this is necessary to optimize dictionary lookups. (when (or simple? sel? (and value (is-type? 'flic-app value) (structured-constant-app? (flic-app-fn value) (flic-app-args value)))) (push `(setf (var-value var) ,(dump-flic-top value)) code)) (when inline-value (push `(setf (var-inline-value var) ,(dump-flic-top inline-value)) code)) ;; Save extra stuff for method vars (when method-var? (push `(init-method-var-slots var ,(dump-object (method-var-class var)) ,(dump-object (method-var-default var)) ,(dump-object (method-var-method-signature var))) code)) ;; Push the whole mess onto the init code. (add-dump-init `(let ((var ,dexp)) ,@(nreverse code))))) (define-walker-method dump method-var (var) (dump-method-var/n var)) (define (dump-method-var/n var) (with-new-def (dexp var *number-vars-dumped*) (do-dump-var dexp var '#t))) (define-walker-method dump con (con) (dump-con/n con)) (define (dump-con/n con) (with-new-def (dexp con *number-types-dumped*) (add-dump-init `(let ((con (init-constructor-slots ,dexp ,(con-arity con) ,(dump-object (con-types con)) ,(dump-object (con-signature con)) ,(con-tag con) ,(dump-object (con-alg con)) ,(dump-object (con-fixity con)) ',(con-infix? con)))) ,@(if (memq '#t (con-slot-strict? con)) `((setf (con-slot-strict? con) ',(con-slot-strict? con))) '()) ,@(if (eq? (con-lisp-fns con) '()) '() `((setf (con-lisp-fns con) ',(con-lisp-fns con)))) con)))) ;;; *** Could define similar init functions for other defs instead ;;; *** of setting slots inline, but I'm lazy and they don't show up ;;; *** nearly as often as the others. (define-walker-method dump algdata (alg) (dump-algdata/n alg)) (define (dump-algdata/n alg) (with-new-def (dexp alg *number-types-dumped*) (dump-def-slots alg algdata dexp (arity n-constr constrs context tyvars signature enum? tuple? real-tuple? implemented-by-lisp?)))) (define-walker-method dump synonym (syn) (dump-synonym/n syn)) (define (dump-synonym/n syn) (with-new-def (dexp syn *number-types-dumped*) (dump-def-slots syn synonym dexp (arity args body)))) (define-walker-method dump class (class) (dump-class/n class)) (define (dump-class/n class) (with-new-def (dexp class *number-classes-dumped*) (dump-def-slots class class dexp (super super* tyvar method-vars selectors kind n-methods dict-size)))) ;;;================================================================== ;;; Dumpers for non-def AST structs ;;;================================================================== ;;; This section contains dumpers to handle type-related structs that ;;; are referenced by the various def guys. (define-walker-method dump instance (o) (if (not (instance-ok? o)) (error "Attempt to dump instance that's not ok!")) `(make-new-instance ,(dump-object (instance-algdata o)) ,(dump-object (instance-tyvars o)) ,(dump-object (instance-class o)) ,(dump-object (instance-context o)) ,(dump-object (instance-gcontext o)) ,(dump-object (instance-dictionary o)) ,(dump-object (instance-methods o)))) (define-walker-method dump gtype (o) (dump-gtype/cse o)) (define-walker-method dump fixity (o) `(**fixity ',(fixity-associativity o) ',(fixity-precedence o))) (define-walker-method dump tyvar (o) `(**tyvar ',(tyvar-name o))) (define-walker-method dump class-ref (o) `(**class/def ,(dump-object (class-ref-class o)))) (define-walker-method dump context (o) `(**context ,(dump-object (context-class o)) ,(dump-object (context-tyvar o)))) (define-walker-method dump tycon (o) `(**tycon/def ,(dump-object (tycon-def o)) ,(dump-object (tycon-args o)))) (define-walker-method dump default-decl (o) `(make default-decl (types ,(dump-object (default-decl-types o))))) (define-walker-method dump signature (o) `(make signature (context ,(dump-object (signature-context o))) (type ,(dump-object (signature-type o))))) ;;; All ntyvars should be instantiated at this point ; (define-walker-method dump ntyvar (o) ; (dump-object (prune o)))