[331] | 1 | #include <mruby.h>
|
---|
| 2 | #include <mruby/array.h>
|
---|
| 3 | #include <mruby/class.h>
|
---|
| 4 | #include <mruby/proc.h>
|
---|
[270] | 5 |
|
---|
| 6 | #define fiber_ptr(o) ((struct RFiber*)mrb_ptr(o))
|
---|
| 7 |
|
---|
| 8 | #define FIBER_STACK_INIT_SIZE 64
|
---|
| 9 | #define FIBER_CI_INIT_SIZE 8
|
---|
[331] | 10 | #define CI_ACC_RESUMED -3
|
---|
[270] | 11 |
|
---|
| 12 | /*
|
---|
| 13 | * call-seq:
|
---|
| 14 | * Fiber.new{...} -> obj
|
---|
| 15 | *
|
---|
| 16 | * Creates a fiber, whose execution is suspend until it is explicitly
|
---|
| 17 | * resumed using <code>Fiber#resume</code> method.
|
---|
| 18 | * The code running inside the fiber can give up control by calling
|
---|
| 19 | * <code>Fiber.yield</code> in which case it yields control back to caller
|
---|
| 20 | * (the caller of the <code>Fiber#resume</code>).
|
---|
| 21 | *
|
---|
| 22 | * Upon yielding or termination the Fiber returns the value of the last
|
---|
| 23 | * executed expression
|
---|
| 24 | *
|
---|
| 25 | * For instance:
|
---|
| 26 | *
|
---|
| 27 | * fiber = Fiber.new do
|
---|
| 28 | * Fiber.yield 1
|
---|
| 29 | * 2
|
---|
| 30 | * end
|
---|
| 31 | *
|
---|
| 32 | * puts fiber.resume
|
---|
| 33 | * puts fiber.resume
|
---|
| 34 | * puts fiber.resume
|
---|
| 35 | *
|
---|
| 36 | * <em>produces</em>
|
---|
| 37 | *
|
---|
| 38 | * 1
|
---|
| 39 | * 2
|
---|
| 40 | * resuming dead fiber (FiberError)
|
---|
| 41 | *
|
---|
| 42 | * The <code>Fiber#resume</code> method accepts an arbitrary number of
|
---|
| 43 | * parameters, if it is the first call to <code>resume</code> then they
|
---|
| 44 | * will be passed as block arguments. Otherwise they will be the return
|
---|
| 45 | * value of the call to <code>Fiber.yield</code>
|
---|
| 46 | *
|
---|
| 47 | * Example:
|
---|
| 48 | *
|
---|
| 49 | * fiber = Fiber.new do |first|
|
---|
| 50 | * second = Fiber.yield first + 2
|
---|
| 51 | * end
|
---|
| 52 | *
|
---|
| 53 | * puts fiber.resume 10
|
---|
| 54 | * puts fiber.resume 14
|
---|
| 55 | * puts fiber.resume 18
|
---|
| 56 | *
|
---|
| 57 | * <em>produces</em>
|
---|
| 58 | *
|
---|
| 59 | * 12
|
---|
| 60 | * 14
|
---|
| 61 | * resuming dead fiber (FiberError)
|
---|
| 62 | *
|
---|
| 63 | */
|
---|
| 64 | static mrb_value
|
---|
| 65 | fiber_init(mrb_state *mrb, mrb_value self)
|
---|
| 66 | {
|
---|
| 67 | static const struct mrb_context mrb_context_zero = { 0 };
|
---|
| 68 | struct RFiber *f = fiber_ptr(self);
|
---|
| 69 | struct mrb_context *c;
|
---|
| 70 | struct RProc *p;
|
---|
| 71 | mrb_callinfo *ci;
|
---|
| 72 | mrb_value blk;
|
---|
| 73 | size_t slen;
|
---|
| 74 |
|
---|
| 75 | mrb_get_args(mrb, "&", &blk);
|
---|
| 76 |
|
---|
[331] | 77 | if (f->cxt) {
|
---|
| 78 | mrb_raise(mrb, E_RUNTIME_ERROR, "cannot initialize twice");
|
---|
| 79 | }
|
---|
[270] | 80 | if (mrb_nil_p(blk)) {
|
---|
| 81 | mrb_raise(mrb, E_ARGUMENT_ERROR, "tried to create Fiber object without a block");
|
---|
| 82 | }
|
---|
| 83 | p = mrb_proc_ptr(blk);
|
---|
| 84 | if (MRB_PROC_CFUNC_P(p)) {
|
---|
| 85 | mrb_raise(mrb, E_FIBER_ERROR, "tried to create Fiber from C defined method");
|
---|
| 86 | }
|
---|
| 87 |
|
---|
[331] | 88 | c = (struct mrb_context*)mrb_malloc(mrb, sizeof(struct mrb_context));
|
---|
| 89 | *c = mrb_context_zero;
|
---|
| 90 | f->cxt = c;
|
---|
[270] | 91 |
|
---|
| 92 | /* initialize VM stack */
|
---|
| 93 | slen = FIBER_STACK_INIT_SIZE;
|
---|
| 94 | if (p->body.irep->nregs > slen) {
|
---|
| 95 | slen += p->body.irep->nregs;
|
---|
| 96 | }
|
---|
| 97 | c->stbase = (mrb_value *)mrb_malloc(mrb, slen*sizeof(mrb_value));
|
---|
| 98 | c->stend = c->stbase + slen;
|
---|
| 99 | c->stack = c->stbase;
|
---|
| 100 |
|
---|
| 101 | #ifdef MRB_NAN_BOXING
|
---|
| 102 | {
|
---|
| 103 | mrb_value *p = c->stbase;
|
---|
| 104 | mrb_value *pend = c->stend;
|
---|
| 105 |
|
---|
| 106 | while (p < pend) {
|
---|
| 107 | SET_NIL_VALUE(*p);
|
---|
| 108 | p++;
|
---|
| 109 | }
|
---|
| 110 | }
|
---|
| 111 | #else
|
---|
| 112 | memset(c->stbase, 0, slen * sizeof(mrb_value));
|
---|
| 113 | #endif
|
---|
| 114 |
|
---|
| 115 | /* copy receiver from a block */
|
---|
| 116 | c->stack[0] = mrb->c->stack[0];
|
---|
| 117 |
|
---|
| 118 | /* initialize callinfo stack */
|
---|
| 119 | c->cibase = (mrb_callinfo *)mrb_calloc(mrb, FIBER_CI_INIT_SIZE, sizeof(mrb_callinfo));
|
---|
| 120 | c->ciend = c->cibase + FIBER_CI_INIT_SIZE;
|
---|
| 121 | c->ci = c->cibase;
|
---|
| 122 | c->ci->stackent = c->stack;
|
---|
| 123 |
|
---|
| 124 | /* adjust return callinfo */
|
---|
| 125 | ci = c->ci;
|
---|
| 126 | ci->target_class = p->target_class;
|
---|
| 127 | ci->proc = p;
|
---|
| 128 | ci->pc = p->body.irep->iseq;
|
---|
| 129 | ci->nregs = p->body.irep->nregs;
|
---|
| 130 | ci[1] = ci[0];
|
---|
| 131 | c->ci++; /* push dummy callinfo */
|
---|
| 132 |
|
---|
| 133 | c->fib = f;
|
---|
| 134 | c->status = MRB_FIBER_CREATED;
|
---|
| 135 |
|
---|
| 136 | return self;
|
---|
| 137 | }
|
---|
| 138 |
|
---|
| 139 | static struct mrb_context*
|
---|
| 140 | fiber_check(mrb_state *mrb, mrb_value fib)
|
---|
| 141 | {
|
---|
| 142 | struct RFiber *f = fiber_ptr(fib);
|
---|
| 143 |
|
---|
| 144 | mrb_assert(f->tt == MRB_TT_FIBER);
|
---|
| 145 | if (!f->cxt) {
|
---|
| 146 | mrb_raise(mrb, E_FIBER_ERROR, "uninitialized Fiber");
|
---|
| 147 | }
|
---|
| 148 | return f->cxt;
|
---|
| 149 | }
|
---|
| 150 |
|
---|
| 151 | static mrb_value
|
---|
| 152 | fiber_result(mrb_state *mrb, const mrb_value *a, mrb_int len)
|
---|
| 153 | {
|
---|
| 154 | if (len == 0) return mrb_nil_value();
|
---|
| 155 | if (len == 1) return a[0];
|
---|
| 156 | return mrb_ary_new_from_values(mrb, len, a);
|
---|
| 157 | }
|
---|
| 158 |
|
---|
| 159 | /* mark return from context modifying method */
|
---|
| 160 | #define MARK_CONTEXT_MODIFY(c) (c)->ci->target_class = NULL
|
---|
| 161 |
|
---|
[331] | 162 | static void
|
---|
| 163 | fiber_check_cfunc(mrb_state *mrb, struct mrb_context *c)
|
---|
[270] | 164 | {
|
---|
| 165 | mrb_callinfo *ci;
|
---|
| 166 |
|
---|
| 167 | for (ci = c->ci; ci >= c->cibase; ci--) {
|
---|
| 168 | if (ci->acc < 0) {
|
---|
| 169 | mrb_raise(mrb, E_FIBER_ERROR, "can't cross C function boundary");
|
---|
| 170 | }
|
---|
| 171 | }
|
---|
[331] | 172 | }
|
---|
| 173 |
|
---|
| 174 | static void
|
---|
| 175 | fiber_switch_context(mrb_state *mrb, struct mrb_context *c)
|
---|
| 176 | {
|
---|
| 177 | if (mrb->c->fib) {
|
---|
| 178 | mrb_write_barrier(mrb, (struct RBasic*)mrb->c->fib);
|
---|
| 179 | }
|
---|
| 180 | c->status = MRB_FIBER_RUNNING;
|
---|
| 181 | mrb->c = c;
|
---|
| 182 | }
|
---|
| 183 |
|
---|
| 184 | static mrb_value
|
---|
| 185 | fiber_switch(mrb_state *mrb, mrb_value self, mrb_int len, const mrb_value *a, mrb_bool resume, mrb_bool vmexec)
|
---|
| 186 | {
|
---|
| 187 | struct mrb_context *c = fiber_check(mrb, self);
|
---|
| 188 | struct mrb_context *old_c = mrb->c;
|
---|
| 189 | mrb_value value;
|
---|
| 190 |
|
---|
| 191 | fiber_check_cfunc(mrb, c);
|
---|
[270] | 192 | if (resume && c->status == MRB_FIBER_TRANSFERRED) {
|
---|
| 193 | mrb_raise(mrb, E_FIBER_ERROR, "resuming transferred fiber");
|
---|
| 194 | }
|
---|
[331] | 195 | if (c->status == MRB_FIBER_RUNNING || c->status == MRB_FIBER_RESUMED) {
|
---|
| 196 | mrb_raise(mrb, E_FIBER_ERROR, "double resume (fib)");
|
---|
[270] | 197 | }
|
---|
| 198 | if (c->status == MRB_FIBER_TERMINATED) {
|
---|
| 199 | mrb_raise(mrb, E_FIBER_ERROR, "resuming dead fiber");
|
---|
| 200 | }
|
---|
[331] | 201 | mrb->c->status = resume ? MRB_FIBER_RESUMED : MRB_FIBER_TRANSFERRED;
|
---|
[270] | 202 | c->prev = resume ? mrb->c : (c->prev ? c->prev : mrb->root_c);
|
---|
| 203 | if (c->status == MRB_FIBER_CREATED) {
|
---|
[331] | 204 | mrb_value *b, *e;
|
---|
[270] | 205 |
|
---|
[331] | 206 | if (len >= c->stend - c->stack) {
|
---|
| 207 | mrb_raise(mrb, E_FIBER_ERROR, "too many arguments to fiber");
|
---|
| 208 | }
|
---|
| 209 | b = c->stack+1;
|
---|
| 210 | e = b + len;
|
---|
[270] | 211 | while (b<e) {
|
---|
| 212 | *b++ = *a++;
|
---|
| 213 | }
|
---|
| 214 | c->cibase->argc = len;
|
---|
[331] | 215 | value = c->stack[0] = c->ci->proc->env->stack[0];
|
---|
| 216 | }
|
---|
| 217 | else {
|
---|
| 218 | value = fiber_result(mrb, a, len);
|
---|
| 219 | }
|
---|
| 220 | fiber_switch_context(mrb, c);
|
---|
[270] | 221 |
|
---|
[331] | 222 | if (vmexec) {
|
---|
| 223 | c->vmexec = TRUE;
|
---|
| 224 | value = mrb_vm_exec(mrb, c->ci[-1].proc, c->ci->pc);
|
---|
| 225 | mrb->c = old_c;
|
---|
| 226 | }
|
---|
| 227 | else {
|
---|
[270] | 228 | MARK_CONTEXT_MODIFY(c);
|
---|
| 229 | }
|
---|
[331] | 230 | return value;
|
---|
[270] | 231 | }
|
---|
| 232 |
|
---|
| 233 | /*
|
---|
| 234 | * call-seq:
|
---|
| 235 | * fiber.resume(args, ...) -> obj
|
---|
| 236 | *
|
---|
| 237 | * Resumes the fiber from the point at which the last <code>Fiber.yield</code>
|
---|
| 238 | * was called, or starts running it if it is the first call to
|
---|
| 239 | * <code>resume</code>. Arguments passed to resume will be the value of
|
---|
| 240 | * the <code>Fiber.yield</code> expression or will be passed as block
|
---|
| 241 | * parameters to the fiber's block if this is the first <code>resume</code>.
|
---|
| 242 | *
|
---|
| 243 | * Alternatively, when resume is called it evaluates to the arguments passed
|
---|
| 244 | * to the next <code>Fiber.yield</code> statement inside the fiber's block
|
---|
| 245 | * or to the block value if it runs to completion without any
|
---|
| 246 | * <code>Fiber.yield</code>
|
---|
| 247 | */
|
---|
| 248 | static mrb_value
|
---|
| 249 | fiber_resume(mrb_state *mrb, mrb_value self)
|
---|
| 250 | {
|
---|
| 251 | mrb_value *a;
|
---|
| 252 | mrb_int len;
|
---|
[331] | 253 | mrb_bool vmexec = FALSE;
|
---|
[270] | 254 |
|
---|
| 255 | mrb_get_args(mrb, "*", &a, &len);
|
---|
[331] | 256 | if (mrb->c->ci->acc < 0) {
|
---|
| 257 | vmexec = TRUE;
|
---|
| 258 | }
|
---|
| 259 | return fiber_switch(mrb, self, len, a, TRUE, vmexec);
|
---|
[270] | 260 | }
|
---|
| 261 |
|
---|
| 262 | /* resume thread with given arguments */
|
---|
| 263 | MRB_API mrb_value
|
---|
| 264 | mrb_fiber_resume(mrb_state *mrb, mrb_value fib, mrb_int len, const mrb_value *a)
|
---|
| 265 | {
|
---|
[331] | 266 | return fiber_switch(mrb, fib, len, a, TRUE, TRUE);
|
---|
[270] | 267 | }
|
---|
| 268 |
|
---|
| 269 | /*
|
---|
| 270 | * call-seq:
|
---|
| 271 | * fiber.alive? -> true or false
|
---|
| 272 | *
|
---|
| 273 | * Returns true if the fiber can still be resumed. After finishing
|
---|
| 274 | * execution of the fiber block this method will always return false.
|
---|
| 275 | */
|
---|
| 276 | static mrb_value
|
---|
| 277 | fiber_alive_p(mrb_state *mrb, mrb_value self)
|
---|
| 278 | {
|
---|
| 279 | struct mrb_context *c = fiber_check(mrb, self);
|
---|
| 280 | return mrb_bool_value(c->status != MRB_FIBER_TERMINATED);
|
---|
| 281 | }
|
---|
| 282 |
|
---|
| 283 | static mrb_value
|
---|
| 284 | fiber_eq(mrb_state *mrb, mrb_value self)
|
---|
| 285 | {
|
---|
| 286 | mrb_value other;
|
---|
| 287 | mrb_get_args(mrb, "o", &other);
|
---|
| 288 |
|
---|
| 289 | if (mrb_type(other) != MRB_TT_FIBER) {
|
---|
| 290 | return mrb_false_value();
|
---|
| 291 | }
|
---|
| 292 | return mrb_bool_value(fiber_ptr(self) == fiber_ptr(other));
|
---|
| 293 | }
|
---|
| 294 |
|
---|
| 295 | /*
|
---|
| 296 | * call-seq:
|
---|
| 297 | * fiber.transfer(args, ...) -> obj
|
---|
| 298 | *
|
---|
| 299 | * Transfers control to receiver fiber of the method call.
|
---|
| 300 | * Unlike <code>resume</code> the receiver wouldn't be pushed to call
|
---|
| 301 | * stack of fibers. Instead it will switch to the call stack of
|
---|
| 302 | * transferring fiber.
|
---|
| 303 | * When resuming a fiber that was transferred to another fiber it would
|
---|
| 304 | * cause double resume error. Though when the fiber is re-transferred
|
---|
| 305 | * and <code>Fiber.yield</code> is called, the fiber would be resumable.
|
---|
| 306 | */
|
---|
| 307 | static mrb_value
|
---|
| 308 | fiber_transfer(mrb_state *mrb, mrb_value self)
|
---|
| 309 | {
|
---|
| 310 | struct mrb_context *c = fiber_check(mrb, self);
|
---|
| 311 | mrb_value* a;
|
---|
| 312 | mrb_int len;
|
---|
| 313 |
|
---|
[331] | 314 | fiber_check_cfunc(mrb, mrb->c);
|
---|
[270] | 315 | mrb_get_args(mrb, "*", &a, &len);
|
---|
| 316 |
|
---|
| 317 | if (c == mrb->root_c) {
|
---|
| 318 | mrb->c->status = MRB_FIBER_TRANSFERRED;
|
---|
[331] | 319 | fiber_switch_context(mrb, c);
|
---|
[270] | 320 | MARK_CONTEXT_MODIFY(c);
|
---|
| 321 | return fiber_result(mrb, a, len);
|
---|
| 322 | }
|
---|
| 323 |
|
---|
| 324 | if (c == mrb->c) {
|
---|
| 325 | return fiber_result(mrb, a, len);
|
---|
| 326 | }
|
---|
| 327 |
|
---|
[331] | 328 | return fiber_switch(mrb, self, len, a, FALSE, FALSE);
|
---|
[270] | 329 | }
|
---|
| 330 |
|
---|
| 331 | /* yield values to the caller fiber */
|
---|
| 332 | /* mrb_fiber_yield() must be called as `return mrb_fiber_yield(...)` */
|
---|
| 333 | MRB_API mrb_value
|
---|
| 334 | mrb_fiber_yield(mrb_state *mrb, mrb_int len, const mrb_value *a)
|
---|
| 335 | {
|
---|
| 336 | struct mrb_context *c = mrb->c;
|
---|
| 337 |
|
---|
| 338 | if (!c->prev) {
|
---|
| 339 | mrb_raise(mrb, E_FIBER_ERROR, "can't yield from root fiber");
|
---|
| 340 | }
|
---|
| 341 |
|
---|
[331] | 342 | fiber_check_cfunc(mrb, c);
|
---|
[270] | 343 | c->prev->status = MRB_FIBER_RUNNING;
|
---|
| 344 | c->status = MRB_FIBER_SUSPENDED;
|
---|
[331] | 345 | fiber_switch_context(mrb, c->prev);
|
---|
[270] | 346 | c->prev = NULL;
|
---|
[331] | 347 | if (c->vmexec) {
|
---|
| 348 | c->vmexec = FALSE;
|
---|
| 349 | mrb->c->ci->acc = CI_ACC_RESUMED;
|
---|
| 350 | }
|
---|
[270] | 351 | MARK_CONTEXT_MODIFY(mrb->c);
|
---|
| 352 | return fiber_result(mrb, a, len);
|
---|
| 353 | }
|
---|
| 354 |
|
---|
| 355 | /*
|
---|
| 356 | * call-seq:
|
---|
| 357 | * Fiber.yield(args, ...) -> obj
|
---|
| 358 | *
|
---|
| 359 | * Yields control back to the context that resumed the fiber, passing
|
---|
| 360 | * along any arguments that were passed to it. The fiber will resume
|
---|
| 361 | * processing at this point when <code>resume</code> is called next.
|
---|
| 362 | * Any arguments passed to the next <code>resume</code> will be the
|
---|
[331] | 363 | *
|
---|
| 364 | * mruby limitation: Fiber resume/yield cannot cross C function boundary.
|
---|
| 365 | * thus you cannot yield from #initialize which is called by mrb_funcall().
|
---|
[270] | 366 | */
|
---|
| 367 | static mrb_value
|
---|
| 368 | fiber_yield(mrb_state *mrb, mrb_value self)
|
---|
| 369 | {
|
---|
| 370 | mrb_value *a;
|
---|
| 371 | mrb_int len;
|
---|
| 372 |
|
---|
| 373 | mrb_get_args(mrb, "*", &a, &len);
|
---|
| 374 | return mrb_fiber_yield(mrb, len, a);
|
---|
| 375 | }
|
---|
| 376 |
|
---|
| 377 | /*
|
---|
| 378 | * call-seq:
|
---|
| 379 | * Fiber.current() -> fiber
|
---|
| 380 | *
|
---|
| 381 | * Returns the current fiber. If you are not running in the context of
|
---|
| 382 | * a fiber this method will return the root fiber.
|
---|
| 383 | */
|
---|
| 384 | static mrb_value
|
---|
| 385 | fiber_current(mrb_state *mrb, mrb_value self)
|
---|
| 386 | {
|
---|
| 387 | if (!mrb->c->fib) {
|
---|
| 388 | struct RFiber *f = (struct RFiber*)mrb_obj_alloc(mrb, MRB_TT_FIBER, mrb_class_ptr(self));
|
---|
| 389 |
|
---|
| 390 | f->cxt = mrb->c;
|
---|
| 391 | mrb->c->fib = f;
|
---|
| 392 | }
|
---|
| 393 | return mrb_obj_value(mrb->c->fib);
|
---|
| 394 | }
|
---|
| 395 |
|
---|
| 396 | void
|
---|
| 397 | mrb_mruby_fiber_gem_init(mrb_state* mrb)
|
---|
| 398 | {
|
---|
| 399 | struct RClass *c;
|
---|
| 400 |
|
---|
| 401 | c = mrb_define_class(mrb, "Fiber", mrb->object_class);
|
---|
| 402 | MRB_SET_INSTANCE_TT(c, MRB_TT_FIBER);
|
---|
| 403 |
|
---|
| 404 | mrb_define_method(mrb, c, "initialize", fiber_init, MRB_ARGS_NONE());
|
---|
| 405 | mrb_define_method(mrb, c, "resume", fiber_resume, MRB_ARGS_ANY());
|
---|
| 406 | mrb_define_method(mrb, c, "transfer", fiber_transfer, MRB_ARGS_ANY());
|
---|
| 407 | mrb_define_method(mrb, c, "alive?", fiber_alive_p, MRB_ARGS_NONE());
|
---|
| 408 | mrb_define_method(mrb, c, "==", fiber_eq, MRB_ARGS_REQ(1));
|
---|
| 409 |
|
---|
| 410 | mrb_define_class_method(mrb, c, "yield", fiber_yield, MRB_ARGS_ANY());
|
---|
| 411 | mrb_define_class_method(mrb, c, "current", fiber_current, MRB_ARGS_NONE());
|
---|
| 412 |
|
---|
| 413 | mrb_define_class(mrb, "FiberError", mrb->eStandardError_class);
|
---|
| 414 | }
|
---|
| 415 |
|
---|
| 416 | void
|
---|
| 417 | mrb_mruby_fiber_gem_final(mrb_state* mrb)
|
---|
| 418 | {
|
---|
| 419 | }
|
---|