Skip to content

Instantly share code, notes, and snippets.

@patrickt
Created January 23, 2010 18:38
Show Gist options
  • Save patrickt/284739 to your computer and use it in GitHub Desktop.
Save patrickt/284739 to your computer and use it in GitHub Desktop.
#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