; -*- Mode: Scheme; Syntax: Scheme; Package: Scheme; -*-

; This is file interp.scm.

; Interpreter state

(define *template*           (unassigned))
(define *code-pointer*       (unassigned)) ; pointer to next instruction byte
(define *nargs*              (unassigned))
(define *val*                (unassigned))
(define *enabled-interrupts* (unassigned))
(define *exception-handler*  (unassigned))
(define *interrupt-handlers* (unassigned))
(define *dynamic-state*      (unassigned))

; Miscellaneous registers

(define *pending-interrupts*       (unassigned))
(define *interrupt-template*       (unassigned))

(define (clear-registers)
  (reset-stack-pointer)
  (reserve-exception-space exception-frame-size universal-key)
  (set-current-env! unspecific)
  (set-template! *interrupt-template*           ; has to be some template
		 (enter-fixnum 0))
  (set! *nargs*              0)                 ; interpreter regs
  (set! *val*                unspecific)
  (set! *dynamic-state*      null)
  (set! *exception-handler*  null)
  (set! *interrupt-handlers* null)
  (set! *enabled-interrupts* 0)

  (set! *pending-interrupts* 0)
  unspecific)

(define (trace-registers)
  (let ((pc (code-pointer->pc *code-pointer* *template*)))
    (set-template! (trace-value *template*) pc))
  (set! *val*                (trace-value *val*))
  (set! *dynamic-state*      (trace-value *dynamic-state*))
  (set! *exception-handler*  (trace-value *exception-handler*))
  (set! *interrupt-handlers* (trace-value *interrupt-handlers*))
  (set! *interrupt-template* (trace-value *interrupt-template*))
  (set-current-env! (trace-value (current-env)))
  (trace-stack trace-locations))

(define (set-template! tem pc)
  (set! *template* tem)
  (set! *code-pointer* (addr+ (address-after-header (template-code tem))
			      (extract-fixnum pc))))

(define (code-pointer->pc pointer template)
  (enter-fixnum (addr- pointer
                       (address-after-header (template-code template)))))

(define (current-pc)
  (code-pointer->pc *code-pointer* *template*))

(define (initialize-interpreter)          ;Used only at startup
  (set! *interrupt-template*
	(make-template-containing-ops (enum op ignore-values)
				      (enum op return-from-interrupt))))

(define initial-interpreter-heap-space (op-template-size 2))

; Continuations

(define (push-continuation! code-pointer size key)
  (let ((pc (code-pointer->pc code-pointer *template*)))
    (push-continuation-on-stack *template* pc size key)))

(define (push-exception-continuation!)
  (let ((key (allow-exception-consing exception-frame-size)))
    (push-continuation! *code-pointer* (arguments-on-stack) key)))

(define (pop-continuation!)
  (pop-continuation-from-stack set-template!))

; Instruction stream access

(define (this-byte)
  (fetch-byte *code-pointer*))

(define (next-byte)
  (let ((b (this-byte)))
    (set! *code-pointer* (addr+ *code-pointer* 1))
    b))

(define (previous-byte)  ;probably not necessary
  (set! *code-pointer* (addr+ *code-pointer* -1)))

(define (next-offset)
  (let ((high (next-byte)))
    (adjoin-bits high (next-byte) bits-used-per-byte)))

(define (nth-offset n)
  (set! *code-pointer* (addr+ *code-pointer* (* n 2)))
  (next-offset))

(define (next-literal)
  (template-ref *template* (next-byte)))

; Return the current op-code.  CODE-ARGS is the number of argument bytes that
; have been used.

(define (current-opcode code-args)
  (fetch-byte (addr- *code-pointer* (+ code-args 1))))

; Different ways to call the GC.

(define (ensure-space space)
  (maybe-ensure-space-saving-temp space (enter-fixnum 0)
    (lambda (okay? key temp)
      (if (not okay?)
	  (error "Scheme48 heap overflow"))
      key)))

