diff options
author | Andy Wingo <wingo@pobox.com> | 2016-12-12 20:55:08 +0100 |
---|---|---|
committer | Andy Wingo <wingo@pobox.com> | 2016-12-12 21:13:00 +0100 |
commit | 6dd87f4d8c764360c8d22c03f65603ea8b8c9e78 (patch) | |
tree | 6a502663cd206f0b4a46f61e8eb090ba4bd2e78b | |
parent | bf4a97898beac167e8b4f565ce4c7540bed24685 (diff) |
Add suspendable-continuation?
* doc/ref/api-control.texi (Prompt Primitives): Document
suspendable-continuation?.
* libguile/control.c (scm_suspendable_continuation_p): New procedure.
(scm_init_ice_9_control): New extension procedure, defines
suspendable-continuation?.
(scm_init_control): Register scm_init_ice_9_control.
* libguile/eval.c (eval):
* libguile/throw.c (catch):
* libguile/continuations.c (scm_i_make_continuation): Restore resumable
prompt cookie after continuation invocation.
* libguile/vm.c (scm_call_n): Arrange to set resumable_prompt_cookie
during invocation of VM.
* libguile/vm.h (struct scm_vm): Add resumable_prompt_cookie member.
* module/ice-9/control.scm: Export suspendable-continuation?.
* test-suite/tests/control.test ("suspendable-continuation?"): New
test.
-rw-r--r-- | doc/ref/api-control.texi | 27 | ||||
-rw-r--r-- | libguile/continuations.c | 3 | ||||
-rw-r--r-- | libguile/control.c | 25 | ||||
-rw-r--r-- | libguile/eval.c | 3 | ||||
-rw-r--r-- | libguile/throw.c | 3 | ||||
-rw-r--r-- | libguile/vm.c | 13 | ||||
-rw-r--r-- | libguile/vm.h | 1 | ||||
-rw-r--r-- | module/ice-9/control.scm | 6 | ||||
-rw-r--r-- | test-suite/tests/control.test | 27 |
9 files changed, 104 insertions, 4 deletions
diff --git a/doc/ref/api-control.texi b/doc/ref/api-control.texi index f0ded98a2..73fbe3607 100644 --- a/doc/ref/api-control.texi +++ b/doc/ref/api-control.texi @@ -628,6 +628,33 @@ This is equivalent to @code{(call/ec (lambda (@var{k}) @var{body} @dots{}))}. @end deffn +Additionally there is another helper primitive exported by @code{(ice-9 +control)}, so load up that module for @code{suspendable-continuation?}: + +@example +(use-modules (ice-9 control)) +@end example + +@deffn {Scheme Procedure} suspendable-continuation? tag +Return @code{#t} if a call to @code{abort-to-prompt} with the prompt tag +@var{tag} would produce a delimited continuation that could be resumed +later. + +Almost all continuations have this property. The exception is where +some code between the @code{call-with-prompt} and the +@code{abort-to-prompt} recursed through C for some reason, the +@code{abort-to-prompt} will succeed but any attempt to resume the +continuation (by calling it) would fail. This is because composing a +saved continuation with the current continuation involves relocating the +stack frames that were saved from the old stack onto a (possibly) new +position on the new stack, and Guile can only do this for stack frames +that it created for Scheme code, not stack frames created by the C +compiler. It's a bit gnarly but if you stick with Scheme, you won't +have any problem. + +If no prompt is found with the given tag, this procedure just returns +@code{#f}. +@end deffn @node Shift and Reset @subsubsection Shift, Reset, and All That diff --git a/libguile/continuations.c b/libguile/continuations.c index 5d146f4a1..3eb31a0f9 100644 --- a/libguile/continuations.c +++ b/libguile/continuations.c @@ -121,6 +121,7 @@ scm_i_make_continuation (int *first, struct scm_vm *vp, SCM vm_cont) SCM cont; scm_t_contregs *continuation; long stack_size; + const void *saved_cookie; SCM_STACKITEM * src; SCM_FLUSH_REGISTER_WINDOWS; @@ -138,6 +139,7 @@ scm_i_make_continuation (int *first, struct scm_vm *vp, SCM vm_cont) memcpy (continuation->stack, src, sizeof (SCM_STACKITEM) * stack_size); continuation->vp = vp; continuation->vm_cont = vm_cont; + saved_cookie = vp->resumable_prompt_cookie; SCM_NEWSMOB (cont, tc16_continuation, continuation); @@ -161,6 +163,7 @@ scm_i_make_continuation (int *first, struct scm_vm *vp, SCM vm_cont) } else { + vp->resumable_prompt_cookie = saved_cookie; scm_gc_after_nonlocal_exit (); return SCM_UNDEFINED; } diff --git a/libguile/control.c b/libguile/control.c index c0bc62ddb..6691d551f 100644 --- a/libguile/control.c +++ b/libguile/control.c @@ -205,10 +205,35 @@ SCM_DEFINE (scm_abort_to_prompt_star, "abort-to-prompt*", 2, 0, 0, } #undef FUNC_NAME +static SCM +scm_suspendable_continuation_p (SCM tag) +{ + scm_t_dynstack_prompt_flags flags; + scm_i_thread *thread = SCM_I_CURRENT_THREAD; + scm_i_jmp_buf *registers; + + if (scm_dynstack_find_prompt (&thread->dynstack, tag, &flags, + NULL, NULL, NULL, ®isters)) + return scm_from_bool (registers == thread->vp->resumable_prompt_cookie); + + return SCM_BOOL_F; +} + +static void +scm_init_ice_9_control (void *unused) +{ + scm_c_define_gsubr ("suspendable-continuation?", 1, 0, 0, + scm_suspendable_continuation_p); +} + void scm_init_control (void) { #include "libguile/control.x" + + scm_c_register_extension ("libguile-" SCM_EFFECTIVE_VERSION, + "scm_init_ice_9_control", scm_init_ice_9_control, + NULL); } /* diff --git a/libguile/eval.c b/libguile/eval.c index 87e6eacbf..93788ebfc 100644 --- a/libguile/eval.c +++ b/libguile/eval.c @@ -425,6 +425,7 @@ eval (SCM x, SCM env) struct scm_vm *vp; SCM k, handler, res; scm_i_jmp_buf registers; + const void *prev_cookie; scm_t_ptrdiff saved_stack_depth; k = EVAL1 (CAR (mx), env); @@ -442,9 +443,11 @@ eval (SCM x, SCM env) vp->ip, ®isters); + prev_cookie = vp->resumable_prompt_cookie; if (SCM_I_SETJMP (registers)) { /* The prompt exited nonlocally. */ + vp->resumable_prompt_cookie = prev_cookie; scm_gc_after_nonlocal_exit (); proc = handler; args = scm_i_prompt_pop_abort_args_x (vp, saved_stack_depth); diff --git a/libguile/throw.c b/libguile/throw.c index a6a95bab1..c3a46161b 100644 --- a/libguile/throw.c +++ b/libguile/throw.c @@ -78,6 +78,7 @@ catch (SCM tag, SCM thunk, SCM handler, SCM pre_unwind_handler) scm_t_dynstack *dynstack = &SCM_I_CURRENT_THREAD->dynstack; scm_t_dynamic_state *dynamic_state = SCM_I_CURRENT_THREAD->dynamic_state; scm_i_jmp_buf registers; + const void *prev_cookie; scm_t_ptrdiff saved_stack_depth; if (!scm_is_eq (tag, SCM_BOOL_T) && !scm_is_symbol (tag)) @@ -102,6 +103,7 @@ catch (SCM tag, SCM thunk, SCM handler, SCM pre_unwind_handler) scm_c_vector_set_x (eh, 3, pre_unwind_handler); vp = scm_the_vm (); + prev_cookie = vp->resumable_prompt_cookie; saved_stack_depth = vp->stack_top - vp->sp; /* Push the prompt and exception handler onto the dynamic stack. */ @@ -120,6 +122,7 @@ catch (SCM tag, SCM thunk, SCM handler, SCM pre_unwind_handler) /* A non-local return. */ SCM args; + vp->resumable_prompt_cookie = prev_cookie; scm_gc_after_nonlocal_exit (); /* FIXME: We know where the args will be on the stack; we could diff --git a/libguile/vm.c b/libguile/vm.c index cc7bbf158..194f989ad 100644 --- a/libguile/vm.c +++ b/libguile/vm.c @@ -1234,8 +1234,11 @@ scm_call_n (SCM proc, SCM *argv, size_t nargs) { scm_i_jmp_buf registers; - int resume = SCM_I_SETJMP (registers); - + int resume; + const void *prev_cookie = vp->resumable_prompt_cookie; + SCM ret; + + resume = SCM_I_SETJMP (registers); if (SCM_UNLIKELY (resume)) { scm_gc_after_nonlocal_exit (); @@ -1243,7 +1246,11 @@ scm_call_n (SCM proc, SCM *argv, size_t nargs) vm_dispatch_abort_hook (vp); } - return vm_engines[vp->engine](thread, vp, ®isters, resume); + vp->resumable_prompt_cookie = ®isters; + ret = vm_engines[vp->engine](thread, vp, ®isters, resume); + vp->resumable_prompt_cookie = prev_cookie; + + return ret; } } diff --git a/libguile/vm.h b/libguile/vm.h index 2ca4f2ab4..b26f7f406 100644 --- a/libguile/vm.h +++ b/libguile/vm.h @@ -47,6 +47,7 @@ struct scm_vm { union scm_vm_stack_element *stack_top; /* highest address in allocated stack */ SCM overflow_handler_stack; /* alist of max-stack-size -> thunk */ SCM hooks[SCM_VM_NUM_HOOKS]; /* hooks */ + const void *resumable_prompt_cookie; /* opaque cookie */ int engine; /* which vm engine we're using */ }; diff --git a/module/ice-9/control.scm b/module/ice-9/control.scm index 3eb71a483..edd184659 100644 --- a/module/ice-9/control.scm +++ b/module/ice-9/control.scm @@ -23,7 +23,11 @@ default-prompt-tag make-prompt-tag) #:export (% abort shift reset shift* reset* call-with-escape-continuation call/ec - let-escape-continuation let/ec)) + let-escape-continuation let/ec + suspendable-continuation?)) + +(load-extension (string-append "libguile-" (effective-version)) + "scm_init_ice_9_control") (define (abort . args) (apply abort-to-prompt (default-prompt-tag) args)) diff --git a/test-suite/tests/control.test b/test-suite/tests/control.test index 52ce6b138..4ca8ed8cd 100644 --- a/test-suite/tests/control.test +++ b/test-suite/tests/control.test @@ -410,3 +410,30 @@ (cons (car xs) (k (cdr xs)))))))) (reset* (lambda () (visit xs)))) (traverse '(1 2 3 4 5)))))) + +(with-test-prefix "suspendable-continuation?" + (let ((tag (make-prompt-tag))) + (pass-if "escape-only" + (call-with-prompt tag + (lambda () + (suspendable-continuation? tag)) + (lambda _ (error "unreachable")))) + (pass-if "full" + (call-with-prompt tag + (lambda () + (suspendable-continuation? tag)) + (lambda (k) (error "unreachable" k)))) + (pass-if "escape-only with barrier" + (call-with-prompt tag + (lambda () + (with-continuation-barrier + (lambda () + (not (suspendable-continuation? tag))))) + (lambda _ (error "unreachable")))) + (pass-if "full with barrier" + (call-with-prompt tag + (lambda () + (with-continuation-barrier + (lambda () + (not (suspendable-continuation? tag))))) + (lambda (k) (error "unreachable" k)))))) |