;;;
;;; bootstrap.lisp
;;;
;;; Initial smalltalk image bootstrap machinery.
;;;

(cl:defpackage :speakeasy
  (:use :cl))
(cl:in-package :speakeasy)

;;; Guaranteed pointers (from the blue book)
;;
;; Some of these are only used in primitive activation, particularly
;; for the UI methods.
;;
;; 0: ???
;; 2: nil
;; 4: False
;; 6: True
;; 8: SchedulerAssociation (the Association in Smalltalk for Processor)
;; 10: ???
;; 12: ???
;; 14: ClassString
;; 16: ClassArray
;; 18: ???
;; 20: ???
;; 22: ClassMethodContext
;; 24: ClassBockContext
;; 26: ClassPoint
;; 28: ClassLargePositiveInteger
;; 30: ???
;; 32: ClassMessage
;; 34: ???
;; 36: ???
;; 38: ???
;; 40: ClassCharacter
;; 42: #doesNotUnderstand:
;; 44: #cannotReturn:
;; 46: ???
;; 48: SpecialSelectors
;; 50: CharacterTable
;; 52: #mustBeBoolean
;;
;; As we are not expecting true ST-80 image compatability, we don't
;; need to treat this as more than a guideline.  Additionally, since
;; we're bootstrapping our own image, we can use variables instead of
;; constants for these values, and just populate them as they are
;; defined.

(defun panic (reason)
  "When in danger, when in doubt: Run in circles, scream and shout!"
  (break reason))

;;;
;;; The object table
;;;

;; An object table entry

(defstruct ote
  class
  data
  pointers
  words
  odd-length)

;; The object table is an array containing information about every
;; object in the system.  It is indexed by (ash oop -1).
;;
;; Each allocated object is represented by an OTE.  Free entries are
;; integers representing a freelist, or NIL representing the end of
;; the freelist.

