0 branches 0 tags
66 67
68
69 78
Variable stack Coroutines WIP
on 3:07 PM Dec 23 2025
cor_platform.h
67
68
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#ifndef COR_PLATFORM_H
#define COR_PLATFORM_H
// platform specific parts collected together
#include <stdbool.h>
#include <pthread.h>
#include <errno.h>
// inspired by CPython to achieve platform indenpendence for thread local variables
#ifdef thread_local
#define _Cor_thread_local thread_local
#elif __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_THREADS__)
#define _Cor_thread_local _Thread_local
#elif defined(_MSC_VER) /* AKA NT_THREADS */
#define _Cor_thread_local __declspec(thread)
#elif defined(__GNUC__) /* includes clang */
#define _Cor_thread_local __thread
#else
#define _Cor_thread_local
#endif
// see CPython again, this time in ctypes.h
#define COROUTINE_HAVE_ALLOCA_H 1
// Non-reentrant Mutexes
typedef struct _Cor_Mutex {
pthread_mutex_t mut;
} _Cor_Mutex;
extern void _Cor_Mutex_ctor(_Cor_Mutex *);
extern void _Cor_Mutex_dtor(_Cor_Mutex *);
extern void _Cor_Mutex_Lock(_Cor_Mutex *);
extern void _Cor_Mutex_Unlock(_Cor_Mutex *);
// The 'now' to use for _Cor_Semaphore_Wait, in ns.
extern int64_t _Cor_Realtime_Now();
typedef struct _Cor_Semaphore {
pthread_mutex_t mut;
pthread_cond_t cond;
unsigned count;
} _Cor_Semaphore;
extern void _Cor_Semaphore_ctor(_Cor_Semaphore *sem);
extern void _Cor_Sempahore_dtor(_Cor_Semaphore *sem);
// timeout_when < 0 means 'wait forever'
// Returns true for success, false for timeout
extern bool _Cor_Semaphore_Wait(_Cor_Semaphore *sem, int64_t timeout_when);
extern void _Cor_Semaphore_Signal(_Cor_Semaphore *sem);
typedef struct _Cor_Thread {
pthread_t th;
bool joined;
} _Cor_Thread;
extern void _Cor_Thread_ctor(_Cor_Thread *, void *(*)(void *), void *);
extern void _Cor_Thread_dtor(_Cor_Thread *);
extern void *_Cor_Thread_Join(_Cor_Thread *);
#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#ifndef COR_PLATFORM_H
#define COR_PLATFORM_H
// platform specific parts collected together
#include <stdbool.h>
#include <stdlib.h>
#include <pthread.h>
#include <errno.h>
// inspired by CPython to achieve platform indenpendence for thread local variables
#ifdef thread_local
#define _Cor_thread_local thread_local
#elif __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_THREADS__)
#define _Cor_thread_local _Thread_local
#elif defined(_MSC_VER) /* AKA NT_THREADS */
#define _Cor_thread_local __declspec(thread)
#elif defined(__GNUC__) /* includes clang */
#define _Cor_thread_local __thread
#else
#define _Cor_thread_local
#endif
// malloc & free...
static inline void *
_Cor_Malloc(size_t size){
return malloc(size);
}
static inline void
_Cor_Free(void *ptr){
free(ptr);
}
// ...malloc & free
// see CPython again, this time in ctypes.h
#define COROUTINE_HAVE_ALLOCA_H 1
// Non-reentrant Mutexes
typedef struct _Cor_Mutex {
pthread_mutex_t mut;
} _Cor_Mutex;
extern void _Cor_Mutex_ctor(_Cor_Mutex *);
extern void _Cor_Mutex_dtor(_Cor_Mutex *);
extern void _Cor_Mutex_Lock(_Cor_Mutex *);
extern void _Cor_Mutex_Unlock(_Cor_Mutex *);
// The 'now' to use for _Cor_Semaphore_Wait, in ns.
extern int64_t _Cor_Realtime_Now();
typedef struct _Cor_Semaphore {
pthread_mutex_t mut;
pthread_cond_t cond;
unsigned count;
} _Cor_Semaphore;
extern void _Cor_Semaphore_ctor(_Cor_Semaphore *sem);
extern void _Cor_Sempahore_dtor(_Cor_Semaphore *sem);
// timeout_when < 0 means 'wait forever'
// Returns true for success, false for timeout
extern bool _Cor_Semaphore_Wait(_Cor_Semaphore *sem, int64_t timeout_when);
extern void _Cor_Semaphore_Signal(_Cor_Semaphore *sem);
typedef struct _Cor_Thread {
pthread_t th;
bool joined;
} _Cor_Thread;
extern void _Cor_Thread_ctor(_Cor_Thread *, void *(*)(void *), void *);
extern void _Cor_Thread_dtor(_Cor_Thread *);
extern void *_Cor_Thread_Join(_Cor_Thread *);
#endif
coroutine.h
67
68
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
#ifndef COROUTINE_H
#define COROUTINE_H
#include <stdbool.h>
#include <stdint.h>
///////////////////////////////////////////////////////////////////////////////
// Coroutine
//
// Coroutines for C, based on setjmp/longjmp.
// Thread safe - each thread has its own coroutine system
// Coroutines are cooperatively scheduled
// Coroutines have their own stack (currently 16K each)
// A coroutine can be continued, queried, or deleted on a different thread.
//
// Usage:
// Coroutine_StartSystem(); // call once per thread before using coroutines
// Coroutine *co = Coroutine_New(start_function);
// void *result;
// if (Coroutine_Run(co, initial_value, &result)){}
// // Handle the failure
// |
// Coroutine_Delete(co);
// Coroutine_StopSystem(); // call once per thread when done with coroutines
//
// Inside the coroutine function:
// void *value = Coroutine_Yield(yield_value, on_yield, this);
// ...
// return return_value;
//
// To create a coroutine:
// Coroutine *co = Coroutine_New(start_function);
// To start or continue a coroutine:
// void *result = Coroutine_Continue(co, value, early);
// // early=true puts the coroutine at the head of the run queue
// // early=false puts the coroutine at the tail of the run queue
// To yield from inside a coroutine:
// void *value = Coroutine_Yield(yield_value, on_yield, this);
// // on_yield is called before the next coroutine is run
// // 'this' is passed to on_yield as its parameter
// // value is the value passed to Coroutine_Continue
// To delete a coroutine:
// Coroutine_Delete(co);
// To get the value yielded from, or returned by a corotuine:
// void *value = Coroutine_GetValue(co);
// To get the currently running coroutine (NULL if none):
// Coroutine *co = Coroutine_GetActive();
// To check if a coroutine is currently running:
// bool running = Coroutine_IsRunning(co);
//
// Notes:
// Coroutine is not expected to be used directly, but as a foundation for
// higher level constructs such as Generators, Async, etc.
//
///////////////////////////////////////////////////////////////////////////////
// The stack is used as follows:
//
// Note: the stack is assumed to grow downwards through memory
//
// (low memory)
//
// <- limit. When set this and lower memory addresses are assumed to be unavailable for stack use
// (- lowest address usable by the stack -)
// . .
// . .
// | coroutine stack |
// +------------------+
// | coroutine header | <- 'tip' coroutine (latest allocated Coroutine)
// +------------------+
// | coroutine stack |
// +------------------+
// | coroutine header | <- 'active' Coroutine (the one currently running - could be 'tip')
// +------------------+
// | coroutine stack |
// +------------------+
// | coroutine header |
// +------------------+
// | coroutine stack |
// +------------------+
// | coroutine header |
// +------------------+
// | startup space | <- set aside by Coroutine_StartSystem
// +------------------+
// | caller | <- This calls Coroutine_StartSystem etc
// +------------------+
// | used stack |
// +------------------+ <- stack 'bottom'; highest address used by the stack
// (high memory)
// Each coroutine has this much stack:
#ifndef COROUTINE_STACK_SIZE
#define COROUTINE_STACK_SIZE 65536
#endif
// When Coroutine is started, an amount of stack is set aside to give
// the caller of Coroutine_StartSystem a bit of room to work before calling
// Coroutine_Run(), that is this amount:
#ifndef COROUTINE_STARTUP_STACK_SIZE
#define COROUTINE_STARTUP_STACK_SIZE 4096
#endif
// When allocating space for a coroutine stack, fill it with guard pattern
// so that lowest_headroom in the Coroutine_Report can be worked out
#ifndef COROUTINE_RECORD_LOWEST_HEADROOM
#define COROUTINE_RECORD_LOWEST_HEADROOM 1
#endif
// Returned by Coroutine_StopSystem(), this summarises the coroutine session
typedef struct Coroutine_Report {
unsigned coroutines_created;
unsigned coroutines_pool_size;
unsigned lowest_headroom;
} Coroutine_Report;
typedef struct Coroutine Coroutine;
typedef void (*Coroutine_YieldCallback)(void *me);
typedef void *(*Coroutine_Start)(void *);
extern void Coroutine_StartSystem(void);
extern void Coroutine_SetStackLimit(void *);
extern Coroutine_Report Coroutine_StopSystem(void);
extern Coroutine *Coroutine_New(Coroutine_Start start);
extern void Coroutine_Run_Coroutine(Coroutine *cor, void *value);
extern bool Coroutine_Run(Coroutine_Start start, void *value, void **result);
extern void Coroutine_Delete(Coroutine *cor);
extern void Coroutine_Continue(Coroutine *cor, void *value, bool early);
extern void *Coroutine_Yield(void *value, Coroutine_YieldCallback on_yield, void *me);
extern void *Coroutine_GetValue(Coroutine *cor);
extern Coroutine *Coroutine_GetActive(void);
extern intptr_t Coroutine_GetStackHeadroom(void);
extern void *Coroutine_GetStackHWM(void);
extern void Coroutine_ClearStackForHWM(void);
extern bool Coroutine_CanStartCoroutine();
extern void *Coroutine_GetCStackTop(void);
extern bool Coroutine_Chain(Coroutine_Start start, void *value, void **result);
extern bool Coroutine_IsStarted(void);
extern bool Coroutine_IsRunning(Coroutine *cor);
extern bool Coroutine_IsComplete(Coroutine *cor);
#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
#ifndef COROUTINE_H
#define COROUTINE_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
///////////////////////////////////////////////////////////////////////////////
// Coroutine
//
// Coroutines for C, based on setjmp/longjmp.
// Thread safe - each thread has its own coroutine system
// Coroutines are cooperatively scheduled
// Coroutines have their own stack (currently 16K each)
// A coroutine can be continued, queried, or deleted on a different thread.
//
// Usage:
// Coroutine_StartSystem(); // call once per thread before using coroutines
// Coroutine *co = Coroutine_New(start_function);
// void *result;
// if (Coroutine_Run(co, initial_value, &result)){}
// // Handle the failure
// |
// Coroutine_Delete(co);
// Coroutine_StopSystem(); // call once per thread when done with coroutines
//
// Inside the coroutine function:
// void *value = Coroutine_Yield(yield_value, on_yield, this);
// ...
// return return_value;
//
// To create a coroutine:
// Coroutine *co = Coroutine_New(start_function);
// To start or continue a coroutine:
// void *result = Coroutine_Continue(co, value, early);
// // early=true puts the coroutine at the head of the run queue
// // early=false puts the coroutine at the tail of the run queue
// To yield from inside a coroutine:
// void *value = Coroutine_Yield(yield_value, on_yield, this);
// // on_yield is called before the next coroutine is run
// // 'this' is passed to on_yield as its parameter
// // value is the value passed to Coroutine_Continue
// To delete a coroutine:
// Coroutine_Delete(co);
// To get the value yielded from, or returned by a corotuine:
// void *value = Coroutine_GetValue(co);
// To get the currently running coroutine (NULL if none):
// Coroutine *co = Coroutine_GetActive();
// To check if a coroutine is currently running:
// bool running = Coroutine_IsRunning(co);
//
// Notes:
// Coroutine is not expected to be used directly, but as a foundation for
// higher level constructs such as Generators, Async, etc.
//
///////////////////////////////////////////////////////////////////////////////
// The stack is used as follows:
//
// Note: the stack is assumed to grow downwards through memory
//
// (low memory)
//
// <- limit. When set this and lower memory addresses are assumed to be unavailable for stack use
// (- lowest address usable by the stack -)
// . .
// . .
// | coroutine stack |
// +------------------+
// | coroutine header | <- 'tip' coroutine (latest allocated Coroutine)
// +------------------+
// | coroutine stack |
// +------------------+
// | coroutine header | <- 'active' Coroutine (the one currently running - could be 'tip')
// +------------------+
// | coroutine stack |
// +------------------+
// | coroutine header |
// +------------------+
// | coroutine stack |
// +------------------+
// | coroutine header |
// +------------------+
// | startup space | <- set aside by Coroutine_StartSystem
// +------------------+
// | caller | <- This calls Coroutine_StartSystem etc
// +------------------+
// | used stack |
// +------------------+ <- stack 'bottom'; highest address used by the stack
// (high memory)
// No coroutine will ask for less stack than this
#ifndef COROUTINE_MINIMUM_STACK_SIZE
#define COROUTINE_MINIMUM_STACK_SIZE (4096 * sizeof(void *))
#endif
// Before Coroutine has New()'ed a Coroutine, this amount of stack
// is set aside for workings between the New() and Run():
#ifndef COROUTINE_STARTUP_STACK_SIZE
#define COROUTINE_STARTUP_STACK_SIZE (128 * sizeof(void *))
#endif
// When allocating space for a coroutine stack, fill it with guard pattern
// so that lowest_headroom in the Coroutine_Report can be worked out
#ifndef COROUTINE_RECORD_LOWEST_HEADROOM
#define COROUTINE_RECORD_LOWEST_HEADROOM 1
#endif
// Returned by Coroutine_StopSystem(), this summarises the coroutine session
typedef struct Coroutine_Report {
unsigned coroutines_created;
unsigned coroutines_pool_size;
size_t lowest_headroom;
size_t largest_stack;
} Coroutine_Report;
typedef struct Coroutine Coroutine;
typedef void (*Coroutine_YieldCallback)(void *me);
typedef void *(*Coroutine_Start)(void *);
extern void Coroutine_StartSystem(void);
extern void Coroutine_SetStackLimit(void *);
extern Coroutine_Report Coroutine_StopSystem(void);
extern Coroutine *Coroutine_New(size_t size, Coroutine_Start start);
extern void Coroutine_Run_Coroutine(Coroutine *cor, void *value);
extern bool Coroutine_Run(size_t size, Coroutine_Start start, void *value, void **result);
extern void Coroutine_Delete(Coroutine *cor);
extern void Coroutine_Continue(Coroutine *cor, void *value, bool early);
extern void *Coroutine_Yield(void *value, Coroutine_YieldCallback on_yield, void *me);
extern void *Coroutine_GetValue(Coroutine *cor);
extern Coroutine *Coroutine_GetActive(void);
extern intptr_t Coroutine_GetStackHeadroom(void);
extern void *Coroutine_GetStackHWM(void);
extern void Coroutine_ClearStackForHWM(void);
extern bool Coroutine_CanStartCoroutine(size_t size);
extern void *Coroutine_GetCStackTop(void);
extern bool Coroutine_Chain(size_t size, Coroutine_Start start, void *value, void **result);
extern bool Coroutine_IsStarted(void);
extern bool Coroutine_IsRunning(Coroutine *cor);
extern bool Coroutine_IsComplete(Coroutine *cor);
#endif
generator.h
67
68
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#ifndef GENERATOR_H
#define GENERATOR_H
#include "coroutine.h"
#include <stdbool.h>
typedef enum Generator_State {
Generator_Running,
Generator_Deleting,
Generator_Complete
} Generator_State;
typedef struct Generator {
Coroutine *coroutine;
Coroutine *caller;
void *(*start)(void *);
void *param;
Generator_State state;
} Generator;
extern void Generator_ctor(Generator *gen, void *(*start)(void *), void *param);
extern Generator *Generator_New(void *(*)(void *), void *);
extern void Generator_dtor(Generator *gen);
extern void Generator_Delete(Generator *);
// Returns true if generator yielded a value, false if generator is complete
// *value is set to generator's value (yield / return value from exit)
extern bool Generator_Next(Generator *, void **value);
// Yield a value from the generator
// Returns true if generator should continue, false for generator to exit PDQ
extern bool Generator_Yield(void *);
#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#ifndef GENERATOR_H
#define GENERATOR_H
#include "coroutine.h"
#include <stdbool.h>
typedef enum Generator_State {
Generator_Running,
Generator_Deleting,
Generator_Complete
} Generator_State;
typedef struct Generator {
Coroutine *coroutine;
Coroutine *caller;
void *(*start)(void *);
void *param;
Generator_State state;
} Generator;
extern void Generator_ctor(Generator *gen, size_t stack_size, void *(*start)(void *), void *param);
extern Generator *Generator_New(size_t stack_size, void *(*)(void *), void *);
extern void Generator_dtor(Generator *gen);
extern void Generator_Delete(Generator *);
// Returns true if generator yielded a value, false if generator is complete
// *value is set to generator's value (yield / return value from exit)
extern bool Generator_Next(Generator *, void **value);
// Yield a value from the generator
// Returns true if generator should continue, false for generator to exit PDQ
extern bool Generator_Yield(void *);
#endif
task.h
67
68
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#ifndef TASK_H
#define TASK_H
#include <stdbool.h>
#include "future.h"
#include "cor_platform.h"
typedef struct Task Task;
extern _Cor_thread_local Task *current_task;
typedef bool (*Task_Entry)(void *param, void **res);
extern void Task_ctor(Task *tsk, Task_Entry entry, void *param);
extern Task *Task_New(Task_Entry entry, void *param);
extern void Task_dtor(Task *tsk);
extern void Task_Delete(Task *tsk);
static inline bool Task_Await(Task *tsk, void **res);
extern void Task_Cancel(Task *tsk, void *cancel_value);
static inline bool Task_IsCanceled(Task *tsk);
static inline Future *Task_GetAwaitedFuture(Task *tsk);
extern bool Task_Run(Task_Entry start, void *value, void **res);
#include "task.def.h"
#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#ifndef TASK_H
#define TASK_H
#include <stdbool.h>
#include "future.h"
#include "cor_platform.h"
typedef struct Task Task;
extern _Cor_thread_local Task *current_task;
typedef bool (*Task_Entry)(void *param, void **res);
extern void Task_ctor(Task *tsk, size_t stack_size, Task_Entry entry, void *param);
extern Task *Task_New(size_t stack_size, Task_Entry entry, void *param);
extern void Task_dtor(Task *tsk);
extern void Task_Delete(Task *tsk);
static inline bool Task_Await(Task *tsk, void **res);
extern void Task_Cancel(Task *tsk, void *cancel_value);
static inline bool Task_IsCanceled(Task *tsk);
static inline Future *Task_GetAwaitedFuture(Task *tsk);
extern bool Task_Run(size_t stack_size, Task_Entry start, void *value, void **res);
#include "task.def.h"
#endif