cs: use call-in-continuation

Using `call-in-continuation` to apply a thunk within a continuation
slightly simplifies and speeds up parts of the implementation of
delimited continuations.
This commit is contained in:
Matthew Flatt 2020-03-12 04:51:28 -06:00
parent 3ddc1ca367
commit f98d0a5cc1
4 changed files with 147 additions and 151 deletions

View File

@ -12,7 +12,7 @@
(define collection 'multi)
(define version "7.6.0.15")
(define version "7.6.0.16")
(define deps `("racket-lib"
["racket" #:version ,version]))

View File

@ -2,7 +2,7 @@
;; Check to make we're using a build of Chez Scheme
;; that has all the features we need.
(define-values (need-maj need-min need-sub need-dev)
(values 9 5 3 22))
(values 9 5 3 23))
(unless (guard (x [else #f]) (eval 'scheme-fork-version-number))
(error 'compile-file

View File

@ -47,14 +47,13 @@
;; continuation. See also `current-mark-splice` below.
;; A metacontinuation frame's `resume-k` is called when control
;; returns or aborts to the frame:
;;
;; * When returning normally to a metacontinuation frame, the
;; `resume-k` continuation receives a function for values returned
;; to the frame.
;;
;; * When aborting to a prompt tag, the `resume-k` continination
;; receives a special value that indicates an abort with arguments.
;; returns or aborts to the frame. When aborting to a prompt tag,
;; metacontinuation frames between the abort and prompt are removed
;; one-by-one, running any winders in each frame. Finally, the
;; `resume-k` continuation of the target prompt's metacontinuation is
;; called; the `resume-k` is called using `call-in-continuation` to
;; run a thunk in the restored continuation to apply the prompt's
;; handler.
;;
;; Calling a non-composable continuation is similar to aborting,
;; except that the target prompt's abort handler is not called. In
@ -102,7 +101,9 @@
(define-virtual-register current-metacontinuation '())
(define-record metacontinuation-frame (tag ; continuation prompt tag or #f
resume-k ; delivers values to the prompt, also keeps mark stack as attachments
resume-k ; delivers values to the prompt
handler ; prompt handler
marks ; marks of `resume-k` plus immediate mark (if any)
winders ; `dynamic-wind` winders
mark-splice ; extra part of mark stack to restore
mark-chain ; #f or a cached list of mark-chain-frame or elem+cache
@ -110,9 +111,6 @@
cc-guard ; for impersonated tag, initially #f
avail-cache)) ; cache for `continuation-prompt-available?`
;; Messages to `resume-k[/no-wind]`:
(define-record aborting (args))
(define-record-type (continuation-prompt-tag create-continuation-prompt-tag authentic-continuation-prompt-tag?)
(fields (mutable name))) ; mutable => constructor generates fresh instances
@ -164,7 +162,7 @@
(or (and (not mc)
(or (eq? tag the-default-continuation-prompt-tag)
(eq? tag the-root-continuation-prompt-tag)))
;; Looks through metacontinuation cache, but cache a search result
;; Look through metacontinuation cache, but cache a search result
;; half-way up if the chain is deep enough
(let ([mc (or mc (current-metacontinuation))])
(let loop ([mc mc] [slow-mc mc] [slow-step? #f] [steps 0])
@ -256,84 +254,68 @@
"\n in: application of default prompt handler"
args)]))
(define (resume-metacontinuation results)
;; pop a metacontinuation frame
(cond
[(null? (current-metacontinuation)) (engine-return)]
[else
(start-uninterrupted 'resume-mc)
(let ([mf (car (current-metacontinuation))])
(pop-metacontinuation-frame)
;; resume
((metacontinuation-frame-resume-k mf) results))]))
(define (pop-metacontinuation-frame)
(let ([mf (car (current-metacontinuation))])
(current-metacontinuation (cdr (current-metacontinuation)))
(current-winders (metacontinuation-frame-winders mf))
(current-mark-splice (metacontinuation-frame-mark-splice mf))))
(current-mark-splice (metacontinuation-frame-mark-splice mf))
mf))
(define (call-in-empty-metacontinuation-frame tag handler new-splice proc)
;; Call `proc` in an empty metacontinuation frame, reifying the
;; current metacontinuation as needed (i.e., if non-empty) as a new
;; frame on `*metacontinuations*`; if the tag is #f and the
;; current metacontinuation frame is already empty, don't push more
;; current metacontinuation as a new frame on `current-metacontinuation`
(assert-in-uninterrupted)
(assert-not-in-system-wind)
(let ([r ; a list of results, or a non-list for special handling
(call/cc
(lambda (resume-k)
;; the `call/cc` to get `k` created a new stack
;; segment; by dropping the link from the current
;; segment to the return context referenced by `k`,
;; we actually delimit the current continuation:
(#%$current-stack-link #%$null-continuation)
(current-mark-stack '())
(let-values ([results
;; mark the "empty" continuation frame
;; that just continues the metacontinuation:
(call-setting-continuation-attachment
'empty
(lambda ()
(let ([mf (make-metacontinuation-frame tag
resume-k
(current-winders)
(current-mark-splice)
#f
#f
#f
#f)])
(current-winders '())
(current-mark-splice new-splice)
;; push the metacontinuation:
(current-metacontinuation (cons mf (current-metacontinuation)))
;; ready:
(proc))))])
;; Prepare to use cc-guard, if one was enabled:
(let ([cc-guard (metacontinuation-frame-cc-guard (car (current-metacontinuation)))])
;; Continue normally; the metacontinuation could be different
;; than when we captured this metafunction frame, though:
(resume-metacontinuation
;; Apply the cc-guard, if any, outside of the prompt:
(if cc-guard
(lambda () (apply cc-guard results))
results))))))])
(cond
[(aborting? r)
;; Remove the prompt as we call the handler:
(pop-metacontinuation-frame)
(end-uninterrupted/call-hook 'handle)
(apply handler
(aborting-args r))]
[else
;; We're returning normally; the metacontinuation frame has
;; been popped already by `resume-metacontinuation`
(end-uninterrupted 'resume)
(if (#%procedure? r)
(r)
(if (and (pair? r) (null? (cdr r)))
(car r)
(#%apply values r)))])))
(call/cc
(lambda (resume-k)
(let ([marks (current-mark-stack)]) ; grab marks before `call-in-continuation`
(call-in-continuation
#%$null-continuation
'()
(lambda ()
(let-values ([results
;; mark the "empty" continuation frame
;; that just continues the metacontinuation:
(call-setting-continuation-attachment
'empty
(lambda ()
(let ([mf (make-metacontinuation-frame tag
resume-k
handler
marks
(current-winders)
(current-mark-splice)
#f
#f
#f
#f)])
(current-winders '())
(current-mark-splice new-splice)
;; push the metacontinuation:
(current-metacontinuation (cons mf (current-metacontinuation)))
;; ready:
(proc))))])
;; Continue normally; the metacontinuation could be different
;; than when we captured this metafunction frame, though:
(cond
[(null? (current-metacontinuation)) (engine-return)]
[else
(start-uninterrupted 'resume-mc)
(let ([mf (pop-metacontinuation-frame)])
(call-in-continuation
(metacontinuation-frame-resume-k mf)
(metacontinuation-frame-marks mf)
(lambda ()
(end-uninterrupted 'resume)
(let ([cc-guard (metacontinuation-frame-cc-guard mf)])
;; Apply the cc-guard, if any, outside of the prompt:
(cond
[cc-guard
(apply cc-guard results)]
[else
(if (and (pair? results) (null? (cdr results)))
(car results)
(#%apply values results))])))))]))))))))
;; Simplified `call-in-empty-metacontinuation-frame` suitable for swapping engines:
(define (call-with-empty-metacontinuation-frame-for-swap proc)
@ -341,23 +323,30 @@
(assert-not-in-system-wind)
(call/cc
(lambda (resume-k)
(#%$current-stack-link #%$null-continuation)
(current-mark-stack '())
(let ([mf (make-metacontinuation-frame #f
resume-k
(current-winders)
(current-mark-splice)
#f
#f
#f
#f)])
(current-winders '())
(current-mark-splice empty-mark-frame)
(current-metacontinuation (cons mf (current-metacontinuation)))
(let ([r (proc (current-metacontinuation))])
(let ([mf (car (current-metacontinuation))])
(pop-metacontinuation-frame)
((metacontinuation-frame-resume-k mf) r)))))))
(let ([marks (current-mark-stack)])
(call-in-continuation
#%$null-continuation
'()
(lambda ()
(let ([mf (make-metacontinuation-frame #f
resume-k
void
marks
(current-winders)
(current-mark-splice)
#f
#f
#f
#f)])
(current-winders '())
(current-mark-splice empty-mark-frame)
(current-metacontinuation (cons mf (current-metacontinuation)))
(let ([r (proc (current-metacontinuation))])
(let ([mf (pop-metacontinuation-frame)])
(call-in-continuation
(metacontinuation-frame-resume-k mf)
(metacontinuation-frame-marks mf)
(lambda () r)))))))))))
(define (call-in-empty-metacontinuation-frame-for-compose proc)
(call-getting-continuation-attachment
@ -369,23 +358,22 @@
;; metacontinuation frame
(proc)]
[else
(call/cc
(lambda (from-k)
(let ([new-splice (keep-immediate-attachment (current-mark-stack)
(continuation-next-attachments from-k))])
;; Prune splicing marks from `resume-k` by dropping the difference
;; between `from-k` and `resume-k`:
(current-mark-stack (continuation-next-attachments from-k))
;; Call
(call-in-empty-metacontinuation-frame
the-compose-prompt-tag
fail-abort-to-delimit-continuation
new-splice
proc))))]))))
;; Consume attachment to move it (if there is one) to the new
;; metacontinuation frame's splice:
(call-consuming-continuation-attachment
empty-mark-frame
(lambda (new-splice)
(call-in-empty-metacontinuation-frame
the-compose-prompt-tag
fail-abort-to-delimit-continuation
new-splice
proc)))]))))
(define (metacontinuation-frame-update-mark-splice current-mf mark-splice)
(make-metacontinuation-frame (metacontinuation-frame-tag current-mf)
(metacontinuation-frame-resume-k current-mf)
(metacontinuation-frame-handler current-mf)
(metacontinuation-frame-marks current-mf)
(metacontinuation-frame-winders current-mf)
mark-splice
#f
@ -397,6 +385,8 @@
;; Ok to keep caches, since the cc-guard doesn't affect them
(make-metacontinuation-frame (metacontinuation-frame-tag current-mf)
(metacontinuation-frame-resume-k current-mf)
(metacontinuation-frame-handler current-mf)
(metacontinuation-frame-marks current-mf)
(metacontinuation-frame-winders current-mf)
(metacontinuation-frame-mark-splice current-mf)
(metacontinuation-frame-mark-chain current-mf)
@ -432,8 +422,16 @@
(let ([mf (car (current-metacontinuation))])
(cond
[(eq? tag (strip-impersonator (metacontinuation-frame-tag mf)))
((metacontinuation-frame-resume-k mf)
(make-aborting args))]
;; Remove the prompt and resume its continuation
;; as we call the handler:
(let ([mf (pop-metacontinuation-frame)])
(call-in-continuation
(metacontinuation-frame-resume-k mf)
(metacontinuation-frame-marks mf)
(lambda ()
(end-uninterrupted/call-hook 'handle)
(apply (metacontinuation-frame-handler mf)
args))))]
[else
;; Aborting to an enclosing prompt, so keep going:
(pop-metacontinuation-frame)
@ -853,8 +851,7 @@
(list mark-splice))
r)))]
;; Get marks shallower than the splice
[marks (let ([marks (continuation-next-attachments
(metacontinuation-frame-resume-k mf))])
[marks (let ([marks (metacontinuation-frame-marks mf)])
(if (and (pair? marks)
(let ([mark (car marks)])
(or (eq? mark 'empty)
@ -1131,13 +1128,6 @@
(mark-frame-table (coerce-to-mark-frame mark-splice)))
#f)]))
(define (keep-immediate-attachment mark-stack next-mark-stack)
(cond
[(eq? mark-stack next-mark-stack)
empty-mark-frame]
[else
(car mark-stack)]))
;; ----------------------------------------
;; Continuation-mark caching
@ -1828,7 +1818,7 @@
(define-virtual-register current-winders '())
(define-record winder (depth k pre post))
(define-record winder (depth k marks pre post))
;; Jobs for `dynamic-wind`:
@ -1847,29 +1837,33 @@
;; parameterizations.
(define (dynamic-wind pre thunk post)
((call/cc
(lambda (k)
(let* ([winders (current-winders)]
[winder (make-winder (if (null? winders)
0
(fx+ 1 (winder-depth (car winders))))
k
pre
post)])
(start-uninterrupted 'dw)
(begin
(call-winder-thunk 'dw-pre pre)
(current-winders (cons winder winders))
(end-uninterrupted/call-hook 'dw-body)
(call-with-values (if (#%procedure? thunk)
thunk
(lambda () (|#%app| thunk)))
(lambda args
(start-uninterrupted 'dw-body)
(current-winders winders)
(call-winder-thunk 'dw-post post)
(end-uninterrupted/call-hook 'dw)
(lambda () (#%apply values args))))))))))
(call/cc
(lambda (k) ; continuation to restore while running pre/post thunk to unwind/rewind
(let* ([winders (current-winders)]
[winder (make-winder (if (null? winders)
0
(fx+ 1 (winder-depth (car winders))))
k
(current-mark-stack)
pre
post)])
(start-uninterrupted 'dw)
(begin
(call-winder-thunk 'dw-pre pre)
(current-winders (cons winder winders))
(end-uninterrupted/call-hook 'dw-body)
(call-with-values (if (#%procedure? thunk)
thunk
(lambda () (|#%app| thunk)))
(lambda args
(start-uninterrupted 'dw-body)
(current-winders winders)
(call-winder-thunk 'dw-post post)
(end-uninterrupted/call-hook 'dw)
(if (and (pair? args)
(null? (cdr args)))
(car args)
(#%apply values args)))))))))
(define (call-winder-thunk who thunk)
(with-continuation-mark
@ -1891,7 +1885,9 @@
[winders (cdr winders)])
(current-winders winders)
(let ([thunk (winder-thunk winder)])
((winder-k winder)
(call-in-continuation
(winder-k winder)
(winder-marks winder)
(lambda ()
(call-winder-thunk who thunk)
(k))))))

View File

@ -16,7 +16,7 @@
#define MZSCHEME_VERSION_X 7
#define MZSCHEME_VERSION_Y 6
#define MZSCHEME_VERSION_Z 0
#define MZSCHEME_VERSION_W 15
#define MZSCHEME_VERSION_W 16
/* A level of indirection makes `#` work as needed: */
#define AS_a_STR_HELPER(x) #x