#ifndef FIBER_H #define FIBER_H #include #include #include #include #include #include #include #ifdef FIBER_SHARED # ifdef fiber_EXPORTS # define FIBER_API HU_DSO_EXPORT # else # define FIBER_API HU_DSO_IMPORT # endif #else # define FIBER_API #endif #ifdef __cplusplus extern "C" { #endif #include typedef uint16_t FiberState; typedef uint16_t FiberFlags; /** * A Fiber represents a couroutine which has its own call stack and a set of * preserved registers. */ typedef struct Fiber { FiberRegs regs; void *stack; void *alloc_stack; size_t stack_size; FiberState state; } Fiber; #define FIBER_STATE_CONSTANT(x) hu_static_cast(FiberState, x) #define FIBER_FLAG_CONSTANT(x) hu_static_cast(FiberFlags, x) #define FIBER_FS_EXECUTING FIBER_STATE_CONSTANT(1) #define FIBER_FS_TOPLEVEL FIBER_STATE_CONSTANT(2) #define FIBER_FS_ALIVE FIBER_STATE_CONSTANT(4) #define FIBER_FS_HAS_LO_GUARD_PAGE FIBER_STATE_CONSTANT(8) #define FIBER_FS_HAS_HI_GUARD_PAGE FIBER_STATE_CONSTANT(16) #define FIBER_FLAG_GUARD_LO FIBER_FLAG_CONSTANT(8) #define FIBER_FLAG_GUARD_HI FIBER_FLAG_CONSTANT(16) typedef void(FIBER_CCONV *FiberFunc)(void *); typedef void(FIBER_CCONV *FiberCleanupFunc)(Fiber *, void *); /** * initialize a Fiber with a preallocated stack. Stack alignment will be * correctly handled, this means in most cases the usuable stack size is * slightly less then the size of the stack buffer. The cleanup function * should never return! There is no stack frame where it could return to. In * practice this means that this function should switch to some other fiber * (usually the toplevel fiber). * @param fbr the fiber to create * @param stack preallocated stack * @param stack_size size of preallocated stack * @param cleanup the initial function on the call stack * @param arg the arg to pass to cleanup when it is invoked */ FIBER_API HU_NONNULL_PARAMS(1, 2, 4) HU_RETURNS_NONNULL Fiber * fiber_init(HU_OUT_NONNULL Fiber *fbr, HU_INOUT_NONNULL void *stack, size_t stack_size, HU_IN_NONNULL FiberCleanupFunc cleanup, void *arg); /** * initialize a toplevel Fiber, a toplevel Fiber represents an OS thread. Should * only be called once per OS Thread! * @param fbr the fiber to create */ FIBER_API HU_NONNULL_PARAMS(1) void fiber_init_toplevel(HU_OUT_NONNULL Fiber *fbr); /** * create a new Fiber by allocating a fresh stack, optionally with bottom or top * guard frames (each page usually adds an overhead of 4kb). It is recommended * to pass FIBER_FLAG_GUARD_LO, to catch stack overflows. @see fiber_init() * @param fbr the fiber to create * @param stack_size size of stack * @param cleanup the initial function on the call stack. * @param arg the arg to pass to cleanup when it is invoked */ HU_NODISCARD FIBER_API HU_NONNULL_PARAMS(1, 3) bool fiber_alloc(HU_OUT_NONNULL Fiber *fbr, size_t stack_size, HU_IN_NONNULL FiberCleanupFunc cleanup, void *arg, FiberFlags flags); /** * Deallocate the stack, does nothing if created by fiber_init(). * @param fbr the fiber to destroy */ FIBER_API HU_NONNULL_PARAMS(1) void fiber_destroy(HU_IN_NONNULL Fiber *fbr); /** * Switch from the current fiber to a different fiber by returning to the stack * frame of the new fiber. from has to be the active fiber! * @param from currently executing fiber * @param to fiber to switch to */ FIBER_API HU_NONNULL_PARAMS(1, 2) void fiber_switch(HU_INOUT_NONNULL Fiber *from, HU_INOUT_NONNULL Fiber *to); /** * Allocate a fresh stack frame at the top of a fiber with an argument buffer of * args_size. If the fiber is switched to it will execute the function. * @param fbr the fiber where the new frame will be allocated, cannot be the * currently executing fiber! * @param f the function to place in the stack frame, it will be called with a * pointer to the beginning of the argument buffer * @param args_dest the argument will receive a pointer to the buffer allocated * on the fiber stack, will be aligned to 8 bytes on all platforms. * @param args_size size of the argument buffer to allocate */ HU_NONNULL_PARAMS(1, 2, 3) FIBER_API void fiber_reserve_return(HU_INOUT_NONNULL Fiber *fbr, HU_IN_NONNULL FiberFunc f, HU_OUT_NONNULL void **args_dest, size_t args_size); /** * similar to @see fiber_reserve_return(), instead of returning the pointer to * the buffer, it will copy the contents into the stack allocated argument * buffer * @param fbr where to allocate the fresh stack frame * @param f function to place into the stack frame * @param args buffer to copy onto the stack frame * @param args_size size of argument buffer to copy */ HU_NONNULL_PARAMS(1, 2) static inline void fiber_push_return(HU_INOUT_NONNULL Fiber *fbr, HU_IN_NONNULL FiberFunc f, const void *args, size_t s) { void *args_dest; fiber_reserve_return(fbr, f, &args_dest, s); memcpy(args_dest, args, s); } /** * Temporarily switch from the active fiber to a temporary fiber and immediately * execute a function on this stack. After the call returns switch back to the * original fiber. This is useful if a call might consume a lot of stack space, * in this case temp should be a toplevel fiber. * @param active currently executing fiber * @param temp fiber where the call to f will take place * @param f function to call * @param argument to pass to f */ FIBER_API HU_NONNULL_PARAMS(1, 2, 3) void fiber_exec_on(HU_IN_NONNULL Fiber *active, HU_INOUT_NONNULL Fiber *temp, HU_IN_NONNULL FiberFunc f, void *args); /** * @return The compiled in stack alignment */ FIBER_API size_t fiber_stack_alignment(void); HU_WARN_UNUSED static inline bool fiber_is_toplevel(const Fiber *fbr) { return (fbr->state & FIBER_FS_TOPLEVEL) != 0; } HU_WARN_UNUSED HU_NONNULL_PARAMS(1) static inline bool fiber_is_executing(HU_IN_NONNULL const Fiber *fbr) { return (fbr->state & FIBER_FS_EXECUTING) != 0; } HU_WARN_UNUSED HU_NONNULL_PARAMS(1) static inline bool fiber_is_alive(HU_IN_NONNULL const Fiber *fbr) { return (fbr->state & FIBER_FS_ALIVE) != 0; } HU_NONNULL_PARAMS(1) static inline void fiber_set_alive(HU_INOUT_NONNULL Fiber *fbr, bool alive) { if (alive) fbr->state |= FIBER_FS_ALIVE; else fbr->state &= ~FIBER_FS_ALIVE; } HU_WARN_UNUSED HU_NONNULL_PARAMS(1) static inline void * fiber_stack(HU_IN_NONNULL const Fiber *fbr) { return fbr->stack; } HU_WARN_UNUSED HU_NONNULL_PARAMS(1) static inline size_t fiber_stack_size(HU_IN_NONNULL const Fiber *fbr) { return fbr->stack_size; } HU_WARN_UNUSED HU_NONNULL_PARAMS(1) static inline size_t fiber_stack_free_size(HU_IN_NONNULL const Fiber *fbr) { return hu_static_cast(char *, fbr->regs.sp) - hu_static_cast(char *, fbr->stack); } HU_WARN_UNUSED HU_NONNULL_PARAMS(1) static inline size_t fiber_stack_used_size(HU_IN_NONNULL const Fiber *fbr) { return fbr->stack_size - fiber_stack_free_size(fbr); } #ifdef __cplusplus } #endif #endif