Patrick Kelley 8fd444092b initial
2025-05-07 15:35:15 -04:00

265 lines
7.1 KiB
C

#ifndef FIBER_H
#define FIBER_H
#include <hu/annotations.h>
#include <hu/lang.h>
#include <hu/objfmt.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#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 <fiber/fiber_mach.h>
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