source: EcnlProtoTool/trunk/mruby-1.3.0/mrbgems/mruby-fiber/src/fiber.c@ 331

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

prototoolに関連するプロジェクトをnewlibからmuslを使うよう変更・更新
ntshellをnewlibの下位の実装から、muslのsyscallの実装に変更・更新
以下のOSSをアップデート
・mruby-1.3.0
・musl-1.1.18
・onigmo-6.1.3
・tcc-0.9.27
以下のOSSを追加
・openssl-1.1.0e
・curl-7.57.0
・zlib-1.2.11
以下のmrbgemsを追加
・iij/mruby-digest
・iij/mruby-env
・iij/mruby-errno
・iij/mruby-iijson
・iij/mruby-ipaddr
・iij/mruby-mock
・iij/mruby-require
・iij/mruby-tls-openssl

  • Property svn:eol-style set to native
  • Property svn:mime-type set to text/x-csrc;charset=UTF-8
File size: 10.8 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 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
88 c = (struct mrb_context*)mrb_malloc(mrb, sizeof(struct mrb_context));
89 *c = mrb_context_zero;
90 f->cxt = c;
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
139static struct mrb_context*
140fiber_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
151static mrb_value
152fiber_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
162static void
163fiber_check_cfunc(mrb_state *mrb, struct mrb_context *c)
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 }
172}
173
174static void
175fiber_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
184static mrb_value
185fiber_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);
192 if (resume && c->status == MRB_FIBER_TRANSFERRED) {
193 mrb_raise(mrb, E_FIBER_ERROR, "resuming transferred fiber");
194 }
195 if (c->status == MRB_FIBER_RUNNING || c->status == MRB_FIBER_RESUMED) {
196 mrb_raise(mrb, E_FIBER_ERROR, "double resume (fib)");
197 }
198 if (c->status == MRB_FIBER_TERMINATED) {
199 mrb_raise(mrb, E_FIBER_ERROR, "resuming dead fiber");
200 }
201 mrb->c->status = resume ? MRB_FIBER_RESUMED : MRB_FIBER_TRANSFERRED;
202 c->prev = resume ? mrb->c : (c->prev ? c->prev : mrb->root_c);
203 if (c->status == MRB_FIBER_CREATED) {
204 mrb_value *b, *e;
205
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;
211 while (b<e) {
212 *b++ = *a++;
213 }
214 c->cibase->argc = len;
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);
221
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 {
228 MARK_CONTEXT_MODIFY(c);
229 }
230 return value;
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 */
248static mrb_value
249fiber_resume(mrb_state *mrb, mrb_value self)
250{
251 mrb_value *a;
252 mrb_int len;
253 mrb_bool vmexec = FALSE;
254
255 mrb_get_args(mrb, "*", &a, &len);
256 if (mrb->c->ci->acc < 0) {
257 vmexec = TRUE;
258 }
259 return fiber_switch(mrb, self, len, a, TRUE, vmexec);
260}
261
262/* resume thread with given arguments */
263MRB_API mrb_value
264mrb_fiber_resume(mrb_state *mrb, mrb_value fib, mrb_int len, const mrb_value *a)
265{
266 return fiber_switch(mrb, fib, len, a, TRUE, TRUE);
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 */
276static mrb_value
277fiber_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
283static mrb_value
284fiber_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 */
307static mrb_value
308fiber_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
314 fiber_check_cfunc(mrb, mrb->c);
315 mrb_get_args(mrb, "*", &a, &len);
316
317 if (c == mrb->root_c) {
318 mrb->c->status = MRB_FIBER_TRANSFERRED;
319 fiber_switch_context(mrb, c);
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
328 return fiber_switch(mrb, self, len, a, FALSE, FALSE);
329}
330
331/* yield values to the caller fiber */
332/* mrb_fiber_yield() must be called as `return mrb_fiber_yield(...)` */
333MRB_API mrb_value
334mrb_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
342 fiber_check_cfunc(mrb, c);
343 c->prev->status = MRB_FIBER_RUNNING;
344 c->status = MRB_FIBER_SUSPENDED;
345 fiber_switch_context(mrb, c->prev);
346 c->prev = NULL;
347 if (c->vmexec) {
348 c->vmexec = FALSE;
349 mrb->c->ci->acc = CI_ACC_RESUMED;
350 }
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
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().
366 */
367static mrb_value
368fiber_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 */
384static mrb_value
385fiber_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
396void
397mrb_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
416void
417mrb_mruby_fiber_gem_final(mrb_state* mrb)
418{
419}
Note: See TracBrowser for help on using the repository browser.