#if 0 /* #/ ================================================================ #/ #/ stackless_coroutine.c #/ #/ ---------------------------------------------------------------- #/ #/ (C) 2025 Mitya Selivanov #/ #/ ================================================================ #/ #/ Self-testing shell script #/ #/ ================================================================ SRC=${0##*./} BIN=${SRC%.*} gcc \ -Wall -Wextra -Werror -pedantic \ -Wno-missing-braces \ -Wno-old-style-declaration \ -Wno-overlength-strings \ -O3 -D NDEBUG \ -D STACKLESS_COROUTINE_TEST_SUITE \ -o $BIN $SRC && \ ./$BIN $@ STATUS=$? rm -f $BIN exit $? # */ #endif // ================================================================ #ifndef TYPES_HEADER_GUARD_ #define TYPES_HEADER_GUARD_ typedef signed char i8; typedef signed short i16; typedef signed i32; typedef signed long long i64; typedef unsigned char u8; typedef unsigned short u16; typedef unsigned u32; typedef unsigned long long u64; typedef char c8; typedef int c32; typedef unsigned char b8; typedef float f32; typedef double f64; typedef struct { f64 x, y; } vec2; typedef struct { f64 x, y, z; } vec3; typedef struct { f64 x, y, z, w; } vec4; typedef struct { f32 x, y; } vec2_f32; typedef struct { f32 x, y, z; } vec3_f32; typedef struct { f32 x, y, z, w; } vec4_f32; typedef struct { i64 x, y; } vec2_i64; typedef struct { i64 x, y, z; } vec3_i64; typedef struct { i64 x, y, z, w; } vec4_i64; typedef struct { f64 v[ 4]; } mat2; typedef struct { f64 v[ 9]; } mat3; typedef struct { f64 v[16]; } mat4; #endif // TYPES_HEADER_GUARD_ // ================================================================ #ifndef STACKLESS_COROUTINE_HEADER_GUARD_ #define STACKLESS_COROUTINE_HEADER_GUARD_ #ifdef EVERY_TEST_SUITE #define STACKLESS_COROUTINE_TEST_SUITE #endif #ifndef NULL #define NULL ((void *) 0) #endif #ifdef __GNUC__ #define CORO_FALLTHROUGH_ __attribute__((fallthrough)); #else #define CORO_FALLTHROUGH_ #endif #ifdef __cplusplus extern "C" { #endif typedef struct { i32 _; } Coro_Void_; typedef void (*Coro_State_Machine)(void *self_void_); #define CORO_PROMISE_DATA_ \ struct { \ i32 _index; \ i32 _id; \ Coro_State_Machine _state_machine; \ } typedef struct { CORO_PROMISE_DATA_; } Coro_Promise_Void; #if defined(__GNUC__) || defined(__clang__) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wunused-function" # pragma GCC diagnostic ignored "-Wunknown-pragmas" #endif #define CORO_PROMISE_FROM(coro_) (*((Coro_Promise_Void *) (coro_))) #ifdef CORO_ENABLE_CUSTOM_DISPATCH // Application should implement this procedure if custom dispatch is enabled. void stackless_coroutine_dispatch(void *promise); #else static void stackless_coroutine_dispatch(void *promise) { // Dynamic dispatch by default. CORO_PROMISE_FROM(promise)._state_machine(promise); } #endif #if defined(__GNUC__) || defined(__clang__) # pragma GCC diagnostic pop #endif #define CORO_PROMISE_STRUCT_(Ret_Type_, coro_, ...) \ struct Coro_Promise_##coro_ { \ CORO_PROMISE_DATA_; \ Ret_Type_ return_value; \ __VA_ARGS__ \ } #define CORO_PROC_DECL_(coro_) void coro_(void *self_void_) #define CORO_LINE_() __LINE__ #define CORO_IMPL(coro_) \ CORO_PROC_DECL_(coro_) { \ b8 _Is_Inside_Coro = 1; \ struct Coro_Promise_##coro_ *self = (struct Coro_Promise_##coro_ *) self_void_; \ switch (self->_index) { \ case 0: (void) _Is_Inside_Coro; #define CORO_END \ } \ (void) _Is_Inside_Coro; \ self->_index = -1; \ } #define CORO_DECL(Ret_Type_, coro_, ...) \ CORO_PROMISE_STRUCT_(Ret_Type_, coro_, __VA_ARGS__); \ CORO_PROC_DECL_(coro_) #define CORO_DECL_STATIC(Ret_Type_, coro_, ...) \ CORO_PROMISE_STRUCT_(Ret_Type_, coro_, __VA_ARGS__); \ static CORO_PROC_DECL_(coro_) #define CORO(Ret_Type_, coro_, ...) \ CORO_PROMISE_STRUCT_(Ret_Type_, coro_, __VA_ARGS__); \ CORO_IMPL(coro_) #define CORO_DECL_VOID(coro_, ...) \ CORO_DECL(Coro_Void_, coro_, __VA_ARGS__) #define CORO_VOID(coro_, ...) \ CORO(Coro_Void_, coro_, __VA_ARGS__) #define CORO_STATIC(Ret_Type_, coro_, ...) \ CORO_PROMISE_STRUCT_(Ret_Type_, coro_, __VA_ARGS__); \ static CORO_IMPL(coro_) #define CORO_STATIC_VOID(coro_, ...) \ CORO_STATIC(Coro_Void_, coro_, __VA_ARGS__) #define coro_resume(promise_) \ stackless_coroutine_dispatch(&(promise_)) #define coro_next(promise_) \ (stackless_coroutine_dispatch(&(promise_)), (promise_).return_value) #define yield(...) \ do { \ (void) _Is_Inside_Coro; \ self->_index = CORO_LINE_(); \ self->return_value = __VA_ARGS__; \ return; \ case CORO_LINE_():; \ } while (0) #define yield_void \ do { \ (void) _Is_Inside_Coro; \ self->_index = CORO_LINE_(); \ return; \ case CORO_LINE_():; \ } while (0) #define async_return(...) \ do { \ (void) _Is_Inside_Coro; \ self->_index = -1; \ self->return_value = __VA_ARGS__; \ return; \ } while (0) #define async_return_void \ do { \ (void) _Is_Inside_Coro; \ self->_index = -1; \ return; \ } while (0) #define await(promise_) \ do { \ (void) _Is_Inside_Coro; \ CORO_FALLTHROUGH_ \ case CORO_LINE_(): \ if ((promise_)._index != -1) { \ self->_index = CORO_LINE_(); \ stackless_coroutine_dispatch(&(promise_)); \ } \ if ((promise_)._index != -1) \ return; \ } while (0) #define yield_await(promise_) \ do { \ (void) _Is_Inside_Coro; \ CORO_FALLTHROUGH_ \ case CORO_LINE_(): \ if ((promise_)._index != -1) { \ self->_index = CORO_LINE_(); \ stackless_coroutine_dispatch(&(promise_)); \ self->return_value = (promise_).return_value; \ return; \ } \ } while (0) #define Promise_Of(coro_) struct Coro_Promise_##coro_ #define CORO_INITIAL_(id_, coro_) \ ._index = 0, ._id = (id_), ._state_machine = (coro_) #define CORO_INIT(promise_, coro_, ...) \ do { \ promise_ = (Promise_Of(coro_)) { CORO_INITIAL_(0, coro_), __VA_ARGS__ }; \ } while (0) #define CORO_INIT_ID(promise_, id_, ...) \ do { \ promise_ = (Promise_Of(coro_)) { CORO_INITIAL_(id_, coro_), __VA_ARGS__ }; \ } while (0) #define coro_finished(promise_) ((promise_)._index == -1) #define coro_finished_n(return_, num_promises_, promises_) \ do { \ i32 num_ = (num_promises_); \ i32 index_; \ (return_) = 1; \ for (index_ = 0; index_ < (num_); ++index_) \ if (!coro_finished((promises_)[index_])) { \ (return_) = 0; \ break; \ } \ } while (0) #define coro_finished_all(return_, promises_) \ coro_finished_n((return_), sizeof(promises_) / sizeof((promises_)[0]), (promises_)) #ifdef __cplusplus } #endif #endif // STACKLESS_COROUTINE_HEADER_GUARD_ // ================================================================ #ifndef STACKLESS_COROUTINE_HEADER #ifndef STACKLESS_COROUTINE_IMPL_GUARD_ #define STACKLESS_COROUTINE_IMPL_GUARD_ #ifdef STACKLESS_COROUTINE_TEST_SUITE #define TEST_FILE stackless_coroutine #include "test.c" CORO_DECL(int, test_foo, ); CORO_IMPL(test_foo) { async_return(42); } CORO_END TEST("coroutine init") { Promise_Of(test_foo) promise; CORO_INIT(promise, test_foo, ); REQUIRE(!coro_finished(promise)); } TEST("coroutine init with value") { Promise_Of(test_foo) promise; CORO_INIT(promise, test_foo, .return_value = 42); REQUIRE(promise.return_value == 42); REQUIRE(!coro_finished(promise)); } TEST("coroutine execute and return") { Promise_Of(test_foo) promise; CORO_INIT(promise, test_foo, ); REQUIRE(coro_next(promise) == 42); REQUIRE(coro_finished(promise)); } CORO_DECL_STATIC(int, test_bar, ); CORO_IMPL(test_bar) { yield_void; async_return(42); } CORO_END TEST("coroutine execute two steps") { Promise_Of(test_bar) promise; CORO_INIT(promise, test_bar, .return_value = 0); coro_resume(promise); REQUIRE(promise.return_value == 0); coro_resume(promise); REQUIRE(promise.return_value == 42); } CORO_STATIC(int, test_gen, int i; int min; int max;) { for (self->i = self->min; self->i < self->max; self->i++) yield(self->i); async_return(self->max); } CORO_END TEST("coroutine generator") { int i; Promise_Of(test_gen) promise; CORO_INIT(promise, test_gen, .min = 10, .max = 15); for (i = 0; i <= 5; i++) REQUIRE(coro_next(promise) == 10 + i); } TEST("coroutine status finished") { Promise_Of(test_bar) promise; CORO_INIT(promise, test_bar, ); REQUIRE(!coro_finished(promise)); coro_resume(promise); REQUIRE(!coro_finished(promise)); coro_resume(promise); REQUIRE(coro_finished(promise)); } CORO_STATIC_VOID(test_task, ) { yield_void; yield_void; async_return_void; } CORO_END TEST("coroutine task") { Promise_Of(test_task) promise; CORO_INIT(promise, test_task, ); coro_resume(promise); REQUIRE(!coro_finished(promise)); coro_resume(promise); REQUIRE(!coro_finished(promise)); coro_resume(promise); REQUIRE(coro_finished(promise)); } CORO_STATIC_VOID(test_nest_task, Promise_Of(test_task) promise;) { CORO_INIT(self->promise, test_task, ); await(self->promise); await(self->promise); await(self->promise); } CORO_END TEST("coroutine nested task") { Promise_Of(test_nest_task) promise; CORO_INIT(promise, test_nest_task, ); coro_resume(promise); REQUIRE(!coro_finished(promise)); coro_resume(promise); REQUIRE(!coro_finished(promise)); coro_resume(promise); REQUIRE(coro_finished(promise)); } CORO_STATIC(int, test_nest_generator, Promise_Of(test_gen) promise;) { CORO_INIT(self->promise, test_gen, .min = 1, .max = 3); yield_await(self->promise); } CORO_END TEST("coroutine nested generator") { Promise_Of(test_nest_generator) promise; CORO_INIT(promise, test_nest_generator, ); REQUIRE(coro_next(promise) == 1); REQUIRE(coro_next(promise) == 2); REQUIRE(coro_next(promise) == 3); REQUIRE(!coro_finished(promise)); coro_resume(promise); REQUIRE(coro_finished(promise)); } #ifndef EVERY_TEST_SUITE i32 main(i32 argc, c8 **argv) { return run_tests(argc, argv); } #endif #endif // STACKLESS_COROUTINE_TEST_SUITE #endif // STACKLESS_COROUTINE_IMPL_GUARD_ #endif // STACKLESS_COROUTINE_HEADER