;;; -*- Mode:LISP; Package:LISP-INTERNALS; Base:10; Readtable:CL -*-

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; OPERATIONS ON CONTROL PDLs

;;; SWAP IN AND WIRE		-not needed-
;;; The control PDL (or at least the part being dumped to) must be wired
;;; since the call hardware dumper isn't allowed to take page faults (or
;;; any call hardware operation).

;;; UNWIRE			-not needed-
;;; only the current control pdl need be wired.

;;; ASSURE SPACE TO DUMP IN
;;; For the current stack group, there must be enough remaining space in the
;;; control pdl (which must be wired) to hold what could potentially be the
;;; entire state of the call hardware.

;;; GROW
;;; control flow for the program might have a deep enough call stack
;;; that the previously allocated control pdl might not be big enough.
;;; this operation is similar to that of growing arrays.
;;; Wiring and unwiring must occur when a bigger control pdl is created.

;;; CONTEXT SWITCH
;;; when a context switch between stack groups occurs the entire call hardware
;;; state must be dumped into the control pdl associated with the outgoing
;;; stack group and restored from the control pdl state of the incomming stack group.
;;; These operations are similar to overflow and underflow handling.
;;; Wiring and unwiring occur at this point.

;;; HANDLE OVERFLOW
;;; when the program overflows the call hardware then its state must be dumped
;;; to the control pdl and restored later.  This dumping must occur on call boundaries.

;;; HANDLE UNDERFLOW
;;; when the program does more returns than the call hardware remembers and there
;;; was previously an overflow, then the state that was dumped to service the overflow
;;; must now be restored.  If there was no previously dumped state then the machine
;;; will have returned to the event horizon.

;;; SCAVENGE
;;; The control pdl must be scavenged in a manner similar to the way the registers
;;; are scavenged.  The scavenger must touch those pieces of the dumped call hardware state
;;; which are boxed, and only those which are boxed.

;;; TOOLS FOR THE DEBUGGER TO LOOK AT THEM
;;; It is probably the case that the debugger will run in a different stack group than
;;; the program being debugged.  In this case, the entire state of the call hardware
;;; for the debugged stack group will be represented in a control pdl.


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; at the base of the call hardware stack a CALL has just occurred.
;;; That call will have come from the call stack underflow handler.

;;; The call stack underflow handler (catcher) uses register set #xff for O and A.
;;; At the base of the call stack will be an entry which will return #xff to O and A,
;;; and the RPC will be a routine which preserves the return values, scrolls the call
;;; stack, and returns with the preserved return values.
;;; This is why the call hardware may only be scrolled so that the base is at a call boundary.

;;; These global registers are used by the control pdl code:
;;;  GR:*CONTROL-PDL*		contains the currently active control pdl.
;;;  GR:*CONTROL-PDL-POINTER*	virtual address into the current control pdl where the next
;;;				record will be written as an unboxed locative.
;;;  GR:*CONTROL-PDL-LIMIT*	if GR:*CONTROL-PDL-POINTER* reaches this then the control pdl is full. 
;;;  GR:*CH-BASE-CSP*		the value of the call stack pointer above which the call stack is valid.
;;;  GR:*CH-TEMP-0*		temporaries used by the call hardware dumper and restorer while
;;;  GR:*CH-TEMP-1*		they can't use locals
;;;  GR:*CH-TEMP-2*
;;;  GR:*CH-TEMP-3*
;;;  GR:*CH-TEMP-4*
;;;  GR:*CH-TEMP-5*
;;;  GR:*CH-TEMP-6*
;;;  GR:*CH-CONTROL-PDL-INDEX*	used by the restorer.  (could just be another temporary)
;;;  GR:*NEXT-CONTROL-PDL*	control pdl to context-switch to


;;; The control pdl object.  Control pdls are arrays of type ART-CONTROL-PDL.

;;; At the base of each control pdl are several words used for bookkeeping.  Immediately after
;;; these words the call hardware dump starts.  These words include
;;;    - pointer to the stack group it belongs to
;;;    - the allocation pointer where dumping continues

;;; The limit after which the control pdl structure must be grown to accomodate more call records
;;; can be calculated from the control pdl's size.

;;; Each frame of the control pdl consists of CONTROL-PDL-FRAME-SIZE words.
;;; The first word contains:
;;;    - the type code: OPEN, OPEN-CALL, TOPEN (2 bits), PROTECTED-OPEN-CALL,
;;;    - the return-destination (7 bits),
;;;    - the global return destination (4 bits) and
;;;    - 16 box bits for the saved registers
;;; The second word contains the typed RPC
;;; The third through eighteenth words contain the saved registers.
(defconstant %%cpdl0-type-code		(byte 2 0))
(defconstant %%cpdl0-rdest		(byte 7 2))
(defconstant %%cpdl0-global-frame	(byte 4 9))
(defconstant %%cpdl0-box-bits		(byte 16 16))	;register zero's box bit is LSB of this field
(defconstant $$cpdl0-type-open			0)	;for type code field
(defconstant $$cpdl0-type-open-call		1)
(defconstant $$cpdl0-type-topen			2)
(defconstant $$cpdl0-type-protected-open-call	3)

