scsh-0.6/scheme/vm/call.scm

629 lines
21 KiB
Scheme

; Copyright (c) 1993-1999 by Richard Kelsey and Jonathan Rees. See file COPYING.
; Code to handle the calling conventions.
; *VAL* is the procedure, the arguments are on stack, and the next byte
; is the protocol. This checks that *VAL* is in fact a closure and checks
; for the common case of a non-n-ary procedure that has few arguments.
; The common case is handled directly and all others are passed off to
; PLAIN-PROTOCOL-MATCH.
(define-opcode call
(let ((stack-arg-count (code-byte 0)))
(if (closure? *val*)
(let* ((template (closure-template *val*))
(code (template-code template)))
(if (= stack-arg-count (code-vector-ref code 1))
(begin
(set! *template* template)
(set-current-env! (closure-env *val*))
(set-code-pointer! code 2)
(ensure-default-procedure-space! ensure-space)
(if (pending-interrupt?)
(goto handle-interrupt stack-arg-count)
(goto interpret *code-pointer*)))
(goto plain-protocol-match stack-arg-count)))
(goto application-exception
(enum exception bad-procedure)
stack-arg-count null 0))))
; Two bytes of arguments.
(define-opcode big-call
(goto perform-application (code-offset 0)))
; Call a template instead of a procedure. This is currently only used for
; stringing together the initialization code made by the static linker.
;
; **limitation**: this code only works for templates that take no arguments.
(define-opcode call-template
(let ((win (lambda ()
(set-template! (get-literal 0)
(enter-fixnum 2)) ; skip the protocol
(if (pending-interrupt?)
(goto handle-interrupt (code-byte 2)) ; pass nargs count
(goto interpret *code-pointer*))))
(code (template-code (get-literal 0))))
(cond ((= 0 (code-vector-ref code 1))
(ensure-default-procedure-space! ensure-space)
(win))
((and (= big-stack-protocol (code-vector-ref code 1))
(= 0 (code-vector-ref code (- (code-vector-length code) 3))))
(ensure-stack-space! (code-vector-ref16
code
(- (code-vector-length code) 2))
ensure-space)
(win))
(else
(raise-exception wrong-type-argument 3 (get-literal 0))))))
; The following two instructions are used only for experiments. The compiler
; does not normally use them.
;
; 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
(let ((nargs (code-byte 0)))
(move-args-above-cont! nargs)
(goto perform-application nargs)))
(define-opcode goto-template
(set-template! (get-literal 0) (enter-fixnum 0))
(goto interpret *code-pointer*))
; APPLY: *VAL* is the procedure, the rest-arg list is on top of the stack,
; the next two bytes are the number of stack arguments below the rest-args list.
; We check that the rest-arg list is a proper list and let
; PERFORM-APPLICATION-WITH-REST-LIST do the work.
(define-opcode apply
(let ((list-args (pop)))
(receive (okay? length)
(okay-argument-list list-args)
(if okay?
(goto perform-application-with-rest-list
(code-offset 0) list-args length)
(begin
(push list-args)
(let ((args (pop-args->list* null (+ (code-offset 0) 1))))
(raise-exception wrong-type-argument -1 *val* args))))))) ; no next
; This is only used for the closed-compiled version of APPLY.
;
; Stack = arg0 arg1 ... argN rest-list N+1 total-arg-count
; Arg0 is the procedure.
;
; Note that the rest-list on the stack is the rest-list passed to APPLY
; procedure and not the rest-list to be used in the call to the procedure.
; Consider (APPLY APPLY (LIST LIST '(1 2 3))), where the initial APPLY
; is not done in-line. The stack for the inner call to APPLY will be
; [(<list-procedure> (1 2 3)), 1, 2], whereas for
; (APPLY APPLY LIST 1 '(2 (3))) the stack will be
; [<list-procedure>, 1, (2 (3)), 3, 4].
;
; We grab the counts and the procedure, and clobber the procedure's stack
; slot for GC safety. Then we get the true stack-arg count and list args
; and again let PERFORM-APPLICATION-WITH-REST-LIST do the work.
(define-opcode closed-apply
(let* ((nargs (extract-fixnum (pop)))
(stack-nargs (extract-fixnum (pop)))
(proc (stack-ref stack-nargs)))
(stack-set! stack-nargs false)
(receive (okay? stack-arg-count list-args list-arg-count)
(get-closed-apply-args nargs stack-nargs)
(if okay?
(begin
(set! *val* proc)
(goto perform-application-with-rest-list
stack-arg-count
list-args
list-arg-count))
(begin
(push list-args)
(let ((args (pop-args->list* null (+ stack-arg-count 1))))
(pop) ; remove procedure
(raise-exception wrong-type-argument -1 proc args)))))))
; Stack = arg0 arg1 ... argN rest-list
; This needs to get the last argument, which is either argN or the last
; element of the rest-list, and splice it into the rest of the arguments.
; If the rest-list is null, then argN is the last argument and becomes the
; new rest-list. If the rest-list is non-null, then we go to the end, get
; the list there, and splice the two together to make a single list.
; This only happens if someone does (APPLY APPLY ...).
(define (get-closed-apply-args nargs stack-nargs)
(let ((rest-list (pop)))
(receive (list-args stack-nargs)
(cond ((vm-eq? rest-list null)
(values (pop)
(- stack-nargs 2))) ; drop proc and final list
((vm-eq? (vm-cdr rest-list) null)
(values (vm-car rest-list)
(- stack-nargs 1))) ; drop proc
(else
(let* ((penultimate-cdr (penultimate-cdr rest-list))
(list-args (vm-car (vm-cdr penultimate-cdr))))
(vm-set-cdr! penultimate-cdr list-args)
(values rest-list
(- stack-nargs 1))))) ; drop proc
(receive (okay? list-arg-count)
(okay-argument-list list-args)
(values okay?
stack-nargs
list-args
list-arg-count)))))
; If LIST is a proper list (the final cdr is null) then we return #T and the
; length of the list, otherwise we return #F.
(define (okay-argument-list list)
(let loop ((fast list) (len 0) (slow list) (move-slow? #f))
(cond ((vm-eq? null fast)
(values #t len))
((or (not (vm-pair? fast)))
(values #f 0))
((not move-slow?)
(loop (vm-cdr fast) (+ len 1) slow #t))
((vm-eq? fast slow)
(values #f 0))
(else
(loop (vm-cdr fast) (+ len 1) (vm-cdr slow) #f)))))
; Return the second-to-last cdr of LIST.
(define (penultimate-cdr list)
(let loop ((list (vm-cdr (vm-cdr list))) (follower list))
(if (eq? null list)
follower
(loop (vm-cdr list) (vm-cdr follower)))))
;----------------
; Call the procedure in *VAL*. STACK-ARG-COUNT is the number of arguments
; on the stack, LIST-ARGS is a list of LIST-ARG-COUNT additional arguments.
;
; The CLOSURE? and protocol checks must come before the interrupt check because
; the interrupt code assumes that the correct template is in place. This delays
; the handling of interrupts by a few instructions; it shouldn't matter.
(define (perform-application stack-arg-count)
(if (closure? *val*)
(goto plain-protocol-match stack-arg-count)
(goto application-exception
(enum exception bad-procedure)
stack-arg-count null 0)))
(define (perform-application-with-rest-list stack-arg-count
list-args list-arg-count)
(cond ((= 0 list-arg-count)
(goto perform-application stack-arg-count))
((closure? *val*)
(goto list-protocol-match
stack-arg-count list-args list-arg-count))
(else
(goto application-exception
(enum exception bad-procedure)
stack-arg-count list-args list-arg-count))))
(define (install-*val*-closure skip)
(let ((template (closure-template *val*)))
(set! *template* template)
(set-code-pointer! (template-code template) skip)
(set-current-env! (closure-env *val*))))
(define (check-interrupts-and-go stack-slots stack-arg-count)
(ensure-stack-space! stack-slots ensure-space)
(if (pending-interrupt?)
(goto handle-interrupt stack-arg-count)
(goto interpret *code-pointer*)))
(define (wrong-nargs stack-arg-count list-args list-arg-count)
(goto application-exception
(enum exception wrong-number-of-arguments)
stack-arg-count list-args list-arg-count))
; Used by RAISE which can't raise an exception when an error occurs.
(define *losing-opcode* 0)
(define (call-exception-handler stack-arg-count opcode)
(set! *template* *val*) ; Use *VAL* (a closure) as a marker.
(set! *losing-opcode* opcode)
(goto plain-protocol-match stack-arg-count))
(define (call-interrupt-handler stack-arg-count interrupt)
(set! *template* *val*) ; Use *VAL* (a closure) as a marker.
(set! *losing-opcode* (- interrupt))
(goto plain-protocol-match stack-arg-count))
; Check that the arguments match those needed by *VAL*, which is a closure,
; moving arguments to or from the stack if necessary, and ensure that there
; is enough stack space for the procedure. The environment needed by *VAL*
; is created.
(define (plain-protocol-match stack-arg-count)
(let ((code (template-code (closure-template *val*)))
(lose (lambda ()
(goto wrong-nargs stack-arg-count null 0))))
(assert (= (enum op protocol)
(code-vector-ref code 0)))
(let loop ((protocol (code-vector-ref code 1))
(stack-space default-stack-space))
(let ((win (lambda (skip stack-arg-count)
(install-*val*-closure skip)
(goto check-interrupts-and-go stack-space stack-arg-count))))
(let (;; Fixed number of arguments.
(fixed-match (lambda (wants skip)
(if (= wants stack-arg-count)
(win skip stack-arg-count)
(lose))))
;; N-ary procedure.
(n-ary-match (lambda (wants-stack-args skip)
(if (<= wants-stack-args stack-arg-count)
(begin
(rest-list-setup wants-stack-args
stack-arg-count
null
0)
(win skip (+ wants-stack-args 1)))
(lose))))
;; Push the rest list, the total number of arguments, and the
;; number on the stack arguments onto the stack.
(args+nargs-match (lambda (skip)
(push null)
(push (enter-fixnum stack-arg-count))
(push (enter-fixnum stack-arg-count))
(win skip (+ stack-arg-count 3)))))
(cond ((= protocol nary-dispatch-protocol)
(if (< stack-arg-count 3)
(let ((skip (code-vector-ref code (+ 2 stack-arg-count))))
(if (= 0 skip)
(lose)
(win skip stack-arg-count)))
(let ((skip (code-vector-ref code 5)))
(if (= 0 skip)
(lose)
(args+nargs-match skip)))))
((= protocol args+nargs-protocol)
(if (>= stack-arg-count
(code-vector-ref code 2))
(args+nargs-match 3)
(lose)))
((= protocol two-byte-nargs+list-protocol)
(n-ary-match (code-vector-ref16 code 2) 4))
((<= protocol maximum-stack-args)
(fixed-match protocol 2))
((= protocol two-byte-nargs-protocol)
(fixed-match (code-vector-ref16 code 2) 4))
((= protocol big-stack-protocol)
(let ((length (code-vector-length code)))
(loop (code-vector-ref code (- length 3))
(code-vector-ref16 code (- length 2)))))
(else
(error "unknown protocol" protocol)
(lose))))))))
; Same thing, except that there is an additional list of arguments.
(define (list-protocol-match stack-arg-count list-args list-arg-count)
(let ((code (template-code (closure-template *val*)))
(total-arg-count (+ stack-arg-count list-arg-count))
(lose (lambda ()
(goto wrong-nargs
stack-arg-count list-args list-arg-count))))
(assert (= (enum op protocol)
(code-vector-ref code 0)))
(let loop ((protocol (code-vector-ref code 1))
(stack-space default-stack-space))
(let ((win (lambda (skip stack-arg-count)
(install-*val*-closure skip)
(goto check-interrupts-and-go stack-space stack-arg-count))))
(let (;; Fixed number of arguments.
(fixed-match (lambda (wants skip)
(if (= wants total-arg-count)
(begin
(push-list list-args list-arg-count)
(win skip total-arg-count))
(lose))))
;; N-ary procedure.
(n-ary-match (lambda (wants-stack-args skip)
(if (<= wants-stack-args total-arg-count)
(begin
(rest-list-setup wants-stack-args
stack-arg-count
list-args
list-arg-count)
(win skip (+ wants-stack-args 1)))
(lose))))
;; If there are > 2 args the top two are pushed on the stack.
;; Then the remaining list, the total number of arguments, and
;; the number on the stack are pushed on the stack.
(args+nargs-match (lambda (skip)
(let ((final-stack-arg-count
(if (< total-arg-count 3)
total-arg-count
(max 2 stack-arg-count))))
(rest-list-setup (max stack-arg-count
final-stack-arg-count)
stack-arg-count
list-args
list-arg-count)
(push (enter-fixnum final-stack-arg-count))
(push (enter-fixnum total-arg-count))
(win skip (+ final-stack-arg-count 3))))))
(cond ((= protocol nary-dispatch-protocol)
(if (< total-arg-count 3)
(let ((skip (code-vector-ref code (+ 2 total-arg-count))))
(if (= 0 skip)
(lose)
(begin
(push-list list-args list-arg-count)
(win skip total-arg-count))))
(let ((skip (code-vector-ref code 5)))
(if (= 0 skip)
(lose)
(args+nargs-match skip)))))
((= protocol args+nargs-protocol)
(if (>= total-arg-count
(code-vector-ref code 2))
(args+nargs-match 3)
(lose)))
((<= protocol maximum-stack-args)
(fixed-match protocol 2))
((= protocol two-byte-nargs+list-protocol)
(n-ary-match (code-vector-ref16 code 2) 4))
((= protocol two-byte-nargs-protocol)
(fixed-match (code-vector-ref16 code 2) 4))
((= protocol big-stack-protocol)
(let ((length (code-vector-length code)))
(loop (code-vector-ref code (- length 3))
(code-vector-ref16 code (- length 2)))))
(else
(error "unknown protocol" protocol)
(lose))))))))
; Adjusts the number of stack arguments to be WANTS-STACK-ARGS by moving
; arguments between the stack and LIST-ARGS as necessary. Whatever is left
; of LIST-ARGS is then copied and the copy is pushed onto the stack.
(define (rest-list-setup wants-stack-args stack-arg-count list-args list-arg-count)
(cond ((= stack-arg-count wants-stack-args)
(push (copy-list* list-args list-arg-count)))
((< stack-arg-count wants-stack-args)
(let ((count (- wants-stack-args stack-arg-count)))
(push (copy-list* (push-list list-args count)
(- list-arg-count count)))))
(else ; (> stack-arg-count wants-stack-args)
(let ((count (- stack-arg-count wants-stack-args)))
(push (pop-args->list* (copy-list* list-args list-arg-count)
count))))))
; Raise an exception, passing to it a list of the arguments on the stack and
; in LIST-ARGS.
(define (application-exception exception
stack-arg-count list-args list-arg-count)
(cond ((not (vm-eq? *template* *val*))
(let ((args (pop-args->list* (copy-list* list-args list-arg-count)
stack-arg-count)))
(raise-exception* exception -1 *val* args))) ; no next opcode
((< 0 *losing-opcode*)
(error "wrong number of arguments to exception handler"
*losing-opcode*))
(else
(error "wrong number of arguments to interrupt handler"
(- *losing-opcode*)))))
;(define (application-exception exception stack-arg-count list-args list-arg-count)
; (if (vm-eq? *template* *val*)
; (error "wrong number of arguments to exception handler"
; *losing-opcode*)
; (let ((args (pop-args->list* (copy-list* list-args list-arg-count)
; stack-arg-count)))
; (push (enter-fixnum (current-opcode)))
; (push (enter-fixnum exception))
; (push *val*)
; (push args)
; (goto raise 2))))
; Get a two-byte number from CODE-VECTOR.
(define (code-vector-ref16 code-vector index)
(let ((high (code-vector-ref code-vector index)))
(adjoin-bits high
(code-vector-ref code-vector (+ index 1))
bits-used-per-byte)))
;----------------
; Manipulating lists of arguments
; Push COUNT elements from LIST onto the stack, returning whatever is left.
(define (push-list list count)
(push list)
(ensure-stack-space! count ensure-space)
(let ((list (pop)))
(do ((i count (- i 1))
(l list (vm-cdr l)))
((<= i 0) l)
(push (vm-car l)))))
; Copy LIST, which has LENGTH elements.
(define (copy-list* list length)
(if (= length 0)
null
(receive (key list)
(ensure-space-saving-temp (* vm-pair-size length) list)
(let ((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))))))
; Pop COUNT arguments into a list with START as the cdr.
(define (pop-args->list* start count)
(receive (key start)
(ensure-space-saving-temp (* vm-pair-size count) start)
(do ((args start (vm-cons (pop) args key))
(count count (- count 1)))
((= count 0)
args))))
;----------------
; Opcodes for the closed-compiled versions of arithmetic primitives.
; The opcode sequences used are:
; binary-reduce1 binary-op binary-reduce2 return
; and
; compare-reduce1 binary-comparison-op compare-reduce2 return
; The compare version quits if the comparison operator returns false.
;
; For ...-reduce1 the stack looks like:
; arg0 arg1 ... argN rest-list N+1
; If there are two or more arguments then at least two arguments are on the
; stack.
; Turn
; *stack* = arg0 (arg1 . more) <3
; into
; *val* = arg1, *stack* = arg0 (arg1 .more) 1 arg0
; or turn
; *stack* = arg0 arg1 ... argN rest-list N+1
; into
; *val* = arg1, *stack* = false arg1 ... argN rest-list N arg0
(define-opcode binary-reduce1
(let ((stack-nargs (extract-fixnum (stack-ref 0))))
(if (= stack-nargs 0)
(let ((rest-list (stack-ref 1))
(arg0 (stack-ref 2)))
(push arg0)
(set! *val* (vm-car rest-list)))
(let ((arg0 (stack-ref (+ stack-nargs 1)))
(arg1 (stack-ref stack-nargs)))
(stack-set! (+ stack-nargs 1) false)
(stack-set! 0 (enter-fixnum (- stack-nargs 1)))
(push arg0)
(set! *val* arg1)))
(goto continue 0)))
; Turn
; *val* = result, *stack* = arg0 (arg1 . more) 2
; into
; *stack* = result more 2
; or turn
; *val* = result, *stack* = arg1 ... argN rest-list N
; into
; *stack* = result ... argN rest-list N
(define-opcode binary-reduce2
(let* ((stack-nargs (extract-fixnum (stack-ref 0)))
(delta (case stack-nargs
((0)
(let ((rest-list (stack-ref 1)))
(if (vm-eq? (vm-cdr rest-list) null)
1
(begin
(stack-set! 1 (vm-cdr rest-list))
(stack-set! 2 *val*)
-2))))
((1)
(let ((rest-list (stack-ref 1)))
(if (vm-eq? rest-list null)
1
(begin
(stack-set! 0 (enter-fixnum 0))
(stack-set! 2 *val*)
-2))))
(else
(stack-set! (+ stack-nargs 1) *val*)
-2))))
(set! *code-pointer* (address+ *code-pointer* delta))
(goto interpret *code-pointer*)))
(define-opcode binary-comparison-reduce2
(if (false? *val*)
(goto continue 0)
(let* ((stack-nargs (extract-fixnum (stack-ref 0)))
(delta (case stack-nargs
((0)
(let ((rest-list (stack-ref 1)))
(if (vm-eq? (vm-cdr rest-list) null)
1
(begin
(stack-set! 1 (vm-cdr rest-list))
(stack-set! 2 (vm-car rest-list))
-2))))
((1)
(let ((rest-list (stack-ref 1)))
(if (vm-eq? rest-list null)
1
(begin
(stack-set! 0 (enter-fixnum 0))
-2))))
(else
-2))))
(set! *code-pointer* (address+ *code-pointer* delta))
(goto interpret *code-pointer*))))
;----------------
; Statistics stuff
;
;(define call-stats (make-vector 16 0))
;
; (let ((i (min stack-arg-count 15)))
; (vector-set! call-stats i (+ 1 (vector-ref call-stats i))))
;
;(define plain-calls (make-vector 7 0))
;
;(define (add-plain-call i)
; (vector-set! plain-calls i (+ (vector-ref plain-calls i) 1)))
;
;(define apply-calls (make-vector 7 0))
;
;(define (add-apply-call i)
; (vector-set! apply-calls i (+ (vector-ref apply-calls i) 1)))
;
;(define (dump-call-stats)
; (let ((out (current-output-port)))
; (write-string "Calls:" out)
; (do ((i 0 (+ i 1)))
; ((= i 16))
; (write-char #\space out)
; (write-integer (vector-ref call-stats i) out))
; (write-char #\newline out)
; (write-string "Plain calls" out)
; (write-char #\newline out)
; (do ((i 0 (+ i 1)))
; ((= i 7))
; (write-char #\space out)
; (write-string (vector-ref call-strings i) out)
; (write-integer (vector-ref plain-calls i) out)
; (write-char #\newline out))
; (write-string "Apply calls" out)
; (write-char #\newline out)
; (do ((i 0 (+ i 1)))
; ((= i 7))
; (write-char #\space out)
; (write-string (vector-ref call-strings i) out)
; (write-integer (vector-ref apply-calls i) out)
; (write-char #\newline out))))
;
;(define call-strings
; '#("nary-dispatch: "
; "args&nargs: "
; "no-env: "
; "two-bytes-nargs+list: "
; "plain: "
; "two-byte-nargs: "
; "big-stack: "))