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

;;;*****************************************************************************************************
;;;*****   This file should be identical to new-fasdump(-for-k-debugger) at all times WKF 5/5/88   *****
;;;*****************************************************************************************************

;;; These duplicate versions are needed to fool make-system since COMPILER-FOR-K and K-DEBUGGER
;;;  both use this file and k-debugger loads it in the k-user hierarchy.   WKF

(export '(
	  fasd-defafun
	  fasd-defconstant
	  fasd-defmacro
	  fasd-defsubst
	  fasd-defun
	  fasd-eof
	  fasd-eval
	  fasd-in-package
	  fasd-object

	  ))


;;; To see how the K responds to the fasdump stream follow the bouncing balls from
;;;  kbug-trap-handler-1 and kbug-trap-handler-2   in  k-sys:kbug2;k2.lisp    to
;;;  kbug-fasl-stream and mini-fasl-read-top-level-object and  mini-fasl-read-object-1  in k-sys:k;warm-loader.lisp


;; This is a hack to deal with all the top level forms with a minimum of special casing.
;; It is not efficient about what it expects to see (i.e. it doesn't allow you to take
;; advantage of the fact that you know a symbol is coming and that you don't need the opcode.)
;; It was easy to do.  If you want to save a few bytes here and there, go ahead and hack.

(defstruct fasd-special-top-level-form
  fasl-op
  guts)

(defun fasd-opcode (opcode stream)
  (write-byte opcode stream))

(defun fasd-eof (stream)
  (fasd-opcode k2:$$fasl-op-end-of-file stream))

(defmacro define-fasd (name internal opcode object-var stream-var &body body)
  (declare (zwei::indentation 5 2))
  `(PROGN (DEFUN ,name (,object-var ,stream-var)
	    (FASD-OPCODE ,opcode ,stream-var)
	    (,internal ,object-var ,stream-var))
	  (DEFUN ,internal (,object-var ,stream-var)
	    ,@body)))
	    
(eval-when (load compile eval)
  (deftype k-size-fixnum () `(signed-byte 24.))
  )

(define-fasd fasd-fixnum fasd-fixnum-internal K2:$$FASL-OP-FIXNUM fixnum stream
   (check-type fixnum k-size-fixnum)
   (write-byte (ldb (byte 8. 0.)  fixnum) stream)
   (write-byte (ldb (byte 8. 8.)  fixnum) stream)
   (write-byte (ldb (byte 8. 16.) fixnum) stream))

(eval-when (load compile eval)
  (deftype k-bignum () `(and integer (not (signed-byte 24.))))
  )

(define-fasd fasd-bignum fasd-bignum-internal K2:$$fasl-op-bignum bignum stream
   (check-type bignum k-bignum)
   (let ((words-needed (ceiling (1+ (integer-length bignum)) 32.)))
     (fasd-fixnum-internal words-needed stream)
     (do ((count (* words-needed 4.) (1- count))
	  (number bignum (ash (logand number (ash -1. 8.)) -8.)))
	 ((zerop count))
       (write-byte (ldb (byte 8. 0.) number) stream))))

#|  These are for when the K fasdumps for itself
(define-fasd fasd-short-float fasd-short-float-internal K2:$$fasl-op-short-float flonum stream
   (check-type flonum k-short-float)
   (write-byte (ldb (byte 8. 0.)  flonum) stream)
   (write-byte (ldb (byte 8. 8.)  flonum) stream)
   (write-byte (ldb (byte 8. 16.) flonum) stream)
   (write-byte (ldb (byte 2. 24.) flonum) stream)
   )

(define-fasd fasd-single-float fasd-single-float-internal K2:$$fasl-op-single-float flonum stream
   (check-type flonum k-single-float)
   (let ((unboxed-word (array::%vm-read32 flonum 1)))
     (write-byte (ldb (byte 8. 0.)  flonum) stream)
     (write-byte (ldb (byte 8. 8.)  flonum) stream)
     (write-byte (ldb (byte 8. 16.) flonum) stream)
     (write-byte (ldb (byte 8. 24.) flonum) stream))
   )

(define-fasd fasd-double-float fasd-double-float-internal K2:$$fasl-op-double-float flonum stream
   (check-type flonum k-double-float)
   (let ((unboxed-word (array::%vm-read32 flonum 1)))
     (write-byte (ldb (byte 8. 0.)  flonum) stream)
     (write-byte (ldb (byte 8. 8.)  flonum) stream)
     (write-byte (ldb (byte 8. 16.) flonum) stream)
     (write-byte (ldb (byte 8. 24.) flonum) stream))
   (let ((unboxed-word (array::%vm-read32 flonum 2)))
     (write-byte (ldb (byte 8. 0.)  flonum) stream)
     (write-byte (ldb (byte 8. 8.)  flonum) stream)
     (write-byte (ldb (byte 8. 16.) flonum) stream)
     (write-byte (ldb (byte 8. 24.) flonum) stream))
   )
|#

;;; IEEE single precision format is as follows:
;;; bit 31:        sign bit
;;; bits 23-30:    8 bit exponent + 127.
;;; bits  0-22:    23 bit fraction with a hidden one bit before the binary point

;;; IEEE double precision format is as follows:
;;; bit 63:        sign bit
;;; bits 52-62:    exponent + 1023.
;;; bits  0-51:    52 bit fraction with hidden one bit before the binary point

;;; These are for the lambda generating KFASL files.  The lambda doesn't understand how to represent
;;; K flonums so we just dump out lambda flonums so that they can be fasloaded by the K.

(define-fasd fasd-lambda-short-float fasd-lambda-short-float-internal K2:$$fasl-op-short-float flonum stream
   (check-type flonum short-float)
   (let ((unboxed-k-single-float-word 0)
	 (minus-p (minusp flonum)))
     (if (zerop flonum)
	 (setq unboxed-k-single-float-word 0)	;zero sign, exponent, mantissa.
       
       (multiple-value-bind (significand exponent ignore)
	   (decode-float (if minus-p (- flonum) flonum))
	 ;;; We have to do this MINUS-P hack because DECODE-FLOAT on the Lambda returns a different significand
	 ;;; and exponent for -1.0 and 1.0.
	 (setq unboxed-k-single-float-word
	       (dpb (if minus-p 1 0) (byte 1 31.) unboxed-k-single-float-word))
	 (setq unboxed-k-single-float-word
	       (dpb (+ exponent 127.) (byte 8. 23.) unboxed-k-single-float-word))
	 (setq unboxed-k-single-float-word
	       (dpb (ldb (byte 23 0)		;hide the "hidden" bit
			 (truncate (* (expt 2 24.) significand) 1))
		    (byte 23 0) unboxed-k-single-float-word)) ))
     
     ;;; Short floats on the K are just like IEEE single floats except they are shifted so that the bottom
     ;;; 6 bits of the mantissa are discarded.
     (write-byte (ldb (byte 8. (+ 6  0.)) unboxed-k-single-float-word) stream)
     (write-byte (ldb (byte 8. (+ 6  8.)) unboxed-k-single-float-word) stream)
     (write-byte (ldb (byte 8. (+ 6 16.)) unboxed-k-single-float-word) stream)
     (write-byte (ldb (byte 2. (+ 6 24.)) unboxed-k-single-float-word) stream)))
	 
(define-fasd fasd-lambda-full-size-float fasd-lambda-full-size-float-internal K2:$$fasl-op-double-float flonum stream
   (check-type flonum single-float)
   ;;; A SINGLE-FLOAT on the Lambda has a 31 bit mantissa and an 11 bit exponent.  This will
   ;;; not fit in a K single float but it will in a K double float.  First we construct the
   ;;; two unboxed words for the K's representation of the double-float then we dump them.
   (let ((unboxed-k-double-float-word 0)	;the first and second unboxed words of a K double-float as 64 bits.
	 (minus-p (minusp flonum)))
     (if (zerop flonum)
	 (setq unboxed-k-double-float-word 0)			;zero sign, exponent, mantissa.
       
       (multiple-value-bind (significand exponent ignore)
	   (decode-float (if minus-p (- flonum) flonum))
	 ;;; We have to do this MINUS-P hack because DECODE-FLOAT on the Lambda returns a different significand
	 ;;; and exponent for -1.0 and 1.0.
	 (setq unboxed-k-double-float-word	;the sign bit
	       (dpb (if minus-p 1 0) (byte 1 63.) unboxed-k-double-float-word))
	 (setq unboxed-k-double-float-word	;the exponent
	       (dpb (+ exponent 1023.) (byte 11. 52.) unboxed-k-double-float-word))
	 
	 ;;; normalize the significand into the fixed-width field
	 (setq significand (truncate (* (expt 2 53.) significand) 1))

	 ;;; move the mantissa 20 bits at a time
	 (setq unboxed-k-double-float-word
	       (dpb (ldb (byte 20. 0) significand)
		    (byte 20. 0) unboxed-k-double-float-word))
	 (setq unboxed-k-double-float-word
	       (dpb (ldb (byte 20. 20.) significand)
		    (byte 20. 20.) unboxed-k-double-float-word))
	 (setq unboxed-k-double-float-word
	       (dpb (ldb (byte 12. 40.) significand)
		    (byte 12. 40.) unboxed-k-double-float-word))
	 ))

     (write-byte (ldb (byte 8.  0.) unboxed-k-double-float-word) stream)
     (write-byte (ldb (byte 8.  8.) unboxed-k-double-float-word) stream)
     (write-byte (ldb (byte 8. 16.) unboxed-k-double-float-word) stream)
     (write-byte (ldb (byte 8. 24.) unboxed-k-double-float-word) stream)
     (write-byte (ldb (byte 8. 32.) unboxed-k-double-float-word) stream)
     (write-byte (ldb (byte 8. 40.) unboxed-k-double-float-word) stream)
     (write-byte (ldb (byte 8. 48.) unboxed-k-double-float-word) stream)
     (write-byte (ldb (byte 8. 56.) unboxed-k-double-float-word) stream)
   ))

(define-fasd fasd-string-character fasd-string-character-internal K2:$$FASL-OP-STRING-CHARACTER character stream
   (check-type character string-char)
   (write-byte (char-int character) stream))

(define-fasd fasd-simple-string fasd-simple-string-internal K2:$$FASL-OP-STRING string stream
   (check-type string simple-string)
   (let ((length (length string)))
     (fasd-fixnum-internal length stream)
     (write-string string stream)))
;     (dotimes (index length)
;       (fasd-string-character-internal (char string index) stream))))

(define-fasd fasd-simple-vector fasd-simple-vector-internal K2:$$FASL-OP-SIMPLE-VECTOR vector stream
   (check-type vector simple-vector)
   (let ((length (length vector)))
     (fasd-fixnum-internal length stream)
     (dotimes (index length)
       (fasd-object-considering-circularity (svref vector index) stream))))

(defun fasd-unbound (stream)
  (fasd-opcode K2:$$fasl-op-unbound stream))

(defun fasd-nil (stream)
  (fasd-opcode K2:$$fasl-op-nil stream))

(defun fasd-cons (cons stream)
  (fasd-cons-internal cons stream t))

(defun fasd-cons-internal (cons stream &optional (fasd-opcode-p nil))
  (check-type cons cons)
   (let ((len (list-length cons)))
     (if (and len
	      (null (nthcdr len cons)))
	 (progn
	   (when fasd-opcode-p (fasd-opcode K2:$$FASL-OP-LIST stream))
	   (fasd-fixnum-internal len stream)
	   (dolist (elt cons)
	     (fasd-object-considering-circularity elt stream)))
       (progn
	 (when fasd-opcode-p (fasd-opcode K2:$$FASL-OP-CONS stream))
	 (fasd-object-considering-circularity (car cons) stream)
	 (fasd-object-considering-circularity (cdr cons) stream)))))
       

(defvar *prims-package* (find-package "PRIMITIVES"))
(defvar *li-package* (find-package "LI"))

(defun get-symbol-package-name (symbol)
  (if (eq (symbol-package symbol) si:pkg-global-package)
      (if (si:intern-soft symbol *prims-package*)
	  "PRIMITIVES"
	;; this is mildly sleazy
	(let ((s (si:intern-soft (symbol-name symbol) *li-package*)))
	  (if s
	      (si:package-primary-name (symbol-package s))
	    ;(error "Where does this GLOBAL symbol go? ~s" symbol))))
	    "GLOBAL")))
      (let ((package (symbol-package symbol)))
	(if package
	    (si:package-primary-name package)
	  ;; This is the wrong thing to do for gensyms
	  ;; needs to use fasl table
	  "NIL"))))

(define-fasd fasd-symbol fasd-symbol-internal K2:$$FASL-OP-SYMBOL symbol stream
   (check-type symbol symbol)
   (fasd-object-considering-circularity (symbol-name    symbol) stream)
   (fasd-object-considering-circularity (get-symbol-package-name symbol)
					stream))


(defun fasd-compiled-function (function stream)
  (let ((fcn (nc::get-ncompiled-function function)))
    (fasd-compiled-function-info
      (nc::ncompiled-function-name fcn)
      (nc::ncompiled-function-local-refs fcn)
      (nc::ncompiled-function-refs fcn)
      (nc::ncompiled-function-immediates fcn)
      (nc::ncompiled-function-entry-points fcn)
      (nc::ncompiled-function-code fcn)
      stream)))

(defun fasd-compiled-function-info (name local-refs refs immediates entry-points code
				    stream)
  (fasd-opcode K2:$$fasl-op-compiled-function stream)
  (fasd-object name stream)
  (fasd-link-info local-refs refs entry-points stream)
  (fasd-fixnum-internal (length code) stream)
  (map nil #'(lambda (instruction)
	       (fasd-instruction instruction stream))
       code)
  (fasd-immediates immediates stream))

(defun fasd-cold-compiled-function-info (name local-refs refs
					 immediates entry-points length starting-address
					 stream)
  (fasd-object name stream)
  (fasd-link-info local-refs refs entry-points stream)
  (fasd-fixnum-internal length stream)
  (fasd-fixnum-internal starting-address stream)
  (fasd-immediates immediates stream))


(defun fasd-link-info (local-refs refs entry-points stream)
  (let ((len (length local-refs)))
    (fasd-fixnum-internal (ash len -1) stream)		         ;number of refs
    (do ((i 0 (+ i 2)))
	((>= i len))
      (fasd-fixnum-internal (svref local-refs i) stream)	 ;ref offset
      (fasd-fixnum-internal (svref local-refs (1+ i)) stream)))  ;target offset
  (let ((len (length refs)))
    (fasd-fixnum-internal (/ len 3) stream)                ;number of refs
    (do ((i 0 (+ i 3)))
	((>= i len))
      (fasd-fixnum-internal (svref refs i) stream)         ;ref offset
      (fasd-object          (svref refs (1+ i)) stream)    ;referenced function name
      (fasd-fixnum-internal (svref refs (+ i 2)) stream))) ;number of args
  (let ((len (length entry-points)))
    (fasd-fixnum-internal (ash len -1) stream)                     ;number of entry points
    (do ((i 0 (+ i 2)))
	((>= i len))
      (fasd-fixnum-internal (svref entry-points i) stream)	   ;number of args
      (fasd-fixnum-internal (svref entry-points (1+ i)) stream)))) ;entry offset

(defun fasd-immediates (immediates stream)
  (let ((len (length immediates)))
    (fasd-fixnum-internal (ash len -1) stream)
    (do ((i 0 (+ i 2)))
	((>= i len))
      (fasd-fixnum-internal (svref immediates i) stream)  ;ref offset
      (fasd-object (svref immediates (1+ i)) stream))))   ;immediate object
    
    
(eval-when (compile load eval)
  (deftype k-instruction () '(unsigned-byte 64))
  )
			
(defun fasd-instruction (inst stream)
  (check-type inst k-instruction)
  (write-byte (ldb (byte 8.  0.) inst) stream) 
  (write-byte (ldb (byte 8.  8.) inst) stream) 
  (write-byte (ldb (byte 8. 16.) inst) stream) 
  (write-byte (ldb (byte 8. 24.) inst) stream)
  (write-byte (ldb (byte 8. 32.) inst) stream)
  (write-byte (ldb (byte 8. 40.) inst) stream)
  (write-byte (ldb (byte 8. 48.) inst) stream)
  (write-byte (ldb (byte 8. 56.) inst) stream))

(defun fasd-defconstant (stream name value &optional (doc-string nil))
  (fasd-opcode K2:$$fasl-op-defconstant stream)
  (fasd-object-considering-circularity name stream)
  (fasd-object-considering-circularity value stream)
  (fasd-object-considering-circularity doc-string stream))

(defun fasd-defparameter (stream name value &optional (doc-string nil))
  (fasd-opcode K2:$$fasl-op-defparameter stream)
  (fasd-object-considering-circularity name stream)
  (fasd-object-considering-circularity value stream)
  (fasd-object-considering-circularity doc-string stream))
  
(defun fasd-defvar (stream name &optional (value nil value-p) (doc-string nil))
  (fasd-opcode K2:$$fasl-op-defvar stream)
  (fasd-object-considering-circularity name stream)
  (if value-p
      (if (constantp value)
	  (fasd-object-considering-circularity
	    (if (and (consp value)
		     (eq (car value) 'quote))
		(second value)
	      value)
	    stream)
	(fasd-eval stream value))
    (fasd-unbound stream))
  (fasd-object-considering-circularity doc-string stream))

(defun fasd-special-top-level-form (obj stream)
  (fasd-opcode (fasd-special-top-level-form-fasl-op obj) stream)
  (mapcar #'(lambda (object) (really-fasd-object object stream)) (fasd-special-top-level-form-guts obj)))
  
(defun really-fasd-object (obj stream)
  (etypecase obj
    (null		(fasd-nil		    stream))
    (symbol       	(fasd-symbol		obj stream))
    (cons		(fasd-cons		obj stream))
    (k-size-fixnum	(fasd-fixnum 		obj stream))
    (k-bignum		(fasd-bignum		obj stream))
    (string-char	(fasd-string-character	obj stream))
    (simple-string	(fasd-simple-string 	obj stream))
    (nc::ncompiled-function (fasd-compiled-function obj stream))
    (fasd-special-top-level-form  (fasd-special-top-level-form      obj stream))
    ;;; The two flonum things will need to be changed when the fasdumper runs on the K:
    (short-float	(fasd-lambda-short-float obj stream))
    (single-float	(fasd-lambda-full-size-float obj stream))
    (simple-vector      (fasd-simple-vector obj stream))
    ))

;;; Circularity checking is truly simple minded.

(defvar *understand-circular-objects?* nil
  "T when the K can fix up circularities.")
(defvar *all-objects*           '())
(defvar *circular-object-list*  '())
(defvar *fasd-table*		'())

(defun fasd-object (obj stream)
  (setq *all-objects* '()
	*fasd-table*  '())
  (when *understand-circular-objects?* (find-circularities obj))
  (if (null *circular-object-list*)
      (fasd-object-considering-circularity obj stream)
      (progn (fasd-fasd-table stream)
	     (fasd-object-considering-circularity obj stream)
	     (fasd-opcode K2:$$fasl-op-end-of-object stream))))

(defun find-circularities (obj)
  (if (member obj *all-objects* :test #'eq)
      (pushnew obj *circular-object-list* :test #'eq)
      (etypecase obj
	(null   	nil)
	(symbol		(push obj *all-objects*)
			(find-circularities (symbol-name obj))
			(find-circularities (si:package-primary-name (symbol-package obj))))
	(cons		(push obj *all-objects*)
			(find-circularities (car obj))
			(find-circularities (cdr obj)))
	(k-size-fixnum	nil)
	(k-bignum	nil)
	(string-char    nil)
	(simple-string	(push obj *all-objects*))
	(nc::ncompiled-function
	 (push obj *all-objects*)
	 (let ((refs       (nc::ncompiled-function-refs obj))
	       (immediates (nc::ncompiled-function-immediates obj)))
	   (find-circularities refs)
	   (find-circularities immediates)))
	(fasd-special-top-level-form (push obj *all-objects*)
				     (mapcar #'find-circularities (fasd-special-top-level-form-guts obj)))
	;;; The two flonum things will need to be changed when the fasdumper runs on the K:
	(short-float	nil)
	(single-float	nil)
	)))

(defun object-has-multiple-references? (object)
  (member object *circular-object-list* :test #'eq))

(defun object-has-been-fasdumped? (object)
  (member object *fasd-table* :test #'eq))

(defun fasdump-object-index (object stream)
  (let ((index (position object *fasd-table* :test #'eq)))
    (fasd-opcode K2:$$fasl-op-reference-table-index stream)
    (fasd-fixnum-internal index stream)))

(defun assign-fasd-index (obj stream)
  (setq *fasd-table* (append *fasd-table* (list obj)))
  (let ((index (position obj *fasd-table* :test #'eq)))
    (fasd-opcode K2:$$fasl-op-store-table-index stream)
    (fasd-fixnum-internal index stream)))

(defun fasd-fasd-table (stream)
  (fasd-opcode K2:$$fasl-op-create-table stream)
  (fasd-fixnum-internal (length *circular-object-list*) stream))
  
(defun fasd-object-considering-circularity (obj stream)
  (if (object-has-multiple-references? obj)
      (if (object-has-been-fasdumped? obj)
	  (fasdump-object-index obj stream)
	  (progn
	    (assign-fasd-index obj stream)
	    (really-fasd-object obj stream)))
      (really-fasd-object obj stream)))

;(defun mini-fasd (object)
;  (let ((string
;	  (zl::with-output-to-string (string)
;	    (fasd-object object string)
;	    (fasd-opcode K2:$$fasl-op-end-of-file string))))
;    (dotimes (index (length string))
;      (kbug-streams::kbug-stream-write-byte
;	kbug2-common::kbug-k-input-fasl-stream
;	(char string index)))))

(defmacro define-top-level-fasdump (name opcode &rest arglist)
  `(DEFUN ,name ,(cons 'STREAM arglist)
     (FASD-OBJECT
       (MAKE-FASD-SPECIAL-TOP-LEVEL-FORM
	 :FASL-OP	,opcode
	 :GUTS		(LIST ,@arglist))
       STREAM)))

;(define-top-level-fasdump fasd-defun K2:$$fasl-op-defun symbol function)
;(define-top-level-fasdump fasd-defsubst K2:$$fasl-op-defsubst symbol function source)
;(define-top-level-fasdump fasd-defmacro K2:$$fasl-op-defmacro symbol expander-function)
;(define-top-level-fasdump fasd-defafun  K2:$$fasl-op-defafun  symbol code)
;(define-top-level-fasdump fasd-defconstant K2:$$fasl-op-defconstant symbol value documentation)
(define-top-level-fasdump fasd-eval K2:$$fasl-op-eval form)
(define-top-level-fasdump fasd-in-package K2:$$fasl-op-in-package cruft)

(defmacro define-fasd-top-level-function (name opcode &rest args)
  `(DEFUN ,name (FCN STREAM ,@args)
     (FASD-OPCODE ,opcode STREAM)
     ;; probably redundant
     (FASD-OBJECT (NC:NCOMPILED-FUNCTION-NAME FCN) STREAM)
     ,@(mapcar #'(lambda (arg)
		   `(FASD-OBJECT ,arg STREAM))
	       args)
     (FASD-COMPILED-FUNCTION FCN STREAM)))

(define-fasd-top-level-function fasd-defun    K2:$$fasl-op-defun)
(define-fasd-top-level-function fasd-defsubst K2:$$fasl-op-defsubst source)
(define-fasd-top-level-function fasd-defmacro K2:$$fasl-op-defmacro)
(define-fasd-top-level-function fasd-defafun  K2:$$fasl-op-defafun)

