source: EcnlProtoTool/trunk/mruby-2.1.1/mrbgems/mruby-fiber/src/fiber.c@ 439

Last change on this file since 439 was 439, checked in by coas-nagasima, 4 years ago

mrubyを2.1.1に更新

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