; -*- Mode: Scheme; Syntax: Scheme; Package: Scheme; -*- ; This is file stack.scm. ; *STACK-BEGIN* and *STACK-END* delimit the stack portion of memory. ; *STACK* points to the next unused cell on top of the stack. ; *STACK-LIMIT* is then end of the currently usable stack space. ; *BOTTOM-OF-STACK* is a continuation that lies a the base of the stack. (define *stack-begin* (unassigned)) (define *stack-end* (unassigned)) (define *stack* (unassigned)) (define *stack-limit* (unassigned)) (define *bottom-of-stack* (unassigned)) (define *cont* (unassigned)) ; For tracking the reason for copying closures and environments (used for ; debugging only). (define-enumeration copy (closure overflow preserve)) ; At the bottom of the stack is a special continuation that is never removed. ; When it is invoked it copies the next continuation out of the heap (if there ; is any such) and invokes that instead. (define (initialize-stack start size) (set! *stack-begin* start) (set! *stack-end* (+ start (cells->a-units size))) (set! *stack-limit* *stack-begin*) (set! *stack* (the-pointer-before *stack-end*)) (set! *cont* false) (set! *env* quiescent) (push-continuation-on-stack (make-template-containing-ops (enum op get-cont-from-heap) (enum op return)) (enter-fixnum 0) 0 universal-key) (set! *bottom-of-stack* *cont*)) ; The amount of heap space required to initialize the stack. (define initial-stack-heap-space (op-template-size 2)) (define (reset-stack-pointer) (set! *stack* (the-pointer-before (the-pointer-before (address-after-header *bottom-of-stack*)))) (set-continuation-cont! *bottom-of-stack* (enter-boolean #f))) (define (within-stack? p) (and (addr>= p *stack-begin*) (addr<= p *stack-end*))) (define (stack-size) (- *stack-end* *stack-begin*)) (define (available-on-stack? cells) (addr> (addr- *stack* (cells->a-units cells)) *stack-limit*)) ; The + 1 is to get room for one header that is made. (define (current-stack-size) (+ 1 (a-units->cells (addr- *stack-end* *stack*)))) ; Value of *NARGS* indicating that the arguments overflowed the stack limit (define arg-stack-overflow-nargs (+ maximum-stack-args 1)) ; Room for MAXIMUM-STACK-ARGS plus one for the procedure argument to op/apply (define maximum-stack-arg-count (+ maximum-stack-args 1)) ; Add CELLS cells onto the stack. ; The stack grows towards negative memory. (define (stack-add cells) (set! *stack* (addr- *stack* (cells->a-units cells)))) (define (the-pointer-before x) (addr- x (cells->a-units 1))) (define (push x) ; check for overflow is done when continuations are pushed (store! *stack* x) (stack-add 1)) (define (pop) (stack-add -1) (fetch *stack*)) (define (stack-ref index) (fetch (addr+ *stack* (cells->a-units (+ 1 index))))) (define (stack-set! index value) (store! (addr+ *stack* (cells->a-units (+ 1 index))) value)) (define (pointer-to-stack-arguments) (addr+ *stack* (cells->a-units 1))) (define (remove-stack-arguments count) (stack-add (- 0 count))) ; Making sure that no one uses stack space without checking for overflow. ; Returns a key. Only the most recent key is valid for allocating storage. ; UNIVERSAL-KEY is always valid. (define (ensure-stack-space space ensure-space) (if (not (available-on-stack? space)) (copy-stack-into-heap (ensure-space (current-stack-size)))) (preallocate-stack-space space)) (define (preallocate-stack-space space) (cond (check-stack-preallocation? (set! *stack-key* (+ *stack-key* -1)) ;go down to distinguish from heap keys (set! *okayed-stack-space* space) *stack-key*) (else 0))) (define check-stack-preallocation? #f) (define *stack-key* 0) (define *okayed-stack-space* 0) ; Checks that KEY is the most recent key, and that the overflow check was ; made for at least CELLS space. (define (check-stack-cons cells key) (cond ((and check-stack-preallocation? (not (= key universal-key))) (if (not (and (= key *stack-key*) (>= *okayed-stack-space* cells))) (error "invalid stack key" key cells)) (set! *okayed-stack-space* (- *okayed-stack-space* cells))))) ; Space for an exception continuation is reserved on the stack to ; ensure that pushing an exception continuation will not trigger a ; garbage collection. Exceptions occur at points where there are ; live values that will not be found by the GC. (define (reserve-stack-space size) (set! *stack-limit* (+ *stack-begin* (cells->a-units size)))) (define (enable-stack-reserve) (set! *stack-limit* *stack-begin*)) (define *exception-space-used?* #t) (define (exception-frame-space exception-frame-size) (if (and *exception-space-used?* (not (available-on-stack? exception-frame-size))) (current-stack-size) 0)) (define (reserve-exception-space exception-frame-size key) (cond (*exception-space-used?* (if (not (available-on-stack? exception-frame-size)) (error "no space on stack to reserve exception space")) (reserve-stack-space exception-frame-size) (set! *exception-space-used?* #f)))) (define (allow-exception-consing exception-frame-size) (cond ((not (available-on-stack? exception-frame-size)) (enable-stack-reserve) (set! *exception-space-used?* #t) (if (not (available-on-stack? exception-frame-size)) (error "insufficient space on stack for exception frame")))) (preallocate-stack-space exception-frame-size)) (define (peek-at-current-continuation) (if (addr= *cont* *bottom-of-stack*) (continuation-cont *bottom-of-stack*) *cont*)) ; Skip to the continuation preceding the current one (used for multiple ; value returns). (define (skip-current-continuation!) (let ((next (continuation-cont *cont*))) (if (addr= *cont* *bottom-of-stack*) (set-continuation-cont! *cont* (continuation-cont next)) (set! *cont* next)))) ; Called when replacing the current continuation with a new one. (define (set-current-continuation! cont) (set! *cont* (cond ((false? cont) (reset-stack-pointer) *bottom-of-stack*) (else (copy-continuation-from-heap cont))))) ; Called when returning off of the end of the stack. (define (get-continuation-from-heap) (continuation-cont *bottom-of-stack*)) ; Copy CONT from heap onto stack just above *BOTTOM-OF-STACK*, linking it ; to *BOTTOM-OF-STACK* and *BOTTOM-OF-STACK* to CONT's continuation. (define (copy-continuation-from-heap cont) (assert (continuation? cont)) (let* ((top (addr- (address-at-header *bottom-of-stack*) (addr1+ (cells->a-units (continuation-length cont))))) (new-cont (address->stob-descriptor (addr1+ top)))) (add-copy-cont-from-heap-stats cont) (set! *stack* (the-pointer-before top)) (copy-cells! (address-at-header cont) top (+ 1 (continuation-length cont))) (set-continuation-cont! *bottom-of-stack* (continuation-cont new-cont)) (set-continuation-cont! new-cont *bottom-of-stack*) new-cont)) ; Pushing and popping continuations. (define stack-continuation-size (+ (+ continuation-cells 1) ; header maximum-stack-arg-count)) ; pre-checking for pushed arguments (define (push-continuation-on-stack template pc arg-count key) (check-stack-cons stack-continuation-size key) (add-continuation-stats arg-count) (stack-add (+ 1 continuation-cells)) (store! (addr1+ *stack*) (make-continuation-header arg-count)) (let ((cont (address->stob-descriptor (addr1+ (addr1+ *stack*))))) (set-continuation-pc! cont pc) (set-continuation-template! cont template) (set-continuation-env! cont *env*) (set-continuation-cont! cont *cont*) (set! *cont* cont))) (define make-continuation-header (let ((type (enum stob continuation))) (lambda (arg-count) (make-header-immutable (make-header type (cells->bytes (+ arg-count continuation-cells))))))) (define (pop-continuation-from-stack set-template!) (let ((cont *cont*)) (set-template! (continuation-template cont) (continuation-pc cont)) (set! *env* (continuation-env cont)) (set! *cont* (continuation-cont cont)) (set! *stack* (addr+ (address-at-header cont) (cells->a-units continuation-cells))))) ; Making environments on the stack - the values are already there so this ; only needs to push space for a pointer and push a header. ; Copying the stack into the heap because there is no more room on the ; stack. This copies any arguments that are on the top of the stack into ; a vector, migrates and recovers the current, and then moves the arguments ; from the vector back to the stack. ; ; Why can't this move the arguments directly? The restored continuation ; cannot be larger than the original. (define (copy-stack-into-heap key) (let* ((arg-count (arguments-on-stack)) (vec (vm-make-vector arg-count key))) (do ((i (+ -1 arg-count) (- i 1))) ((<= i -1)) (vm-vector-set! vec i (pop))) (preserve-continuation key (enum copy overflow)) (do ((i 0 (+ i 1))) ((>= i arg-count)) (push (vm-vector-ref vec i))) (unassigned))) (define (arguments-on-stack) (do ((p (addr1+ *stack*) (addr1+ p)) (i 0 (+ i 1))) ((header? (fetch p)) i))) ; Migrating the current continuation into the heap, saving the environment ; first. (define current-continuation-size current-stack-size) (define (current-continuation key) (preserve-continuation key (enum copy preserve))) (define (preserve-continuation key reason) (preserve-current-env-with-reason key reason) (let ((end (continuation-cont *bottom-of-stack*))) (let loop ((cont *cont*) (previous *bottom-of-stack*)) (cond ((vm-eq? cont *bottom-of-stack*) (set-continuation-cont! previous end)) (else (if (within-stack? (continuation-env cont)) (save-env-in-heap (continuation-env cont) cont key reason) 0) ; for type inferencer (which could use some improvement) (let ((new (copy-stob cont key))) (add-preserve-cont-stats new reason) (set-continuation-cont! previous new) (loop (continuation-cont new) new))))) (let ((cont (continuation-cont *bottom-of-stack*))) (set! *cont* (if (false? cont) *bottom-of-stack* (copy-continuation-from-heap cont))) cont))) ; Copy NARGS arguments from the top of the stack to just above CONT. ; Used by OP/MOVE-ARGS-AND-CALL to implement tail-recursive calls. ; ; The IF saves work in a rare case at the cost of a test in the common ; case; is it worth it? (define (move-args-above-cont! nargs) (let ((start-loc (the-pointer-before (address-at-header *cont*))) (start-arg (addr+ *stack* (cells->a-units nargs)))) (if (not (addr<= start-loc start-arg)) (do ((loc start-loc (the-pointer-before loc)) (arg start-arg (the-pointer-before arg))) ((addr<= arg *stack*) (set! *stack* loc)) (store! loc (fetch arg)))))) ; Tracing the stack for garbage collection - first trace any arguments pushed ; above the current continuation, then loop down the continuations, tracing ; each one along with its environment (if the environment has not yet been ; done). (define (trace-stack trace-locations) (trace-locations (addr1+ *stack*) (address-at-header *cont*)) (let loop ((cont *cont*) (last-env 0)) (let ((env (continuation-env cont))) (trace-locations (address-after-header cont) (address-after-stob cont)) (if (not (vm-eq? env last-env)) (trace-env env trace-locations)) (if (not (vm-eq? cont *bottom-of-stack*)) (loop (continuation-cont cont) env))))) ; I do not think that the recursive call is necessary as the superior ; env will be traced by TRACE-STACK as it goes down the list of ; continuations. For every superior env that is on the stack, there should ; be a continuation on the stack that points to it. (define (trace-env env trace-locations) (let loop ((env env)) (cond ((within-stack? env) (trace-locations (address-after-header env) (address-after-stob env)) (loop (vm-vector-ref env 0)))))) ; Collecting and printing statistics on stack usage (define collect-statistics? #f) (define *conts* 0) (define *conts-slots* 0) (define *conts-overflow* 0) (define *conts-overflow-slots* 0) (define *conts-preserved* 0) (define *conts-preserved-slots* 0) (define *conts-from-heap* 0) (define *conts-from-heap-slots* 0) (define *envs* 0) (define *envs-slots* 0) (define *envs-closed* 0) (define *envs-closed-slots* 0) (define *envs-overflow* 0) (define *envs-overflow-slots* 0) (define *envs-preserved* 0) (define *envs-preserved-slots* 0) (define (reset-stack-stats) (cond (collect-statistics? (set! *conts* 0) (set! *conts-slots* 0) (set! *conts-overflow* 0) (set! *conts-overflow-slots* 0) (set! *conts-preserved* 0) (set! *conts-preserved-slots* 0) (set! *conts-from-heap* 0) (set! *conts-from-heap-slots* 0) (set! *envs* 0) (set! *envs-slots* 0) (set! *envs-closed* 0) (set! *envs-closed-slots* 0) (set! *envs-overflow* 0) (set! *envs-overflow-slots* 0) (set! *envs-preserved* 0) (set! *envs-preserved-slots* 0) ) (else 0))) (define (print-stack-stats port) (if collect-statistics? (really-print-stack-stats port))) (define (really-print-stack-stats port) (let ((one-record (lambda (name count slots port) (newline port) (write-string "(" port) (write-string name port) (write-string " " port) (write-number count port) (write-number slots port) (write-string ")" port)))) (newline port) (write-string "(continuations" port) (one-record "made" *conts* *conts-slots* port) (one-record "overflow" *conts-overflow* *conts-overflow-slots* port) (one-record "preserved" *conts-preserved* *conts-preserved-slots* port) (one-record "from-heap" *conts-from-heap* *conts-from-heap-slots* port) (write-string ")" port) (newline port) (write-string "(environments" port) (one-record "made" *envs* *envs-slots* port) (one-record "closed" *envs-closed* *envs-closed-slots* port) (one-record "overflow" *envs-overflow* *envs-overflow-slots* port) (one-record "preserved" *envs-preserved* *envs-preserved-slots* port) (write-string ")" port) (newline port) )) (define (add-continuation-stats arg-count) (cond (collect-statistics? (set! *conts* (+ *conts* 1)) (set! *conts-slots* (+ *conts-slots* (+ arg-count continuation-cells)))))) (define (add-env-stats count) (cond (collect-statistics? (set! *envs* (+ *envs* 1)) (set! *envs-slots* (+ *envs-slots* (+ count 1)))))) (define (add-copy-env-stats env reason) (cond ((not collect-statistics?) (unassigned)) ((= reason (enum copy closure)) (set! *envs-closed* (+ *envs-closed* 1)) (set! *envs-closed-slots* (+ *envs-closed-slots* (vm-vector-length env)))) ((= reason (enum copy overflow)) (set! *envs-overflow* (+ *envs-overflow* 1)) (set! *envs-overflow-slots* (+ *envs-overflow-slots* (vm-vector-length env)))) ((= reason (enum copy preserve)) (set! *envs-preserved* (+ *envs-preserved* 1)) (set! *envs-preserved-slots* (+ *envs-preserved-slots* (vm-vector-length env)))))) (define (add-preserve-cont-stats new reason) (cond ((not collect-statistics?) (unassigned)) ((= reason (enum copy overflow)) (set! *conts-overflow* (+ *conts-overflow* 1)) (set! *conts-overflow-slots* (+ *conts-overflow-slots* (continuation-length new)))) ((= reason (enum copy preserve)) (set! *conts-preserved* (+ *conts-preserved* 1)) (set! *conts-preserved-slots* (+ *conts-preserved-slots* (continuation-length new)))))) (define (add-copy-cont-from-heap-stats cont) (cond (collect-statistics? (set! *conts-from-heap* (+ *conts-from-heap* 1)) (set! *conts-from-heap-slots* (+ *conts-from-heap-slots* (continuation-length cont))))))