; -*- 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)))))