From ffee9be3dfda408bb8c78349efa3e3cd69c90f0f Mon Sep 17 00:00:00 2001 From: Mitya Selivanov Date: Mon, 20 Jan 2025 07:25:47 +0100 Subject: Add stackless coroutine --- stackless_coroutine.c | 413 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 413 insertions(+) create mode 100755 stackless_coroutine.c (limited to 'stackless_coroutine.c') diff --git a/stackless_coroutine.c b/stackless_coroutine.c new file mode 100755 index 0000000..a8f0cb2 --- /dev/null +++ b/stackless_coroutine.c @@ -0,0 +1,413 @@ +#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_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 + +CORO_DECL_STATIC(int, test_bar, ); + +CORO_IMPL(test_bar) { + yield_void; + async_return(42); +} CORO_END + +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 + +CORO_STATIC_VOID(test_task, ) { + yield_void; + yield_void; + async_return_void; +} CORO_END + +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 + +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 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)); +} + +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); +} + +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)); +} + +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)); +} + +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)); +} + +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 + +#endif // STACKLESS_COROUTINE_IMPL_GUARD_ +#endif // STACKLESS_COROUTINE_HEADER -- cgit v1.2.3