738 lines
22 KiB
Scheme
738 lines
22 KiB
Scheme
|
; -*- 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)))))
|
||
|
|
||
|
|