From 58daab21cd043e1dc37024a7f99b396788372918 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 9 Mar 2024 14:19:48 +0100 Subject: Merging upstream version 1.44.3. Signed-off-by: Daniel Baumann --- .../deps/mruby/mrbgems/mruby-fiber/src/fiber.c | 420 +++++++++++++++++++++ 1 file changed, 420 insertions(+) create mode 100644 web/server/h2o/libh2o/deps/mruby/mrbgems/mruby-fiber/src/fiber.c (limited to 'web/server/h2o/libh2o/deps/mruby/mrbgems/mruby-fiber/src/fiber.c') diff --git a/web/server/h2o/libh2o/deps/mruby/mrbgems/mruby-fiber/src/fiber.c b/web/server/h2o/libh2o/deps/mruby/mrbgems/mruby-fiber/src/fiber.c new file mode 100644 index 000000000..9de175f34 --- /dev/null +++ b/web/server/h2o/libh2o/deps/mruby/mrbgems/mruby-fiber/src/fiber.c @@ -0,0 +1,420 @@ +#include +#include +#include +#include + +#define fiber_ptr(o) ((struct RFiber*)mrb_ptr(o)) + +#define FIBER_STACK_INIT_SIZE 64 +#define FIBER_CI_INIT_SIZE 8 +#define CI_ACC_RESUMED -3 + +/* + * call-seq: + * Fiber.new{...} -> obj + * + * Creates a fiber, whose execution is suspend until it is explicitly + * resumed using Fiber#resume method. + * The code running inside the fiber can give up control by calling + * Fiber.yield in which case it yields control back to caller + * (the caller of the Fiber#resume). + * + * Upon yielding or termination the Fiber returns the value of the last + * executed expression + * + * For instance: + * + * fiber = Fiber.new do + * Fiber.yield 1 + * 2 + * end + * + * puts fiber.resume + * puts fiber.resume + * puts fiber.resume + * + * produces + * + * 1 + * 2 + * resuming dead fiber (FiberError) + * + * The Fiber#resume method accepts an arbitrary number of + * parameters, if it is the first call to resume then they + * will be passed as block arguments. Otherwise they will be the return + * value of the call to Fiber.yield + * + * Example: + * + * fiber = Fiber.new do |first| + * second = Fiber.yield first + 2 + * end + * + * puts fiber.resume 10 + * puts fiber.resume 14 + * puts fiber.resume 18 + * + * produces + * + * 12 + * 14 + * resuming dead fiber (FiberError) + * + */ +static mrb_value +fiber_init(mrb_state *mrb, mrb_value self) +{ + static const struct mrb_context mrb_context_zero = { 0 }; + struct RFiber *f = fiber_ptr(self); + struct mrb_context *c; + struct RProc *p; + mrb_callinfo *ci; + mrb_value blk; + size_t slen; + + mrb_get_args(mrb, "&", &blk); + + if (f->cxt) { + mrb_raise(mrb, E_RUNTIME_ERROR, "cannot initialize twice"); + } + if (mrb_nil_p(blk)) { + mrb_raise(mrb, E_ARGUMENT_ERROR, "tried to create Fiber object without a block"); + } + p = mrb_proc_ptr(blk); + if (MRB_PROC_CFUNC_P(p)) { + mrb_raise(mrb, E_FIBER_ERROR, "tried to create Fiber from C defined method"); + } + + c = (struct mrb_context*)mrb_malloc(mrb, sizeof(struct mrb_context)); + *c = mrb_context_zero; + f->cxt = c; + + /* initialize VM stack */ + slen = FIBER_STACK_INIT_SIZE; + if (p->body.irep->nregs > slen) { + slen += p->body.irep->nregs; + } + c->stbase = (mrb_value *)mrb_malloc(mrb, slen*sizeof(mrb_value)); + c->stend = c->stbase + slen; + c->stack = c->stbase; + +#ifdef MRB_NAN_BOXING + { + mrb_value *p = c->stbase; + mrb_value *pend = c->stend; + + while (p < pend) { + SET_NIL_VALUE(*p); + p++; + } + } +#else + memset(c->stbase, 0, slen * sizeof(mrb_value)); +#endif + + /* copy receiver from a block */ + c->stack[0] = mrb->c->stack[0]; + + /* initialize callinfo stack */ + c->cibase = (mrb_callinfo *)mrb_calloc(mrb, FIBER_CI_INIT_SIZE, sizeof(mrb_callinfo)); + c->ciend = c->cibase + FIBER_CI_INIT_SIZE; + c->ci = c->cibase; + c->ci->stackent = c->stack; + + /* adjust return callinfo */ + ci = c->ci; + ci->target_class = p->target_class; + ci->proc = p; + mrb_field_write_barrier(mrb, (struct RBasic*)mrb_obj_ptr(self), (struct RBasic*)p); + ci->pc = p->body.irep->iseq; + ci->nregs = p->body.irep->nregs; + ci[1] = ci[0]; + c->ci++; /* push dummy callinfo */ + + c->fib = f; + c->status = MRB_FIBER_CREATED; + + return self; +} + +static struct mrb_context* +fiber_check(mrb_state *mrb, mrb_value fib) +{ + struct RFiber *f = fiber_ptr(fib); + + mrb_assert(f->tt == MRB_TT_FIBER); + if (!f->cxt) { + mrb_raise(mrb, E_FIBER_ERROR, "uninitialized Fiber"); + } + return f->cxt; +} + +static mrb_value +fiber_result(mrb_state *mrb, const mrb_value *a, mrb_int len) +{ + if (len == 0) return mrb_nil_value(); + if (len == 1) return a[0]; + return mrb_ary_new_from_values(mrb, len, a); +} + +/* mark return from context modifying method */ +#define MARK_CONTEXT_MODIFY(c) (c)->ci->target_class = NULL + +static void +fiber_check_cfunc(mrb_state *mrb, struct mrb_context *c) +{ + mrb_callinfo *ci; + + for (ci = c->ci; ci >= c->cibase; ci--) { + if (ci->acc < 0) { + mrb_raise(mrb, E_FIBER_ERROR, "can't cross C function boundary"); + } + } +} + +static void +fiber_switch_context(mrb_state *mrb, struct mrb_context *c) +{ + if (mrb->c->fib) { + mrb_write_barrier(mrb, (struct RBasic*)mrb->c->fib); + } + c->status = MRB_FIBER_RUNNING; + mrb->c = c; +} + +static mrb_value +fiber_switch(mrb_state *mrb, mrb_value self, mrb_int len, const mrb_value *a, mrb_bool resume, mrb_bool vmexec) +{ + struct mrb_context *c = fiber_check(mrb, self); + struct mrb_context *old_c = mrb->c; + mrb_value value; + + fiber_check_cfunc(mrb, c); + if (resume && c->status == MRB_FIBER_TRANSFERRED) { + mrb_raise(mrb, E_FIBER_ERROR, "resuming transferred fiber"); + } + if (c->status == MRB_FIBER_RUNNING || c->status == MRB_FIBER_RESUMED) { + mrb_raise(mrb, E_FIBER_ERROR, "double resume (fib)"); + } + if (c->status == MRB_FIBER_TERMINATED) { + mrb_raise(mrb, E_FIBER_ERROR, "resuming dead fiber"); + } + mrb->c->status = resume ? MRB_FIBER_RESUMED : MRB_FIBER_TRANSFERRED; + c->prev = resume ? mrb->c : (c->prev ? c->prev : mrb->root_c); + if (c->status == MRB_FIBER_CREATED) { + mrb_value *b, *e; + + if (len >= c->stend - c->stack) { + mrb_raise(mrb, E_FIBER_ERROR, "too many arguments to fiber"); + } + b = c->stack+1; + e = b + len; + while (bcibase->argc = len; + value = c->stack[0] = c->ci->proc->env->stack[0]; + } + else { + value = fiber_result(mrb, a, len); + } + fiber_switch_context(mrb, c); + + if (vmexec) { + c->vmexec = TRUE; + value = mrb_vm_exec(mrb, c->ci[-1].proc, c->ci->pc); + mrb->c = old_c; + } + else { + MARK_CONTEXT_MODIFY(c); + } + return value; +} + +/* + * call-seq: + * fiber.resume(args, ...) -> obj + * + * Resumes the fiber from the point at which the last Fiber.yield + * was called, or starts running it if it is the first call to + * resume. Arguments passed to resume will be the value of + * the Fiber.yield expression or will be passed as block + * parameters to the fiber's block if this is the first resume. + * + * Alternatively, when resume is called it evaluates to the arguments passed + * to the next Fiber.yield statement inside the fiber's block + * or to the block value if it runs to completion without any + * Fiber.yield + */ +static mrb_value +fiber_resume(mrb_state *mrb, mrb_value self) +{ + mrb_value *a; + mrb_int len; + mrb_bool vmexec = FALSE; + + mrb_get_args(mrb, "*!", &a, &len); + if (mrb->c->ci->acc < 0) { + vmexec = TRUE; + } + return fiber_switch(mrb, self, len, a, TRUE, vmexec); +} + +/* resume thread with given arguments */ +MRB_API mrb_value +mrb_fiber_resume(mrb_state *mrb, mrb_value fib, mrb_int len, const mrb_value *a) +{ + return fiber_switch(mrb, fib, len, a, TRUE, TRUE); +} + +/* + * call-seq: + * fiber.alive? -> true or false + * + * Returns true if the fiber can still be resumed. After finishing + * execution of the fiber block this method will always return false. + */ +static mrb_value +fiber_alive_p(mrb_state *mrb, mrb_value self) +{ + struct mrb_context *c = fiber_check(mrb, self); + return mrb_bool_value(c->status != MRB_FIBER_TERMINATED); +} + +static mrb_value +fiber_eq(mrb_state *mrb, mrb_value self) +{ + mrb_value other; + mrb_get_args(mrb, "o", &other); + + if (mrb_type(other) != MRB_TT_FIBER) { + return mrb_false_value(); + } + return mrb_bool_value(fiber_ptr(self) == fiber_ptr(other)); +} + +/* + * call-seq: + * fiber.transfer(args, ...) -> obj + * + * Transfers control to receiver fiber of the method call. + * Unlike resume the receiver wouldn't be pushed to call + * stack of fibers. Instead it will switch to the call stack of + * transferring fiber. + * When resuming a fiber that was transferred to another fiber it would + * cause double resume error. Though when the fiber is re-transferred + * and Fiber.yield is called, the fiber would be resumable. + */ +static mrb_value +fiber_transfer(mrb_state *mrb, mrb_value self) +{ + struct mrb_context *c = fiber_check(mrb, self); + mrb_value* a; + mrb_int len; + + fiber_check_cfunc(mrb, mrb->c); + mrb_get_args(mrb, "*!", &a, &len); + + if (c == mrb->root_c) { + mrb->c->status = MRB_FIBER_TRANSFERRED; + fiber_switch_context(mrb, c); + MARK_CONTEXT_MODIFY(c); + return fiber_result(mrb, a, len); + } + + if (c == mrb->c) { + return fiber_result(mrb, a, len); + } + + return fiber_switch(mrb, self, len, a, FALSE, FALSE); +} + +/* yield values to the caller fiber */ +/* mrb_fiber_yield() must be called as `return mrb_fiber_yield(...)` */ +MRB_API mrb_value +mrb_fiber_yield(mrb_state *mrb, mrb_int len, const mrb_value *a) +{ + struct mrb_context *c = mrb->c; + + if (!c->prev) { + mrb_raise(mrb, E_FIBER_ERROR, "can't yield from root fiber"); + } + + fiber_check_cfunc(mrb, c); + c->prev->status = MRB_FIBER_RUNNING; + c->status = MRB_FIBER_SUSPENDED; + fiber_switch_context(mrb, c->prev); + c->prev = NULL; + if (c->vmexec) { + c->vmexec = FALSE; + mrb->c->ci->acc = CI_ACC_RESUMED; + } + MARK_CONTEXT_MODIFY(mrb->c); + return fiber_result(mrb, a, len); +} + +/* + * call-seq: + * Fiber.yield(args, ...) -> obj + * + * Yields control back to the context that resumed the fiber, passing + * along any arguments that were passed to it. The fiber will resume + * processing at this point when resume is called next. + * Any arguments passed to the next resume will be the + * + * mruby limitation: Fiber resume/yield cannot cross C function boundary. + * thus you cannot yield from #initialize which is called by mrb_funcall(). + */ +static mrb_value +fiber_yield(mrb_state *mrb, mrb_value self) +{ + mrb_value *a; + mrb_int len; + + mrb_get_args(mrb, "*!", &a, &len); + return mrb_fiber_yield(mrb, len, a); +} + +/* + * call-seq: + * Fiber.current() -> fiber + * + * Returns the current fiber. If you are not running in the context of + * a fiber this method will return the root fiber. + */ +static mrb_value +fiber_current(mrb_state *mrb, mrb_value self) +{ + if (!mrb->c->fib) { + struct RFiber *f = (struct RFiber*)mrb_obj_alloc(mrb, MRB_TT_FIBER, mrb_class_ptr(self)); + + f->cxt = mrb->c; + mrb->c->fib = f; + } + return mrb_obj_value(mrb->c->fib); +} + +void +mrb_mruby_fiber_gem_init(mrb_state* mrb) +{ + struct RClass *c; + + c = mrb_define_class(mrb, "Fiber", mrb->object_class); + MRB_SET_INSTANCE_TT(c, MRB_TT_FIBER); + + mrb_define_method(mrb, c, "initialize", fiber_init, MRB_ARGS_NONE()); + mrb_define_method(mrb, c, "resume", fiber_resume, MRB_ARGS_ANY()); + mrb_define_method(mrb, c, "transfer", fiber_transfer, MRB_ARGS_ANY()); + mrb_define_method(mrb, c, "alive?", fiber_alive_p, MRB_ARGS_NONE()); + mrb_define_method(mrb, c, "==", fiber_eq, MRB_ARGS_REQ(1)); + + mrb_define_class_method(mrb, c, "yield", fiber_yield, MRB_ARGS_ANY()); + mrb_define_class_method(mrb, c, "current", fiber_current, MRB_ARGS_NONE()); + + mrb_define_class(mrb, "FiberError", mrb->eStandardError_class); +} + +void +mrb_mruby_fiber_gem_final(mrb_state* mrb) +{ +} -- cgit v1.2.3