mbed I/F binding for mruby

Dependents:   mruby_mbed_web mirb_mbed

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers fiber.c Source File

fiber.c

00001 #include "mruby.h"
00002 #include "mruby/array.h"
00003 #include "mruby/class.h"
00004 #include "mruby/proc.h"
00005 
00006 #define fiber_ptr(o) ((struct RFiber*)mrb_ptr(o))
00007 
00008 #define FIBER_STACK_INIT_SIZE 64
00009 #define FIBER_CI_INIT_SIZE 8
00010 
00011 /*
00012  *  call-seq:
00013  *     Fiber.new{...} -> obj
00014  *
00015  *  Creates a fiber, whose execution is suspend until it is explicitly
00016  *  resumed using <code>Fiber#resume</code> method.
00017  *  The code running inside the fiber can give up control by calling
00018  *  <code>Fiber.yield</code> in which case it yields control back to caller
00019  *  (the caller of the <code>Fiber#resume</code>).
00020  *
00021  *  Upon yielding or termination the Fiber returns the value of the last
00022  *  executed expression
00023  *
00024  *  For instance:
00025  *
00026  *    fiber = Fiber.new do
00027  *      Fiber.yield 1
00028  *      2
00029  *    end
00030  *
00031  *    puts fiber.resume
00032  *    puts fiber.resume
00033  *    puts fiber.resume
00034  *
00035  *  <em>produces</em>
00036  *
00037  *    1
00038  *    2
00039  *    resuming dead fiber (FiberError)
00040  *
00041  *  The <code>Fiber#resume</code> method accepts an arbitrary number of
00042  *  parameters, if it is the first call to <code>resume</code> then they
00043  *  will be passed as block arguments. Otherwise they will be the return
00044  *  value of the call to <code>Fiber.yield</code>
00045  *
00046  *  Example:
00047  *
00048  *    fiber = Fiber.new do |first|
00049  *      second = Fiber.yield first + 2
00050  *    end
00051  *
00052  *    puts fiber.resume 10
00053  *    puts fiber.resume 14
00054  *    puts fiber.resume 18
00055  *
00056  *  <em>produces</em>
00057  *
00058  *    12
00059  *    14
00060  *    resuming dead fiber (FiberError)
00061  *
00062  */
00063 static mrb_value
00064 fiber_init(mrb_state *mrb, mrb_value self)
00065 {
00066   static const struct mrb_context mrb_context_zero = { 0 };
00067   struct RFiber *f = fiber_ptr(self);
00068   struct mrb_context *c;
00069   struct RProc *p;
00070   mrb_callinfo *ci;
00071   mrb_value blk;
00072   size_t slen;
00073 
00074   mrb_get_args(mrb, "&", &blk);
00075 
00076   if (mrb_nil_p(blk)) {
00077     mrb_raise(mrb, E_ARGUMENT_ERROR, "tried to create Fiber object without a block");
00078   }
00079   p = mrb_proc_ptr(blk);
00080   if (MRB_PROC_CFUNC_P(p)) {
00081     mrb_raise(mrb, E_FIBER_ERROR, "tried to create Fiber from C defined method");
00082   }
00083 
00084   f->cxt = (struct mrb_context*)mrb_malloc(mrb, sizeof(struct mrb_context));
00085   *f->cxt = mrb_context_zero;
00086   c = f->cxt;
00087 
00088   /* initialize VM stack */
00089   slen = FIBER_STACK_INIT_SIZE;
00090   if (p->body.irep->nregs > slen) {
00091     slen += p->body.irep->nregs;
00092   }
00093   c->stbase = (mrb_value *)mrb_malloc(mrb, slen*sizeof(mrb_value));
00094   c->stend = c->stbase + slen;
00095   c->stack = c->stbase;
00096 
00097 #ifdef MRB_NAN_BOXING
00098   {
00099     mrb_value *p = c->stbase;
00100     mrb_value *pend = c->stend;
00101 
00102     while (p < pend) {
00103       SET_NIL_VALUE(*p);
00104       p++;
00105     }
00106   }
00107 #else
00108   memset(c->stbase, 0, slen * sizeof(mrb_value));
00109 #endif
00110 
00111   /* copy receiver from a block */
00112   c->stack[0] = mrb->c->stack[0];
00113 
00114   /* initialize callinfo stack */
00115   c->cibase = (mrb_callinfo *)mrb_calloc(mrb, FIBER_CI_INIT_SIZE, sizeof(mrb_callinfo));
00116   c->ciend = c->cibase + FIBER_CI_INIT_SIZE;
00117   c->ci = c->cibase;
00118   c->ci->stackent = c->stack;
00119 
00120   /* adjust return callinfo */
00121   ci = c->ci;
00122   ci->target_class = p->target_class;
00123   ci->proc = p;
00124   ci->pc = p->body.irep->iseq;
00125   ci->nregs = p->body.irep->nregs;
00126   ci[1] = ci[0];
00127   c->ci++;                      /* push dummy callinfo */
00128 
00129   c->fib = f;
00130   c->status = MRB_FIBER_CREATED;
00131 
00132   return self;
00133 }
00134 
00135 static struct mrb_context*
00136 fiber_check(mrb_state *mrb, mrb_value fib)
00137 {
00138   struct RFiber *f = fiber_ptr(fib);
00139 
00140   mrb_assert(f->tt == MRB_TT_FIBER);
00141   if (!f->cxt) {
00142     mrb_raise(mrb, E_FIBER_ERROR, "uninitialized Fiber");
00143   }
00144   return f->cxt;
00145 }
00146 
00147 static mrb_value
00148 fiber_result(mrb_state *mrb, const mrb_value *a, mrb_int len)
00149 {
00150   if (len == 0) return mrb_nil_value();
00151   if (len == 1) return a[0];
00152   return mrb_ary_new_from_values(mrb, len, a);
00153 }
00154 
00155 /* mark return from context modifying method */
00156 #define MARK_CONTEXT_MODIFY(c) (c)->ci->target_class = NULL
00157 
00158 static mrb_value
00159 fiber_switch(mrb_state *mrb, mrb_value self, mrb_int len, const mrb_value *a, mrb_bool resume)
00160 {
00161   struct mrb_context *c = fiber_check(mrb, self);
00162   mrb_callinfo *ci;
00163 
00164   for (ci = c->ci; ci >= c->cibase; ci--) {
00165     if (ci->acc < 0) {
00166       mrb_raise(mrb, E_FIBER_ERROR, "can't cross C function boundary");
00167     }
00168   }
00169   if (resume && c->status == MRB_FIBER_TRANSFERRED) {
00170     mrb_raise(mrb, E_FIBER_ERROR, "resuming transfered fiber");
00171   }
00172   if (c->status == MRB_FIBER_RUNNING || c->status == MRB_FIBER_RESUMING) {
00173     mrb_raise(mrb, E_FIBER_ERROR, "double resume");
00174   }
00175   if (c->status == MRB_FIBER_TERMINATED) {
00176     mrb_raise(mrb, E_FIBER_ERROR, "resuming dead fiber");
00177   }
00178   mrb->c->status = resume ? MRB_FIBER_RESUMING : MRB_FIBER_TRANSFERRED;
00179   c->prev = resume ? mrb->c : (c->prev ? c->prev : mrb->root_c);
00180   if (c->status == MRB_FIBER_CREATED) {
00181     mrb_value *b = c->stack+1;
00182     mrb_value *e = b + len;
00183 
00184     while (b<e) {
00185       *b++ = *a++;
00186     }
00187     c->cibase->argc = len;
00188     if (c->prev->fib)
00189       mrb_field_write_barrier(mrb, (struct RBasic*)c->fib, (struct RBasic*)c->prev->fib);
00190     mrb_write_barrier(mrb, (struct RBasic*)c->fib);
00191     c->status = MRB_FIBER_RUNNING;
00192     mrb->c = c;
00193 
00194     MARK_CONTEXT_MODIFY(c);
00195     return c->ci->proc->env->stack[0];
00196   }
00197   MARK_CONTEXT_MODIFY(c);
00198   if (c->prev->fib)
00199     mrb_field_write_barrier(mrb, (struct RBasic*)c->fib, (struct RBasic*)c->prev->fib);
00200   mrb_write_barrier(mrb, (struct RBasic*)c->fib);
00201   c->status = MRB_FIBER_RUNNING;
00202   mrb->c = c;
00203   return fiber_result(mrb, a, len);
00204 }
00205 
00206 /*
00207  *  call-seq:
00208  *     fiber.resume(args, ...) -> obj
00209  *
00210  *  Resumes the fiber from the point at which the last <code>Fiber.yield</code>
00211  *  was called, or starts running it if it is the first call to
00212  *  <code>resume</code>. Arguments passed to resume will be the value of
00213  *  the <code>Fiber.yield</code> expression or will be passed as block
00214  *  parameters to the fiber's block if this is the first <code>resume</code>.
00215  *
00216  *  Alternatively, when resume is called it evaluates to the arguments passed
00217  *  to the next <code>Fiber.yield</code> statement inside the fiber's block
00218  *  or to the block value if it runs to completion without any
00219  *  <code>Fiber.yield</code>
00220  */
00221 static mrb_value
00222 fiber_resume(mrb_state *mrb, mrb_value self)
00223 {
00224   mrb_value *a;
00225   mrb_int len;
00226 
00227   mrb_get_args(mrb, "*", &a, &len);
00228   return fiber_switch(mrb, self, len, a, TRUE);
00229 }
00230 
00231 /*
00232  *  call-seq:
00233  *     fiber.alive? -> true or false
00234  *
00235  *  Returns true if the fiber can still be resumed. After finishing
00236  *  execution of the fiber block this method will always return false.
00237  */
00238 static mrb_value
00239 fiber_alive_p(mrb_state *mrb, mrb_value self)
00240 {
00241   struct mrb_context *c = fiber_check(mrb, self);
00242   return mrb_bool_value(c->status != MRB_FIBER_TERMINATED);
00243 }
00244 
00245 static mrb_value
00246 fiber_eq(mrb_state *mrb, mrb_value self)
00247 {
00248   mrb_value other;
00249   mrb_get_args(mrb, "o", &other);
00250 
00251   if (mrb_type(other) != MRB_TT_FIBER) {
00252     return mrb_false_value();
00253   }
00254   return mrb_bool_value(fiber_ptr(self) == fiber_ptr(other));
00255 }
00256 
00257 /*
00258  *  call-seq:
00259  *     fiber.transfer(args, ...) -> obj
00260  *
00261  *  Transfers control to reciever fiber of the method call.
00262  *  Unlike <code>resume</code> the reciever wouldn't be pushed to call
00263  * stack of fibers. Instead it will switch to the call stack of
00264  * transferring fiber.
00265  *  When resuming a fiber that was transferred to another fiber it would
00266  * cause double resume error. Though when the fiber is re-transferred
00267  * and <code>Fiber.yield</code> is called, the fiber would be resumable.
00268  */
00269 static mrb_value
00270 fiber_transfer(mrb_state *mrb, mrb_value self)
00271 {
00272   struct mrb_context *c = fiber_check(mrb, self);
00273   mrb_value* a;
00274   mrb_int len;
00275 
00276   mrb_get_args(mrb, "*", &a, &len);
00277 
00278   if (c == mrb->root_c) {
00279     mrb->c->status = MRB_FIBER_TRANSFERRED;
00280     mrb->c = c;
00281     c->status = MRB_FIBER_RUNNING;
00282     MARK_CONTEXT_MODIFY(c);
00283     mrb_write_barrier(mrb, (struct RBasic*)c->fib);
00284     return fiber_result(mrb, a, len);
00285   }
00286 
00287   if (c == mrb->c) {
00288     return fiber_result(mrb, a, len);
00289   }
00290 
00291   return fiber_switch(mrb, self, len, a, FALSE);
00292 }
00293 
00294 MRB_API mrb_value
00295 mrb_fiber_yield(mrb_state *mrb, mrb_int len, const mrb_value *a)
00296 {
00297   struct mrb_context *c = mrb->c;
00298   mrb_callinfo *ci;
00299 
00300   for (ci = c->ci; ci >= c->cibase; ci--) {
00301     if (ci->acc < 0) {
00302       mrb_raise(mrb, E_FIBER_ERROR, "can't cross C function boundary");
00303     }
00304   }
00305   if (!c->prev) {
00306     mrb_raise(mrb, E_FIBER_ERROR, "can't yield from root fiber");
00307   }
00308 
00309   c->prev->status = MRB_FIBER_RUNNING;
00310   c->status = MRB_FIBER_SUSPENDED;
00311   mrb->c = c->prev;
00312   c->prev = NULL;
00313   MARK_CONTEXT_MODIFY(mrb->c);
00314   mrb_write_barrier(mrb, (struct RBasic*)c->fib);
00315   return fiber_result(mrb, a, len);
00316 }
00317 
00318 /*
00319  *  call-seq:
00320  *     Fiber.yield(args, ...) -> obj
00321  *
00322  *  Yields control back to the context that resumed the fiber, passing
00323  *  along any arguments that were passed to it. The fiber will resume
00324  *  processing at this point when <code>resume</code> is called next.
00325  *  Any arguments passed to the next <code>resume</code> will be the
00326  *  value that this <code>Fiber.yield</code> expression evaluates to.
00327  */
00328 static mrb_value
00329 fiber_yield(mrb_state *mrb, mrb_value self)
00330 {
00331   mrb_value *a;
00332   mrb_int len;
00333 
00334   mrb_get_args(mrb, "*", &a, &len);
00335   return mrb_fiber_yield(mrb, len, a);
00336 }
00337 
00338 /*
00339  *  call-seq:
00340  *     Fiber.current() -> fiber
00341  *
00342  *  Returns the current fiber. If you are not running in the context of
00343  *  a fiber this method will return the root fiber.
00344  */
00345 static mrb_value
00346 fiber_current(mrb_state *mrb, mrb_value self)
00347 {
00348   if (!mrb->c->fib) {
00349     struct RFiber *f = (struct RFiber*)mrb_obj_alloc(mrb, MRB_TT_FIBER, mrb_class_ptr(self));
00350 
00351     f->cxt = mrb->c;
00352     mrb->c->fib = f;
00353   }
00354   return mrb_obj_value(mrb->c->fib);
00355 }
00356 
00357 void
00358 mrb_mruby_fiber_gem_init(mrb_state* mrb)
00359 {
00360   struct RClass *c;
00361 
00362   c = mrb_define_class(mrb, "Fiber", mrb->object_class);
00363   MRB_SET_INSTANCE_TT(c, MRB_TT_FIBER);
00364 
00365   mrb_define_method(mrb, c, "initialize", fiber_init,    MRB_ARGS_NONE());
00366   mrb_define_method(mrb, c, "resume",     fiber_resume,  MRB_ARGS_ANY());
00367   mrb_define_method(mrb, c, "transfer",   fiber_transfer, MRB_ARGS_ANY());
00368   mrb_define_method(mrb, c, "alive?",     fiber_alive_p, MRB_ARGS_NONE());
00369   mrb_define_method(mrb, c, "==",         fiber_eq,      MRB_ARGS_REQ(1));
00370 
00371   mrb_define_class_method(mrb, c, "yield", fiber_yield, MRB_ARGS_ANY());
00372   mrb_define_class_method(mrb, c, "current", fiber_current, MRB_ARGS_NONE());
00373 
00374   mrb_define_class(mrb, "FiberError", mrb->eStandardError_class);
00375 }
00376 
00377 void
00378 mrb_mruby_fiber_gem_final(mrb_state* mrb)
00379 {
00380 }
00381