; Allocation
;  *hp* is the heap pointer and *limit* is the limit beyond which no
;  storage should be allocated.  Both of these are addresses (not
;  descriptors).

; these two are for export in heap-extra for static linker support in scsh
(define (newspace-begin) *newspace-begin*)
(define (heap-pointer) *hp*)

(define check-preallocation? #f)

(define *hp* 0)
(define *limit* 0)

; These are all in address units
(define *newspace-begin* (unassigned))
(define *newspace-end*   (unassigned))
(define *oldspace-begin* (unassigned))
(define *oldspace-end*   (unassigned))

(define (initialize-heap start size)
  (let ((semisize (cells->a-units (quotient size 2))))
    (set! *newspace-begin* start)
    (set! *newspace-end* (addr+ *newspace-begin* semisize))
    (set! *oldspace-begin* *newspace-end*)
    (set! *oldspace-end* (addr+ *oldspace-begin* semisize))
    (set! *hp* *newspace-begin*)
    (set! *limit* *newspace-end*)))

(define (available? cells)
  (addr< (addr+ *hp* (cells->a-units cells)) *limit*))

(define (available)
  (a-units->cells (addr- *limit* *hp*)))

(define (heap-size)
  (- *newspace-end* *newspace-begin*))

(define *heap-key* 0)
(define universal-key 0)
(define *okayed-space* 0)

(define (preallocate-space cells)
  (cond (check-preallocation?
	 (assert (available? cells))
	 (set! *heap-key* (+ *heap-key* 1))
	 (set! *okayed-space* cells)
	 *heap-key*)
	(else
	 universal-key)))

(define (make-stob type len key)	;len is in bytes
  (if check-preallocation?
      (let ((cells (+ (bytes->cells len) 1)))
	(assert (available? cells))
	(cond ((not (= key universal-key))
	       (if (not (and (= key *heap-key*)
			     (>= *okayed-space* cells)))
		   (error "invalid heap key" key cells))
	       (set! *okayed-space* (- *okayed-space* cells))))))
  (store! *hp* (make-header type len))
  (set! *hp* (addr1+ *hp*))
  (let ((new (address->stob-descriptor *hp*)))
    (set! *hp* (addr+ *hp* (bytes->a-units len)))
    (if (> len 0)	 ; for B-VECTORs that don't want to use all of the
        (store! (addr+ *hp* (cells->bytes -1)) 0))       ; last descriptor
    new))


(define *pure-areas*   (unassigned))
(define *impure-areas* (unassigned))
(define *pure-sizes*   (unassigned))
(define *impure-sizes* (unassigned))
(define *pure-area-count*   0)
(define *impure-area-count* 0)

(define (register-static-areas pure-count pure-areas pure-sizes
			       impure-count impure-areas impure-sizes)
  (set! *pure-area-count* pure-count)
  (set! *pure-areas* pure-areas)
  (set! *pure-sizes* pure-sizes)
  (set! *impure-area-count* impure-count)
  (set! *impure-areas* impure-areas)
  (set! *impure-sizes* impure-sizes))

(define (walk-areas proc areas sizes count)
  (let loop ((i 0))
    (cond ((>= i count)
	   #t)
	  ((proc (vector-ref areas i)
		 (+ (vector-ref areas i)
		    (vector-ref sizes i)))
	   (loop (+ i 1)))
	  (else
	   #f))))

(define (walk-pure-areas proc)
  (walk-areas proc *pure-areas* *pure-sizes* *pure-area-count*))

(define (walk-impure-areas proc)
  (walk-areas proc *impure-areas* *impure-sizes* *impure-area-count*))



; Used to find end of an object
(define (header-a-units h)
  (bytes->a-units (header-length-in-bytes h)))

(define (walk-over-type-in-area type proc)
  (lambda (start end)
    (let loop ((addr start))
      (cond ((addr< addr end)
	     (let ((d (fetch addr)))
	       (cond ((not (header? d))
		      (write-string "heap is in an inconsistent state."
				    (current-output-port))
		      #f)
		     ((or (not (= type (header-type d)))
			  (proc (address->stob-descriptor (addr1+ addr))))
		      (loop (addr1+ (addr+ addr (header-a-units d)))))
		     (else
		      #f))))
	    (else
	     #t)))))

(define (walk-over-symbols proc)
  (let ((proc (walk-over-type-in-area (enum stob symbol) proc))
	(start-hp *hp*))
    (cond ((and (proc *newspace-begin* *hp*)
		(walk-pure-areas proc))
	   #t)
	  (else
	   (set! *hp* start-hp) ; out of space, so undo and give up
	   #f))))

(define (find-all-xs type)
  (let ((proc (walk-over-type-in-area type maybe-push-obj))
	(start-hp *hp*))
    (store-next! 0)                              ; reserve space for header
    (cond ((and (proc *newspace-begin* start-hp)
		(walk-impure-areas proc)
		(walk-pure-areas proc))
	   (let ((size (addr- *hp* (addr1+ start-hp))))
	     (store! start-hp (make-header (enum stob vector) size) )
	     (address->stob-descriptor (addr1+ start-hp))))
	  (else
	   (set! *hp* start-hp) ; out of space, so undo and give up
	   false))))

(define (maybe-push-obj thing)
  (cond ((available? (cells->a-units 1))
	 (store-next! thing)
	 #t)
	(else #f)))


;;;; Write-image and read-image

(define level 15)

(define (image-writing-okay?)
  (and (= 0 *pure-area-count*)
       (= 0 *impure-area-count*)))

(define (write-image port restart-proc)
  (write-string "This is a Scheme48 heap image file." port)
  (newline port)
  (write-page port)
  (newline port)
  (write-number level            port)
  (write-number bytes-per-cell   port)
  (write-number (a-units->cells *newspace-begin*) port)
  (write-number (a-units->cells *hp*)             port)
  (write-number restart-proc    port)
  (write-page port)
  (store! *hp* 1)  ; used to detect endianess of image
  (write-bytes *hp* (- (addr1+ *hp*) *hp*) port)
  (write-bytes *newspace-begin* (- *hp* *newspace-begin*) port)
  (- *hp* *newspace-begin*))

; Make sure the image file is okay and return the size of the heap it
; contains.

(define (check-image-header filename)
  (call-with-input-file filename
    (lambda (port)
      (let ((lose (lambda (message)
		    (let ((out (current-output-port)))
		      (write-string message out)
		      (newline out)
		      -1))))
	(cond ((null-port? port)
	       (lose "Can't open heap image file"))
	      (else
	       (read-page port) ; read past any user cruft at the beginning of the file
	       (let* ((old-level          (read-number port))
		      (old-bytes-per-cell (read-number port))
		      (old-begin (cells->a-units (read-number port)))
		      (old-hp    (cells->a-units (read-number port))))
		 (cond ((not (= old-level level))
			(lose "format of image is incompatible with this version of system"))
		       ((not (= old-bytes-per-cell bytes-per-cell))
			(lose "incompatible bytes-per-cell in image"))
		       (else
			(- old-hp old-begin))))))))))

(define (read-image filename startup-space)
  (call-with-input-file filename
    (lambda (port)
      (if (null-port? port)
	  (error "Can't open heap image file"))
      (read-page port) ; read past any user cruft at the beginning of the file
      (let* ((old-level          (read-number port))
             (old-bytes-per-cell (read-number port))
             (old-begin (cells->a-units (read-number port)))
             (old-hp    (cells->a-units (read-number port)))
             (startup-proc       (read-number port)))
        (read-page port)
        (if (not (= old-level level))
            (error "format of image is incompatible with this version of system"
                   old-level level))
        (if (not (= old-bytes-per-cell bytes-per-cell))
            (error "incompatible bytes-per-cell"
                   old-bytes-per-cell bytes-per-cell))
        (let* ((delta (- *hp* old-begin))
               (new-hp (+ old-hp delta))
               (new-limit *newspace-end*)
	       (start *hp*))
          (if (addr>= (+ startup-space new-hp) new-limit)
	      (error "heap not big enough to restore this image"
		     new-hp new-limit))
	  (let ((reverse? (check-image-byte-order port)))
	    (read-bytes *hp* (- old-hp old-begin) port)
	    (if reverse?
		(reverse-byte-order start new-hp))
	    (if (not (= delta 0))
		(relocate-image delta start new-hp))
	    (set! *hp* new-hp)
	    (adjust startup-proc delta)))))))

(define (check-image-byte-order port)
  (read-bytes *hp* (cells->a-units 1) port)
  (cond ((= (fetch *hp*) 1)
	 #f)
	(else
	 (reverse-descriptor-byte-order! *hp*)
	 (if (= (fetch *hp*) 1)
	     #t
	     (begin (error "unable to correct byte order" (fetch *hp*))
		    #f))))) ; to keep from confusing the type checker

; ABCD => DCBA

; memory intensive, but independent of Scheme's integer size

(define (reverse-descriptor-byte-order! addr)
  (let ((x (fetch-byte addr)))
    (store-byte! addr (fetch-byte (addr+ addr 3)))
    (store-byte! (addr+ addr 3) x))
  (let ((x (fetch-byte (addr+ addr 1))))
    (store-byte! (addr+ addr 1) (fetch-byte (addr+ addr 2)))
    (store-byte! (addr+ addr 2) x)))

(define (reverse-byte-order start end)
  (write-string "Correcting byte order of resumed image."
		 (current-output-port))
  (newline (current-output-port))
  (let loop ((ptr start))
    (reverse-descriptor-byte-order! ptr)
    (let ((value (fetch ptr)))
      (if (addr< ptr end)
	  (loop (if (b-vector-header? value)
		    (addr+ (addr1+ ptr) (header-a-units value))
		    (addr1+ ptr)))))))

(define (adjust descriptor delta)
  (if (stob? descriptor)
      (address->stob-descriptor (addr+ (address-after-header descriptor) delta))
      descriptor))

(define (relocate-image delta start end)
  (let loop ((ptr start))
    (if (addr< ptr end)
	(let ((d (adjust (fetch ptr) delta)))
	  (store! ptr d)
	  (loop (if (b-vector-header? d)
		    (addr+ (addr1+ ptr) (header-a-units d))
		    (addr1+ ptr)))))))

; The page character is used to mark the ends of the user and prelude sections
; of image files.

(define page-character (ascii->char 12))

(define (write-page port)
  (write-char page-character port))

(define (read-page port)
  (let loop ()
    (ps-read-char port
		  (lambda (ch)
		    (if (not (char=? ch page-character))
			(loop)))
		  (lambda ()
		    (error "end of file while looking for page break")))))