(defconstant control-pdl-frame-size 18)
(defconstant control-pdl-frame-offset-to-registers 2)

;;; The control pdl could be full of OPEN-CALL TOPEN frames
(defconstant max-call-hardware-dump (* 2 control-pdl-frame-size 256)
  "The largest possible size that a call hardware dump can be")

(defvar control-pdl-area nil
  "This is the area in which control pdls live")

(defun make-control-pdl-area ()
  (when (or (not (boundp 'control-pdl-area))
	    (null control-pdl-area))
    (setq control-pdl-area
	  (area-data:make-area
	    7 (vinc:dpb-multiple-boxed
		(ceiling max-call-hardware-dump vinc:*qs-in-cluster*) 	region-bits:%%region-bits-swapin-quantum
		region-bits:$$scavenge-enabled				region-bits:%%region-bits-scavenge-bit
		region-bits:$$region-read-write				region-bits:%%region-bits-read-only
		region-bits:$$region-space-structure			region-bits:%%region-bits-space-type
		region-bits:$$region-new-space				region-bits:%%region-bits-new-space
		;;; what should this really be:
		region-bits:$$region-fixed				region-bits:%%region-bits-flippable
		region-bits:$$region-internal-memory			region-bits:%%region-bits-external-bus
		0) 10))))
	 
(defconstant control-pdl-allocation-quantum
	     (* 2 max-call-hardware-dump)
  "amount by which a control pdl is grown when it fills up")	;at least enough for one full call hardware dump

;;; The zeroth slot of the control pdl contains a pointer 
;;; back to the stack group to which the control pdl belongs.
(defmacro CONTROL-PDL-STACK-GROUP (control-pdl)
  "Return the stack group associated with the CONTROL-PDL"
  `(array:%vm-read (hw:24+ 1 ,control-pdl)))

(defmacro SET-CONTROL-PDL-STACK-GROUP (control-pdl stack-group)
  "Set the stack group associated with CONTROL-PDL to STACK-GROUP"
  `(array:%vm-write (hw:24+ 1 ,control-pdl) ,stack-group))

;;; The first slot of the control pdl contains the saved value of the control pdl pointer
;;; when the control pdl is not the current one.
(defmacro CONTROL-PDL-POINTER (control-pdl)
  "Return the saved value of CONTROL-PDL's top of stack pointer"
  `(array:%vm-read (hw:24+ 2 ,control-pdl)))

(defmacro SET-CONTROL-PDL-POINTER (control-pdl new-pointer)
  "Changes the saved value of CONTROL-PDL's top of stack pointer to NEW-POINTER"
  `(array:%vm-write (hw:24+ 2 ,control-pdl) ,new-pointer))

(defconstant control-pdl-base 3
  "Add to a control-pdl objects pointer to find the base for call hardware dumps")

(defun make-control-pdl (stack-group &optional (total-size control-pdl-allocation-quantum))
  (setq total-size (* (ceiling (max total-size control-pdl-allocation-quantum)
			       vinc:*qs-in-cluster*)
		      vinc:*qs-in-cluster*))	;must fall on cluster boundary (for ease in wiring).
  (let ((control-pdl (cons:allocate-structure-in-area
		       control-pdl-base					;3 words including header
		       (- total-size 3)
		       vinc:$$dtp-array
		       (vinc:dpb-multiple-boxed
			 (1- total-size)		array::%%bounds	;don't count header word
			 array:art-control-pdl		array::%%sv-art
			 vinc:$$dtp-array-header-single	vinc:%%data-type
			 0)
		       control-pdl-area)))
    ;;; touch each page
    (do ((i 3 (1+ i)))
	((>= i (1- total-size)))
      (array:%vm-write32 control-pdl i (hw:unboxed-constant 0)))
    (set-control-pdl-pointer control-pdl control-pdl-base)
    (set-control-pdl-stack-group control-pdl stack-group)
    control-pdl))

(defun control-pdl-p (object)
  (and (array:arrayp object)
       (= array:art-control-pdl (hw:ldb object array::%%sv-art 0))))

(defsubst control-pdl-empty-p (control-pdl)
  (progn (when (< (control-pdl-pointer control-pdl) control-pdl-base)
	   (error "control-pdl-pointer below control-pdl-base"))
	 (<= (control-pdl-pointer control-pdl) control-pdl-base)))

(defsubst control-pdl-limit (control-pdl)
  "if control-pdl-pointer reaches here we are out of room"
  (hw:ldb (array:%vm-read32 control-pdl 0) array::%%bounds 0))

(defun control-pdl-assure-room (control-pdl)
  (if (>= (+ (control-pdl-pointer control-pdl)
	     max-call-hardware-dump)		;max size of call hardware
	  (control-pdl-limit control-pdl))
      (grow-control-pdl control-pdl)
    control-pdl))

(defun grow-control-pdl (control-pdl)
  (let* ((stack-group (control-pdl-stack-group control-pdl))
	 (new-control-pdl (make-control-pdl stack-group
					    (+ (control-pdl-limit control-pdl)
					       control-pdl-allocation-quantum))))
    ;;; this will copy stack-group, pointer and the dumped call hardware state
    (do ((offset 1 (1+ offset))
	 (end (control-pdl-limit control-pdl)))
	((>= offset end))
      (array:%vm-write32 new-control-pdl offset
			 (array:%vm-read32 control-pdl offset)))
    ;;; change the control pdl in the stack group
    (setf (sg-control-pdl stack-group) new-control-pdl)
    (set-control-pdl-stack-group control-pdl nil)	;disassociate the old control pdl from any stack group
    new-control-pdl))


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; The state of the current control pdl is stored in global registers rather than
;;; in the object itself.  These are used to maintain consistency and for context switching.

(defsubst save-control-pdl-state ()
  (set-control-pdl-pointer gr:*control-pdl*
			   (hw:ldb (hw:24- gr:*control-pdl-pointer* gr:*control-pdl*)
				   (byte 24 0) 0)))

(defsubst load-control-pdl-state ()
  (macrolet ((index-to-address (index)
	       `(cons:make-pointer vinc:$$dtp-unboxed-locative
				   (hw:24+ gr:*control-pdl* ,index))))
    (setq gr:*control-pdl-limit*   (index-to-address (control-pdl-limit gr:*control-pdl*))
	  gr:*control-pdl-pointer* (index-to-address (control-pdl-pointer gr:*control-pdl*)))))

(defun select-control-pdl (control-pdl)
  "Set up the global registers associated with the call hardware dump/restore code
to use CONTROL-PDL.  The previous values are stored in the outgoing control pdl"
  ;;; make sure it is a control pdl
  (unless (control-pdl-p control-pdl)
    (trap:illop "~s is not a control pdl" control-pdl))
  (save-control-pdl-state)
  (setq gr:*control-pdl* control-pdl)
  (load-control-pdl-state))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; hooks for the debugger
;;; read and modify registers in a frame

;(defun control-pdl-depth (control-pdl)
;  "Index of topmost frame of control pdl"
;  (floor (- (control-pdl-pointer control-pdl) control-pdl-base)
;	 control-pdl-frame-size))

;(defun control-pdl-frame-info (control-pdl frame-number)
;;  (declare (values type rpc rdest global-frame))
;  (let ((frame-index (+ control-pdl-base (* control-pdl-frame-size frame-number)))
;	word0 word1)
;    (when (>= frame-index (control-pdl-pointer control-pdl))
;      (li:error "frame not in control pdl"))
;    (setq word0 (array:%vm-read32 control-pdl frame-index))
;    (setq word1 (array:%vm-read32 control-pdl (1+ frame-index)))
;    (values (hw:ldb word0 %%cpdl0-type-code 0)		;type
;	    word1					;RPC
;	    (hw:ldb word0 %%cpdl0-rdest	0)		;RDEST
;	    (hw:ldb word0 %%cpdl0-global-frame 0))))	;global frame

;(defun control-pdl-frame-examine-register (control-pdl frame-number register-number)
;  (let ((frame-index (+ control-pdl-base (* control-pdl-frame-size frame-number)))
;	box-bits datum)
;    (when (>= frame-index (control-pdl-pointer control-pdl))
;      (li:error "frame not in control pdl"))
;    (setq box-bits (hw:ldb (array:%vm-read32 control-pdl frame-index) %%cpdl0-box-bits 0))
;    (setq datum (array:%vm-read32 control-pdl (+ control-pdl-frame-offset-to-registers
;						 register-number frame-index)))
;    (if (hw::32logbitp register-number box-bits)
;	(hw:dpb-boxed datum (byte 32 32) (hw:unboxed-constant 0))	;make boxed
;      datum)))								;unboxed

;(defun control-pdl-frame-modify-register (control-pdl frame-number register-number new-value boxed-p)
;  (let ((frame-index (+ control-pdl-base (* control-pdl-frame-size frame-number)))
;	word0)
;    (when (>= frame-index (control-pdl-pointer control-pdl))
;      (li:error "frame not in control pdl"))
;    (setq word0 (array:%vm-read32 control-pdl frame-index))
;    (flet ((change-datum ()
;	     (array:%vm-write32 control-pdl (+ control-pdl-frame-offset-to-registers
;					 register-number frame-index) new-value))
;	   (change-box-bit ()
;	     (array:%vm-write32 control-pdl frame-index
;			  (hw:dpb (hw:dpb (if boxed-p 1 0)
;					  (byte 1 register-number)
;					  (hw:ldb word0 %%cpdl0-box-bits 0))
;				  %%cpdl0-box-bits
;				  word0))))
;      (if boxed-p				;try and do this safely
;	  (progn (change-datum)
;		 (change-box-bit))
;	(progn (change-box-bit)
;	       (change-datum))) )))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;(defun scavenge-control-pdl (control-pdl)
;  (macrolet ((scavenge-word (word)
;	       `(hw:vma-start-read ,word)))
;    (SCAVENGE-WORD (control-pdl-stack-group control-pdl))
;    (do ((frame-offset control-pdl-base (+ frame-offset control-pdl-frame-size))
;	 (end (control-pdl-pointer control-pdl))
;	 frame-box-bits)
;	((>= frame-offset end))
;      (setq frame-box-bits (hw:ldb (array:%vm-read32 control-pdl frame-offset)
;				   %%cpdl0-box-bits 0))
;      ;;; scavenge the pc also
;      (dotimes (register 16)
;	(when (hw:32logbitb register frame-box-bits)
;	  (SCAVENGE-WORD (array:%vm-read32 control-pdl			;do we need to make sure it is boxed
;					   (+ frame-offset		;in case the transporter refuses to move it?
;					      control-pdl-frame-offset-to-registers
;					      register)))) )) ))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;; we should maybe use a vanilla global variable for the interlock
(defmacro DUMPING-OR-RESTORING-CALL-HARDWARE (doing-it)
  (let ((interlock 'gr:*ch-dumper-return-pc*))
    (if doing-it
	`(progn
	   (unless (null ,interlock)
	     (trap:illop "call hardware dump/restore entered recursively"))
	   (setq ,interlock t))
      `(setq ,interlock nil))))

(defmacro set-rpc-rdest (rpc rdest global-frame)
  `(let ((rpc-rdest (hw:dpb ,rdest hw:%%ch-rpcd-return-dest ,rpc)))
     (hw:trap-off)
     ;;;once Kent fixes the hardware we won't need to do the hack with SPARE-17 any more.
     (hw:write-processor-control
       (vinc:dpb-multiple-unboxed
	 ,global-frame	hw:%%processor-control-misc
	 1		hw:%%processor-control-spare-17
	 (hw:read-processor-control)))
     (hw:nop) (hw:nop) (hw:nop) (hw:nop)
     (hw:write-return-pc-return-dest rpc-rdest)
     (hw:nop)
     (hw:ch-open-call)
     (hw:write-processor-control
       (hw:dpb-unboxed 0 hw:%%processor-control-spare-17
		       (hw:read-processor-control)))
     (hw:nop) (hw:nop)
     (hw:write-memory-control
       (hw:dpb-unboxed hw:$$trap-enable hw:%%memory-control-master-trap-enable
		       (hw:read-memory-control)))))

(defmacro FORGE-CATCHER-FRAME ()
  `(set-rpc-rdest (k2:%compiled-function-code
		    (symbol-function
		      'call-hardware-underflow-catcher))
		  (vinc::dpb-multiple-unboxed
		    hw:$$i-reg-base-active	hw:%%i-reg-base	; return destination A0
		    0				hw:%%i-reg-offset
		    (hw:unboxed-constant 0))
		  0))

;;; This is what lives at the bottom of the call hardware stack.  It is never called.  Things return
;;; to it at PC offset 0.  The macro FORGE-CATCHER-FRAME knows how to install it.  Be sure the call hardware is
;;; empty when you install it otherwise the frames below it will be lost.
(defun CALL-HARDWARE-UNDERFLOW-CATCHER (result)
  ;;; traps are on.  This is guaranteed by some hack somewhere.
  ;;; Forge an open call frame onto the control pdl.
  ;;; It should look like this:
  ;;;    RPC:		either CALL-HARDWARE-UNDERFLOW-RETURN-MULTIPLE-VALUES or CALL-HARDWARE-UNDERFLOW-RETURN-1-VALUE.
  ;;;    RDEST:		ignore, return-frame-0.
  ;;;    type:		unprotected open-call.
  ;;;    boxed bits:	register zero is same as result, all others unboxed.
  ;;;    global frame:	doesn't matter.
  ;;;    registers:	saved A0 has value of RESULT, all others are unboxed zero.
  (if (control-pdl-empty-p gr:*control-pdl*)
      (trap:illop "Control PDL is empty")	; if the control pdl is empty we should loose in some appropriate way
    (let ((return-function (if (hw:return-code-mv-p)
			       'call-hardware-underflow-return-multiple-values 
			     'call-hardware-underflow-return-1-value))
	  (word-1 (vinc:dpb-multiple-unboxed
		    (hw:accumulate-box-bits (hw:unboxed-constant 0) result)	%%cpdl0-box-bits
		    $$cpdl0-type-open-call					%%cpdl0-type-code
		    (vinc:dpb-multiple-unboxed
		      hw:$$i-reg-base-return	hw:%%i-reg-base
		      0				hw:%%i-reg-offset
		      (hw:unboxed-constant 0))					%%cpdl0-rdest
		    0								%%cpdl0-global-frame
		    (hw:unboxed-constant 0))))
      (macrolet ((control-pdl-write-word (word)
		   `(progn (hw:write-md-unboxed ,word)
			   (hw:vma-start-write-no-gc-trap-unboxed gr:*control-pdl-pointer*)
			   (setq gr:*control-pdl-pointer* (hw:24+ 1 gr:*control-pdl-pointer*)))))
	(control-pdl-write-word word-1)
	(control-pdl-write-word (k2:%compiled-function-code (symbol-function return-function)))
	(control-pdl-write-word result) 		 (control-pdl-write-word (hw:unboxed-constant 0))
	(control-pdl-write-word (hw:unboxed-constant 0)) (control-pdl-write-word (hw:unboxed-constant 0))
	(control-pdl-write-word (hw:unboxed-constant 0)) (control-pdl-write-word (hw:unboxed-constant 0))
	(control-pdl-write-word (hw:unboxed-constant 0)) (control-pdl-write-word (hw:unboxed-constant 0))
	(control-pdl-write-word (hw:unboxed-constant 0)) (control-pdl-write-word (hw:unboxed-constant 0))
	(control-pdl-write-word (hw:unboxed-constant 0)) (control-pdl-write-word (hw:unboxed-constant 0))
	(control-pdl-write-word (hw:unboxed-constant 0)) (control-pdl-write-word (hw:unboxed-constant 0))
	(control-pdl-write-word (hw:unboxed-constant 0)) (control-pdl-write-word (hw:unboxed-constant 0)))
      (restore-call-headware))))

(defun call-hardware-underflow-return-1-value (value)
  (hw:return value))

(defun call-hardware-underflow-return-multiple-values (value)
  (hw:return-mv value))

(defun where-to-restore-from-control-pdl (control-pdl)
  ;;; find a place to start restoring the call hardware from.  Must be an unprotected open-call frame
  (do* ((control-pdl-index (- (control-pdl-pointer control-pdl) control-pdl-frame-size)
			   (- control-pdl-index control-pdl-frame-size))
	(number-of-frames 0 (1+ number-of-frames))
	open-call-index
	(open-call-index-number-of-frames 0))
       (nil)
    (cond ((= control-pdl-index control-pdl-base)	;empty control pdl?
	   (return control-pdl-base))
	  ((>= open-call-index-number-of-frames (floor 256 3))	;one third of call headware size?
	   (return open-call-index))
	  ((< control-pdl-index control-pdl-base)
	   (trap:illop "phase error in control pdl"))
	  ((hw:field= (array::%vm-read32 gr:*control-pdl* control-pdl-index)
		      $$cpdl0-type-open-call
		      %%cpdl0-type-code)
	   (setq open-call-index control-pdl-index
		 open-call-index-number-of-frames number-of-frames)))))

;;; should get called with traps ON!
(defun restore-call-hardware (return-value)
  (dumping-or-restoring-call-hardware t)
  (macrolet ((saved-return-value ()	'gr:*ch-temp-0*)
	     (frame-first-word ()	'gr:*ch-temp-1*)
	     (next-rpc-rdest ()		'gr:*ch-temp-2*)
	     (global-frame ()		'gr:*ch-temp-3*)
	     (control-pdl-top ()	'gr:*ch-temp-4*)
	     (restore-register-prep ()
	       `(setf (frame-first-word)
		      (hw:ldb (frame-first-word) %%cpdl0-box-bits (hw:unboxed-constant 0))))
	     (restore-register (register)
	       `(progn
		  (if (hw:32logbitp 0 (frame-first-word))
		      (hw:vma-start-read-vma-unboxed-md-boxed gr:*ch-control-pdl-index* 0)
		    (hw:vma-start-read-no-transport-vma-unboxed-md-unboxed gr:*ch-control-pdl-index* 0))
		  (setq gr:*ch-control-pdl-index* (hw:24-1+ gr:*ch-control-pdl-index*))
		  (setf (frame-first-word) (hw:32logical-shift-down (frame-first-word) 1))
		  (setf (,register) (hw:read-md)))) )
    (setf (saved-return-value) return-value)
    (forge-catcher-frame)
    (save-control-pdl-state)
    (setq gr:*ch-control-pdl-index* (hw:24+ gr:*control-pdl*
					    (where-to-restore-from-control-pdl gr:*control-pdl*)))

    ;;; we should probably flush our frames so they will return to the heap

    (tagbody 	;;; WARNING: no locals are allowed.  Use of OPEN and ACTIVE frames is prohibited
	(setq gr:*ch-base-csp* (hw:ldb (hw:read-call-sp-hp)
				       hw:%%ch-csphp-call-stack-pointer 0))
	(setf (control-pdl-top) gr:*control-pdl-pointer*)
	(setq gr:*control-pdl-pointer* gr:*ch-control-pdl-index*)
     loop
	(when (hw:24= gr:*ch-control-pdl-index* (control-pdl-top))
	  (go end))
	(when (hw:24> gr:*ch-control-pdl-index* (control-pdl-top))
	  (trap:illop "frame alignment phase error in control pdl"))
	(hw:vma-start-read-no-transport-vma-unboxed-md-unboxed gr:*ch-control-pdl-index*)
	(setq gr:*ch-control-pdl-index* (hw:24-1+ gr:*ch-control-pdl-index*))			;point to second word
	(setf (frame-first-word) (hw:read-md))
	(dispatch %%cpdl0-type-code (frame-first-word)
		  (($$cpdl0-type-open-call $$cpdl0-type-protected-open-call)
		   (progn
		     (hw:trap-off)
		     ;;;once Kent fixes the hardware we won't need to do the hack with SPARE-17 any more.
		     (hw:write-processor-control
		       (vinc:dpb-multiple-unboxed
			 (global-frame)	hw:%%processor-control-misc	;global frame number
			 1		hw:%%processor-control-spare-17
			 (hw:read-processor-control)))	;read boxed rpc
		     (hw:nop) (hw:nop) (hw:nop) (hw:nop)
		     (hw:write-return-pc-return-dest (next-rpc-rdest))
		     (hw:nop)
		     (hw:ch-open-call)
		     (hw:write-processor-control
		       (hw:dpb-unboxed 0 hw:%%processor-control-spare-17
				       (hw:read-processor-control)))
		     ;(trap:trap-on)
		     (hw:nop) (hw:nop)
		     (hw:write-memory-control
		       (hw:dpb-unboxed hw:$$trap-enable hw:%%memory-control-master-trap-enable
				       (hw:read-memory-control))))
		    ;;; setup RPC, RDEST and global return destination for next time around:
		    (hw:vma-start-read-no-transport-vma-unboxed-md-unboxed
		      gr:*ch-control-pdl-index*)
		    (setq gr:*ch-control-pdl-index* (hw:24-1+ gr:*ch-control-pdl-index*))
		    (setf (next-rpc-rdest) (hw:ldb (frame-first-word) %%cpdl0-rdest (hw:unboxed-constant 0)))
		    (setf (next-rpc-rdest) (hw:dpb (next-rpc-rdest) hw:%%ch-rpcd-return-dest (hw:read-md)))
		    (setf (global-frame) (hw:ldb (frame-first-word) %%cpdl0-global-frame (hw:unboxed-constant 0)))
		    (go restore-a-frame))
		  ($$cpdl0-type-open
		    ;;; we need only restore the open frame
		    (setq gr:*ch-control-pdl-index* (hw:24-1+ gr:*ch-control-pdl-index*))		;skip RPC
		    (hw:open-frame)
		    (go restore-o-frame))
		  ($$cpdl0-type-topen
		    ;;; we need only restore the active frame
		    (setq gr:*ch-control-pdl-index* (hw:24-1+ gr:*ch-control-pdl-index*))		;skip RPC
		    (hw:ch-topen)
		    (go restore-o-frame)))
     restore-o-frame
	(progn
	  (restore-register-prep)
	  (restore-register hw:o0)  (restore-register hw:o1)
	  (restore-register hw:o2)  (restore-register hw:o3)
	  (restore-register hw:o4)  (restore-register hw:o5)
	  (restore-register hw:o6)  (restore-register hw:o7)
	  (restore-register hw:o8)  (restore-register hw:o9)
	  (restore-register hw:o10) (restore-register hw:o11)
	  (restore-register hw:o12) (restore-register hw:o13)
	  (restore-register hw:o14) (restore-register hw:o15)
	  (go loop))
     restore-a-frame
	(progn
	  (restore-register-prep)
	  (restore-register hw:a0)  (restore-register hw:a1)
	  (restore-register hw:a2)  (restore-register hw:a3)
	  (restore-register hw:a4)  (restore-register hw:a5)
	  (restore-register hw:a6)  (restore-register hw:a7)
	  (restore-register hw:a8)  (restore-register hw:a9)
	  (restore-register hw:a10) (restore-register hw:a11)
	  (restore-register hw:a12) (restore-register hw:a13)
	  (restore-register hw:a14) (restore-register hw:a15)
	  (go loop))
     end)
    (save-control-pdl-state)
    (setq gr:*control-pdl* (control-pdl-assure-room gr:*control-pdl*))
    (load-control-pdl-state)
    (dumping-or-restoring-call-hardware nil)
    (li:error "call hardware restored")
    ))


;;; when the control pdl and call hardware are scrolled the base of the call hardware
;;; must be an open-call frame so that the underflow catcher can be reached via a return operation.
;;; Since the call hardware dumper and restorer play with traps and can only be invoked while
;;; traps are enabled, this boundary can not fall on an open-call across which traps are disabled.
;;; To prevent the restorer from putting the underflow handler boundary at an open-call across which
;;; traps are disabled, we make the rule that only the trap handler is allowed to disable traps.
;;; We also have the rule that it must reenable traps by the time is goes TRAP-CALL-PROTECTION-COUNT
;;; deep in calls.  When the call hardware dumper sees a return pc that is a trap entry (pc < 64)
;;; it protects the next TRAP-CALL-PROTECTION-COUNT open-calls that is sees by recording them
;;; as being control pdl frame type $$CPDL0-TYPE-PROTECTED-OPEN-CALL rather than $$CPDL0-TYPE-OPEN-CALL.
;;; The restorer only looks for $$CPDL0-TYPE-OPEN-CALL when deciding how much to restore but when
;;; it does restore, it treats both type codes the same.
(defconstant trap-call-protection-count 2)

(defmacro with-dumper-macros (&body body)
  `(macrolet ((previous-a-frame ()	'gr:*ch-temp-0*)
	      (accumulated-box-bits ()	'gr:*ch-temp-1*)
	      (saved-csp ()		'gr:*ch-temp-2*)
	      (protection-count ()	'gr:*ch-temp-3*)
	      (this-rpc ()		'gr:*ch-temp-4*)
	      (word-1 ()		'gr:*ch-temp-5*)
	      (save-register (register)
		`(progn (hw:write-md-unboxed (,register))
			(hw:vma-start-write-no-gc-trap-unboxed gr:*control-pdl-pointer*)
			(setq gr:*control-pdl-pointer* (hw:24+ 1 gr:*control-pdl-pointer*))))
	      (save-box-bit (register)
		`(setf (accumulated-box-bits) (HW:ACCUMULATE-BOX-BITS (accumulated-box-bits) (,register)))))
       ,@body))

;;; CLEAR-R-FRAME, WRITE-OPEN-CALL-FRAME and WRITE-OPEN-FRAME are invoked using the same hack that CONS-REST
;;; is called with.  This allows the function to return to its caller (whose pc is stored in 
;;; GR:*RETURN-PC-1*).  This hack is required so that no call hardware operation is performed
;;; when calling the function or returning from it.  This way we can access the same registers
;;; as our caller.
(defun write-open-call-frame ()
  (with-dumper-macros
    (progn
      (save-register hw:a0)  (save-box-bit hw:a15)   (save-register hw:a1)  (save-box-bit hw:a14)
      (save-register hw:a2)  (save-box-bit hw:a13)   (save-register hw:a3)  (save-box-bit hw:a12)
      (save-register hw:a4)  (save-box-bit hw:a11)   (save-register hw:a5)  (save-box-bit hw:a10)
      (save-register hw:a6)  (save-box-bit hw:a9)    (save-register hw:a7)  (save-box-bit hw:a8)
      (save-register hw:a8)  (save-box-bit hw:a7)    (save-register hw:a9)  (save-box-bit hw:a6)
      (save-register hw:a10) (save-box-bit hw:a5)    (save-register hw:a11) (save-box-bit hw:a4)
      (save-register hw:a12) (save-box-bit hw:a3)    (save-register hw:a13) (save-box-bit hw:a2)
      (save-register hw:a14) (save-box-bit hw:a1)    (save-register hw:a15) (save-box-bit hw:a0))
    (setf (word-1)
	  (hw:dpb-unboxed (if (zerop (protection-count))
			      $$cpdl0-type-open-call
			    (progn (setf (protection-count) (1- (protection-count)))
				   $$cpdl0-type-protected-open-call))
			  %%cpdl0-type-code
			  (word-1)))
    (when (< (hw:ldb (this-rpc) vinc:%%pointer 0) 64.)
      (setf (protection-count) trap-call-protection-count))
    (hw:write-md-unboxed (hw:dpb-unboxed (accumulated-box-bits) %%cpdl0-box-bits (word-1)))
    (hw:vma-start-write-no-gc-trap-unboxed
      (hw:24+ (- control-pdl-frame-size) gr:*control-pdl-pointer*))
    (hw:dispatch (hw:24+ 1 gr:*ch-dumper-return-pc*))))

(defun write-open-frame ()
  (with-dumper-macros
    (progn
      (save-register hw:o0)  (save-box-bit hw:o15)   (save-register hw:o1)  (save-box-bit hw:o14)
      (save-register hw:o2)  (save-box-bit hw:o13)   (save-register hw:o3)  (save-box-bit hw:o12)
      (save-register hw:o4)  (save-box-bit hw:o11)   (save-register hw:o5)  (save-box-bit hw:o10)
      (save-register hw:o6)  (save-box-bit hw:o9)    (save-register hw:o7)  (save-box-bit hw:o8)
      (save-register hw:o8)  (save-box-bit hw:o7)    (save-register hw:o9)  (save-box-bit hw:o6)
      (save-register hw:o10) (save-box-bit hw:o5)    (save-register hw:o11) (save-box-bit hw:o4)
      (save-register hw:o12) (save-box-bit hw:o3)    (save-register hw:o13) (save-box-bit hw:o2)
      (save-register hw:o14) (save-box-bit hw:o1)    (save-register hw:o15) (save-box-bit hw:o0))
    (hw:write-md-unboxed (hw:dpb-unboxed (accumulated-box-bits) %%cpdl0-box-bits (word-1)))
    (hw:vma-start-write-no-gc-trap-unboxed
      (hw:24+ (- control-pdl-frame-size) gr:*control-pdl-pointer*))
    (hw:dispatch (hw:24+ 1 gr:*ch-dumper-return-pc*))))

(defun clear-r-frame ()
  (setf (hw:r0)  (hw:unboxed-constant 0))   (setf (hw:r1)  (hw:unboxed-constant 0))
  (setf (hw:r2)  (hw:unboxed-constant 0))   (setf (hw:r3)  (hw:unboxed-constant 0))
  (setf (hw:r4)  (hw:unboxed-constant 0))   (setf (hw:r5)  (hw:unboxed-constant 0))
  (setf (hw:r6)  (hw:unboxed-constant 0))   (setf (hw:r7)  (hw:unboxed-constant 0))
  (setf (hw:r8)  (hw:unboxed-constant 0))   (setf (hw:r9)  (hw:unboxed-constant 0))
  (setf (hw:r10) (hw:unboxed-constant 0))   (setf (hw:r11) (hw:unboxed-constant 0))
  (setf (hw:r12) (hw:unboxed-constant 0))   (setf (hw:r13) (hw:unboxed-constant 0))
  (setf (hw:r14) (hw:unboxed-constant 0))   (setf (hw:r15) (hw:unboxed-constant 0))
  (hw:dispatch (hw:24+ 1 gr:*return-pc-1*)))

(defun dump-call-hardware ()
  (with-dumper-macros
    (tagbody
	(dumping-or-restoring-call-hardware t)
	(hw:write-processor-control (hw:dpb hw:$$call-heap-underflow-trap-disable
					    hw:%%processor-control-heap-underflow-trap-enable
					    (hw:read-processor-control)))
	(setf (protection-count) 0)
	(setf (previous-a-frame) 256)	;nonexistant
	(setf (saved-csp) (hw:ldb (hw:read-call-sp-hp) hw:%%ch-csphp-call-stack-pointer 0))
	(hw:trap-off)
     loop
	(setq gr:*ch-base-csp* (hw:8-1+ gr:*ch-base-csp*))
	(hw:write-call-sp-hp (hw:dpb-unboxed gr:*ch-base-csp*
					     hw:%%ch-csphp-call-stack-pointer (hw:read-call-sp-hp)))
	(hw:nop) (hw:nop) (hw:nop) (hw:nop) (hw:nop)	;how many do we need?
	(setf (this-rpc) (hw:ldb (hw:read-return-pc-return-dest)
				 hw:%%ch-rpcd-return-pc gr:*trap-dtp-code-5*))
	(setf (word-1) (hw:unboxed-constant 0))
	(setq gr:*ch-temp-6* (hw:ldb (hw:read-return-pc-return-dest)
				     hw:%%ch-rpcd-return-dest (hw:unboxed-constant 0)))
	(setf (word-1) (hw:dpb-unboxed gr:*ch-temp-6* %%cpdl0-rdest (word-1)))
	(setq gr:*ch-temp-6* (hw:ldb (hw:read-processor-status)
				     hw:%%processor-status-global-return-frame
				     (hw:unboxed-constant 0)))
	(setf (word-1) (hw:dpb-unboxed gr:*ch-temp-6* %%cpdl0-global-frame (word-1)))
;	(hw:jump-saving-pc 'clear-r-frame gr:*ch-dumper-return-pc*)
	(hw::ch-return) (hw:nop) (hw:nop) (hw:nop) (hw:nop) (hw:nop)	;do we need these NOPs
	(hw:write-call-sp-hp (hw:dpb-unboxed (saved-csp)
					     hw:%%ch-csphp-call-stack-pointer (hw:read-call-sp-hp)))
	(hw:nop) (hw:nop) (hw:nop) (hw:nop) (hw:nop)
;	(trap:trap-on)
	(hw:write-memory-control
	  (hw:dpb-unboxed hw:$$trap-enable hw:%%memory-control-master-trap-enable
			  (hw:read-memory-control)))
	(hw:nop) (hw:nop) (hw:nop) (hw:nop)
	(hw:write-md-unboxed (this-rpc))
	(hw:vma-start-write-no-gc-trap-unboxed (hw:24+ 1 gr:*control-pdl-pointer*))
	(setq gr:*ch-temp-6* (hw:ldb (hw:read-open-active-return) hw:%%ch-oar-active 0))
	(setq gr:*control-pdl-pointer* (hw:24+ control-pdl-frame-offset-to-registers
					       gr:*control-pdl-pointer*))
	(cond ((= gr:*ch-temp-6*
		  (hw:ldb (hw:read-open-active-return) hw:%%ch-oar-open 0))	;OPEN-CALL
	       (hw:jump-saving-pc 'write-open-call-frame gr:*ch-dumper-return-pc*)
	       (setf (previous-a-frame) (hw:ldb (hw:read-open-active-return) hw:%%ch-oar-active 0))
	       (hw:trap-off))
	      ((= gr:*ch-temp-6* (previous-a-frame))		;OPEN
	       (setf (word-1) (hw:dpb-unboxed $$cpdl0-type-open %%cpdl0-type-code (word-1)))
	       (hw:jump-saving-pc 'write-open-frame gr:*ch-dumper-return-pc*)
	       (setf (previous-a-frame) (hw:ldb (hw:read-open-active-return) hw:%%ch-oar-active 0))
	       (hw:trap-off)
	       (hw:ch-tcall)
	       (hw:ch-topen-call)
	       (hw:ch-topen))
	      (t				;OPEN-CALL TOPEN
	       (hw:jump-saving-pc 'write-open-call-frame gr:*ch-dumper-return-pc*)
	       (setq gr:*control-pdl-pointer* (hw:24+ control-pdl-frame-offset-to-registers
						      gr:*control-pdl-pointer*))
	       (setf (word-1) (hw:dpb-unboxed $$cpdl0-type-topen %%cpdl0-type-code (word-1)))
	       (hw:jump-saving-pc 'write-open-frame gr:*ch-dumper-return-pc*)
	       (setf (previous-a-frame) (hw:ldb (hw:read-open-active-return) hw:%%ch-oar-active 0))
	       (hw:trap-off)
	       (hw::ch-tcall)))
	(unless (= gr:*ch-base-csp* (saved-csp))
	  (go loop))
     end
	(hw:ch-tcall)
	(hw:write-processor-control (hw:dpb hw:$$call-heap-underflow-trap-enable
					    hw:%%processor-control-heap-underflow-trap-enable
					    (hw:read-processor-control)))
	(trap:trap-on))
    (select-control-pdl gr:*next-control-pdl*)
    (restore-call-hardware)))
