summaryrefslogtreecommitdiff
path: root/stackless_coroutine.c
diff options
context:
space:
mode:
Diffstat (limited to 'stackless_coroutine.c')
-rwxr-xr-xstackless_coroutine.c413
1 files changed, 413 insertions, 0 deletions
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 <guattari.tech>
+#/
+#/ ================================================================
+#/
+#/ 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