(define (maybe-ensure-space-saving-temp space temp cont)
  (if (available? space)
      (cont #t (preallocate-space space) temp)
      (let ((temp (collect-saving-temp temp)))
	(cont (available? space) 0 temp))))

; Actual call to GC

(define *minimum-recovered-space* 0)

(define (collect)
  (collect-saving-temp (enter-fixnum 0)))

(define (collect-saving-temp value)
  (begin-collection)
  (trace-registers)
  (trace-impure-areas)
  (let ((value (trace-value value)))
    (do-gc)
    (end-collection)
    (close-untraced-ports!)
    (if (not (available? *minimum-recovered-space*))
	(note-interrupt! (enum interrupt memory-shortage)))
    value))

; INTERPRET is the main instruction dispatch for the interpreter.

;(define trace-instructions? #f)

(define (interpret)
;  (if trace-instructions?
;      (write-instruction *template* (extract-fixnum (current-pc))))
  (let ((op-code (next-byte)))
    ((vector-ref opcode-dispatch op-code))))

;;; Opcodes

(define (uuo) (goto raise-exception 0))

(define opcode-dispatch (make-vector op-count))

(vector+length-fill! opcode-dispatch op-count uuo)

;(define (define-opcode opcode tag)
;  (vector-set! opcode-dispatch opcode tag))

(define-syntax define-opcode
  (syntax-rules ()
    ((define-opcode op-name body ...)
     (vector-set! opcode-dispatch (enum op op-name) (lambda () body ...)))))

; Check number of arguments

(define-opcode check-nargs=
  (if (= *nargs* (next-byte))
      (goto interpret)
      (goto application-exception 1)))

(define-opcode check-nargs>=
  (if (>= *nargs* (next-byte))
      (goto interpret)
      (goto application-exception 1)))

(define-opcode nargs
  (set! *val* (enter-fixnum *nargs*))
  (goto interpret))

; Pop off all arguments into a list and raise and exception.  *val* is the
; procedure being called.  BYTE-ARGS is the number of bytes of the instruction
; stream that the instruction has consumed.

(define (application-exception byte-args)
  (let ((args (if (= *nargs* arg-stack-overflow-nargs)
		  (pop-args-list (pop) maximum-stack-args)
		  (pop-args-list null *nargs*))))
    (goto raise-exception-no-cont2 byte-args *val* args)))

(define (pop-args-list start count)
  (let ((key (ensure-space (* vm-pair-size *nargs*))))
    (do ((args start (vm-cons (pop) args key))
	 (count count (- count 1)))
	((<= count 0)
	 args))))

;  Create a list to hold all but MIN-ARGS of the available arguments and put
; it in *val*.
;  If *nargs* is args-stack-overflow-nargs the top of the stack is a list of
; arguments and not just the last argument.

(define-opcode make-rest-list
  (let* ((min-args (next-byte))
	 (args (if (= *nargs* arg-stack-overflow-nargs)
		   (pop-args-list (pop) (- (- *nargs* 1) min-args))
		   (pop-args-list null  (- *nargs* min-args)))))
    (set! *val* args)
    (set! *nargs* (+ min-args 1))
    (goto interpret)))

; Environment creation
;  The MAKE-ENV instruction adds a env to the local environment.
; It pops values off the stack and stores them into the new env.

(define-opcode make-env
  (let ((key (ensure-stack-space (stack-env-space (this-byte)) ensure-space)))
    (pop-args-into-env (next-byte) key)
    (goto interpret)))

; The above with the environment in the heap and not on the stack.

(define-opcode make-heap-env
  (let ((key (ensure-space (heap-env-space (this-byte)))))
    (pop-args-into-heap-env (next-byte) key)
    (goto interpret)))

(define-opcode pop-env
  (set-current-env! (env-parent (current-env)))
  (goto interpret))

; Literals

(define-opcode literal    ;Load a literal into *val*.
  (set! *val* (next-literal))
  (goto interpret))

; Local variable access and assignment

(define-opcode local      ;Load value of a local.
  (goto finish-local (env-back (current-env) (next-byte)) 2))

(define-opcode local0
  (goto finish-local (current-env) 1))

(define-opcode local1
  (goto finish-local (env-parent (current-env)) 1))

(define-opcode local2
  (goto finish-local (env-parent (env-parent (current-env))) 1))

(define (finish-local env arg-count)
  (set! *val* (env-ref env (next-byte)))
  (cond ((not (vm-eq? *val* unassigned-marker))
	 (goto interpret))
	(else
	 (goto raise-exception arg-count))))

(define-opcode set-local!
  (let ((back (next-byte)))
    (env-set! (env-back (current-env) back)
              (next-byte)
              *val*)
    (set! *val* unspecific)
    (goto interpret)))

; Global variable access

(define-opcode global        ;Load a global variable.
  (let ((location (next-literal)))
    (set! *val* (contents location))
    (cond ((undefined? *val*)           ;unbound or unassigned
	   (goto raise-exception1 1 location))
          (else
           (goto interpret)))))

(define-opcode set-global!
  (let ((location (next-literal)))
    (cond ((vm-eq? (contents location) unbound-marker)
           (goto raise-exception2 1 location *val*))
          (else
           (set-contents! location *val*)
           (set! *val* unspecific)
           (goto interpret)))))

; Stack operation

(define-opcode push       ;Push *val* onto the stack.
  (push *val*)
  (goto interpret))

(define-opcode pop       ;Pop *val* from the stack.
  (set! *val* (pop))
  (goto interpret))

(define-opcode stack-ref
  (set! *val* (stack-ref (next-byte)))
  (goto interpret))

(define-opcode stack-set!
  (stack-set! (next-byte) *val*)
  (goto interpret))

; LAMBDA

(define-opcode closure
  (let ((env (preserve-current-env (ensure-space (current-env-size))))
	(key (ensure-space closure-size)))
    (set! *val* (make-closure (next-literal) env key))
    (goto interpret)))

; Procedure call

(define-opcode call
  (set! *nargs* (this-byte))
  (goto perform-application 0))

; Same as op/call except that the arguments are moved to just above the
; current continuation before the call is made.  For non-tail calls and some
; tail-calls the arguments will already be there.

(define-opcode move-args-and-call
  (set! *nargs* (this-byte))
  (move-args-above-cont! *nargs*)
  (goto perform-application 0))

(define-opcode goto-template
  (set-template! (next-literal) 0)
  (goto interpret))

(define-opcode call-template
  (set! *nargs* (next-byte))  ; needed in case of interrupts
  (set-template! (next-literal) 0)
  (goto poll-for-interrupts))

; Continuation creation and invocation

(define-opcode make-cont   ;Start a non-tail call.
  (let* ((offset (next-offset))
	 (size (next-byte))
	 (key (ensure-stack-space stack-continuation-size ensure-space)))
    (push-continuation! (addr+ *code-pointer* offset) size key)
    (goto interpret)))

(define-opcode return            ;Invoke the continuation.
  (pop-continuation!)
  (goto interpret))

; The arguments are sitting on the stack, with the count in *nargs*.
; This is only used in the closed-compiled version of VALUES.

(define-opcode values
  (goto return-values *nargs*))

; Same as the above, except that the value count is in the instruction stream.
; This is used for in-lining calls to VALUES.

(define-opcode return-values
  (goto return-values (this-byte)))

; NARGS return values are on the stack.  If there is only one value, pop
; it off and do a normal return.  Otherwise, find the actual continuation
; and see if it ignores the values, wants the values, or doesn't know
; anything about them.

(define (return-values nargs)
  (cond ((= nargs 1)
	 (set! *val* (pop))
	 (pop-continuation!)
	 (goto interpret))
	(else
	 (let ((cont (peek-at-current-continuation)))
	   (if (continuation? cont)
	       (goto really-return-values cont nargs)
	       (goto return-exception nargs))))))
	 
; If the next op-code is:
; op/ignore-values - just return, ignoring the return values
; op/call-with-values - remove the continuation containing the consumer
;  (the consumer is the only useful information it contains), and then call
;  the consumer.
; anything else - only one argument was expected so raise an exception.

(define (really-return-values cont nargs)
  (let ((next-op (code-vector-ref (template-code (continuation-template cont))
				  (extract-fixnum (continuation-pc cont)))))
    (cond ((= next-op (enum op ignore-values))
	   (pop-continuation!)
	   (goto interpret))
	  ((= next-op (enum op call-with-values))
	   (skip-current-continuation!)
	   (set! *nargs* nargs)
	   (set! *val* (continuation-ref cont continuation-cells))
	   (goto perform-application 0))
	  (else
	   (goto return-exception nargs)))))

; This would avoid the need for the consumer to be a closure.  It doesn't
; work because the NARGS check (which would be the next instruction to be
; executed) assumes that a closure has just been called.

;(define (do-call-with-values nargs)
;  (cond ((addr= *cont* *bottom-of-stack*)
;	 (restore-from-continuation (continuation-cont *cont*))
;	 (set-continuation-cont! *bottom-of-stack* *cont*)
;	 (set! *cont* *bottom-of-stack*))
;	(else
;	 (restore-from-continuation cont)))
;  (set! *nargs* nargs)
;  (set! *code-pointer* (addr+ *code-pointer* 1)) ; move past (enum op call-with-values
;)  (goto interpret))

(define (return-exception nargs)
  (let ((args (if (= nargs arg-stack-overflow-nargs)
		  (pop-args-list (pop) maximum-stack-args)
		  (pop-args-list null nargs))))
    (goto raise-exception1 0 args)))

; This is executed only if the producer returned exactly one value.

(define-opcode call-with-values
  (let ((consumer (pop)))
    (push *val*)
    (set! *val* consumer)
    (set! *nargs* 1)
    (goto perform-application 0)))

; This is just a marker for the code that handles returns.

(define-opcode ignore-values
  (goto interpret))

; IF

(define-opcode jump-if-false
  (let ((offset (next-offset)))
    (cond ((false? *val*)
           (set! *code-pointer* (addr+ *code-pointer* offset))
           (goto interpret))
          (else
           (goto interpret)))))

; Unconditional jump

(define-opcode jump
  (let ((offset (next-offset)))
    (set! *code-pointer* (addr+ *code-pointer* offset))
    (goto interpret)))

; Computed goto
; Goto index is in *val*, the next byte is the number of offsets specified
; The default is to jump to the instruction following the offsets
; The instruction stream looks like
; op/computed-goto max offset0 offset1 ... offsetmax-1 code-for-default...

(define-opcode computed-goto
  (if (not (fixnum? *val*))
      (goto raise-exception1 0 *val*)
      (let ((max (next-byte))
	    (val (extract-fixnum *val*)))
	(let ((offset (if (and (>= val 0)
			       (< val max))
			  (nth-offset val)
			  (* max 2))))
	  (set! *code-pointer* (addr+ *code-pointer* offset))
	  (goto interpret)))))

; Preserve the current continuation and put it in *val*.

(define-opcode current-cont
  (let ((key (ensure-space (current-continuation-size))))
    (set! *val* (current-continuation key))
    (goto interpret)))

(define-opcode with-continuation
  (set-current-continuation! (pop))
  (set! *nargs* 0)
  (goto perform-application 0))

; only used in the stack underflow template

(define-opcode get-cont-from-heap
  (let ((cont (get-continuation-from-heap)))
    (cond ((not (false? cont))
	   (set-current-continuation! cont)
	   (goto interpret))
	  ((fixnum? *val*)           ; VM returns here
	   (set! *val* (extract-fixnum *val*))
	   (enum return-option exit))
	  (else
	   (reset-stack-pointer)     ; get a real continuation on the stack
	   (goto raise-exception1 0 *val*)))))

; APPLY - pop the procedure off of the stack, push each argument from the list,
; and then call the procedure.  Tne next byte is the number of arguments that
; have already been pushed on the stack.

(define-opcode apply
  (let ((proc (pop)))
    (okay-argument-list
     *val*
     (lambda (length)
       (push-argument-list *val* length (this-byte))
       (set! *val* proc)
       (goto perform-application 0))
     (lambda ()
       (let ((args (pop-args-list null (this-byte))))
	 (goto raise-exception3 0 proc args *val*))))))
       
; If LIST is a proper list (final cdr is null) then OKAY-CONT is called on the
; length of LIST, otherwise LOSE-CONT is called.

(define (okay-argument-list list okay-cont lose-cont)
    (let loop ((fast list) (len 0) (slow list) (move-slow? #f))
      (cond ((vm-eq? null fast)
	     (okay-cont len))
	    ((not (vm-pair? fast))
             (lose-cont))
	    ((not move-slow?)
	     (loop (vm-cdr fast) (+ len 1) slow #t))
	    ((vm-eq? fast slow)
	     (lose-cont))
	    (else
	     (loop (vm-cdr fast) (+ len 1) (vm-cdr slow) #f)))))

; Push ARGS onto the stack.  LENGTH is the length of the arguments,
; STACK-ARGS is the number of arguments already on the stack.
; If the total number of arguments is greater than the allowed amount,
; the list of extras is copies and pushed.

(define (push-argument-list args length stack-args)
  (let* ((nargs (+ length stack-args))
	 (overflow (if (<= nargs maximum-stack-args)
		       0
		       (- nargs maximum-stack-args)))
	 (rest (push-list args (- length overflow))))
    (cond ((= overflow 0)
	   (set! *nargs* nargs))
	  (else
	   (push (copy-arg-list rest overflow))
	   (set! *nargs* arg-stack-overflow-nargs)))))

; push COUNT elements of LIST onto stack, returning the remainder of LIST

(define (push-list list count)
  (do ((i count (- i 1))
       (l list (vm-cdr l)))
      ((<= i 0) l)
    (push (vm-car l))))

; Assumes list is non-null

(define (copy-arg-list list length)
  (push list)
  (let* ((key (ensure-space (* vm-pair-size length)))
	 (list (pop))
	 (res (vm-cons (vm-car list) null key)))
    (do ((l (vm-cdr list) (vm-cdr l))
	 (last res (let ((next (vm-cons (vm-car l) null key)))
		     (vm-set-cdr! last next)
		     next)))
	((vm-eq? null l)
	 res))))

; Miscellaneous primitive procedures

(define-opcode unassigned
  (set! *val* unassigned-marker)
  (goto interpret))

(define-opcode unspecific
  (set! *val* unspecific)
  (goto interpret))

(define-opcode set-exception-handler!
  ;; New exception handler in *val*
  (cond ((not (closure? *val*))
	 (goto raise-exception1 0 *val*))
	(else
	 (set! *exception-handler* *val*)
	 (goto interpret))))

(define-opcode set-interrupt-handlers!
  ;; New interrupt handler vector in *val*
  (cond ((or (not (vm-vector? *val*))
	     (< (vm-vector-length *val*) interrupt-count))
	 (goto raise-exception1 0 *val*))
	(else
	 (set! *interrupt-handlers* *val*)
	 (goto interpret))))

(define-opcode set-enabled-interrupts!
  ;; New interrupt mask as fixnum in *val*
  (let ((temp *enabled-interrupts*))
    (set! *enabled-interrupts* (extract-fixnum *val*))
    (set! *val* (enter-fixnum temp))
    (goto interpret)))

;;; Procedure call
; The CLOSURE? check must come before the interrupt check, as the interrupt
; code assumes that the correct template is in place.  This delays the
; interrupt handling by a few instructions.

(define (perform-application bytes-consumed)
  (cond ((closure? *val*)
         (set-current-env! (closure-env *val*))
         (set-template! (closure-template *val*) 0)
	 (goto poll-for-interrupts))
	(else
	 (goto application-exception bytes-consumed))))

(define (poll-for-interrupts)
  (if (= 0 (bitwise-and *pending-interrupts* *enabled-interrupts*))
      (goto interpret)
      (goto handle-interrupt)))

; Exceptions
; The system reserves enough stack space to allow for an exception at any time.
; If the reserved space is used a gc must be done before the exception handler
; is called.

(define exception-frame-size      ; a continuation plus up to five arguments
  (+ stack-continuation-size 5))  ; one of which is the opcode

(define (start-exception args)
  (push-exception-continuation!)
  (push (enter-fixnum (current-opcode args))))

(define (raise-exception args)
  (start-exception args)
  (goto raise 0))

(define (raise-exception1 args a1)
  (start-exception args)
  (push a1)
  (goto raise 1))

(define (raise-exception2 args a1 a2)
  (start-exception args)
  (push a1)
  (push a2)
  (goto raise 2))

(define (raise-exception-no-cont2 args a1 a2)
  (push (enter-fixnum (current-opcode args)))
  (push a1)
  (push a2)
  (goto raise 2))

(define (raise-exception3 args a1 a2 a3)
  (start-exception args)
  (push a1)
  (push a2)
  (push a3)
  (goto raise 3))

(define (raise-exception4 args a1 a2 a3 a4)
  (start-exception args)
  (push a1)
  (push a2)
  (push a3)
  (push a4)
  (goto raise 4))

(define no-exceptions? #f)

(define (raise nargs)
;  (if no-exceptions?
;      (breakpoint "exception check" nargs))
  (set! *nargs* (+ nargs 1))   ; extra arg is the op-code
  (set! *val* *exception-handler*)
  (if (not (closure? *val*))
      (error "exception handler is not a closure"))
  (reserve-exception-space exception-frame-size
			   (ensure-stack-space exception-frame-size
					       ensure-space))
  (goto perform-application 0))

; Interrupts

; This is only called when a template is about to be jumped to, so the only
; values that must be saved are *env*, *template*, *nargs*, and
; *enabled-interrupts*.

(define (handle-interrupt)
  (let ((key (ensure-stack-space stack-continuation-size ensure-space))
	(interrupt (get-highest-priority-interrupt!)))
    (push *val*)       ; may be needed for nargs exception
    (push *template*)
    (push (current-env))
    (push (enter-fixnum *nargs*))
    (push (enter-fixnum *enabled-interrupts*))
    (set-template! *interrupt-template* (enter-fixnum 0))
    (push-continuation! *code-pointer* (+ *nargs* 5) key)
    (push (enter-fixnum *enabled-interrupts*))
    (set! *nargs* 1)
    (if (not (vm-vector? *interrupt-handlers*))
	(error "interrupt handler is not a vector"))
    (set! *val* (vm-vector-ref *interrupt-handlers* interrupt))
    (set! *enabled-interrupts* 0)   ;Disable all interrupts
    (if (not (closure? *val*))
        (error "interrupt handler is not a closure" interrupt))
    (goto perform-application 0)))

(define-opcode return-from-interrupt
  (set! *enabled-interrupts* (extract-fixnum (pop)))
  (set! *nargs*              (extract-fixnum (pop)))
  (set-current-env! (pop))
  (set-template!    (pop) 0)
  (set! *val*       (pop))
  (goto interpret))

(define (get-highest-priority-interrupt!)
  (let ((n (bitwise-and *pending-interrupts* *enabled-interrupts*)))
    (let loop ((i 0) (m 1))
      (cond ((= 0 (bitwise-and n m))
	     (loop (+ i 1) (* m 2)))
	    (else
	     (set! *pending-interrupts* (bitwise-and n (bitwise-not m)))
	     i)))))
	 
(define (note-interrupt! interrupt)
  (set! *pending-interrupts*
	(bitwise-ior *pending-interrupts*
		     (ashl 1 interrupt))))

(define (clear-interrupt! interrupt)
  (set! *pending-interrupts*
	(bitwise-and *pending-interrupts*
		     (bitwise-not (ashl 1 interrupt)))))


