|
DataMuseum.dkPresents historical artifacts from the history of: DKUUG/EUUG Conference tapes |
This is an automatic "excavation" of a thematic subset of
See our Wiki for more about DKUUG/EUUG Conference tapes Excavated with: AutoArchaeologist - Free & Open Source Software. |
top - metrics - downloadIndex: T f
Length: 18359 (0x47b7) Types: TextFile Names: »future.scm.11«
└─⟦a0efdde77⟧ Bits:30001252 EUUGD11 Tape, 1987 Spring Conference Helsinki └─⟦this⟧ »EUUGD11/gnu-31mar87/scheme/scm/future.scm.11«
; This is -*- SCHEME -*- code ; This is part of the Butterfly fly SCHEME basic band load ; so that people can tell that they actually have more than ; processor. ; ; Note that this doesn't actually start the FUTURE system ; until someone does a (futures-on) (declare (usual-integrations) (compilable-primitive-functions (= eq?) set-fixed-objects-vector! vector-set! get-external-number get-work (catch call-with-current-continuation))) (define put-work (make-primitive-procedure 'put-work)) (define global-interrupt (make-primitive-procedure 'global-interrupt)) (define TOUCH (make-primitive-procedure 'TOUCH)) (define SET-CAR-IF-EQ?! (make-primitive-procedure 'SET-CAR-IF-EQ?!)) (define SET-CDR-IF-EQ?! (make-primitive-procedure 'SET-CDR-IF-EQ?!)) (define VECTOR-SET-IF-EQ?! (make-primitive-procedure 'VECTOR-SET-IF-EQ?!)) (define SET-CXR-IF-EQ?! (make-primitive-procedure 'SET-CXR-IF-EQ?!)) (define FUTURE? (make-primitive-procedure 'FUTURE?)) (define FUTURE-REF (make-primitive-procedure 'FUTURE-REF)) (define FUTURE-SET! (make-primitive-procedure 'FUTURE-SET!)) (define FUTURE-SIZE (make-primitive-procedure 'FUTURE-SIZE)) (define LOCK-FUTURE! (make-primitive-procedure 'LOCK-FUTURE!)) (define UNLOCK-FUTURE! (make-primitive-procedure 'UNLOCK-FUTURE!)) \f (define n-interpreters (make-primitive-procedure 'N-INTERPRETERS)) (define my-processor-number (make-primitive-procedure 'MY-PROCESSOR-NUMBER)) (define my-interpreter-number (make-primitive-procedure 'MY-INTERPRETER-NUMBER)) (define statistics-package (make-package statistics-package ((get-statistics (make-primitive-procedure 'get-statistics #!true)) (stat-names '((CONTENTION-COUNT . 0) (GC-MASTER-IDLE-TIME . 1) (GC-SLAVE-IDLE-TIME . 2) (GC-LOOP-TIME . 3) (GC-DAEMON-TIME . 4))) (This-load)) (define (load-statistics) (set! this-load (get-statistics))) (define (get-statistic name) (if (unassigned? this-load) (load-statistics)) (vector-ref this-load (cdr (assq name stat-names)))) (define (clear-statistics) (set! this-load)))) (define load-statistics (access load-statistics statistics-package)) (define get-statistic (access get-statistic statistics-package)) (define clear-statistics (access clear-statistics statistics-package)) \f ; Slots in a future ; First the ones the microcode knows about explicitly (define FUTURE-DETERMINED-SLOT 0) (define FUTURE-LOCK-SLOT 1) (define FUTURE-VALUE-SLOT 2) (define FUTURE-QUEUE-SLOT 2) ; Then the ones referenced only by the runtime system (define FUTURE-PROCESS-SLOT 3) (define FUTURE-DELAYED?-SLOT 4) (define FUTURE-ORIG-CODE-SLOT 5) (define FUTURE-PROCESS-PRIVATE-SLOT 6) \f ; Some useful macros for dealing with atomicity. Notice that ;;DEFINE-MACRO happens when the text is turned into code (i.e. ;;at syntax time), while ADD-SYNTAX! happens only when the program ;;is actually executed. So both are used when this file uses the ;;macro, but only ADD-SYNTAX! is used for user macros which ;;are not referenced here. ; ATOMIC takes a list of expression and guarantees that they ;;are done without interrupts. (define-macro (atomic . expressions) `(WITHOUT-INTERRUPTS (LAMBDA () . ,expressions))) (add-syntax! 'ATOMIC (macro expressions `(WITHOUT-INTERRUPTS (LAMBDA () . ,expressions)))) ; DEFINE-ATOMIC is like the procedural version of DEFINE, except ;;that the body is wrapped in WITHOUT-INTERRUPTS. (define-macro (define-atomic arg-template . body) `(DEFINE ,arg-template (ATOMIC . ,body))) (add-syntax! 'DEFINE-ATOMIC (macro (arg-template . body) `(DEFINE ,arg-template (ATOMIC . ,body)))) \f ; LOCKING-FUTURE is the same as ATOMIC except that it also wraps ;;a LOCK-FUTURE! and UNLOCK-FUTURE! around the expression(s). ;;LOCKED? is a flag which can be used in BODY -- it will be #!true ;;if the future is still valid (you hang until you can lock it), ;;or #!false if it has been spliced out. (define-macro (LOCKING-FUTURE FUTURE LOCKED? . BODY) `(WITH-FUTURE-LOCKED ,future (LAMBDA (,locked?) . ,body))) (add-syntax! 'LOCKING-FUTURE (macro (FUTURE LOCKED? . BODY) `(WITH-FUTURE-LOCKED ,future (LAMBDA (,locked?) . ,body)))) (define-macro (WITH-STATE STATE . BODY) `(CATCH (LAMBDA (,state) . ,body))) (DEFINE-ATOMIC (with-future-locked future thunk) (if (lock-future! future) (let ((result (thunk #!true))) (unlock-future! future) result) (thunk #!false))) (define (determine! future value #!optional keep-slot?) (let ((result (touch value))) (LOCKING-FUTURE future was-still-a-future? (if was-still-a-future? (let ((known? (future-ref future FUTURE-DETERMINED-SLOT)) (waiters (future-ref future FUTURE-QUEUE-SLOT))) (if (eq? known? #!true) (error "Future cannot be determined twice." future)) (future-set! future FUTURE-VALUE-SLOT result) (if (unassigned? keep-slot?) (if (eq? known? #!false) (future-set! future FUTURE-DETERMINED-SLOT #!true)) (future-set! future FUTURE-DETERMINED-SLOT (if keep-slot? 'KEEP-SLOT #!true))) ((access awaken! scheduler) waiters)) (error "Future cannot be determined twice." future))) result)) \f (define scheduler (make-package scheduler ((Current-Future-Vector) ; Process currently running (Start-Process) ; Default scheduler for FUTURE creation (Idle-Future) ; Future to wait until idle on (sti (make-primitive-procedure 'setup-timer-interrupt #!true)) ; Procedure to initiate timer interrupt (drain-work-queue! (make-primitive-procedure 'drain-work-queue!)) (preempting? #!false) ; No timer currently set (Delta '())) ; Scheduling frequency, centi-seconds (DEFINE-ATOMIC (start-preempting interval) (if (not preempting?) (sequence (set! timer-interrupt (lambda () (let ((My-Task (Current-Future))) (WITH-STATE me (LOCKING-FUTURE My-Task I-am-running? (if I-am-running? (sequence (future-set! My-Task FUTURE-PROCESS-SLOT me) (put-work My-Task)) (sequence (stop-preempting) (bkpt "TIMER: Existential crisis!")))) (next))))) (set! preempting? #!true) (set! delta interval) (sti 0 interval)) (print "Already preempting when START-PREEMPTING called"))) (DEFINE-ATOMIC (stop-preempting) (sti '() '()) (set! preempting? #!false)) (define make-future (let ((future-type (microcode-type 'FUTURE))) (named-lambda (make-future orig-code name) (primitive-set-type future-type (VECTOR #!FALSE ; No value yet #!FALSE ; Not locked ((ACCESS MAKE-EMPTY-QUEUE SCHEDULER)) ; No waiters orig-code ; How to resume #!FALSE ; Not delayed orig-code ; For debugging (if (unbound? open-console-channel) 'no-console-channel (vector (open-console-channel name)))))))) \f (define (spawn-process thunk doc #!optional start) (with-interrupt-mask INTERRUPT-MASK-GC-OK (lambda (old-interrupts) (let ((object)) (set! object (make-future (lambda (ignore) (set-interrupt-enables! old-interrupts) (let ((result (thunk))) (ATOMIC (determine! object result) (next)))) doc)) ((if (unassigned? start) (access start-process scheduler) start) object) object)))) \f (define (Current-Future) (if (unassigned? Current-Future-Vector) '() (vector-ref Current-Future-Vector (My-Interpreter-Number)))) (define (Set-Current-Future! Future) (vector-set! Current-Future-Vector (My-Interpreter-Number) Future)) (define (initialize-scheduler! #!optional interval default-scheduler) (pause-everything) ; Stop all processors & drain queue (set! Start-Process (if (unassigned? default-scheduler) dfuture-scheduler default-scheduler)) (set! Current-Future-Vector (vector-cons (N-Interpreters) 'NO-FUTURE-YET)) (set! Idle-Future (make-future 'NO-PROCESS "Idle-Loop")) (if (not (unassigned? interval)) (set! delta interval)) (let ((fobj (get-fixed-objects-vector))) (vector-set! fobj (fixed-objects-vector-slot 'SCHEDULER) await-future) (set-fixed-objects-vector! fobj)) (Set-Current-Future! (make-future 'INITIAL-PROCESS "The Initial Process")) (global-interrupt 1 (lambda (IntCode IntEnb) (set-interrupt-enables! IntEnb) (next)) (lambda () #!true)) (or Delta 'NOT-PREEMPTIVE-SCHEDULING)) \f ; Scheduling support (define (next) (Set-Current-Future! 'WAITING-FOR-WORK) (run (get-work (lambda () (stop-preempting) (determine! Idle-Future 'DONE) (set! Idle-Future (make-future 'NO-PROCESS "Idle Loop")))))) ;; RUN starts a process running (define (run future) (if (and delta preempting?) (sti 0 delta)) ; Full time interval (Set-Current-Future! future) ((future-set! future FUTURE-PROCESS-SLOT 'RUNNING) 'YOUR-TURN)) ;; AWAKEN! is called with a queue (of processes waiting ;; for a future) and promotes them all to runnable status. (define (awaken! queue) (if (empty-queue? queue) 'DONE (sequence (let loop () (if (empty-queue? queue) 'DONE (let ((next-item (dequeue! queue))) (if (not (future? next-item)) (bkpt "Non-future awakened!") (put-work next-item)) (loop)))) (if (and delta (not preempting?)) (start-preempting delta))))) \f ;; AWAIT-FUTURE suspends the current process and adds it to the ;; queue waiting for the specified future to get a value. (DEFINE-ATOMIC (await-future future) (WITH-STATE me ((LOCKING-FUTURE future waiting-for-a-future? (if (not (conjunction waiting-for-a-future? (null? (future-ref future FUTURE-DETERMINED-SLOT)))) (lambda () 'NO-NEED-TO-SUSPEND) (let ((My-Task (Current-Future)) (next-process (if (future-ref future FUTURE-DELAYED?-SLOT) (lambda () (future-set! future FUTURE-DELAYED?-SLOT #!false) (run future)) next))) (LOCKING-FUTURE My-Task I-am-running? (if I-am-running? (sequence (future-set! My-Task FUTURE-PROCESS-SLOT me) (enqueue! (future-ref future FUTURE-QUEUE-SLOT) My-Task) next-process) (sequence ; I wasn't a legit. process (print "AWAIT-FUTURE: Existential crisis!") next))))))))) \f ;; AWAIT-FUTURE-AFTER-ACTION suspends the current process after ;; executing a thunk and adds it to the queue waiting for the ;; specified future to get a value. Its purpose is to ensure ;; that the process is actually on the wait queue of the future ;; when the action takes place. This prevents a race condition ;; which might cause problems if the action is intended to determine ;; the future (e.g., externally) and wake up the process. In other words, ;; if the process has not been added to the wait queue when another ;; process or an external interrupt determines the future, the event ;; would not wake up the process as intended. ;; ;; Since this is rather specialized code (it was added to support ;; the new console i/o system), many safeguards of the normal ;; AWAIT-FUTURE are removed for speed. Normally the future will ;; have been explicitly constructed by the user and is not being ;; determined by any other process. (define (await-future-after-action future action) (let ((My-Task (Current-Future))) (catch (lambda (me) (future-set! My-Task FUTURE-PROCESS-SLOT me) (enqueue! (future-ref future FUTURE-QUEUE-SLOT) My-Task) (action) (next))))) \f ; Special scheduler operations ;; RESCHEDULE allows me to give up my processor slice and ;; wait until the scheduler gets back to me. (define-atomic (reschedule) (WITH-STATE me (future-set! (Current-Future) FUTURE-PROCESS-SLOT me) (put-work me) (next))) ;; WAIT-UNTIL-IDLE causes a process to just continue ;; going to sleep until there are no other active processes. (define (wait-until-idle) (touch idle-future)) ;; DFUTURE-SCHEDULER is a future creation scheduler which ;; defers the child process and continues on with the parent. ;; Note that all creation schedulers are called as part of ;; the parent process, so this is the easy case. (DEFINE-ATOMIC (dfuture-scheduler future) (put-work future) (if (and delta (not preempting?)) (start-preempting delta)) 'CHILD-QUEUED-FOR-EXECUTION) ;; FUTURE-SCHEDULER is a future creation scheduler which ;; defers the parent process and continues on with the child. ;; This is a little harder than DFUTURE, since it is called ;; running as the parent. (DEFINE-ATOMIC (future-scheduler future) (WITH-STATE parent-process (let ((My-Future (Current-Future))) (LOCKING-FUTURE My-Future Still-Runnable? (if Still-Runnable? (sequence (future-set! My-Future FUTURE-PROCESS-SLOT parent-process) (put-work My-Future) (if (and delta (not preempting?)) (start-preempting delta))))) (run future)))) ;; DELAY-SCHEDULER is a future creation scheduler which defers ;; execution of the newly created future until it is first ;; touched. (DEFINE-ATOMIC (delay-scheduler future) (future-set! future FUTURE-DELAYED?-SLOT #!true) 'OK-I-DELAYED-IT) \f ; Queue Abstraction ; ; ------------------------------- ; | Tail Pointer | Head Pointer | ; ------------------------------- ; | | ; | | ; V V ; ----- ----- ----- ; | |=|=>| |=|=>| |/| add new items by clobbering '() ; ----- ----- ----- ; remove from start of list ; ; The queue is empty when Tail=Head=#!NULL ; (thus it has one item when Tail=Head but they are not #!NULL) ; ; These operations assume that the caller has arranged for any ; desired ity. (define (make-empty-queue) (cons '() ())) (define queue-head-ptr car) (define queue-tail-ptr cdr) (define set-queue-head-ptr! set-car!) (define set-queue-tail-ptr! set-cdr!) (define (empty-queue? queue) (null? (queue-head-ptr queue))) (define (enqueue! queue object) (if (null? (queue-head-ptr queue)) (sequence (set-queue-head-ptr! queue (list object)) (set-queue-tail-ptr! queue (queue-head-ptr queue))) (sequence (set-cdr! (queue-head-ptr queue) (list object)) (set-queue-head-ptr! queue (cdr (queue-head-ptr queue)))))) (define (dequeue! queue) (let ((current-tail (queue-tail-ptr queue))) (if (null? current-tail) (error "Queue empty" queue) (let ((result (car current-tail))) (if (null? (cdr current-tail)) (sequence (set-queue-head-ptr! queue '()) (set-queue-tail-ptr! queue '())) (set-queue-tail-ptr! queue (cdr current-tail))) result)))) \f ;; PAUSE-EVERYTHING is used to make every processor save its state ;; and all but the caller then go quiescent. The value returned ;; by Pause-Everything is a procedure which will put the work queue ;; back to its initial state (modulo order of futures on the queue) ; ; The state of the system consists of the current-future-vector ; and the current queue. (DEFINE-ATOMIC (pause-everything) (stop-preempting) (if (unassigned? Current-Future-Vector) (lambda () 'DONE) (let ((drain-synch (make-synchronizer)) (proceed-synch (make-synchronizer))) (global-interrupt 1 (lambda (int-code int-mask) (if (eq? (stuff-yourself) 'GOT-STUFFED) (sequence (set-interrupt-enables! int-mask) (await-synchrony drain-synch) (await-synchrony proceed-synch) (next)))) (lambda () #!TRUE)) (if (eq? (stuff-yourself) 'GOT-STUFFED) (sequence (await-synchrony drain-synch) (let ((the-queue (drain-work-queue!)) (the-array Current-Future-Vector)) (set! Current-Future-Vector (vector-cons (N-Interpreters) 'PAUSED)) (Set-Current-Future! (make-future 'PAUSE-MASTER "Pause-Everyone")) (await-synchrony proceed-synch) (lambda () (if the-array (sequence (release-state! the-array) (release-state! the-queue))) (set! the-array '()) (next)))) #!FALSE)))) ;; STUFF-YOURSELF is really save processor state ;; put the current continuation into the current future (define (stuff-yourself) (WITH-STATE me (let ((basket (Current-Future))) (LOCKING-FUTURE basket I-am-Running? (if I-am-Running? (future-set! basket FUTURE-PROCESS-SLOT me))) 'GOT-STUFFED))) ;; RELEASE-STATE! puts all the work in an array back on the queue (define (release-state! array) (let ((last (vector-size array))) (let release-next ((i 0)) (if (< i last) (let ((work-unit (vector-ref array i))) (LOCKING-FUTURE work-unit work-to-do? (conjunction work-to-do? (not (symbol? (future-ref work-unit FUTURE-PROCESS-SLOT))) (put-work work-unit))) (release-next (1+ i))) 'RESTARTED)))) )) ; End of Make-Package for Scheduler \f ; Export definitions to the world outside the scheduler (define initialize-scheduler! (access initialize-scheduler! scheduler)) (define future-scheduler (access future-scheduler scheduler)) (define dfuture-scheduler (access dfuture-scheduler scheduler)) (define delay-scheduler (access delay-scheduler scheduler)) (define wait-until-idle (access wait-until-idle scheduler)) (define pause-everything (access pause-everything scheduler)) (define Current-Future (access Current-Future scheduler)) (define (futures-on #!optional slice) (if (unassigned? slice) (set! slice '())) (initialize-scheduler! slice dfuture-scheduler)) \f (add-syntax! 'future (macro (expression #!optional doc user-scheduler) `((ACCESS SPAWN-PROCESS SCHEDULER) (LAMBDA () ,expression) ; Work to do ,(if (unassigned? doc) ; Documentation (if (unbound? prin1-to-string) "NO DOCUMENTATION AVAILABLE" (prin1-to-string `,expression)) doc) ,@(if (unassigned? user-scheduler) ; Start-up procedure '() `(,user-scheduler)))))