Use TLSF allocator for immobile text space

And with a few related improvements:
* a finalizer gives memory back to the allocator
* code never acts as filler
* easier way of tracking text page scan start offset
This commit is contained in:
Douglas Katzman 2022-07-30 22:30:43 -04:00
parent c12cd5c47c
commit 5cd3e88fb6
22 changed files with 652 additions and 812 deletions

View file

@ -83,315 +83,10 @@
(define-alien-variable ("fixedobj_free_pointer" *fixedobj-space-free-pointer*)
system-area-pointer)
(eval-when (:compile-toplevel)
(eval-when (:compile-toplevel) ; FIXME: these assertions look irrelevant now
(assert (eql code-boxed-size-slot 1))
(assert (eql code-debug-info-slot 2)))
(define-alien-variable "text_holes" long)
(define-alien-variable "text_page_touched_bits" (* (unsigned 32)))
(define-alien-variable "text_pages" (* (unsigned 32)))
(define-alien-routine "find_preceding_object" long (where long))
;;; Lazily created freelist, used only when unallocate is called:
;;; A cons whose car is a sorted list of hole sizes available
;;; and whose cdr is a hashtable.
;;; The keys in the hashtable are hole sizes, values are lists of holes.
;;; A better structure would be just a sorted array of sizes
;;; with each entry pointing to the holes which are threaded through
;;; some bytes in the storage itself rather than through cons cells.
(define-load-time-global *immobile-freelist* nil)
;;; Return the zero-based index within the text subspace of immobile space.
(defun text-page-index (address)
(declare (type (and fixnum unsigned-byte) address))
(values (floor (- address text-space-start) immobile-card-bytes)))
(defun text-page-address (index)
(+ text-space-start (* index immobile-card-bytes)))
(declaim (inline (setf text-page-scan-start-offset)))
(defun (setf text-page-scan-start-offset) (newval index)
;; NEWVAL is passed in as a byte count but we want to store it as doublewords
;; so it needs right-shifting by 1+ word-shift. However because it is a field
;; of a packed word, it needs left-shifting by 8. We can shift by the net amount
;; provided that no zero bits would be right-shifted out.
(aver (zerop (logand newval lowtag-mask)))
(setf (deref text-pages index)
(logior (ash newval (- 8 (1+ word-shift)))
(logand (deref text-pages index) #xFF)))
newval)
;;; Convert a zero-based text page index into a scan start address.
(defun text-page-scan-start (index)
(- (+ text-space-start (* (1+ index) immobile-card-bytes))
(* 2 n-word-bytes (ash (deref text-pages index) -8))))
(declaim (inline hole-p))
(defun hole-p (raw-address)
;; A code header with 0 boxed words is a hole.
;; See also CODE-OBJ-IS-FILLER-P
(eql (sap-ref-64 (int-sap raw-address) 0) code-header-widetag))
(defun freed-hole-p (address)
(and (hole-p address)
;; A hole is not considered to have been freed until it is
;; no longer in the chain of objects linked through
;; the debug_info slot.
(eql (sap-ref-word (int-sap address)
(ash code-debug-info-slot word-shift))
nil-value)))
(declaim (inline hole-size))
(defun hole-size (hole-address) ; in bytes
(ash (sap-ref-32 (int-sap hole-address) 4) word-shift))
(declaim (inline (setf hole-size)))
(defun (setf hole-size) (new-size hole) ; NEW-SIZE is in bytes
(setf (sap-ref-32 (int-sap hole) 4) (ash new-size (- word-shift)))
new-size)
(declaim (inline hole-end-address))
(defun hole-end-address (hole-address)
(+ hole-address (hole-size hole-address)))
(defun sorted-list-insert (item list key-fn)
(declare (function key-fn))
(let ((key (funcall key-fn item)) (tail list) prev)
(loop
(when (null tail)
(let ((new-tail (list item)))
(return (cond ((not prev) new-tail)
(t (setf (cdr prev) new-tail) list)))))
(let ((head (car tail)))
(when (< key (funcall key-fn head))
(rplaca tail item)
(rplacd tail (cons head (cdr tail)))
(return list)))
(setq prev tail tail (cdr tail)))))
;;; These routines are not terribly efficient, but very straightforward
;;; since we can assume the existence of hashtables.
(defun add-to-freelist (hole)
(let* ((size (hole-size hole))
(freelist *immobile-freelist*)
(table (cdr freelist))
(old (gethash (hole-size hole) table)))
;; Check for double-free error
#+immobile-space-debug (aver (not (member hole (gethash size table))))
(unless old
(setf (car freelist)
(sorted-list-insert size (car freelist) #'identity)))
(setf (gethash size table) (cons hole old))))
(defun remove-from-freelist (hole)
(let* ((key (hole-size hole))
(freelist *immobile-freelist*)
(table (cdr freelist))
(list (gethash key table))
(old-length (length list))
(new (delete hole list :count 1)))
(declare (ignorable old-length))
#+immobile-space-debug (aver (= (length new) (1- old-length)))
(cond (new
(setf (gethash key table) new))
(t
(setf (car freelist) (delete key (car freelist) :count 1))
(remhash key table)))))
(defun find-in-freelist (size test)
(let* ((freelist *immobile-freelist*)
(hole-size
(if (eq test '<=)
(let ((sizes (member size (car freelist) :test '<=)))
(unless sizes
(return-from find-in-freelist nil))
(car sizes))
size))
(found (car (gethash hole-size (cdr freelist)))))
(when found
(remove-from-freelist found))
found))
(defun set-text-space-free-pointer (free-ptr)
(declare (type (and fixnum unsigned-byte) free-ptr))
(setq *text-space-free-pointer* (int-sap free-ptr))
;; When the free pointer is not page-aligned - it usually won't be -
;; then we create an unboxed array from the pointer to the page end
;; so that it appears as one contiguous object when scavenging.
;; instead of a bunch of cons cells.
(when (logtest free-ptr (1- immobile-card-bytes))
(let ((n-trailing-bytes
(- (nth-value 1 (ceiling free-ptr immobile-card-bytes)))))
(setf (sap-ref-word (int-sap free-ptr) 0) simple-array-fixnum-widetag
(%array-fill-pointer
(%make-lisp-obj (logior free-ptr other-pointer-lowtag)))
;; Convert bytes to words, subtract the header and vector length.
(- (ash n-trailing-bytes (- word-shift)) 2)))))
(defun unallocate (hole)
#+immobile-space-debug
(awhen *in-use-bits* (mark-range it hole (hole-size hole) nil))
(let* ((hole-end (hole-end-address hole))
(end-is-free-ptr (eql hole-end (sap-int *text-space-free-pointer*))))
;; First, ensure that no page's scan-start points to this hole.
;; For smaller-than-page objects, this will do nothing if the hole
;; was not the scan-start. For larger-than-page, we have to update
;; a range of pages. Example:
;; | page1 | page2 | page3 | page4 |
;; |-- hole A ------ | -- hole B --
;; If page1 had an object preceding the hole, then it is not empty,
;; but if it pointed to the hole, and the hole extended to the end
;; of the first page, then that page is empty.
;; Pages (1+ first-page) through (1- last-page) inclusive
;; must become empty. last-page may or may not be depending
;; on whether another object can be found on it.
(let ((first-page (text-page-index hole))
(last-page (text-page-index (1- hole-end))))
(when (and (eql (text-page-scan-start first-page) hole)
(< first-page last-page))
(setf (text-page-scan-start-offset first-page) 0))
(loop for page from (1+ first-page) below last-page
do (setf (text-page-scan-start-offset page) 0))
;; Only touch the offset for the last page if it pointed to this hole.
;; If the following object is a hole that is in the pending free list,
;; it's ok, but if it's a hole that is already in the freelist,
;; it's not OK, so look beyond that object. We don't have to iterate,
;; since there can't be two consecutive holes - so it's either the
;; object after this hole, or the one after that.
(when (eql (text-page-scan-start last-page) hole)
(let* ((page-end (text-page-address (1+ last-page)))
(new-scan-start (cond (end-is-free-ptr page-end)
((freed-hole-p hole-end)
(hole-end-address hole-end))
(t hole-end))))
(setf (text-page-scan-start-offset last-page)
(if (< new-scan-start page-end)
;; Compute new offset backwards relative to the page end.
(- page-end new-scan-start)
0))))) ; Page becomes empty
(unless *immobile-freelist*
(setf *immobile-freelist* (cons nil (make-hash-table :test #'eq))))
;; find-preceding is the most expensive operation in this sequence
;; of steps. Not sure how to improve it, but I doubt it's a problem.
(let* ((predecessor (find-preceding-object hole))
(pred-is-free (and (not (eql predecessor 0))
(freed-hole-p predecessor))))
(when pred-is-free
(remove-from-freelist predecessor)
(setf hole predecessor))
(when end-is-free-ptr
;; Give back space below the free pointer for better space conservation.
;; Consider when the hole touching the free pointer is equal in size
;; to another hole that could have been used instead. Taking space at
;; the free pointer diminishes the opportunity to use the frontier
;; to later allocate a larger object that would not have fit
;; into any existing hole.
(set-text-space-free-pointer hole)
(return-from unallocate))
(let* ((successor hole-end)
(succ-is-free (freed-hole-p successor)))
(when succ-is-free
(setf hole-end (hole-end-address successor))
(remove-from-freelist successor)))
;; The hole must be an integral number of doublewords.
(aver (not (logtest (- hole-end hole) lowtag-mask)))
(setf (hole-size hole) (- hole-end hole))))
(add-to-freelist hole))
(defun alloc-immobile-code (n-bytes word0 word1 lowtag errorp)
(declare (type (and fixnum unsigned-byte) n-bytes))
(setq n-bytes (align-up n-bytes (* 2 n-word-bytes)))
;; Can't allocate fewer than 4 words due to min hole size.
(aver (>= n-bytes (* 4 n-word-bytes)))
(with-system-mutex (*allocator-mutex* :without-gcing t)
(unless (zerop text-holes)
;; If deferred sweep needs to happen, do so now.
;; Concurrency could potentially be improved here: at most one thread
;; should do this step, but it doesn't need to be exclusive with GC
;; as long as we can atomically pop items off the list of holes.
(let ((hole-addr text-holes))
(setf text-holes 0)
(loop
(let ((next (sap-ref-word (int-sap hole-addr)
(ash code-debug-info-slot word-shift))))
(setf (sap-ref-word (int-sap hole-addr)
(ash code-debug-info-slot word-shift))
nil-value)
(unallocate hole-addr)
(if (eql (setq hole-addr next) 0) (return))))))
(let* ((residual)
(shrunk-size)
(addr
(or (and *immobile-freelist*
(or (find-in-freelist n-bytes '=) ; 1. Exact match?
;; 2. Try splitting a hole, adding some slack so that
;; both pieces can potentially be used.
(let ((found (find-in-freelist (+ n-bytes 192) '<=)))
(when found
(let* ((actual-size (hole-size found))
(remaining (- actual-size n-bytes)))
(aver (not (logtest actual-size lowtag-mask)))
(setq residual found ; Shorten the lower piece
shrunk-size remaining)
(+ found remaining)))))) ; Consume the upper piece
;; 3. Extend the frontier.
(let* ((addr (sap-int *text-space-free-pointer*))
(free-ptr (+ addr n-bytes))
(limit (+ text-space-start text-space-size)))
(when (> free-ptr limit)
(cond (errorp
(format t "~&Immobile space exhausted~%")
(sb-debug:print-backtrace)
(sb-impl::%halt))
(t
(return-from alloc-immobile-code nil))))
(set-text-space-free-pointer free-ptr)
addr))))
(aver (not (logtest addr lowtag-mask))) ; Assert proper alignment
;; Compute the start and end of the first page consumed.
(let* ((page-start (logandc2 addr (1- immobile-card-bytes)))
(page-end (+ page-start immobile-card-bytes))
(index (text-page-index addr))
(obj-end (+ addr n-bytes)))
;; Mark the page as being used by a nursery object.
(setf (deref text-pages index) (logior (deref text-pages index) 1))
;; On the object's first page, set the scan start only if addr
;; is lower than the current page-scan-start object.
;; Note that offsets are expressed in doublewords backwards from
;; page end, so that we can direct the scan start to any doubleword
;; on the page or in the preceding 256MiB (approximately).
(when (< addr (text-page-scan-start index))
(setf (text-page-scan-start-offset index) (- page-end addr)))
;; On subsequent pages, always set the scan start, since there can not
;; be a lower-addressed object touching those pages.
(loop
(setq page-start page-end)
(incf page-end immobile-card-bytes)
(incf index)
(when (>= page-start obj-end) (return))
(setf (text-page-scan-start-offset index) (- page-end addr))))
#+immobile-space-debug ; "address sanitizer"
(awhen *in-use-bits* (mark-range it addr n-bytes t))
(setf (sap-ref-word (int-sap addr) 0) word0
(sap-ref-word (int-sap addr) n-word-bytes) word1)
;; 0-fill the remainder of the object
(alien-funcall (extern-alien "memset" (function void system-area-pointer int unsigned))
(sap+ (int-sap addr) (* 2 n-word-bytes)) 0 (- n-bytes (* 2 n-word-bytes)))
;; Only after making the new object can we reduce the size of the hole
;; that contained the new allocation (if it entailed chopping a hole
;; into parts). In this way, heap scans do not read junk.
(when residual
(setf (hole-size residual) shrunk-size)
(add-to-freelist residual))
;; The object is live despite not having a tagged pointer yet nor
;; this code being pseudoatomic, because the mutex acquire has
;; :WITHOUT-GCING. Ideally we'd have some notion of hazard pointers
;; that could prevent GC from evicting objects from pointed-to pages
;; so that we needn't inhibit GC.
(%make-lisp-obj (logior addr lowtag)))))
;;; Size-class segregation (implying which page we try to allocate to)
;;; is done from lisp now, not C. There are 4 objects types we'll see,
;;; each in its own size class (even if some are coincidentally the same size).
@ -450,7 +145,18 @@
(defun immobile-space-obj-p (obj)
(immobile-space-addr-p (get-lisp-obj-address obj)))
(define-load-time-global *codeblob-tree* nil)
(define-load-time-global *immobile-codeblob-tree* nil)
(define-load-time-global *dynspace-codeblob-tree* nil)
(define-alien-variable codeblob-freelist unsigned)
;;; For the immobile code allocator:
;;; * Insertion performs low-level allocate, then tree-insert
;;; * Deletion performs tree-delete, then low-level deallocate
;;; Invariants:
;;; - a linear space walk visits at least all the objects in the tree, possibly more
;;; - tree does not contain any key that is not the base address of an object
;;; - if a tree node points to an object, then that object is either code
;;; or a filler that has not been returned to the low-level allocator
;;; Enforce limit on boxed words based on maximum total number of words
;;; that can be indicated in the header for 32-bit words.
@ -471,40 +177,55 @@
(declare (ignorable space))
(let* ((total-words
(the (unsigned-byte 22) ; Enforce limit on total words as well
(align-up (+ boxed (ceiling unboxed n-word-bytes)) 2)))
(code
#+gencgc
(or #+immobile-code
(when (member space '(:immobile :auto))
;; We don't need to inhibit GC here - ALLOC-IMMOBILE-CODE does it.
;; Indicate that there are initially 2 boxed words, otherwise
;; immobile space GC thinks this object is freeable.
(alloc-immobile-code (ash total-words word-shift)
(logior (ash total-words code-header-size-shift)
code-header-widetag)
(* boxed n-word-bytes)
other-pointer-lowtag
(eq space :immobile)))
;; x86-64 has a vop which wraps pseudo-atomic around the foreign call,
;; as is the custom for allocation trampolines.
#+x86-64 (%primitive alloc-code total-words boxed)
#-x86-64
(without-gcing
(%make-lisp-obj
(alien-funcall (extern-alien "alloc_code_object"
(function unsigned (unsigned 32) (unsigned 32)))
total-words boxed))))))
(with-pinned-objects (code)
(let ((sap (sap+ (int-sap (get-lisp-obj-address code))
(- other-pointer-lowtag))))
;; Record it in the balanced tree.
(let ((tree *codeblob-tree*) (addr (sap-int sap)))
(align-up (+ boxed (ceiling unboxed n-word-bytes)) 2))))
#+immobile-code
(when (member space '(:immobile :auto))
(let (addr code holder)
;; CODE needs to have a heap or TLS reference to it prior to adding it to the tree
;; since implicit pinning uses the tree to find pinned ojects.
(declare (special holder))
(with-alien ((tlsf-alloc-codeblob (function unsigned system-area-pointer unsigned)
:extern)
(tlsf-control system-area-pointer :extern))
(with-system-mutex (*allocator-mutex* :without-gcing t)
(unless (zerop (setq addr (alien-funcall tlsf-alloc-codeblob
tlsf-control total-words)))
(setf code (%make-lisp-obj (logior addr other-pointer-lowtag))
holder code))))
;; GC is allowed to run now because HOLDER references CODE
(when code
(alien-funcall (extern-alien "memset" (function void system-area-pointer int unsigned))
(sap+ (int-sap addr) n-word-bytes) 0 (ash (1- boxed) word-shift))
;; BOXED-SIZE is a raw slot holding a byte count, but SET-SLOT takes its VALUE
;; arg as a descriptor-reg, so just cleverly make it right by shifting.
(%primitive set-slot code (ash boxed (- word-shift n-fixnum-tag-bits))
'(setf %code-boxed-size) code-boxed-size-slot other-pointer-lowtag)
(aver (= (sap-ref-8 (int-sap addr) 0) code-header-widetag)) ; wasn't trashed
(let ((tree *immobile-codeblob-tree*))
(loop (when (eq tree (setq tree (cas *immobile-codeblob-tree* tree
(sb-brothertree:insert addr tree))))
(return-from allocate-code-object (values code total-words)))))))
(when (eq space :immobile)
(error "Immobile code space exhausted")))
(let ((code
;; x86-64 has a vop which wraps pseudo-atomic around the foreign call,
;; as is the custom for allocation trampolines.
#+x86-64 (%primitive alloc-code total-words boxed)
#-x86-64
(without-gcing
(%make-lisp-obj
(alien-funcall (extern-alien "alloc_code_object"
(function unsigned (unsigned 32) (unsigned 32)))
total-words boxed)))))
(with-pinned-objects (code)
(let ((addr (logandc2 (get-lisp-obj-address code) other-pointer-lowtag))
(tree *dynspace-codeblob-tree*))
(loop (let ((newtree (sb-brothertree:insert addr tree)))
;; check that it hasn't been promoted from gen0 -> gen1 already
;; (very unlikely, but certainly possible).
(unless (eq (generation-of code) 0) (return))
(let ((oldval (cas *codeblob-tree* tree newtree)))
(if (eq oldval tree) (return) (setq tree oldval))))))))
(let ((oldval (cas *dynspace-codeblob-tree* tree newtree)))
(if (eq oldval tree) (return) (setq tree oldval)))))))
;; FIXME: there may be random values in the unboxed payload and it's not obvious
;; that all callers of ALLOCATE-CODE-OBJECT always write all raw bytes.
@ -513,5 +234,48 @@
;; of the object so that we can find the function table.
;; But what about other things that create code objects?
;; It could be a subtle source of nondeterministic core images.
(values code total-words))))
(values code total-words)))
;; The freelist can only be read while holding a mutex because the codeblobs
;; themselves are used to construct the freelist. It would be dangerous to
;; suppose that you could read the "link" field out of a blob (that currently
;; has FILLER_WIDETAG allegedly) if another thread got scheduled such that it
;; freed that filler and the reallocated into it instantly.
(defun immobile-code-dealloc-1 (scratchpad)
(declare (cons scratchpad) (ignorable scratchpad))
#+immobile-code
(flet ((pop-1 ()
;; Remove an item from codeblob-freelist. Mutex must be held
;; and GC inhibited.
(let ((head codeblob-freelist))
(unless (eql head 0)
(setf codeblob-freelist (sap-ref-word (int-sap head) n-word-bytes)))
(%make-lisp-obj head)))) ; use funny fixnum representation
(when (eql (car scratchpad) 0)
;; I'd prefer that this be done using WITH-SYSTEM-MUTEX + pseudo-atomic
;; rather than WITHOUT-GCING in as much as WITHOUT-GCING needs to cease to exist.
(let ((word (if (eql codeblob-freelist 0)
0
(with-system-mutex (*allocator-mutex* :without-gcing t) (pop-1)))))
(when (eql word 0)
(return-from immobile-code-dealloc-1 nil))
(setf (car scratchpad) word)))
(let ((addr (get-lisp-obj-address (car scratchpad))))
;; We have to remove from the tree before removing from the TLSF pool,
;; because presence of a key in the tree is an assertion that there is
;; memory block at that address. Consider if we removed from the pool
;; without removing from the tree, the block could be coalesced on either side
;; and there would not necessarily be a block where the tree says it is.
(let ((tree sb-vm::*immobile-codeblob-tree*))
(loop (when (eq tree (setq tree (cas *immobile-codeblob-tree* tree
(sb-brothertree:delete addr tree))))
(return))))
(with-alien ((tlsf-unalloc-codeblob (function void system-area-pointer unsigned)
:extern)
(tlsf-control system-area-pointer :extern))
;; Prevent GC from walking the text space at the exact same instant
;; the block coalescing algorithm alters block headers.
(with-system-mutex (*allocator-mutex* :without-gcing t)
(alien-funcall tlsf-unalloc-codeblob tlsf-control addr)
(setf (car scratchpad) (pop-1)))))
t))

View file

@ -136,7 +136,8 @@
(/show0 "entering !COLD-INIT")
#+sb-show (setq */show* t)
(setq sb-vm::*codeblob-tree* nil)
(setq sb-vm::*immobile-codeblob-tree* nil
sb-vm::*dynspace-codeblob-tree* nil)
(setq sb-kernel::*defstruct-hooks* '(sb-kernel::!bootstrap-defstruct-hook)
sb-kernel::*struct-accesss-fragments-delayed* nil)
(let ((stream (!make-cold-stderr-stream)))

View file

@ -239,21 +239,29 @@ Examples:
;;; See the trace at the bottom of this file.
(defvar *in-a-finalizer* nil)
#+sb-thread (define-alien-variable finalizer-thread-runflag int)
(defun run-pending-finalizers ()
(defun run-pending-finalizers (&aux (hashtable (finalizer-id-map **finalizer-store**))
(ran-a-system-finalizer)
(system-finalizer-scratchpad (list 0))
(ran-a-user-finalizer))
(declare (truly-dynamic-extent system-finalizer-scratchpad))
;; This never acquires the finalizer store lock. Code accordingly.
(let ((hashtable (finalizer-id-map **finalizer-store**)))
(loop
;; Perform no further work if trying to stop the thread, even if there is work.
#+sb-thread (when (zerop finalizer-thread-runflag) (return))
(let ((cell (hash-table-culled-values hashtable)))
;; This is like atomic-pop, but its obtains the first cons cell
;; in the list, not the car of the first cons.
;; Possible TODO: when no other work remains, free the *JOINABLE-THREADS*,
;; though MAKE-THREAD and JOIN-THREAD do that also, so there's no memory leak.
(loop (unless cell (return-from run-pending-finalizers))
(let ((actual (cas (hash-table-culled-values hashtable)
cell (cdr cell))))
(if (eq actual cell) (return) (setq cell actual))))
(loop
;; Perform no further work if trying to stop the thread, even if there is work.
#+sb-thread (when (zerop finalizer-thread-runflag) (return))
;; Try to run 1 system finalizer
(setq ran-a-system-finalizer (sb-vm::immobile-code-dealloc-1 system-finalizer-scratchpad))
;; Try to run 1 user finalizer
(let ((cell (hash-table-culled-values hashtable)))
;; This is like atomic-pop, but its obtains the first cons cell
;; in the list, not the car of the first cons.
;; Possible TODO: when no other work remains, free the *JOINABLE-THREADS*,
;; though MAKE-THREAD and JOIN-THREAD do that also, so there's no memory leak.
(loop (unless cell (return))
(let ((actual (cas (hash-table-culled-values hashtable)
cell (cdr cell))))
(if (eq actual cell) (return) (setq cell actual))))
(when cell
(let* ((id (the index (car cell)))
;; No other thread can modify **FINALIZER-STORE** at index ID
;; because the table no longer contains an object mapping to
@ -280,6 +288,7 @@ Examples:
(if (simple-vector-p finalizers)
(map nil #'call finalizers)
(call finalizers)))
(setq ran-a-user-finalizer t)
;; While the assignment to (SVREF STORE ID) should have been adequate,
;; we don't know that the vector is current - a new vector could have
;; gotten assigned into **FINALIZER-STORE** in between [1] and [2],
@ -297,7 +306,11 @@ Examples:
;; removing dangling references, but if it's just a function,
;; there's nothing to smash.
(cond ((simple-vector-p finalizers) (fill finalizers 0))
((consp finalizers) (rplaca finalizers 0))))))))
((consp finalizers) (rplaca finalizers 0))))))
;; Did this iteration do anything at all?
(unless (or ran-a-system-finalizer ran-a-user-finalizer) (return))
(setq ran-a-system-finalizer nil
ran-a-user-finalizer nil)))
(define-load-time-global *finalizer-thread* nil)
(declaim (type (or sb-thread:thread (eql :start) null) *finalizer-thread*))

View file

@ -754,10 +754,10 @@ functions when called with no arguments."
(let ((sap (int-sap (get-lisp-obj-address code))))
;; NB: This is not threadsafe on machines that don't promise that
;; stores to single bytes are atomic.
(setf (sap-ref-8 sap #+little-endian (- 1 sb-vm:other-pointer-lowtag)
#+big-endian (- 6 sb-vm:other-pointer-lowtag))
(setf (sap-ref-8 sap #+little-endian (- 2 sb-vm:other-pointer-lowtag)
#+big-endian (- 5 sb-vm:other-pointer-lowtag))
bit)
;; touch the card mark
;; touch the card mark - WHY???
(setf (code-header-ref code 1) (code-header-ref code 1)))))
;;; FIXME: Symbol is lost by accident

View file

@ -1880,10 +1880,7 @@ variable: an unreadable object representing the error is printed instead.")
(defmethod print-object ((component code-component) stream)
(print-unreadable-object (component stream :identity t)
(let (dinfo)
(cond ((code-obj-is-filler-p component)
(format stream "filler ~dw"
(ash (code-object-size component) (- sb-vm:word-shift))))
((eq (setq dinfo (%code-debug-info component)) :bpt-lra)
(cond ((eq (setq dinfo (%code-debug-info component)) :bpt-lra)
(write-string "bpt-trap-return" stream))
((functionp dinfo)
(format stream "trampoline ~S" dinfo))

View file

@ -323,18 +323,13 @@ We could try a few things to mitigate this:
(map-objects-in-range fun start end)))
#+immobile-space
(:immobile
;; Filter out filler objects. These either look like cons cells
;; in fixedobj subspace, or code without enough header words
;; in text subspace. (cf 'filler_obj_p' in gc-internal.h)
(with-system-mutex (*allocator-mutex*)
(map-immobile-objects fun :variable))
;; Filter out padding words
(dx-flet ((filter (obj type size)
(unless (= type list-pointer-lowtag)
(funcall fun obj type size))))
(map-immobile-objects #'filter :fixed))
(dx-flet ((filter (obj type size)
(unless (and (code-component-p obj)
(code-obj-is-filler-p obj))
(funcall fun obj type size))))
(map-immobile-objects #'filter :variable))))))
(map-immobile-objects #'filter :fixed))))))
(do-rest-arg ((space) spaces)
(if (eq space :dynamic)
(without-gcing #+cheneygc (do-1-space space)
@ -445,15 +440,20 @@ We could try a few things to mitigate this:
(used-bytes (ash (- free-pointer start) n-fixnum-tag-bits))
(holes '())
(hole-bytes 0))
(map-immobile-objects
(lambda (obj type size)
(let ((address (logandc2 (get-lisp-obj-address obj) lowtag-mask)))
(when (case subspace
(:fixed (= type list-pointer-lowtag))
(:variable (hole-p address)))
(push (cons address size) holes)
(incf hole-bytes size))))
subspace)
(if (eq subspace :fixed)
(map-immobile-objects
(lambda (obj type size)
(let ((address (logandc2 (get-lisp-obj-address obj) lowtag-mask)))
(when (= type list-pointer-lowtag)
(incf hole-bytes size))))
subspace)
(let ((sum-sizes 0))
(map-immobile-objects
(lambda (obj type size)
(declare (ignore obj type))
(incf sum-sizes size))
subspace)
(setq hole-bytes (- used-bytes sum-sizes))))
(values holes hole-bytes used-bytes)))
(defun show-fragmentation (&key (subspaces '(:fixed :variable))

View file

@ -391,12 +391,6 @@
(code-header-set code sb-vm::code-fixups-slot newval)
newval)
(declaim (inline code-obj-is-filler-p))
(defun code-obj-is-filler-p (code-obj)
;; See also HOLE-P in the allocator (same thing but using SAPs)
;; and filler_obj_p() in the C code
(eql (sb-vm::%code-boxed-size code-obj) 0))
#+(or sparc ppc64)
(defun code-trailer-ref (code offset)
(with-pinned-objects (code)
@ -404,15 +398,12 @@
(+ (code-object-size code) offset (- sb-vm:other-pointer-lowtag)))))
;;; The last 'uint16' in the object holds the trailer length (see 'src/runtime/code.h')
;;; but do not attempt to read it if the object is a filler.
(declaim (inline code-trailer-len))
(defun code-trailer-len (code-obj)
(if (code-obj-is-filler-p code-obj)
0
(let ((word (code-trailer-ref code-obj -4)))
;; TRAILER-REF returns 4-byte quantities. Extract a two-byte quantity.
#+little-endian (ldb (byte 16 16) word)
#+big-endian (ldb (byte 16 0) word))))
(let ((word (code-trailer-ref code-obj -4)))
;; TRAILER-REF returns 4-byte quantities. Extract a two-byte quantity.
#+little-endian (ldb (byte 16 16) word)
#+big-endian (ldb (byte 16 0) word)))
;;; The fun-table-count is a uint16_t immediately preceding the trailer length
;;; containing two subfields:

View file

@ -259,7 +259,7 @@
(print-unreadable-object (gspace stream :type t)
(format stream "@#x~X ~S" (gspace-byte-address gspace) (gspace-name gspace))))
(defun make-gspace (name identifier byte-address)
(defun make-gspace (name identifier byte-address &rest rest)
;; Genesis should be agnostic of space alignment except in so far as it must
;; be a multiple of the backend page size. We used to care more, in that
;; descriptor-bits were composed of a high half and low half for the
@ -272,7 +272,7 @@
(unless (zerop (rem byte-address target-space-alignment))
(error "The byte address #X~X is not aligned on a #X~X-byte boundary."
byte-address target-space-alignment)))
(%make-gspace :name name
(apply #'%make-gspace :name name
:identifier identifier
;; Track page usage
:page-table (if (= identifier dynamic-core-space-id)
@ -282,7 +282,8 @@
((= identifier immobile-fixedobj-core-space-id)
(/ sb-vm:immobile-card-bytes sb-vm:n-word-bytes))
(t
0))))
0))
rest))
(defstruct (model-sap (:constructor make-model-sap (address gspace)))
(address 0 :type sb-vm:word)
@ -1904,9 +1905,35 @@ core and return a descriptor to it."
;; Put the C-callable fdefns into the static-fdefn vector if #+immobile-code.
#+immobile-code
(loop for i from 0 for sym in sb-vm::+c-callable-fdefns+
do (cold-svset *c-callable-fdefn-vector* i
(ensure-cold-fdefn sym)))
(let* ((space *immobile-text*)
(wordindex (gspace-free-word-index space))
(words-per-page (/ sb-vm:immobile-card-bytes sb-vm:n-word-bytes)))
(loop for i from 0 for sym in sb-vm::+c-callable-fdefns+
do (cold-svset *c-callable-fdefn-vector* i
(ensure-cold-fdefn sym)))
(let* ((objects (gspace-objects space))
(count (length objects)))
(let ((remainder (rem wordindex words-per-page)))
(unless (zerop remainder)
(let* ((fill-nwords (- words-per-page remainder))
(des
;; technically FILLER_WIDETAG has no valid lowtag because it's not an object
;; that lisp can address. But WRITE-WORDINDEXED requires a pointer descriptor
(allocate-cold-descriptor space (* fill-nwords sb-vm:n-word-bytes)
sb-vm:other-pointer-lowtag)))
(aver (zerop (rem (gspace-free-word-index space) words-per-page)))
(write-header-word des (logior (ash fill-nwords 32) sb-vm:filler-widetag)))))
;; Construct a ub32 array of object offsets.
(let* ((n-data-words (ceiling count 2)) ; lispword = 2 ub32s
(vect (allocate-vector sb-vm:simple-array-unsigned-byte-32-widetag
count n-data-words))
(data-ptr (+ (descriptor-byte-offset vect)
(ash sb-vm:vector-data-offset sb-vm:word-shift))))
(dotimes (i count)
(setf (bvref-32 (descriptor-mem vect) data-ptr)
(descriptor-byte-offset (aref objects i)))
(incf data-ptr 4))
(cold-set 'sb-vm::*immobile-codeblob-vector* vect))))
;; Symbols for which no call to COLD-INTERN would occur - due to not being
;; referenced until warm init - must be artificially cold-interned.
@ -3841,7 +3868,8 @@ III. initially undefined function references (alphabetically):
#+immobile-space
(*immobile-text* (make-gspace :immobile-text
immobile-text-core-space-id
sb-vm:text-space-start))
sb-vm:text-space-start
:objects (make-array 20000 :fill-pointer 0 :adjustable t)))
(*dynamic* (make-gspace :dynamic
dynamic-core-space-id
#+gencgc sb-vm:dynamic-space-start

View file

@ -215,8 +215,10 @@
,@'(*current-catch-block*
*current-unwind-protect-block*)
#+immobile-space *immobile-freelist* ; not per-thread (yet...)
#+metaspace *metaspace-tracts*
*immobile-codeblob-tree* ; for generations 0 through 5 inclusive
*immobile-codeblob-vector* ; for pseudo-static-generation
*dynspace-codeblob-tree*
;; stack pointers
#-sb-thread *binding-stack-start* ; a thread slot if #+sb-thread
@ -227,7 +229,6 @@
;; threading support
#+sb-thread ,@'(sb-thread::*starting-threads* *free-tls-index*)
*codeblob-tree*
;; runtime linking of lisp->C calls (regardless of whether
;; the C function is in a dynamic shared object or not)

View file

@ -439,13 +439,6 @@ static void relocate_space(uword_t start, lispobj* end, struct heap_adjust* adj)
adjust_word_at(where+3, adj);
continue;
case CODE_HEADER_WIDETAG:
if (filler_obj_p(where)) {
// OMGWTF! Why does a filler code object merit adjustment?
// (Probably for when holes were chained through debug-info?
// But we don't save holes any more, because of defrag)
if (where[2]) adjust_word_at(where+2, adj);
continue;
}
// Fixup the constant pool. The word at where+1 is a fixnum.
code = (struct code*)where;
adjust_pointers(where+2, code_header_words(code)-2, adj);

View file

@ -547,13 +547,21 @@ static void clobber_headered_object(lispobj* addr, sword_t nwords)
// FIXME: clobbering an object on single-object pages should free entire pages
page_index_t page = find_page_index(addr);
if (page < 0) { // code space
struct code* code = (struct code*)addr;
if (!filler_obj_p((lispobj*)code)) {
code->boxed_size = 0;
code->header = (nwords << CODE_HEADER_SIZE_SHIFT)
| CODE_HEADER_WIDETAG;
memset(addr+2, 0, (nwords - 2) * N_WORD_BYTES);
#ifdef LISP_FEATURE_IMMOBILE_SPACE
extern lispobj codeblob_freelist;
if (widetag_of(addr) == CODE_HEADER_WIDETAG) {
// OAOO violation - like sweep_immobile_text()
assign_widetag(addr, FILLER_WIDETAG);
((char*)addr)[2] = 0; // clear the TRACED flag
((char*)addr)[3] = 0; // clear the WRITTEN flag and the generation
// add to list only if it is above tlsf_mem_start
// (below it will never by utilized by the TLSF allocator)
if (addr >= tlsf_mem_start) {
addr[1] = codeblob_freelist; // push into to-be-freed list
codeblob_freelist = (lispobj)addr;
}
}
#endif
} else if ((SINGLE_OBJECT_FLAG|page_table[page].type) == (SINGLE_OBJECT_FLAG|PAGE_TYPE_CODE)) {
// Code pages don't want (0 . 0) fillers, otherwise heap checking
// gets an error: "object @ 0x..... is non-code on code page"
@ -584,7 +592,8 @@ static uword_t sweep(lispobj* where, lispobj* end,
fullcgcmarks[index / N_WORD_BITS] & ((uword_t)1 << (index % N_WORD_BITS));
if (is_header(word)) {
nwords = headerobj_size2(where, word);
if (!livep) clobber_headered_object(where, nwords);
if (!livep && header_widetag(word) != FILLER_WIDETAG)
clobber_headered_object(where, nwords);
} else {
nwords = 2;
if (!livep) where[0] = where[1] = (uword_t)-1;
@ -626,6 +635,15 @@ void execute_full_sweep_phase()
if (sweeplog) fprintf(sweeplog, "-- text space --\n");
sweep((lispobj*)TEXT_SPACE_START, text_space_highwatermark,
(uword_t)words_zeroed);
// Recompute generation masks for text space
int npages = (ALIGN_UP((uword_t)text_space_highwatermark, IMMOBILE_CARD_BYTES)
- TEXT_SPACE_START) / IMMOBILE_CARD_BYTES;
memset(text_page_genmask, 0, npages);
lispobj* where = (lispobj*)TEXT_SPACE_START;
for ( ; where < text_space_highwatermark ; where += object_size(where) )
if (widetag_of(where) == CODE_HEADER_WIDETAG)
text_page_genmask[find_text_page_index(where)]
|= (1 << immobile_obj_gen_bits(where));
#endif
if (sweeplog) fprintf(sweeplog, "-- dynamic space --\n");
walk_generation(sweep, -1, (uword_t)words_zeroed);

View file

@ -152,10 +152,6 @@ static inline int header_rememberedp(lispobj header) {
return (header & (OBJ_WRITTEN_FLAG << 24)) != 0;
}
static inline boolean filler_obj_p(lispobj* obj) {
return widetag_of(obj) == CODE_HEADER_WIDETAG && obj[1] == 0;
}
#ifdef LISP_FEATURE_IMMOBILE_SPACE
extern void enliven_immobile_obj(lispobj*,int);
@ -575,4 +571,10 @@ static inline boolean plausible_tag_p(lispobj addr)
# define filler_total_nwords(header) ((header)>>N_WIDETAG_BITS)
#endif
#ifdef LISP_FEATURE_BIG_ENDIAN
# define assign_widetag(addr, byte) ((unsigned char*)addr)[N_WORD_BYTES-1] = byte
#else
# define assign_widetag(addr, byte) *(unsigned char*)addr = byte
#endif
#endif /* _GC_PRIVATE_H_ */

View file

@ -83,4 +83,5 @@ int hexdump_and_verify_heap(lispobj*, int flags);
page_index_t gc_find_freeish_pages(page_index_t *restart_page_ptr, sword_t nbytes,
int page_type, generation_index_t gen);
extern void tlsf_dump_pool(void*, void*, char *pathname);
#endif /* _GC_H_ */

View file

@ -402,10 +402,7 @@ int count_immobile_objects(__attribute__((unused)) int gen, int res[5])
where = (lispobj*)TEXT_SPACE_START;
end = text_space_highwatermark;
while (where < end) {
if (immobile_obj_generation(where) == gen
&& widetag_of(where) == CODE_HEADER_WIDETAG
// don't count filler code
&& ((struct code*)where)->boxed_size)
if (widetag_of(where) != FILLER_WIDETAG && immobile_obj_generation(where) == gen)
++res[4];
where += object_size(where);
}
@ -1884,7 +1881,7 @@ lispobj *search_dynamic_space(void *pointer)
// in generation 0 following a non-promotion cycle.
if (type == PAGE_TYPE_CODE && page_table[page_index].gen == 0) {
lispobj node = brothertree_find_lesseql((uword_t)pointer,
SYMBOL(CODEBLOB_TREE)->value);
SYMBOL(DYNSPACE_CODEBLOB_TREE)->value);
if (node != NIL) {
lispobj *codeblob = (lispobj*)((struct binary_node*)INSTANCE(node))->key;
if (widetag_of(codeblob) != CODE_HEADER_WIDETAG)
@ -2812,9 +2809,8 @@ static lispobj* range_dirty_p(lispobj* where, lispobj* limit, generation_index_t
if (leaf_obj_widetag_p(widetag)) {
// Do nothing
} else if (widetag == CODE_HEADER_WIDETAG) {
// This function will never be called on a page of code, hence if we
// see genuine (non-filler) code, that's a bug. */
if (!filler_obj_p(where)) lose("code @ %p on non-code page", where);
// This function will never be called on a page of code
lose("code @ %p on non-code page", where);
} else {
#ifdef LISP_FEATURE_COMPACT_INSTANCE_HEADER
if (instanceoid_widetag_p(widetag)) {
@ -4382,7 +4378,7 @@ maybe_verify:
* so just erase the tree now.
* This is WRONG for immobile code, but not worse than status quo
* in terms of inability to find objects in the SIGPROF handler etc */
SYMBOL(CODEBLOB_TREE)->value = NIL;
SYMBOL(DYNSPACE_CODEBLOB_TREE)->value = NIL;
if (generation >= verify_gens)
hexdump_and_verify_heap(cur_thread_approx_stackptr, VERIFY_POST_GC | (generation<<16));
@ -5791,7 +5787,8 @@ static inline boolean obj_gen_lessp(lispobj obj, generation_index_t b)
sword_t scav_code_blob(lispobj *object, lispobj header)
{
struct code* code = (struct code*)object;
if (filler_obj_p(object)) goto done; /* it's not code at all */
int nboxed = code_header_words(code);
if (!nboxed) goto done;
++n_scav_calls[CODE_HEADER_WIDETAG/4];
@ -5850,7 +5847,7 @@ sword_t scav_code_blob(lispobj *object, lispobj header)
* the object, then scavenge all entry points. Otherwise there is no need,
* as trans_code() made necessary adjustments to internal entry points.
* This test is just an optimization to avoid some work */
if (((*object >> 8) & 0xff) == CODE_IS_TRACED) {
if (((*object >> 16) & 0xff) == CODE_IS_TRACED) {
#else
{ /* Not enough spare bits in the header to hold random flags.
* Just do the extra work always */

View file

@ -62,6 +62,7 @@ lispobj *text_space_highwatermark;
lispobj *fixedobj_free_pointer;
#endif
os_vm_address_t anon_dynamic_space_start;
lispobj* tlsf_mem_start; // meaningful only if immobile space
#ifndef LISP_FEATURE_GENCGC /* GENCGC has its own way to record trigger */
lispobj *current_auto_gc_trigger;

View file

@ -103,6 +103,7 @@ extern lispobj *text_space_highwatermark;
extern lispobj *fixedobj_free_pointer;
#endif
extern os_vm_address_t anon_dynamic_space_start;
extern lispobj* tlsf_mem_start; // meaningful only if immobile space
# ifndef LISP_FEATURE_GENCGC
extern lispobj *current_auto_gc_trigger;

View file

@ -59,7 +59,9 @@
#include "unaligned.h"
#include "code.h"
#include "lispstring.h"
#include "tlsf-bsd/tlsf/tlsf.h"
#include "search.h"
#include "brothertree.h"
#include <stdlib.h>
#include <stdio.h>
@ -126,9 +128,18 @@ static inline lispobj* compute_fixedobj_limit(void* base, int spacing_bytes) {
/// Variable-length pages:
unsigned char* text_page_genmask;
// scan-start-offset, measured in bytes from page base address.
// one per page *excluding* all pseudostatic pages.
// Unlike with dynamic-space, the scan start for a text page
// is an address not lower than the base page.
unsigned short int* tlsf_page_sso;
// Array of inverted write-protect flags, 1 bit per page.
unsigned int* text_page_touched_bits;
static int n_bitmap_elts; // length of array measured in 'int's
// List of FILLER_WIDETAG objects to be stuffed back into the TLSF-managed pool
// chained through the word after their header.
lispobj codeblob_freelist;
boolean immobile_card_protected_p(void* addr)
{
@ -140,28 +151,90 @@ boolean immobile_card_protected_p(void* addr)
lose("immobile_card_protected_p(%p)", addr);
}
struct text_page *text_pages;
// Holes to be stuffed back into the managed free list.
lispobj text_holes;
void* tlsf_control;
#define text_page_touched(x) ((text_page_touched_bits[x/32] >> (x&31)) & 1)
// These bits are in the same place in the code header as the ones
// in 'tlsf.c' but shifted by 8 more so that they refer to the header
// word as a whole.
static const unsigned block_header_free_bit = 1 << 8;
static const unsigned block_header_prev_free_bit = 1 << 9;
static const unsigned block_header_oversized = 1 << 10;
//// Variable-length utilities
/* Return the generation mask for objects headers on 'page_index'
including at most one object that starts before the page but ends on
or after it.
If the scan start is within the page, i.e. less than DOUBLEWORDS_PER_PAGE
(note that the scan start is measured relative to the page end) then
we don't need to OR in the generation byte from an extra object,
as all headers on the page are accounted for in the page generation mask.
Also an empty page (where scan start is zero) avoids looking
at the next page's first object by accident via the same test. */
unsigned char text_page_gens_augmented(low_page_index_t page_index)
#define IMMOBILE_CARD_SHIFT 12
void *tlsf_alloc_codeblob(tlsf_t tlsf, int requested_nwords)
{
return (text_pages[page_index].scan_start_offset <= DOUBLEWORDS_PER_PAGE
? 0 : (1<<immobile_obj_generation(text_page_scan_start(page_index))))
| text_pages[page_index].generations;
// The size we request is 1 word less, because the allocator's block header
// counts as part of the resulting object as far as Lisp is concerned.
int size = (requested_nwords - 1) << WORD_SHIFT;
void* tlsf_result = tlsf_malloc(tlsf, size);
if (!tlsf_result) return 0;
struct code* c = (void*)((lispobj*)tlsf_result - 1);
gc_assert(!((uintptr_t)c & LOWTAG_MASK));
assign_widetag(c, CODE_HEADER_WIDETAG);
c->boxed_size = c->debug_info = c->fixups = 0;
int nwords = code_total_nwords(c);
// Indicate oversized allocation in a header bit so that we can eliminate
// 2 words of padding when saving a core (not done yet)
if (nwords > requested_nwords) c->header |= block_header_oversized;
((lispobj*)c)[nwords-1] = 0; // trailer word with the simple-fun table
lispobj* end = (lispobj*)c + nwords;
if (end > text_space_highwatermark) text_space_highwatermark = end;
// Adjust the scan start if this became the lowest addressable in-use block on its page
low_page_index_t tlsf_page = ((char*)c - (char*)tlsf_mem_start) >> IMMOBILE_CARD_SHIFT;
int offset = (uword_t)c & (IMMOBILE_CARD_BYTES-1);
if (offset < tlsf_page_sso[tlsf_page]) tlsf_page_sso[tlsf_page] = offset;
text_page_genmask[find_text_page_index(c)] |= 1;
#if 0
if (code_total_nwords(c) > requested_nwords)
fprintf(stderr, "NOTE: asked for %d words but got %d\n",
requested_nwords, code_total_nwords(c));
#endif
return c;
}
void tlsf_unalloc_codeblob(tlsf_t tlsf, struct code* code)
{
int nwords = code_total_nwords(code);
lispobj* end = (lispobj*)code + nwords;
/* If the HWM is the end of the object being freed, adjust the HWM. There are 2 cases:
* 1. if the previous block is free, then the start of the previous physical block
* is the new high water mark. The rightmost blocks in this picture get conbined.
* The block to the left of the already-free one is definitely used.
* +-------+------+----------+
* | used | free | freeing | <- current HWM
* +-------+------+----------+
* ^ new HWM
*
* 2. previous block is in-use: this object's address is the new HWM
*/
if (end == text_space_highwatermark) {
if (code->header & block_header_prev_free_bit)
/* The word prior to 'code' is the pointer to the previous physical block_header_t.
* The high water mark is 1 word beyond the previous physical block due to the
* discrepancy between block_header_t and where a block logically begins. */
text_space_highwatermark = 1 + (lispobj*)((lispobj*)code)[-1];
else
text_space_highwatermark = (lispobj*)code;
gc_assert(!((uword_t)text_space_highwatermark & LOWTAG_MASK));
}
// See if the page scan start needs to change
low_page_index_t tlsf_page = ((char*)code - (char*)tlsf_mem_start) >> IMMOBILE_CARD_SHIFT;
int offset = (uword_t)code & (IMMOBILE_CARD_BYTES-1);
if (offset == tlsf_page_sso[tlsf_page]) {
lispobj* next = (lispobj*)code + code_total_nwords((struct code*)code);
if (*next & block_header_free_bit) {
next += code_total_nwords((struct code*)next);
gc_assert(!(*next & block_header_free_bit)); // adjacent free blocks can't occur
}
// If the next used block is on the same page, then it becomes the page scan start
// even if it the ending sentinel block (which counts as "used").
tlsf_page_sso[tlsf_page] =
(((uword_t)next ^ (uword_t)code) >> IMMOBILE_CARD_SHIFT) == 0
? (uword_t)next & (IMMOBILE_CARD_BYTES-1) : USHRT_MAX;
}
// point to the user data, not the header, when calling free
tlsf_free(tlsf, (lispobj*)code + 1);
}
//// Fixed-length object allocator
@ -371,7 +444,7 @@ enliven_immobile_obj(lispobj *ptr, int rescan) // a native pointer
if (page_index < 0) {
page_index = find_text_page_index(ptr);
gc_assert(page_index >= 0);
text_pages[page_index].generations |= 1<<new_space;
text_page_genmask[page_index] |= 1<<new_space;
is_text = 1;
} else {
fixedobj_pages[page_index].gens |= 1<<new_space;
@ -404,6 +477,50 @@ enliven_immobile_obj(lispobj *ptr, int rescan) // a native pointer
++immobile_scav_queue_count;
}
// The end of immobile text mapped from disk, equivalently the starting address
// of new objects handed out by the code allocator.
static uint32_t* loaded_codeblob_offsets;
static int loaded_codeblob_offsets_len;
// Find the lowest addressed object on specified page, or 0 if there isn't one
lispobj* text_page_scan_start(low_page_index_t page) {
char* pagebase = text_page_address(page);
if (pagebase < (char*)tlsf_mem_start) {
uint32_t* data = loaded_codeblob_offsets;
int index = bsearch_greatereql_uint32((int)(pagebase-(char*)TEXT_SPACE_START),
data, loaded_codeblob_offsets_len);
lispobj* start = 0;
// I don't think index could ever be -1 ("not found"), could it?
if (index >= 0) start = (lispobj*)(TEXT_SPACE_START+data[index]);
// But it is possible for a page to have no scan start (nothing starts on it)
return (start && (char*)start < pagebase+IMMOBILE_CARD_BYTES) ? start : 0;
}
if (pagebase > (char*)text_space_highwatermark) return 0;
int tlsf_page = (pagebase - (char*)tlsf_mem_start) / IMMOBILE_CARD_BYTES;
unsigned short sso = tlsf_page_sso[tlsf_page];
return (sso < IMMOBILE_CARD_BYTES) ? (lispobj*)(pagebase + sso) : 0;
}
lispobj* search_immobile_code(char* ptr) {
if (ptr < (char*)TEXT_SPACE_START) return 0;
lispobj* candidate = 0;
if (ptr < (char*)tlsf_mem_start) {
uint32_t* data = loaded_codeblob_offsets;
int index = bsearch_lesseql_uint32((int)(ptr-(char*)TEXT_SPACE_START),
data, loaded_codeblob_offsets_len);
if (index >= 0) candidate = (lispobj*)(TEXT_SPACE_START+data[index]);
} else if (ptr < (char*)text_space_highwatermark) {
lispobj node = brothertree_find_lesseql((uword_t)ptr,
SYMBOL(IMMOBILE_CODEBLOB_TREE)->value);
if (node != NIL) candidate = (lispobj*)((struct binary_node*)INSTANCE(node))->key;
}
if (candidate && widetag_of(candidate) == CODE_HEADER_WIDETAG) {
int nwords = code_total_nwords((struct code*)candidate);
if (ptr < (char*)(candidate+nwords)) return candidate;
}
return 0;
}
/* If 'addr' points to an immobile object, then make the object
live by promotion. But if the object is not in the generation
being collected, do nothing */
@ -412,25 +529,10 @@ boolean immobile_space_preserve_pointer(void* addr)
unsigned char genmask = compacting_p() ? 1<<from_space : 0xff;
lispobj* object_start;
int valid = 0;
low_page_index_t page_index = find_text_page_index(addr);
low_page_index_t page_index;
if (page_index >= 0) {
// Restrict addr to lie below 'text_space_highwatermark'.
// This way, if the gens byte is nonzero but there is
// a final array acting as filler on the remainder of the
// final page, we won't accidentally find that.
lispobj* scan_start;
valid = addr < (void*)text_space_highwatermark
&& (text_page_gens_augmented(page_index) & genmask)
&& (scan_start = text_page_scan_start(page_index)) <= (lispobj*)addr
&& (object_start = gc_search_space(scan_start, addr)) != 0
/* gc_search_space can return filler objects, unlike
* search_immobile_space which can not */
&& !filler_obj_p(object_start)
&& (instruction_ptr_p(addr, object_start)
|| properly_tagged_descriptor_p(addr, object_start));
} else if ((page_index = find_fixedobj_page_index(addr)) >= FIXEDOBJ_RESERVED_PAGES
&& ((fixedobj_pages[page_index].gens & genmask) != 0)) {
if ((page_index = find_fixedobj_page_index(addr)) >= FIXEDOBJ_RESERVED_PAGES
&& ((fixedobj_pages[page_index].gens & genmask) != 0)) {
int obj_spacing = fixedobj_page_obj_align(page_index);
int obj_index = ((uword_t)addr & (IMMOBILE_CARD_BYTES-1)) / obj_spacing;
dprintf((logfile,"Pointer %p is to immobile page %d, object %d\n",
@ -441,8 +543,12 @@ boolean immobile_space_preserve_pointer(void* addr)
&& (widetag_of(object_start) == FUNCALLABLE_INSTANCE_WIDETAG ||
widetag_of(object_start) == FDEFN_WIDETAG ||
properly_tagged_descriptor_p(addr, object_start));
} else {
return 0;
} else if (compacting_p() && (lispobj*)addr < tlsf_mem_start) {
// Can ignore this pointer if it's point to pseudostatic text
return 0;
} else if ((object_start = search_immobile_code(addr)) != 0) {
valid = instruction_ptr_p(addr, object_start)
|| properly_tagged_descriptor_p(addr, object_start);
}
if (valid && (!compacting_p() ||
immobile_obj_gen_bits(object_start) == from_space)) {
@ -490,10 +596,12 @@ static void full_scavenge_immobile_newspace()
// Find the next page with anything in newspace.
do {
if (++page > max_used_text_page) return;
} while ((text_pages[page].generations & bit) == 0);
} while ((text_page_genmask[page] & bit) == 0);
lispobj* obj = text_page_scan_start(page);
if (!obj) continue; // page contains nothing - can this happen?
do {
lispobj* limit = (lispobj*)text_page_address(page) + WORDS_PER_PAGE;
if (limit > text_space_highwatermark) limit = text_space_highwatermark;
int n_words;
for ( ; obj < limit ; obj += n_words ) {
lispobj header = *obj;
@ -504,12 +612,13 @@ static void full_scavenge_immobile_newspace()
n_words = headerobj_size2(obj, header);
}
}
page = find_text_page_index(obj);
gc_assert(obj <= text_space_highwatermark);
// Bail out if exact absolute end of immobile space was reached.
if (page < 0) return;
if (obj == text_space_highwatermark) break;
// If 'page' should be scanned, then pick up where we left off,
// without recomputing 'obj' but setting a higher 'limit'.
} while (text_pages[page].generations & bit);
page = find_text_page_index(obj);
} while (text_page_genmask[page] & bit);
}
}
@ -561,33 +670,6 @@ void scavenge_immobile_newspace()
}
}
// Return a page >= page_index having potential old->young pointers,
// or -1 if there isn't one.
static int next_text_root_page(unsigned int page_index,
unsigned int end_bitmap_index,
unsigned char genmask)
{
unsigned int map_index = page_index / 32;
if (map_index >= end_bitmap_index) return -1;
int bit_index = page_index & 31;
// Look only at bits of equal or greater weight than bit_index.
unsigned int word = (0xFFFFFFFFU << bit_index) & text_page_touched_bits[map_index];
while (1) {
if (word) {
bit_index = ffs(word) - 1;
page_index = map_index * 32 + bit_index;
if (text_page_gens_augmented(page_index) & genmask)
return page_index;
else {
word ^= (1U<<bit_index);
continue;
}
}
if (++map_index >= end_bitmap_index) return -1;
word = text_page_touched_bits[map_index];
}
}
void
scavenge_immobile_roots(generation_index_t min_gen, generation_index_t max_gen)
{
@ -615,16 +697,20 @@ scavenge_immobile_roots(generation_index_t min_gen, generation_index_t max_gen)
} while (NEXT_FIXEDOBJ(obj, obj_spacing) <= limit);
}
// Variable-length object pages
// Text pages
low_page_index_t max_used_text_page = calc_max_used_text_page();
unsigned n_text_pages = 1+max_used_text_page;
unsigned end_bitmap_index = (n_text_pages+31)/32;
page = next_text_root_page(0, end_bitmap_index, genmask);
while (page >= 0) {
page = 0;
while (page <= max_used_text_page) {
if (!text_page_touched(page) || !(text_page_genmask[page] & genmask)) {
++page;
continue;
}
lispobj* obj = text_page_scan_start(page);
if (!obj) { ++page; continue; }
do {
lispobj* limit = (lispobj*)text_page_address(page) + WORDS_PER_PAGE;
int n_words, gen;
if (limit > text_space_highwatermark) limit = text_space_highwatermark;
for ( ; obj < limit ; obj += n_words ) {
lispobj header = *obj;
// scav_code_blob will do nothing if the object isn't
@ -636,12 +722,12 @@ scavenge_immobile_roots(generation_index_t min_gen, generation_index_t max_gen)
n_words = headerobj_size2(obj, header);
}
}
if (obj == text_space_highwatermark) { page = -1; break; }
page = find_text_page_index(obj);
} while (page > 0
&& (text_pages[page].generations & genmask)
&& (text_page_genmask[page] & genmask)
&& text_page_touched(page));
if (page < 0) break;
page = next_text_root_page(1+page, end_bitmap_index, genmask);
}
if (sb_sprof_enabled) {
// Make another pass over all code and enliven all of 'from_space'
@ -769,41 +855,6 @@ fixedobj_points_to_younger_p(lispobj* obj, int n_words,
return range_points_to_younger_p(obj+1, obj+n_words, gen, keep_gen, new_gen);
}
static boolean
text_points_to_younger_p(lispobj* obj, int gen, int keep_gen, int new_gen,
os_vm_address_t page_begin,
os_vm_address_t page_end) // upper (exclusive) bound
{
lispobj *begin, *end, word = *obj;
unsigned char widetag = header_widetag(word);
if (widetag == CODE_HEADER_WIDETAG) { // usual case. Like scav_code_blob()
return header_rememberedp(word);
} else if (widetag == FDEFN_WIDETAG ||
widetag == FUNCALLABLE_INSTANCE_WIDETAG) {
// both of these have non-descriptor bits in at least one word,
// thus precluding a simple range scan.
// Due to ignored address bounds, in the rare case of a FIN or fdefn in text
// subspace and spanning cards, we might say that neither card can be protected,
// when one or the other could be. Not a big deal.
return fixedobj_points_to_younger_p(obj, sizetab[widetag](obj),
gen, keep_gen, new_gen);
} else if (widetag == SIMPLE_VECTOR_WIDETAG) {
sword_t length = vector_len((struct vector *)obj);
begin = obj + 2; // skip the header and length
end = obj + ALIGN_UP(length + 2, 2);
} else if (leaf_obj_widetag_p(widetag)) {
return 0;
} else {
lose("Unexpected widetag %x @ %p", widetag, obj);
}
// Fallthrough: scan words from begin to end
if (page_begin > (os_vm_address_t)begin) begin = (lispobj*)page_begin;
if (page_end < (os_vm_address_t)end) end = (lispobj*)page_end;
if (end > begin && range_points_to_younger_p(begin, end, gen, keep_gen, new_gen))
return 1;
return 0;
}
/// The next two functions are analogous to 'update_page_write_prot()'
/// but they differ in that they are "precise" - random code bytes that look
/// like pointers are not accidentally treated as pointers.
@ -831,24 +882,15 @@ static inline boolean can_wp_fixedobj_page(page_index_t page, int keep_gen, int
return 1;
}
// To scan _only_ 'page' is impossible in general, but we can act like only
// one page was scanned by backing up to the first object whose end is on
// or after it, and then restricting points_to_younger within the boundaries.
// Doing it this way is probably much better than conservatively assuming
// that any word satisfying is_lisp_pointer() is a pointer.
static inline boolean can_wp_text_page(page_index_t page, int keep_gen, int new_gen)
// Return 1 if any header on 'page' is in the remembered set.
static inline boolean can_wp_text_page(page_index_t page)
{
lispobj *begin = text_page_address(page);
lispobj *end = begin + WORDS_PER_PAGE;
lispobj *obj = text_page_scan_start(page);
for ( ; obj < end ; obj += headerobj_size(obj) ) {
gc_assert(other_immediate_lowtag_p(*obj));
if (!filler_obj_p(obj) &&
text_points_to_younger_p(obj,
immobile_obj_generation(obj),
keep_gen, new_gen,
(os_vm_address_t)begin,
(os_vm_address_t)end))
if (widetag_of(obj) == CODE_HEADER_WIDETAG && header_rememberedp(*obj))
return 0;
}
return 1;
@ -984,42 +1026,31 @@ sweep_fixedobj_pages(int raise)
memset(fixedobj_page_hint, 0, sizeof fixedobj_page_hint);
}
static void make_filler(void* where, int nbytes)
{
if (nbytes < 4*N_WORD_BYTES)
lose("can't place filler @ %p - too small", where);
else { // Create a filler object.
struct code* code = (struct code*)where;
code->header = ((uword_t)nbytes << (CODE_HEADER_SIZE_SHIFT-WORD_SHIFT))
| CODE_HEADER_WIDETAG;
code->boxed_size = 0;
code->debug_info = text_holes;
text_holes = (lispobj)code;
}
}
// Scan for freshly trashed objects and turn them into filler.
// Lisp is responsible for consuming the free space
// when it next allocates a variable-size object.
static void
sweep_text_pages(int raise)
{
lispobj *freelist = 0, *freelist_tail = 0;
SETUP_GENS();
low_page_index_t max_used_text_page = calc_max_used_text_page();
lispobj* free_pointer = text_space_highwatermark;
low_page_index_t page;
for (page = 0; page <= max_used_text_page; ++page) {
int genmask = text_pages[page].generations;
int genmask = text_page_genmask[page];
if (!(genmask & relevant_genmask)) { // Has nothing in oldspace or newspace.
// Scan for old->young pointers, and WP if there are none.
if (ENABLE_PAGE_PROTECTION && text_page_touched(page)
&& text_page_gens_augmented(page) > 1
&& can_wp_text_page(page, keep_gen, new_gen)) {
&& text_page_genmask[page] > 1
&& can_wp_text_page(page)) {
text_page_touched_bits[page/32] &= ~(1U<<(page & 31));
}
continue;
}
lispobj* obj = text_page_scan_start(page);
gc_assert(obj);
lispobj* page_base = text_page_address(page);
lispobj* limit = page_base + WORDS_PER_PAGE;
if (limit > free_pointer) limit = free_pointer;
@ -1027,44 +1058,23 @@ sweep_text_pages(int raise)
// wp_it is 1 if we should try to write-protect it now.
// If already write-protected, skip the tests.
int wp_it = ENABLE_PAGE_PROTECTION && text_page_touched(page);
lispobj* obj = text_page_scan_start(page);
int size, gen;
if (obj < page_base) {
// An object whose tail is on this page, or which spans this page,
// would have been promoted/kept while dealing with the page with
// the object header. Therefore we don't need to consider that object,
// * except * that we do need to consider whether it is an old object
// pointing to a young object.
if (wp_it // If we wanted to try write-protecting this page,
// and the object starting before this page is strictly older
// than the generation that we're moving retained objects into
&& (gen = immobile_obj_gen_bits(obj)) > new_gen
// and it contains an old->young pointer
&& text_points_to_younger_p(obj, gen, keep_gen, new_gen,
(os_vm_address_t)page_base,
(os_vm_address_t)limit)) {
wp_it = 0;
}
// We MUST skip this object in the sweep, because in the case of
// non-promotion (raise=0), we could see an object in from_space
// and believe it to be dead.
obj += headerobj_size(obj);
// obj can't hop over this page. If it did, there would be no
// headers on the page, and genmask would have been zero.
gc_assert(obj < limit);
}
for ( ; obj < limit ; obj += size ) {
lispobj word = *obj;
size = object_size2(obj, word);
if (filler_obj_p(obj)) { // do nothing
if (header_widetag(word) == FILLER_WIDETAG) { // ignore
} else if ((gen = immobile_obj_gen_bits(obj)) == discard_gen) {
if (header_widetag(word) == CODE_HEADER_WIDETAG) {
/* fprintf(stderr, "%lX freed (id=%x)\n",
make_lispobj(obj, OTHER_POINTER_LOWTAG),
code_serialno((struct code*)obj)); */
}
make_filler(obj, size * N_WORD_BYTES);
gc_assert(header_widetag(word) == CODE_HEADER_WIDETAG);
assign_widetag(obj, FILLER_WIDETAG);
// ASSUMPTION: little-endian
((char*)obj)[2] = 0; // clear the TRACED flag
((char*)obj)[3] = 0; // clear the WRITTEN flag and the generation
// Building the list in ascending order means less work later on
// because the HWM will get adjusted once only, at the end.
// Descending order would decrease the HWM for each deallocation.
if (freelist) freelist_tail[1] = (lispobj)obj; else freelist = obj;
freelist_tail = obj;
} else if (gen == keep_gen) {
assign_generation(obj, gen = new_gen);
#ifdef DEBUG
@ -1073,17 +1083,19 @@ sweep_text_pages(int raise)
(os_vm_address_t)limit));
#endif
any_kept = -1;
} else if (wp_it &&
text_points_to_younger_p(obj, gen, keep_gen, new_gen,
(os_vm_address_t)page_base,
(os_vm_address_t)limit))
} else if (wp_it && header_rememberedp(*obj))
wp_it = 0;
}
COMPUTE_NEW_MASK(mask, text_pages[page].generations);
text_pages[page].generations = mask;
COMPUTE_NEW_MASK(mask, text_page_genmask[page]);
text_page_genmask[page] = mask;
if ( mask && wp_it )
text_page_touched_bits[page/32] &= ~(1U << (page & 31));
}
// Stuff the new freelist onto the front of codeblob_freelist
if (freelist_tail) {
freelist_tail[1] = codeblob_freelist;
codeblob_freelist = (lispobj)freelist;
}
}
// TODO: (Maybe this won't work. Not sure yet.) rather than use the
@ -1110,14 +1122,15 @@ void gc_init_immobile()
gc_assert(fixedobj_pages);
n_bitmap_elts = ALIGN_UP(n_text_pages, 32) / 32;
int request = n_bitmap_elts * sizeof (int) + n_text_pages * sizeof (int);
text_page_touched_bits = (unsigned int*)calloc(1, request);
text_page_touched_bits = (unsigned int*)calloc(n_bitmap_elts, sizeof (int));
gc_assert(text_page_touched_bits);
// The conservative value for 'touched' is 1.
memset(text_page_touched_bits, 0xff, n_bitmap_elts * sizeof (int));
text_pages = (struct text_page*)(text_page_touched_bits + n_bitmap_elts);
text_page_genmask = calloc(n_text_pages, 1);
// Scav queue is arbitrarily located.
immobile_scav_queue = malloc(QCAPACITY * sizeof(lispobj));
tlsf_control = os_validate(0, (char*)0x90000000, ALIGN_UP(tlsf_size(), 4096), 0, 0);
tlsf_create(tlsf_control);
}
// Signify that scan_start is initially not reliable
@ -1141,9 +1154,8 @@ void immobile_space_coreparse(uword_t fixedobj_len, uword_t text_len)
where += object_size(where);
}
where = (lispobj*)TEXT_SPACE_START;
end = (lispobj*)((char*)where + text_len);
while (where < end) {
if (!filler_obj_p(where)) assign_generation(where, gen);
while (where < text_space_highwatermark) {
if (widetag_of(where) != FILLER_WIDETAG) assign_generation(where, gen);
where += object_size(where);
}
// If the regression suite is run with core pages in gen0 (to more aggressively
@ -1187,60 +1199,36 @@ void immobile_space_coreparse(uword_t fixedobj_len, uword_t text_len)
return;
}
uword_t address = TEXT_SPACE_START;
n_pages = text_len / IMMOBILE_CARD_BYTES;
lispobj* obj = (lispobj*)address;
int n_words;
low_page_index_t last_page = 0;
gc_assert(PTR_ALIGN_UP(text_space_highwatermark, IMMOBILE_CARD_BYTES) == text_space_highwatermark);
// Don't use text_len because that measures backend pages (32k),
// but we're willing to start in the middle of such a page
// with new code allocation.
n_pages = ((uword_t)text_space_highwatermark-TEXT_SPACE_START) / IMMOBILE_CARD_BYTES;
for (page = 0; page < n_pages ; ++page) text_page_genmask[page] |= 1<<gen;
// coreparse() already set text_space_highwatermark
lispobj* limit = text_space_highwatermark;
gc_assert(limit != 0 /* would be zero if not mmapped yet */
&& limit <= (lispobj*)(address + text_len));
for ( ; obj < limit ; obj += n_words ) {
gc_assert(other_immediate_lowtag_p(obj[0]));
n_words = headerobj_size(obj);
if (filler_obj_p(obj)) {
// Holes were chained through the debug_info slot at save.
// Just update the head of the chain.
text_holes = (lispobj)obj;
continue;
}
low_page_index_t first_page = find_text_page_index(obj);
last_page = find_text_page_index(obj+n_words-1);
// Only the page with this object header gets a bit in its gen mask.
text_pages[first_page].generations |= 1<<immobile_obj_gen_bits(obj);
// For each page touched by this object, set the page's
// scan_start_offset, unless it was already set.
int page;
for (page = first_page ; page <= last_page ; ++page) {
if (!text_pages[page].scan_start_offset) {
long offset = (char*)text_page_address(page+1) - (char*)obj;
text_pages[page].scan_start_offset = offset >> (WORD_SHIFT + 1);
}
}
}
// Write a padding object if necessary
if ((uword_t)limit & (IMMOBILE_CARD_BYTES-1)) {
int remainder = IMMOBILE_CARD_BYTES - ((uword_t)limit & (IMMOBILE_CARD_BYTES-1));
int words = (remainder >> WORD_SHIFT) - 2; // discount the array header itself
if (limit[0] == SIMPLE_ARRAY_FIXNUM_WIDETAG) {
gc_assert(vector_len((struct vector*)limit) == words);
} else {
#ifdef LISP_FEATURE_UBSAN
limit[0] = ((uword_t)words << (32+N_FIXNUM_TAG_BITS)) | SIMPLE_ARRAY_FIXNUM_WIDETAG;
#else
limit[0] = SIMPLE_ARRAY_FIXNUM_WIDETAG;
limit[1] = make_fixnum(words);
#endif
}
int size = sizetab[SIMPLE_ARRAY_FIXNUM_WIDETAG](limit);
lispobj* __attribute__((unused)) padded_end = limit + size;
gc_assert(!((uword_t)padded_end & (IMMOBILE_CARD_BYTES-1)));
}
gc_assert(text_space_highwatermark != 0 /* would be zero if not mmapped yet */
&& text_space_highwatermark <= (lispobj*)(address + text_len));
tlsf_mem_start = text_space_highwatermark;
struct vector* v = VECTOR(SYMBOL(IMMOBILE_CODEBLOB_VECTOR)->value);
gc_assert(widetag_of((lispobj*)v) == SIMPLE_ARRAY_UNSIGNED_BYTE_32_WIDETAG);
// The vector itself is pseudo-static in dynamic space
if(gencgc_verbose) fprintf(stderr, "pseudostatic codeblob vector is %p\n", v);
loaded_codeblob_offsets = (void*)v->data;
loaded_codeblob_offsets_len = vector_len(v);
// Create a TLSF pool
char *tlsf_memory_end = (char*)TEXT_SPACE_START + text_space_size;
int tlsf_memory_size = tlsf_memory_end - (char*)tlsf_mem_start;
tlsf_add_pool(tlsf_control, tlsf_mem_start, tlsf_memory_size);
int n_tlsf_pages = tlsf_memory_size / IMMOBILE_CARD_BYTES;
tlsf_page_sso = malloc(n_tlsf_pages * sizeof (short int));
memset(tlsf_page_sso, 0xff, n_tlsf_pages * sizeof (short int));
// Set the WP bits for pages occupied by the core file.
// (There can be no inter-generation pointers.)
if (gen != 0 && ENABLE_PAGE_PROTECTION) {
low_page_index_t page;
for (page = 0 ; page <= last_page ; ++page)
for (page = 0 ; page <= n_pages ; ++page)
text_page_touched_bits[page/32] &= ~(1U<<(page & 31));
}
page_attributes_valid = 1;
@ -1253,9 +1241,6 @@ void prepare_immobile_space_for_final_gc()
char* page_base;
char* page_end = (char*)fixedobj_free_pointer;
// The list of holes need not be saved.
SYMBOL(IMMOBILE_FREELIST)->value = NIL;
for (page = 0, page_base = fixedobj_page_address(page) ;
page_base < page_end ;
page_base += IMMOBILE_CARD_BYTES, ++page) {
@ -1274,12 +1259,23 @@ void prepare_immobile_space_for_final_gc()
lispobj* obj = (lispobj*)TEXT_SPACE_START;
lispobj* limit = text_space_highwatermark;
int npages = (ALIGN_UP((uword_t)limit, IMMOBILE_CARD_BYTES) - TEXT_SPACE_START)
/ IMMOBILE_CARD_BYTES;
memset(text_page_genmask, 0, npages);
for ( ; obj < limit ; obj += headerobj_size(obj) ) {
if (!filler_obj_p(obj) && immobile_obj_gen_bits(obj) != 0) {
if (widetag_of(obj) != FILLER_WIDETAG) {
assign_generation(obj, 0);
text_pages[find_text_page_index(obj)].generations = 1;
text_page_genmask[find_text_page_index(obj)] = 1;
}
}
// The object offset vector needs to be copied out of the heap
// so that it can be freed by GC.
int nbytes = sizeof (uint32_t) * loaded_codeblob_offsets_len;
lispobj* vector_copy = malloc(nbytes);
loaded_codeblob_offsets = memcpy(vector_copy, loaded_codeblob_offsets, nbytes);
SYMBOL(IMMOBILE_CODEBLOB_VECTOR)->value = NIL;
SYMBOL(IMMOBILE_CODEBLOB_TREE)->value = NIL;
}
int* code_component_order;
@ -1304,21 +1300,37 @@ void prepare_immobile_space_for_save(boolean verbose)
obj += object_size(obj);
}
obj = (lispobj*)TEXT_SPACE_START;
limit = text_space_highwatermark;
for ( text_holes = 0 ; obj < limit ; obj += headerobj_size(obj) ) {
if (filler_obj_p(obj)) {
struct code* code = (struct code*)obj;
code->debug_info = text_holes;
code->fixups = 0;
text_holes = (lispobj)code;
// 0-fill the unused space.
int nwords = headerobj_size(obj);
memset(code->constants, 0,
(nwords * N_WORD_BYTES) - offsetof(struct code, constants));
} else
assign_generation(obj, PSEUDO_STATIC_GENERATION);
int codeblob_count = 0;
for ( obj = (lispobj*)TEXT_SPACE_START ; obj < text_space_highwatermark ; obj += headerobj_size(obj) ) {
// *obj &= ~(uword_t)block_header_prev_free_bit;
assign_generation(obj, PSEUDO_STATIC_GENERATION);
++codeblob_count;
}
// Create a vector of code offsets
int n_data_words = ALIGN_UP(codeblob_count, 2) >> 1;
int vector_nwords = 2 + ALIGN_UP(n_data_words, 2);
// FIXME: need to reorder some things so that this gets moved to R/O space
struct vector* v = gc_general_alloc(unboxed_region, vector_nwords<<WORD_SHIFT,
PAGE_TYPE_UNBOXED);
v->header = SIMPLE_ARRAY_UNSIGNED_BYTE_32_WIDETAG;
v->length_ = make_fixnum(codeblob_count);
gc_close_region(unboxed_region, PAGE_TYPE_UNBOXED);
// Zero-fill the end of dynamic space since we aready performed zero_all_free_ranges().
uword_t vector_end = (uword_t)((lispobj*)v + vector_nwords),
aligned_end = ALIGN_UP(vector_end, BACKEND_PAGE_BYTES);
memset((char*)vector_end, 0, aligned_end-vector_end);
uint32_t* data = (uint32_t*)v->data;
int i = 0;
for ( obj = (lispobj*)TEXT_SPACE_START ; obj < text_space_highwatermark ; obj += headerobj_size(obj) ) {
data[i++] = (int)((uword_t)obj - TEXT_SPACE_START);
}
// Write a filler if needed to align tlsf_mem_start
lispobj* aligned_hwm = PTR_ALIGN_UP(text_space_highwatermark, IMMOBILE_CARD_BYTES);
if (text_space_highwatermark < aligned_hwm) {
*text_space_highwatermark = make_filler_header(aligned_hwm - text_space_highwatermark);
text_space_highwatermark = aligned_hwm;
}
SYMBOL(IMMOBILE_CODEBLOB_VECTOR)->value = make_lispobj(v, OTHER_POINTER_LOWTAG);
if (verbose) printf("done]\n");
}
@ -1353,19 +1365,9 @@ static struct tempspace {
lispobj *
search_immobile_space(void *pointer)
{
lispobj *start;
if ((void*)TEXT_SPACE_START <= pointer
&& pointer < (void*)text_space_highwatermark) {
low_page_index_t page_index = find_text_page_index(pointer);
if (page_attributes_valid) {
start = text_page_scan_start(page_index);
if (start > (lispobj*)pointer) return NULL;
} else {
start = (lispobj*)TEXT_SPACE_START;
}
lispobj* found = gc_search_space(start, pointer);
return (found && filler_obj_p(found)) ? 0 : found;
if ((void*)TEXT_SPACE_START <= pointer && pointer < (void*)text_space_highwatermark) {
if (!page_attributes_valid)lose("Can't search");
return search_immobile_code(pointer);
} else if ((void*)FIXEDOBJ_SPACE_START <= pointer
&& pointer < (void*)fixedobj_free_pointer) {
low_page_index_t page_index = find_fixedobj_page_index(pointer);
@ -1399,34 +1401,6 @@ search_immobile_space(void *pointer)
return NULL;
}
// For coalescing holes, we need to scan backwards, which is done by
// looking backwards for a page that contains the start of a
// block of objects one of which must abut 'obj'.
lispobj* find_preceding_object(lispobj* obj)
{
int page = find_text_page_index(obj);
gc_assert(page >= 0);
while (1) {
int offset = text_pages[page].scan_start_offset;
if (offset) { // 0 means the page is empty.
lispobj* start = text_page_scan_start(page);
if (start < obj) { // Scan from here forward
while (1) {
lispobj* end = start + headerobj_size(start);
if (end == obj) return start;
gc_assert(end < obj);
start = end;
}
}
}
if (page == 0) {
gc_assert(obj == text_page_address(0));
return 0; // Predecessor does not exist
}
--page;
}
}
// FIXME: Figure out not to hardcode
#define FUN_TRAMP_SIZE 6
#define GF_SIZE 6
@ -1679,7 +1653,7 @@ static lispobj* get_load_address(lispobj* old)
{
if (forwarding_pointer_p(old))
return native_pointer(forwarding_pointer_value(old));
gc_assert(filler_obj_p(old));
gc_assert(widetag_of(old) == FILLER_WIDETAG);
return 0;
}
@ -1933,15 +1907,12 @@ static void defrag_immobile_space(boolean verbose)
gc_assert(lowtag_of((lispobj)addr) == OTHER_POINTER_LOWTAG);
addr = native_pointer((lispobj)addr);
int widetag = widetag_of(addr);
gc_assert(widetag == CODE_HEADER_WIDETAG);
gc_assert(widetag == CODE_HEADER_WIDETAG || widetag == FILLER_WIDETAG);
lispobj new_vaddr = 0;
// A code component can become garbage in the final GC
// (defrag happens after the last GC) leaving a filler object
// which was in components[] because it was live before GC.
if (!filler_obj_p(addr)) {
// must not be a trampoline object
if ((lispobj)addr > TEXT_SPACE_START)
gc_assert(code_n_funs((struct code*)addr));
if (widetag == CODE_HEADER_WIDETAG) {
++n_code_components;
new_vaddr = TEXT_SPACE_START + n_code_bytes;
n_code_bytes += sizetab[widetag](addr) << WORD_SHIFT;
@ -1949,11 +1920,7 @@ static void defrag_immobile_space(boolean verbose)
components[i*2+1] = new_vaddr;
}
}
int aligned_nbytes = ALIGN_UP(n_code_bytes, IMMOBILE_CARD_BYTES);
if (aligned_nbytes - n_code_bytes == 2 * N_WORD_BYTES)
// waste another page because it can't be a 2-word filler
aligned_nbytes += IMMOBILE_CARD_BYTES;
text_tempspace.n_bytes = aligned_nbytes;
text_tempspace.n_bytes = n_code_bytes;
text_tempspace.start = calloc(text_tempspace.n_bytes, 1);
if (verbose)
@ -1993,9 +1960,6 @@ static void defrag_immobile_space(boolean verbose)
set_forwarding_pointer(addr, make_lispobj((void*)new_vaddr,
OTHER_POINTER_LOWTAG));
}
if (aligned_nbytes > n_code_bytes)
make_filler(tempspace_addr((char*)TEXT_SPACE_START + n_code_bytes),
aligned_nbytes - n_code_bytes);
}
#if DEFRAGMENT_FIXEDOBJ_SUBSPACE

View file

@ -44,28 +44,8 @@ text_page_address(low_page_index_t page_num)
return (void*)(TEXT_SPACE_START + (page_num * IMMOBILE_CARD_BYTES));
}
struct text_page {
// Generation mask for objects which start on this page.
// An object which ends on but does not start on this page
// does not set the respective bit.
unsigned int generations: 8,
// Offset backwards in double-lispwords from the page end to the
// lowest-addressed object touching the page. This offset can point to
// a hole, but we prefer that it not. If the offset is zero, the page
// has no object other than possibly a hole resulting from a freed object.
// The entire space size defaults to just over 100MiB,
// so 24 bits is more than adequate to point back to any word.
scan_start_offset: 24;
};
extern struct text_page *text_pages;
/* Calculate the address where the first object touching this page starts. */
static inline lispobj*
text_page_scan_start(low_page_index_t page_index)
{
return (lispobj*)((char*)text_page_address(page_index+1)
- text_pages[page_index].scan_start_offset * (2 * N_WORD_BYTES));
}
extern unsigned char* text_page_genmask;
extern unsigned short int* tlsf_page_sso;
static inline low_page_index_t find_fixedobj_page_index(void *addr)
{

View file

@ -38,7 +38,8 @@
#include "genesis/primitive-objects.h"
#include "genesis/gc-tables.h"
#include "gc-internal.h"
#include "tlsf-bsd/tlsf/tlsf.h"
extern void* tlsf_control;
/* When we need to do command input, we use this stream, which is not
* in general stdin, so that things will "work" (as well as being
@ -69,13 +70,16 @@ struct crash_preamble {
uword_t fixedobj_start, fixedobj_size, fixedobj_free_pointer;
// text data dumped: pages, touched_bits, page table
uword_t text_start, text_size;
lispobj *text_space_highwatermark;
lispobj *tlsf_mem_start, *text_space_highwatermark;
lispobj sentinel_block[3];
void* tlsf_control_address;
int nthreads;
int tls_size;
lispobj lisp_package_vector;
int sizeof_context;
int tlsf_control_size;
char sprof_enabled;
char pin_dynspace_code;
int sizeof_context;
};
struct crash_thread_preamble {
uword_t address;
@ -130,12 +134,17 @@ void save_gc_crashdump(char *pathname,
preamble.pin_dynspace_code = pin_all_dynamic_space_code;
preamble.sizeof_context = sizeof (os_context_t);
#ifdef LISP_FEATURE_IMMOBILE_SPACE
char *tlsf_memory_end = (char*)TEXT_SPACE_START + TEXT_SPACE_SIZE;
preamble.fixedobj_start = FIXEDOBJ_SPACE_START;
preamble.fixedobj_size = FIXEDOBJ_SPACE_SIZE;
preamble.fixedobj_free_pointer = (uword_t)fixedobj_free_pointer;
preamble.text_start = TEXT_SPACE_START;
preamble.text_size = TEXT_SPACE_SIZE;
preamble.text_space_highwatermark = text_space_highwatermark;
preamble.tlsf_mem_start = tlsf_mem_start;
preamble.tlsf_control_address = tlsf_control;
preamble.tlsf_control_size = tlsf_size();
memcpy(preamble.sentinel_block, tlsf_memory_end-3*N_WORD_BYTES, 3*N_WORD_BYTES);
#endif
// write the preamble and static + readonly spaces
checked_write("preamble", fd, &preamble, sizeof preamble);
@ -152,11 +161,16 @@ void save_gc_crashdump(char *pathname,
int total_npages = FIXEDOBJ_SPACE_SIZE / IMMOBILE_CARD_BYTES;
checked_write("fixedobj_PTE", fd, fixedobj_pages, total_npages * sizeof sizeof(struct fixedobj_page));
usage = (uword_t)text_space_highwatermark - TEXT_SPACE_START;
checked_write("text", fd, (char*)TEXT_SPACE_START, usage);
// write the block_header_t that is just beyond the high water mark
checked_write("text", fd, (char*)TEXT_SPACE_START, usage+3*N_WORD_BYTES);
total_npages = TEXT_SPACE_SIZE / IMMOBILE_CARD_BYTES;
int n_bitmap_elts = ALIGN_UP(total_npages, 32) / 32;
checked_write("text_gen", fd, text_page_genmask, total_npages); // 1 byte per page
checked_write("text_WP", fd, text_page_touched_bits, n_bitmap_elts * sizeof (int));
checked_write("text_PTE", fd, text_pages, total_npages * sizeof (int));
checked_write("TLSF_control", fd, tlsf_control, preamble.tlsf_control_size);
int tlsf_memory_size = tlsf_memory_end - (char*)tlsf_mem_start;
int n_tlsf_pages = tlsf_memory_size / IMMOBILE_CARD_BYTES;
checked_write("TLSF_sso", fd, tlsf_page_sso, n_tlsf_pages * sizeof (short));
#endif
struct crash_thread_preamble thread_preamble;
for_each_thread(th) {
@ -205,7 +219,7 @@ void save_gc_crashdump(char *pathname,
// write the preamble
checked_write("thread", fd, &thread_preamble, sizeof thread_preamble);
// write 0 or 1 contexts, control-stack, binding-stack, TLS
if (ici) write(fd, threadcontext, preamble.sizeof_context);
if (ici) checked_write(" ctxt", fd, threadcontext, preamble.sizeof_context);
#ifdef LISP_FEATURE_C_STACK_IS_CONTROL_STACK
checked_write(" stack", fd, (char*)sp, nbytes_control_stack);
#else
@ -289,6 +303,13 @@ static int gc_cmd(char **ptr) {
return 0;
}
static int tlsf_cmd(__attribute__((unused)) char **ptr) {
#ifdef LISP_FEATURE_IMMOBILE_SPACE
tlsf_dump_pool(tlsf_control, tlsf_mem_start, "/dev/tty");
#endif
return 0;
}
static struct cmd {
char *cmd, *help;
int (*fn)(char **ptr);
@ -311,6 +332,7 @@ static struct cmd {
{"search", "Search heap for object.", search_cmd},
{"save", "Produce crashdump", save_cmd},
{"threads", "List threads", threads_cmd},
{"tlsfdump", "Dump TLSF structures", tlsf_cmd},
{"verify", "Check heap invariants", verify_cmd},
{"gc", "Collect garbage", gc_cmd},
{NULL, NULL, NULL}
@ -903,11 +925,23 @@ int load_gc_crashdump(char* pathname)
checked_read("fixedobj_PTE", fd, fixedobj_pages, total_npages * sizeof sizeof(struct fixedobj_page));
// Read text space
usage = (uword_t)text_space_highwatermark - TEXT_SPACE_START;
checked_read("text", fd, (char*)TEXT_SPACE_START, usage);
char *tlsf_memory_end = (char*)TEXT_SPACE_START + TEXT_SPACE_SIZE;
tlsf_mem_start = preamble.tlsf_mem_start;
fprintf(stderr, "tlsf_mem_start=%p\n", tlsf_mem_start);
int tlsf_memory_size = tlsf_memory_end - (char*)tlsf_mem_start;
checked_read("text", fd, (char*)TEXT_SPACE_START, usage+3*N_WORD_BYTES);
memcpy(tlsf_memory_end-3*N_WORD_BYTES, preamble.sentinel_block, 3*N_WORD_BYTES);
total_npages = TEXT_SPACE_SIZE / IMMOBILE_CARD_BYTES;
int n_bitmap_elts = ALIGN_UP(total_npages, 32) / 32;
checked_read("text_gen", fd, text_page_genmask, total_npages); // 1 byte per page
checked_read("text_WP", fd, text_page_touched_bits, n_bitmap_elts * sizeof (int));
checked_read("text_PTE", fd, text_pages, total_npages * sizeof (int));
tlsf_control = preamble.tlsf_control_address; // already mapped at a fixed address
// TLSF control was mapped in gc_init_immobile()
checked_read("TLSF_control", fd, tlsf_control, preamble.tlsf_control_size);
int n_tlsf_pages = tlsf_memory_size / IMMOBILE_CARD_BYTES;
fprintf(stderr, "%d TLSF pages\n", n_tlsf_pages);
tlsf_page_sso = malloc(n_tlsf_pages * sizeof (short));
checked_read("TLSF_sso", fd, tlsf_page_sso, n_tlsf_pages * sizeof (short));
write_protect_immobile_space();
#endif
fprintf(stderr, "%d threads:\n", (int)preamble.nthreads);

View file

@ -134,9 +134,7 @@ verify_pointer(lispobj thing, lispobj *where, struct verify_state *state)
"unallocated space");
} else {
// The object pointed to must not have been discarded as garbage.
FAIL_IF(!other_immediate_lowtag_p(*native_pointer(thing)) ||
filler_obj_p(native_pointer(thing)),
"trashed object");
FAIL_IF(!other_immediate_lowtag_p(*native_pointer(thing)), "trashed object");
}
// Must not point to a forwarding pointer
FAIL_IF(*native_pointer(thing) == FORWARDING_HEADER, "forwarding ptr");
@ -321,10 +319,7 @@ static int verify_range(lispobj* start, lispobj* end, struct verify_state* state
continue;
}
#endif
// FIXME: should not have code acting as filler
if (widetag != FILLER_WIDETAG && !filler_obj_p(where)) {
// Any page can have a filler on it
if (pg >= 0) {
if (widetag != FILLER_WIDETAG && pg >= 0) {
// Assert proper page type
if (state->object_header) // is not a cons
gc_assert(page_table[pg].type != PAGE_TYPE_CONS);
@ -342,7 +337,6 @@ static int verify_range(lispobj* start, lispobj* end, struct verify_state* state
if (is_code(page_table[pg].type))
lose("object @ %p is non-code on code page", where);
}
}
}
}
if (!state->object_header) {
@ -480,7 +474,7 @@ void gc_show_pte(lispobj obj)
page = find_text_page_index((void*)obj);
if (page>=0) {
lispobj* text_page_scan_start(low_page_index_t page);
int gens = text_pages[page].generations;
int gens = text_page_genmask[page];
char genstring[9];
int i;
for (i=0;i<8;++i) genstring[i] = (gens & (1<<i)) ? '0'+i : '-';

View file

@ -203,7 +203,8 @@
sb-impl::*token-buf-pool*
sb-impl::*user-hash-table-tests*
sb-impl::**finalizer-store**
sb-vm::*codeblob-tree*
sb-vm::*immobile-codeblob-tree*
sb-vm::*dynspace-codeblob-tree*
,(maybe "SB-KERNEL" "*EVAL-CALLS*")
sb-kernel::*type-cache-nonce*
sb-ext:*gc-run-time*

View file

@ -11,9 +11,9 @@
#include <stdlib.h>
#include <string.h>
#include "tlsf.h"
#include "tlsf-bsd/tlsf/tlsf.h"
#include "tlsf_utils.h"
#include "tlsf-bsd/tlsf/tlsf_utils.h"
#if __GNUC__ || __INTEL_COMPILER
#define likely(x) __builtin_expect(!!(x), 1)
@ -30,6 +30,9 @@
#define tlsf_assert(expr) (void)(0)
#endif
#include "genesis/config.h"
#include "genesis/constants.h"
/* Public constants: may be modified. */
enum tlsf_public {
/* log2 of number of linear subdivisions of block sizes. Larger
@ -61,11 +64,7 @@ enum tlsf_private {
* blocks below that size into the 0th first-level list.
*/
#if defined(TLSF_64BIT)
FL_INDEX_MAX = 40, /* 1 TB */
#else
FL_INDEX_MAX = 30,
#endif
SL_INDEX_COUNT = (1 << SL_INDEX_COUNT_LOG2),
FL_INDEX_SHIFT = (SL_INDEX_COUNT_LOG2 + ALIGN_SIZE_LOG2),
FL_INDEX_COUNT = (FL_INDEX_MAX - FL_INDEX_SHIFT + 1),
@ -117,8 +116,17 @@ typedef struct block_header_t {
/* Points to the previous physical block. */
struct block_header_t *prev_phys_block;
/* The size of this block, excluding the block header. */
size_t size;
#ifdef LISP_FEATURE_BIG_ENDIAN
// For this word to read as an object header, the size and widetag
// are flipped relative to little-endian.
#error "not done"
#else
unsigned char widetag;
unsigned char _flags; // must have at most bits 0, 1, 2 on
unsigned char unused; // must be zero
unsigned char gen; // low 4 must be 0..5 and bit 0x4 can be on
uint32_t _nwords; // including the header
#endif
/* Next and previous free blocks. */
struct block_header_t *next_free;
@ -126,13 +134,11 @@ typedef struct block_header_t {
} block_header_t;
/*
* Since block sizes are always at least a multiple of 4, the two least
* significant bits of the size field are used to store the block status:
* - bit 0: whether block is busy or free
* - bit 1: whether previous block is busy or free
* - byte 1 bit 0: whether block is busy (0) or free (1)
* - byte 1 bit 1: whether previous block is busy (0) or free (1)
*/
static const size_t block_header_free_bit = 1 << 0;
static const size_t block_header_prev_free_bit = 1 << 1;
static const unsigned char block_header_free_bit = 1 << 0;
static const unsigned char block_header_prev_free_bit = 1 << 1;
/*
* The size of the block header exposed to used blocks is the size field.
@ -147,8 +153,7 @@ static const size_t block_header_overhead = sizeof(size_t);
static const size_t block_header_overlap = sizeof(block_header_t *);
/* User data starts directly after the size field in a used block. */
static const size_t block_start_offset =
offsetof(block_header_t, size) + sizeof(size_t);
static const size_t block_start_offset = offsetof(block_header_t, next_free);
/*
* A free block must be large enough to store its header minus the size of
@ -178,49 +183,49 @@ typedef struct control_t {
static size_t block_size(const block_header_t *block)
{
return block->size & ~(block_header_free_bit | block_header_prev_free_bit);
return (block->_nwords - 1) << WORD_SHIFT; // nbytes excluding the lispobj header
}
static void block_set_size(block_header_t *block, size_t size)
{
const size_t oldsize = block->size;
block->size =
size | (oldsize & (block_header_free_bit | block_header_prev_free_bit));
// convert to words inclusive of the header, as codeblobs require
block->_nwords = (size >> WORD_SHIFT) + 1;
}
__attribute__((unused)) static int block_is_last(const block_header_t *block)
{
return block_size(block) == 0;
return block->_nwords <= 2;
}
static int block_is_free(const block_header_t *block)
{
return tlsf_cast(int, block->size &block_header_free_bit);
return tlsf_cast(int, block->_flags & block_header_free_bit);
}
static void block_set_free(block_header_t *block)
{
block->size |= block_header_free_bit;
tlsf_assert(block->widetag == FILLER_WIDETAG);
block->_flags |= block_header_free_bit;
}
static void block_set_used(block_header_t *block)
{
block->size &= ~block_header_free_bit;
block->_flags &= ~block_header_free_bit;
}
static int block_is_prev_free(const block_header_t *block)
{
return tlsf_cast(int, block->size &block_header_prev_free_bit);
return tlsf_cast(int, block->_flags & block_header_prev_free_bit);
}
static void block_set_prev_free(block_header_t *block)
{
block->size |= block_header_prev_free_bit;
block->_flags |= block_header_prev_free_bit;
}
static void block_set_prev_used(block_header_t *block)
{
block->size &= ~block_header_prev_free_bit;
block->_flags &= ~block_header_prev_free_bit;
}
static block_header_t *block_from_ptr(const void *ptr)
@ -365,7 +370,7 @@ static block_header_t *search_suitable_block(control_t *control,
*/
unsigned int sl_map = control->sl_bitmap[fl] & (((unsigned int)~0) << sl);
if (!sl_map) {
/* No block exists. Search in the next largest first-level list. */
/* No block exists. Search in the next first-level list. */
const unsigned int fl_map =
control->fl_bitmap & (((unsigned int)~0) << (fl + 1));
if (!fl_map) {
@ -475,6 +480,8 @@ static block_header_t *block_split(block_header_t *block, size_t size)
tlsf_assert(block_size(block) ==
remain_size + size + block_header_overhead);
// Clear the block header word to 0 but stuff in a valid widetag.
*(1 + (uintptr_t*)remaining) = FILLER_WIDETAG;
block_set_size(remaining, remain_size);
tlsf_assert(block_size(remaining) >= block_size_min &&
"block split with invalid size");
@ -490,7 +497,7 @@ static block_header_t *block_absorb(block_header_t *prev, block_header_t *block)
{
tlsf_assert(!block_is_last(prev) && "previous block can't be last");
/* Note: Leaves flags untouched. */
prev->size += block_size(block) + block_header_overhead;
prev->_nwords += block->_nwords;
block_link_next(prev);
return prev;
}
@ -602,8 +609,11 @@ static block_header_t *block_locate_free(control_t *control, size_t size)
remove_free_block(control, block, fl, sl);
}
if (unlikely(block && !block->size))
block = NULL;
// Not sure what this is trying to guard against. If there is a block,
// it was just asserted that block->size equals or exceeds 'size',
// and block can be non-NULL only if size was nonzero.
// if (unlikely(block && !block->size)
// block = NULL;
return block;
}
@ -815,7 +825,10 @@ pool_t tlsf_add_pool(tlsf_t tlsf, void *mem, size_t bytes)
block_header_t *next;
const size_t pool_overhead = tlsf_pool_overhead();
const size_t pool_bytes = align_down(bytes - pool_overhead, ALIGN_SIZE);
// subtract another word so that the end sentinel consumes 2 words
// (including its header)
const size_t pool_bytes = align_down(bytes - pool_overhead, ALIGN_SIZE)
- N_WORD_BYTES;
if (((ptrdiff_t)mem % ALIGN_SIZE) != 0) {
printf("tlsf_add_pool: Memory must be aligned by %u bytes.\n",
@ -836,6 +849,7 @@ pool_t tlsf_add_pool(tlsf_t tlsf, void *mem, size_t bytes)
* it will never be used.
*/
block = offset_to_block(mem, 0);
block->widetag = FILLER_WIDETAG;
block_set_size(block, pool_bytes);
block_set_free(block);
block_set_prev_used(block);
@ -843,7 +857,8 @@ pool_t tlsf_add_pool(tlsf_t tlsf, void *mem, size_t bytes)
/* Split the block to create a zero-size sentinel block. */
next = block_link_next(block);
block_set_size(next, 0);
next->widetag = FILLER_WIDETAG;
block_set_size(next, N_WORD_BYTES);
block_set_used(next);
block_set_prev_free(next);
@ -1043,3 +1058,47 @@ void *tlsf_realloc(tlsf_t tlsf, void *ptr, size_t size)
return p;
}
void tlsf_dump_freelists(tlsf_t tlsf, FILE *f)
{
control_t *control = tlsf_cast(control_t *, tlsf);
fprintf(f, "Freelists:\n");
int i,j;
for (i=0; i<FL_INDEX_COUNT; ++i)
for (j=0; j<SL_INDEX_COUNT; ++j) {
block_header_t *l = control->blocks[i][j];
if (l != &control->block_null) {
fprintf(f, "[%2d,%2d]=", i, j);
do {
fprintf(f, "%p (%x) ", l, l->_nwords);
l = l->next_free;
} while (l != &control->block_null);
putc('\n', f);
}
}
}
void tlsf_dump_pool(tlsf_t tlsf, pool_t pool, char *pathname)
{
FILE* f = fopen(pathname, "w");
if (tlsf) tlsf_dump_freelists(tlsf, f);
fprintf(f, " Free &header header nbytes &prev_header\n");
fprintf(f, " (incl hdr)\n");
fprintf(f, " ----- ---------- --------------- ----------- -------------\n");
block_header_t *block = offset_to_block(pool, 0);
while (block) {
unsigned long* header = (unsigned long*)block + 1, word = *header;
fprintf(f, " %s %12lx %7x:%08x %10lx",
block_is_free(block) ? "free":" ",
(long)header,
(int)(word>>32), (int)(word & 0xFFFFFFFF),
block_size(block)+N_WORD_BYTES);
if (block_is_prev_free(block))
fprintf(f, " %12lx", (long)block->prev_phys_block+N_WORD_BYTES);
putc('\n', f);
if (block_is_last(block)) break; // include the sentinel in the display
block = block_next(block);
}
fprintf(f, "-- end --\n");
fclose(f);
}