New functions for immobile space introspection
[sbcl.git] / src / code / room.lisp
blob6cb8ba28884531f73598e4bd6c9d7ffaac049c10
1 ;;;; heap-grovelling memory usage stuff
3 ;;;; This software is part of the SBCL system. See the README file for
4 ;;;; more information.
5 ;;;;
6 ;;;; This software is derived from the CMU CL system, which was
7 ;;;; written at Carnegie Mellon University and released into the
8 ;;;; public domain. The software is in the public domain and is
9 ;;;; provided with absolutely no warranty. See the COPYING and CREDITS
10 ;;;; files for more information.
12 (in-package "SB!VM")
15 ;;;; type format database
17 (eval-when (:compile-toplevel :load-toplevel :execute)
19 (def!struct (room-info (:constructor make-room-info (name kind))
20 (:copier nil))
21 ;; the name of this type
22 (name nil :type symbol :read-only t)
23 ;; kind of type (how to reconstitute an object)
24 (kind (missing-arg)
25 :type (member :other :tiny-other :closure :instance :list
26 :code :vector-nil :weak-pointer)
27 :read-only t))
28 (!set-load-form-method room-info (:xc))
30 (defun room-info-type-name (info)
31 (if (specialized-array-element-type-properties-p info)
32 (saetp-primitive-type-name info)
33 (room-info-name info))))
35 (eval-when (:compile-toplevel :execute)
37 (defvar *meta-room-info* (make-array 256 :initial-element nil))
39 (dolist (obj *primitive-objects*)
40 (let ((widetag (primitive-object-widetag obj))
41 (lowtag (primitive-object-lowtag obj))
42 (name (primitive-object-name obj)))
43 (when (and (eq lowtag 'other-pointer-lowtag)
44 (not (member widetag '(t nil)))
45 (not (eq name 'weak-pointer)))
46 (setf (svref *meta-room-info* (symbol-value widetag))
47 (make-room-info name (if (member name '(fdefn symbol))
48 :tiny-other
49 :other))))))
51 (dolist (code (list #!+sb-unicode complex-character-string-widetag
52 complex-base-string-widetag simple-array-widetag
53 complex-bit-vector-widetag complex-vector-widetag
54 complex-array-widetag complex-vector-nil-widetag))
55 (setf (svref *meta-room-info* code)
56 (make-room-info 'array-header :other)))
58 (setf (svref *meta-room-info* bignum-widetag)
59 (make-room-info 'bignum :other))
61 (setf (svref *meta-room-info* closure-widetag)
62 (make-room-info 'closure :closure))
64 (dotimes (i (length *specialized-array-element-type-properties*))
65 (let ((saetp (aref *specialized-array-element-type-properties* i)))
66 (when (saetp-specifier saetp) ;; SIMPLE-ARRAY-NIL is a special case.
67 (setf (svref *meta-room-info* (saetp-typecode saetp)) saetp))))
69 (setf (svref *meta-room-info* simple-array-nil-widetag)
70 (make-room-info 'simple-array-nil :vector-nil))
72 (setf (svref *meta-room-info* code-header-widetag)
73 (make-room-info 'code :code))
75 (setf (svref *meta-room-info* instance-widetag)
76 (make-room-info 'instance :instance))
78 (setf (svref *meta-room-info* funcallable-instance-widetag)
79 (make-room-info 'funcallable-instance :closure))
81 (setf (svref *meta-room-info* weak-pointer-widetag)
82 (make-room-info 'weak-pointer :weak-pointer))
84 (let ((cons-info (make-room-info 'cons :list)))
85 ;; A cons consists of two words, both of which may be either a
86 ;; pointer or immediate data. According to the runtime this means
87 ;; either a fixnum, a character, an unbound-marker, a single-float
88 ;; on a 64-bit system, or a pointer.
89 (dotimes (i (ash 1 (- n-widetag-bits n-fixnum-tag-bits)))
90 (setf (svref *meta-room-info* (ash i n-fixnum-tag-bits)) cons-info))
92 (dotimes (i (ash 1 (- n-widetag-bits n-lowtag-bits)))
93 (setf (svref *meta-room-info* (logior (ash i n-lowtag-bits)
94 instance-pointer-lowtag))
95 cons-info)
96 (setf (svref *meta-room-info* (logior (ash i n-lowtag-bits)
97 list-pointer-lowtag))
98 cons-info)
99 (setf (svref *meta-room-info* (logior (ash i n-lowtag-bits)
100 fun-pointer-lowtag))
101 cons-info)
102 (setf (svref *meta-room-info* (logior (ash i n-lowtag-bits)
103 other-pointer-lowtag))
104 cons-info))
106 (setf (svref *meta-room-info* character-widetag) cons-info)
108 (setf (svref *meta-room-info* unbound-marker-widetag) cons-info)
110 ;; Single-floats are immediate data on 64-bit systems.
111 #!+64-bit
112 (setf (svref *meta-room-info* single-float-widetag) cons-info))
114 ) ; EVAL-WHEN
116 (define-load-time-global *room-info*
117 ;; SAETP instances don't dump properly from XC (or possibly
118 ;; normally), and we'd rather share structure with the master copy
119 ;; if we can anyway, so...
120 (make-array 256
121 :initial-contents
122 #.`(list
123 ,@(map 'list
124 (lambda (info)
125 (if (specialized-array-element-type-properties-p info)
126 `(aref *specialized-array-element-type-properties*
127 ,(position info *specialized-array-element-type-properties*))
128 info))
129 *meta-room-info*))))
131 (eval-when (:compile-toplevel :load-toplevel :execute)
133 (defglobal **heap-spaces**
134 #1='((:dynamic "Dynamic space" sb!kernel:dynamic-usage)
135 (:read-only "Read-only space" sb!kernel::read-only-space-usage)
136 (:static "Static space" sb!kernel::static-space-usage)))
138 (defglobal **stack-spaces**
139 #2='((:control-stack "Control stack" sb!kernel::control-stack-usage)
140 (:binding-stack "Binding stack" sb!kernel::binding-stack-usage)))
142 (defglobal **spaces**
143 (append #1# #2#)))
145 (deftype spaces ()
146 `(member #!+immobile-space :immobile ,@(mapcar #'first **heap-spaces**)))
149 ;;;; MAP-ALLOCATED-OBJECTS
151 ;;; Since they're represented as counts of words, we should never
152 ;;; need bignums to represent these:
153 (declaim (type fixnum
154 *static-space-free-pointer*
155 *read-only-space-free-pointer*))
157 #!-sb-fluid
158 (declaim (inline current-dynamic-space-start))
159 (defun current-dynamic-space-start ()
160 #!+(and gencgc relocatable-heap)
161 (extern-alien "DYNAMIC_SPACE_START" unsigned-long)
162 #!+(and gencgc (not relocatable-heap))
163 sb!vm:dynamic-space-start
164 #!-gencgc (extern-alien "current_dynamic_space" unsigned-long))
166 (defun space-bounds (space)
167 (declare (type spaces space))
168 (ecase space
169 (:static
170 (values (int-sap static-space-start)
171 (int-sap (ash *static-space-free-pointer* n-fixnum-tag-bits))))
172 (:read-only
173 (values (int-sap read-only-space-start)
174 (int-sap (ash *read-only-space-free-pointer* n-fixnum-tag-bits))))
175 #!+immobile-space
176 (:immobile
177 (values (int-sap immobile-space-start)
178 (int-sap (ash *immobile-space-free-pointer* n-fixnum-tag-bits))))
179 (:dynamic
180 (values (int-sap (current-dynamic-space-start))
181 (dynamic-space-free-pointer)))))
183 ;;; Return the total number of bytes used in SPACE.
184 (defun space-bytes (space)
185 (multiple-value-bind (start end) (space-bounds space)
186 (- (sap-int end) (sap-int start))))
188 ;;; Round SIZE (in bytes) up to the next dualword boundary. A dualword
189 ;;; is eight bytes on platforms with 32-bit word size and 16 bytes on
190 ;;; platforms with 64-bit word size.
191 #!-sb-fluid (declaim (inline round-to-dualword))
192 (defun round-to-dualword (size)
193 (logand (the word (+ size lowtag-mask)) (lognot lowtag-mask)))
195 ;;; Return the vector OBJ, its WIDETAG, and the number of octets
196 ;;; required for its storage (including padding and alignment).
197 (defun reconstitute-vector (obj saetp)
198 (declare (type (simple-array * (*)) obj)
199 (type specialized-array-element-type-properties saetp))
200 (let* ((length (+ (length obj)
201 (saetp-n-pad-elements saetp)))
202 (n-bits (saetp-n-bits saetp))
203 (alignment-pad (floor 7 n-bits))
204 (n-data-octets (if (>= n-bits 8)
205 (* length (ash n-bits -3))
206 (ash (* (+ length alignment-pad)
207 n-bits)
208 -3))))
209 (values obj
210 (saetp-typecode saetp)
211 (round-to-dualword (+ (* vector-data-offset n-word-bytes)
212 n-data-octets)))))
214 ;;; Given the address (untagged, aligned, and interpreted as a FIXNUM)
215 ;;; of a lisp object, return the object, its "type code" (either
216 ;;; LIST-POINTER-LOWTAG or a header widetag), and the number of octets
217 ;;; required for its storage (including padding and alignment). Note
218 ;;; that this function is designed to NOT CONS, even if called
219 ;;; out-of-line.
220 (defun reconstitute-object (address)
221 (let* ((object-sap (int-sap (get-lisp-obj-address address)))
222 (header (sap-ref-word object-sap 0))
223 (widetag (logand header widetag-mask))
224 (header-value (ash header (- n-widetag-bits)))
225 (info (svref *room-info* widetag)))
226 (macrolet
227 ((boxed-size (header-value)
228 `(round-to-dualword (ash (1+ ,header-value) word-shift)))
229 (tagged-object (tag)
230 `(%make-lisp-obj (logior ,tag (get-lisp-obj-address address)))))
231 (cond
232 ;; Pick off arrays, as they're the only plausible cause for
233 ;; a non-nil, non-ROOM-INFO object as INFO.
234 ((specialized-array-element-type-properties-p info)
235 (reconstitute-vector (tagged-object other-pointer-lowtag) info))
237 ((null info)
238 (error "Unrecognized widetag #x~2,'0X in reconstitute-object"
239 widetag))
242 (case (room-info-kind info)
243 (:list
244 (values (tagged-object list-pointer-lowtag)
245 list-pointer-lowtag
246 (* 2 n-word-bytes)))
248 (:closure ; also funcallable-instance
249 (values (tagged-object fun-pointer-lowtag)
250 widetag
251 (boxed-size (logand header-value short-header-max-words))))
253 (:instance
254 (values (tagged-object instance-pointer-lowtag)
255 widetag
256 (boxed-size (logand header-value short-header-max-words))))
258 (:other
259 (values (tagged-object other-pointer-lowtag)
260 widetag
261 (boxed-size header-value)))
263 (:tiny-other
264 (values (tagged-object other-pointer-lowtag)
265 widetag
266 (boxed-size (logand header-value #xFF))))
268 (:vector-nil
269 (values (tagged-object other-pointer-lowtag)
270 simple-array-nil-widetag
271 (* 2 n-word-bytes)))
273 (:weak-pointer
274 (values (tagged-object other-pointer-lowtag)
275 weak-pointer-widetag
276 (round-to-dualword
277 (* weak-pointer-size
278 n-word-bytes))))
280 (:code
281 (let ((c (tagged-object other-pointer-lowtag)))
282 (values c
283 code-header-widetag
284 (round-to-dualword
285 (+ (* (logand header-value short-header-max-words)
286 n-word-bytes)
287 (%code-code-size (truly-the code-component c)))))))
289 (error "Unrecognized room-info-kind ~S in reconstitute-object"
290 (room-info-kind info)))))))))
292 ;;; Iterate over all the objects in the contiguous block of memory
293 ;;; with the low address at START and the high address just before
294 ;;; END, calling FUN with the object, the object's type code, and the
295 ;;; object's total size in bytes, including any header and padding.
296 ;;; START and END are untagged, aligned memory addresses interpreted
297 ;;; as FIXNUMs (unlike SAPs or tagged addresses, these will not cons).
298 (defun map-objects-in-range (fun start end)
299 (declare (type function fun))
300 ;; If START is (unsigned) greater than END, then we have somehow
301 ;; blown past our endpoint.
302 (aver (<= (get-lisp-obj-address start)
303 (get-lisp-obj-address end)))
304 (unless (= start end)
305 (multiple-value-bind
306 (obj typecode size)
307 (reconstitute-object start)
308 (aver (zerop (logand n-lowtag-bits size)))
309 (let ((next-start
310 ;; This special little dance is to add a number of octets
311 ;; (and it had best be a number evenly divisible by our
312 ;; allocation granularity) to an unboxed, aligned address
313 ;; masquerading as a fixnum. Without consing.
314 (%make-lisp-obj
315 (mask-field (byte #.n-word-bits 0)
316 (+ (get-lisp-obj-address start)
317 size)))))
318 (funcall fun obj typecode size)
319 (map-objects-in-range fun next-start end)))))
321 ;;; Access to the GENCGC page table for better precision in
322 ;;; MAP-ALLOCATED-OBJECTS
323 #!+gencgc
324 (progn
325 (define-alien-type (struct page)
326 (struct page
327 ;; To cut down the size of the page table, the scan_start_offset
328 ;; - a/k/a "start" - is measured in 4-byte integers regardless
329 ;; of word size. This is fine for 32-bit address space,
330 ;; but if 64-bit then we have to scale the value. Additionally
331 ;; there is a fallback for when even the scaled value is too big.
332 ;; (None of this matters to Lisp code for the most part)
333 (start #!+64-bit (unsigned 32) #!-64-bit signed)
334 ;; On platforms with small enough GC pages, this field
335 ;; will be a short. On platforms with larger ones, it'll
336 ;; be an int.
337 ;; Measured in bytes; the low bit has to be masked off.
338 (bytes-used (unsigned
339 #.(if (typep gencgc-card-bytes '(unsigned-byte 16))
341 32)))
342 (flags (unsigned 8))
343 (gen (signed 8))))
344 #!+immobile-space
345 (progn
346 (define-alien-type (struct immobile-page)
347 ;; ... and yet another place for Lisp to become out-of-sync with C.
348 (struct immobile-page
349 (flags (unsigned 8))
350 (obj-spacing (unsigned 8))
351 (obj-size (unsigned 8))
352 (generations (unsigned 8))
353 (free-index (unsigned 32))
354 (page-link (unsigned 16))
355 (prior-free-index (unsigned 16))))
356 (define-alien-variable "fixedobj_pages" (* (struct immobile-page))))
357 (declaim (inline find-page-index))
358 (define-alien-routine ("ext_find_page_index" find-page-index)
359 long (index signed))
360 (define-alien-variable "last_free_page" sb!kernel::page-index-t)
361 (define-alien-variable "page_table" (* (struct page))))
363 (declaim (inline code-header-words))
364 (defun code-header-words (code)
365 (logand (get-header-data code) short-header-max-words))
367 ;;; Iterate over all the objects allocated in each of the SPACES, calling FUN
368 ;;; with the object, the object's type code, and the object's total size in
369 ;;; bytes, including any header and padding. As a special case, if exactly one
370 ;;; space named :ALL is requested, then map over the known spaces.
371 (defun map-allocated-objects (fun &rest spaces)
372 (declare (type function fun))
373 (when (and (= (length spaces) 1) (eq (first spaces) :all))
374 (return-from map-allocated-objects
375 (map-allocated-objects fun
376 :read-only :static
377 #!+immobile-space :immobile
378 :dynamic)))
379 ;; You can't specify :ALL and also a list of spaces. Check that up front.
380 (do-rest-arg ((space) spaces) (the spaces space))
381 (flet ((do-1-space (space)
382 (ecase space
383 (:static
384 ;; Static space starts with NIL, which requires special
385 ;; handling, as the header and alignment are slightly off.
386 (multiple-value-bind (start end) (space-bounds space)
387 (funcall fun nil symbol-widetag (* 8 n-word-bytes))
388 (map-objects-in-range fun
389 (%make-lisp-obj (+ (* 8 n-word-bytes)
390 (sap-int start)))
391 (%make-lisp-obj (sap-int end)))))
393 ((:read-only #!-gencgc :dynamic)
394 ;; Read-only space (and dynamic space on cheneygc) is a block
395 ;; of contiguous allocations.
396 (multiple-value-bind (start end) (space-bounds space)
397 (map-objects-in-range fun
398 (%make-lisp-obj (sap-int start))
399 (%make-lisp-obj (sap-int end)))))
401 #!+immobile-space
402 (:immobile
403 ;; Filter out filler objects. These either look like cons cells
404 ;; in fixedobj subspace, or code without enough header words
405 ;; in varyobj subspace. (cf 'immobile_filler_p' in gc-internal.h)
406 (dx-flet ((filter (obj type size)
407 (unless (or (and (code-component-p obj)
408 (eql (code-header-words obj) 2))
409 (consp obj))
410 (funcall fun obj type size))))
411 (map-immobile-objects #'filter :fixed :variable)))
413 #!+gencgc
414 (:dynamic
415 ;; Dynamic space on gencgc requires walking the GC page tables
416 ;; in order to determine what regions contain objects.
418 ;; We explicitly presume that any pages in an allocation region
419 ;; that are in-use have a BYTES-USED of GENCGC-CARD-BYTES
420 ;; (indicating a full page) or an otherwise-valid BYTES-USED.
421 ;; We also presume that the pages of an open allocation region
422 ;; after the first page, and any pages that are unallocated,
423 ;; have a BYTES-USED of zero. GENCGC seems to guarantee this.
425 ;; Our procedure is to scan forward through the page table,
426 ;; maintaining an "end pointer" until we reach a page where
427 ;; BYTES-USED is not GENCGC-CARD-BYTES or we reach
428 ;; LAST-FREE-PAGE. We then MAP-OBJECTS-IN-RANGE if the range
429 ;; is not empty, and proceed to the next page (unless we've hit
430 ;; LAST-FREE-PAGE). We happily take advantage of the fact that
431 ;; MAP-OBJECTS-IN-RANGE will simply return if passed two
432 ;; coincident pointers for the range.
434 ;; FIXME: WITHOUT-GCING prevents a GC flip, but doesn't prevent
435 ;; closing allocation regions and opening new ones. This may
436 ;; prove to be an issue with concurrent systems, or with
437 ;; spectacularly poor timing for closing an allocation region
438 ;; in a single-threaded system.
440 (loop
441 with page-size = (ash gencgc-card-bytes (- n-fixnum-tag-bits))
442 ;; This magic dance gets us an unboxed aligned pointer as a
443 ;; FIXNUM.
444 with start = (%make-lisp-obj (current-dynamic-space-start))
445 with end = start
447 ;; This is our page range. The type constraint is far too generous,
448 ;; but it does its job of producing efficient code.
449 for page-index
450 of-type (integer -1 (#.(/ (ash 1 n-machine-word-bits) gencgc-card-bytes)))
451 from 0 below last-free-page
452 for next-page-addr from (+ start page-size) by page-size
453 for page-bytes-used
454 ;; The low bits of bytes-used is the need-to-zero flag.
455 = (logandc1 1 (slot (deref page-table page-index) 'bytes-used))
457 when (< page-bytes-used gencgc-card-bytes)
458 do (progn
459 (incf end (ash page-bytes-used (- n-fixnum-tag-bits)))
460 (map-objects-in-range fun start end)
461 (setf start next-page-addr)
462 (setf end next-page-addr))
463 else do (incf end page-size)
465 finally (map-objects-in-range fun start end))))))
466 (do-rest-arg ((space) spaces)
467 (if (eq space :dynamic)
468 (without-gcing (do-1-space space))
469 (do-1-space space)))))
471 ;;;; MEMORY-USAGE
473 ;;; Return a list of 3-lists (bytes object type-name) for the objects
474 ;;; allocated in Space.
475 (defun type-breakdown (space)
476 (declare (muffle-conditions t))
477 (let ((sizes (make-array 256 :initial-element 0 :element-type '(unsigned-byte #.n-word-bits)))
478 (counts (make-array 256 :initial-element 0 :element-type '(unsigned-byte #.n-word-bits))))
479 (map-allocated-objects
480 (lambda (obj type size)
481 (declare (word size) (optimize (speed 3)) (ignore obj))
482 (incf (aref sizes type) size)
483 (incf (aref counts type)))
484 space)
486 (let ((totals (make-hash-table :test 'eq)))
487 (dotimes (i 256)
488 (let ((total-count (aref counts i)))
489 (unless (zerop total-count)
490 (let* ((total-size (aref sizes i))
491 (name (room-info-type-name (aref *room-info* i)))
492 (found (ensure-gethash name totals (list 0 0 name))))
493 (incf (first found) total-size)
494 (incf (second found) total-count)))))
496 (collect ((totals-list))
497 (maphash (lambda (k v)
498 (declare (ignore k))
499 (totals-list v))
500 totals)
501 (sort (totals-list) #'> :key #'first)))))
503 ;;; Handle the summary printing for MEMORY-USAGE. Totals is a list of lists
504 ;;; (space-name . totals-for-space), where totals-for-space is the list
505 ;;; returned by TYPE-BREAKDOWN.
506 (defun print-summary (spaces totals)
507 (let ((summary (make-hash-table :test 'eq))
508 (space-count (length spaces)))
509 (dolist (space-total totals)
510 (dolist (total (cdr space-total))
511 (push (cons (car space-total) total)
512 (gethash (third total) summary))))
514 (collect ((summary-totals))
515 (maphash (lambda (k v)
516 (declare (ignore k))
517 (let ((sum 0))
518 (declare (unsigned-byte sum))
519 (dolist (space-total v)
520 (incf sum (first (cdr space-total))))
521 (summary-totals (cons sum v))))
522 summary)
524 (format t "~2&Summary of space~P: ~(~{~A ~}~)~%" space-count spaces)
525 (let ((summary-total-bytes 0)
526 (summary-total-objects 0))
527 (declare (unsigned-byte summary-total-bytes summary-total-objects))
528 (dolist (space-totals
529 (mapcar #'cdr (sort (summary-totals) #'> :key #'car)))
530 (let ((total-objects 0)
531 (total-bytes 0)
532 name)
533 (declare (unsigned-byte total-objects total-bytes))
534 (collect ((spaces))
535 (dolist (space-total space-totals)
536 (let ((total (cdr space-total)))
537 (setq name (third total))
538 (incf total-bytes (first total))
539 (incf total-objects (second total))
540 (spaces (cons (car space-total) (first total)))))
541 (format t "~%~A:~% ~:D bytes, ~:D object~:P"
542 name total-bytes total-objects)
543 (unless (= 1 space-count)
544 (dolist (space (spaces))
545 (format t ", ~D% ~(~A~)"
546 (round (* (cdr space) 100) total-bytes) (car space))))
547 (format t ".~%")
548 (incf summary-total-bytes total-bytes)
549 (incf summary-total-objects total-objects))))
550 (format t "~%Summary total:~% ~:D bytes, ~:D objects.~%"
551 summary-total-bytes summary-total-objects)))))
553 ;;; Report object usage for a single space.
554 (defun report-space-total (space-info cutoff)
555 (declare (list space-info) (type (or single-float null) cutoff))
556 (destructuring-bind (space . types) space-info
557 (format t "~2&Breakdown for ~(~A~) space:~%" space)
558 (let* ((total-bytes (reduce #'+ (mapcar #'first types)))
559 (bytes-width (decimal-with-grouped-digits-width total-bytes))
560 (total-objects (reduce #'+ (mapcar #'second types)))
561 (objects-width (decimal-with-grouped-digits-width total-objects))
562 (cutoff-point (if cutoff
563 (truncate (* (float total-bytes) cutoff))
565 (reported-bytes 0)
566 (reported-objects 0))
567 (declare (unsigned-byte total-objects total-bytes cutoff-point
568 reported-objects reported-bytes))
569 (flet ((type-usage (bytes objects name &optional note)
570 (format t " ~V:D bytes for ~V:D ~(~A~) object~2:*~P~*~
571 ~:[~; ~:*(~A)~]~%"
572 bytes-width bytes objects-width objects name note)))
573 (loop for (bytes objects name) in types do
574 (when (<= bytes cutoff-point)
575 (type-usage (- total-bytes reported-bytes)
576 (- total-objects reported-objects)
577 "other")
578 (return))
579 (incf reported-bytes bytes)
580 (incf reported-objects objects)
581 (type-usage bytes objects name))
582 (terpri)
583 (type-usage total-bytes total-objects space "space total")))))
585 ;;; Print information about the heap memory in use. PRINT-SPACES is a
586 ;;; list of the spaces to print detailed information for.
587 ;;; COUNT-SPACES is a list of the spaces to scan. For either one, T
588 ;;; means all spaces (i.e. :STATIC, :DYNAMIC and :READ-ONLY.) If
589 ;;; PRINT-SUMMARY is true, then summary information will be printed.
590 ;;; The defaults print only summary information for dynamic space. If
591 ;;; true, CUTOFF is a fraction of the usage in a report below which
592 ;;; types will be combined as OTHER.
593 (defun memory-usage (&key print-spaces (count-spaces '(:dynamic))
594 (print-summary t) cutoff)
595 (declare (type (or single-float null) cutoff))
596 (let* ((spaces (if (eq count-spaces t)
597 (mapcar #'first **heap-spaces**)
598 count-spaces))
599 (totals (mapcar (lambda (space)
600 (cons space (type-breakdown space)))
601 spaces)))
603 (dolist (space-total totals)
604 (when (or (eq print-spaces t)
605 (member (car space-total) print-spaces))
606 (report-space-total space-total cutoff)))
608 (when print-summary (print-summary spaces totals)))
610 (values))
612 ;;; Print a breakdown by instance type of all the instances allocated
613 ;;; in SPACE. If TOP-N is true, print only information for the
614 ;;; TOP-N types with largest usage.
615 (defun instance-usage (space &key (top-n 15))
616 (declare (type spaces space) (type (or fixnum null) top-n))
617 (format t "~2&~@[Top ~W ~]~(~A~) instance types:~%" top-n space)
618 (let ((totals (make-hash-table :test 'eq))
619 (total-objects 0)
620 (total-bytes 0))
621 (declare (unsigned-byte total-objects total-bytes))
622 (map-allocated-objects
623 (lambda (obj type size)
624 (declare (optimize (speed 3)))
625 (when (eql type instance-widetag)
626 (incf total-objects)
627 (let* ((classoid (layout-classoid (%instance-layout obj)))
628 (found (ensure-gethash classoid totals (cons 0 0)))
629 (size size))
630 (declare (fixnum size))
631 (incf total-bytes size)
632 (incf (the fixnum (car found)))
633 (incf (the fixnum (cdr found)) size))))
634 space)
635 (let* ((sorted (sort (%hash-table-alist totals) #'> :key #'cddr))
636 (interesting (if top-n
637 (subseq sorted 0 (min (length sorted) top-n))
638 sorted))
639 (bytes-width (decimal-with-grouped-digits-width total-bytes))
640 (objects-width (decimal-with-grouped-digits-width total-objects))
641 (types-width (reduce #'max interesting
642 :key (lambda (x) (length (symbol-name (classoid-name (first x)))))
643 :initial-value 0))
644 (printed-bytes 0)
645 (printed-objects 0))
646 (declare (unsigned-byte printed-bytes printed-objects))
647 (flet ((type-usage (type objects bytes)
648 (let ((name (etypecase type
649 (string type)
650 (classoid (symbol-name (classoid-name type))))))
651 (format t " ~V@<~A~> ~V:D bytes, ~V:D object~:P.~%"
652 (1+ types-width) name bytes-width bytes
653 objects-width objects))))
654 (loop for (type . (objects . bytes)) in interesting do
655 (incf printed-bytes bytes)
656 (incf printed-objects objects)
657 (type-usage type objects bytes))
658 (let ((residual-objects (- total-objects printed-objects))
659 (residual-bytes (- total-bytes printed-bytes)))
660 (unless (zerop residual-objects)
661 (type-usage "Other types" residual-bytes residual-objects)))
662 (type-usage (format nil "~:(~A~) instance total" space)
663 total-bytes total-objects))))
664 (values))
666 ;;;; PRINT-ALLOCATED-OBJECTS
668 (defun print-allocated-objects (space &key (percent 0) (pages 5)
669 type larger smaller count
670 (stream *standard-output*))
671 (declare (type (integer 0 99) percent) (type index pages)
672 (type stream stream) (type spaces space)
673 (type (or index null) type larger smaller count))
674 (multiple-value-bind (start-sap end-sap) (space-bounds space)
675 (let* ((space-start (sap-int start-sap))
676 (space-end (sap-int end-sap))
677 (space-size (- space-end space-start))
678 (pagesize (get-page-size))
679 (start (+ space-start (round (* space-size percent) 100)))
680 (printed-conses (make-hash-table :test 'eq))
681 (pages-so-far 0)
682 (count-so-far 0)
683 (last-page 0))
684 (declare (type word last-page start)
685 (fixnum pages-so-far count-so-far pagesize))
686 (labels ((note-conses (x)
687 (unless (or (atom x) (gethash x printed-conses))
688 (setf (gethash x printed-conses) t)
689 (note-conses (car x))
690 (note-conses (cdr x)))))
691 (map-allocated-objects
692 (lambda (obj obj-type size)
693 (let ((addr (get-lisp-obj-address obj)))
694 (when (>= addr start)
695 (when (if count
696 (> count-so-far count)
697 (> pages-so-far pages))
698 (return-from print-allocated-objects (values)))
700 (unless count
701 (let ((this-page (* (the (values word t)
702 (truncate addr pagesize))
703 pagesize)))
704 (declare (type word this-page))
705 (when (/= this-page last-page)
706 (when (< pages-so-far pages)
707 ;; FIXME: What is this? (ERROR "Argh..")? or
708 ;; a warning? or code that can be removed
709 ;; once the system is stable? or what?
710 (format stream "~2&**** Page ~W, address ~X:~%"
711 pages-so-far addr))
712 (setq last-page this-page)
713 (incf pages-so-far))))
715 (when (and (or (not type) (eql obj-type type))
716 (or (not smaller) (<= size smaller))
717 (or (not larger) (>= size larger)))
718 (incf count-so-far)
719 (case type
720 (#.code-header-widetag
721 (let ((dinfo (%code-debug-info obj)))
722 (format stream "~&Code object: ~S~%"
723 (if dinfo
724 (sb!c::compiled-debug-info-name dinfo)
725 "No debug info."))))
726 (#.symbol-widetag
727 (format stream "~&~S~%" obj))
728 (#.list-pointer-lowtag
729 (unless (gethash obj printed-conses)
730 (note-conses obj)
731 (let ((*print-circle* t)
732 (*print-level* 5)
733 (*print-length* 10))
734 (format stream "~&~S~%" obj))))
736 (fresh-line stream)
737 (let ((str (write-to-string obj :level 5 :length 10
738 :pretty nil)))
739 (unless (eql type instance-widetag)
740 (format stream "~S: " (type-of obj)))
741 (format stream "~A~%"
742 (subseq str 0 (min (length str) 60))))))))))
743 space))))
744 (values))
746 ;;;; LIST-ALLOCATED-OBJECTS, LIST-REFERENCING-OBJECTS
748 (defvar *ignore-after* nil)
750 (defun valid-obj (space x)
751 (or (not (eq space :dynamic))
752 ;; this test looks bogus if the allocator doesn't work linearly,
753 ;; which I suspect is the case for GENCGC. -- CSR, 2004-06-29
754 (< (get-lisp-obj-address x) (get-lisp-obj-address *ignore-after*))))
756 (defun maybe-cons (space x stuff)
757 (if (valid-obj space x)
758 (cons x stuff)
759 stuff))
761 (defun list-allocated-objects (space &key type larger smaller count
762 test)
763 (declare (type spaces space)
764 (type (or index null) larger smaller type count)
765 (type (or function null) test))
766 (unless *ignore-after*
767 (setq *ignore-after* (cons 1 2)))
768 (collect ((counted 0 1+))
769 (let ((res ()))
770 (map-allocated-objects
771 (lambda (obj obj-type size)
772 (when (and (or (not type) (eql obj-type type))
773 (or (not smaller) (<= size smaller))
774 (or (not larger) (>= size larger))
775 (or (not test) (funcall test obj)))
776 (setq res (maybe-cons space obj res))
777 (when (and count (>= (counted) count))
778 (return-from list-allocated-objects res))))
779 space)
780 res)))
782 ;;; Convert the descriptor into a SAP. The bits all stay the same, we just
783 ;;; change our notion of what we think they are.
785 ;;; Defining this here (as opposed to in 'debug-int' where it belongs)
786 ;;; is the path of least resistance to avoiding an inlining failure warning.
787 #!-sb-fluid (declaim (inline sb!di::descriptor-sap))
788 (defun sb!di::descriptor-sap (x)
789 (int-sap (get-lisp-obj-address x)))
791 ;;; Calls FUNCTION with all objects that have (possibly conservative)
792 ;;; references to them on current stack.
793 (defun map-stack-references (function)
794 (let ((end
795 (sb!di::descriptor-sap
796 #!+stack-grows-downward-not-upward *control-stack-end*
797 #!-stack-grows-downward-not-upward *control-stack-start*))
798 (sp (current-sp))
799 (seen nil))
800 (loop until #!+stack-grows-downward-not-upward (sap> sp end)
801 #!-stack-grows-downward-not-upward (sap< sp end)
802 do (multiple-value-bind (obj ok) (make-lisp-obj (sap-ref-word sp 0) nil)
803 (when (and ok (typep obj '(not (or fixnum character))))
804 (unless (member obj seen :test #'eq)
805 (funcall function obj)
806 (push obj seen))))
807 (setf sp
808 #!+stack-grows-downward-not-upward (sap+ sp n-word-bytes)
809 #!-stack-grows-downward-not-upward (sap+ sp (- n-word-bytes))))))
811 ;;; This interface allows one either to be agnostic of the referencing space,
812 ;;; or specify exactly one space, but not specify a list of spaces.
813 ;;; An upward-compatible change would be to assume a list, and call ENSURE-LIST.
814 (defun map-referencing-objects (fun space object)
815 (declare (type (or (eql :all) spaces) space))
816 (unless *ignore-after*
817 (setq *ignore-after* (cons 1 2)))
818 (flet ((ref-p (this widetag nwords) ; return T if 'this' references object
819 (when (listp this)
820 (return-from ref-p
821 (or (eq (car this) object) (eq (cdr this) object))))
822 (case widetag
823 ;; purely boxed objects
824 ((#.ratio-widetag #.complex-widetag #.value-cell-widetag
825 #.symbol-widetag #.weak-pointer-widetag
826 #.simple-array-widetag #.simple-vector-widetag
827 #.complex-array-widetag #.complex-vector-widetag
828 #.complex-bit-vector-widetag #.complex-vector-nil-widetag
829 #.complex-base-string-widetag
830 #!+sb-unicode #.complex-character-string-widetag))
831 ;; mixed boxed/unboxed objects
832 (#.code-header-widetag
833 (dotimes (i (code-n-entries this))
834 (let ((f (%code-entry-point this i)))
835 (when (or (eq f object)
836 (eq (%simple-fun-name f) object)
837 (eq (%simple-fun-arglist f) object)
838 (eq (%simple-fun-type f) object)
839 (eq (%simple-fun-info f) object))
840 (return-from ref-p t))))
841 (setq nwords (code-header-words this)))
842 (#.instance-widetag
843 (return-from ref-p
844 (or (eq (%instance-layout this) object)
845 (do-instance-tagged-slot (i this)
846 (when (eq (%instance-ref this i) object)
847 (return t))))))
848 (#.funcallable-instance-widetag
849 (let ((l (%funcallable-instance-layout this)))
850 (when (eq l object)
851 (return-from ref-p t))
852 (let ((bitmap (layout-bitmap l)))
853 (unless (eql bitmap -1)
854 ;; tagged slots precede untagged slots,
855 ;; so integer-length is the count of tagged slots.
856 (setq nwords (1+ (integer-length bitmap)))))))
857 (#.closure-widetag
858 (when (eq (%closure-fun this) object)
859 (return-from ref-p t)))
860 (#.fdefn-widetag
861 #!+immobile-code
862 (when (eq (make-lisp-obj
863 (alien-funcall
864 (extern-alien "fdefn_raw_referent" (function unsigned unsigned))
865 (logandc2 (get-lisp-obj-address this) lowtag-mask)))
866 object)
867 (return-from ref-p t))
868 ;; Without immobile-code the 'raw-addr' slot either holds the same thing
869 ;; as the 'fun' slot, or holds a trampoline address. We'll overlook the
870 ;; minor issue that due to concurrent writes, two representations of the
871 ;; allegedly same referent may diverge; thus the last slot is skipped
872 ;; even if it refers to a different simple-fun.
873 (decf nwords))
875 (return-from ref-p nil)))
876 ;; gencgc has WITHOUT-GCING in map-allocated-objects over dynamic space,
877 ;; so we don't have to pin each object inside REF-P.
878 (#!+cheneygc with-pinned-objects #!+cheneygc (this)
879 #!-cheneygc progn
880 (do ((sap (int-sap (logandc2 (get-lisp-obj-address this) lowtag-mask)))
881 (i (* (1- nwords) n-word-bytes) (- i n-word-bytes)))
882 ((<= i 0) nil)
883 (when (eq (sap-ref-lispobj sap i) object)
884 (return t))))))
885 (let ((fun (%coerce-callable-to-fun fun)))
886 (dx-flet ((mapfun (obj widetag size)
887 (when (and (ref-p obj widetag (/ size n-word-bytes))
888 (valid-obj space obj))
889 (funcall fun obj))))
890 (map-allocated-objects #'mapfun space)))))
892 (defun list-referencing-objects (space object)
893 (collect ((res))
894 (map-referencing-objects
895 (lambda (obj) (res obj)) space object)
896 (res)))
899 #+nil ; for debugging
900 (defun dump-dynamic-space-code (&optional (stream *standard-output*)
901 &aux (n-pages 0) (n-code-bytes 0))
902 (flet ((dump-page (page-num)
903 (incf n-pages)
904 (format stream "~&Page ~D~%" page-num)
905 (let ((where (+ dynamic-space-start (* page-num gencgc-card-bytes)))
906 (seen-filler nil))
907 (loop
908 (multiple-value-bind (obj type size)
909 (reconstitute-object (ash where (- n-fixnum-tag-bits)))
910 (when (= type code-header-widetag)
911 (incf n-code-bytes size))
912 (when (if (and (consp obj) (eq (car obj) 0) (eq (cdr obj) 0))
913 (if seen-filler
914 (progn (write-char #\. stream) nil)
915 (setq seen-filler t))
916 (progn (setq seen-filler nil) t))
917 (let ((*print-pretty* nil))
918 (format stream "~& ~X ~4X ~S " where size obj)))
919 (incf where size))
920 (let ((next-page (find-page-index where)))
921 (cond ((= (logand where (1- gencgc-card-bytes)) 0)
922 (format stream "~&-- END OF PAGE --~%")
923 (return next-page))
924 ((eq next-page page-num))
926 (incf n-pages)
927 (setq page-num next-page seen-filler nil))))))))
928 (let ((i 0))
929 (loop while (< i last-free-page)
930 do (let ((allocation (ldb (byte 2 0)
931 (slot (deref page-table i) 'flags))))
932 (if (= allocation 3)
933 (setq i (dump-page i))
934 (incf i)))))
935 (let* ((tot (* n-pages gencgc-card-bytes))
936 (waste (- tot n-code-bytes)))
937 (format t "~&Used=~D Waste=~D (~F%)~%" n-code-bytes waste
938 (* 100 (/ waste tot))))))