Use FILE-INFO-%TRUENAME not -TRUENAME
[sbcl.git] / src / cold / shared.lisp
blobc659f30b9abacfc82d4bc7d6ca1c60e8188f3d7c
1 ;;;; stuff which is not specific to any particular build phase, but
2 ;;;; used by most of them
3 ;;;;
4 ;;;; Note: It's specifically not used when bootstrapping PCL, because
5 ;;;; we do SAVE-LISP after that, and we don't want to save extraneous
6 ;;;; bootstrapping machinery into the frozen image which will
7 ;;;; subsequently be used as the mother of all Lisp sessions.
9 ;;;; This software is part of the SBCL system. See the README file for
10 ;;;; more information.
11 ;;;;
12 ;;;; This software is derived from the CMU CL system, which was
13 ;;;; written at Carnegie Mellon University and released into the
14 ;;;; public domain. The software is in the public domain and is
15 ;;;; provided with absolutely no warranty. See the COPYING and CREDITS
16 ;;;; files for more information.
18 ;;; SB-COLD holds stuff used to build the initial SBCL core file
19 ;;; (including not only the final construction of the core file, but
20 ;;; also the preliminary steps like e.g. building the cross-compiler
21 ;;; and running the cross-compiler to produce target FASL files).
22 (defpackage "SB-COLD"
23 (:use "CL")
24 (:export genesis))
26 #+nil ; change to #+sbcl if desired, but at your own risk!
27 (when (sb-sys:find-dynamic-foreign-symbol-address "show_gc_generation_throughput")
28 (setf (extern-alien "show_gc_generation_throughput" int) 1))
30 #+sbcl ; prevent "illegal to redefine standard type: RATIONAL" etc
31 (when (member "SB-XC" (package-nicknames "CL") :test 'string=)
32 (sb-ext:unlock-package "CL")
33 (rename-package "CL" "COMMON-LISP" '("CL"))
34 (sb-ext:lock-package "CL"))
36 (in-package "SB-COLD")
38 (defun parse-make-host-parallelism (str)
39 (multiple-value-bind (value1 end) (parse-integer str :junk-allowed t)
40 (when value1
41 (let ((value2 (if (and value1
42 (< end (1- (length str))) ; ~ /,[\d]+/
43 (eql (char str end) #\,))
44 (parse-integer str :start (1+ end)))))
45 ;; If only 1 integer, assume same parallelism for both passes.
46 (unless value2
47 (setq value2 value1))
48 ;; 0 means no parallelism. 1 means use at most one subjob,
49 ;; just in case you want to test the controlling loop.
50 (when (eql value1 0) (setq value1 nil))
51 (when (eql value2 0) (setq value2 nil))
52 ;; Parallelism on pass 1 works only if LOAD does not compile.
53 ;; Otherwise it's slower than compiling serially.
54 ;; (And this has only been tested with sb-fasteval, not sb-eval.)
55 (cons (and (find-package "SB-INTERPRETER") value1)
56 value2)))))
58 (defvar *make-host-parallelism* nil)
59 (defvar *fail-on-warnings* t)
60 (defun make-host-1-parallelism () (car *make-host-parallelism*))
61 (defun make-host-2-parallelism () (cdr *make-host-parallelism*))
63 #+sbcl
64 (progn
65 (setq *make-host-parallelism*
66 (let ((envvar (sb-ext:posix-getenv "SBCL_MAKE_PARALLEL")))
67 (when envvar
68 (require :sb-posix)
69 (parse-make-host-parallelism envvar))))
70 (defmacro with-subprocesses (&rest body) `(progn ,@body))
71 (let ((f (multiple-value-bind (sym access) (find-symbol "OS-EXIT" "SB-SYS")
72 (if (eq access :external) sym 'sb-unix:unix-exit))))
73 (defun exit-process (arg) (funcall f arg))
74 (defun exit-subprocess (arg) (funcall f arg)))
75 ;; Lazily reference sb-posix because it may not be loaded
76 (defun posix-fork () (funcall (intern "FORK" "HOST-SB-POSIX")))
77 (defun getpid () (funcall (intern "UNIX-GETPID" "HOST-SB-UNIX")))
78 (defun posix-wait () (funcall (intern "WAIT" "HOST-SB-POSIX"))))
80 #+clisp
81 (progn
82 (setq *make-host-parallelism*
83 (let ((envvar (ext:getenv "SBCL_MAKE_PARALLEL")))
84 (when envvar
85 (parse-make-host-parallelism envvar))))
86 ;; FFI symbols won't exist if libffcall could not be found at build time.
87 (defmacro with-subprocesses (&rest rest)
88 (cons (or (find-symbol "WITH-SUBPROCESSES" "POSIX") 'progn) rest))
89 ;; clisp doesn't expose fork() and consequently doesn't behave
90 ;; correctly when (EXT:EXIT) is called in a forked child process.
91 (defun exit-process (arg) (ext:exit arg))
92 #+#.(cl:if (cl:find-package "FFI") '(and) '(or))
93 (progn (ffi:def-call-out exit-subprocess (:name "exit") (:arguments (arg ffi:int))
94 (:library :default) (:language :stdc))
95 (ffi:def-call-out posix-fork (:name "fork") (:return-type ffi:int)
96 (:library :default) (:language :stdc)))
97 (defun getpid () (posix:process-id))
98 (defun posix-wait ()
99 (multiple-value-bind (pid status code) (posix:wait)
100 (if (eql status :exited)
101 (values pid code)
102 (values pid (- code))))))
104 ;;; If TRUE, then COMPILE-FILE is being invoked only to process
105 ;;; :COMPILE-TOPLEVEL forms, not to produce an output file.
106 ;;; This is part of the implementation of parallelized make-host-2.
107 (defvar *compile-for-effect-only* nil)
109 ;;; prefixes for filename stems when cross-compiling. These are quite arbitrary
110 ;;; (although of course they shouldn't collide with anything we don't want to
111 ;;; write over). In particular, they can be either relative path names (e.g.
112 ;;; "host-objects/" or absolute pathnames (e.g. "/tmp/sbcl-xc-host-objects/").
114 ;;; The cross-compilation process will force the creation of these directories
115 ;;; by executing CL:ENSURE-DIRECTORIES-EXIST (on the xc host Common Lisp).
116 (defvar *host-obj-prefix*)
117 (defvar *target-obj-prefix*)
119 (defvar *target-obj-suffix*
120 ;; Target fasl files are LOADed (actually only quasi-LOADed, in
121 ;; GENESIS) only by SBCL code, and it doesn't care about particular
122 ;; extensions, so we can use something arbitrary.
123 ".lisp-obj")
124 (defvar *target-assem-obj-suffix*
125 ;; Target fasl files from SB-C:ASSEMBLE-FILE are LOADed via GENESIS.
126 ;; The source files are compiled once as assembly files and once as
127 ;; normal lisp files. In the past, they were kept separate by
128 ;; clever symlinking in the source tree, but that became less clean
129 ;; as ports to host environments without symlinks started appearing.
130 ;; In order to keep them separate, we have the assembled versions
131 ;; with a separate suffix.
132 ".assem-obj")
134 ;;; a function of one functional argument, which calls its functional argument
135 ;;; in an environment suitable for compiling the target. (This environment
136 ;;; includes e.g. a suitable *READTABLE* that looks in SB-XC:*FEATURES*
137 ;;; when it reads #- and #+ syntax)
138 (declaim (type function *in-target-compilation-mode-fn*))
139 (defvar *in-target-compilation-mode-fn*)
141 ;;; a function with the same calling convention as CL:COMPILE-FILE, to be
142 ;;; used to translate ordinary Lisp source files into target object files
143 (declaim (type function *target-compile-file*))
144 (defvar *target-compile-file*)
146 ;;; designator for a function with the same calling convention as
147 ;;; SB-C:ASSEMBLE-FILE, to be used to translate assembly files into target
148 ;;; object files
149 (defvar *target-assemble-file*)
151 ;;;; some tools
153 ;;; Take the file named X and make it into a file named Y. Sorta like
154 ;;; UNIX, and unlike Common Lisp's bare RENAME-FILE, we don't allow
155 ;;; information from the original filename to influence the final
156 ;;; filename. (The reason that it's only sorta like UNIX is that in
157 ;;; UNIX "mv foo bar/" will work, but the analogous
158 ;;; (RENAME-FILE-A-LA-UNIX "foo" "bar/") should fail.)
160 ;;; (This is a workaround for the weird behavior of Debian CMU CL
161 ;;; 2.4.6, where (RENAME-FILE "dir/x" "dir/y") tries to create a file
162 ;;; called "dir/dir/y". If that behavior goes away, then we should be
163 ;;; able to get rid of this function and use plain RENAME-FILE in the
164 ;;; COMPILE-STEM function above. -- WHN 19990321
165 (defun rename-file-a-la-unix (x y)
167 (let ((path ;; (Note that the TRUENAME expression here is lifted from an
168 ;; example in the ANSI spec for TRUENAME.)
169 (with-open-file (stream y :direction :output)
170 (close stream)
171 ;; From the ANSI spec: "In this case, the file is closed
172 ;; when the truename is tried, so the truename
173 ;; information is reliable."
174 (truename stream))))
175 (delete-file path)
176 (rename-file x path)))
177 (compile 'rename-file-a-la-unix)
179 (export '(*target-sbcl-version* *generated-sources-root*
180 *build-dependent-generated-sources-root*
181 stem-source-path find-bootstrap-file read-from-file))
182 (defvar *sources-root* "")
183 (defvar *generated-sources-root* "")
184 (defvar *build-dependent-generated-sources-root* "")
185 (defvar *src-cold-shared-pathname* *load-pathname*)
187 ;;; See remark in COMPILE-STEM about strings vs. The Common Lisp Way
188 (defun find-bootstrap-file (namestring &optional build-dependent)
189 (cond ((char= (char namestring 0) #\^)
190 ;; If it starts with a "^" then it means "src/cold/..."
191 (let ((this *src-cold-shared-pathname*)
192 (name (subseq namestring 1)))
193 (make-pathname :host (pathname-host this)
194 :device (pathname-device this)
195 :directory (pathname-directory this)
196 :name (pathname-name name)
197 :type (or (pathname-type name) (pathname-type this)))))
198 ((find #\/ namestring)
199 ;; Otherwise if it contains a slash, then it's a source file which is either
200 ;; in the tree as checked in, or generated by a prior build step.
201 (concatenate 'string
202 (if (eql (mismatch "output/" namestring) 7) ; a generated source
203 (if build-dependent
204 *build-dependent-generated-sources-root*
205 *generated-sources-root*)
206 *sources-root*)
207 namestring))
209 ;; Else, it's an optional user-supplied customization file,
210 ;; or a generated data file in the root directory such as "version.lisp-expr"
211 namestring)))
212 (compile 'find-bootstrap-file) ; seems in vogue to compile everything in this file
214 ;;; Return an expression read from the file named NAMESTRING.
215 ;;; For user-supplied inputs, protect against more than one expression
216 ;;; appearing in the file. (Our ^build-order.lisp-expr file has more than
217 ;;; one expression in it, so we need to be able to not enforce.)
218 (defun read-from-file (namestring &key (enforce-single-expr t) build-dependent)
219 (with-open-file (s (find-bootstrap-file namestring build-dependent))
220 (let* ((result (read s))
221 (eof-result (cons nil nil)))
222 (unless enforce-single-expr
223 (return-from read-from-file result))
224 (unless (eq (read s nil eof-result) eof-result)
225 (error "more than one expression in file ~S" namestring))
226 result)))
227 (compile 'read-from-file)
229 #+sbcl (let ((ext (find-package "SB-EXT")))
230 ;; prevent things from working by accident when they would not work in
231 ;; ANSI lisp, e.g. ~/print-symbol-with-prefix/ (missing SB-EXT:)
232 (when (member ext (package-use-list "CL-USER"))
233 (unuse-package ext "CL-USER")))
235 #+cmu
236 (progn
237 ;; too much noise, can't see the actual warnings
238 (setq cl:*compile-print* nil
239 ext:*gc-verbose* nil))
241 #+sbcl
242 (progn
243 (setq cl:*compile-print* nil)
244 (load (find-bootstrap-file "^muffler"))
245 ;; Let's just say we never care to see these.
246 (declaim (sb-ext:muffle-conditions
247 (satisfies unable-to-optimize-note-p)
248 (satisfies optional+key-style-warning-p)
249 sb-ext:code-deletion-note)))
251 ;;;; special read-macros for building the cold system (and even for
252 ;;;; building some of our tools for building the cold system)
254 (load (find-bootstrap-file "^shebang"))
256 ;;; Subfeatures could be assigned as late as the beginning of make-host-2,
257 ;;; but I don't want to introduce another mechanism for delaying reading
258 ;;; of the customizer just because we can.
259 ;;; But it's not well-advertised; does it really merit a customization file?
260 (export 'backend-subfeatures)
261 (defvar backend-subfeatures
262 (let ((customizer-file-name "customize-backend-subfeatures.lisp"))
263 (when (probe-file customizer-file-name)
264 (copy-list (funcall (compile nil (read-from-file customizer-file-name)) nil)))))
266 ;;; When cross-compiling, the *FEATURES* set for the target Lisp is
267 ;;; not in general the same as the *FEATURES* set for the host Lisp.
268 ;;; In order to refer to target features specifically, we refer to
269 ;;; SB-XC:*FEATURES* instead of CL:*FEATURES*.
271 ;;; To support building in a read-only filesystem, the 'local-target-features'
272 ;;; file might not be directly located here, since it's a generated file.
273 ;;; In as much as we use files as the means of passing parameters to
274 ;;; our Lisp scripts - because we can't in general assume that we can read
275 ;;; the command-line arguments in any Lisp - it doesn't make sense to have
276 ;;; another file specifying the name of the local-target-features file.
277 ;;; The compromise is to examine a variable specifying a path
278 ;;; (and it can't go in SB-COLD because the package is not made soon enough)
279 (setf sb-xc:*features*
280 (let* ((pathname (let ((var 'cl-user::*sbcl-local-target-features-file*))
281 (if (boundp var)
282 (symbol-value var)
283 "local-target-features.lisp-expr")))
284 (default-features
285 (funcall (compile nil (read-from-file pathname))
286 (read-from-file "^base-target-features.lisp-expr")))
287 (customizer-file-name "customize-target-features.lisp")
288 (customizer (if (probe-file customizer-file-name)
289 (compile nil
290 (read-from-file customizer-file-name))
291 #'identity))
292 ;; Bind temporarily so that TARGET-FEATUREP and TARGET-PLATFORM-KEYWORD
293 ;; can see the tentative list.
294 (sb-xc:*features* (funcall customizer default-features))
295 (gc (intersection '(:cheneygc :gencgc :mark-region-gc)
296 sb-xc:*features*))
297 (arch (target-platform-keyword)))
298 (when (member :mark-region-gc sb-xc:*features*)
299 (setf sb-xc:*features* (remove :gencgc sb-xc:*features*)
300 gc (remove :gencgc gc)))
301 (unless (and gc (not (cdr gc)))
302 (error "Exactly 1 GC implementation needs to be selected"))
303 (setq gc (car gc))
304 ;; all our GCs are generational
305 (when (member gc '(:gencgc :mark-region-gc))
306 (pushnew :generational sb-xc:*features*))
307 (when (eq gc :mark-region-gc)
308 (setq sb-xc:*features* (remove :immobile-space sb-xc:*features*)))
309 ;; Win32 conditionally adds :sb-futex in grovel-features.sh
310 ;; Futexes aren't available in all macos versions, but they are available in
311 ;; all versions that support arm, so always enable them there
312 (when (target-featurep '(:and :sb-thread (:or :linux :freebsd :openbsd (:and :darwin :arm64))))
313 (pushnew :sb-futex sb-xc:*features*))
314 (when (target-featurep '(:and :sb-thread (:or :arm64 :x86-64)))
315 (pushnew :system-tlabs sb-xc:*features*))
316 (when (target-featurep '(:and (:or :permgen :immobile-space) :x86-64))
317 (pushnew :compact-instance-header sb-xc:*features*))
318 (when (target-featurep :immobile-space)
319 (pushnew :immobile-code sb-xc:*features*))
320 (when (target-featurep :64-bit)
321 (push :compact-symbol sb-xc:*features*))
322 (when (target-featurep :64-bit)
323 ;; Considering that a single config file governs rv32 and rv64, I don't
324 ;; know how to make this properly configurable. In theory, 32-bit builds could
325 ;; have a salted hash (gaining 3 bits by making the hash slot raw), but
326 ;; they don't, so in light of things, this is a valid criterion.
327 (push :salted-symbol-hash sb-xc:*features*))
328 (when (target-featurep '(:and :sb-thread (:or (:and :darwin (:not (:or :ppc :x86))) :openbsd)))
329 (push :os-thread-stack sb-xc:*features*))
330 (when (target-featurep '(:and :x86 :int4-breakpoints))
331 ;; 0xCE is a perfectly good 32-bit instruction,
332 ;; unlike on x86-64 where it is illegal. It's therefore
333 ;; confusing to allow this feature in a 32-bit build.
334 ;; But it's annoying to have a build script that otherwise works
335 ;; for a native x86/x86-64 build except for needing one change.
336 ;; Just print something and go on with life.
337 (setq sb-xc:*features* (remove :int4-breakpoints sb-xc:*features*))
338 (warn "Removed :INT4-BREAKPOINTS from target features"))
339 (when (target-featurep :x86-64)
340 (let ((int3-enable (target-featurep :int3-breakpoints))
341 (int4-enable (target-featurep :int4-breakpoints))
342 (ud2-enable (target-featurep :ud2-breakpoints)))
343 (when (or ud2-enable int4-enable)
344 (setq sb-xc:*features* (remove :int3-breakpoints sb-xc:*features*))
345 (when (and ud2-enable int4-enable)
346 (error "UD2-BREAKPOINTS and INT4-BREAKPOINTS are mutually exclusive choices")))
347 (unless (or int3-enable int4-enable ud2-enable)
348 ;; don't love the name, but couldn't think of a better one
349 (push :sw-int-avoidance sb-xc:*features*))))
350 (when (or (target-featurep :arm64)
351 (and (target-featurep :x86-64)
352 (member :sse4 backend-subfeatures)))
353 (push :round-float sb-xc:*features*))
354 (when (target-featurep '(:and :arm64 :darwin))
355 (push :arm-v8.1 backend-subfeatures))
357 ;; Putting arch and gc choice first is visually convenient, versus
358 ;; having to parse a random place in the line to figure out the value
359 ;; of a binary choice {cheney vs gencgc} and architecture.
360 ;; De-duplicate the rest of the symbols because the command line
361 ;; can add redundant --with-mumble options.
362 (list* arch gc (sort (remove-duplicates
363 (remove arch (remove gc sb-xc:*features*)))
364 #'string<))))
366 ;;; Call for effect of signaling an error if no target picked.
367 (target-platform-keyword)
369 ;;; You can get all the way through make-host-1 without either one of these
370 ;;; features, but then 'bit-bash' will fail to cross-compile.
371 (unless (intersection '(:big-endian :little-endian) sb-xc:*features*)
372 (warn "You'll have bad time without either endian-ness defined"))
374 ;;; Some feature combinations simply don't work, and sometimes don't
375 ;;; fail until quite a ways into the build. Pick off the more obvious
376 ;;; combinations now, and provide a description of what the actual
377 ;;; failure is (not always obvious from when the build fails).
378 (let ((feature-compatibility-tests
379 '(("(and sb-safepoint (not sb-thread))" ":SB-SAFEPOINT requires :SB-THREAD")
380 ("(and sb-thread (not (or riscv ppc ppc64 x86 x86-64 arm64)))"
381 ":SB-THREAD not supported on selected architecture")
382 ("(and mark-region-gc (not (or x86-64 arm64)))"
383 "mark-region is not supported on selected architecture")
384 ("(and (not sb-thread) (or arm64 ppc64))"
385 "The selected architecture requires :SB-THREAD")
386 ("(and gencgc cheneygc)"
387 ":GENCGC and :CHENEYGC are incompatible")
388 ("(and sb-safepoint (not (and (or arm64 x86 x86-64) (or darwin linux win32))))"
389 ":SB-SAFEPOINT not supported on selected arch/OS")
390 ("(not (or elf mach-o win32))"
391 "No execute object file format feature defined")
392 ("(and cons-profiling (not sb-thread))" ":CONS-PROFILING requires :SB-THREAD")
393 ("(and sb-linkable-runtime (not (or arm arm64 x86 x86-64 ppc ppc64)))"
394 ":SB-LINKABLE-RUNTIME not supported on selected architecture")
395 ("(and sb-linkable-runtime (not (or darwin freebsd linux win32)))"
396 ":SB-LINKABLE-RUNTIME not supported on selected operating system")
397 ("(and sb-eval sb-fasteval)"
398 ;; It sorta kinda works to have both, but there should be no need,
399 ;; and it's not really supported.
400 "At most one interpreter can be selected")
401 ("(and compact-instance-header (not (or permgen immobile-space)))"
402 ":COMPACT-INSTANCE-HEADER requires :IMMOBILE-SPACE feature")
403 ("(and immobile-code (not immobile-space))"
404 ":IMMOBILE-CODE requires :IMMOBILE-SPACE feature")
405 ("(and immobile-symbols (not immobile-space))"
406 ":IMMOBILE-SYMBOLS requires :IMMOBILE-SPACE feature")
407 ("(and system-tlabs (not sb-thread))"
408 ":SYSTEM-TLABS requires SB-THREAD")
409 ("(and sb-futex (not sb-thread))"
410 "Can't enable SB-FUTEX on platforms lacking thread support")
411 ;; There is still hope to make multithreading on DragonFly x86-64
412 ("(and sb-thread x86 dragonfly)"
413 ":SB-THREAD not supported on selected architecture")))
414 (failed-test-descriptions nil))
415 (dolist (test feature-compatibility-tests)
416 (let ((*readtable* *xc-readtable*))
417 (when (read-from-string (concatenate 'string "#+" (first test) "T NIL"))
418 (push (second test) failed-test-descriptions))))
419 (when failed-test-descriptions
420 (error "Feature compatibility check failed, ~S"
421 (reverse failed-test-descriptions))))
423 ;;;; cold-init-related PACKAGE and SYMBOL tools
425 ;;; Once we're done with possibly ANSIfying the COMMON-LISP package,
426 ;;; it's probably a mistake if we change it (beyond changing the
427 ;;; values of special variables such as *** and +, anyway). Set up
428 ;;; machinery to warn us when/if we change it.
430 ;;; All code depending on this is itself dependent on #+SB-SHOW.
431 (defvar *cl-snapshot*)
432 (when (member :sb-show sb-xc:*features*)
433 (load (find-bootstrap-file "^snapshot"))
434 (setq *cl-snapshot* (take-snapshot "COMMON-LISP")))
436 ;;;; master list of source files and their properties
438 ;;; flags which can be used to describe properties of source files
439 (defparameter *expected-stem-flags*
440 '(;; meaning: This file is needed to generate C headers if doing so
441 ;; independently of make-host-1
442 :c-headers
443 ;; meaning: This file is not to be compiled when building the
444 ;; cross-compiler which runs on the host ANSI Lisp. ("not host
445 ;; code", i.e. does not execute on host -- but may still be
446 ;; cross-compiled by the host, so that it executes on the target)
447 :not-host
448 ;; meaning: This file is not to be compiled as part of the target
449 ;; SBCL. ("not target code" -- but still presumably host code,
450 ;; used to support the cross-compilation process)
451 :not-target
452 ;; meaning: The #'COMPILE-STEM argument :TRACE-FILE should be T.
453 ;; When the compiler is SBCL's COMPILE-FILE or something like it,
454 ;; compiling "foo.lisp" will generate "foo.trace" which contains lots
455 ;; of exciting low-level information about representation selection,
456 ;; VOPs used by the compiler, and bits of assembly.
457 :trace-file
458 ;; meaning: When cold-loading this file while producing the
459 ;; initial cold core, genesis should produce a trace file of the
460 ;; fops (fasl operations) executed.
461 :foptrace-file
462 ;; meaning: The #'COMPILE-STEM argument :BLOCK-COMPILE should be
463 ;; T. That is, the entire file will be block compiled. Like
464 ;; :TRACE-FILE, this applies to all COMPILE-FILEs which support
465 ;; something like :BLOCK-COMPILE.
466 :block-compile
467 ;; meaning: This file is to be processed with the SBCL assembler,
468 ;; not COMPILE-FILE. (Note that this doesn't make sense unless
469 ;; :NOT-HOST is also set, since the SBCL assembler doesn't exist
470 ;; while the cross-compiler is being built in the host ANSI Lisp.)
471 :assem
472 ;; meaning: ignore this flag.
473 ;; This works around nonstandard behavior of "#." in certain hosts.
474 ;; When the evaluated form yields 0 values, ECL and CLISP treat it
475 ;; as though if yielded NIL:
476 ;; * (read-from-string "#(#.(cl:if (cl:eql 1 2) x (values)))")
477 ;; => #(NIL)
478 ;; The correct value for the above expression - as obtained in SBCL,
479 ;; CCL, and ABCL - is #() because _any_ reader macro is permitted
480 ;; to produce 0 values. In fact you can demonstrate this by actually
481 ;; implementing your own "#." which conditionally returns 0 values,
482 ;; and seeing that it works in any lisp including the suspect ones.
483 ;; The oft-used idiom of "#+#.(cl:if (test) '(and) '(or)) X"
484 ;; is sufficiently unclear that its worth allowing a spurious NIL
485 ;; just to avoid that ugly mess.
486 nil))
488 (defmacro do-stems-and-flags ((stem flags build-phase) &body body)
489 (let ((stem-and-flags (gensym "STEM-AND-FLAGS")))
490 `(dolist (,stem-and-flags (get-stems-and-flags ,build-phase))
491 (let ((,stem (first ,stem-and-flags))
492 (,flags (rest ,stem-and-flags)))
493 ,@body))))
495 ;;; Given a STEM, remap the path components "/{arch}/" and "/asm-target/"
496 ;;; to suitable directories.
497 (defun stem-remap-target (stem)
498 (flet ((try-replacing (this that)
499 (let ((position (search this stem)))
500 (when position
501 (concatenate 'string
502 (subseq stem 0 (1+ position))
503 (string-downcase that)
504 (subseq stem (+ position (length this) -1)))))))
505 (or (try-replacing "/{arch}/" (target-platform-keyword))
506 (try-replacing "/asm-target/" (backend-assembler-target-name))
507 stem)))
508 (compile 'stem-remap-target)
510 ;;; Determine the source path for a stem by remapping from the abstract name
511 ;;; if it contains "/{arch}/" and appending a ".lisp" suffix.
512 ;;; Assume that STEM is source-tree-relative unless it starts with "output/"
513 ;;; in which case it could be elsewhere, if you prefer to keep the sources
514 ;;; devoid of compilation artifacts. (The production of out-of-tree artifacts
515 ;;; is not actually implemented in the generic build, however if your build
516 ;;; system does that by itself, then hooray for you)
517 (defun stem-source-path (stem)
518 (concatenate 'string (find-bootstrap-file (stem-remap-target stem)) ".lisp"))
519 (compile 'stem-source-path)
521 ;;; Determine the object path for a stem/flags/mode combination.
522 (export 'stem-object-path)
523 (defun stem-object-path (stem flags mode)
524 (multiple-value-bind (obj-prefix obj-suffix)
525 (ecase mode
526 (:host-compile
527 ;; On some xc hosts, it's impossible to LOAD a fasl file unless it
528 ;; has the same extension that the host uses for COMPILE-FILE
529 ;; output, so we have to be careful to use the xc host's preferred
530 ;; extension.
531 (values *host-obj-prefix*
532 (concatenate 'string "."
533 (pathname-type (compile-file-pathname stem)))))
534 (:target-compile
535 (values *target-obj-prefix*
536 (cond ((find :assem flags) *target-assem-obj-suffix*)
537 (t *target-obj-suffix*)))))
538 (concatenate 'string obj-prefix (stem-remap-target stem) obj-suffix)))
539 (compile 'stem-object-path)
541 (defvar *stems-and-flags* nil)
542 ;;; Read the set of files to compile with respect to a build phase, 1 or 2.
543 (defun get-stems-and-flags (build-phase)
544 (when (and *stems-and-flags* (eql (car *stems-and-flags*) build-phase))
545 (return-from get-stems-and-flags (cdr *stems-and-flags*)))
546 (let* ((feature (aref #(:sb-xc-host :sb-xc) (1- build-phase)))
547 (list
548 ;; The build phase feature goes into CL:*FEATURES*, not SB-XC:*FEATURES*
549 ;; because firstly we don't use feature expressions to control the set of
550 ;; files pertinent to the build phase - that is governed by :NOT-{HOST,TARGET}
551 ;; flags, and secondly we can not assume existence of the SB-XC package in
552 ;; warm build. The sole reason for this hack is to allow testing for CMU
553 ;; as the build host in make-host-1 which apparently needs to be allowed
554 ;; to produce warnings as a bug workaround.
555 (let ((cl:*features* (cons feature cl:*features*))
556 (*readtable* *xc-readtable*))
557 (read-from-file "^build-order.lisp-expr" :enforce-single-expr nil))))
558 (setf *stems-and-flags* (cons build-phase list)))
559 ;; Now check for duplicate stems and bogus flags.
560 (let ((stems (make-hash-table :test 'equal)))
561 (do-stems-and-flags (stem flags build-phase)
562 ;; We do duplicate stem comparison based on the object path in
563 ;; order to cover the case of stems with an :assem flag, which
564 ;; have two entries but separate object paths for each. KLUDGE:
565 ;; We have to bind *target-obj-prefix* here because it's normally
566 ;; set up later in the build process and we don't actually care
567 ;; what it is so long as it doesn't change while we're checking
568 ;; for duplicate stems.
569 (let* ((*target-obj-prefix* "")
570 (object-path (stem-object-path stem flags :target-compile)))
571 (if (gethash object-path stems)
572 (error "duplicate stem ~S in *STEMS-AND-FLAGS*" stem)
573 (setf (gethash object-path stems) t)))
574 ;; Check for stupid typos in FLAGS list keywords.
575 ;; FIXME: We should make sure that the :assem flag is only used
576 ;; when paired with :not-host.
577 (let ((set-difference (set-difference flags *expected-stem-flags*)))
578 (when set-difference
579 (error "found unexpected flag(s) in *STEMS-AND-FLAGS*: ~S"
580 set-difference)))))
581 (cdr *stems-and-flags*))
583 ;;;; tools to compile SBCL sources to create the cross-compiler
585 ;;; a wrapper for compilation/assembly, used mostly to centralize
586 ;;; the procedure for finding full filenames from "stems"
588 ;;; Compile the source file whose basic name is STEM, using some
589 ;;; standard-for-the-SBCL-build-process procedures to generate the
590 ;;; full pathnames of source file and object file. Return the pathname
591 ;;; of the object file for STEM.
593 ;;; STEM and FLAGS are as per DO-STEMS-AND-FLAGS. MODE is one of
594 ;;; :HOST-COMPILE and :TARGET-COMPILE.
595 (defun compile-stem (stem flags mode)
596 (let* ((src (stem-source-path stem))
597 (obj (stem-object-path stem flags mode))
598 ;; Compile-for-effect happens simultaneously with a forked compile,
599 ;; so we need the for-effect output not to stomp on the real output.
600 (tmp-obj
601 (concatenate 'string obj
602 (if *compile-for-effect-only* "-scratch" "-tmp")))
603 (compilation-fn
604 (ecase mode
605 (:host-compile
606 #+abcl ; ABCL complains about its own deficiency and then returns T
607 ;; for warnings and failure. "Unable to compile function" is not our problem,
608 ;; but I tried everything to muffle it, and nothing worked; so if it occurs,
609 ;; treat the file as a success despite any actual problems that may exist.
610 (lambda (&rest args)
611 (let (compiler-bug)
612 ;; Even though COMPILER-UNSUPPORTED-FEATURE-ERROR is a condition class,
613 ;; HANDLER-BIND seems unable to match it. What the hell? Bugs all the way down.
614 (handler-bind ((condition
615 (lambda (c)
616 (when (search "Using interpreted form" (princ-to-string c))
617 (setq compiler-bug t)))))
618 (multiple-value-bind (fasl warn err) (apply #'compile-file args)
619 (if compiler-bug (values fasl nil nil) (values fasl warn err))))))
620 #+ccl ; CCL doesn't like NOTINLINE on unknown functions
621 (lambda (&rest args)
622 (handler-bind ((ccl:compiler-warning
623 (lambda (c)
624 (when (eq (ccl::compiler-warning-warning-type c)
625 :unknown-declaration-function)
626 (muffle-warning c)))))
627 (apply #'compile-file args)))
628 #-(or abcl ccl) #'compile-file)
629 (:target-compile (if (find :assem flags)
630 *target-assemble-file*
631 *target-compile-file*))))
632 (trace-file (if (find :trace-file flags) t nil))
633 (block-compile (if (find :block-compile flags) t :specified)))
634 (declare (type function compilation-fn))
636 (ensure-directories-exist obj :verbose cl:*compile-print*) ; host's value
638 ;; We're about to set about building a new object file. First, we
639 ;; delete any preexisting object file in order to avoid confusing
640 ;; ourselves later should we happen to bail out of compilation
641 ;; with an error.
642 (when (and (not *compile-for-effect-only*) (probe-file obj))
643 (delete-file obj))
645 ;; Original comment:
647 ;; Work around a bug in CLISP 1999-01-08 #'COMPILE-FILE: CLISP
648 ;; mangles relative pathnames passed as :OUTPUT-FILE arguments,
649 ;; but works OK with absolute pathnames.
651 ;; following discussion on cmucl-imp 2002-07
652 ;; "COMPILE-FILE-PATHNAME", it would seem safer to deal with
653 ;; absolute pathnames all the time; it is no longer clear that the
654 ;; original behaviour in CLISP was wrong or that the current
655 ;; behaviour is right; and in any case absolutifying the pathname
656 ;; insulates us against changes of behaviour. -- CSR, 2002-08-09
657 (setf tmp-obj
658 ;; (Note that this idiom is taken from the ANSI
659 ;; documentation for TRUENAME.)
660 (with-open-file (stream tmp-obj
661 :direction :output
662 ;; Compilation would overwrite the
663 ;; temporary object anyway and overly
664 ;; strict implementations default
665 ;; to :ERROR.
666 :if-exists :supersede)
667 (close stream)
668 (truename stream)))
669 ;; and some compilers (e.g. OpenMCL) will complain if they're
670 ;; asked to write over a file that exists already (and isn't
671 ;; recognizeably a fasl file), so
672 (when (probe-file tmp-obj)
673 (delete-file tmp-obj))
675 ;; Try to use the compiler to generate a new temporary object file.
676 (flet ((report-recompile-restart (stream)
677 (format stream "Recompile file ~S" src))
678 (report-continue-restart (stream)
679 (format stream "Continue, using possibly bogus file ~S" obj)))
680 (tagbody
681 retry-compile
682 (multiple-value-bind (output-truename warnings-p failure-p)
683 (restart-case
684 (apply compilation-fn src
685 :output-file tmp-obj
686 :block-compile (and
687 ;; Block compilation was
688 ;; completely broken from the
689 ;; beginning of SBCL history
690 ;; until version 2.0.2.
691 #+sbcl
692 (or (eq mode :target-compile)
693 (and (find-symbol "SPLIT-VERSION-STRING" "HOST-SB-C")
694 (funcall (find-symbol "VERSION>=" "HOST-SB-C")
695 (funcall (find-symbol "SPLIT-VERSION-STRING" "HOST-SB-C")
696 (lisp-implementation-version))
697 '(2 0 2))))
698 block-compile)
699 :allow-other-keys t
700 ;; If tracing, also print, but don't specify :PRINT unless specifying
701 ;; :TRACE-FILE so that whatever the default is for *COMPILE-PRINT*
702 ;; prevails, insensitively to whether it's the SB-XC: or CL: symbol.
703 (when trace-file
704 '(:trace-file t :print t)))
705 (recompile ()
706 :report report-recompile-restart
707 (go retry-compile)))
708 (declare (ignore warnings-p))
709 (cond ((not output-truename)
710 (error "couldn't compile ~S" src))
711 (failure-p
712 (unwind-protect
713 (restart-case
714 (error "FAILURE-P was set when creating ~S."
715 obj)
716 (recompile ()
717 :report report-recompile-restart
718 (go retry-compile))
719 (continue ()
720 :report report-continue-restart
721 (setf failure-p nil)))
722 ;; Don't leave failed object files lying around.
723 (when (and failure-p (probe-file tmp-obj))
724 (delete-file tmp-obj)
725 (format t "~&deleted ~S~%" tmp-obj))))
726 ;; Otherwise: success, just fall through.
727 (t nil)))))
729 ;; If we get to here, compilation succeeded, so it's OK to rename
730 ;; the temporary output file to the permanent object file.
731 (cond ((not *compile-for-effect-only*)
732 (rename-file-a-la-unix tmp-obj obj))
733 ((probe-file tmp-obj)
734 (delete-file tmp-obj))) ; clean up the trash
736 ;; nice friendly traditional return value
737 (pathname obj)))
738 (compile 'compile-stem)
740 (defparameter *host-quirks*
741 (or #+cmu '(:host-quirks-cmu)
742 #+ecl '(:host-quirks-ecl)
743 #+ccl '(:host-quirks-ccl)
744 #+sbcl '(:host-quirks-sbcl))) ; not so much a "quirk", but consistent anyway
746 ;;; Execute function FN in an environment appropriate for compiling the
747 ;;; cross-compiler's source code in the cross-compilation host.
748 (defun in-host-compilation-mode (fn)
749 (declare (type function fn))
750 (let ((sb-xc:*features* (append '(:sb-xc-host) *host-quirks* sb-xc:*features*))
751 (*readtable* *xc-readtable*))
752 (funcall fn)))
753 (compile 'in-host-compilation-mode)
755 ;;; Process a file as source code for the cross-compiler, compiling it
756 ;;; (if necessary) in the appropriate environment, then loading it
757 ;;; into the cross-compilation host Common lisp.
758 (defun host-cload-stem (stem flags)
759 (loop
760 (with-simple-restart (recompile "Recompile")
761 (let ((compiled-filename (in-host-compilation-mode
762 (lambda ()
763 (compile-stem stem flags :host-compile)))))
764 (return
765 (load compiled-filename))))))
766 (compile 'host-cload-stem)
768 ;;; like HOST-CLOAD-STEM, except that we don't bother to compile
769 (defun host-load-stem (stem flags)
770 (loop
771 (with-simple-restart (recompile "Reload")
772 (return (load (stem-object-path stem flags :host-compile))))))
773 (compile 'host-load-stem)
775 ;;;; tools to compile SBCL sources to create object files which will
776 ;;;; be used to create the target SBCL .core file
778 (defun lpnify-stem (stem)
779 ;; Don't want genfiles path to sneak in - avoid (STEM-SOURCE-PATH ...) here.
780 (let ((string (stem-remap-target stem)))
781 ;; Distrust that random hosts don't bork up the translation.
782 ;; Simply replace '/' with ';' and be done.
783 (format nil "SYS:~:@(~A~).LISP" (substitute #\; #\/ string))))
784 (compile 'lpnify-stem)
786 ;;; Run the cross-compiler on a file in the source directory tree to
787 ;;; produce a corresponding file in the target object directory tree.
788 (defun target-compile-stem (stem flags)
789 (let ((system-tlab-p
790 (or (search "src/pcl" stem)
791 (search "src/code/alieneval" stem)
792 (search "src/code/arena" stem)
793 (search "src/code/avltree" stem)
794 (search "src/code/brothertree" stem)
795 (search "src/code/early-classoid" stem)
796 (search "src/code/type-class" stem)
797 (search "src/code/class" stem)
798 (search "src/code/debug" stem) ; also matches debug-{info,int,var-io}
799 (search "src/code/early-defmethod" stem)
800 (search "src/code/final" stem)
801 (search "src/code/format" stem)
802 (search "src/code/solist" stem))))
803 (funcall *in-target-compilation-mode-fn*
804 (lambda ()
805 (progv (list (intern "*SOURCE-NAMESTRING*" "SB-C")
806 (intern "*FORCE-SYSTEM-TLAB*" "SB-C"))
807 (list (lpnify-stem stem)
808 system-tlab-p)
809 (loop
810 (with-simple-restart (recompile "Recompile")
811 (return (compile-stem stem flags :target-compile)))))))))
812 (compile 'target-compile-stem)
814 ;;; (This function is not used by the build process, but is intended
815 ;;; for interactive use when experimenting with the system. It runs
816 ;;; the cross-compiler on test files with arbitrary filenames, not
817 ;;; necessarily in the source tree, e.g. in "/tmp".)
818 (defun target-compile-file (filename)
819 (funcall *in-target-compilation-mode-fn*
820 (lambda ()
821 (funcall *target-compile-file* filename))))
822 (compile 'target-compile-file)
824 (defvar *math-ops-memoization* (make-hash-table :test 'equal))
825 (defun math-journal-pathname (direction)
826 ;; Initialy we read from the file in the source tree, but writeback occurs
827 ;; to a new local file. Then if re-reading we read the local copy of the cache.
828 ;; This should allow multiple builds to happen (via make-all-targets.sh) in a
829 ;; single source tree. If exactly one target is built, we can mv the local file
830 ;; on top of the source file. For more than one, we could either merge them
831 ;; or just ignore any modifications.
832 (let* ((base "xfloat-math.lisp-expr")
833 (final (concatenate 'string "output/" base))
834 (local (concatenate 'string *host-obj-prefix* base)))
835 (pathname
836 (ecase direction
837 (:input (if (probe-file local) local final))
838 (:output local)))))
840 (defun count-lines-of (pathname &aux (n 0))
841 (with-open-file (f pathname)
842 (loop (let ((line (read-line f nil)))
843 (if line (incf n) (return n))))))
845 (defmacro with-math-journal (&body body)
846 `(let* ((table *math-ops-memoization*)
847 (memo (cons table (hash-table-count table))))
848 (assert (atom table)) ; prevent nested use of this macro
849 (let ((*math-ops-memoization* memo))
850 ,@body)
851 (when nil ; *compile-verbose*
852 (funcall (intern "SHOW-INTERNED-NUMBERS" "SB-IMPL") *standard-output*))
853 (when (> (hash-table-count table) (cdr memo))
854 (let ((filename (math-journal-pathname :output)))
855 (with-open-file (stream filename :direction :output
856 :if-exists :supersede)
857 (funcall (intern "DUMP-MATH-MEMOIZATION-TABLE" "SB-IMPL")
858 table stream))
859 ;; Enforce absence of spurious newlines from pretty-printing or whatever
860 ;; If this assertion is wrong on other lisps we can just remove it
861 (assert (= (count-lines-of filename) (+ (hash-table-count table) 5)))
862 (format t "~&; Math journal: wrote ~S (~d entries)"
863 filename (hash-table-count table))))))
865 ;;;; One more journal file because the math file isn't enough
866 ;;; Define this before renaming the SB- packages
867 ;;; A non-parallelized compile using a sufficiently new SBCL as the host
868 ;;; will use a paravirtualized implementation of generate-perfect-hash-sexpr
869 ;;; which is to say, it just uses the host; as a side-effect it records
870 ;;; the generated string so that we can replay it for any host
871 ;;; or for parallelized build.
872 (defvar *perfect-hash-generator-mode* :PLAYBACK)
873 (defvar *perfect-hash-generator-memo* nil)
875 ;;; A separate file is used for each possible value of N-FIXNUM-BITS.
876 ;;; Therefore any particular set of symbols appears at most once per file.
877 (defun perfect-hash-generator-journal (direction)
878 (let* ((bits (symbol-value (intern "N-FIXNUM-BITS" "SB-VM")))
879 (stem (ecase bits
880 ((30 61 63)
881 (format nil "xperfecthash~D.lisp-expr" bits)))))
882 (ecase direction
883 (:input stem)
884 (:output (if (search "/xbuild/" *host-obj-prefix*)
885 ;; parallel build writes to a subdirectory
886 (concatenate 'string *target-obj-prefix* stem)
887 ;; normal build writes the file in place
888 stem)))))
890 (defun perfect-hash-generator-program ()
891 ;; The path depends on what the host is, not what the target is
892 #+unix "tools-for-build/perfecthash"
893 #+win32 "tools-for-build/perfecthash.exe")
895 #+sbcl (when (and (probe-file (perfect-hash-generator-program))
896 (find-symbol "RUN-PROGRAM" "SB-EXT"))
897 (pushnew :use-host-hash-generator cl:*features*)
898 (setq *perfect-hash-generator-mode* :RECORD))
900 ;;; I want this to work using the host-native readtable if sb-cold:*xc-readtable*
901 ;;; isn't established. The caller should bind *READTABLE* to ours if reading
902 ;;; on a non-SBCL host; it's purposely not done here.
903 (defun preload-perfect-hash-generator (pathname)
904 (with-open-file (stream pathname :if-does-not-exist nil)
905 (when stream
906 (let ((entries (let ((*read-base* 16)) (read stream)))
907 (uniqueness-checker (make-hash-table :test 'equalp))
908 (errors 0)
909 (linenum 0))
910 (setq *perfect-hash-generator-memo*
911 ;; Compute the XOR of all the hashes of each entry as a quick pass/fail
912 ;; when searching, assuming thst EQUALP compares (CAR CONS) before
913 ;; the CDR, which is almost surely, though not necessarily, what it does.
914 (mapcar (lambda (entry)
915 (incf linenum)
916 (destructuring-bind (array identifier expression) entry
917 ;; ARRAY is read as simple-vector, not UB32.
918 ;; (It actually doesn't matter how it's stored in memory)
919 (setq array (coerce array '(simple-array (unsigned-byte 32) (*))))
920 ;; assert that the entry was stored in canonical form
921 (assert (equalp array (sort (copy-seq array) #'<)))
922 ;; assert that there are not redundant lines.
923 ;; (Changing the pretty-printing from C must not write
924 ;; a distinct line if its key already existed)
925 (let ((existsp (gethash array uniqueness-checker)))
926 (cond ((not existsp)
927 (setf (gethash array uniqueness-checker)
928 (list expression linenum)))
930 (warn "~X maps to~%~{~S from line ~D~}~%~S from line ~D~%"
931 array existsp expression linenum)
932 (incf errors))))
933 (let ((digest (reduce #'logxor array)))
934 (list* (cons digest array) identifier expression))))
935 entries))
936 (when (plusp errors)
937 (error "hash generator duplicates: ~D" errors))))))
938 (compile 'preload-perfect-hash-generator)
940 (defun emulate-generate-perfect-hash-sexpr (array identifier digest)
941 (declare #-use-host-hash-generator (ignore identifier))
942 (let (computed)
943 (declare (ignorable computed))
944 ;; Entries are written to disk with hashes sorted in ascending order so that
945 ;; comparing as sets can be done using EQUALP.
946 ;; Sort nondestructively in case something else looks at the value as supplied.
947 (let* ((canonical-array (sort (copy-seq array) #'<))
948 (match (assoc (cons digest canonical-array) *perfect-hash-generator-memo*
949 :test #'equalp)))
950 (when match
951 (return-from emulate-generate-perfect-hash-sexpr (cddr match)))
952 (ecase *perfect-hash-generator-mode*
953 (:playback
954 (error "perfect hash file is missing a needed entry for ~x" array))
955 (:record
956 ;; This will only display anything when we didn't have the data,
957 ;; so it's actually not too "noisy" in a normal build.
958 #+use-host-hash-generator
959 (let ((output (make-string-output-stream))
960 (process
961 (sb-ext:run-program (perfect-hash-generator-program)
962 '("perfecthash")
963 ;; win32 misbehaves with :input string-stream
964 :input :stream :output :stream
965 :wait nil
966 :allow-other-keys t
967 :use-posix-spawn t)))
968 (format (sb-ext:process-input process) "~{~X~%~}" (coerce array 'list))
969 (close (sb-ext:process-input process))
970 (loop for char = (read-char (sb-ext:process-output process) nil)
971 while char
972 do (write-char char output))
973 (sb-ext:process-wait process)
974 (sb-ext:process-close process)
975 (unless (zerop (sb-ext:process-exit-code process))
976 (error "Error running perfecthash: exit code ~D"
977 (sb-ext:process-exit-code process)))
978 (let* ((string (get-output-stream-string output))
979 ;; don't need the final newline, it looks un-lispy in the file
980 (l (length string)))
981 (assert (char= (char string (1- l)) #\newline))
982 (setq computed (subseq string 0 (1- l))))
983 (let ((*print-right-margin* 200) (*print-level* nil) (*print-length* nil))
984 (format t "~&Recording perfect hash:~%~S~%~X~%"
985 identifier array))
986 (setf *perfect-hash-generator-memo*
987 (nconc *perfect-hash-generator-memo*
988 (list (list* (cons digest canonical-array)
989 identifier
990 computed))))
991 computed))))))
993 ;;; Unlike xfloat-math which expresses universal truths, the perfect-hash file
994 ;;; expresses facts about the behavior of a _particular_ SBCL revision.
995 ;;; It was overly challenging to alter the calc-symbol-name-hash algorithm
996 ;;; without being forced to re-run every cross-build to determine what the 32-bit
997 ;;; inputs would be to the perfect hash generator. By storing a representations of
998 ;;; objects that contributed to the key calculation, we can in theory recreate the
999 ;;; perfect hash file for all relevant objects without a rebuild under every
1000 ;;; combination of architectures and features. A few difficulties:
1001 ;;; * Hashes should be emitted in base 16 because the C program wants that,
1002 ;;; but our extended array syntax uses base 10 since it's more natural.
1003 ;;; i..e "#A((16) (unsigned-byte 8) ...)"
1004 ;;; * Packages have to exist when the file is read, so we can't read symbols
1005 ;;; into an architecture-specific package like sb-arm-asm.
1006 ;;; Since symbol-name-hash is based solely on print-name, it's irrelevant what
1007 ;;; the package is, so it's always OK to use keywords.
1009 ;;; I screwed this up multiple differerent ways when developing it.
1010 ;;; For example the symbol ADD was getting printed without a colon, and reparsed
1011 ;;; as 2781 in base 16.
1012 ;;; Finally I hit upon a solution which solves just about everything:
1013 ;;; write the identifying information as a string, which works around nonexistence
1014 ;;; of the following, among others:
1015 ;;; - symbol SB-KERNEL:SIMD-PACK-TYPE for #-x86-64
1016 ;;; - symbol SB-KERNEL:HANDLE-WIN32-EXCEPTION for #-win32
1017 ;;; - package SB-APROF for #-x86-64
1019 ;;; Separating the files by N-FIXNUM-TAG-BITS works to nearly guarantee
1020 ;;; that any particular set of symbols appears once and once only, so you don't
1021 ;;; have to wonder why it appears more than once, and under what circumstance
1022 ;;; the hashes should be different for the same symbols.
1023 ;;; Unfortunately, that was too aspirational. The problem stems from NIL in a
1024 ;;; list of symbols. Since the address of NIL depends on the architecture,
1025 ;;; and not only that, the particular *FEATURES*, we might have different
1026 ;;; hashes for NIL. Like without sb-thread, there will be an alloc-region
1027 ;;; placed in static space below NIL which shifts NIL's address higher,
1028 ;;; which changes its hash.
1029 (defun save-perfect-hashfuns (pathname entries)
1030 (with-open-file (*standard-output* pathname
1031 :direction :output
1032 :if-exists :supersede
1033 :if-does-not-exist :create)
1034 (write-string "(
1036 (let ((*print-pretty* t) (*print-length* nil) (*print-level* nil)
1037 (*print-lines* nil) (*print-right-margin* 128))
1038 (dolist (entry entries)
1039 (destructuring-bind ((digest . array) identifier . string) entry
1040 (declare (ignore digest))
1041 (unless (stringp identifier)
1042 ;; If this entry was read from the file, it's already a string
1043 (setq identifier
1044 (let ((*package* (find-package "SB-KERNEL")))
1045 (write-to-string identifier :escape :t :pretty nil))))
1046 (format t "(~X~% ~S~% ~S)~%" array identifier string))))
1047 (write-string ")
1048 ;; EOF
1049 ")))
1050 (compile 'save-perfect-hashfuns)
1052 (defun update-perfect-hashfuns (sources destination)
1053 (flet ((load-file (pathname)
1054 (mapcar (lambda (entry)
1055 (destructuring-bind (array identifier expression) entry
1056 (setq array (coerce array '(simple-array (unsigned-byte 32) (*))))
1057 (assert (equalp array (sort (copy-seq array) #'<)))
1058 (let ((digest (reduce #'logxor array)))
1059 (list* (cons digest array) identifier expression))))
1060 (with-open-file (stream pathname :if-does-not-exist nil)
1061 (when stream
1062 (let ((*read-base* 16)) (read stream))))))
1063 (compare (a1 a2)
1064 (do ((i 0 (1+ i)))
1065 ((or (= i (length a1)) (= i (length a2)))
1066 (= i (length a1)))
1067 (unless (= (aref a1 i) (aref a2 i))
1068 (return (< (aref a1 i) (aref a2 i)))))))
1069 (let ((entries (load-file destination)))
1070 (dolist (source sources)
1071 (dolist (entry (load-file source))
1072 (unless (assoc (car entry) entries :test #'equalp)
1073 (push entry entries))))
1074 (setq entries (sort entries #'compare :key #'cdar))
1075 (save-perfect-hashfuns destination entries))))
1077 (defun maybe-save-perfect-hashfuns-for-playback ()
1078 ;; Check again for corruption
1079 (let ((uniqueness-checker (make-hash-table :test 'equalp)))
1080 (dolist (entry *perfect-hash-generator-memo*)
1081 (let ((array (cdar entry)))
1082 (assert (not (gethash array uniqueness-checker)))
1083 (setf (gethash array uniqueness-checker) t))))
1084 #+use-host-hash-generator
1085 (when (eq *perfect-hash-generator-mode* :record)
1086 (save-perfect-hashfuns (perfect-hash-generator-journal :output)
1087 *perfect-hash-generator-memo*))
1090 ;;;; Please avoid writing "consecutive" (un-nested) reader conditionals
1091 ;;;; in this file, whether for the same or different feature test.
1092 ;;;; The following example prints 3 different results in 3 different lisp
1093 ;;;; implementations all of which have feature :linux (and not :nofeat).
1095 (let ((i 0))
1096 (dolist (s '("#-nofeat #+linux a b c d e"
1097 "#-nofeat #-linux a b c d e"
1098 "#+nofeat #-linux a b c d e"
1099 "#+nofeat #+linux a b c d e"
1100 "#+linux #-nofeat a b c d e"
1101 "#-linux #-nofeat a b c d e"
1102 "#-linux #+nofeat a b c d e"
1103 "#+linux #+nofeat a b c d e"
1104 "#+linux #-linux a b c d e"
1105 "#-linux #-linux a b c d e"
1106 "#-linux #+linux a b c d e"
1107 "#+linux #+linux a b c d e"
1108 "#+nofeat #-nofeat a b c d e"
1109 "#-nofeat #-nofeat a b c d e"
1110 "#-nofeat #+nofeat a b c d e"
1111 "#+nofeat #+nofeat a b c d e"))
1112 (format t "test ~2d: ~a ~s~%"
1113 (incf i)
1114 (let ((stream (make-string-input-stream s)))
1115 (list (read stream) (read stream) (read stream)))
1116 s)))