(defvar *object-table* (make-array #x8000 :initial-element nil))
(defvar *next-free-oop* 0)

;; Initialize the freelist.

(defun clear-object-memory ()
  (setf *next-free-oop* 0)
  (dotimes (i #x7fff)
    (setf (aref *object-table* i) (ash (1+ i) 1))))

;;;
;;; Known OOPs.
;;;

;; "Known" OOPs need placeholder values until their objects have
;; actually been defined.  In some cases, we arrange for them to be
;; defined before use, in others, we backpatch (such as for known
;; classes, as that's an easy fixup).  We will be attempting to avoid
;; backpatching non-class fields, as doing a full image search for a
;; given OOP is more code to write and would of necessity be slower
;; than scanning the class fields of the object table.

(defvar +smalltalk-oop+)
(defvar +nil-oop+)
(defvar +false-oop+)
(defvar +true-oop+)
(defvar +does-not-understand-oop+)
(defvar +cannot-return-oop+)
(defvar +must-be-boolean-oop+)
(defvar +character-table-oop+)

(defvar +array-class-oop+ 'array)
(defvar +symbol-class-oop+ 'symbol)
(defvar +association-class-oop+ 'association)
(defvar +identity-dictionary-class-oop+ 'identity-dictionary)
(defvar +dictionary-class-oop+ 'dictionary)
(defvar +character-class-oop+ 'character)
(defvar +metaclass-class-oop+ 'metaclass)
(defvar +compiled-method-class-oop+ 'compiled-method)

(defun reset-known-oops ()
  (makunbound '+smalltalk-oop+)
  (makunbound '+nil-oop+)
  (makunbound '+false-oop+)
  (makunbound '+true-oop+)
  (makunbound '+does-not-understand-oop+)
  (makunbound '+cannot-return-oop+)
  (makunbound '+must-be-boolean-oop+)
  (makunbound '+character-table-oop+)

  (setf +array-class-oop+ 'array)
  (setf +symbol-class-oop+ 'symbol)
  (setf +association-class-oop+ 'association)
  (setf +identity-dictionary-class-oop+ 'identity-dictionary)
  (setf +dictionary-class-oop+ 'dictionary)
  (setf +character-class-oop+ 'character)
  (setf +metaclass-class-oop+ 'metaclass)
  (setf +compiled-method-class-oop+ 'compiled-method)
  )

;;;
;;; Object allocation and creation.
;;;

(defun allocate-oop ()
  "Obtain an un-allocated OOP for use."
  (prog1
      *next-free-oop*
    (when (not (setf *next-free-oop* (aref *object-table* (ash *next-free-oop* -1))))
      ;;; Oops: Out of OOPs. We really should GC here, but meanwhile...
      (panic "No free OOPs"))))

(defun make-object (oop type length class)
  "Create an OTE for a given OOP, allocating required data space."
  (when (ote-p (aref *object-table* (ash oop -1)))
    (panic "make-object overwriting existing OTE"))
  (setf (aref *object-table* (ash oop -1))
	(make-ote :class class
		  :pointers (eql type :pointers)
		  :words (eql type :words)
		  :odd-length (and (eql type :bytes)
				   (oddp length))
		  :data (make-array (if (eql type :bytes)
					(ash (1+ length) -1)
					length)
				    :element-type '(unsigned-byte 16)
				    :initial-element (if (eql type :pointers)
							 +nil-oop+
							 0)))))

;;;
;;; Object data access.
;;;

(defmacro with-ote ((var oop) &body body)
  ;; FIXME: Should probably validate that oop isn't a SmallInteger.
  `(let ((,var (aref *object-table* (ash ,oop -1))))
     (unless (ote-p ,var) (panic "Accessing non-allocated object"))
     ,@body))

(defun obj-byte (oop index)
  (with-ote (ob oop)
    (ldb (byte 8 (ash (logand index 1) 3))
	 (aref (ote-data ob) (ash index -1)))))

(defun (setf obj-byte) (value oop index)
  (with-ote (ob oop)
    (setf (aref (ote-data ob) (ash index -1))
	  (dpb value
	       (byte 8 (ash (logand index 1) 3))
	       (aref (ote-data ob) (ash index -1)))))
  value)

(defun obj-word (oop index)
  (with-ote (ob oop)
    (aref (ote-data ob) index)))

(defun (setf obj-word) (value oop index)
  (with-ote (ob oop)
    (setf (aref (ote-data ob) index) value)))

;; These next two are hooks to support refcounting.

(defun obj-oop (oop index)
  (with-ote (ob oop)
    (aref (ote-data ob) index)))

(defun (setf obj-oop) (value oop index)
  (with-ote (ob oop)
    (setf (aref (ote-data ob) index) value)))

(defun box-integer (value)
  ;; FIXME: Only works on SmallIntegers, no error checking.
  (logior 1 (ash value 1)))

;;;
;;; Symbol access.
;;;

(defvar *symbol-mapping* (make-hash-table :test 'equal))

(defun find-symbol-in-image (name)
  "Look through all Symbols in the system looking for one matching NAME."
  (dotimes (i #x8000)
    (let ((ob (aref *object-table* i)))
      (when (and (ote-p ob)
		 (eql (ote-class ob) +symbol-class-oop+)
		 (= (length (ote-data ob)) (ash (1+ (length name)) -1))
		 (eql (ote-odd-length ob) (oddp (length name))))
	;; We have an allocated object, that is a symbol, and is the
	;; same length as the name.  Now check we the actual data.
	(loop for j from 0 below (length name)
	      when (/= (obj-byte (ash i 1) j) (char-code (aref name j)))
	      return nil
	      finally (return-from find-symbol-in-image (ash i 1)))))))

(defun create-symbol (name)
  "Create a new Symbol in the system called NAME."
  (let ((oop (allocate-oop)))
    (make-object oop :bytes (length name) +symbol-class-oop+)
    (dotimes (i (length name))
      (setf (obj-byte oop i) (char-code (aref name i))))
    oop))

(defun intern-symbol (name)
  "Find a Symbol for NAME, creating one if necessary."
  (or (gethash name *symbol-mapping*)
      (setf (gethash name *symbol-mapping*)
	    (or (find-symbol-in-image name)
		(create-symbol name)))))

;;;
;;; IdentityDictionary (method dictionary) access.
;;;

;; An IdentityDictionary is supposed to be a subclass of Set.  It has
;; two fixed fields (a count of the number of methods and an Array of
;; the actual methods) and stores the method selectors in its indexed
;; fields.  This is probably one of the "implementation convenience"
;; nastinesses in the Smalltalk-80 class hierarchy, as I can't see how
;; Set would work given the way the method selectors are arranged
;; within the indexed fields.

;; Complexities here include resizing the dictionary when it gets too
;; full, and a good notion of what "too full" is.

(defun create-method-dictionary (&key (size 16))
  (let ((method-array (allocate-oop))
	(dictionary (allocate-oop)))
    (make-object method-array :pointers size +array-class-oop+)
    (make-object dictionary :pointers (+ size 2) +identity-dictionary-class-oop+)
    (setf (obj-oop dictionary 0) (box-integer 0))
    (setf (obj-oop dictionary 1) method-array)
    dictionary))

;; There's supposed to be some sort of hashing logic based on the
;; message selector OOP for finding the method.  In the interests of
;; getting this working, we'll go with a linear scan for now.

;; XXX: Would these be better as a dictionary-method accessor?

(defun lookup-method-in-dictionary (dictionary selector)
  (with-ote (dict dictionary)
    (let* ((data (ote-data dict))
	   (length (- (length data) 2)))
      (loop for i below length
	    when (= (aref data (+ i 2)) selector)
	    return (obj-oop (aref data 1) i)))))

(defun add-method-to-dictionary (dictionary selector method)
  (with-ote (dict dictionary)
    (let* ((data (ote-data dict))
	   (length (- (length data) 2)))
      (loop for i below length
	    when (= (aref data (+ i 2)) selector)
	    do (setf (obj-oop (aref data 1) i) method)
	       (return)
	    finally
	    ;; FIXME: Check for incipient dictionary overflow.
	    ;;(when )
	    (loop for i below length
		  when (= (aref data (+ i 2)) +nil-oop+)
		  do (setf (obj-oop (aref data 1) i) method)
		     (setf (aref data (+ i 2)) selector)
		     (return)))))
  method)

(defun remove-method-from-dictionary (dictionary selector)
  ;; More for completeness sake than anything else.
  (with-ote (dict dictionary)
    (let* ((data (ote-data dict))
	   (length (- (length data) 2)))
      (loop for i below length
	    when (= (aref data (+ i 2)) selector)
	    do (setf (obj-oop (aref data 1) i) +nil-oop+)
	       (setf (aref data (+ i 2)) +nil-oop+)))))

;;;
;;; Dictionary (system dictionary (Smalltalk) and pool variable) access.
;;;

(defun association-key (oop)
  (obj-oop oop 0))

(defun (setf association-key) (value oop)
  (setf (obj-oop oop 0) value))

(defun association-value (oop)
  (obj-oop oop 1))

(defun (setf association-value) (value oop)
  (setf (obj-oop oop 1) value))

(defun make-association (key value)
  (let ((association (allocate-oop)))
    (make-object association :pointers 2 +association-class-oop+)
    (setf (association-key association) key)
    (setf (association-value association) value)
    association))

;; The accessors for a Dictionary are similar to those for an
;; IdentityDictionary, only the key/value pairs are stored in an
;; Association instead of split over the dictionary and an Array of
;; values.  Similar auto-resizing and hashing rules apply (including
;; the bit about not implementing the hashing yet).

(defun create-dictionary ()
  (let ((dictionary (allocate-oop)))
    (make-object dictionary :pointers 16 +dictionary-class-oop+)
    dictionary))

(defun dictionary-association-at (dictionary key)
  (with-ote (ob dictionary)
    (let ((data (ote-data ob)))
      (loop for entry across data
	    when (and (not (= entry +nil-oop+))
		      (= (association-key entry) key))
	    return entry))))

(defun dictionary-at (dictionary key)
  (association-value (dictionary-association-at dictionary key)))

(defun dictionary-add-association (dictionary association)
  ;; We are adding a new Association.  It has already been built, and
  ;; is not a duplicate of an existing entry.
  (with-ote (ob dictionary)
    (let ((data (ote-data ob)))
      (loop for entry across data
	    for index from 0
	    when (= entry +nil-oop+)
	    return (setf (aref data index) association)
	    finally
	    ;; Grow the Dictionary.  Theoretically, we should rehash here.
	    (let ((new-data (make-array (* 2 (length data))
					:element-type '(unsigned-byte 16)
					:initial-element +nil-oop+)))
	      (setf (ote-data ob) new-data)
	      (setf (subseq new-data 0) data)
	      (setf (aref new-data (length data)) association))))))

(defun dictionary-at-put (dictionary key value)
  (let ((entry (dictionary-association-at dictionary key)))
    (if entry
	(setf (association-key entry) value)
	(dictionary-add-association dictionary (make-association key value)))))

;;;
;;; Classes.
;;;

;; As far as the VM is concerned, a class consists of a superclass, a
;; method dictionary and an instance specification (a SmallInteger).
;; This much is defined in class Behavior (three instance variables).

(defconstant +superclass-ofs+ 0)
(defconstant +method-dictionary-ofs+ 1)
(defconstant +instance-spec-ofs+ 2)

;; Layered over this is ClassDescription, which adds names for the
;; class and instance variables, comments, and grouping methods into
;; categories (call it four more instance variables; one for the class
;; name, one for the collection of instance variable names (an
;; Array?), one for the class comment, and one for some structure for
;; grouping the methods).

;; The next layer is Metaclass and Class.

;; As far as instance variabes go, Class has an additional one to hold
;; the class variable pool.

(defconstant +class-pool-ofs+ 7)

;; When a new class is created, the superclass is sent a message
;; indicating that a new subclass is wanted with a given name and
;; certain properties (instance variables, pools, etc.).  The
;; superclass turns around and sends a message to Metaclass asking for
;; a new metaclass to be created for the new subclass.  Metaclass then
;; creates the new metaclass for the new class and sends it a message
;; requesting that it create the new class.  I don't know if this is a
;; spiral dance, an innocent dance, or what, but it's certainly an
;; -intricate- dance.

;; For bootstrapping purposes, we accept two types of value for a
;; superclass.  The first is nil, used as Object's superclass.  The
;; second is a string, which is converted to a symbol OOP and looked
;; up in the system dictionary in order to find the corresponding
;; class OOP.

;; Interestingly, as every class's metaclass's class is supposed to be
;; Metaclass, including Metaclass itself, everything but Object's
;; metaclass being a subclass of Class is covered by the class
;; placeholder fixup mechanism.

(defun define-class (superclass name &key inst-vars class-vars (encoding :pointer))
  (let* ((superclass-oop (if superclass
			     (dictionary-at +smalltalk-oop+
					    (intern-symbol superclass))
			     +nil-oop+))
	 (supermeta-oop (if superclass
			    (ote-class (aref *object-table*
					     (ash superclass-oop -1)))
			    ;; If we have no superclass, this is
			    ;; supposed to be the OOP of Class, but
			    ;; since we are, by definition, defining
			    ;; Object we haven't defined Class yet, so
			    ;; we'll have to backpatch this later.
			    +nil-oop+))
	 (metaclass-oop (allocate-oop))
	 (class-oop (allocate-oop))
	 (superclass-instance-spec (if superclass
				       (obj-oop superclass-oop +instance-spec-ofs+)
				       #x8001))
	 (inherited-vars (ldb (byte 11 1) superclass-instance-spec)))

    (when (and (member encoding '(:variable-byte :variable-word))
	       (or (> inherited-vars 0)
		   inst-vars))
      (panic "non-pointer class definition has instance variables"))

    (make-object metaclass-oop :pointers 7 +metaclass-class-oop+)
    (setf (obj-oop metaclass-oop +superclass-ofs+) supermeta-oop)
    (setf (obj-oop metaclass-oop +method-dictionary-ofs+) (create-method-dictionary))
    (setf (obj-oop metaclass-oop +instance-spec-ofs+) (box-integer (logior #x4000 7)))

    (make-object class-oop :pointers 8 metaclass-oop)
    (setf (obj-oop class-oop +superclass-ofs+) superclass-oop)
    (setf (obj-oop class-oop +method-dictionary-ofs+) (create-method-dictionary))
    (setf (obj-oop class-oop +instance-spec-ofs+)
	  (box-integer (logior
			(ecase encoding
			  (:pointer #x4000)
			  (:variable #x5000)
			  (:variable-byte #x1000)
			  (:variable-word #x3000))
			(+
			 inherited-vars
			 (length inst-vars)))))
    (when class-vars
      (let ((pool (create-dictionary)))
	(setf (obj-oop class-oop +class-pool-ofs+) pool)
	(dolist (var class-vars)
	  (dictionary-at-put pool (intern-symbol var) +nil-oop+))))

    (dictionary-at-put +smalltalk-oop+ (intern-symbol name) class-oop)))

;; Because we need to create objects well before we can create
;; classes, we use symbolic placeholders for some object classes.
;; Once the actual classes are defined we need to fixup the references
;; to the placeholder to point to the actual class instead.

(defun class-fixup (old-class new-class-name)
  "Fixup class references to OLD-CLASS in the object table to point to NEW-CLASS instead."
  (let ((new-class (dictionary-at +smalltalk-oop+
				  (intern-symbol new-class-name))))
    (loop for ob across *object-table*
	  when (and (ote-p ob)
		    (eql old-class (ote-class ob)))
	  do (setf (ote-class ob) new-class))
    new-class))

;;;
;;; Compiled methods.
;;;

;; Compiled methods are the weirdest objects in Smalltalk.  They
;; combine an indexed pointer data area with an indexed byte data area
;; (the only class to do so), and have two "special" locations within
;; the litera area that have special uses.  The last literal slot is
;; used, in methods which use a super-send bytecode, to hold the
;; Association from the system dictionary between the class name and
;; the class.  The next-to-last literal slot holds a header extension
;; word in those methods which require it (those with associated
;; primitive routines or requiring more than four arguments).

;; As laying out methods by hand would be tedious at best, the current
;; thought is to write a compiler in Lisp for bootstrap purposes (if
;; not production use).

;; This is a low-level pure object-creation function.  It doesn't do
;; any hand-holding like setting the method class for methods using
;; super sends, setting header extensions, declarative header
;; creation, setting the method up in a method dictionary, or anything
;; like that.  The only hand-holding it does is to set the literal
;; count of the header from the length of the list of literals passed.
(defun create-method (header literals bytecode)
  (let ((oop (allocate-oop))
	(length (1+ (+ (length literals)
		       (ash (1+ (length bytecode)) -1))))
	(bytecode-start (ash (1+ (length literals)) 1)))

    (make-object oop :pointers length +compiled-method-class-oop+)

    (setf (obj-oop oop 0) (dpb (length literals) (byte 6 1) header))

    (loop for literal in literals
	  for index from 1
	  do (setf (obj-oop oop index) literal))

    (loop for byte across bytecode
	  for index from bytecode-start
	  do (setf (obj-byte oop index) byte))

    oop))

;;;
;;; Building the virtual image.
;;;

(progn
  ;; Reset the object memory and known pointers.
  (clear-object-memory)
  (reset-known-oops)
  (setf *symbol-mapping* (make-hash-table :test 'equal))

  ;; Preset +nil-oop+ so we can run create-dictionary.
  (setf +nil-oop+ 2)

  ;; Smalltalk
  (setf +smalltalk-oop+ (create-dictionary))
  (with-ote (ob +smalltalk-oop+)
    ;; Special case dictionary.
    (setf (ote-class ob) 'system-dictionary))

  ;; nil
  (assert (= +nil-oop+ (allocate-oop)))
  (make-object +nil-oop+ :pointers 0 'undefined-object)

  ;; False
  (setf +false-oop+ (allocate-oop))
  (make-object +false-oop+ :pointers 0 'false)
  
  ;; True
  (setf +true-oop+ (allocate-oop))
  (make-object +true-oop+ :pointers 0 'true)

  ;; Known method selectors.
  (setf +does-not-understand-oop+ (intern-symbol "doesNotUnderstand:"))
  (setf +cannot-return-oop+ (intern-symbol "cannotReturn:"))
  (setf +must-be-boolean-oop+ (intern-symbol "mustBeBoolean"))

  ;; Globals thus far allocated.
  (dictionary-at-put +smalltalk-oop+ (intern-symbol "Smalltalk") +smalltalk-oop+)
  (dictionary-at-put +smalltalk-oop+ (intern-symbol "nil") +nil-oop+)
  (dictionary-at-put +smalltalk-oop+ (intern-symbol "false") +false-oop+)
  (dictionary-at-put +smalltalk-oop+ (intern-symbol "true") +true-oop+)

  ;; Initial class/metaclass hierarchy setup.
  (define-class nil "Object")
  (define-class "Object" "Behavior" :inst-vars '("superclass" "methodDictionary" "instanceSpec"))
  (define-class "Behavior" "ClassDescription" :inst-vars '("name" "comment" "instVars" "methodCategories"))
  (define-class "ClassDescription" "Class" :inst-vars '("classPool"))
  (define-class "ClassDescription" "Metaclass")

  ;; Fix Object class superclass (is nil during creation).
  (with-ote (obj (dictionary-at +smalltalk-oop+ (intern-symbol "Object")))
    (setf (obj-oop (ote-class obj) +superclass-ofs+)
	  (dictionary-at +smalltalk-oop+ (intern-symbol "Class"))))

  ;; Control flow classes.
  (define-class "Object" "Boolean")
  (define-class "Boolean" "False")
  (define-class "Boolean" "True")
  (define-class "Object" "UndefinedObject")

  ;; Numberish things.
  (define-class "Object" "Magnitude")
  (define-class "Magnitude" "Character" :encoding :variable-byte)
  (define-class "Magnitude" "Number")
  (define-class "Number" "Integer")
  (define-class "Integer" "SmallInteger")
  ;; FIXME: Float, Fraction, Date, Time, LargeNegativeInteger and
  ;; LargePositiveInteger go here.
  (define-class "Magnitude" "LookupKey")
  (define-class "LookupKey" "Association" :inst-vars '("key" "value"))

  ;; Collection classes.
  (define-class "Object" "Collection")
  (define-class "Collection" "SequencableCollection")
  (define-class "SequencableCollection" "ArrayedCollection")
  (define-class "ArrayedCollection" "Array" :encoding :variable)
  (define-class "ArrayedCollection" "String" :encoding :variable-byte)
  (define-class "String" "Symbol" :encoding :variable-byte)
  (define-class "Collection" "Set" :encoding :variable :inst-vars '("tally"))
  (define-class "Set" "Dictionary" :encoding :variable)
  (define-class "Dictionary" "IdentityDictionary" :encoding :variable
		:inst-vars '("values"))
  (define-class "Dictionary" "SystemDictionary" :encoding :variable)

  ;; CharacterTable and the Characters.
  (setf +character-table-oop+ (allocate-oop))
  (make-object +character-table-oop+ :pointers 256 +array-class-oop+)

  (loop for char across (concatenate 'string
				     ;; Hopefully all of the important ones.
				     "0123456789"
				     "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
				     "abcdefghijklmnopqrstuvwxyz"
				     "+/\\*~<>=@%|&?!"
				     "[]{}()_^,;$#:"
				     "'\"-.")
	for oop = (allocate-oop)
	do
	(make-object oop :bytes 1 +character-class-oop+)
	(setf (obj-byte oop 0) (char-code char))
	(setf (obj-oop +character-table-oop+ (char-code char)) oop))

  ;; Methods.
  (define-class "Object" "CompiledMethod" :inst-vars '(header) :encoding :variable)

  ;; Snap all placeholder class links.
  (class-fixup 'undefined-object "UndefinedObject")
  (class-fixup 'false "False")
  (class-fixup 'true "True")
  (class-fixup 'system-dictionary "SystemDictionary")

  (setf +array-class-oop+ (class-fixup 'array "Array"))
  (setf +symbol-class-oop+ (class-fixup 'symbol "Symbol"))
  (setf +association-class-oop+ (class-fixup 'association "Association"))
  (setf +identity-dictionary-class-oop+ (class-fixup 'identity-dictionary "IdentityDictionary"))
  (setf +dictionary-class-oop+ (class-fixup 'dictionary "Dictionary"))
  (setf +character-class-oop+ (class-fixup 'character "Character"))
  (setf +metaclass-class-oop+ (class-fixup 'metaclass "Metaclass"))
  (setf +compiled-method-class-oop+ (class-fixup 'compiled-method "CompiledMethod"))

  ;; Initial method load.

  ;; Scheduler setup and initial task.
)

;;; EOF
