source: EcnlProtoTool/trunk/mruby-1.2.0/mrbgems/mruby-fiber/src/fiber.c@ 321

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

文字コードを設定

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