/********************************************************************** cont.c - $Author$ $Date$ created at: Thu May 23 09:03:43 2007 Copyright (C) 2007 Koichi Sasada **********************************************************************/ #include "ruby.h" #include "yarvcore.h" #include "gc.h" #include "eval_intern.h" typedef struct rb_continuation_struct { rb_thread_t saved_thread; rb_jmpbuf_t jmpbuf; VALUE retval; VALUE *vm_stack; VALUE *machine_stack; VALUE *machine_stack_src; int machine_stack_size; } rb_continuation_t; #define GetContPtr(obj, ptr) \ Data_Get_Struct(obj, rb_continuation_t, ptr) NOINLINE(static VALUE cont_capture(volatile int *stat)); void rb_thread_mark(rb_thread_t *th); static void cont_mark(void *ptr) { MARK_REPORT_ENTER("cont"); if (ptr) { rb_continuation_t *cont = ptr; rb_gc_mark(cont->retval); rb_thread_mark(&cont->saved_thread); if (cont->vm_stack) { rb_gc_mark_locations(cont->vm_stack, cont->vm_stack + cont->saved_thread.stack_size); } if (cont->machine_stack) { rb_gc_mark_locations(cont->machine_stack, cont->machine_stack + cont->machine_stack_size); } } MARK_REPORT_LEAVE("cont"); } static void cont_free(void *ptr) { FREE_REPORT_ENTER("cont"); if (ptr) { rb_continuation_t *cont = ptr; FREE_UNLESS_NULL(cont->machine_stack); FREE_UNLESS_NULL(cont->vm_stack); ruby_xfree(ptr); } FREE_REPORT_LEAVE("cont"); } static VALUE cont_capture(volatile int *stat) { rb_continuation_t *cont; VALUE contval; rb_thread_t *th = GET_THREAD(), *sth; int size; contval = Data_Make_Struct(rb_cCont, rb_continuation_t, cont_mark, cont_free, cont); /* save context */ cont->saved_thread = *th; sth = &cont->saved_thread; sth->stack = 0; /* clear to skip GC marking */ cont->vm_stack = ALLOC_N(VALUE, sth->stack_size); MEMCPY(cont->vm_stack, th->stack, VALUE, sth->stack_size); rb_gc_set_stack_end(&th->machine_stack_end); if (th->machine_stack_start > th->machine_stack_end) { size = cont->machine_stack_size = th->machine_stack_start - th->machine_stack_end; cont->machine_stack_src = th->machine_stack_end; } else { size = cont->machine_stack_size = th->machine_stack_end - th->machine_stack_start; cont->machine_stack_src = th->machine_stack_start; } cont->machine_stack = ALLOC_N(VALUE, size); MEMCPY(cont->machine_stack, cont->machine_stack_src, VALUE, size); if (ruby_setjmp(cont->jmpbuf)) { VALUE retval; retval = cont->retval; cont->retval = Qnil; *stat = 1; return retval; } else { *stat = 0; return contval; } } static void cont_restore_context_1(rb_continuation_t *cont) { rb_thread_t *th = GET_THREAD(), *sth = &cont->saved_thread; /* restore thread context */ MEMCPY(th->stack, cont->vm_stack, VALUE, sth->stack_size); th->cfp = sth->cfp; th->safe_level = sth->safe_level; th->raised_flag = sth->raised_flag; th->state = sth->state; th->status = sth->status; th->tag = sth->tag; /* restore machine stack */ MEMCPY(cont->machine_stack_src, cont->machine_stack, VALUE, cont->machine_stack_size); ruby_longjmp(cont->jmpbuf, 1); } NORETURN(NOINLINE(static void restore_context_0(rb_continuation_t *, VALUE *))); static void cont_restore_context_0(rb_continuation_t *cont, VALUE *addr_in_prev_frame) { #define STACK_PAD_SIZE 1024 VALUE space[STACK_PAD_SIZE]; #if STACK_GROW_DIRECTION < 0 /* downward */ if (addr_in_prev_frame > cont->machine_stack_src) { cont_restore_context_0(cont, &space[0]); } #elif STACK_GROW_DIRECTION > 0 /* upward */ if (addr_in_prev_frame < cont->machine_stack_src + cont->machine_stack_size) { cont_restore_context_0(cont, &space[STACK_PAD_SIZE-1]); } #else if (addr_in_prev_frame > &space[0]) { /* Stack grows downward */ if (addr_in_prev_frame > cont->saved_thread.machine_stack_src) { cont_restore_context_0(cont, &space[0]); } } else { /* Stack grows upward */ if (addr_in_prev_frame < cont->machine_stack_src + cont->machine_stack_size) { cont_restore_context_0(cont, &space[STACK_PAD_SIZE-1]); } } #endif cont_restore_context_1(cont); } /* * Document-class: Continuation * * Continuation objects are generated by * Kernel#callcc. They hold a return address and execution * context, allowing a nonlocal return to the end of the * callcc block from anywhere within a program. * Continuations are somewhat analogous to a structured version of C's * setjmp/longjmp (although they contain more state, so * you might consider them closer to threads). * * For instance: * * arr = [ "Freddie", "Herbie", "Ron", "Max", "Ringo" ] * callcc{|$cc|} * puts(message = arr.shift) * $cc.call unless message =~ /Max/ * * produces: * * Freddie * Herbie * Ron * Max * * This (somewhat contrived) example allows the inner loop to abandon * processing early: * * callcc {|cont| * for i in 0..4 * print "\n#{i}: " * for j in i*5...(i+1)*5 * cont.call() if j == 17 * printf "%3d", j * end * end * } * print "\n" * * produces: * * 0: 0 1 2 3 4 * 1: 5 6 7 8 9 * 2: 10 11 12 13 14 * 3: 15 16 */ VALUE rb_cCont; /* * call-seq: * callcc {|cont| block } => obj * * Generates a Continuation object, which it passes to the * associated block. Performing a cont.call will * cause the callcc to return (as will falling through the * end of the block). The value returned by the callcc is * the value of the block, or the value passed to * cont.call. See class Continuation * for more details. Also see Kernel::throw for * an alternative mechanism for unwinding a call stack. */ static VALUE rb_callcc(VALUE self) { volatile int called; volatile VALUE val = cont_capture(&called); if (called) { return val; } else { return rb_yield(val); } } /* * call-seq: * cont.call(args, ...) * cont[args, ...] * * Invokes the continuation. The program continues from the end of the * callcc block. If no arguments are given, the original * callcc returns nil. If one argument is * given, callcc returns it. Otherwise, an array * containing args is returned. * * callcc {|cont| cont.call } #=> nil * callcc {|cont| cont.call 1 } #=> 1 * callcc {|cont| cont.call 1, 2, 3 } #=> [1, 2, 3] */ static VALUE rb_cont_call(int argc, VALUE *argv, VALUE contval) { rb_continuation_t *cont; rb_thread_t *th = GET_THREAD(); GetContPtr(contval, cont); if (cont->saved_thread.value != th->value) { rb_raise(rb_eRuntimeError, "continuation called across threads"); } if (cont->saved_thread.trap_tag != th->trap_tag) { rb_raise(rb_eRuntimeError, "continuation called across trap"); } switch(argc) { case 0: cont->retval = Qnil; break; case 1: cont->retval = argv[0]; break; default: cont->retval = rb_ary_new4(argc, argv); break; } cont_restore_context_0(cont, (VALUE *)&cont); return Qnil; /* unreachable */ } void Init_Cont(void) { rb_cCont = rb_define_class("Continuation", rb_cObject); rb_undef_alloc_func(rb_cCont); rb_undef_method(CLASS_OF(rb_cCont), "new"); rb_define_method(rb_cCont, "call", rb_cont_call, -1); rb_define_method(rb_cCont, "[]", rb_cont_call, -1); rb_define_global_function("callcc", rb_callcc, 0); }