Last active
May 9, 2024 17:37
-
-
Save Frityet/8c9d34670b0cf7b43df488cdacbb0de2 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#define _XOPEN_SOURCE 700 | |
#include <signal.h> | |
#include <stdint.h> | |
#include <stddef.h> | |
#include <string.h> | |
#include <stdlib.h> | |
#include <stdio.h> | |
#include <assert.h> | |
#include <unistd.h> | |
#include <stdbool.h> | |
#include <ucontext.h> | |
enum { | |
COROUTINE_MAX_YIELD_SIZE = 0x10000, | |
}; | |
#define EXPAND(x) x | |
#define _GLUE(X,Y) X##Y | |
#define GLUE(X,Y) _GLUE(X,Y) | |
// typedef void coroutine; | |
#define coroutine [[gnu::noinline]] | |
/* Returns the 100th argument. */ | |
#define _ARG_100(_,\ | |
_100,_99,_98,_97,_96,_95,_94,_93,_92,_91,_90,_89,_88,_87,_86,_85,_84,_83,_82,_81, \ | |
_80,_79,_78,_77,_76,_75,_74,_73,_72,_71,_70,_69,_68,_67,_66,_65,_64,_63,_62,_61, \ | |
_60,_59,_58,_57,_56,_55,_54,_53,_52,_51,_50,_49,_48,_47,_46,_45,_44,_43,_42,_41, \ | |
_40,_39,_38,_37,_36,_35,_34,_33,_32,_31,_30,_29,_28,_27,_26,_25,_24,_23,_22,_21, \ | |
_20,_19,_18,_17,_16,_15,_14,_13,_12,_11,_10,_9,_8,_7,_6,_5,_4,_3,_2,X_,...) X_ | |
/* Returns whether __VA_ARGS__ has a comma (up to 100 arguments). */ | |
#define HAS_COMMA(...) EXPAND(_ARG_100(__VA_ARGS__, \ | |
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \ | |
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \ | |
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \ | |
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ,1, \ | |
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0)) | |
/* Produces a comma if followed by a parenthesis. */ | |
#define _TRIGGER_PARENTHESIS_(...) , | |
#define _PASTE5(_0, _1, _2, _3, _4) _0 ## _1 ## _2 ## _3 ## _4 | |
#define _IS_EMPTY_CASE_0001 , | |
/* Returns true if inputs expand to (false, false, false, true) */ | |
#define _IS_EMPTY(_0, _1, _2, _3) HAS_COMMA(_PASTE5(_IS_EMPTY_CASE_, _0, _1, _2, _3)) | |
/* Returns whether __VA_ARGS__ is empty. */ | |
#define IS_EMPTY(...) \ | |
_IS_EMPTY( \ | |
/* Testing for an argument with a comma \ | |
e.g. "ARG1, ARG2", "ARG1, ...", or "," */ \ | |
HAS_COMMA(__VA_ARGS__), \ | |
/* Testing for an argument around parenthesis \ | |
e.g. "(ARG1)", "(...)", or "()" */ \ | |
HAS_COMMA(_TRIGGER_PARENTHESIS_ __VA_ARGS__), \ | |
/* Testing for a macro as an argument, which will \ | |
expand the parenthesis, possibly generating a comma. */ \ | |
HAS_COMMA(__VA_ARGS__ (/*empty*/)), \ | |
/* If all previous checks are false, __VA_ARGS__ does not \ | |
generate a comma by itself, nor with _TRIGGER_PARENTHESIS_ \ | |
behind it, nor with () after it. \ | |
Therefore, "_TRIGGER_PARENTHESIS_ __VA_ARGS__ ()" \ | |
only generates a comma if __VA_ARGS__ is empty. \ | |
So, this tests for an empty __VA_ARGS__ (given the \ | |
previous conditionals are false). */ \ | |
HAS_COMMA(_TRIGGER_PARENTHESIS_ __VA_ARGS__ (/*empty*/)) \ | |
) | |
#define _VAR_COUNT_EMPTY_1(...) 0 | |
#define _VAR_COUNT_EMPTY_0(...) EXPAND(_ARG_100(__VA_ARGS__, \ | |
100,99,98,97,96,95,94,93,92,91,90,89,88,87,86,85,84,83,82,81, \ | |
80,79,78,77,76,75,74,73,72,71,70,69,68,67,66,65,64,63,62,61, \ | |
60,59,58,57,56,55,54,53,52,51,50,49,48,47,46,45,44,43,42,41, \ | |
40,39,38,37,36,35,34,33,32,31,30,29,28,27,26,25,24,23,22,21, \ | |
20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1)) | |
#define VA_ARGS_COUNT(...) GLUE(_VAR_COUNT_EMPTY_, IS_EMPTY(__VA_ARGS__))(__VA_ARGS__) | |
enum CoroutineStatus { | |
CoroutineStatus_SUSPENDED, | |
CoroutineStatus_RUNNING, | |
CoroutineStatus_DEAD, | |
}; | |
static inline const char *coroutine_status_tostring(enum CoroutineStatus status) | |
{ | |
switch (status) { | |
case CoroutineStatus_SUSPENDED: return "suspended"; | |
case CoroutineStatus_RUNNING: return "running"; | |
case CoroutineStatus_DEAD: return "dead"; | |
} | |
return "unknown"; | |
} | |
typedef struct { | |
ucontext_t context; | |
enum CoroutineStatus status; | |
bool has_value; | |
uint8_t yield_value[COROUTINE_MAX_YIELD_SIZE], resume_value[COROUTINE_MAX_YIELD_SIZE]; | |
uint8_t stack[SIGSTKSZ]; | |
} thread; | |
static thread main_coroutine; | |
#define auto __auto_type | |
// #define coroutine_resume(c, ...) (typeof(c.return_type_check))coroutine_resume(c.coro __VA_OPT__(, &(typeof(__VA_ARGS__)){__VA_ARGS__}, sizeof(__VA_ARGS__))) | |
#define coroutine_resume(c, ...) (typeof(c.return_type_check))({\ | |
__VA_OPT__(\ | |
coroutine_resume(c.coro, &(typeof(__VA_ARGS__)){__VA_ARGS__}, sizeof(__VA_ARGS__));\ | |
\ | |
if (false) {\ | |
)\ | |
coroutine_resume(c.coro, NULL, 0);\ | |
__VA_OPT__(\ | |
}\ | |
)\ | |
}) | |
static inline void *(coroutine_resume)(thread *coro, void *value, size_t value_size) | |
{ | |
if (value) { | |
coro->has_value = true; | |
memcpy(coro->resume_value, value, value_size); | |
} else { | |
coro->has_value = false; | |
memset(coro->resume_value, 0, sizeof(coro->resume_value)); | |
} | |
coro->status = CoroutineStatus_RUNNING; | |
swapcontext(&main_coroutine.context, &coro->context); | |
if (coro->status != CoroutineStatus_DEAD) | |
coro->status = CoroutineStatus_SUSPENDED; | |
return coro->has_value ? coro->yield_value : NULL; | |
} | |
// Clang-tidy reports theres a "suspicious use of sizeof(A *)" which is fine because its intentional | |
// NOLINTBEGIN | |
#define coroutine_yield(c, ...) ({\ | |
__VA_OPT__(\ | |
if (false) {\ | |
return (typeof(__VA_ARGS__)){__VA_ARGS__}; /*Checking if the return values match, only if additonal params are provided*/\ | |
}\ | |
(coroutine_yield)(c, &(typeof(__VA_ARGS__)){__VA_ARGS__}, sizeof(__VA_ARGS__), false);\ | |
\ | |
if (false) {\ | |
)\ | |
(coroutine_yield)(c, NULL, 0, false);\ | |
__VA_OPT__(\ | |
}\ | |
)\ | |
}) | |
//NOLINTEND | |
#define coroutine_return(c, ...) ({\ | |
__VA_OPT__(\ | |
if (false) {\ | |
return (typeof(__VA_ARGS__)){__VA_ARGS__};\ | |
}\ | |
(coroutine_yield)(c, &(typeof(__VA_ARGS__)){__VA_ARGS__}, sizeof(__VA_ARGS__), true);\ | |
\ | |
if (false) {\ | |
)\ | |
(coroutine_yield)(c, NULL, 0, true);\ | |
__VA_OPT__(\ | |
}\ | |
)\ | |
__builtin_unreachable();\ | |
}) | |
static inline void *(coroutine_yield)(thread *coro, void *value, size_t value_size, bool kill) | |
{ | |
if (value) { | |
coro->has_value = true; | |
memcpy(coro->yield_value, value, value_size); | |
} else { | |
coro->has_value = false; | |
memset(coro->yield_value, 0, sizeof(coro->yield_value)); | |
} | |
if (kill) { | |
coro->status = CoroutineStatus_DEAD; | |
swapcontext(&coro->context, &main_coroutine.context); | |
fprintf(stderr, "Attempt to resume a dead coroutine\n"); | |
exit(1); | |
} | |
coro->status = CoroutineStatus_SUSPENDED; | |
swapcontext(&coro->context, &main_coroutine.context); | |
coro->status = CoroutineStatus_RUNNING; | |
if (coro->has_value) | |
return coro->resume_value; | |
return NULL; | |
} | |
#define coroutine_create(fn, ...) ({\ | |
thread *coro = calloc(1, sizeof(thread));\ | |
union { thread *coro; typeof(fn(coro __VA_OPT__(, __VA_ARGS__))) *return_type_check; } thr = { .coro = coro }; /*Function type checking*/\ | |
getcontext(&coro->context);\ | |
coro->context.uc_stack.ss_sp = coro->stack;\ | |
coro->context.uc_stack.ss_size = SIGSTKSZ;\ | |
coro->context.uc_link = NULL;\ | |
coro->status = CoroutineStatus_SUSPENDED;\ | |
makecontext(&coro->context, (void(*)())fn, VA_ARGS_COUNT(__VA_ARGS__)+1, coro __VA_OPT__(, __VA_ARGS__));\ | |
thr;\ | |
}) | |
#define coroutine_status(c) (c.coro->status) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "coroutine.h" | |
struct Node { | |
struct Node *next; | |
size_t size; | |
uint8_t data[]; | |
}; | |
static void LinkedList_push(struct Node *head, void *data, size_t size) | |
{ | |
struct Node *node = malloc(sizeof(struct Node) + size); | |
node->next = nullptr; | |
node->size = size; | |
memcpy(node->data, data, size); | |
struct Node *last = head; | |
while (last->next) { | |
last = last->next; | |
} | |
last->next = node; | |
} | |
coroutine static struct Node *LinkedList_iterate(thread *co, struct Node *last) | |
{ | |
for (struct Node *node = last->next; node; node = node->next) { | |
coroutine_yield(co, node); | |
} | |
coroutine_return(co); | |
} | |
coroutine static int range(thread *co, int start, int end) | |
{ | |
for (int i = start; i < end; i++) { | |
coroutine_yield(co, i); | |
} | |
coroutine_return(co); | |
} | |
#define $FOREACH(var, coro_fn, ...)\ | |
for (auto $coro = coroutine_create(coro_fn __VA_OPT__(, __VA_ARGS__)); ({\ | |
bool res;\ | |
if (coroutine_status($coro) != CoroutineStatus_DEAD) \ | |
res = true;\ | |
else {\ | |
free($coro.coro);\ | |
res = false;\ | |
}\ | |
res;\ | |
});)\ | |
for (auto $result = coroutine_resume($coro); $result; $result = coroutine_resume($coro))\ | |
for (int GLUE($runonce_, __LINE__) = 0; GLUE($runonce_, __LINE__) < 1;)\ | |
for (var = *$result; GLUE($runonce_, __LINE__) < 1; GLUE($runonce_, __LINE__)++) | |
#define in , | |
#define foreach(...) $FOREACH(__VA_ARGS__) | |
int main() | |
{ | |
struct Node list = {0}; | |
foreach (int i in range, 0, 10) { | |
LinkedList_push(&list, &(int){9-i}, sizeof(i)); | |
} | |
foreach (auto node in LinkedList_iterate, &list) { | |
printf("%d\n", *(int *)node->data); | |
} | |
int points[16][16]; | |
foreach (int i in range, 0, 16) { | |
foreach (int j in range, 0, 16) { | |
printf("Setting points[%d][%d] = %d\n", i, j, i * j); | |
points[i][j] = i * j; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment