;;;
;;; faslizer.lisp
;;;
;;; fasl file analyzer.
;;;


;; Boilerplate

(cl:defpackage :faslizer
  (:use :common-lisp)
  (:export "FASLIZE-FILE"))

(cl:in-package :faslizer)


;; Utility functions

(defun hexdump (array index base &optional (n #x80))
  (let ((textbuf (make-string 18)))
    (unless (zerop (logand base 15))
      (format t "~(~8,'0X:~)" base))

    (setf (aref textbuf 0) #\|)
  
    (dotimes (i (logand base 15))
      (format t "   ")
      (setf (aref textbuf i) #\Space)
      (setf (aref textbuf (1+ i)) #\|))

    (setf (aref textbuf 17) #\|)
  
    (dotimes (j n)
      (when (zerop (logand 15 (+ base j)))
	(format t "~(~8,'0X:~)" (+ base j)))
      (format t " ~(~2,'0X~)" (aref array (+ index j)))
      (setf (aref textbuf (1+ (logand 15 (+ base j))))
	    (if (and (< (aref array (+ index j)) #x80)
		     (graphic-char-p (code-char (aref array (+ index j)))))
		(code-char (aref array (+ index j)))
		#\.))
      (when (= 15 (logand 15 (+ base j)))
	(format t " ~A~%" textbuf)
	(setf (aref textbuf 0) #\|)))
  
    (unless (zerop (logand 15 (+ base n)))
      (setf (aref textbuf (1+ (logand 15 (+ base n)))) #\|)
      (dotimes (i (- 16 (logand 15 (+ base n))))
	(format t "   ")
	(setf (aref textbuf (+ 2 i (logand 15 (+ base n)))) #\Space))
      (format t " ~A~%" textbuf))))


;; Fasl encoding processing

(defun read-word (fasl)
  (let ((b0 (read-byte fasl))
	(b1 (read-byte fasl)))
    (dpb b1 (byte 8 8) b0)))

(defun read-dword (fasl)
  (let ((w0 (read-word fasl))
	(w1 (read-word fasl)))
    (dpb w1 (byte 16 16) w0)))

(defun read-string (fasl)
  (let* ((length (read-dword fasl))
	 (result (make-array length :element-type 'character)))
    (dotimes (i length result)
      (setf (aref result i) (code-char (read-byte fasl))))))

(defun read-small-string (fasl)
  (let* ((length (read-byte fasl))
	 (result (make-array length :element-type 'character)))
    (dotimes (i length result)
      (setf (aref result i) (code-char (read-byte fasl))))))

;; Header parsing

(defun dump-fasl-header (fasl)
  ;; header text
  (loop
     for octet = (read-byte fasl)
     until (= octet #xff)
     do (princ (code-char octet)))

  ;; target arch
  (format t "arch: ~S~%" (read-string fasl))

  ;; target fasl version
  (format t "fasl version: ~S~%" (read-dword fasl))

  ;; features
  (format t "features: ~S~%" (read-string fasl)))

;; Dispatch table by fopcode

(defvar *fop-dump-table*
  (make-array #x100
	      :initial-element (lambda (fasl octet)
				 (declare (ignore fasl))
				 (format t "unknown fop #x~2,'0X (~S)~%"
					 octet (aref sb-fasl::*fop-names* octet))
				 (throw 'faslize-abort nil))))

;; Fopalizer definition helper

(defmacro defopalize (fop-name args &body forms)
  (let ((octet (get (find-symbol (symbol-name fop-name)
				 (find-package :sb-fasl))
		    'sb-fasl::fop-code)))
    (unless octet
      (error "fop ~S not found" fop-name))
    `(setf (aref *fop-dump-table* ,octet)
	   (lambda (fasl octet)
	     (declare (ignore octet))
	     (funcall (lambda ,args
			(declare (ignorable ,(first args)))
			,@forms)
		      fasl ,(symbol-name fop-name))))))

(defmacro defopalize-noarg (fop-name)
  `(defopalize ,fop-name (fasl name)
     (format t "~A~%" name)))

;; Fopalizers

(defopalize-noarg fop-misc-trap)
(defopalize-noarg fop-pop)
(defopalize-noarg fop-normal-load)
(defopalize-noarg fop-maybe-cold-load)
(defopalize-noarg fop-package)
(defopalize-noarg fop-fdefinition)
(defopalize-noarg fop-truth)
(defopalize-noarg fop-layout)
(defopalize-noarg fop-empty-list)
(defopalize-noarg fop-list-2)
(defopalize-noarg fop-list-3)
(defopalize-noarg fop-list*-2)
(defopalize-noarg fop-list*-5)
(defopalize-noarg fop-sanctify-for-execution)
(defopalize-noarg fop-fset)
(defopalize-noarg fop-verify-empty-stack)
(defopalize-noarg fop-end-group)

(defopalize fop-word-integer (fasl name)
  (format t "~A #x~2,'0X~%" name (read-dword fasl)))

(defopalize fop-byte-integer (fasl name)
  (format t "~A #x~2,'0X~%" name (read-byte fasl)))

(defopalize fop-code-object-fixup (fasl name)
  (format t "~A #x~2,'0X~%" name (read-dword fasl)))

(defopalize fop-fun-entry (fasl name)
  (format t "~A #x~2,'0X~%" name (read-dword fasl)))

(defopalize fop-verify-table-size (fasl name)
  (format t "~A ~S~%" name (read-dword fasl)))

(defopalize fop-byte-push (fasl name)
  (format t "~A ~S~%" name (read-byte fasl)))

(defopalize fop-funcall-for-effect (fasl name)
  (format t "~A ~S~%" name (read-byte fasl)))

(defopalize fop-small-vector (fasl name)
  (format t "~A ~S~%" name (read-byte fasl)))

(defopalize fop-small-struct (fasl name)
  (format t "~A ~S~%" name (read-byte fasl)))

(defopalize fop-small-base-string (fasl name)
  (format t "~A ~S~%" name (read-small-string fasl)))

(defopalize fop-lisp-small-symbol-save (fasl name)
  (format t "~A ~S~%" name (read-small-string fasl)))

(defopalize fop-keyword-small-symbol-save (fasl name)
  (format t "~A ~S~%" name (read-small-string fasl)))

(defopalize fop-small-symbol-in-byte-package-save (fasl name)
  (format t "~A ~S ~S~%" name (read-byte fasl) (read-small-string fasl)))

(defopalize fop-small-code (fasl name)
  (let* ((num-boxed (read-byte fasl))
	 (code-length (read-word fasl))
	 (code-vector (make-array code-length
				  :element-type '(unsigned-byte 8))))
    (format t "~A ~S ~S~%" name num-boxed code-length)
    (read-sequence code-vector fasl)
    (hexdump code-vector 0 0 code-length)))

(defopalize fop-int-vector (fasl name)
  (let* ((len (read-dword fasl))
	 (size (read-byte fasl))
	 (data (make-array (ceiling (* size len) 8)
			   :element-type '(unsigned-byte 8))))
    (format t "~A ~S ~S~%" name len size)
    (read-sequence data fasl)
    (hexdump data 0 0 (length data))))

(defopalize fop-small-integer (fasl name)
  (let* ((len (read-byte fasl))
	 (data (make-array len :element-type '(unsigned-byte 8))))
    (format t "~A ~S~%" name len)
    (read-sequence data fasl)
    (hexdump data 0 0 len)))

(defopalize fop-structset (fasl name)
  (format t "~A ~S ~S~%" name (read-dword fasl) (read-dword fasl)))

;; cold-nonpaged fop

(setf (aref *fop-dump-table* #xdc)
      (lambda (fasl octet)
	(declare (ignore fasl octet))
	(format t "FOP-COLD-NONPAGED~%")))

;; Entry point

(defun faslize-file (filename)
  (with-open-file (fasl filename
			:direction :input
			:element-type '(unsigned-byte 8))
    (dump-fasl-header fasl)

    (catch 'faslize-abort
      (loop
	 for octet = (read-byte fasl nil nil)
	 while octet
	 do (funcall (aref *fop-dump-table* octet) fasl octet)))))

;;; EOF
