Created
January 23, 2010 18:38
-
-
Save patrickt/284739 to your computer and use it in GitHub Desktop.
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
#include "ruby/ruby.h" | |
#include "ruby/node.h" | |
#include "vm.h" | |
static VALUE rb_cContinuation; | |
#define GET_THREAD() GetThreadPtr(rb_vm_current_thread()) | |
#define GetContPtr(obj, ptr) \ | |
Data_Get_Struct(obj, rb_vm_context_t, ptr) | |
typedef struct rb_vm_context { | |
VALUE self; // the owning object, usually a Continuation | |
int argc; // the number of arguments passed to #call | |
VALUE retval; // the value passed to #call. | |
rb_vm_thread_t thread; // the MacRuby thread on which the continuation was created. | |
jmp_buf jmpbuf; // the buffer we use to store registers | |
VALUE alive; // debugging; have we called this yet? | |
} rb_vm_context_t; | |
NOINLINE(static VALUE cont_capture(volatile int *stat)); | |
static rb_vm_context_t* | |
cont_new(VALUE klass) | |
{ | |
rb_vm_thread_t *current_thread; | |
rb_vm_context_t *ctx; | |
volatile VALUE continuation; | |
continuation = Data_Make_Struct(klass, rb_vm_context_t, NULL, NULL, ctx); | |
current_thread = GetThreadPtr(rb_vm_current_thread()); | |
ctx->self = continuation; | |
ctx->thread = *current_thread; | |
ctx->alive = Qfalse; | |
return ctx; | |
} | |
static void | |
cont_save_machine_stack(rb_vm_thread_t *th, rb_vm_context_t *cont) | |
{ | |
// there's a lot of stuff going on in the YARV implementation that doesn't make much sense to me. | |
// the first thing they do is get the current loc of the machine's stack with | |
// SET_MACHINE_STACK_END(&th->machine_stack_end);, which expands to | |
// __asm__("mov %%esp, %0" : "=r" (*p)) | |
// apparently the start of the machine's stack is autoprovided by YARV. | |
// i don't know how we would get it with Roxor. | |
// this looks like a helpful link: | |
// http://www.mail-archive.com/[email protected]/msg01646.html | |
// as does this: | |
// http://developer.apple.com/mac/library/qa/qa2005/qa1419.html | |
// pthread.h defines pthread_get_stackaddr_np and pthread_get_stacksize_np. | |
// this is probably our best bet. | |
// so after copying the stack, it calls FLUSH_REGISTER_WINDOWS; | |
// however, defines.h doesn't seem to define that for x86. | |
} | |
static VALUE | |
cont_capture(volatile int *stat) | |
{ | |
rb_vm_context_t *cont = cont_new(rb_cContinuation); | |
rb_vm_thread_t *thread = GET_THREAD(); | |
// there are three stacks we have to copy - the VM's internal stack that | |
// contains all of the variable bindings, the Ruby call stack, and the | |
// machine stack. | |
// here we need to copy the call stack onto the heap so that we can restore it after the longjmp. | |
// in YARV they do this with rb_vm_stack_to_heap(), which allocates heap space for the current thread's stack | |
// inside an rb_control_frame_t which is inside the rb_thread_t. | |
// i'm not sure how to do with MacRuby threads, since pthreads are more or less opaque. | |
// additionally, i don't know whether we could or should use C++ exceptions to capture the stack. | |
// Do you have any ideas, Laurent? | |
// rb_vm_stack_to_heap(thread); | |
printf("Address of the continuation is %p\n", (void*)cont); | |
printf("Int pointer is %p\n", (void*)stat); | |
// now we need to copy the VM's thread stack onto the heap. they do this in YARV | |
// by just copying information from the current thread into the context object. | |
// i don't know what information we have to copy or how to do it. :-( | |
// copy_vm_stack_information_to_context(thread, cont); | |
// now we need to copy the machine-wide stack information to the context. | |
// check the definition of cont_save_machine_stack to see how i think we might do this. | |
cont_save_machine_stack(thread, cont); | |
// in yarv, they use ruby_setjmp, which /.configure defines as _setjmp. | |
// looks like _setjmp doesn't copy the signal mask, just the instructions | |
if(_setjmp(cont->jmpbuf)) { | |
// this is where we run into trouble. after restoring, the stat pointer | |
// is no longer what it *should* be, and I have no idea why. i declared | |
// it volatile, damn it! | |
printf("Int pointer is %p\n", (void*)stat); | |
*stat = 1; // this crashes. | |
VALUE value = cont->retval; | |
cont->retval = Qnil; | |
return value; | |
} else { | |
*stat = 0; | |
printf("Saved context to %p\n", (void*)&cont->jmpbuf); | |
return cont->self; | |
} | |
} | |
NOINLINE(NORETURN(static void cont_restore_1(rb_vm_context_t *))); | |
static void | |
cont_restore_1(rb_vm_context_t *cont) | |
{ | |
//rb_thread_t *th = GET_THREAD(); | |
// rb_thread_t *sth = &cont->thread; | |
// here we have to copy all of the saved thread information | |
// into the current thread. i don't know how to do this, either. | |
_longjmp(cont->jmpbuf, 1); | |
} | |
NORETURN(NOINLINE(static void cont_restore_0(rb_vm_context_t *, VALUE *))); | |
static void | |
cont_restore_0(rb_vm_context_t *cont, VALUE *addr_in_prev_frame) | |
{ | |
// okay, this function is really confusing. | |
// they go to great lengths to get the correct address of the continuation | |
// object in the previous frame, then refuse to do anything with it. | |
// maybe it's just padding used for Fibers? not sure. | |
cont_restore_1(cont); | |
} | |
// this is about the only code i understood from YARV's cont.c | |
static VALUE | |
get_passed_callcc_argument(int argc, VALUE *argv) | |
{ | |
switch(argc) { | |
case 0: | |
return Qnil; | |
case 1: | |
return argv[0]; | |
default: | |
return rb_ary_new4(argc, argv); | |
} | |
} | |
static VALUE | |
rb_cont_call(VALUE self, SEL sel, int argc, VALUE *argv) | |
{ | |
rb_vm_context_t *cont; | |
// rb_vm_thread_t *th = GET_THREAD(); | |
GetContPtr(self, cont); | |
// here we do a bunch of sanity checking. i've commented it out for now, | |
// since i ported it from YARV and don't even know if it works. | |
#if 0 | |
if (!pthread_equal(cont->thread.thread, th->thread)) { | |
rb_raise(rb_eRuntimeError, "continuation called across threads"); | |
} | |
// todo: check that this is not being called from within a trap handler | |
#endif | |
cont->argc = argc; | |
cont->retval = get_passed_callcc_argument(argc, argv); | |
cont_restore_0(cont, &self); | |
return Qnil; // not reached | |
} | |
static VALUE | |
rb_callcc(VALUE self, SEL sel) | |
{ | |
volatile int did_jump = 0; | |
volatile VALUE val = cont_capture(&did_jump); | |
// we need to jump back here after the continuation is called, with the | |
// only difference being that did_jump will be 1 instead of 0. | |
// we might have to save the stack and instruction pointers, since setjmp | |
// and longjmp won't save them across function invocations. | |
if (did_jump) { | |
return val; | |
} else { | |
return rb_yield(val); | |
} | |
} | |
void Init_Continuation_body(void) | |
{ | |
rb_cContinuation = rb_define_class("Continuation", rb_cObject); | |
rb_undef_alloc_func(rb_cContinuation); | |
rb_undef_method(CLASS_OF(rb_cContinuation), "new"); | |
rb_objc_define_method(rb_cContinuation, "call", rb_cont_call, -1); | |
rb_objc_define_module_function(rb_mKernel, "callcc", rb_callcc, 0); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment