|
|
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)))))