Created
February 21, 2012 22:20
-
-
Save acfoltzer/1879417 to your computer and use it in GitHub Desktop.
amb with ivars
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;; Adam Foltzer | |
;; amb with ivars | |
(load "pmatch.scm") | |
;;;; Global scheduler queue and helpers; ugly! | |
(define *q* '()) | |
(define push-right! | |
(lambda (x) | |
(set! *q* (append *q* `(,x))))) | |
(define peek-left | |
(lambda () | |
(cond | |
[(null? *q*) #f] | |
[else (car *q*)]))) | |
(define pop-left! | |
(lambda () | |
(cond | |
[(null? *q*) #f] | |
[else (let ([a (car *q*)]) | |
(set! *q* (cdr *q*)) | |
a)]))) | |
(define filter! | |
(lambda (pred) | |
(set! *q* (filter pred *q*)))) | |
;; Representation of work units to be put in the queue | |
(define-record-type work (fields eng sk id)) | |
;;;; ids | |
;; ids are lists of gensyms where parent/child relationships are | |
;; represented by shared prefixes | |
(define prefix? | |
(lambda (p ls) | |
(cond | |
[(null? p) #t] | |
[(null? ls) #f] | |
[else (and (eq? (car p) (car ls)) | |
(prefix? (cdr p) (cdr ls)))]))) | |
;; Create a new id as a child of the currently active work unit, or | |
;; just a gensym if there's no current work | |
(define new-id | |
(lambda () | |
(cond | |
[(null? *q*) `(,(gensym))] | |
[else (append (work-id (peek-left)) `(,(gensym)))]))) | |
;;;; ivars | |
;; A variant of ivars where rather than just empty and full, we also | |
;; have a case where the ivar represents the top level of the | |
;; computation. This variant contains a continuation that breaks out | |
;; of the whole nested amb. | |
(define new-ivar | |
(lambda () | |
(box `(empty)))) | |
(define new-top-level-ivar | |
(lambda (k) | |
(box `(top-level ,k)))) | |
;; get-ivar blocks until the ivar is filled in | |
(define get-ivar | |
(lambda (iv) | |
(let ([contents (unbox iv)]) | |
(pmatch contents | |
[(empty) (begin (engine-block) (get-ivar iv))] | |
[(full ,x) x])))) | |
;; since we're only calling set-ivar! during the sk of an engine | |
;; (rather than in the engine body), we don't have to worry about | |
;; interrupts as with most ivar implementations | |
(define set-ivar! | |
(lambda (iv x) | |
(let ([contents (unbox iv)]) | |
(pmatch contents | |
[(empty) (set-box! iv `(full ,x))] | |
[(full ,x) (warningf 'put-ivar "multiple put")] | |
;; when top-level, make sure we clear out the *q* before exiting | |
[(top-level ,k) (begin (set! *q* '()) (k x))])))) | |
;;;; main implementation | |
(define-syntax amb | |
(syntax-rules () | |
[(_ e1 e2) (amb-impl (lambda () e1) (lambda () e2))])) | |
(define amb-impl | |
(lambda (th1 th2) | |
(call/cc ;; grab a continuation in case we're calling non-nested | |
(lambda (k) | |
(let* ([id (new-id)] | |
[top-level? (null? *q*)] ;; *q* is empty if we're top level | |
[iv (if top-level? (new-top-level-ivar k) (new-ivar))] | |
[sk (lambda (_ v) | |
(begin (set-ivar! iv v) | |
;; if we make it here, the ivar was *not* | |
;; top-level, so we have to shoot down any | |
;; children of this amb, including the one | |
;; that just succeeded | |
(shootdown! id) | |
;; since we removed the current engine, we | |
;; reenter the loop rather than | |
;; rescheduling with the current engine | |
(enter-loop)))]) | |
;; we're fiddling with the queue in code that might be run | |
;; in an engine, so we have to disable interrupts | |
(with-interrupts-disabled | |
(push-right! (make-work (make-engine th1) sk id)) | |
(push-right! (make-work (make-engine th2) sk id))) | |
;; if top-level, we just make a tail call to enter the loop; | |
;; the answer will be returned as the value of the whole | |
;; call/cc. Otherwise, we do a [blocking] get on the ivar we | |
;; just made, which will eventually return the right value | |
(if top-level? | |
(enter-loop) | |
(get-ivar iv))))))) | |
(define nticks (make-parameter 50)) | |
;; reschedule! is the fk of engines, so we don't have to worry about interrupts | |
(define reschedule! | |
(lambda (new-eng) | |
(when (null? *q*) (errorf 'reschedule! "empty work queue!")) | |
;; push the current work unit (with new engine) on the right of the queue | |
(let ([old (peek-left)]) | |
(push-right! (make-work new-eng (work-sk old) (work-id old))) | |
;; pop only after pushing, in case this is the only work unit | |
;; (although this shouldn't arise) | |
(pop-left!) | |
(let ([new (peek-left)]) | |
((work-eng new) (nticks) (work-sk new) reschedule!))))) | |
(define enter-loop | |
(lambda () | |
(let ([new (peek-left)]) | |
((work-eng new) (nticks) (work-sk new) reschedule!)))) | |
;; remove all work units which have the given id as a prefix | |
(define shootdown! | |
(lambda (id) | |
(filter! (lambda (w) (not (prefix? id (work-id w))))))) | |
;;;; Examples | |
(define f | |
(lambda (x) | |
(amb (f x) x))) | |
(define fact | |
(lambda (n) | |
(if (zero? n) | |
1 | |
(* n (fact (sub1 n)))))) | |
(define fact-1 | |
(lambda () | |
(amb (fact -1) (fact 5)))) | |
(define fact-2 | |
(lambda () | |
(amb (fact -1) | |
(amb ((lambda (x) (x x)) (lambda (x) (x x))) | |
(fact 5))))) | |
(define fib | |
(lambda (n) | |
(if (< n 2) | |
n | |
(+ (fib (- n 1)) (fib (- n 2)))))) | |
(define fib-1 | |
(lambda () | |
(amb (fib 100) | |
(fib 25)))) | |
(define fib/ticks | |
(lambda (ticks) | |
(parameterize ([nticks ticks]) | |
(printf "nticks=~a " ticks) | |
(collect) | |
(time (fib-1))))) | |
(define many-fibs | |
(lambda () | |
(map fib/ticks '(1 2 4 8 16 32 64 128)))) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment