1 ;;;;------------------------------------------------------------------
3 ;;;; Copyright (C) 2003-2005,
4 ;;;; Department of Computer Science, University of Tromso, Norway.
6 ;;;; For distribution policy, see the accompanying file COPYING.
8 ;;;; Filename: los0-gc.lisp
9 ;;;; Description: A simple GC architecture for los0.
10 ;;;; Author: Frode Vatvedt Fjeld <frodef@acm.org>
11 ;;;; Created at: Sat Feb 21 17:48:32 2004
13 ;;;; $Id: los0-gc.lisp,v 1.62 2007/04/09 17:30:09 ffjeld Exp $
15 ;;;;------------------------------------------------------------------
21 (defvar *gc-quiet
* nil
)
22 (defvar *gc-running
* nil
)
23 (defvar *gc-break
* nil
)
24 (defvar *gc-trigger
* nil
)
25 (defvar *gc-consistency-check
* nil
)
28 (defmacro space-fresh-pointer
(space)
29 `(memref ,space -
6 :index
2))
31 (defmacro space-other
(space)
32 `(memref ,space -
6 :index
3))
34 (defun allocate-space (size &optional other-space
)
35 (let ((space (make-array size
:element-type
'(unsigned-byte 32))))
36 (initialize-space space
)
37 (setf (space-other space
) other-space
)
40 (defun initialize-space (space)
41 (setf (space-fresh-pointer space
) 2)
44 (defun allocate-duo-space (size)
45 (let* ((space1 (allocate-space size
))
46 (space2 (allocate-space size space1
)))
47 (setf (space-other space1
) space2
)
50 (defun make-space (location size
)
51 "Make a space vector at a fixed location."
52 (assert (evenp location
))
54 `(memref location
0 :index
,index
:type
:unsigned-byte32
)))
55 (setf (x 1) (* #.movitz
:+movitz-fixnum-factor
+ size
)
56 (x 0) #.
(cl:dpb
(bt:enum-value
'movitz
:movitz-vector-element-type
:u32
)
58 (bt:enum-value
'movitz
:other-type-byte
:basic-vector
))))
59 (%word-offset location
#.
(movitz:tag
:other
)))
61 (defun make-duo-space (location size
)
64 (let ((space1 (make-space location size
))
65 (space2 (make-space (logand -
4 (+ location
3 size
)) size
)))
66 (initialize-space space1
)
67 (initialize-space space2
)
68 (setf (space-other space1
) space2
69 (space-other space2
) space1
)
72 (defun duo-space-end-location (space1)
73 (let ((space2 (space-other space1
)))
74 (max (+ (object-location space1
) (length space2
) 2)
75 (+ (object-location space2
) (length space2
) 2))))
79 (install-los0-consing 4)
80 (warn "nursery: ~Z, other: ~Z"
81 (%run-time-context-slot nil
'muerte
::nursery-space
)
82 (space-other (%run-time-context-slot nil
'muerte
::nursery-space
)))
83 (warn "first cons: ~Z" (funcall 'truncate
#x100000000
3))
84 (warn "second cons: ~Z" (funcall 'truncate
#x100000000
3))
88 (define-primitive-function los0-fast-cons
()
89 "Allocate a cons cell of EAX and EBX from nursery-space."
92 `(with-inline-assembly (:returns
:eax
)
95 ;; (:locally (:cmpl #xabbabee0 (:edi (:edi-offset values) ,(* 4 #x30))))
97 ;; (:locally (:movl (:edi (:edi-offset nursery-space)) :edx))
98 ;; (:movl (:edx 6) :edx); other
100 ;; (:jne '(:sub-program ()
101 ;; (:locally (:movl #xabbabee0 (:edi (:edi-offset values) ,(* 4 #x30))))
105 ;; Set up thread-atomical execution
106 (:locally
(:movl
,(movitz::atomically-continuation-simple-pf
'fast-cons
)
107 (:edi
(:edi-offset atomically-continuation
))))
109 (:locally
(:movl
(:edi
(:edi-offset nursery-space
)) :edx
))
110 (:movl
(:edx
2) :ecx
)
111 (:cmpl
(:edx
,(bt:slot-offset
'movitz
:movitz-basic-vector
'movitz
::num-elements
))
113 (:jae
'(:sub-program
(allocation-failed)
115 (:movl
:eax
(:edx
:ecx
2))
116 (:movl
:ebx
(:edx
:ecx
6))
118 (:movl
:ecx
(:edx
2)) ; Commit allocation
119 (:leal
(:edx
:ecx -
5) :edx
)
120 ;; Exit thread-atomical
121 (:locally
(:movl
0 (:edi
(:edi-offset atomically-continuation
))))
127 (defun trigger-full-newspace (free-space)
128 "Make it so that there's only free-space words left before newspace is full."
130 (let ((trigger (if (consp *gc-trigger
*)
136 `(with-inline-assembly (:returns
:nothing
)
138 (:compile-form
(:result-mode
:eax
) (+ free-space trigger
))
139 (:locally
(:movl
(:edi
(:edi-offset nursery-space
)) :edx
))
140 (:testl
,(logxor #xffffffff
141 (* #xfff movitz
:+movitz-fixnum-factor
+))
143 (:jnz
'(:sub-program
() (:int
64)))
146 (:movl
(:edx
,(bt:slot-offset
'movitz
:movitz-basic-vector
'movitz
::num-elements
))
149 (:movl
(:edx
2) :ebx
)
151 (:jc
'(:sub-program
()
152 ;; Current newspace was too full, so trigger a GC.
155 (:movl
:ecx
(:edx
2))
158 (:movl
:edi
(:edx
:ebx -
6))
166 (define-primitive-function los0-cons-pointer
()
167 "Return in EAX the next object location with space for EAX words, with tag 6.
171 `(with-inline-assembly (:returns
:multiple-values
)
172 (:locally
(:cmpl
0 (:edi
(:edi-offset atomically-continuation
)))) ; Atomically?
173 (:je
'(:sub-program
()
174 (:int
63))) ; This must be called inside atomically.
175 (:locally
(:movl
(:edi
(:edi-offset nursery-space
)) :edx
))
176 (:movl
(:edx
2) :ebx
)
177 (:leal
(:ebx
:eax
4) :eax
)
179 (:cmpl
(:edx
,(bt:slot-offset
'movitz
:movitz-basic-vector
'movitz
::num-elements
))
181 (:ja
'(:sub-program
(probe-failed)
184 (:movl
#xabbabee3
(:edx
:ebx
8 ,movitz
:+other-type-offset
+)) ; a recognizable illegal value?
185 (:leal
(:edx
:ebx
8) :eax
)
189 (define-primitive-function los0-cons-commit
()
190 "Commit allocation of ECX/fixnum words.
191 Preserve EAX and EBX."
194 `(with-inline-assembly (:returns
:multiple-values
)
195 (:locally
(:cmpl
0 (:edi
(:edi-offset atomically-continuation
)))) ; Atomically?
196 (:je
'(:sub-program
()
197 (:int
63))) ; This must be called inside atomically.
198 (:addl
,movitz
:+movitz-fixnum-factor
+ :ecx
)
199 (:locally
(:movl
(:edi
(:edi-offset nursery-space
)) :edx
))
201 (:addl
(:edx
2) :ecx
)
202 (:cmpl
(:edx
,(bt:slot-offset
'movitz
:movitz-basic-vector
'movitz
::num-elements
))
204 (:ja
'(:sub-program
(commit-failed)
207 (:movl
:ecx
(:edx
2))
208 (:leal
(:edx
:ecx
) :ecx
)
212 (define-primitive-function los0-box-u32-ecx
()
213 "Make u32 in ECX into a fixnum or bignum in EAX."
216 `(with-inline-assembly (:returns
:multiple-values
)
217 (:cmpl
,movitz
:+movitz-most-positive-fixnum
+ :ecx
)
219 (:leal
((:ecx
,movitz
:+movitz-fixnum-factor
+)) :eax
)
222 (:locally
(:movl
,(movitz::atomically-continuation-simple-pf
'box-u32-ecx
)
223 (:edi
(:edi-offset atomically-continuation
))))
224 (:locally
(:movl
(:edi
(:edi-offset nursery-space
)) :edx
))
225 (:movl
(:edx
2) :eax
)
226 (:cmpl
(:edx
,(bt:slot-offset
'movitz
:movitz-basic-vector
'movitz
::num-elements
))
228 (:jae
'(:sub-program
()
230 (:movl
,(dpb movitz
:+movitz-fixnum-factor
+
231 (byte 16 16) (movitz:tag
:bignum
0))
233 (:movl
:ecx
(:edx
:eax
6))
235 (:movl
:eax
(:edx
2)) ; Commit allocation
236 (:leal
(:edx
:eax
) :eax
)
237 ;; Exit thread-atomical
238 (:locally
(:movl
0 (:edi
(:edi-offset atomically-continuation
))))
242 (defvar *gc-stack
* nil
)
243 (defvar *gc-stack2
* nil
)
245 (defmacro with-hack-space
((&key
(size 409600)) &body body
)
246 `(let* ((id (with-inline-assembly (:returns
:eax
) (:movl
:esp
:eax
)))
247 (save-space (%run-time-context-slot nil
'muerte
::nursery-space
))
248 (hack-space (make-duo-space (duo-space-end-location save-space
) ,size
)))
249 (warn "[~A] hack-space ~Z from ~Z/~Z: ~A" id hack-space save-space
(space-other save-space
) ',body
)
252 (setf (%run-time-context-slot nil
'muerte
::nursery-space
) hack-space
)
254 (warn "[~A] hack-space done." id
)
255 (setf (%run-time-context-slot nil
'muerte
::nursery-space
) save-space
))))
257 (defun install-los0-consing (&key
(context (current-run-time-context))
260 "Install the 'Los0' GC architecture on run-time-context CONTEXT.
261 Either use an explicitly provided DUO-SPACE, or allocate a fresh
262 duo-space where each space is KB-SIZE kilobytes."
263 (setf (exception-handler 113)
264 (lambda (exception interrupt-frame
)
265 (declare (ignore exception interrupt-frame
))
267 (let ((muerte::*active-condition-handlers
* nil
)
268 (*debugger-hook
* nil
)
269 (*standard-output
* *terminal-io
*))
272 (let* ((full-space (%run-time-context-slot nil
'muerte
::nursery-space
))
273 (hack-space (make-duo-space (duo-space-end-location full-space
) 102400)))
274 (setf (%run-time-context-slot nil
'muerte
::nursery-space
) hack-space
)
275 (break "Recursive GC triggered. Full-space: ~Z, hack-space: ~Z"
276 full-space hack-space
)))
277 (t (let ((*gc-running
* t
))
279 (format t
"~&;; GC ~Z.." (%run-time-context-slot nil
'muerte
::nursery-space
)))
280 (let* ((space0 (%run-time-context-slot nil
'nursery-space
))
281 (space1 (space-other space0
)))
282 (unless (= 2 (space-fresh-pointer space1
))
284 (error "PRE space-other is not initialized: ~S" (space-fresh-pointer space1
)))))
287 (let* ((space0 (%run-time-context-slot nil
'nursery-space
))
288 (space1 (space-other space0
)))
289 (unless (= 2 (space-fresh-pointer space1
))
291 (error "UP space-other is not initialized: ~S" (space-fresh-pointer space1
))))
295 (loop ; This is a nice opportunity to poll the keyboard..
296 (case (muerte.x86-pc.keyboard
:poll-char
)
298 (break "Los0 GC keyboard poll."))
301 (let* ((actual-duo-space (or duo-space
302 (allocate-duo-space (* kb-size
#x100
))))
303 (last-location (object-location (cons 1 2))))
304 (macrolet ((install-primitive (name slot
)
305 `(let ((code-vector (symbol-value ',name
)))
306 (check-type code-vector code-vector
)
307 (if (eq context
(current-run-time-context))
308 ;; The point of this is to not trigger CLOS bootstrapping.
309 (setf (%run-time-context-slot nil
',slot
) code-vector
)
310 (setf (%run-time-context-slot context
',slot
) code-vector
)))))
311 (install-primitive los0-fast-cons muerte
::fast-cons
)
312 (install-primitive los0-box-u32-ecx muerte
::box-u32-ecx
)
313 (install-primitive los0-cons-pointer muerte
::cons-pointer
)
314 (install-primitive los0-cons-commit muerte
::cons-commit
))
315 (if (eq context
(current-run-time-context))
316 (setf (%run-time-context-slot nil
'muerte
::nursery-space
)
318 (setf (%run-time-context-slot context
'muerte
::nursery-space
)
320 ;; Pretend that the heap stops here, so that we don't have to scan
321 ;; the entire tail end of memory, which isn't going to be used.
322 (setf (cdar muerte
::%memory-map-roots%
) last-location
))
325 (defun object-in-space-p (space object
)
326 (check-type space
(simple-array (unsigned-byte 32) 1))
327 (and (typep object
'pointer
)
328 (<= (+ 2 (object-location space
))
329 (object-location object
)
330 (+ 1 (object-location space
)
331 (array-dimension space
0)))))
333 (defun report-nursery (x location
)
334 "Write a message if x is inside newspace."
335 (when (object-in-space-p (%run-time-context-slot nil
'nursery-space
) x
)
336 (format t
"~&~Z: ~S: ~S from ~S" x
(type-of x
) x location
))
339 (defun report-inactive-space (x location
)
340 "Check that x is not pointing into (what is presumably) oldspace."
341 (when (object-in-space-p (space-other (%run-time-context-slot nil
'nursery-space
)) x
)
342 (break "~Z: ~S: ~S from ~S" x
(type-of x
) x location
))
345 (defun location-finder (find-location)
347 (when (location-in-object-p x find-location
)
348 (break "The location ~S is in the object at ~Z referenced from location ~S."
349 find-location x location
))
354 (install-old-consing)
355 (install-los0-consing))
358 (defun kill-the-newborns ()
359 (let* ((oldspace (%run-time-context-slot nil
'nursery-space
))
360 (newspace (space-other oldspace
)))
361 (setf (%run-time-context-slot nil
'nursery-space
) newspace
)
362 (flet ((zap-oldspace (x location
)
363 (declare (ignore location
))
364 (if (object-in-space-p oldspace x
)
367 (map-header-vals #'zap-oldspace
0 (malloc-end))
368 (map-stack-vector #'zap-oldspace nil
(current-stack-frame))
369 (initialize-space oldspace
)
373 (defparameter *x
* (make-array #x1000
:fill-pointer
0)) ; Have this in static space.
374 ;;;(defparameter *xx* #4000(nil)) ; Have this in static space.
379 (defparameter *code-vector-foo
* 0)
380 (defvar *old-code-vectors
* #250(nil))
381 (defvar *new-code-vectors
* #250(nil))
383 (defun stop-and-copy (&optional evacuator
)
384 (setf (fill-pointer *x
*) 0)
385 (setf (fill-pointer *old-code-vectors
*) 0)
386 (multiple-value-bind (newspace oldspace
)
388 (let* ((space0 (%run-time-context-slot nil
'nursery-space
))
389 (space1 (space-other space0
)))
390 (check-type space0
(simple-array (unsigned-byte 32) 1))
391 (check-type space1
(simple-array (unsigned-byte 32) 1))
392 (assert (eq space0
(space-other space1
)))
393 (unless (= 2 (space-fresh-pointer space1
))
395 (error "space1 is not initialized: ~S" (space-fresh-pointer space1
))
397 (setf (%run-time-context-slot nil
'nursery-space
) space1
)
398 (values space1 space0
)))
399 ;; Evacuate-oldspace is to be mapped over every potential pointer.
400 (let ((*code-vector-foo
* (incf *code-vector-foo
* 2))
404 "If x is in oldspace, migrate it to newspace."
405 (declare (ignore location
))
409 ((object-in-space-p newspace x
)
411 #+ignore
((and (typep x
'code-vector
)
412 (not (object-in-space-p oldspace x
))
413 (not (object-in-space-p newspace x
))
414 (= (ldb (byte 12 0) (object-location x
))
415 (ldb (byte 12 0) *code-vector-foo
*))
416 (not (eq x
(funobj-code-vector #'stop-and-copy
)))
417 (not (eq x
(symbol-value 'muerte
::default-interrupt-trampoline
)))
418 (not (muerte::scavenge-find-pf
(lambda (x y
) x
) (object-location x
))))
419 (let ((p (position (object-location x
) *old-code-vectors
*)))
421 (aref *new-code-vectors
* p
)
422 (setf (aref *new-code-vectors
*
423 (vector-push (object-location x
) *old-code-vectors
*))
424 (let ((new (shallow-copy x
)))
425 (warn "[~S] Migrating ~@[~S ~]~Z => ~Z."
427 (muerte::locate-function
(object-location x
))
430 ((not (object-in-space-p oldspace x
))
432 #+ignore
((when (typep x
'run-time-context
)
433 (warn "Scavenging ~S" x
)))
434 (t (or (and (eq (object-tag x
)
436 (memref (object-location x
) 0 :type
:unsigned-byte8
)))
437 (let ((forwarded-x (memref (object-location x
) 0)))
438 (and (object-in-space-p newspace forwarded-x
)
440 (let ((forward-x (shallow-copy x
)))
441 (when (and *gc-consistency-check
*
442 (typep x
'muerte
::pointer
))
444 (vector-push (%object-lispval x
) a
)
445 (vector-push (memref (object-location x
) 0 :type
:unsigned-byte32
) a
)
446 (assert (vector-push (%object-lispval forward-x
) a
))))
447 (setf (memref (object-location x
) 0) forward-x
)
450 (with-simple-restart (nil "Scanning stack.")
451 (map-stack-vector evacuator nil
(current-stack-frame)))
452 (with-simple-restart (nil "Scanning heap.")
453 (dolist (range muerte
::%memory-map-roots%
)
454 (map-header-vals evacuator
(car range
) (cdr range
))))
455 ;; Scan newspace, Cheney style.
456 (loop with newspace-location of-type index
= (+ 2 (object-location newspace
))
457 with scan-pointer of-type index
= 2
458 as fresh-pointer of-type index
= (space-fresh-pointer newspace
)
459 while
(< scan-pointer fresh-pointer
)
460 do
(let ((start (+ newspace-location scan-pointer
))
461 (end (+ newspace-location
(space-fresh-pointer newspace
))))
462 (map-header-vals evacuator start end
)
465 (setf scan-pointer fresh-pointer
))
466 (when *gc-consistency-check
*
467 ;; Consistency check..
468 (map-stack-vector (lambda (x foo
)
469 (declare (ignore foo
))
472 (current-stack-frame))
473 (with-simple-restart (continue "Skip GC consistency check.")
476 ;; First, restore the state of old-space
477 (do ((end (- (length a
) 2))
480 (let ((old (%lispval-object
(aref a i
)))
481 (old-class (aref a
(+ i
1))))
482 (setf (memref (object-location old
) 0 :type
:unsigned-byte32
) old-class
)))
483 ;; Then, check that each migrated object is equalp to its new self.
486 (let ((old (%lispval-object
(aref a i
)))
487 (new (%lispval-object
(aref a
(+ i
2)))))
488 (unless (and (object-in-space-p newspace new
)
489 (not (object-in-space-p newspace old
))
490 (objects-equalp old new
))
491 (let ((*evacuator
* evacuator
)
494 (*old-class
* (aref a
(+ i
1))))
495 (declare (special *old
* *new
* *old-class
* *evacuator
*))
496 (error "GC consistency check failed:
500 oldspace: ~Z, newspace: ~Z, i: ~D"
501 old old new new
(objects-equalp old new
) oldspace newspace i
))))))
502 (map-header-vals (lambda (x y
)
504 (when (location-in-object-p (space-other (%run-time-context-slot nil
'nursery-space
))
506 (break "Seeing old object in values-vector: ~Z" x
))
510 (let* ((stack (%run-time-context-slot nil
'muerte
::stack-vector
))
511 (stack-start (- (length stack
) (muerte::current-control-stack-depth
))))
514 (let* ((offender?
(aref a i
))
515 (offender-index (position offender? stack
:start stack-start
)))
517 (break "Seeing old object ~S in current stack at ~S, new is ~S"
519 (+ (object-location stack
)
522 (loop for i from stack-start below
(length stack
)
523 as o
= (aref stack i
)
524 do
(when (and (typep o
'pointer
)
525 (location-in-object-p oldspace
(object-location o
)))
526 (break "Seeing old (unmapped) object ~Z in stack at ~S."
527 o
(+ (object-location stack
) i
2))))))))
528 (loop for o across
*old-code-vectors
*
529 for n across
*new-code-vectors
*
530 do
(setf (memref o
0) (memref n -
6))
531 (fill (muerte::%location-object o
6) #xcc
))
532 ;; GC completed, oldspace is evacuated.
534 (let ((old-size (truncate (- (space-fresh-pointer oldspace
) 2) 2))
535 (new-size (truncate (- (space-fresh-pointer newspace
) 2) 2)))
536 (format t
"Old space: ~/muerte:pprint-clumps/, new space: ~
537 ~/muerte:pprint-clumps/, freed: ~/muerte:pprint-clumps/.~%"
538 old-size new-size
(- old-size new-size
))))
539 (dolist (hook *gc-hooks
*)
541 (initialize-space oldspace
)
542 (when *gc-consistency-check
*
543 (fill oldspace
#x13
:start
2)
544 ;; (setf *gc-stack2* *gc-stack*)
545 (setf *gc-stack
* (muerte::copy-current-control-stack
))
546 #+ignore
(setf (fill-pointer *xx
*) (fill-pointer *x
*))
547 #+ignore
(replace *xx
* *x
*))))
550 (defun simple-stop-and-copy (newspace oldspace
)
551 (flet ((evacuator (x)
552 "If x is in oldspace, migrate it to newspace."
553 (if (not (object-in-space-p oldspace x
))
555 (or (and (eq (object-tag x
)
556 (memref (object-location x
) 0 :type
:tag
))
557 (let ((forwarded-x (memref (object-location x
) 0)))
558 (and (object-in-space-p newspace forwarded-x
)
560 (setf (memref (object-location x
) 0)
561 (shallow-copy x
))))))
563 (map-stack-vector #'evacuator nil
(current-stack-frame))
564 (dolist (range muerte
::%memory-map-roots%
)
565 (map-header-vals #'evacuator
(car range
) (cdr range
)))
566 ;; Scan newspace, Cheney style.
567 (loop with newspace-location
= (+ 2 (object-location newspace
))
568 with scan-pointer
= 2
569 as fresh-pointer
= (space-fresh-pointer newspace
)
570 while
(< scan-pointer fresh-pointer
)
571 do
(map-header-vals #'evacuator
572 (+ newspace-location scan-pointer
)
573 (+ newspace-location
(space-fresh-pointer newspace
)))
574 (setf scan-pointer fresh-pointer
))
575 (initialize-space oldspace
)
579 (defun find-object-by-location (location &key
(continuep t
) (breakp nil
))
580 "Scan the heap for a (pointer) object that matches location.
581 This is a debugging tool."
583 (flet ((searcher (x ignore
)
584 (declare (ignore ignore
))
585 (when (and (typep x
'(or muerte
::tag1 muerte
::tag6 muerte
::tag7
))
586 (not (eq x
(%run-time-context-slot nil
'muerte
::nursery-space
)))
587 (location-in-object-p x location
)
588 (not (member x results
)))
590 (funcall (if breakp
#'break
#'warn
)
591 "Found pointer ~Z of type ~S at location ~S."
592 x
(type-of x
) (object-location x
)))
595 ((serious-condition (lambda (c)
597 (find-restart 'muerte
::continue-map-header-vals
))
598 (warn "Automatic continue from scanning error: ~A" c
)
599 (invoke-restart 'muerte
::continue-map-header-vals
)))))
600 (dolist (range muerte
::%memory-map-roots%
)
601 (map-header-vals #'searcher
(car range
) (cdr range
)))
602 (let ((nursery (%run-time-context-slot nil
'muerte
::nursery-space
)))
603 (map-header-vals #'searcher
604 (+ 4 (object-location nursery
))
605 (+ 4 (object-location nursery
) (space-fresh-pointer nursery
))))
606 (map-stack-vector #'searcher nil
(current-stack-frame))))
609 (defun report-lispval (lispval &optional breakp newspace
)
610 (let* ((location (truncate lispval
4))
611 (newspace (or newspace
(%run-time-context-slot nil
'muerte
::nursery-space
)))
612 (oldspace (space-other newspace
)))
614 ((location-in-object-p newspace location
)
615 (format t
"#x~X is in newspace ~Z." lispval newspace
))
616 ((location-in-object-p oldspace location
)
617 (funcall (if breakp
'break
'warn
) "#x~X is in oldspace ~Z." lispval oldspace
))
618 (t (funcall (if breakp
'break
'warn
) "#x~X is neither old nor new?" lispval
))))