#include <mruby.h>
#include <mruby/array.h>
#include <mruby/class.h>
#include <mruby/error.h>
#include <mruby/proc.h>
#define fiber_ptr(o) ((struct RFiber*)mrb_ptr(o))
#define FIBER_STACK_INIT_SIZE 64
#define FIBER_CI_INIT_SIZE 8
#define CINFO_RESUMED 3
static mrb_value
fiber_init(mrb_state *mrb, mrb_value self)
{
static const struct mrb_context mrb_context_zero = { 0 };
struct RFiber *f = fiber_ptr(self);
struct mrb_context *c;
struct RProc *p;
mrb_callinfo *ci;
mrb_value blk;
size_t slen;
mrb_get_args(mrb, "&!", &blk);
if (f->cxt) {
mrb_raise(mrb, E_RUNTIME_ERROR, "cannot initialize twice");
}
p = mrb_proc_ptr(blk);
if (MRB_PROC_CFUNC_P(p)) {
mrb_raise(mrb, E_FIBER_ERROR, "tried to create Fiber from C defined method");
}
c = (struct mrb_context*)mrb_malloc(mrb, sizeof(struct mrb_context));
*c = mrb_context_zero;
f->cxt = c;
slen = FIBER_STACK_INIT_SIZE;
if (p->body.irep->nregs > slen) {
slen += p->body.irep->nregs;
}
c->stbase = (mrb_value *)mrb_malloc(mrb, slen*sizeof(mrb_value));
c->stend = c->stbase + slen;
{
mrb_value *p = c->stbase;
mrb_value *pend = c->stend;
while (p < pend) {
SET_NIL_VALUE(*p);
p++;
}
}
c->stbase[0] = mrb->c->ci->stack[0];
c->cibase = (mrb_callinfo *)mrb_calloc(mrb, FIBER_CI_INIT_SIZE, sizeof(mrb_callinfo));
c->ciend = c->cibase + FIBER_CI_INIT_SIZE;
c->ci = c->cibase;
ci = c->ci;
mrb_vm_ci_target_class_set(ci, MRB_PROC_TARGET_CLASS(p));
mrb_vm_ci_proc_set(ci, p);
mrb_field_write_barrier(mrb, (struct RBasic*)mrb_obj_ptr(self), (struct RBasic*)p);
ci->stack = c->stbase;
ci[1] = ci[0];
c->ci++;
c->fib = f;
c->status = MRB_FIBER_CREATED;
return self;
}
static struct mrb_context*
fiber_check(mrb_state *mrb, mrb_value fib)
{
struct RFiber *f = fiber_ptr(fib);
mrb_assert(f->tt == MRB_TT_FIBER);
if (!f->cxt) {
mrb_raise(mrb, E_FIBER_ERROR, "uninitialized Fiber");
}
return f->cxt;
}
static mrb_value
fiber_result(mrb_state *mrb, const mrb_value *a, mrb_int len)
{
if (len == 0) return mrb_nil_value();
if (len == 1) return a[0];
return mrb_ary_new_from_values(mrb, len, a);
}
#define MARK_CONTEXT_MODIFY(c) (c)->ci->u.target_class = NULL
static void
fiber_check_cfunc(mrb_state *mrb, struct mrb_context *c)
{
mrb_callinfo *ci;
for (ci = c->ci; ci >= c->cibase; ci--) {
if (ci->cci > 0) {
mrb_raise(mrb, E_FIBER_ERROR, "can't cross C function boundary");
}
}
}
static void
fiber_switch_context(mrb_state *mrb, struct mrb_context *c)
{
if (mrb->c->fib) {
mrb_write_barrier(mrb, (struct RBasic*)mrb->c->fib);
}
c->status = MRB_FIBER_RUNNING;
mrb->c = c;
}
static mrb_value
fiber_error(mrb_state *mrb, const char *mesg)
{
mrb_value str = mrb_str_new_static(mrb, mesg, strlen(mesg));
mrb_value exc = mrb_exc_new_str(mrb, E_FIBER_ERROR, str);
if (mrb->jmp) {
mrb_exc_raise(mrb, exc);
}
mrb->exc = mrb_obj_ptr(exc);
return exc;
}
static mrb_value
fiber_switch(mrb_state *mrb, mrb_value self, mrb_int len, const mrb_value *a, mrb_bool resume, mrb_bool vmexec)
{
struct mrb_context *c = fiber_check(mrb, self);
struct mrb_context *old_c = mrb->c;
enum mrb_fiber_state status;
mrb_value value;
if (resume && c == mrb->c) {
return fiber_error(mrb, "attempt to resume the current fiber");
}
fiber_check_cfunc(mrb, c);
status = c->status;
switch (status) {
case MRB_FIBER_TRANSFERRED:
if (resume) {
return fiber_error(mrb, "resuming transferred fiber");
}
break;
case MRB_FIBER_RUNNING:
case MRB_FIBER_RESUMED:
return fiber_error(mrb, "double resume");
break;
case MRB_FIBER_TERMINATED:
return fiber_error(mrb, "resuming dead fiber");
break;
default:
break;
}
if (resume) {
old_c->status = MRB_FIBER_RESUMED;
c->prev = mrb->c;
}
else {
old_c->status = MRB_FIBER_TRANSFERRED;
c->prev = NULL;
}
fiber_switch_context(mrb, c);
if (status == MRB_FIBER_CREATED) {
mrb_value *b, *e;
if (!c->ci->proc) {
return fiber_error(mrb, "double resume (current)");
}
if (vmexec) {
c->ci--;
}
if (len >= 15) {
mrb_stack_extend(mrb, 3);
c->stbase[1] = mrb_ary_new_from_values(mrb, len, a);
len = 15;
}
else {
mrb_stack_extend(mrb, len+2);
b = c->stbase+1;
e = b + len;
while (b<e) {
*b++ = *a++;
}
}
c->cibase->n = (uint8_t)len;
value = c->stbase[0] = MRB_PROC_ENV(c->cibase->proc)->stack[0];
}
else {
value = fiber_result(mrb, a, len);
if (vmexec) {
c->ci[1].stack[0] = value;
}
}
if (vmexec) {
c->vmexec = TRUE;
value = mrb_vm_exec(mrb, c->ci->proc, c->ci->pc);
mrb->c = old_c;
}
else {
MARK_CONTEXT_MODIFY(c);
}
return value;
}
static mrb_value
fiber_resume(mrb_state *mrb, mrb_value self)
{
const mrb_value *a;
mrb_int len;
mrb_bool vmexec = FALSE;
fiber_check_cfunc(mrb, mrb->c);
mrb_get_args(mrb, "*!", &a, &len);
if (mrb->c->ci->cci > 0) {
vmexec = TRUE;
}
return fiber_switch(mrb, self, len, a, TRUE, vmexec);
}
MRB_API mrb_value
mrb_fiber_resume(mrb_state *mrb, mrb_value fib, mrb_int len, const mrb_value *a)
{
return fiber_switch(mrb, fib, len, a, TRUE, TRUE);
}
MRB_API mrb_value
mrb_fiber_alive_p(mrb_state *mrb, mrb_value self)
{
struct mrb_context *c = fiber_check(mrb, self);
return mrb_bool_value(c->status != MRB_FIBER_TERMINATED);
}
#define fiber_alive_p mrb_fiber_alive_p
static mrb_value
fiber_eq(mrb_state *mrb, mrb_value self)
{
mrb_value other = mrb_get_arg1(mrb);
if (!mrb_fiber_p(other)) {
return mrb_false_value();
}
return mrb_bool_value(fiber_ptr(self) == fiber_ptr(other));
}
static mrb_value
fiber_transfer(mrb_state *mrb, mrb_value self)
{
struct mrb_context *c = fiber_check(mrb, self);
const mrb_value* a;
mrb_int len;
fiber_check_cfunc(mrb, mrb->c);
mrb_get_args(mrb, "*!", &a, &len);
if (c->status == MRB_FIBER_RESUMED) {
mrb_raise(mrb, E_FIBER_ERROR, "attempt to transfer to a resuming fiber");
}
if (c == mrb->root_c) {
mrb->c->status = MRB_FIBER_TRANSFERRED;
fiber_switch_context(mrb, c);
MARK_CONTEXT_MODIFY(c);
return fiber_result(mrb, a, len);
}
if (c == mrb->c) {
return fiber_result(mrb, a, len);
}
return fiber_switch(mrb, self, len, a, FALSE, FALSE);
}
MRB_API mrb_value
mrb_fiber_yield(mrb_state *mrb, mrb_int len, const mrb_value *a)
{
struct mrb_context *c = mrb->c;
if (!c->prev) {
return fiber_error(mrb, "attempt to yield on a not resumed fiber");
}
if (c == mrb->root_c) {
return fiber_error(mrb, "can't yield from root fiber");
}
if (c->prev->status == MRB_FIBER_TRANSFERRED) {
return fiber_error(mrb, "attempt to yield on a not resumed fiber");
}
fiber_check_cfunc(mrb, c);
c->status = MRB_FIBER_SUSPENDED;
fiber_switch_context(mrb, c->prev);
c->prev = NULL;
if (c->vmexec) {
c->vmexec = FALSE;
mrb->c->ci->cci = CINFO_RESUMED;
c->ci--;
}
MARK_CONTEXT_MODIFY(mrb->c);
return fiber_result(mrb, a, len);
}
static mrb_value
fiber_yield(mrb_state *mrb, mrb_value self)
{
const mrb_value *a;
mrb_int len;
mrb_get_args(mrb, "*!", &a, &len);
return mrb_fiber_yield(mrb, len, a);
}
static mrb_value
fiber_current(mrb_state *mrb, mrb_value self)
{
if (!mrb->c->fib) {
struct RFiber *f = MRB_OBJ_ALLOC(mrb, MRB_TT_FIBER, mrb_class_ptr(self));
f->cxt = mrb->c;
mrb->c->fib = f;
}
return mrb_obj_value(mrb->c->fib);
}
void
mrb_mruby_fiber_gem_init(mrb_state* mrb)
{
struct RClass *c;
c = mrb_define_class(mrb, "Fiber", mrb->object_class);
MRB_SET_INSTANCE_TT(c, MRB_TT_FIBER);
mrb_define_method(mrb, c, "initialize", fiber_init, MRB_ARGS_NONE()|MRB_ARGS_BLOCK());
mrb_define_method(mrb, c, "resume", fiber_resume, MRB_ARGS_ANY());
mrb_define_method(mrb, c, "transfer", fiber_transfer, MRB_ARGS_ANY());
mrb_define_method(mrb, c, "alive?", fiber_alive_p, MRB_ARGS_NONE());
mrb_define_method(mrb, c, "==", fiber_eq, MRB_ARGS_REQ(1));
mrb_define_class_method(mrb, c, "yield", fiber_yield, MRB_ARGS_ANY());
mrb_define_class_method(mrb, c, "current", fiber_current, MRB_ARGS_NONE());
mrb_define_class(mrb, "FiberError", mrb->eStandardError_class);
}
void
mrb_mruby_fiber_gem_final(mrb_state* mrb)
{
}