;;; -*- Mode:LISP; Package:QUANTUM-MAP; Base:10; Readtable:CL -*-

(export '(
	  allocate-quanta
	  cluster-region
	  deallocate-quanta
	  quantum-region
	  quantum-region-origin
	  valid-quantum?))

;;; Fields in the quantum map.

(defconstant %%quantum-map-dqin          (byte 12.  0.))	;Any device can map all quanta
(defconstant %%quantum-map-device        (byte  4. 12.))	;Up to 16 I/O or paging devices
(defconstant %%quantum-map-region-origin (byte 12. 16.))      ;Same as region number
(defconstant %%quantum-map-status        (byte  2. 28.))
(defconstant %%quantum-map-valid-bit     (byte  1. 28.))
(defconstant %%quantum-map-mapped-bit    (byte  1. 29.))
;(defconstant %%quantum-map
;unused (byte 2. 30.)

(defconstant $$quantum-invalid 0)
(defconstant $$quantum-valid   1)

(defconstant $$quantum-map-bit-unmapped 0)
(defconstant $$quantum-map-bit-mapped   1)

(defconstant $$quantum-empty     #b00)
(defconstant $$quantum-allocated #b01)
(defconstant $$quantum-error     #b10)
(defconstant $$quantum-mapped    #b11)

(vinc::defextractor      quantum-dqin        %%quantum-map-dqin)
(vinc::defextractor      quantum-device      %%quantum-map-device)
(vinc::defflag-extractor quantum-valid?      %%quantum-map-valid-bit $$quantum-valid)
(vinc::defextractor      quantum-status-bits %%quantum-map-status)
(vinc::defflag-extractor quantum-empty?      %%quantum-map-status $$quantum-empty)
(vinc::defextractor      region-origin       %%quantum-map-region-origin)

;;; The invariant for the quantum map is that all chunks for allocated quanta
;;; have the same origin, and that empty chunks have zero for the origin.

(defmacro locking-quantum-map (thunk)
  `(LET ((THUNK ,thunk))
     (IF (ZEROP (INCF GR::*QUANTUM-MAP-SEMAPHORE*))
	 (PROG1 (FUNCALL THUNK)
		(SETQ GR::*QUANTUM-MAP-SEMAPHORE* -1))
	 (TRAP::ILLOP "Quantum map being hacked recursively."))))

(defsubst read-quantum-map (quantum)
  (system-table-ref gr::*quantum-map* quantum))

(defsubst write-quantum-map (quantum value)
  (system-table-store gr::*quantum-map* quantum value))

(defmacro modify-quantum-map (quantum modifier)
  `(LET ((MODIFIER ,modifier)
	 (QUANTUM  ,quantum))
     (LOCKING-QUANTUM-MAP
       #'(LAMBDA ()
	   (WRITE-QUANTUM-MAP QUANTUM
	     (FUNCALL MODIFIER (READ-QUANTUM-MAP QUANTUM)))))))

(defmacro modify-quantum-map-prelocked (quantum modifier)
  `(LET ((MODIFIER ,modifier)
	 (QUANTUM  ,quantum))
     (WRITE-QUANTUM-MAP QUANTUM (FUNCALL MODIFIER (READ-QUANTUM-MAP QUANTUM)))))

;;; In the interest of making 26 bit pointer arithmetic possible
;;; on a machine that primarily supports 24 bit fixnums, we
;;; divide the quantum map up into chunks of 2^23 qs.  We never
;;; allocate across one of these boundaries.

;;; There are 2^12 quanta.  There are 2^3 zones.  We cannot
;;; group quanta across zone boundaries. (see zoning restrictions)

;;; Also, the PC on this machine can only address the top half of the
;;; virtual memory.  In order to hack this, there is an additional
;;; argument to the allocator that makes it look in instruction space.

;(defconstant *number-of-zones* (lisp::expt 2. (- (byte-size vinc:%%pointer)
;						 (- (byte-size vinc:%%fixnum-field)
;						    1))))

;(defconstant *quanta-per-zone* (lisp::floor vinc:*number-of-quanta* *number-of-zones*))
;(defconstant *first-instruction-zone* (lisp::floor *quanta-per-zone* 2.))

(defconstant *number-of-zones* 8.)
(defconstant *zone-within-quantum* (byte 4. 9.))
(defconstant *first-instruction-zone* 4.)

(defsubst zone-origin-in-quanta (zone)
  (hw:dpb zone *zone-within-quantum* 0))

(defun locate-contiguous-quanta (how-many for-instruction-space?)
  ;; Does a first fit search for contiguous quanta in the
  ;; map.
  ;; See Knuth's Fundamental Algorithms Vol I pg 437. before you go
  ;; changing this to best fit.
  (labels ((scan-zones (zone)
	     (if (= zone *number-of-zones*)
		 (trap::illop "Could not allocate requested contiguous quanta.")
		 (let ((origin (find-fit-in-zone zone)))
		   (if (null origin)
		       (scan-zones (1+ zone))
		       origin))))

	   (find-fit-in-zone (zone)
	     (let ((origin (zone-origin-in-quanta zone))
		   (limit  (zone-origin-in-quanta (1+ zone))))
	       (find-fit origin limit)))

	   (find-fit (scan limit)
	     (if (= scan limit)
		 '()
		 (if (quantum-empty? (read-quantum-map scan))
		     (let ((size (chunk-acceptable scan limit)))
		       (if (= size how-many)
			   scan
			   (find-fit (+ scan size) limit)))
		     (find-fit (1+ scan) limit))))

	   (chunk-acceptable (scan limit)
	     (scan-chunk scan limit 0))

	   (scan-chunk (scan limit size)
	     (cond ((= size how-many) size)
		   ((= scan limit)    size)
		   ((quantum-empty? (read-quantum-map scan))
		    (scan-chunk (1+ scan) limit (1+ size)))
		   (t size))))

    (scan-zones (if for-instruction-space?
		    *first-instruction-zone*
		    0))))

;(defun find-contiguous-quanta (how-many)
;  (let ((best-origin)
;	(best-size))
;    (do ((scan 0 (1+ scan)))
;	((>= scan vinc:*number-of-quanta*)
;	 (if (null best-size)
;	     (values best-origin best-size)))
;      (let ((quantum-map-entry (read-quantum-map scan)))
;	(when (quantum-empty? quantum-map-entry)
;	  ;; Found a free chunk, what is the size?
;	  (block find-size
;	    (do ((scan-ahead scan (1+ scan-ahead))
;		 (size       0    (1+ size)))
;		(())
;	      (let ((quantum-map-entry (read-quantum-map scan-ahead)))
;		(when (or (not (quantum-empty? quantum-map-entry))
;			  (zerop (mod scan-ahead *quanta-per-zone*)))
;		  (when (and (>= size how-many)
;			     (or (null best-size)
;				 (< size best-size)))
;		    (setq best-origin scan)
;		    (setq best-size   size))
;		  (setq scan scan-ahead)
;		  (return-from find-size))))))))))

(defun allocate-quanta (how-many instruction-space? volatility)
  (locking-quantum-map
    #'(lambda ()
	(let ((origin (locate-contiguous-quanta how-many instruction-space?)))
;	  (trap::illop "Found quanta.")
	  (labels ((initialize-quanta-volatility (quantum count)
		     (if (zerop count)
			 nil
			 (progn (gc-ram:initialize-quantum quantum volatility)
				(modify-quantum-map-prelocked quantum
						    #'(lambda (old)
							(vinc::dpb-multiple-unboxed
							  $$quantum-allocated %%quantum-map-status
							  origin              %%quantum-map-region-origin
							  old)))
				(initialize-quanta-volatility (1+ quantum) (1- count))))))
	    (initialize-quanta-volatility origin how-many)
	    origin)))))

(defun deallocate-quanta (origin)
  (let ((bits (read-quantum-map origin)))
    (when (not (= origin (region-origin bits)))
      (trap::illop "Invalid deallocation:  Didn't deallocate from start."))
    (when (not (quantum-valid? bits))
      (trap::illop "Invalid deallocation:  Chunk is not active.")))
  (labels ((deallocate-quantum (n)
	     (if (= n vinc:*number-of-quanta*)
		 '()
		 (let ((bits (read-quantum-map n)))
		   (when (= origin (region-origin bits))
		     (dispatch %%quantum-map-status bits
		       ($$quantum-empty     (trap::illop "Quantum map inconsistant."))
		       ($$quantum-allocated)
		       ($$quantum-error     (trap::illop "Quantum map inconsistant."))
		       ($$quantum-mapped    (trap::illop "Deallocate mapped quantum!")))
		     ;	    (paging-devices:deallocate-quantum (sim-debug::get-quantum-device bits)
		     ;				(quantum-dqin bits))))
		     (modify-quantum-map n
					 #'(lambda (old)
					     (hw:dpb $$quantum-empty %%quantum-map-status old)))
		     (deallocate-quantum (1+ n)))))))
    (deallocate-quantum origin)))

(defun quantum-region-origin (quantum)
  (region-origin (read-quantum-map quantum)))

(defun valid-quantum? (quantum)
  (quantum-valid? (read-quantum-map quantum)))

(defun quantum-region (quantum)
  (when (not (valid-quantum? quantum))
    (li:error "Quantum not in any region." quantum))
  (quantum-region-origin quantum))

(defun cluster-region (cluster)
  (quantum-region (cluster-quantum cluster)))
