Apply PIE to nghttpx
[platform/upstream/nghttp2.git] / third-party / mruby / mrbgems / mruby-fiber / src / fiber.c
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  */
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
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 = MRB_PROC_TARGET_CLASS(p);
127   ci->proc = p;
128   mrb_field_write_barrier(mrb, (struct RBasic*)mrb_obj_ptr(self), (struct RBasic*)p);
129   ci->pc = p->body.irep->iseq;
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
162 static void
163 fiber_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
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   enum mrb_fiber_state status;
190   mrb_value value;
191
192   fiber_check_cfunc(mrb, c);
193   status = c->status;
194   if (resume && status == MRB_FIBER_TRANSFERRED) {
195     mrb_raise(mrb, E_FIBER_ERROR, "resuming transferred fiber");
196   }
197   if (status == MRB_FIBER_RUNNING || status == MRB_FIBER_RESUMED) {
198     mrb_raise(mrb, E_FIBER_ERROR, "double resume");
199   }
200   if (status == MRB_FIBER_TERMINATED) {
201     mrb_raise(mrb, E_FIBER_ERROR, "resuming dead fiber");
202   }
203   old_c->status = resume ? MRB_FIBER_RESUMED : MRB_FIBER_TRANSFERRED;
204   c->prev = resume ? mrb->c : (c->prev ? c->prev : mrb->root_c);
205   fiber_switch_context(mrb, c);
206   if (status == MRB_FIBER_CREATED) {
207     mrb_value *b, *e;
208
209     if (!c->ci->proc) {
210       mrb_raise(mrb, E_FIBER_ERROR, "double resume (current)");
211     }
212     mrb_stack_extend(mrb, len+2); /* for receiver and (optional) block */
213     b = c->stack+1;
214     e = b + len;
215     while (b<e) {
216       *b++ = *a++;
217     }
218     c->cibase->argc = (int)len;
219     value = c->stack[0] = MRB_PROC_ENV(c->ci->proc)->stack[0];
220   }
221   else {
222     value = fiber_result(mrb, a, len);
223   }
224
225   if (vmexec) {
226     c->vmexec = TRUE;
227     value = mrb_vm_exec(mrb, c->ci[-1].proc, c->ci->pc);
228     mrb->c = old_c;
229   }
230   else {
231     MARK_CONTEXT_MODIFY(c);
232   }
233   return value;
234 }
235
236 /*
237  *  call-seq:
238  *     fiber.resume(args, ...) -> obj
239  *
240  *  Resumes the fiber from the point at which the last <code>Fiber.yield</code>
241  *  was called, or starts running it if it is the first call to
242  *  <code>resume</code>. Arguments passed to resume will be the value of
243  *  the <code>Fiber.yield</code> expression or will be passed as block
244  *  parameters to the fiber's block if this is the first <code>resume</code>.
245  *
246  *  Alternatively, when resume is called it evaluates to the arguments passed
247  *  to the next <code>Fiber.yield</code> statement inside the fiber's block
248  *  or to the block value if it runs to completion without any
249  *  <code>Fiber.yield</code>
250  */
251 static mrb_value
252 fiber_resume(mrb_state *mrb, mrb_value self)
253 {
254   mrb_value *a;
255   mrb_int len;
256   mrb_bool vmexec = FALSE;
257
258   mrb_get_args(mrb, "*!", &a, &len);
259   if (mrb->c->ci->acc < 0) {
260     vmexec = TRUE;
261   }
262   return fiber_switch(mrb, self, len, a, TRUE, vmexec);
263 }
264
265 /* resume thread with given arguments */
266 MRB_API mrb_value
267 mrb_fiber_resume(mrb_state *mrb, mrb_value fib, mrb_int len, const mrb_value *a)
268 {
269   return fiber_switch(mrb, fib, len, a, TRUE, TRUE);
270 }
271
272 /*
273  *  call-seq:
274  *     fiber.alive? -> true or false
275  *
276  *  Returns true if the fiber can still be resumed. After finishing
277  *  execution of the fiber block this method will always return false.
278  */
279 MRB_API mrb_value
280 mrb_fiber_alive_p(mrb_state *mrb, mrb_value self)
281 {
282   struct mrb_context *c = fiber_check(mrb, self);
283   return mrb_bool_value(c->status != MRB_FIBER_TERMINATED);
284 }
285 #define fiber_alive_p mrb_fiber_alive_p
286
287 static mrb_value
288 fiber_eq(mrb_state *mrb, mrb_value self)
289 {
290   mrb_value other;
291   mrb_get_args(mrb, "o", &other);
292
293   if (mrb_type(other) != MRB_TT_FIBER) {
294     return mrb_false_value();
295   }
296   return mrb_bool_value(fiber_ptr(self) == fiber_ptr(other));
297 }
298
299 /*
300  *  call-seq:
301  *     fiber.transfer(args, ...) -> obj
302  *
303  *  Transfers control to receiver fiber of the method call.
304  *  Unlike <code>resume</code> the receiver wouldn't be pushed to call
305  * stack of fibers. Instead it will switch to the call stack of
306  * transferring fiber.
307  *  When resuming a fiber that was transferred to another fiber it would
308  * cause double resume error. Though when the fiber is re-transferred
309  * and <code>Fiber.yield</code> is called, the fiber would be resumable.
310  */
311 static mrb_value
312 fiber_transfer(mrb_state *mrb, mrb_value self)
313 {
314   struct mrb_context *c = fiber_check(mrb, self);
315   mrb_value* a;
316   mrb_int len;
317
318   fiber_check_cfunc(mrb, mrb->c);
319   mrb_get_args(mrb, "*!", &a, &len);
320
321   if (c == mrb->root_c) {
322     mrb->c->status = MRB_FIBER_TRANSFERRED;
323     fiber_switch_context(mrb, c);
324     MARK_CONTEXT_MODIFY(c);
325     return fiber_result(mrb, a, len);
326   }
327
328   if (c == mrb->c) {
329     return fiber_result(mrb, a, len);
330   }
331
332   return fiber_switch(mrb, self, len, a, FALSE, FALSE);
333 }
334
335 /* yield values to the caller fiber */
336 /* mrb_fiber_yield() must be called as `return mrb_fiber_yield(...)` */
337 MRB_API mrb_value
338 mrb_fiber_yield(mrb_state *mrb, mrb_int len, const mrb_value *a)
339 {
340   struct mrb_context *c = mrb->c;
341
342   if (!c->prev) {
343     mrb_raise(mrb, E_FIBER_ERROR, "can't yield from root fiber");
344   }
345
346   fiber_check_cfunc(mrb, c);
347   c->prev->status = MRB_FIBER_RUNNING;
348   c->status = MRB_FIBER_SUSPENDED;
349   fiber_switch_context(mrb, c->prev);
350   c->prev = NULL;
351   if (c->vmexec) {
352     c->vmexec = FALSE;
353     mrb->c->ci->acc = CI_ACC_RESUMED;
354   }
355   MARK_CONTEXT_MODIFY(mrb->c);
356   return fiber_result(mrb, a, len);
357 }
358
359 /*
360  *  call-seq:
361  *     Fiber.yield(args, ...) -> obj
362  *
363  *  Yields control back to the context that resumed the fiber, passing
364  *  along any arguments that were passed to it. The fiber will resume
365  *  processing at this point when <code>resume</code> is called next.
366  *  Any arguments passed to the next <code>resume</code> will be the
367  *
368  *  mruby limitation: Fiber resume/yield cannot cross C function boundary.
369  *  thus you cannot yield from #initialize which is called by mrb_funcall().
370  */
371 static mrb_value
372 fiber_yield(mrb_state *mrb, mrb_value self)
373 {
374   mrb_value *a;
375   mrb_int len;
376
377   mrb_get_args(mrb, "*!", &a, &len);
378   return mrb_fiber_yield(mrb, len, a);
379 }
380
381 /*
382  *  call-seq:
383  *     Fiber.current() -> fiber
384  *
385  *  Returns the current fiber. If you are not running in the context of
386  *  a fiber this method will return the root fiber.
387  */
388 static mrb_value
389 fiber_current(mrb_state *mrb, mrb_value self)
390 {
391   if (!mrb->c->fib) {
392     struct RFiber *f = (struct RFiber*)mrb_obj_alloc(mrb, MRB_TT_FIBER, mrb_class_ptr(self));
393
394     f->cxt = mrb->c;
395     mrb->c->fib = f;
396   }
397   return mrb_obj_value(mrb->c->fib);
398 }
399
400 void
401 mrb_mruby_fiber_gem_init(mrb_state* mrb)
402 {
403   struct RClass *c;
404
405   c = mrb_define_class(mrb, "Fiber", mrb->object_class);
406   MRB_SET_INSTANCE_TT(c, MRB_TT_FIBER);
407
408   mrb_define_method(mrb, c, "initialize", fiber_init,    MRB_ARGS_NONE());
409   mrb_define_method(mrb, c, "resume",     fiber_resume,  MRB_ARGS_ANY());
410   mrb_define_method(mrb, c, "transfer",   fiber_transfer, MRB_ARGS_ANY());
411   mrb_define_method(mrb, c, "alive?",     fiber_alive_p, MRB_ARGS_NONE());
412   mrb_define_method(mrb, c, "==",         fiber_eq,      MRB_ARGS_REQ(1));
413
414   mrb_define_class_method(mrb, c, "yield", fiber_yield, MRB_ARGS_ANY());
415   mrb_define_class_method(mrb, c, "current", fiber_current, MRB_ARGS_NONE());
416
417   mrb_define_class(mrb, "FiberError", mrb->eStandardError_class);
418 }
419
420 void
421 mrb_mruby_fiber_gem_final(mrb_state* mrb)
422 {
423 }