;;; Package for Static heaps for the Scheme Shell
;;; Copyright (c) 1995 by Brian D. Carlstrom.

;;; based on Scheme48 implementation.
;;; Copyright (c) 1993, 1994 by Richard Kelsey and Jonathan Rees.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; prefix for temp files - in their own dir
(define *temp-dir*   "/tmp")

(define (test) 
  (scsh-do-it *scsh-image* *temp-dir* *image-lib* "gcc -c" "ar cq"))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define-record heap
  (length    0)
  (objects '())
  )
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (scsh-do-it infile tempdir outfile cc-command ar-command)
  (let* ((temp-dir (format #f "~a/scsh~s" tempdir (pid)))
	 (prefix (string-append temp-dir "/static"))
	 (start (read-heap-image infile)))
    (receive (pure impure reloc externs)
	(create-heaps-and-tables)
      (if (file-exists? temp-dir)
	  (if (file-directory? temp-dir)
	      (with-cwd temp-dir
			(map delete-file (directory-files temp-dir #t)))
	      (delete-file temp-dir)))
      (create-directory temp-dir #o755 #t)
      (with-cwd temp-dir
		(write-c-header-file pure impure externs infile outfile prefix)
		(write-c-image pure impure reloc externs prefix)
		(write-main-c-file start reloc prefix)
		(compile-c-files cc-command prefix))
      (archive-files ar-command outfile prefix)
      )))


(define debug #f)

(define (vm-string->string x)
  (cond ((vm-string? x)
	 (let ((len (vm-string-length x)))
	   (let loop ((i 0) 
		      (l '()))
	     (cond ((= i len) 
		    (list->string (reverse l)))
		   (else
		    (loop (+ i 1) (cons (vm-string-ref x i) l)))))))
	(else
	 (message x " is not a vm-string"))))

(define (read-heap-image infile)
  (let ((bytes (file-info:size (file-info infile))))
    (init (inexact->exact (floor (* 1.1 bytes))) infile)))
; XXX need little extra space for find-all-xs

(define (create-heaps-and-tables)
  (let* ((n       (nchunks))
	 (  pure  (make-vector n))
	 (impure  (make-vector n))
	 (reloc   (make-vector n))
	 (externs (make-table   )))
    ;; initialize to blanks
    (let loop ((i 0))
      (cond ((not (= i n))
	     (vector-set!   pure i (make-heap ))
	     (vector-set! impure i (make-heap ))
	     (vector-set!  reloc i (make-table))
	     (loop (+ i 1)))))
    (scsh-for-each-stored-object
     (lambda (chunk)
       (format #t "Reading chunk number ~s" chunk))
     (lambda (chunk x len)
       (if debug
	   (write x))
       (let* ((mutable (mutable? x))
	      (heap (vector-ref (if mutable impure pure) chunk)))
	 (table-set! (vector-ref reloc chunk) x (heap:length heap))
	 (set-heap:objects heap (cons x (heap:objects heap)))
	 (set-heap:length  heap (+ len  (heap:length  heap)))
	 (cond (debug
		(display (if mutable "   mutable " " immutable "))
		(cond ((d-vector? x)  (display " d-vector"))
		      ((vm-string? x) (display "vm-string"))
		      (else           (display " b-vector")))
		(let ((m (heap:length (vector-ref impure chunk)))
		      (i (heap:length (vector-ref   pure chunk))))
		  (message " m" m "+i" i "=" (+ m i))))))
       (if (= (header-type (stob-header x)) (enum stob external))
	   (table-set! externs 
		       (external-value x) 
		       (vm-string->string (external-name x))))
       )
     (lambda (chunk) 
       (newline)))
    (let loop ((i 0))
      (cond ((not (= i n))
	     (let ((p (vector-ref   pure i))
		   (i (vector-ref impure i)))
	       (set-heap:objects p (reverse (heap:objects p)))
	       (set-heap:objects i (reverse (heap:objects i))))
	     (loop (+ i 1)))))
    (values pure impure reloc externs)))

(define (write-c-header-file pure impure externs infile outfile prefix)
  (message "Writing header file")
  (call-with-output-file (string-append prefix ".h")
    (lambda (port)
      (format port "/* Static Heap File Automatically Generated~%")
      (format port " * by   scsh/static.scm~%")
      (format port " * from ~a~%" infile)
      (format port " * to   ~a~%" outfile)
      (format port " */~%")
      (let ((n (nchunks)))
	(do ((i 0 (+ i 1)))
	    ((= i n))
	  (format port "extern const long p~s[~s];~%" i 
		  (quotient (heap:length (vector-ref   pure i)) 4)))
	(do ((i 0 (+ i 1)))
	    ((= i n))
	  (format port "extern long i~s[~s];~%" i
		  (quotient (heap:length (vector-ref impure i)) 4))))
      (table-walk
       (lambda (address name)
	 (format port "const extern ~a();~%" name))
       externs)
      )))

(define (d-vector-for-each proc d-vector)
  (do ((i 0 (+ i 1)))
      ((>= i (d-vector-length d-vector)))
    (proc (d-vector-ref d-vector i))))

(define (write-c-image pure impure reloc externs prefix)
  (message "Writing   pure c files")
  (scsh-write-c-image   pure "p" "const " reloc externs prefix)
  (message "Writing impure c files")
  (scsh-write-c-image impure "i" ""       reloc externs prefix))

(define (scsh-write-c-image heap name const reloc externs prefix)
  (let ((n (nchunks)))
    (let chunk-loop ((c 0))
      (cond ((not (= c n))
	     (format #t "Writing ~a-~a~s.c~%" prefix name c)
	     (call-with-output-file 
		 (format #f "~a-~a~s.c" prefix name c)
	       (lambda (port)	     
		 (format port "#include \"~a.h\"~%" prefix)
		 (format port "~a long ~a~s[]={~%" const name c)
		 (let ((heap (vector-ref heap c)))
		   (let heap-loop ((l (heap:objects heap)))
		     (cond ((not (null? l))
			    (scsh-emit-initializer (car l) reloc externs port)
			    (heap-loop (cdr l))))))
		 (display "};" port)
		 (newline port)))
	     (chunk-loop (+ 1 c)))))))

(define (write-main-c-file start reloc prefix)
  (let ((n (nchunks)))
    (call-with-output-file (string-append prefix ".c")
      (lambda (port)
	(format port "#include \"~a.h\"~%" prefix)
	(format port "const long p_count = ~s;~%" n)
	(format port "const long i_count = ~s;~%" n)
	    
	(format port "const long * const p_areas[~s] = {" n)
	(do ((i 0 (+ i 1)))
	    ((= i n))
	  (format port "(const long *) &p~s, " i))
	(format port "};~%")

	(format port "long * const i_areas[~s] = {" n)
	(do ((i 0 (+ i 1)))
	    ((= i n))
	  (format port "(long *) &i~s, " i))
	(format port "};~%")

	(format port "const long p_sizes[~s] = {" n)
	(do ((i 0 (+ i 1)))
	    ((= i n))
	  (format port "sizeof(p~s), " i))
	(format port "};~%")

	(format port "const long i_sizes[~s] = {" n)
	(do ((i 0 (+ i 1)))
	    ((= i n))
	  (format port "sizeof(i~s), " i))
	(format port "};~%")

	(display "const long entry = " port)
	(scsh-emit-descriptor start reloc port)
	(write-char #\; port)
	(newline port)))))

(define (compile-c-files cc-command prefix)
  (let ((n (nchunks))
	(cc (line->list cc-command)))
    (message "Compiling main C file")
    (run (,@(append cc (list (format #f "~a.c" prefix)))))
    (do ((i 0 (+ i 1)))
	((= i n))
      (message "Compiling C file for   pure chunk " i)
      (run (,@(append cc 
		      (list (format #f "~a-p~s.c" prefix i)))))
      (message "Compiling C file for impure chunk " i)
      (run (,@(append cc 
		      (list (format #f "~a-i~s.c" prefix i))))))))

(define (archive-files ar-command outfile prefix)
  (let ((n (nchunks))
	(ar (line->list ar-command)))
    (message "Archiving object files")
    (run (,@(append 
	     ar
	     (cons 
	      outfile
	      (let loop ((i 0)
			 (l '()))
		(cond ((not (= i n))
		       (loop (+ i 1)
			     (cons 
			      (format #f "~a-i~s.o" prefix i)
			      (cons
			       (format #f "~a-p~s.o" prefix i)
			       l))))
		      (else 
		       (reverse 		
			(cons 
			 (string-append prefix ".o")
			 l)))))))))))

(define (scsh-emit-initializer x reloc externs port)
  (write-hex port (stob-header x))
  (cond ((d-vector? x)
	 (scsh-emit-d-vector-initializer x reloc port))
	((vm-string? x)
	 (scsh-emit-vm-string-initializer x port))
	(else
	 (scsh-emit-b-vector-initializer x reloc externs port)))
  (if *comments?*
      (begin (display " /* " port)
	     (writex x port)
	     (display " */" port)))
  (newline port))


(define (scsh-emit-d-vector-initializer x reloc port)
  (let ((len (d-vector-length x)))
    (do ((i 0 (+ i 1)))
	((= i len))
      (scsh-emit-descriptor (d-vector-ref x i) reloc port)
      (write-char #\, port))))

(define (scsh-emit-vm-string-initializer x port)
  (let* ((len (vm-string-length x))	; end is jawilson style hack
	 (end (- (cells->bytes (bytes->cells (+ len 1))) 4)))
    (do ((i 0 (+ i 4)))
	((= i end) 
	 (case (- len end)
	   ((0)
	    (write-hex port 0))
	   ((1)
	    (write-hex
	     port
	     (net-to-host-32 (arithmetic-shift 
		     (char->ascii (vm-string-ref x i)) 24))))
	   ((2)
	    (write-hex 
	     port
	     (net-to-host-32 
	      (bitwise-ior
	       (arithmetic-shift
		(char->ascii (vm-string-ref x i))       24)
	       (arithmetic-shift
		(char->ascii (vm-string-ref x (+ i 1))) 16)))))
	   ((3)
	    (write-hex
	     port
	     (net-to-host-32
	      (bitwise-ior
	       (bitwise-ior
		(arithmetic-shift 
		 (char->ascii (vm-string-ref x i))       24)
		(arithmetic-shift 
		 (char->ascii (vm-string-ref x (+ i 1))) 16))
	       (arithmetic-shift  
		(char->ascii (vm-string-ref x (+ i 2)))  8)))))))
      (write-hex port
		 (net-to-host-32 (bitwise-ior
			 (bitwise-ior
			  (arithmetic-shift 
			   (char->ascii (vm-string-ref x i))       24)
			  (arithmetic-shift 
			   (char->ascii (vm-string-ref x (+ i 1))) 16))
			 (bitwise-ior
			  (arithmetic-shift 
			   (char->ascii (vm-string-ref x (+ i 2)))  8)
			  (char->ascii  (vm-string-ref x (+ i 3))))))
		 ))))

(define (scsh-emit-b-vector-initializer x reloc externs port)
  (cond ((and (code-vector? x)
	      (table-ref externs x)) =>
	      (lambda (name)
		(format port "(long) *~a," name)))
	(else 
	 (let* ((len (b-vector-length x)) ;end is jawilson style hack
		(end (- (cells->bytes (bytes->cells (+ len 1))) 4)))
	   (do ((i 0 (+ i 4)))
	       ((= i end)
		(case (- len end)
		  ((1)
		   (write-hex
		    port
		    (net-to-host-32 (arithmetic-shift (b-vector-ref x i) 24))))
		  ((2)
		   (write-hex 
		    port
		    (net-to-host-32
		     (bitwise-ior
		      (arithmetic-shift (b-vector-ref x i)       24)
		      (arithmetic-shift (b-vector-ref x (+ i 1)) 16)))))
		  ((3)
		   (write-hex
		    port
		    (net-to-host-32
		     (bitwise-ior
		      (bitwise-ior
		       (arithmetic-shift (b-vector-ref x i)       24)
		       (arithmetic-shift (b-vector-ref x (+ i 1)) 16))
		      (arithmetic-shift  (b-vector-ref x (+ i 2))  8)))
		    ))))
	     (write-hex 
	      port
	      (net-to-host-32 (bitwise-ior
		      (bitwise-ior
		       (arithmetic-shift (b-vector-ref x i)       24)
		       (arithmetic-shift (b-vector-ref x (+ i 1)) 16))
		      (bitwise-ior
		       (arithmetic-shift (b-vector-ref x (+ i 2))  8)
		       (b-vector-ref x (+ i 3))))))))
	 )))

(define (scsh-emit-descriptor x reloc port)
  (if (stob? x)
      (let ((n (chunk-number x)))
	(display "(long)(&" port)
	(if (immutable? x)
	    (display "p" port)
	    (display "i" port))
	(display n port)
	(display "[" port)
	(display (quotient (table-ref (vector-ref reloc n) x) 4) port)
	(display "])+7" port))
      (format port 
	      (if (negative? x) "-0x~a" "0x~a")
	      (number->string (abs x) 16))))

(define (scsh-for-each-stored-object chunk-start proc chunk-end)
  (let ((limit (heap-pointer)))
    (let chunk-loop ((addr (newspace-begin))
		     (i 0)
		     (chunk (+ (newspace-begin) *chunk-size*)))
      (if (addr< addr limit)
	  (begin (chunk-start i)
		 (let loop ((addr addr))
		   (if (and (addr< addr limit)
			    (addr< addr chunk))
		       (let* ((d   (fetch addr))
			      (len (addr1+ (header-a-units d))))
			 (if (not (header? d))
			     (warn "heap is in an inconsistent state" d))
			 (proc i (address->stob-descriptor (addr1+ addr)) len)
			 (loop (addr+ addr len)))
		       (begin (chunk-end i)
			      (chunk-loop addr
					  (+ i 1)
					  (+ chunk *chunk-size*))))))))))

(define (write-hex port x) 
  (format port 
	  (if (negative? x) "-0x~a," "0x~a,")
	  (number->string (abs x) 16)))

;; takes a string and break it into a list at whitespace
;; rewrite using scsh stuff?
(define (line->list line)
  (let ((len (string-length line)))
    (let loop ((start 0)
	       (end 0)
	       (l '()))
      (cond ((>= end len)
	     (if (= start end)
		 l
		 (append l (list (substring line start end)))))
	    ((and (= start end)
		  (or (char=? (string-ref line start) (ascii->char 32))
		      (char=? (string-ref line start) (ascii->char 9))))
	     (loop (+ 1 start) 
		   (+ 1 end) 
		   l))
	    ((or (char=? (string-ref line end) (ascii->char 32))
		 (char=? (string-ref line end) (ascii->char 9)))
	     (loop (+ 1 end) 
		   (+ 1 end) 
		   (append l (list (substring line start end)))))
	    ((< end len)
	     (loop start 
		   (+ 1 end)
		   l))
	    (else (error "unexpected case in line->list"))))))

;;;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
;;; Debugging
;;;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

(define (bin n)
  (number->string n 2))

(define (oct n)
  (number->string n 8))

(define (dec n)
  (number->string n 10))

(define (hex n)
  (number->string n 16))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; For example:
;   (do-it 100000 "~/s48/debug/little.image" "little-heap.c")
;
; The first argument to do-it should be somewhat larger than the size,
; in bytes, of the image file to be converted (which you can obtain with
; "ls -l").
;
; If the image contains 0-length stored objects, then the .c file will
; have to be compiled by gcc, since 0-length arrays aren't allowed in
; ANSI C.  This wouldn't be difficult to work around.

(define *comments?* #f)

; 800,000 bytes => 200,000 words => at least 100,000 objects
;   50 chunks => 16,000 bytes per chunk => 2,000 objects per chunk
(define *chunk-size* 10000)

(define (do-it bytes infile outfile)
  (let ((start (init bytes infile)))
    (call-with-output-file outfile
      (lambda (port)
	(format port "#define D(x) (long)(&x)+7~%")
	(format port "#define H unsigned long~%")
	(emit-area-declarations "p" immutable? "const " port)
	(emit-area-declarations "i" mutable? "" port)
	(emit-area-initializers "p" immutable? "const " port)
	(emit-area-initializers "i" mutable? "" port)
	(display "const long entry = " port)
	(emit-descriptor start port)
	(write-char #\; port)
	(newline port)))))

(define (init bytes infile)
  (create-memory (quotient bytes 2) quiescent) ;Output of ls -l
  (initialize-heap (memory-begin) (memory-size))
  (let ((start (read-image infile 0)))
    (message (nchunks)
	     " chunks")
    start))

(define (nchunks) (+ (chunk-number (heap-pointer)) 1))

; emit struct declarations for areas

(define (emit-area-declarations name in-area? const port)
  (for-each-stored-object
   (lambda (chunk)
     (message name chunk " declaration")
     (display "struct " port) (display name port) (display chunk port)
     (display " {" port) (newline port))
   (lambda (x)
     (if (in-area? x)
	 (emit-declaration x port)))
   (lambda (chunk)
     (display "};" port)
     (newline port)
     (display const port)
     (display "extern struct " port) (display name port) (display chunk port)
     (write-char #\space port) (display name port) (display chunk port)
     (write-char #\; port) (newline port)
     chunk)))

(define (emit-declaration x port)
  (display "  H x" port)
  (writex x port)
  (cond ((d-vector? x)
	 (display "; long d" port)
	 (writex x port)
	 (write-char #\[ port)
	 (write (d-vector-length x) port))
	((vm-string? x)
	 (display "; char d" port)
	 (writex x port)
	 (write-char #\[ port)
	 ;; Ensure alignment (thanks Ian)
	 (write (cells->bytes (bytes->cells (b-vector-length x)))
		port))
	(else
	 (display "; unsigned char d" port)
	 (writex x port)
	 (write-char #\[ port)
	 ;; Ensure alignment
	 (write (cells->bytes (bytes->cells (b-vector-length x)))
		port)))
  (display "];" port)
  (if *comments?*
      (begin (display " /* " port)
	     (display (enumerand->name (stob-type x) stob) port)
	     (display " */" port)))
  (newline port))

; Emit initializers for areas

(define (emit-area-initializers name in-area? const port)
  (for-each-stored-object
   (lambda (chunk)
     (message name chunk " initializer")

     (display const port)
     (display "struct " port) (display name port) (write chunk port)
     (write-char #\space port) (display name port) (write chunk port)
     (display " =" port) (newline port)

     (write-char #\{ port) (newline port))
   (lambda (x)
     (if (in-area? x)
	 (emit-initializer x port)))
   (lambda (chunk)
     (display "};" port) (newline port)))

  (let ((n (nchunks)))
    (format port "const long ~a_count = ~s;~%" name n)
    (format port "~a long * const ~a_areas[~s] = {" const name n)
    (do ((i 0 (+ i 1)))
	((= i n))
      (format port "(~a long *)&~a~s, " const name i))
    (format port "};~%const long ~a_sizes[~s] = {" name n)
    (do ((i 0 (+ i 1)))
	((= i n))
      (format port "sizeof(~a~s), " name i))
    (format port "};~%")))


(define (message . stuff)
  (for-each display stuff) (newline))

(define (emit-initializer x port)
  (display "  " port)
  (write (stob-header x) port)
  (write-char #\, port)
  (cond ((d-vector? x)
	 (emit-d-vector-initializer x port))
	((vm-string? x)
	 (write-char #\" port)
	 (let ((len (vm-string-length x)))
	   (do ((i 0 (+ i 1)))
	       ((= i len) (write-char #\" port))
	     (let ((c (vm-string-ref x i)))
	       (cond ((or (char=? c #\") (char=? c #\\))
		      (write-char #\\ port))
		     ((char=? c #\newline)
		      (display "\\n\\" port)))
	       (write-char c port)))))
	(else
	 (write-char #\{ port)
	 (let ((len (b-vector-length x)))
	   (do ((i 0 (+ i 1)))
	       ((= i len) (write-char #\} port))
	     (write (b-vector-ref x i) port)
	     (write-char #\, port)))))
  (write-char #\, port)
  (if *comments?*
      (begin (display " /* " port)
	     (writex x port)
	     (display " */" port)))
  (newline port))

(define (emit-d-vector-initializer x port)
  (write-char #\{ port)
  (let ((len (d-vector-length x)))
    (do ((i 0 (+ i 1)))
	((= i len) (write-char #\} port))
      (emit-descriptor (d-vector-ref x i) port)
      (write-char #\, port))))

(define (emit-descriptor x port)
  (if (stob? x)
      (begin (if (immutable? x)
		 (display "D(p" port)
		 (display "D(i" port))
	     (display (chunk-number x) port)
	     (display ".x" port)
	     (writex x port)
	     (write-char #\) port))
      (write x port)))


; Foo

(define (writex x port)
  (write (quotient (- (- x (memory-begin)) 7) 4) port))

(define (chunk-number x)
  (quotient (- (- x (memory-begin)) 7) *chunk-size*))


; Image traversal utility

(define (for-each-stored-object chunk-start proc chunk-end)
  (let ((limit (heap-pointer)))
    (let chunk-loop ((addr (newspace-begin))
		     (i 0)
		     (chunk (+ (newspace-begin) *chunk-size*)))
      (if (addr< addr limit)
	  (begin (chunk-start i)
		 (let loop ((addr addr))
		   (if (and (addr< addr limit)
			    (addr< addr chunk))
		       (let ((d (fetch addr)))
			 (if (not (header? d))
			     (warn "heap is in an inconsistent state" d))
			 (proc (address->stob-descriptor (addr1+ addr)))
			 (loop (addr1+ (addr+ addr (header-a-units d)))))
		       (begin (chunk-end i)
			      (chunk-loop addr
					  (+ i 1)
					  (+ chunk *chunk-size*))))))))))

(define (mutable? x) (not (immutable? x)))