Skip to content

Instantly share code, notes, and snippets.

@Frityet
Last active May 9, 2024 17:37
Show Gist options
  • Save Frityet/8c9d34670b0cf7b43df488cdacbb0de2 to your computer and use it in GitHub Desktop.
Save Frityet/8c9d34670b0cf7b43df488cdacbb0de2 to your computer and use it in GitHub Desktop.
#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)
#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