(declaim (special *compiler-error-bailout*))
+;;; *CURRENT-FORM-NUMBER* is used in FIND-SOURCE-PATHS to compute the
+;;; form number to associate with a source path. This should be bound
+;;; to an initial value of 0 before the processing of each truly
+;;; top level form.
+(declaim (type index *current-form-number*))
+(defvar *current-form-number*)
+
;;; *SOURCE-PATHS* is a hashtable from source code forms to the path
;;; taken through the source to reach the form. This provides a way to
;;; keep track of the location of original source forms, even when
;;; macroexpansions and other arbitary permutations of the code
;;; happen. This table is initialized by calling FIND-SOURCE-PATHS on
;;; the original source.
+;;;
+;;; It is fairly useless to store symbols, characters, or fixnums in
+;;; this table, as 42 is EQ to 42 no matter where in the source it
+;;; appears. GET-SOURCE-PATH and NOTE-SOURCE-PATH functions should be
+;;; always used to access this table.
(declaim (hash-table *source-paths*))
(defvar *source-paths*)
+(declaim (inline source-form-has-path-p))
+(defun source-form-has-path-p (form)
+ (not (typep form '(or symbol fixnum character))))
+
+(defun get-source-path (form)
+ (when (source-form-has-path-p form)
+ (gethash form *source-paths*)))
+
+(defun note-source-path (form &rest arguments)
+ (when (source-form-has-path-p form)
+ (setf (gethash form *source-paths*)
+ (apply #'list* 'original-source-start *current-form-number* arguments))))
+
;;; *CURRENT-COMPONENT* is the COMPONENT structure which we link
;;; blocks into as we generate them. This just serves to glue the
;;; emitted blocks together until local call analysis and flow graph
(defvar *fun-names-in-this-file* nil)
-;;; *ALLOW-DEBUG-CATCH-TAG* controls whether we should allow the
-;;; insertion a (CATCH ...) around code to allow the debugger RETURN
-;;; command to function.
-(defvar *allow-debug-catch-tag* t)
+(defvar *post-binding-variable-lexenv* nil)
\f
;;;; namespace management utilities
(let ((fun (lexenv-find name funs :test #'equal)))
;; a declaration will trump a proclamation
(if (and fun (defined-fun-p fun))
- (eq (defined-fun-inlinep fun) :notinline)
- (eq (info :function :inlinep name) :notinline))))
+ (eq (defined-fun-inlinep fun) :notinline)
+ (eq (info :function :inlinep name) :notinline))))
+
+;; This will get redefined in PCL boot.
+(declaim (notinline update-info-for-gf))
+(defun maybe-update-info-for-gf (name)
+ (declare (ignorable name))
+ (values))
;;; Return a GLOBAL-VAR structure usable for referencing the global
;;; function NAME.
-(defun find-free-really-fun (name)
+(defun find-global-fun (name latep)
(unless (info :function :kind name)
(setf (info :function :kind name) :function)
(setf (info :function :where-from name) :assumed))
-
(let ((where (info :function :where-from name)))
(when (and (eq where :assumed)
- ;; In the ordinary target Lisp, it's silly to report
- ;; undefinedness when the function is defined in the
- ;; running Lisp. But at cross-compile time, the current
- ;; definedness of a function is irrelevant to the
- ;; definedness at runtime, which is what matters.
- #-sb-xc-host (not (fboundp name)))
+ ;; In the ordinary target Lisp, it's silly to report
+ ;; undefinedness when the function is defined in the
+ ;; running Lisp. But at cross-compile time, the current
+ ;; definedness of a function is irrelevant to the
+ ;; definedness at runtime, which is what matters.
+ #-sb-xc-host (not (fboundp name))
+ ;; LATEP is true when the user has indicated that
+ ;; late-late binding is desired by using eg. a quoted
+ ;; symbol -- in which case it makes little sense to
+ ;; complain about undefined functions.
+ (not latep))
(note-undefined-reference name :function))
(make-global-var
:kind :global-function
:%source-name name
- :type (if (or *derive-function-types*
- (eq where :declared)
- (and (member name *fun-names-in-this-file* :test #'equal)
- (not (fun-lexically-notinline-p name))))
- (info :function :type name)
- (specifier-type 'function))
+ :type (if (or (eq where :declared)
+ (and (not latep)
+ (or *derive-function-types*
+ (eq where :defined-method)
+ (and (not (fun-lexically-notinline-p name))
+ (member name *fun-names-in-this-file*
+ :test #'equal)))))
+ (progn
+ (maybe-update-info-for-gf name)
+ (info :function :type name))
+ (specifier-type 'function))
+ :defined-type (if (eq where :defined)
+ (info :function :type name)
+ *universal-type*)
:where-from where)))
-;;; Has the *FREE-FUNS* entry FREE-FUN become invalid?
+;;; Have some DEFINED-FUN-FUNCTIONALS of a *FREE-FUNS* entry become invalid?
+;;; Drop 'em.
;;;
-;;; In CMU CL, the answer was implicitly always true, so this
-;;; predicate didn't exist.
-;;;
-;;; This predicate was added to fix bug 138 in SBCL. In some obscure
-;;; circumstances, it was possible for a *FREE-FUNS* entry to contain a
-;;; DEFINED-FUN whose DEFINED-FUN-FUNCTIONAL object contained IR1
-;;; stuff (NODEs, BLOCKs...) referring to an already compiled (aka
-;;; "dead") component. When this IR1 stuff was reused in a new
-;;; component, under further obscure circumstances it could be used by
+;;; This was added to fix bug 138 in SBCL. It is possible for a *FREE-FUNS*
+;;; entry to contain a DEFINED-FUN whose DEFINED-FUN-FUNCTIONAL object
+;;; contained IR1 stuff (NODEs, BLOCKs...) referring to an already compiled
+;;; (aka "dead") component. When this IR1 stuff was reused in a new component,
+;;; under further obscure circumstances it could be used by
;;; WITH-IR1-ENVIRONMENT-FROM-NODE to generate a binding for
-;;; *CURRENT-COMPONENT*. At that point things got all confused, since
-;;; IR1 conversion was sending code to a component which had already
-;;; been compiled and would never be compiled again.
-(defun invalid-free-fun-p (free-fun)
+;;; *CURRENT-COMPONENT*. At that point things got all confused, since IR1
+;;; conversion was sending code to a component which had already been compiled
+;;; and would never be compiled again.
+;;;
+;;; Note: as of 1.0.24.41 this seems to happen only in XC, and the original
+;;; BUGS entry also makes it seem like this might not be an issue at all on
+;;; target.
+(defun clear-invalid-functionals (free-fun)
;; There might be other reasons that *FREE-FUN* entries could
;; become invalid, but the only one we've been bitten by so far
;; (sbcl-0.pre7.118) is this one:
- (and (defined-fun-p free-fun)
- (let ((functional (defined-fun-functional free-fun)))
- (or (and functional
- (eql (functional-kind functional) :deleted))
- (and (lambda-p functional)
- (or
- ;; (The main reason for this first test is to bail
- ;; out early in cases where the LAMBDA-COMPONENT
- ;; call in the second test would fail because links
- ;; it needs are uninitialized or invalid.)
- ;;
- ;; If the BIND node for this LAMBDA is null, then
- ;; according to the slot comments, the LAMBDA has
- ;; been deleted or its call has been deleted. In
- ;; that case, it seems rather questionable to reuse
- ;; it, and certainly it shouldn't be necessary to
- ;; reuse it, so we cheerfully declare it invalid.
- (null (lambda-bind functional))
- ;; If this IR1 stuff belongs to a dead component,
- ;; then we can't reuse it without getting into
- ;; bizarre confusion.
- (eql (component-info (lambda-component functional))
- :dead)))))))
+ (when (defined-fun-p free-fun)
+ (setf (defined-fun-functionals free-fun)
+ (delete-if (lambda (functional)
+ (or (eq (functional-kind functional) :deleted)
+ (when (lambda-p functional)
+ (or
+ ;; (The main reason for this first test is to bail
+ ;; out early in cases where the LAMBDA-COMPONENT
+ ;; call in the second test would fail because links
+ ;; it needs are uninitialized or invalid.)
+ ;;
+ ;; If the BIND node for this LAMBDA is null, then
+ ;; according to the slot comments, the LAMBDA has
+ ;; been deleted or its call has been deleted. In
+ ;; that case, it seems rather questionable to reuse
+ ;; it, and certainly it shouldn't be necessary to
+ ;; reuse it, so we cheerfully declare it invalid.
+ (not (lambda-bind functional))
+ ;; If this IR1 stuff belongs to a dead component,
+ ;; then we can't reuse it without getting into
+ ;; bizarre confusion.
+ (eq (component-info (lambda-component functional))
+ :dead)))))
+ (defined-fun-functionals free-fun)))
+ nil))
;;; If NAME already has a valid entry in *FREE-FUNS*, then return
;;; the value. Otherwise, make a new GLOBAL-VAR using information from
(declaim (ftype (sfunction (t string) global-var) find-free-fun))
(defun find-free-fun (name context)
(or (let ((old-free-fun (gethash name *free-funs*)))
- (and (not (invalid-free-fun-p old-free-fun))
- old-free-fun))
+ (when old-free-fun
+ (clear-invalid-functionals old-free-fun)
+ old-free-fun))
(ecase (info :function :kind name)
- ;; FIXME: The :MACRO and :SPECIAL-FORM cases could be merged.
- (:macro
- (compiler-error "The macro name ~S was found ~A." name context))
- (:special-form
- (compiler-error "The special form name ~S was found ~A."
- name
- context))
- ((:function nil)
- (check-fun-name name)
- (note-if-setf-fun-and-macro name)
- (let ((expansion (fun-name-inline-expansion name))
- (inlinep (info :function :inlinep name)))
- (setf (gethash name *free-funs*)
- (if (or expansion inlinep)
- (make-defined-fun
- :%source-name name
- :inline-expansion expansion
- :inlinep inlinep
- :where-from (info :function :where-from name)
- :type (if (eq inlinep :notinline)
- (specifier-type 'function)
- (info :function :type name)))
- (find-free-really-fun name))))))))
+ ;; FIXME: The :MACRO and :SPECIAL-FORM cases could be merged.
+ (:macro
+ (compiler-error "The macro name ~S was found ~A." name context))
+ (:special-form
+ (compiler-error "The special form name ~S was found ~A."
+ name
+ context))
+ ((:function nil)
+ (check-fun-name name)
+ (note-if-setf-fun-and-macro name)
+ (let ((expansion (fun-name-inline-expansion name))
+ (inlinep (info :function :inlinep name)))
+ (setf (gethash name *free-funs*)
+ (if (or expansion inlinep)
+ (let ((where (info :function :where-from name)))
+ (make-defined-fun
+ :%source-name name
+ :inline-expansion expansion
+ :inlinep inlinep
+ :where-from where
+ :type (if (and (eq inlinep :notinline)
+ (neq where :declared))
+ (specifier-type 'function)
+ (info :function :type name))))
+ (find-global-fun name nil))))))))
;;; Return the LEAF structure for the lexically apparent function
;;; definition of NAME.
(defun find-lexically-apparent-fun (name context)
(let ((var (lexenv-find name funs :test #'equal)))
(cond (var
- (unless (leaf-p var)
- (aver (and (consp var) (eq (car var) 'macro)))
- (compiler-error "found macro name ~S ~A" name context))
- var)
- (t
- (find-free-fun name context)))))
+ (unless (leaf-p var)
+ (aver (and (consp var) (eq (car var) 'macro)))
+ (compiler-error "found macro name ~S ~A" name context))
+ var)
+ (t
+ (find-free-fun name context)))))
+
+(defun maybe-find-free-var (name)
+ (gethash name *free-vars*))
;;; Return the LEAF node for a global variable reference to NAME. If
;;; NAME is already entered in *FREE-VARS*, then we just return the
(compiler-error "Variable name is not a symbol: ~S." name))
(or (gethash name *free-vars*)
(let ((kind (info :variable :kind name))
- (type (info :variable :type name))
- (where-from (info :variable :where-from name)))
- (when (and (eq where-from :assumed) (eq kind :global))
- (note-undefined-reference name :variable))
- (setf (gethash name *free-vars*)
- (case kind
- (:alien
- (info :variable :alien-info name))
+ (type (info :variable :type name))
+ (where-from (info :variable :where-from name)))
+ (when (eq kind :unknown)
+ (note-undefined-reference name :variable))
+ (setf (gethash name *free-vars*)
+ (case kind
+ (:alien
+ (info :variable :alien-info name))
;; FIXME: The return value in this case should really be
;; of type SB!C::LEAF. I don't feel too badly about it,
;; because the MACRO idiom is scattered throughout this
(:macro
(let ((expansion (info :variable :macro-expansion name))
(type (type-specifier (info :variable :type name))))
- `(MACRO . (the ,type ,expansion))))
- (:constant
- (let ((value (info :variable :constant-value name)))
- (make-constant :value value
- :%source-name name
- :type (ctype-of value)
- :where-from where-from)))
- (t
- (make-global-var :kind kind
- :%source-name name
- :type type
- :where-from where-from)))))))
+ `(macro . (the ,type ,expansion))))
+ (:constant
+ (let ((value (symbol-value name)))
+ ;; Override the values of standard symbols in XC,
+ ;; since we can't redefine them.
+ #+sb-xc-host
+ (when (eql (find-symbol (symbol-name name) :cl) name)
+ (multiple-value-bind (xc-value foundp)
+ (info :variable :xc-constant-value name)
+ (cond (foundp
+ (setf value xc-value))
+ ((not (eq value name))
+ (compiler-warn
+ "Using cross-compilation host's definition of ~S: ~A~%"
+ name (symbol-value name))))))
+ (find-constant value name)))
+ (t
+ (make-global-var :kind kind
+ :%source-name name
+ :type type
+ :where-from where-from)))))))
\f
;;; Grovel over CONSTANT checking for any sub-parts that need to be
;;; processed with MAKE-LOAD-FORM. We have to be careful, because
;;; CONSTANT might be circular. We also check that the constant (and
;;; any subparts) are dumpable at all.
-(eval-when (:compile-toplevel :load-toplevel :execute)
- ;; The EVAL-WHEN is necessary for #.(1+ LIST-TO-HASH-TABLE-THRESHOLD)
- ;; below. -- AL 20010227
- (def!constant list-to-hash-table-threshold 32))
-(defun maybe-emit-make-load-forms (constant)
- (let ((things-processed nil)
- (count 0))
- ;; FIXME: Does this LIST-or-HASH-TABLE messiness give much benefit?
- (declare (type (or list hash-table) things-processed)
- (type (integer 0 #.(1+ list-to-hash-table-threshold)) count)
- (inline member))
- (labels ((grovel (value)
- ;; Unless VALUE is an object which which obviously
- ;; can't contain other objects
- (unless (typep value
- '(or #-sb-xc-host unboxed-array
- symbol
- number
- character
- string))
- (etypecase things-processed
- (list
- (when (member value things-processed :test #'eq)
- (return-from grovel nil))
- (push value things-processed)
- (incf count)
- (when (> count list-to-hash-table-threshold)
- (let ((things things-processed))
- (setf things-processed
- (make-hash-table :test 'eq))
- (dolist (thing things)
- (setf (gethash thing things-processed) t)))))
- (hash-table
- (when (gethash value things-processed)
- (return-from grovel nil))
- (setf (gethash value things-processed) t)))
- (typecase value
- (cons
- (grovel (car value))
- (grovel (cdr value)))
- (simple-vector
- (dotimes (i (length value))
- (grovel (svref value i))))
- ((vector t)
- (dotimes (i (length value))
- (grovel (aref value i))))
- ((simple-array t)
- ;; Even though the (ARRAY T) branch does the exact
- ;; same thing as this branch we do this separately
- ;; so that the compiler can use faster versions of
- ;; array-total-size and row-major-aref.
- (dotimes (i (array-total-size value))
- (grovel (row-major-aref value i))))
- ((array t)
- (dotimes (i (array-total-size value))
- (grovel (row-major-aref value i))))
- (;; In the target SBCL, we can dump any instance,
- ;; but in the cross-compilation host,
- ;; %INSTANCE-FOO functions don't work on general
- ;; instances, only on STRUCTURE!OBJECTs.
- #+sb-xc-host structure!object
- #-sb-xc-host instance
- (when (emit-make-load-form value)
- (dotimes (i (%instance-length value))
- (grovel (%instance-ref value i)))))
- (t
- (compiler-error
- "Objects of type ~S can't be dumped into fasl files."
- (type-of value)))))))
- (grovel constant)))
+(defun maybe-emit-make-load-forms (constant &optional (name nil namep))
+ (let ((xset (alloc-xset)))
+ (labels ((trivialp (value)
+ (typep value
+ '(or
+ #-sb-xc-host unboxed-array
+ #+sb-xc-host (simple-array (unsigned-byte 8) (*))
+ symbol
+ number
+ character
+ string)))
+ (grovel (value)
+ ;; Unless VALUE is an object which which obviously
+ ;; can't contain other objects
+ (unless (trivialp value)
+ (if (xset-member-p value xset)
+ (return-from grovel nil)
+ (add-to-xset value xset))
+ (typecase value
+ (cons
+ (grovel (car value))
+ (grovel (cdr value)))
+ (simple-vector
+ (dotimes (i (length value))
+ (grovel (svref value i))))
+ ((vector t)
+ (dotimes (i (length value))
+ (grovel (aref value i))))
+ ((simple-array t)
+ ;; Even though the (ARRAY T) branch does the exact
+ ;; same thing as this branch we do this separately
+ ;; so that the compiler can use faster versions of
+ ;; array-total-size and row-major-aref.
+ (dotimes (i (array-total-size value))
+ (grovel (row-major-aref value i))))
+ ((array t)
+ (dotimes (i (array-total-size value))
+ (grovel (row-major-aref value i))))
+ (#+sb-xc-host structure!object
+ #-sb-xc-host instance
+ ;; In the target SBCL, we can dump any instance, but
+ ;; in the cross-compilation host, %INSTANCE-FOO
+ ;; functions don't work on general instances, only on
+ ;; STRUCTURE!OBJECTs.
+ ;;
+ ;; FIXME: What about funcallable instances with
+ ;; user-defined MAKE-LOAD-FORM methods?
+ (when (emit-make-load-form value)
+ (dotimes (i (- (%instance-length value)
+ #+sb-xc-host 0
+ #-sb-xc-host (layout-n-untagged-slots
+ (%instance-ref value 0))))
+ (grovel (%instance-ref value i)))))
+ (t
+ (compiler-error
+ "Objects of type ~S can't be dumped into fasl files."
+ (type-of value)))))))
+ ;; Dump all non-trivial named constants using the name.
+ (if (and namep (not (typep constant '(or symbol character
+ ;; FIXME: Cold init breaks if we
+ ;; try to reference FP constants
+ ;; thru their names.
+ #+sb-xc-host number
+ #-sb-xc-host fixnum))))
+ (emit-make-load-form constant name)
+ (grovel constant))))
(values))
\f
;;;; some flow-graph hacking utilities
(defun %use-ctran (node ctran)
(declare (type node node) (type ctran ctran) (inline member))
(let ((block (ctran-block ctran))
- (node-block (ctran-block (node-prev node))))
+ (node-block (ctran-block (node-prev node))))
(aver (eq (ctran-kind ctran) :block-start))
(when (block-last node-block)
(error "~S has already ended." node-block))
(error "~S is already a predecessor of ~S." node-block block))
(push node-block (block-pred block))))
+;;; Insert NEW before OLD in the flow-graph.
+(defun insert-node-before (old new)
+ (let ((prev (node-prev old))
+ (temp (make-ctran)))
+ (ensure-block-start prev)
+ (setf (ctran-next prev) nil)
+ (link-node-to-previous-ctran new prev)
+ (use-ctran new temp)
+ (link-node-to-previous-ctran old temp))
+ (values))
+
;;; This function is used to set the ctran for a node, and thus
;;; determine what receives the value.
(defun use-lvar (node lvar)
;;; The hashtables used to hold global namespace info must be
;;; reallocated elsewhere. Note also that *LEXENV* is not bound, so
;;; that local macro definitions can be introduced by enclosing code.
-(defun ir1-toplevel (form path for-value)
+(defun ir1-toplevel (form path for-value &optional (allow-instrumenting t))
(declare (list path))
(let* ((*current-path* path)
- (component (make-empty-component))
- (*current-component* component))
- (setf (component-name component) "initial component")
+ (component (make-empty-component))
+ (*current-component* component)
+ (*allow-instrumenting* allow-instrumenting))
+ (setf (component-name component) 'initial-component)
(setf (component-kind component) :initial)
(let* ((forms (if for-value `(,form) `(,form nil)))
- (res (ir1-convert-lambda-body
- forms ()
- :debug-name (debug-namify "top level form ~S" form))))
+ (res (ir1-convert-lambda-body
+ forms ()
+ :debug-name (debug-name 'top-level-form #+sb-xc-host nil #-sb-xc-host form))))
(setf (functional-entry-fun res) res
- (functional-arg-documentation res) ()
- (functional-kind res) :toplevel)
+ (functional-arg-documentation res) ()
+ (functional-kind res) :toplevel)
res)))
-;;; *CURRENT-FORM-NUMBER* is used in FIND-SOURCE-PATHS to compute the
-;;; form number to associate with a source path. This should be bound
-;;; to an initial value of 0 before the processing of each truly
-;;; top level form.
-(declaim (type index *current-form-number*))
-(defvar *current-form-number*)
-
;;; This function is called on freshly read forms to record the
;;; initial location of each form (and subform.) Form is the form to
;;; find the paths in, and TLF-NUM is the top level form number of the
(sub-find-source-paths form (list tlf-num)))
(values))
(defun sub-find-source-paths (form path)
- (unless (gethash form *source-paths*)
- (setf (gethash form *source-paths*)
- (list* 'original-source-start *current-form-number* path))
+ (unless (get-source-path form)
+ (note-source-path form path)
(incf *current-form-number*)
(let ((pos 0)
- (subform form)
- (trail form))
+ (subform form)
+ (trail form))
(declare (fixnum pos))
(macrolet ((frob ()
- '(progn
- (when (atom subform) (return))
- (let ((fm (car subform)))
- (when (consp fm)
- (sub-find-source-paths fm (cons pos path)))
- (incf pos))
- (setq subform (cdr subform))
- (when (eq subform trail) (return)))))
- (loop
- (frob)
- (frob)
- (setq trail (cdr trail)))))))
+ `(progn
+ (when (atom subform) (return))
+ (let ((fm (car subform)))
+ (cond ((consp fm)
+ ;; If it's a cons, recurse.
+ (sub-find-source-paths fm (cons pos path)))
+ ((eq 'quote fm)
+ ;; Don't look into quoted constants.
+ (return))
+ ((not (zerop pos))
+ ;; Otherwise store the containing form. It's not
+ ;; perfect, but better than nothing.
+ (note-source-path subform pos path)))
+ (incf pos))
+ (setq subform (cdr subform))
+ (when (eq subform trail) (return)))))
+ (loop
+ (frob)
+ (frob)
+ (setq trail (cdr trail)))))))
\f
;;;; IR1-CONVERT, macroexpansion and special form dispatching
(declaim (ftype (sfunction (ctran ctran (or lvar null) t) (values))
- ir1-convert))
+ ir1-convert))
(macrolet (;; Bind *COMPILER-ERROR-BAILOUT* to a function that throws
- ;; out of the body and converts a proxy form instead.
- (ir1-error-bailout ((start next result
- form
- &optional
- (proxy ``(error 'simple-program-error
- :format-control "execution of a form compiled with errors:~% ~S"
- :format-arguments (list ',,form))))
- &body body)
- (with-unique-names (skip)
- `(block ,skip
- (catch 'ir1-error-abort
- (let ((*compiler-error-bailout*
- (lambda ()
- (throw 'ir1-error-abort nil))))
- ,@body
- (return-from ,skip nil)))
- (ir1-convert ,start ,next ,result ,proxy)))))
+ ;; out of the body and converts a condition signalling form
+ ;; instead. The source form is converted to a string since it
+ ;; may contain arbitrary non-externalizable objects.
+ (ir1-error-bailout ((start next result form) &body body)
+ (with-unique-names (skip condition)
+ `(block ,skip
+ (let ((,condition (catch 'ir1-error-abort
+ (let ((*compiler-error-bailout*
+ (lambda (&optional e)
+ (throw 'ir1-error-abort e))))
+ ,@body
+ (return-from ,skip nil)))))
+ (ir1-convert ,start ,next ,result
+ (make-compiler-error-form ,condition
+ ,form)))))))
;; Translate FORM into IR1. The code is inserted as the NEXT of the
;; CTRAN START. RESULT is the LVAR which receives the value of the
;; namespace.
(defun ir1-convert (start next result form)
(ir1-error-bailout (start next result form)
- (let ((*current-path* (or (gethash form *source-paths*)
- (cons form *current-path*))))
- (if (atom form)
- (cond ((and (symbolp form) (not (keywordp form)))
- (ir1-convert-var start next result form))
- ((leaf-p form)
- (reference-leaf start next result form))
- (t
- (reference-constant start next result form)))
- (let ((opname (car form)))
- (cond ((or (symbolp opname) (leaf-p opname))
- (let ((lexical-def (if (leaf-p opname)
- opname
- (lexenv-find opname funs))))
- (typecase lexical-def
- (null
- (ir1-convert-global-functoid start next result
- form))
- (functional
- (ir1-convert-local-combination start next result
- form
- lexical-def))
- (global-var
- (ir1-convert-srctran start next result
- lexical-def form))
- (t
- (aver (and (consp lexical-def)
- (eq (car lexical-def) 'macro)))
- (ir1-convert start next result
- (careful-expand-macro (cdr lexical-def)
- form))))))
- ((or (atom opname) (not (eq (car opname) 'lambda)))
- (compiler-error "illegal function call"))
- (t
- ;; implicitly (LAMBDA ..) because the LAMBDA
- ;; expression is the CAR of an executed form
- (ir1-convert-combination start next result
- form
- (ir1-convert-lambda
- opname
- :debug-name (debug-namify
- "LAMBDA CAR ~S"
- opname)
- :allow-debug-catch-tag t))))))))
+ (let* ((*current-path* (or (get-source-path form)
+ (cons form *current-path*)))
+ (start (instrument-coverage start nil form)))
+ (cond ((atom form)
+ (cond ((and (symbolp form) (not (keywordp form)))
+ (ir1-convert-var start next result form))
+ ((leaf-p form)
+ (reference-leaf start next result form))
+ (t
+ (reference-constant start next result form))))
+ (t
+ (ir1-convert-functoid start next result form)))))
(values))
;; Generate a reference to a manifest constant, creating a new leaf
- ;; if necessary. If we are producing a fasl file, make sure that
- ;; MAKE-LOAD-FORM gets used on any parts of the constant that it
- ;; needs to be.
+ ;; if necessary.
(defun reference-constant (start next result value)
(declare (type ctran start next)
- (type (or lvar null) result)
- (inline find-constant))
- (ir1-error-bailout
- (start next result value '(error "attempt to reference undumpable constant"))
- (when (producing-fasl-file)
- (maybe-emit-make-load-forms value))
- (let* ((leaf (find-constant value))
- (res (make-ref leaf)))
- (push res (leaf-refs leaf))
- (link-node-to-previous-ctran res start)
- (use-continuation res next result)))
+ (type (or lvar null) result))
+ (ir1-error-bailout (start next result value)
+ (let* ((leaf (find-constant value))
+ (res (make-ref leaf)))
+ (push res (leaf-refs leaf))
+ (link-node-to-previous-ctran res start)
+ (use-continuation res next result)))
(values)))
;;; Add FUNCTIONAL to the COMPONENT-REANALYZE-FUNCTIONALS, unless it's
(aver (eql (lambda-component functional) *current-component*)))
(pushnew functional
- (component-reanalyze-functionals *current-component*)))
+ (component-reanalyze-functionals *current-component*)))
functional)
;;; needed. If LEAF represents a defined function which has already
;;; been converted, and is not :NOTINLINE, then reference the
;;; functional instead.
-(defun reference-leaf (start next result leaf)
+(defun reference-leaf (start next result leaf &optional (name '.anonymous.))
(declare (type ctran start next) (type (or lvar null) result) (type leaf leaf))
- (when (functional-p leaf)
- (assure-functional-live-p leaf))
+ (assure-leaf-live-p leaf)
(let* ((type (lexenv-find leaf type-restrictions))
(leaf (or (and (defined-fun-p leaf)
(not (eq (defined-fun-inlinep leaf)
:notinline))
(let ((functional (defined-fun-functional leaf)))
- (when (and functional
- (not (functional-kind functional)))
+ (when (and functional (not (functional-kind functional)))
(maybe-reanalyze-functional functional))))
(when (and (lambda-p leaf)
(memq (functional-kind leaf)
'(nil :optional)))
(maybe-reanalyze-functional leaf))
leaf))
- (ref (make-ref leaf)))
+ (ref (make-ref leaf name)))
(push ref (leaf-refs leaf))
(setf (leaf-ever-used leaf) t)
(link-node-to-previous-ctran ref start)
(defun ir1-convert-var (start next result name)
(declare (type ctran start next) (type (or lvar null) result) (symbol name))
(let ((var (or (lexenv-find name vars) (find-free-var name))))
- (etypecase var
- (leaf
- (when (lambda-var-p var)
- (let ((home (ctran-home-lambda-or-null start)))
- (when home
- (pushnew var (lambda-calls-or-closes home))))
- (when (lambda-var-ignorep var)
- ;; (ANSI's specification for the IGNORE declaration requires
- ;; that this be a STYLE-WARNING, not a full WARNING.)
- (compiler-style-warn "reading an ignored variable: ~S" name)))
- (reference-leaf start next result var))
- (cons
- (aver (eq (car var) 'MACRO))
- ;; FIXME: [Free] type declarations. -- APD, 2002-01-26
- (ir1-convert start next result (cdr var)))
- (heap-alien-info
- (ir1-convert start next result `(%heap-alien ',var)))))
+ (if (and (global-var-p var) (not result))
+ ;; KLUDGE: If the reference is dead, convert using SYMBOL-VALUE
+ ;; which is not flushable, so that unbound dead variables signal
+ ;; an error (bug 412).
+ (ir1-convert start next result
+ (if (eq (global-var-kind var) :global)
+ `(symbol-global-value ',name)
+ `(symbol-value ',name)))
+ (etypecase var
+ (leaf
+ (when (lambda-var-p var)
+ (let ((home (ctran-home-lambda-or-null start)))
+ (when home
+ (sset-adjoin var (lambda-calls-or-closes home))))
+ (when (lambda-var-ignorep var)
+ ;; (ANSI's specification for the IGNORE declaration requires
+ ;; that this be a STYLE-WARNING, not a full WARNING.)
+ #-sb-xc-host
+ (compiler-style-warn "reading an ignored variable: ~S" name)
+ ;; there's no need for us to accept ANSI's lameness when
+ ;; processing our own code, though.
+ #+sb-xc-host
+ (warn "reading an ignored variable: ~S" name)))
+ (reference-leaf start next result var name))
+ (cons
+ (aver (eq (car var) 'macro))
+ ;; FIXME: [Free] type declarations. -- APD, 2002-01-26
+ (ir1-convert start next result (cdr var)))
+ (heap-alien-info
+ (ir1-convert start next result `(%heap-alien ',var))))))
(values))
-;;; Convert anything that looks like a special form, global function
-;;; or compiler-macro call.
-(defun ir1-convert-global-functoid (start next result form)
- (declare (type ctran start next) (type (or lvar null) result) (list form))
- (let* ((fun-name (first form))
- (translator (info :function :ir1-convert fun-name))
- (cmacro-fun (sb!xc:compiler-macro-function fun-name *lexenv*)))
+;;; Find a compiler-macro for a form, taking FUNCALL into account.
+(defun find-compiler-macro (opname form)
+ (if (eq opname 'funcall)
+ (let ((fun-form (cadr form)))
+ (cond ((and (consp fun-form) (eq 'function (car fun-form)))
+ (let ((real-fun (cadr fun-form)))
+ (if (legal-fun-name-p real-fun)
+ (values (sb!xc:compiler-macro-function real-fun *lexenv*)
+ real-fun)
+ (values nil nil))))
+ ((sb!xc:constantp fun-form *lexenv*)
+ (let ((fun (constant-form-value fun-form *lexenv*)))
+ (if (legal-fun-name-p fun)
+ ;; CLHS tells us that local functions must shadow
+ ;; compiler-macro-functions, but since the call is
+ ;; through a name, we are obviously interested
+ ;; in the global function.
+ (values (sb!xc:compiler-macro-function fun nil) fun)
+ (values nil nil))))
+ (t
+ (values nil nil))))
+ (if (legal-fun-name-p opname)
+ (values (sb!xc:compiler-macro-function opname *lexenv*) opname)
+ (values nil nil))))
+
+;;; Picks of special forms and compiler-macro expansions, and hands
+;;; the rest to IR1-CONVERT-COMMON-FUNCTOID
+(defun ir1-convert-functoid (start next result form)
+ (let* ((op (car form))
+ (translator (and (symbolp op) (info :function :ir1-convert op))))
(cond (translator
- (when cmacro-fun
- (compiler-warn "ignoring compiler macro for special form"))
- (funcall translator start next result form))
- ((and cmacro-fun
- ;; gotcha: If you look up the DEFINE-COMPILER-MACRO
- ;; macro in the ANSI spec, you might think that
- ;; suppressing compiler-macro expansion when NOTINLINE
- ;; is some pre-ANSI hack. However, if you look up the
- ;; NOTINLINE declaration, you'll find that ANSI
- ;; requires this behavior after all.
- (not (eq (info :function :inlinep fun-name) :notinline)))
- (let ((res (careful-expand-macro cmacro-fun form)))
- (if (eq res form)
- (ir1-convert-global-functoid-no-cmacro
- start next result form fun-name)
- (ir1-convert start next result res))))
- (t
- (ir1-convert-global-functoid-no-cmacro start next result
- form fun-name)))))
-
-;;; Handle the case of where the call was not a compiler macro, or was
-;;; a compiler macro and passed.
-(defun ir1-convert-global-functoid-no-cmacro (start next result form fun)
+ (when (sb!xc:compiler-macro-function op *lexenv*)
+ (compiler-warn "ignoring compiler macro for special form"))
+ (funcall translator start next result form))
+ (t
+ (multiple-value-bind (cmacro-fun cmacro-fun-name)
+ (find-compiler-macro op form)
+ (if (and cmacro-fun
+ ;; CLHS 3.2.2.1.3 specifies that NOTINLINE
+ ;; suppresses compiler-macros.
+ (not (fun-lexically-notinline-p cmacro-fun-name)))
+ (let ((res (careful-expand-macro cmacro-fun form t)))
+ (cond ((eq res form)
+ (ir1-convert-common-functoid start next result form op))
+ (t
+ (unless (policy *lexenv* (zerop store-xref-data))
+ (record-call cmacro-fun-name (ctran-block start) *current-path*))
+ (ir1-convert start next result res))))
+ (ir1-convert-common-functoid start next result form op)))))))
+
+;;; Handles the "common" cases: any other forms except special forms
+;;; and compiler-macros.
+(defun ir1-convert-common-functoid (start next result form op)
+ (cond ((or (symbolp op) (leaf-p op))
+ (let ((lexical-def (if (leaf-p op) op (lexenv-find op funs))))
+ (typecase lexical-def
+ (null
+ (ir1-convert-global-functoid start next result form op))
+ (functional
+ (ir1-convert-local-combination start next result form
+ lexical-def))
+ (global-var
+ (ir1-convert-srctran start next result lexical-def form))
+ (t
+ (aver (and (consp lexical-def) (eq (car lexical-def) 'macro)))
+ (ir1-convert start next result
+ (careful-expand-macro (cdr lexical-def) form))))))
+ ((or (atom op) (not (eq (car op) 'lambda)))
+ (compiler-error "illegal function call"))
+ (t
+ ;; implicitly (LAMBDA ..) because the LAMBDA expression is
+ ;; the CAR of an executed form.
+ (ir1-convert-combination
+ start next result form
+ (ir1-convert-lambda op
+ :debug-name (debug-name 'inline-lambda op))))))
+
+;;; Convert anything that looks like a global function call.
+(defun ir1-convert-global-functoid (start next result form fun)
(declare (type ctran start next) (type (or lvar null) result)
(list form))
;; FIXME: Couldn't all the INFO calls here be converted into
- ;; standard CL functions, like MACRO-FUNCTION or something?
- ;; And what happens with lexically-defined (MACROLET) macros
- ;; here, anyway?
+ ;; standard CL functions, like MACRO-FUNCTION or something? And what
+ ;; happens with lexically-defined (MACROLET) macros here, anyway?
(ecase (info :function :kind fun)
(:macro
(ir1-convert start next result
- (careful-expand-macro (info :function :macro-function fun)
- form)))
+ (careful-expand-macro (info :function :macro-function fun)
+ form))
+ (unless (policy *lexenv* (zerop store-xref-data))
+ (record-macroexpansion fun (ctran-block start) *current-path*)))
((nil :function)
(ir1-convert-srctran start next result
- (find-free-fun fun "shouldn't happen! (no-cmacro)")
- form))))
+ (find-free-fun fun "shouldn't happen! (no-cmacro)")
+ form))))
(defun muffle-warning-or-die ()
(muffle-warning)
;;; Expand FORM using the macro whose MACRO-FUNCTION is FUN, trapping
;;; errors which occur during the macroexpansion.
-(defun careful-expand-macro (fun form)
+(defun careful-expand-macro (fun form &optional cmacro)
(let (;; a hint I (WHN) wish I'd known earlier
- (hint "(hint: For more precise location, try *BREAK-ON-SIGNALS*.)"))
+ (hint "(hint: For more precise location, try *BREAK-ON-SIGNALS*.)"))
(flet (;; Return a string to use as a prefix in error reporting,
- ;; telling something about which form caused the problem.
- (wherestring ()
- (let ((*print-pretty* nil)
- ;; We rely on the printer to abbreviate FORM.
- (*print-length* 3)
- (*print-level* 1))
- (format
- nil
- #-sb-xc-host "(in macroexpansion of ~S)"
- ;; longer message to avoid ambiguity "Was it the xc host
- ;; or the cross-compiler which encountered the problem?"
- #+sb-xc-host "(in cross-compiler macroexpansion of ~S)"
- form))))
+ ;; telling something about which form caused the problem.
+ (wherestring ()
+ (let ((*print-pretty* nil)
+ ;; We rely on the printer to abbreviate FORM.
+ (*print-length* 3)
+ (*print-level* 3))
+ (format
+ nil
+ #-sb-xc-host "(in ~A of ~S)"
+ ;; longer message to avoid ambiguity "Was it the xc host
+ ;; or the cross-compiler which encountered the problem?"
+ #+sb-xc-host "(in cross-compiler ~A of ~S)"
+ (if cmacro "compiler-macroexpansion" "macroexpansion")
+ form))))
(handler-bind ((style-warning (lambda (c)
- (compiler-style-warn
- "~@<~A~:@_~A~@:_~A~:>"
- (wherestring) hint c)
- (muffle-warning-or-die)))
- ;; KLUDGE: CMU CL in its wisdom (version 2.4.6 for
+ (compiler-style-warn
+ "~@<~A~:@_~A~@:_~A~:>"
+ (wherestring) hint c)
+ (muffle-warning-or-die)))
+ ;; KLUDGE: CMU CL in its wisdom (version 2.4.6 for
;; Debian Linux, anyway) raises a CL:WARNING
;; condition (not a CL:STYLE-WARNING) for undefined
;; symbols when converting interpreted functions,
(wherestring)
c)
(muffle-warning-or-die)))
- #-(and cmu sb-xc-host)
- (warning (lambda (c)
- (compiler-warn "~@<~A~:@_~A~@:_~A~:>"
- (wherestring) hint c)
- (muffle-warning-or-die)))
+ #-(and cmu sb-xc-host)
+ (warning (lambda (c)
+ (warn "~@<~A~:@_~A~@:_~A~:>"
+ (wherestring) hint c)
+ (muffle-warning-or-die)))
(error (lambda (c)
(compiler-error "~@<~A~:@_~A~@:_~A~:>"
(wherestring) hint c))))
;;; Convert a bunch of forms, discarding all the values except the
;;; last. If there aren't any forms, then translate a NIL.
(declaim (ftype (sfunction (ctran ctran (or lvar null) list) (values))
- ir1-convert-progn-body))
+ ir1-convert-progn-body))
(defun ir1-convert-progn-body (start next result body)
(if (endp body)
(reference-constant start next result nil)
(let ((this-start start)
- (forms body))
- (loop
- (let ((form (car forms)))
- (when (endp (cdr forms))
- (ir1-convert this-start next result form)
- (return))
- (let ((this-ctran (make-ctran)))
- (ir1-convert this-start this-ctran nil form)
- (setq this-start this-ctran
- forms (cdr forms)))))))
+ (forms body))
+ (loop
+ (let ((form (car forms)))
+ (setf this-start
+ (maybe-instrument-progn-like this-start forms form))
+ (when (endp (cdr forms))
+ (ir1-convert this-start next result form)
+ (return))
+ (let ((this-ctran (make-ctran)))
+ (ir1-convert this-start this-ctran nil form)
+ (setq this-start this-ctran
+ forms (cdr forms)))))))
(values))
+
+\f
+;;;; code coverage
+
+;;; Check the policy for whether we should generate code coverage
+;;; instrumentation. If not, just return the original START
+;;; ctran. Otherwise insert code coverage instrumentation after
+;;; START, and return the new ctran.
+(defun instrument-coverage (start mode form)
+ ;; We don't actually use FORM for anything, it's just convenient to
+ ;; have around when debugging the instrumentation.
+ (declare (ignore form))
+ (if (and (policy *lexenv* (> store-coverage-data 0))
+ *code-coverage-records*
+ *allow-instrumenting*)
+ (let ((path (source-path-original-source *current-path*)))
+ (when mode
+ (push mode path))
+ (if (member (ctran-block start)
+ (gethash path *code-coverage-blocks*))
+ ;; If this source path has already been instrumented in
+ ;; this block, don't instrument it again.
+ start
+ (let ((store
+ ;; Get an interned record cons for the path. A cons
+ ;; with the same object identity must be used for
+ ;; each instrument for the same block.
+ (or (gethash path *code-coverage-records*)
+ (setf (gethash path *code-coverage-records*)
+ (cons path +code-coverage-unmarked+))))
+ (next (make-ctran))
+ (*allow-instrumenting* nil))
+ (push (ctran-block start)
+ (gethash path *code-coverage-blocks*))
+ (let ((*allow-instrumenting* nil))
+ (ir1-convert start next nil
+ `(locally
+ (declare (optimize speed
+ (safety 0)
+ (debug 0)
+ (check-constant-modification 0)))
+ ;; We're being naughty here, and
+ ;; modifying constant data. That's ok,
+ ;; we know what we're doing.
+ (%rplacd ',store t))))
+ next)))
+ start))
+
+;;; In contexts where we don't have a source location for FORM
+;;; e.g. due to it not being a cons, but where we have a source
+;;; location for the enclosing cons, use the latter source location if
+;;; available. This works pretty well in practice, since many PROGNish
+;;; macroexpansions will just directly splice a block of forms into
+;;; some enclosing form with `(progn ,@body), thus retaining the
+;;; EQness of the conses.
+(defun maybe-instrument-progn-like (start forms form)
+ (or (when (and *allow-instrumenting*
+ (not (get-source-path form)))
+ (let ((*current-path* (get-source-path forms)))
+ (when *current-path*
+ (instrument-coverage start nil form))))
+ start))
+
+(defun record-code-coverage (info cc)
+ (setf (gethash info *code-coverage-info*) cc))
+
+(defun clear-code-coverage ()
+ (clrhash *code-coverage-info*))
+
+(defun reset-code-coverage ()
+ (maphash (lambda (info cc)
+ (declare (ignore info))
+ (dolist (cc-entry cc)
+ (setf (cdr cc-entry) +code-coverage-unmarked+)))
+ *code-coverage-info*))
+
+(defun code-coverage-record-marked (record)
+ (aver (consp record))
+ (ecase (cdr record)
+ ((#.+code-coverage-unmarked+) nil)
+ ((t) t)))
+
\f
;;;; converting combinations
+;;; Does this form look like something that we should add single-stepping
+;;; instrumentation for?
+(defun step-form-p (form)
+ (flet ((step-symbol-p (symbol)
+ (not (member (symbol-package symbol)
+ (load-time-value
+ ;; KLUDGE: packages we're not interested in
+ ;; stepping.
+ (mapcar #'find-package '(sb!c sb!int sb!impl
+ sb!kernel sb!pcl)))))))
+ (and *allow-instrumenting*
+ (policy *lexenv* (= insert-step-conditions 3))
+ (listp form)
+ (symbolp (car form))
+ (step-symbol-p (car form)))))
+
;;; Convert a function call where the function FUN is a LEAF. FORM is
;;; the source for the call. We return the COMBINATION node so that
;;; the caller can poke at it if it wants to.
(declaim (ftype (sfunction (ctran ctran (or lvar null) list leaf) combination)
- ir1-convert-combination))
+ ir1-convert-combination))
(defun ir1-convert-combination (start next result form fun)
(let ((ctran (make-ctran))
(fun-lvar (make-lvar)))
(ir1-convert start ctran fun-lvar `(the (or function symbol) ,fun))
- (ir1-convert-combination-args fun-lvar ctran next result (cdr form))))
+ (let ((combination
+ (ir1-convert-combination-args fun-lvar ctran next result
+ (cdr form))))
+ (when (step-form-p form)
+ ;; Store a string representation of the form in the
+ ;; combination node. This will let the IR2 translator know
+ ;; that we want stepper instrumentation for this node. The
+ ;; string will be stored in the debug-info by DUMP-1-LOCATION.
+ (setf (combination-step-info combination)
+ (let ((*print-pretty* t)
+ (*print-circle* t)
+ (*print-readably* nil))
+ (prin1-to-string form))))
+ combination)))
;;; Convert the arguments to a call and make the COMBINATION
;;; node. FUN-LVAR yields the function to call. ARGS is the list of
(let ((node (make-combination fun-lvar)))
(setf (lvar-dest fun-lvar) node)
(collect ((arg-lvars))
- (let ((this-start start))
- (dolist (arg args)
- (let ((this-ctran (make-ctran))
+ (let ((this-start start)
+ (forms args))
+ (dolist (arg args)
+ (setf this-start
+ (maybe-instrument-progn-like this-start forms arg))
+ (setf forms (cdr forms))
+ (let ((this-ctran (make-ctran))
(this-lvar (make-lvar node)))
- (ir1-convert this-start this-ctran this-lvar arg)
- (setq this-start this-ctran)
- (arg-lvars this-lvar)))
- (link-node-to-previous-ctran node this-start)
- (use-continuation node next result)
- (setf (combination-args node) (arg-lvars))))
+ (ir1-convert this-start this-ctran this-lvar arg)
+ (setq this-start this-ctran)
+ (arg-lvars this-lvar)))
+ (link-node-to-previous-ctran node this-start)
+ (use-continuation node next result)
+ (setf (combination-args node) (arg-lvars))))
node))
;;; Convert a call to a global function. If not :NOTINLINE, then we do
(declare (type ctran start next) (type (or lvar null) result)
(type global-var var))
(let ((inlinep (when (defined-fun-p var)
- (defined-fun-inlinep var))))
+ (defined-fun-inlinep var))))
(if (eq inlinep :notinline)
- (ir1-convert-combination start next result form var)
- (let ((transform (info :function
- :source-transform
- (leaf-source-name var))))
+ (ir1-convert-combination start next result form var)
+ (let* ((name (leaf-source-name var))
+ (transform (info :function :source-transform name)))
(if transform
(multiple-value-bind (transformed pass) (funcall transform form)
- (if pass
- (ir1-convert-maybe-predicate start next result form var)
- (ir1-convert start next result transformed)))
+ (cond (pass
+ (ir1-convert-maybe-predicate start next result form var))
+ (t
+ (unless (policy *lexenv* (zerop store-xref-data))
+ (record-call name (ctran-block start) *current-path*))
+ (ir1-convert start next result transformed))))
(ir1-convert-maybe-predicate start next result form var))))))
+;;; KLUDGE: If we insert a synthetic IF for a function with the PREDICATE
+;;; attribute, don't generate any branch coverage instrumentation for it.
+(defvar *instrument-if-for-code-coverage* t)
+
;;; If the function has the PREDICATE attribute, and the RESULT's DEST
;;; isn't an IF, then we convert (IF <form> T NIL), ensuring that a
;;; predicate always appears in a conditional context.
(type global-var var))
(let ((info (info :function :info (leaf-source-name var))))
(if (and info
- (ir1-attributep (fun-info-attributes info) predicate)
- (not (if-p (and result (lvar-dest result)))))
- (ir1-convert start next result `(if ,form t nil))
- (ir1-convert-combination-checking-type start next result form var))))
+ (ir1-attributep (fun-info-attributes info) predicate)
+ (not (if-p (and result (lvar-dest result)))))
+ (let ((*instrument-if-for-code-coverage* nil))
+ (ir1-convert start next result `(if ,form t nil)))
+ (ir1-convert-combination-checking-type start next result form var))))
;;; Actually really convert a global function call that we are allowed
;;; to early-bind.
(list form)
(type leaf var))
(let* ((node (ir1-convert-combination start next result form var))
- (fun-lvar (basic-combination-fun node))
- (type (leaf-type var)))
- (when (validate-call-type node type t)
+ (fun-lvar (basic-combination-fun node))
+ (type (leaf-type var))
+ (defined-type (leaf-defined-type var)))
+ (when (validate-call-type node type defined-type t)
(setf (lvar-%derived-type fun-lvar)
(make-single-value-type type))
(setf (lvar-reoptimize fun-lvar) nil)))
(defun ir1-convert-local-combination (start next result form functional)
(assure-functional-live-p functional)
(ir1-convert-combination start next result
- form
- (maybe-reanalyze-functional functional)))
+ form
+ (maybe-reanalyze-functional functional)))
\f
;;;; PROCESS-DECLS
;;; *last* variable with that name, since LET* bindings may be
;;; duplicated, and declarations always apply to the last.
(declaim (ftype (sfunction (list symbol) (or lambda-var list))
- find-in-bindings))
+ find-in-bindings))
(defun find-in-bindings (vars name)
(let ((found nil))
(dolist (var vars)
(cond ((leaf-p var)
- (when (eq (leaf-source-name var) name)
- (setq found var))
- (let ((info (lambda-var-arg-info var)))
- (when info
- (let ((supplied-p (arg-info-supplied-p info)))
- (when (and supplied-p
- (eq (leaf-source-name supplied-p) name))
- (setq found supplied-p))))))
- ((and (consp var) (eq (car var) name))
- (setf found (cdr var)))))
+ (when (eq (leaf-source-name var) name)
+ (setq found var))
+ (let ((info (lambda-var-arg-info var)))
+ (when info
+ (let ((supplied-p (arg-info-supplied-p info)))
+ (when (and supplied-p
+ (eq (leaf-source-name supplied-p) name))
+ (setq found supplied-p))))))
+ ((and (consp var) (eq (car var) name))
+ (setf found (cdr var)))))
found))
;;; Called by PROCESS-DECLS to deal with a variable type declaration.
;;; If a LAMBDA-VAR being bound, we intersect the type with the var's
;;; type, otherwise we add a type restriction on the var. If a symbol
;;; macro, we just wrap a THE around the expansion.
-(defun process-type-decl (decl res vars)
+(defun process-type-decl (decl res vars context)
(declare (list decl vars) (type lexenv res))
(let ((type (compiler-specifier-type (first decl))))
(collect ((restr nil cons)
(new-vars nil cons))
(dolist (var-name (rest decl))
- (let* ((bound-var (find-in-bindings vars var-name))
- (var (or bound-var
- (lexenv-find var-name vars)
- (find-free-var var-name))))
- (etypecase var
- (leaf
- (flet ((process-var (var bound-var)
- (let* ((old-type (or (lexenv-find var type-restrictions)
- (leaf-type var)))
- (int (if (or (fun-type-p type)
- (fun-type-p old-type))
- type
- (type-approx-intersection2 old-type type))))
- (cond ((eq int *empty-type*)
- (unless (policy *lexenv* (= inhibit-warnings 3))
- (compiler-warn
- "The type declarations ~S and ~S for ~S conflict."
- (type-specifier old-type) (type-specifier type)
- var-name)))
- (bound-var (setf (leaf-type bound-var) int))
- (t
- (restr (cons var int)))))))
+ (when (boundp var-name)
+ (program-assert-symbol-home-package-unlocked
+ context var-name "declaring the type of ~A"))
+ (let* ((bound-var (find-in-bindings vars var-name))
+ (var (or bound-var
+ (lexenv-find var-name vars)
+ (find-free-var var-name))))
+ (etypecase var
+ (leaf
+ (flet
+ ((process-var (var bound-var)
+ (let* ((old-type (or (lexenv-find var type-restrictions)
+ (leaf-type var)))
+ (int (if (or (fun-type-p type)
+ (fun-type-p old-type))
+ type
+ (type-approx-intersection2
+ old-type type))))
+ (cond ((eq int *empty-type*)
+ (unless (policy *lexenv* (= inhibit-warnings 3))
+ (warn
+ 'type-warning
+ :format-control
+ "The type declarations ~S and ~S for ~S conflict."
+ :format-arguments
+ (list
+ (type-specifier old-type)
+ (type-specifier type)
+ var-name))))
+ (bound-var
+ (setf (leaf-type bound-var) int
+ (leaf-where-from bound-var) :declared))
+ (t
+ (restr (cons var int)))))))
(process-var var bound-var)
(awhen (and (lambda-var-p var)
(lambda-var-specvar var))
(process-var it nil))))
- (cons
- ;; FIXME: non-ANSI weirdness
- (aver (eq (car var) 'MACRO))
- (new-vars `(,var-name . (MACRO . (the ,(first decl)
+ (cons
+ ;; FIXME: non-ANSI weirdness
+ (aver (eq (car var) 'macro))
+ (new-vars `(,var-name . (macro . (the ,(first decl)
,(cdr var))))))
- (heap-alien-info
- (compiler-error
- "~S is an alien variable, so its type can't be declared."
- var-name)))))
+ (heap-alien-info
+ (compiler-error
+ "~S is an alien variable, so its type can't be declared."
+ var-name)))))
(if (or (restr) (new-vars))
- (make-lexenv :default res
- :type-restrictions (restr)
- :vars (new-vars))
- res))))
+ (make-lexenv :default res
+ :type-restrictions (restr)
+ :vars (new-vars))
+ res))))
;;; This is somewhat similar to PROCESS-TYPE-DECL, but handles
;;; declarations for function variables. In addition to allowing
;;; declarations for functions being bound, we must also deal with
;;; declarations that constrain the type of lexically apparent
;;; functions.
-(defun process-ftype-decl (spec res names fvars)
+(defun process-ftype-decl (spec res names fvars context)
(declare (type list names fvars)
(type lexenv res))
(let ((type (compiler-specifier-type spec)))
(collect ((res nil cons))
(dolist (name names)
- (let ((found (find name fvars
- :key #'leaf-source-name
- :test #'equal)))
- (cond
- (found
- (setf (leaf-type found) type)
- (assert-definition-type found type
- :unwinnage-fun #'compiler-notify
- :where "FTYPE declaration"))
- (t
- (res (cons (find-lexically-apparent-fun
- name "in a function type declaration")
- type))))))
+ (when (fboundp name)
+ (program-assert-symbol-home-package-unlocked
+ context name "declaring the ftype of ~A"))
+ (let ((found (find name fvars :key #'leaf-source-name :test #'equal)))
+ (cond
+ (found
+ (setf (leaf-type found) type)
+ (assert-definition-type found type
+ :unwinnage-fun #'compiler-notify
+ :where "FTYPE declaration"))
+ (t
+ (res (cons (find-lexically-apparent-fun
+ name "in a function type declaration")
+ type))))))
(if (res)
- (make-lexenv :default res :type-restrictions (res))
- res))))
+ (make-lexenv :default res :type-restrictions (res))
+ res))))
;;; Process a special declaration, returning a new LEXENV. A non-bound
;;; special declaration is instantiated by throwing a special variable
-;;; into the variables.
-(defun process-special-decl (spec res vars)
+;;; into the variables if BINDING-FORM-P is NIL, or otherwise into
+;;; *POST-BINDING-VARIABLE-LEXENV*.
+(defun process-special-decl (spec res vars binding-form-p context)
(declare (list spec vars) (type lexenv res))
(collect ((new-venv nil cons))
(dolist (name (cdr spec))
+ ;; While CLHS seems to allow local SPECIAL declarations for constants,
+ ;; whatever the semantics are supposed to be is not at all clear to me
+ ;; -- since constants aren't allowed to be bound it should be a no-op as
+ ;; no-one can observe the difference portably, but specials are allowed
+ ;; to be bound... yet nowhere does it say that the special declaration
+ ;; removes the constantness. Call it a spec bug and prohibit it. Same
+ ;; for GLOBAL variables.
+ (let ((kind (info :variable :kind name)))
+ (unless (member kind '(:special :unknown))
+ (error "Can't declare ~(~A~) variable locally special: ~S" kind name)))
+ (program-assert-symbol-home-package-unlocked
+ context name "declaring ~A special")
(let ((var (find-in-bindings vars name)))
- (etypecase var
- (cons
- (aver (eq (car var) 'MACRO))
- (compiler-error
- "~S is a symbol-macro and thus can't be declared special."
- name))
- (lambda-var
- (when (lambda-var-ignorep var)
- ;; ANSI's definition for "Declaration IGNORE, IGNORABLE"
- ;; requires that this be a STYLE-WARNING, not a full WARNING.
- (compiler-style-warn
- "The ignored variable ~S is being declared special."
- name))
- (setf (lambda-var-specvar var)
- (specvar-for-binding name)))
- (null
- (unless (assoc name (new-venv) :test #'eq)
- (new-venv (cons name (specvar-for-binding name))))))))
- (if (new-venv)
- (make-lexenv :default res :vars (new-venv))
- res)))
+ (etypecase var
+ (cons
+ (aver (eq (car var) 'macro))
+ (compiler-error
+ "~S is a symbol-macro and thus can't be declared special."
+ name))
+ (lambda-var
+ (when (lambda-var-ignorep var)
+ ;; ANSI's definition for "Declaration IGNORE, IGNORABLE"
+ ;; requires that this be a STYLE-WARNING, not a full WARNING.
+ (compiler-style-warn
+ "The ignored variable ~S is being declared special."
+ name))
+ (setf (lambda-var-specvar var)
+ (specvar-for-binding name)))
+ (null
+ (unless (or (assoc name (new-venv) :test #'eq))
+ (new-venv (cons name (specvar-for-binding name))))))))
+ (cond (binding-form-p
+ (setf *post-binding-variable-lexenv*
+ (append (new-venv) *post-binding-variable-lexenv*))
+ res)
+ ((new-venv)
+ (make-lexenv :default res :vars (new-venv)))
+ (t
+ res))))
;;; Return a DEFINED-FUN which copies a GLOBAL-VAR but for its INLINEP
-;;; (and TYPE if notinline).
-(defun make-new-inlinep (var inlinep)
+;;; (and TYPE if notinline), plus type-restrictions from the lexenv.
+(defun make-new-inlinep (var inlinep local-type)
(declare (type global-var var) (type inlinep inlinep))
- (let ((res (make-defined-fun
- :%source-name (leaf-source-name var)
- :where-from (leaf-where-from var)
- :type (if (and (eq inlinep :notinline)
- (not (eq (leaf-where-from var) :declared)))
- (specifier-type 'function)
- (leaf-type var))
- :inlinep inlinep)))
+ (let* ((type (if (and (eq inlinep :notinline)
+ (not (eq (leaf-where-from var) :declared)))
+ (specifier-type 'function)
+ (leaf-type var)))
+ (res (make-defined-fun
+ :%source-name (leaf-source-name var)
+ :where-from (leaf-where-from var)
+ :type (if local-type
+ (type-intersection local-type type)
+ type)
+ :inlinep inlinep)))
(when (defined-fun-p var)
(setf (defined-fun-inline-expansion res)
- (defined-fun-inline-expansion var))
- (setf (defined-fun-functional res)
- (defined-fun-functional var)))
+ (defined-fun-inline-expansion var))
+ (setf (defined-fun-functionals res)
+ (defined-fun-functionals var)))
+ ;; FIXME: Is this really right? Needs we not set the FUNCTIONAL
+ ;; to the original global-var?
res))
;;; Parse an inline/notinline declaration. If it's a local function we're
;;; defining, set its INLINEP. If a global function, add a new FENV entry.
(defun process-inline-decl (spec res fvars)
(let ((sense (cdr (assoc (first spec) *inlinep-translations* :test #'eq)))
- (new-fenv ()))
+ (new-fenv ()))
(dolist (name (rest spec))
- (let ((fvar (find name fvars
- :key #'leaf-source-name
- :test #'equal)))
- (if fvar
- (setf (functional-inlinep fvar) sense)
- (let ((found
- (find-lexically-apparent-fun
- name "in an inline or notinline declaration")))
- (etypecase found
- (functional
- (when (policy *lexenv* (>= speed inhibit-warnings))
- (compiler-notify "ignoring ~A declaration not at ~
- definition of local function:~% ~S"
- sense name)))
- (global-var
- (push (cons name (make-new-inlinep found sense))
- new-fenv)))))))
-
+ (let ((fvar (find name fvars :key #'leaf-source-name :test #'equal)))
+ (if fvar
+ (setf (functional-inlinep fvar) sense)
+ (let ((found (find-lexically-apparent-fun
+ name "in an inline or notinline declaration")))
+ (etypecase found
+ (functional
+ (when (policy *lexenv* (>= speed inhibit-warnings))
+ (compiler-notify "ignoring ~A declaration not at ~
+ definition of local function:~% ~S"
+ sense name)))
+ (global-var
+ (let ((type
+ (cdr (assoc found (lexenv-type-restrictions res)))))
+ (push (cons name (make-new-inlinep found sense type))
+ new-fenv))))))))
(if new-fenv
- (make-lexenv :default res :funs new-fenv)
- res)))
+ (make-lexenv :default res :funs new-fenv)
+ res)))
;;; like FIND-IN-BINDINGS, but looks for #'FOO in the FVARS
(defun find-in-bindings-or-fbindings (name vars fvars)
(declare (list vars fvars))
(if (consp name)
(destructuring-bind (wot fn-name) name
- (unless (eq wot 'function)
- (compiler-error "The function or variable name ~S is unrecognizable."
- name))
- (find fn-name fvars :key #'leaf-source-name :test #'equal))
+ (unless (eq wot 'function)
+ (compiler-error "The function or variable name ~S is unrecognizable."
+ name))
+ (find fn-name fvars :key #'leaf-source-name :test #'equal))
(find-in-bindings vars name)))
;;; Process an ignore/ignorable declaration, checking for various losing
(let ((var (find-in-bindings-or-fbindings name vars fvars)))
(cond
((not var)
- ;; ANSI's definition for "Declaration IGNORE, IGNORABLE"
- ;; requires that this be a STYLE-WARNING, not a full WARNING.
- (compiler-style-warn "declaring unknown variable ~S to be ignored"
- name))
+ ;; ANSI's definition for "Declaration IGNORE, IGNORABLE"
+ ;; requires that this be a STYLE-WARNING, not a full WARNING.
+ (compiler-style-warn "declaring unknown variable ~S to be ignored"
+ name))
;; FIXME: This special case looks like non-ANSI weirdness.
((and (consp var) (eq (car var) 'macro))
- ;; Just ignore the IGNORE decl.
- )
+ ;; Just ignore the IGNORE decl.
+ )
((functional-p var)
- (setf (leaf-ever-used var) t))
+ (setf (leaf-ever-used var) t))
((and (lambda-var-specvar var) (eq (first spec) 'ignore))
- ;; ANSI's definition for "Declaration IGNORE, IGNORABLE"
- ;; requires that this be a STYLE-WARNING, not a full WARNING.
- (compiler-style-warn "declaring special variable ~S to be ignored"
- name))
+ ;; ANSI's definition for "Declaration IGNORE, IGNORABLE"
+ ;; requires that this be a STYLE-WARNING, not a full WARNING.
+ (compiler-style-warn "declaring special variable ~S to be ignored"
+ name))
((eq (first spec) 'ignorable)
- (setf (leaf-ever-used var) t))
+ (setf (leaf-ever-used var) t))
(t
- (setf (lambda-var-ignorep var) t)))))
+ (setf (lambda-var-ignorep var) t)))))
(values))
+(defun process-dx-decl (names vars fvars kind)
+ (let ((dx (cond ((eq 'truly-dynamic-extent kind)
+ :truly)
+ ((and (eq 'dynamic-extent kind)
+ *stack-allocate-dynamic-extent*)
+ t))))
+ (if dx
+ (dolist (name names)
+ (cond
+ ((symbolp name)
+ (let* ((bound-var (find-in-bindings vars name))
+ (var (or bound-var
+ (lexenv-find name vars)
+ (maybe-find-free-var name))))
+ (etypecase var
+ (leaf
+ (if bound-var
+ (setf (leaf-dynamic-extent var) dx)
+ (compiler-notify
+ "Ignoring free DYNAMIC-EXTENT declaration: ~S" name)))
+ (cons
+ (compiler-error "DYNAMIC-EXTENT on symbol-macro: ~S" name))
+ (heap-alien-info
+ (compiler-error "DYNAMIC-EXTENT on alien-variable: ~S"
+ name))
+ (null
+ (compiler-style-warn
+ "Unbound variable declared DYNAMIC-EXTENT: ~S" name)))))
+ ((and (consp name)
+ (eq (car name) 'function)
+ (null (cddr name))
+ (valid-function-name-p (cadr name)))
+ (let* ((fname (cadr name))
+ (bound-fun (find fname fvars
+ :key #'leaf-source-name
+ :test #'equal))
+ (fun (or bound-fun (lexenv-find fname funs))))
+ (etypecase fun
+ (leaf
+ (if bound-fun
+ #!+stack-allocatable-closures
+ (setf (leaf-dynamic-extent bound-fun) dx)
+ #!-stack-allocatable-closures
+ (compiler-notify
+ "Ignoring DYNAMIC-EXTENT declaration on function ~S ~
+ (not supported on this platform)." fname)
+ (compiler-notify
+ "Ignoring free DYNAMIC-EXTENT declaration: ~S" name)))
+ (cons
+ (compiler-error "DYNAMIC-EXTENT on macro: ~S" name))
+ (null
+ (compiler-style-warn
+ "Unbound function declared DYNAMIC-EXTENT: ~S" name)))))
+ (t
+ (compiler-error "DYNAMIC-EXTENT on a weird thing: ~S" name))))
+ (when (policy *lexenv* (= speed 3))
+ (compiler-notify "Ignoring DYNAMIC-EXTENT declarations: ~S" names)))))
+
;;; FIXME: This is non-ANSI, so the default should be T, or it should
;;; go away, I think.
(defvar *suppress-values-declaration* nil
;;; Process a single declaration spec, augmenting the specified LEXENV
;;; RES. Return RES and result type. VARS and FVARS are as described
;;; PROCESS-DECLS.
-(defun process-1-decl (raw-spec res vars fvars)
+(defun process-1-decl (raw-spec res vars fvars binding-form-p context)
(declare (type list raw-spec vars fvars))
(declare (type lexenv res))
(let ((spec (canonized-decl-spec raw-spec))
(result-type *wild-type*))
(values
(case (first spec)
- (special (process-special-decl spec res vars))
+ (special (process-special-decl spec res vars binding-form-p context))
(ftype
(unless (cdr spec)
(compiler-error "no type specified in FTYPE declaration: ~S" spec))
- (process-ftype-decl (second spec) res (cddr spec) fvars))
+ (process-ftype-decl (second spec) res (cddr spec) fvars context))
((inline notinline maybe-inline)
(process-inline-decl spec res fvars))
((ignore ignorable)
(make-lexenv
:default res
:policy (process-optimize-decl spec (lexenv-policy res))))
+ (muffle-conditions
+ (make-lexenv
+ :default res
+ :handled-conditions (process-muffle-conditions-decl
+ spec (lexenv-handled-conditions res))))
+ (unmuffle-conditions
+ (make-lexenv
+ :default res
+ :handled-conditions (process-unmuffle-conditions-decl
+ spec (lexenv-handled-conditions res))))
(type
- (process-type-decl (cdr spec) res vars))
+ (process-type-decl (cdr spec) res vars context))
(values
(unless *suppress-values-declaration*
(let ((types (cdr spec)))
(car types)
`(values ,@types)))))
res))
- (dynamic-extent
- (when (policy *lexenv* (> speed inhibit-warnings))
- (compiler-notify
- "compiler limitation: ~
- ~% There's no special support for DYNAMIC-EXTENT (so it's ignored)."))
+ ((dynamic-extent truly-dynamic-extent)
+ (process-dx-decl (cdr spec) vars fvars (first spec))
res)
+ ((disable-package-locks enable-package-locks)
+ (make-lexenv
+ :default res
+ :disabled-package-locks (process-package-lock-decl
+ spec (lexenv-disabled-package-locks res))))
(t
(unless (info :declaration :recognized (first spec))
(compiler-warn "unrecognized declaration ~S" raw-spec))
- res))
+ (let ((fn (info :declaration :handler (first spec))))
+ (if fn
+ (funcall fn res spec vars fvars)
+ res))))
result-type)))
;;; Use a list of DECLARE forms to annotate the lists of LAMBDA-VAR
;;; filling in slots in the leaf structures, we return a new LEXENV,
;;; which reflects pervasive special and function type declarations,
;;; (NOT)INLINE declarations and OPTIMIZE declarations, and type of
-;;; VALUES declarations.
+;;; VALUES declarations. If BINDING-FORM-P is true, the third return
+;;; value is a list of VARs that should not apply to the lexenv of the
+;;; initialization forms for the bindings, but should apply to the body.
;;;
;;; This is also called in main.lisp when PROCESS-FORM handles a use
;;; of LOCALLY.
-(defun process-decls (decls vars fvars &optional (env *lexenv*))
+(defun process-decls (decls vars fvars &key
+ (lexenv *lexenv*) (binding-form-p nil) (context :compile))
(declare (list decls vars fvars))
- (let ((result-type *wild-type*))
+ (let ((result-type *wild-type*)
+ (*post-binding-variable-lexenv* nil))
(dolist (decl decls)
(dolist (spec (rest decl))
- (unless (consp spec)
- (compiler-error "malformed declaration specifier ~S in ~S" spec decl))
- (multiple-value-bind (new-env new-result-type)
- (process-1-decl spec env vars fvars)
- (setq env new-env)
- (unless (eq new-result-type *wild-type*)
- (setq result-type
- (values-type-intersection result-type new-result-type))))))
- (values env result-type)))
+ (progv
+ ;; Kludge: EVAL calls this function to deal with LOCALLY.
+ (when (eq context :compile) (list '*current-path*))
+ (when (eq context :compile) (list (or (get-source-path spec)
+ (get-source-path decl)
+ *current-path*)))
+ (unless (consp spec)
+ (compiler-error "malformed declaration specifier ~S in ~S" spec decl))
+ (multiple-value-bind (new-env new-result-type)
+ (process-1-decl spec lexenv vars fvars binding-form-p context)
+ (setq lexenv new-env)
+ (unless (eq new-result-type *wild-type*)
+ (setq result-type
+ (values-type-intersection result-type new-result-type)))))))
+ (values lexenv result-type *post-binding-variable-lexenv*)))
-(defun %processing-decls (decls vars fvars ctran lvar fun)
- (multiple-value-bind (*lexenv* result-type)
- (process-decls decls vars fvars)
+(defun %processing-decls (decls vars fvars ctran lvar binding-form-p fun)
+ (multiple-value-bind (*lexenv* result-type post-binding-lexenv)
+ (process-decls decls vars fvars :binding-form-p binding-form-p)
(cond ((eq result-type *wild-type*)
- (funcall fun ctran lvar))
+ (funcall fun ctran lvar post-binding-lexenv))
(t
(let ((value-ctran (make-ctran))
(value-lvar (make-lvar)))
(multiple-value-prog1
- (funcall fun value-ctran value-lvar)
+ (funcall fun value-ctran value-lvar post-binding-lexenv)
(let ((cast (make-cast value-lvar result-type
(lexenv-policy *lexenv*))))
(link-node-to-previous-ctran cast value-ctran)
(setf (lvar-dest value-lvar) cast)
(use-continuation cast ctran lvar))))))))
-(defmacro processing-decls ((decls vars fvars ctran lvar) &body forms)
+(defmacro processing-decls ((decls vars fvars ctran lvar
+ &optional post-binding-lexenv)
+ &body forms)
(check-type ctran symbol)
(check-type lvar symbol)
- `(%processing-decls ,decls ,vars ,fvars ,ctran ,lvar
- (lambda (,ctran ,lvar) ,@forms)))
+ (let ((post-binding-lexenv-p (not (null post-binding-lexenv)))
+ (post-binding-lexenv (or post-binding-lexenv (sb!xc:gensym "LEXENV"))))
+ `(%processing-decls ,decls ,vars ,fvars ,ctran ,lvar
+ ,post-binding-lexenv-p
+ (lambda (,ctran ,lvar ,post-binding-lexenv)
+ (declare (ignorable ,post-binding-lexenv))
+ ,@forms))))
;;; Return the SPECVAR for NAME to use when we see a local SPECIAL
;;; declaration. If there is a global variable of that name, then
;;; anonymous GLOBAL-VAR.
(defun specvar-for-binding (name)
(cond ((not (eq (info :variable :where-from name) :assumed))
- (let ((found (find-free-var name)))
- (when (heap-alien-info-p found)
- (compiler-error
- "~S is an alien variable and so can't be declared special."
- name))
- (unless (global-var-p found)
- (compiler-error
- "~S is a constant and so can't be declared special."
- name))
- found))
- (t
- (make-global-var :kind :special
- :%source-name name
- :where-from :declared))))
+ (let ((found (find-free-var name)))
+ (when (heap-alien-info-p found)
+ (compiler-error
+ "~S is an alien variable and so can't be declared special."
+ name))
+ (unless (global-var-p found)
+ (compiler-error
+ "~S is a constant and so can't be declared special."
+ name))
+ found))
+ (t
+ (make-global-var :kind :special
+ :%source-name name
+ :where-from :declared))))