; Copyright (c) 1993-1999 by Richard Kelsey and Jonathan Rees. See file COPYING. ; Allocation ; s48-*hp* is the heap pointer and s48-*limit* is the limit beyond which no ; storage should be allocated. Both of these are addresses (not ; descriptors). (define s48-*hp*) (define s48-*limit*) (define *oldspace-hp*) (define *oldspace-limit*) ; These are all in address units (define *newspace-begin*) (define *newspace-end*) (define *oldspace-begin*) (define *oldspace-end*) ; For the GC (which is in another module) (define (heap-pointer) s48-*hp*) (define (set-heap-pointer! new-hp) (set! s48-*hp* new-hp)) (define (heap-start) *newspace-begin*) (define (heap-limit) *newspace-end*) ;---------------- (define (s48-initialize-heap start size) (let ((semisize (cells->a-units (quotient size 2)))) (set! *newspace-begin* start) (set! *newspace-end* (address+ *newspace-begin* semisize)) (set! s48-*hp* *newspace-begin*) (set! s48-*limit* *newspace-end*) (set! *oldspace-begin* *newspace-end*) (set! *oldspace-end* (address+ *oldspace-begin* semisize)) (set! *oldspace-hp* *oldspace-begin*) (set! *oldspace-limit* *oldspace-end*))) ; To write images we need to be able to undo the swapping. (define-syntax swap! (syntax-rules () ((swap! a b) (let ((temp a)) (set! a b) (set! b temp))))) (define (swap-spaces) (swap! s48-*limit* *oldspace-limit*) (swap! s48-*hp* *oldspace-hp*) (swap! *newspace-begin* *oldspace-begin*) (swap! *newspace-end* *oldspace-end*)) (define (s48-newspacea-units cells)) s48-*limit*)) (define (s48-available) (a-units->cells (address-difference s48-*limit* s48-*hp*))) (define (s48-heap-size) (address-difference *newspace-end* *newspace-begin*)) (define (store-next! descriptor) (store! s48-*hp* descriptor) (set! s48-*hp* (address1+ s48-*hp*))) ; Pre-Allocation ; ; Preallocation and keys are used to ensure that for every call to MAKE-STOB ; there is a corresponding call to ENSURE-SPACE to see if there is sufficient ; heap space. ENSURE-SPACE returns a key and MAKE-STOB checks that the ; key it is passed is the most recently allocated key and that the space ; needed is no greater than the argument to ENSURE-SPACE. ; ; Another solution would be to make ENSURE-SPACE and MAKE-STOB a single ; procedure. The difficulty is that ENSURE-SPACE may trigger a garbage ; collection, which in turn requires that all live data be reachable ; from the VM's registers. The VM solves this by only calling ENSURE-SPACE ; at the beginning of an instruction, before any values have been removed ; from the stack or any of the registers. Once the key has been obtained ; the instruction is free to make any number of calls to MAKE-STOB, as long ; as the total heap space required is no more than what was checked for. ; ; There is a flag, CHECK-PREALLOCATION?, that determines whether MAKE-STOB ; actually checks the keys. In the VM as seen by the Pre-Scheme compiler ; this flag is defined to be #f and never set, so all of the key code is ; constant-folded into oblivion. ; ; The main virtue of the keys is not that they can be checked but ; that they exist at all. MAKE-STOB requires a key argument, and ; if there is none available you know that you forgot an ENSURE-SPACE. ; Occasionally I run the VM in Scheme with checking enabled, just ; to see if it all still works. (define check-preallocation? #f) (define *heap-key* 0) (define *okayed-space* 0) (define (s48-preallocate-space cells) (cond (check-preallocation? (assert (s48-available? cells)) (set! *heap-key* (+ *heap-key* 1)) (set! *okayed-space* cells) *heap-key*) (else 0))) (define (s48-allocate-space type len key) ;len is in bytes (= type 0) ; declaration for type-checker (if check-preallocation? (let ((cells (+ (bytes->cells len) 1))) (assert (s48-available? cells)) (if (not (and (= key *heap-key*) (>= *okayed-space* cells))) (error "invalid heap key" key cells)) (set! *okayed-space* (- *okayed-space* cells)))) (let ((new s48-*hp*)) (set! s48-*hp* (address+ s48-*hp* (bytes->a-units len))) new)) (define (s48-write-barrier stob address value) (address+ address (+ stob value)) ; for the type checker (unspecific)) ;---------------- ; Keeping track of all the areas. (define *pure-areas*) (define *impure-areas*) (define *pure-sizes*) (define *impure-sizes*) (define *pure-area-count* 0) (define *impure-area-count* 0) (define (have-static-areas?) (or (< 0 *impure-area-count*) (< 0 *pure-area-count*))) (define (s48-register-static-areas pure-count pure-areas pure-sizes impure-count impure-areas impure-sizes) (set! *pure-area-count* pure-count) (set! *pure-areas* pure-areas) (set! *pure-sizes* pure-sizes) (set! *impure-area-count* impure-count) (set! *impure-areas* impure-areas) (set! *impure-sizes* impure-sizes)) (define (walk-areas proc areas sizes count) (let loop ((i 0)) (cond ((>= i count) #t) ((proc (vector-ref areas i) (address+ (vector-ref areas i) (vector-ref sizes i))) (loop (+ i 1))) (else #f)))) (define (walk-pure-areas proc) (if (< 0 *pure-area-count*) (walk-areas proc *pure-areas* *pure-sizes* *pure-area-count*))) (define (walk-impure-areas proc) (if (< 0 *impure-area-count*) (walk-areas proc *impure-areas* *impure-sizes* *impure-area-count*))) ;---------------------------------------------------------------- ; Finding things in the heap. (define *finding-type* (enum stob symbol)) ; work around lack of closures ; Call PREDICATE on all objects of type *FINDING-TYPE* found between START and END. ; The objects for which PREDICATE returns #T are pushed onto the heap using STORE-NEXT!. ; Returns #T for success and #F for failure. (define (collect-type-in-area predicate) (lambda (start end) (let ((type *finding-type*)) (let loop ((addr start)) (if (address>= addr end) #t (let* ((d (fetch addr)) (next (address+ addr (+ (cells->a-units stob-overhead) (header-length-in-a-units d))))) (cond ((not (header? d)) (write-string "heap is in an inconsistent state." (current-error-port)) #f) ((not (= type (header-type d))) (loop next)) (else (let ((obj (address->stob-descriptor (address1+ addr)))) (cond ((not (predicate obj)) (loop next)) ((s48-available? (cells->a-units 1)) (store-next! obj) (loop next)) (else #f))))))))))) ; Returns a procedure that will walk the heap calling PREDICATE on every ; object of a particular type. Returns a vector containing all objects ; for which PREDICATE returns #t. If the heap is screwed up or there isn't ; room for the vector we return FALSE. (define (generic-find-all predicate) (let ((proc (collect-type-in-area predicate))) (lambda (type) (set! *finding-type* type) ; we don't have closures (let ((start-hp s48-*hp*)) (store-next! 0) ; reserve space for header (cond ((and (proc *newspace-begin* start-hp) (walk-impure-areas proc) (walk-pure-areas proc)) (let ((size (address-difference s48-*hp* (address1+ start-hp)))) (store! start-hp (make-header (enum stob vector) size) ) (address->stob-descriptor (address1+ start-hp)))) (else (set! s48-*hp* start-hp) ; out of space, so undo and give up false)))))) ; Find everything with a given type. (define s48-find-all (generic-find-all (lambda (thing) #t))) ; Find all records of a given record type (as determined by the first slot ; in each record). (define s48-find-all-records (let* ((the-record-type false) (finder (generic-find-all (lambda (record) (vm-eq? (record-type record) the-record-type))))) (lambda (record-type) (set! the-record-type record-type) (finder (enum stob record))))) (define find-resumer-records (let ((finder (generic-find-all (lambda (record) (let ((type (record-type record))) (and (record? type) (stob? (record-type-resumer type)))))))) (lambda () (finder (enum stob record))))) ; Functions for accessing records. Getting these from STRUCT would introduce ; a circular module dependency. (define (record? x) (and (stob? x) (= (header-type (stob-header x)) (enum stob record)))) (define (record-type record) (record-ref record -1)) (define (record-type-resumer record-type) (record-ref record-type 0)) (define (record-ref record offset) (fetch (address+ (address-after-header record) (cells->a-units (+ offset 1)))))