;;; compile-file.lisp ;;; ;;; Copyright (C) 2004-2006 Peter Graves ;;; $Id$ ;;; ;;; This program is free software; you can redistribute it and/or ;;; modify it under the terms of the GNU General Public License ;;; as published by the Free Software Foundation; either version 2 ;;; of the License, or (at your option) any later version. ;;; ;;; This program is distributed in the hope that it will be useful, ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;;; GNU General Public License for more details. ;;; ;;; You should have received a copy of the GNU General Public License ;;; along with this program; if not, write to the Free Software ;;; Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ;;; ;;; As a special exception, the copyright holders of this library give you ;;; permission to link this library with independent modules to produce an ;;; executable, regardless of the license terms of these independent ;;; modules, and to copy and distribute the resulting executable under ;;; terms of your choice, provided that you also meet, for each linked ;;; independent module, the terms and conditions of the license of that ;;; module. An independent module is a module which is not derived from ;;; or based on this library. If you modify this library, you may extend ;;; this exception to your version of the library, but you are not ;;; obligated to do so. If you do not wish to do so, delete this ;;; exception statement from your version. (in-package #:system) (require "COMPILER-PASS2") (export 'compile-file-if-needed) (defvar *fbound-names*) (defvar *class-number*) (defvar *output-file-pathname*) (defvar *toplevel-functions*) (defvar *toplevel-macros*) (defvar *toplevel-exports*) (defvar *toplevel-setf-expanders*) (defvar *toplevel-setf-functions*) (defun base-classname (&optional (output-file-pathname *output-file-pathname*)) (sanitize-class-name (pathname-name output-file-pathname))) (defun fasl-loader-classname (&optional (output-file-pathname *output-file-pathname*)) (%format nil "~A_0" (base-classname output-file-pathname))) (declaim (ftype (function (t) t) compute-classfile)) (defun compute-classfile (n &optional (output-file-pathname *output-file-pathname*)) "Computes the pathname of the class file associated with number `n'." (let ((name (sanitize-class-name (%format nil "~A_~D" (pathname-name output-file-pathname) n)))) (merge-pathnames (make-pathname :name name :type *compile-file-class-extension*) output-file-pathname))) (defun sanitize-class-name (name) (let ((name (copy-seq name))) (dotimes (i (length name)) (declare (type fixnum i)) (when (or (char= (char name i) #\-) (char= (char name i) #\.) (char= (char name i) #\Space)) (setf (char name i) #\_))) name)) (declaim (ftype (function () t) next-classfile)) (defun next-classfile () (compute-classfile (incf *class-number*))) (defmacro report-error (&rest forms) `(handler-case (progn ,@forms) (compiler-unsupported-feature-error (condition) (fresh-line) (%format t "; UNSUPPORTED-FEATURE: ~A~%" condition) (values nil condition)))) ;; Dummy function. Should never be called. (defun dummy (&rest ignored) (declare (ignore ignored)) (assert nil)) ;;; ??? rename to something shorter? (defparameter *compiler-diagnostic* nil "The stream to emit compiler diagnostic messages to, or nil to muffle output.") (export '*compiler-diagnostic*) (defun diag (format &rest args) (apply #'cl:format *compiler-diagnostic* (cl:concatenate 'string "~&SYSTEM::*COMPILER-DIAGNOSTIC* " format "~&") (when args args))) (declaim (ftype (function (t) t) verify-load)) (defun verify-load (classfile &key (force nil)) "Return whether the file at the path denoted by CLASSFILE is a loadable JVM artifact." (declare (ignore force)) (unless classfile (diag "Nil classfile argument passed to verify-load.") (return-from verify-load nil)) (with-open-file (cf classfile :direction :input) (when (= 0 (file-length cf)) ;;; TODO hook into a real ABCL compiler condition hierarchy (diag "Internal compiler error detected: Fasl contains ~ zero-length jvm classfile corresponding to ~A." classfile) (return-from verify-load nil))) ;; ### FIXME ;; The section below can't work, because we have ;; circular references between classes of outer- and innerscoped ;; functions. We need the class loader to resolve these circular ;; references for us. Our FASL class loader does exactly that, ;; so we need a class loader here which knows how to find ;; all the .cls files related to the current scope being loaded. #+nil (when (or force (> *safety* *speed*)) (diag "Testing compiled bytecode by loading classfile into JVM.") (let ((*load-truename* *output-file-pathname*)) ;; load-compiled-function used to be wrapped via report-error (return-from verify-load (load-compiled-function classfile)))) t) (declaim (ftype (function (t) t) note-toplevel-form)) (defun note-toplevel-form (form) (when *compile-print* (fresh-line) (princ "; ") (let ((*print-length* 2) (*print-level* 2) (*print-pretty* nil)) (prin1 form)) (terpri))) (defun output-form (form) (if *binary-fasls* (push form *forms-for-output*) (progn (dump-form form *fasl-stream*) (%stream-terpri *fasl-stream*)))) (defun finalize-fasl-output () (when *binary-fasls* (let ((*package* (find-package :keyword)) (*double-colon-package-separators* T)) (dump-form (convert-toplevel-form (list* 'PROGN (nreverse *forms-for-output*)) t) *fasl-stream*)) (%stream-terpri *fasl-stream*))) (declaim (ftype (function (t) t) simple-toplevel-form-p)) (defun simple-toplevel-form-p (form) "Returns NIL if the form is too complex to become an interpreted toplevel form, non-NIL if it is 'simple enough'." (and (consp form) (every #'(lambda (arg) (or (and (atom arg) (not (and (symbolp arg) (symbol-macro-p arg)))) (and (consp arg) (eq 'QUOTE (car arg))))) (cdr form)))) (declaim (ftype (function (t t) t) convert-toplevel-form)) (defun convert-toplevel-form (form declare-inline) (when (or (simple-toplevel-form-p form) (and (eq (car form) 'SETQ) ;; for SETQ, look at the evaluated part (simple-toplevel-form-p (third form)))) ;; single form with simple or constant arguments ;; Without this exception, toplevel function calls ;; will be compiled into lambdas which get compiled to ;; compiled-functions. Those need to be loaded. ;; Conclusion: Top level interpreting the function call ;; and its arguments may be (and should be) more efficient. (return-from convert-toplevel-form (precompiler:precompile-form form nil *compile-file-environment*))) (let* ((toplevel-form (third form)) (expr `(lambda () ,form)) (saved-class-number *class-number*) (classfile (next-classfile)) (result (with-open-file (f classfile :direction :output :element-type '(unsigned-byte 8) :if-exists :supersede) (report-error (jvm:compile-defun nil expr *compile-file-environment* classfile f declare-inline)))) (compiled-function (verify-load classfile))) (declare (ignore toplevel-form result)) (progn #+nil (when (> *debug* 0) ;; TODO (annotate form toplevel-form classfile compiled-function fasl-class-number) ;;; ??? define an API by perhaps exporting these symbols? (setf (getf form 'form-source) toplevel-form (getf form 'classfile) classfile (getf form 'compiled-function) compiled-function (getf form 'class-number) saved-class-number)) (setf form (if compiled-function `(funcall (sys::get-fasl-function *fasl-loader* ,saved-class-number)) (precompiler:precompile-form form nil *compile-file-environment*)))))) (declaim (ftype (function (t stream t) t) process-progn)) (defun process-progn (forms stream compile-time-too) (dolist (form forms) (process-toplevel-form form stream compile-time-too)) nil) (declaim (ftype (function (t t t) t) process-toplevel-form)) (defun precompile-toplevel-form (form stream compile-time-too) (declare (ignore stream)) (let ((form (precompiler:precompile-form form nil *compile-file-environment*))) (when compile-time-too (eval form)) form)) (defun process-toplevel-macrolet (form stream compile-time-too) (let ((*compile-file-environment* (make-environment *compile-file-environment*))) (dolist (definition (cadr form)) (environment-add-macro-definition *compile-file-environment* (car definition) (make-macro (car definition) (make-macro-expander definition)))) (dolist (body-form (cddr form)) (process-toplevel-form body-form stream compile-time-too))) nil) (declaim (ftype (function (t t t) t) process-toplevel-defconstant)) (defun process-toplevel-defconstant (form stream compile-time-too) (declare (ignore stream compile-time-too)) ;; "If a DEFCONSTANT form appears as a top level form, the compiler ;; must recognize that [the] name names a constant variable. An ;; implementation may choose to evaluate the value-form at compile ;; time, load time, or both. Therefore, users must ensure that the ;; initial-value can be evaluated at compile time (regardless of ;; whether or not references to name appear in the file) and that ;; it always evaluates to the same value." (note-toplevel-form form) (eval form) ;;; emit make-array when initial-value is a specialized vector (let ((initial-value (third form))) (when (and (atom initial-value) (arrayp initial-value) (= (length (array-dimensions initial-value)) 1) (not (eq (array-element-type initial-value) t))) (setf (third form) `(common-lisp:make-array ',(array-dimensions initial-value) :element-type ',(array-element-type initial-value) :initial-contents ',(coerce initial-value 'list))))) `(progn (sys:put ',(second form) 'sys::source (cl:cons '(,(second form) ,(namestring *source*) ,*source-position*) (cl:get ',(second form) 'sys::source nil))) ,form)) (declaim (ftype (function (t t t) t) process-toplevel-quote)) (defun process-toplevel-quote (form stream compile-time-too) (declare (ignore stream)) (when compile-time-too (eval form)) nil) (declaim (ftype (function (t t t) t) process-toplevel-import)) (defun process-toplevel-import (form stream compile-time-too) (declare (ignore stream)) (let ((form (precompiler:precompile-form form nil *compile-file-environment*))) (let ((*package* +keyword-package+)) (output-form form)) (when compile-time-too (eval form))) nil) (declaim (ftype (function (t t t) t) process-toplevel-export)) (defun process-toplevel-export (form stream compile-time-too) (when (and (listp (second form)) (eq (car (second form)) 'QUOTE)) ;; constant export list (let ((sym-or-syms (second (second form)))) (setf *toplevel-exports* (append *toplevel-exports* (if (listp sym-or-syms) sym-or-syms (list sym-or-syms)))))) (precompile-toplevel-form form stream compile-time-too)) (declaim (ftype (function (t t t) t) process-record-source-information)) (defun process-record-source-information (form stream compile-time-too) (declare (ignore stream compile-time-too)) (let* ((name (second form)) (type (third form))) (when (quoted-form-p name) (setq name (second name))) (when (quoted-form-p type) (setq type (second type))) (let ((sym (if (consp name) (second name) name))) `(sys:put ',sym 'sys::source (cl:cons '(,type ,(namestring *source*) ,*source-position*) (cl:get ',sym 'sys::source nil)))))) (declaim (ftype (function (t t t) t) process-toplevel-mop.ensure-method)) (defun process-toplevel-mop.ensure-method (form stream compile-time-too) (declare (ignore stream)) (flet ((convert-ensure-method (form key) (let* ((tail (cddr form)) (function-form (getf tail key))) (when (and function-form (consp function-form) (eq (%car function-form) 'FUNCTION)) (let ((lambda-expression (cadr function-form))) (jvm::with-saved-compiler-policy (let* ((saved-class-number *class-number*) (classfile (next-classfile)) (result (with-open-file (f classfile :direction :output :element-type '(unsigned-byte 8) :if-exists :supersede) (report-error (jvm:compile-defun nil lambda-expression *compile-file-environment* classfile f nil)))) (compiled-function (verify-load classfile))) (declare (ignore result)) (cond (compiled-function (setf (getf tail key) `(sys::get-fasl-function *fasl-loader* ,saved-class-number))) (t ;; FIXME This should be a warning or error of some sort... (format *error-output* "; Unable to compile method~%")))))))))) (when compile-time-too (let* ((copy-form (copy-tree form)) ;; ### Ideally, the precompiler would leave the forms alone ;; and copy them where required, instead of forcing us to ;; do a deep copy in advance (precompiled-form (precompiler:precompile-form copy-form nil *compile-file-environment*))) (eval precompiled-form))) (convert-ensure-method form :function) (convert-ensure-method form :fast-function)) (precompiler:precompile-form form nil *compile-file-environment*)) (declaim (ftype (function (t t t) t) process-toplevel-defvar/defparameter)) (defun process-toplevel-defvar/defparameter (form stream compile-time-too) (declare (ignore stream)) (note-toplevel-form form) (if compile-time-too (eval form) ;; "If a DEFVAR or DEFPARAMETER form appears as a top level form, ;; the compiler must recognize that the name has been proclaimed ;; special. However, it must neither evaluate the initial-value ;; form nor assign the dynamic variable named NAME at compile ;; time." (let ((name (second form))) (%defvar name))) (let ((name (second form)) (initial-value (third form))) ;;; emit make-array when initial-value is a specialized vector (when (and (atom initial-value) (arrayp initial-value) (= (length (array-dimensions initial-value)) 1) (not (eq (array-element-type initial-value) t))) (setf (third form) `(common-lisp:make-array ',(array-dimensions initial-value) :element-type ',(array-element-type initial-value) :initial-contents ',(coerce initial-value 'list)))) `(progn (sys:put ',name 'sys::source (cl:cons (list :variable ,(namestring *source*) ,*source-position*) (cl:get ',name 'sys::source nil))) ,form))) (declaim (ftype (function (t t t) t) process-toplevel-defpackage/in-package)) (defun process-toplevel-defpackage/in-package (form stream compile-time-too) (declare (ignore stream compile-time-too)) (note-toplevel-form form) (let ((defpackage-name (and (eq (car form) 'defpackage) (intern (string (second form)) :keyword))) ) (setf form (precompiler:precompile-form form nil *compile-file-environment*)) (eval form) ;; Force package prefix to be used when dumping form. (let ((*package* +keyword-package+)) (output-form form)) ;; a bit ugly here. Since we precompile, and added ;; record-source-information we need to know where it is. ;; The defpackage is at top, so we know where the name is (though ;; it is a string by now) (if it is a defpackage) (if defpackage-name `(sys:put ,defpackage-name 'sys::source (cl:cons '(:package ,(namestring *source*) ,*source-position*) (cl:get ,defpackage-name 'sys::source nil))) nil))) (declaim (ftype (function (t t t) t) process-toplevel-declare)) (defun process-toplevel-declare (form stream compile-time-too) (declare (ignore stream compile-time-too)) (compiler-style-warn "Misplaced declaration: ~S" form) nil) (declaim (ftype (function (t t t) t) process-toplevel-progn)) (defun process-toplevel-progn (form stream compile-time-too) (process-progn (cdr form) stream compile-time-too) nil) (declaim (ftype (function (t t t) t) process-toplevel-deftype)) (defun process-toplevel-deftype (form stream compile-time-too) (declare (ignore stream compile-time-too)) (note-toplevel-form form) (eval form) `(progn (sys:put ',(second form) 'sys::source (cl:cons '(,(second form) ,(namestring *source*) ,*source-position*) (cl:get ',(second form) 'sys::source nil))) ,form)) (declaim (ftype (function (t t t) t) process-toplevel-eval-when)) (defun process-toplevel-eval-when (form stream compile-time-too) (flet ((parse-eval-when-situations (situations) "Parse an EVAL-WHEN situations list, returning three flags, (VALUES COMPILE-TOPLEVEL LOAD-TOPLEVEL EXECUTE), indicating the types of situations present in the list." ; Adapted from SBCL. (when (or (not (listp situations)) (set-difference situations '(:compile-toplevel compile :load-toplevel load :execute eval))) (error "Bad EVAL-WHEN situation list: ~S." situations)) (values (intersection '(:compile-toplevel compile) situations) (intersection '(:load-toplevel load) situations) (intersection '(:execute eval) situations)))) (multiple-value-bind (ct lt e) (parse-eval-when-situations (cadr form)) (let ((new-compile-time-too (or ct (and compile-time-too e))) (body (cddr form))) (if lt (process-progn body stream new-compile-time-too) (when new-compile-time-too (eval `(progn ,@body))))))) nil) (declaim (ftype (function (t t t) t) process-toplevel-defmethod/defgeneric)) (defun process-toplevel-defmethod/defgeneric (form stream compile-time-too) (note-toplevel-form form) (note-name-defined (second form)) (push (second form) *toplevel-functions*) (when (and (consp (second form)) (eq 'setf (first (second form)))) (push (second (second form)) *toplevel-setf-functions*)) (let ((*compile-print* nil)) (process-toplevel-form (macroexpand-1 form *compile-file-environment*) stream compile-time-too)) (let* ((sym (if (consp (second form)) (second (second form)) (second form)))) (when (eq (car form) 'defgeneric) `(progn (sys:put ',sym 'sys::source (cl:cons '((:generic-function ,(second form)) ,(namestring *source*) ,*source-position*) (cl:get ',sym 'sys::source nil))) ,@(loop for method-form in (cdddr form) when (eq (car method-form) :method) collect (multiple-value-bind (function-name qualifiers lambda-list specializers documentation declarations body) (mop::parse-defmethod `(,(second form) ,@(rest method-form))) ;;; FIXME: style points for refactoring double backquote to "normal" form `(sys:put ',sym 'sys::source (cl:cons `((:method ,',sym ,',qualifiers ,',specializers) ,,(namestring *source*) ,,*source-position*) (cl:get ',sym 'sys::source nil))))))))) (declaim (ftype (function (t t t) t) process-toplevel-locally)) (defun process-toplevel-locally (form stream compile-time-too) (jvm::with-saved-compiler-policy (multiple-value-bind (forms decls) (parse-body (cdr form) nil) (process-optimization-declarations decls) (let* ((jvm::*visible-variables* jvm::*visible-variables*) (specials (jvm::process-declarations-for-vars (cdr form) nil nil))) (dolist (special specials) (push special jvm::*visible-variables*)) (process-progn forms stream compile-time-too)))) nil) (declaim (ftype (function (t t t) t) process-toplevel-defmacro)) (defun process-toplevel-defmacro (form stream compile-time-too) (declare (ignore stream compile-time-too)) (note-toplevel-form form) (let ((name (second form))) (eval form) (push name *toplevel-macros*) (let* ((expr (function-lambda-expression (macro-function name))) (saved-class-number *class-number*) (classfile (next-classfile))) (with-open-file (f classfile :direction :output :element-type '(unsigned-byte 8) :if-exists :supersede) (ignore-errors (jvm:compile-defun nil expr *compile-file-environment* classfile f nil))) (when (null (verify-load classfile)) ;; FIXME error or warning (format *error-output* "; Unable to compile macro ~A~%" name) (return-from process-toplevel-defmacro form)) (if (special-operator-p name) `(sys:put ',name 'macroexpand-macro (sys:make-macro ',name (sys::get-fasl-function *fasl-loader* ,saved-class-number))) `(progn (sys:put ',name 'sys::source (cl:cons '(:macro ,(namestring *source*) ,*source-position*) (cl:get ',name 'sys::source nil))) (sys:fset ',name (sys:make-macro ',name (sys::get-fasl-function *fasl-loader* ,saved-class-number)) ,*source-position* ',(third form) ,(%documentation name 'cl:function))))))) (declaim (ftype (function (t t t) t) process-toplevel-defun)) (defun process-toplevel-defun (form stream compile-time-too) (declare (ignore stream)) (note-toplevel-form form) (let* ((name (second form)) (block-name (fdefinition-block-name name)) (lambda-list (third form)) (body (nthcdr 3 form))) (jvm::with-saved-compiler-policy (multiple-value-bind (body decls doc) (parse-body body) (let* ((expr `(lambda ,lambda-list ,@decls (block ,block-name ,@body))) (saved-class-number *class-number*) (classfile (next-classfile)) (internal-compiler-errors nil) (result (with-open-file (f classfile :direction :output :element-type '(unsigned-byte 8) :if-exists :supersede) (handler-bind ((internal-compiler-error #'(lambda (e) (push e internal-compiler-errors) (continue)))) (report-error (jvm:compile-defun name expr *compile-file-environment* classfile f nil))))) (compiled-function (if (not internal-compiler-errors) (verify-load classfile) nil))) (declare (ignore result)) (cond ((and (not internal-compiler-errors) compiled-function) (when compile-time-too (eval form)) (let ((sym (if (consp name) (second name) name))) (setf form `(progn (sys:put ',sym 'sys::source (cl:cons '((:function ,name) ,(namestring *source*) ,*source-position*) (cl:get ',sym 'sys::source nil))) (sys:fset ',name (sys::get-fasl-function *fasl-loader* ,saved-class-number) ,*source-position* ',lambda-list ,doc))))) (t (compiler-warn "Unable to compile function ~A. Using interpreted form instead.~%" name) (when internal-compiler-errors (dolist (e internal-compiler-errors) (format *error-output* "; ~A~%" e))) (let ((precompiled-function (precompiler:precompile-form expr nil *compile-file-environment*))) (setf form `(sys:fset ',name ,precompiled-function ,*source-position* ',lambda-list ,doc))) (when compile-time-too (eval form))))) (when (and (symbolp name) (eq (get name '%inline) 'INLINE)) ;; FIXME Need to support SETF functions too! (setf (inline-expansion name) (jvm::generate-inline-expansion block-name lambda-list (append decls body))) (output-form `(cl:setf (inline-expansion ',name) ',(inline-expansion name)))))) (push name jvm::*functions-defined-in-current-file*) (note-name-defined name) (push name *toplevel-functions*) (when (and (consp name) (or (eq 'setf (first name)) (eq 'cl:setf (first name)))) (push (second name) *toplevel-setf-functions*)) ;; If NAME is not fbound, provide a dummy definition so that ;; getSymbolFunctionOrDie() will succeed when we try to verify that ;; functions defined later in the same file can be loaded correctly. (unless (fboundp name) (setf (fdefinition name) #'dummy) (push name *fbound-names*))) form) ;; toplevel handlers ;; each toplevel handler takes a form and stream as input (defun install-toplevel-handler (symbol handler) (setf (get symbol 'toplevel-handler) handler)) (dolist (pair '((COMPILER-DEFSTRUCT precompile-toplevel-form) (DECLARE process-toplevel-declare) (DEFCONSTANT process-toplevel-defconstant) (DEFGENERIC process-toplevel-defmethod/defgeneric) (DEFMACRO process-toplevel-defmacro) (DEFMETHOD process-toplevel-defmethod/defgeneric) (DEFPACKAGE process-toplevel-defpackage/in-package) (DEFPARAMETER process-toplevel-defvar/defparameter) (DEFTYPE process-toplevel-deftype) (DEFUN process-toplevel-defun) (DEFVAR process-toplevel-defvar/defparameter) (EVAL-WHEN process-toplevel-eval-when) (EXPORT process-toplevel-export) (IMPORT process-toplevel-import) (IN-PACKAGE process-toplevel-defpackage/in-package) (LOCALLY process-toplevel-locally) (MACROLET process-toplevel-macrolet) (PROCLAIM precompile-toplevel-form) (PROGN process-toplevel-progn) (PROVIDE precompile-toplevel-form) (PUT precompile-toplevel-form) (QUOTE process-toplevel-quote) (REQUIRE precompile-toplevel-form) (SHADOW precompile-toplevel-form) (%SET-FDEFINITION precompile-toplevel-form) (MOP::ENSURE-METHOD process-toplevel-mop.ensure-method) (record-source-information-for-type process-record-source-information))) (install-toplevel-handler (car pair) (cadr pair))) (declaim (ftype (function (t stream t) t) process-toplevel-form)) (defun process-toplevel-form (form stream compile-time-too) (unless (atom form) (let* ((operator (%car form)) (handler (if (symbolp operator) (get operator 'toplevel-handler)))) (when handler (let ((out-form (funcall handler form stream compile-time-too))) (when out-form (output-form out-form))) (return-from process-toplevel-form)) (when (and (symbolp operator) (macro-function operator *compile-file-environment*)) (when (eq operator 'define-setf-expander) (push (second form) *toplevel-setf-expanders*)) (when (and (eq operator 'defsetf) (consp (third form))) ;; long form of DEFSETF (push (second form) *toplevel-setf-expanders*)) (note-toplevel-form form) ;; Note that we want MACROEXPAND-1 and not MACROEXPAND here, in ;; case the form being expanded expands into something that needs ;; special handling by PROCESS-TOPLEVEL-FORM (e.g. DEFMACRO). (let ((*compile-print* nil)) (process-toplevel-form (macroexpand-1 form *compile-file-environment*) stream compile-time-too)) (return-from process-toplevel-form)) (cond ((and (symbolp operator) (not (special-operator-p operator)) (null (cdr form))) (setf form (precompiler:precompile-form form nil *compile-file-environment*))) (t (note-toplevel-form form) (setf form (convert-toplevel-form form nil))))) (when (consp form) (output-form form))) ;; Make sure the compiled-function loader knows where ;; to load the compiled functions. Note that this trickery ;; was already used in verify-load before I used it, ;; however, binding *load-truename* isn't fully compliant, I think. (when compile-time-too (let ((*load-truename* *output-file-pathname*) (*fasl-loader* (make-fasl-class-loader (concatenate 'string "org.armedbear.lisp." (base-classname))))) (eval form)))) (defun populate-zip-fasl (output-file) (let* ((type ;; Don't use ".zip", it'll result in an extension with ;; a dot, which is rejected by NAMESTRING (%format nil "~A~A" (pathname-type output-file) "-zip")) (output-file (if (logical-pathname-p output-file) (translate-logical-pathname output-file) output-file)) (zipfile (if (find :windows *features*) (make-pathname :defaults output-file :type type) (make-pathname :defaults output-file :type type :device :unspecific))) (pathnames nil) (fasl-loader (make-pathname :defaults output-file :name (fasl-loader-classname) :type *compile-file-class-extension*))) (when (probe-file fasl-loader) (push fasl-loader pathnames)) (dotimes (i *class-number*) (let ((truename (probe-file (compute-classfile (1+ i))))) (when truename (push truename pathnames) ;;; XXX it would be better to just use the recorded number ;;; of class constants, but probing for the first at least ;;; makes this subjectively bearable. (when (probe-file (make-pathname :name (format nil "~A_0" (pathname-name truename)) :type "clc" :defaults truename)) (dolist (resource (directory (make-pathname :name (format nil "~A_*" (pathname-name truename)) :type "clc" :defaults truename))) (push resource pathnames)))))) (setf pathnames (nreverse (remove nil pathnames))) (let ((load-file (make-pathname :defaults output-file :name "__loader__" :type "_"))) (rename-file output-file load-file) (push load-file pathnames)) (zip zipfile pathnames) (dolist (pathname pathnames) (ignore-errors (delete-file pathname))) (rename-file zipfile output-file))) (defun write-fasl-prologue (stream in-package) "Write the forms that form the fasl to STREAM. The last form will use IN-PACKAGE to set the *package* to its value when COMPILE-FILE was invoked." (let ((out stream) (*package* (find-package :keyword))) ;; write header (write "; -*- Mode: Lisp -*-" :escape nil :stream out) (%stream-terpri out) (write (list 'sys:init-fasl :version *fasl-version*) :stream out) (%stream-terpri out) (write (list 'cl:setq 'sys:*source* *compile-file-truename*) :stream out) (%stream-terpri out) ;; Note: Beyond this point, you can't use DUMP-FORM, ;; because the list of uninterned symbols has been fixed now. (when *fasl-uninterned-symbols* (write (list 'cl:setq 'sys::*fasl-uninterned-symbols* (coerce (mapcar #'car (nreverse *fasl-uninterned-symbols*)) 'vector)) :stream out :length nil)) (%stream-terpri out) (when (> *class-number* 0) (write (list 'cl:setq 'sys:*fasl-loader* `(sys::make-fasl-class-loader ,(concatenate 'string "org.armedbear.lisp." (base-classname)))) :stream out)) (%stream-terpri out) (write `(in-package ,(package-name in-package)) :stream out) (%stream-terpri out))) (defvar *binary-fasls* nil) (defvar *forms-for-output* nil) (defvar *fasl-stream* nil) (defun compile-from-stream (in output-file temp-file temp-file2 extract-toplevel-funcs-and-macros functions-file macros-file exports-file setf-functions-file setf-expanders-file) (let* ((*compile-file-pathname* (make-pathname :defaults (pathname in) :version nil)) (*compile-file-truename* (make-pathname :defaults (truename in) :version nil)) (*source* *compile-file-truename*) (*class-number* 0) (namestring (namestring *compile-file-truename*)) (start (get-internal-real-time)) *fasl-uninterned-symbols* (warnings-p nil) (in-package *package*) (failure-p nil)) (when *compile-verbose* (format t "; Compiling ~A ...~%" namestring)) (with-compilation-unit () (with-open-file (out temp-file :direction :output :if-exists :supersede :external-format *fasl-external-format*) (let ((*readtable* *readtable*) (*read-default-float-format* *read-default-float-format*) (*read-base* *read-base*) (*package* *package*) (jvm::*functions-defined-in-current-file* '()) (*fbound-names* '()) (*fasl-stream* out) *forms-for-output*) (jvm::with-saved-compiler-policy (jvm::with-file-compilation (handler-bind ((style-warning #'(lambda (c) (setf warnings-p t) ;; let outer handlers do their thing (signal c) ;; prevent the next handler ;; from running: we're a ;; WARNING subclass (continue))) ((or warning compiler-error) #'(lambda (c) (declare (ignore c)) (setf warnings-p t failure-p t)))) (loop (let* ((*source-position* (file-position in)) (jvm::*source-line-number* (stream-line-number in)) (form (read in nil in)) (*compiler-error-context* form)) (when (eq form in) (return)) (handler-case (process-toplevel-form form out nil) (compiler-bytecode-length-error () ;; Following the solution propose here: ;; see https://github.com/armedbear/abcl/issues/246#issuecomment-698854437 ;; just include the offending interpreted form in the loader ;; using it instead of the compiled representation (diag "Falling back to interpreted version of top-level form longer ~ than 65535 bytes") (write (ext:macroexpand-all form *compile-file-environment*) :stream out))) ))) (finalize-fasl-output) (dolist (name *fbound-names*) (fmakunbound name))))))) (when extract-toplevel-funcs-and-macros (setf *toplevel-functions* (remove-if-not (lambda (func-name) (if (symbolp func-name) (symbol-package func-name) T)) (remove-duplicates *toplevel-functions*))) (when *toplevel-functions* (with-open-file (f-out functions-file :direction :output :if-does-not-exist :create :if-exists :supersede) (let ((*package* (find-package :keyword))) (write *toplevel-functions* :stream f-out)))) (setf *toplevel-macros* (remove-if-not (lambda (mac-name) (if (symbolp mac-name) (symbol-package mac-name) T)) (remove-duplicates *toplevel-macros*))) (when *toplevel-macros* (with-open-file (m-out macros-file :direction :output :if-does-not-exist :create :if-exists :supersede) (let ((*package* (find-package :keyword))) (write *toplevel-macros* :stream m-out)))) (setf *toplevel-exports* (remove-if-not (lambda (sym) (if (symbolp sym) (symbol-package sym) T)) (remove-duplicates *toplevel-exports*))) (when *toplevel-exports* (with-open-file (e-out exports-file :direction :output :if-does-not-exist :create :if-exists :supersede) (let ((*package* (find-package :keyword))) (write *toplevel-exports* :stream e-out)))) (setf *toplevel-setf-functions* (remove-if-not (lambda (sym) (if (symbolp sym) (symbol-package sym) T)) (remove-duplicates *toplevel-setf-functions*))) (when *toplevel-setf-functions* (with-open-file (e-out setf-functions-file :direction :output :if-does-not-exist :create :if-exists :supersede) (let ((*package* (find-package :keyword))) (write *toplevel-setf-functions* :stream e-out)))) (setf *toplevel-setf-expanders* (remove-if-not (lambda (sym) (if (symbolp sym) (symbol-package sym) T)) (remove-duplicates *toplevel-setf-expanders*))) (when *toplevel-setf-expanders* (with-open-file (e-out setf-expanders-file :direction :output :if-does-not-exist :create :if-exists :supersede) (let ((*package* (find-package :keyword))) (write *toplevel-setf-expanders* :stream e-out))))) (with-open-file (in temp-file :direction :input :external-format *fasl-external-format*) (with-open-file (out temp-file2 :direction :output :if-does-not-exist :create :if-exists :supersede :external-format *fasl-external-format*) (let ((*package* (find-package :keyword)) (*print-fasl* t) (*print-array* t) (*print-base* 10) (*print-case* :upcase) (*print-circle* nil) (*print-escape* t) (*print-gensym* t) (*print-length* nil) (*print-level* nil) (*print-lines* nil) (*print-pretty* nil) (*print-radix* nil) (*print-readably* t) (*print-right-margin* nil) (*print-structure* t) ;; make sure to write all floats with their exponent marker: ;; the dump-time default may not be the same at load-time (*read-default-float-format* nil)) ;; these values are also bound by WITH-STANDARD-IO-SYNTAX, ;; but not used by our reader/printer, so don't bind them, ;; for efficiency reasons. ;; (*read-eval* t) ;; (*read-suppress* nil) ;; (*print-miser-width* nil) ;; (*print-pprint-dispatch* (copy-pprint-dispatch nil)) ;; (*read-base* 10) ;; (*read-default-float-format* 'single-float) ;; (*readtable* (copy-readtable nil)) (write-fasl-prologue out in-package) ;; copy remaining content (loop for line = (read-line in nil :eof) while (not (eq line :eof)) do (write-line line out))))) (delete-file temp-file) (when (subtypep (type-of output-file) 'jar-pathname) (remove-zip-cache-entry output-file)) (rename-file temp-file2 output-file) (when *compile-file-zip* (populate-zip-fasl output-file)) (when *compile-verbose* (format t "~&; Wrote ~A (~A seconds)~%" (namestring output-file) (/ (- (get-internal-real-time) start) 1000.0))) (values (truename output-file) warnings-p failure-p))) (defun compile-file (input-file &key output-file ((:verbose *compile-verbose*) *compile-verbose*) ((:print *compile-print*) *compile-print*) (extract-toplevel-funcs-and-macros nil) (external-format :utf-8)) (flet ((pathname-with-type (pathname type &optional suffix) (when suffix (setq type (concatenate 'string type suffix))) (make-pathname :type type :defaults pathname))) (unless (or (and (probe-file input-file) (not (file-directory-p input-file))) (pathname-type input-file)) (let ((pathname (pathname-with-type input-file "lisp"))) (when (probe-file pathname) (setf input-file pathname)))) (setf output-file (compile-file-pathname input-file :output-file output-file)) (let* ((*output-file-pathname* output-file) (type (pathname-type output-file)) (temp-file (pathname-with-type output-file type "-tmp")) (temp-file2 (pathname-with-type output-file type "-tmp2")) (functions-file (pathname-with-type output-file "funcs")) (macros-file (pathname-with-type output-file "macs")) (exports-file (pathname-with-type output-file "exps")) (setf-functions-file (pathname-with-type output-file "setf-functions")) (setf-expanders-file (pathname-with-type output-file "setf-expanders")) *toplevel-functions* *toplevel-macros* *toplevel-exports* *toplevel-setf-functions* *toplevel-setf-expanders*) (with-open-file (in input-file :direction :input :external-format external-format) (multiple-value-bind (output-file-truename warnings-p failure-p) (compile-from-stream in output-file temp-file temp-file2 extract-toplevel-funcs-and-macros functions-file macros-file exports-file setf-functions-file setf-expanders-file) (values (truename output-file) warnings-p failure-p)))))) (defun compile-file-if-needed (input-file &rest allargs &key force-compile &allow-other-keys) (setf input-file (truename input-file)) (cond (force-compile (remf allargs :force-compile) (apply 'compile-file input-file allargs)) (t (let* ((source-write-time (file-write-date input-file)) (output-file (or (getf allargs :output-file) (compile-file-pathname input-file))) (target-write-time (and (probe-file output-file) (file-write-date output-file)))) (if (or (null target-write-time) (<= target-write-time source-write-time)) (apply #'compile-file input-file allargs) output-file))))) (provide 'compile-file)