From 38ae9c11815258dcac2d180bf7d3e975d0658a65 Mon Sep 17 00:00:00 2001 From: "Michael D. Lowis" Date: Sat, 3 Sep 2022 20:28:23 -0400 Subject: [PATCH] add coroutine experiment --- experiments/ctxswitch.c | 184 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 experiments/ctxswitch.c diff --git a/experiments/ctxswitch.c b/experiments/ctxswitch.c new file mode 100644 index 0000000..06bf48c --- /dev/null +++ b/experiments/ctxswitch.c @@ -0,0 +1,184 @@ +#include +#include + +/* TODO: + +* Added multi-threading +* Add message passing + +*/ + +typedef struct Task_T { + long* stack_top; // top of coroutine's stack + long* stack_base; // allocated memory for stack + struct Task_T* next; // pointer to next task + int id; +} Task_T; + +static struct { + Task_T* curr; + Task_T* head; + Task_T* tail; + Task_T* dead; +} Queue = { 0 }; + +extern void Task_Switch(Task_T* prev, Task_T* next); +asm ( +".global Task_Switch\n" +"Task_Switch:\n" +" push %rdi\n" +" push %rbp\n" +" push %rbx\n" +" push %r12\n" +" push %r13\n" +" push %r14\n" +" push %r15\n" +" mov %rsp,(%rdi)\n" +" mov (%rsi),%rsp\n" +" pop %r15\n" +" pop %r14\n" +" pop %r13\n" +" pop %r12\n" +" pop %rbx\n" +" pop %rbp\n" +" pop %rdi\n" +" ret\n" + ); + +void Enqueue(Task_T* task) +{ + /* enqueue the currently running task */ + if (task && task != Queue.dead) + { + if (Queue.tail) + { + Queue.tail->next = task; + task->next = NULL; + } + Queue.tail = task; + if (!Queue.head) + { + Queue.head = Queue.tail; + } + } +} + +void Task_Yield(void) +{ + if (Queue.head) + { + /* unload the current task */ + Task_T* curr = Queue.curr; + Enqueue(curr); + Queue.curr = NULL; + + /* now pick the next task and start it */ + Queue.curr = Queue.head; + Queue.head = Queue.head->next; + Queue.curr->next = NULL; + if (!Queue.head) + { + Queue.tail = NULL; + } + + /* run the selected task */ + Task_Switch(curr, Queue.curr); + + /* clean up the dead task if we have one */ + if (Queue.dead) + { + printf("destroying task %d\n", Queue.dead->id); + free(Queue.dead->stack_base); + free(Queue.dead); + Queue.dead = NULL; + } + } +} + +void Task_Exit(void) +{ + printf("exiting task %d\n", Queue.curr->id); + Queue.dead = Queue.curr; + if (Queue.head) + { + Task_Yield(); + } + else + { + exit(0); + } +} + +void Task_Create(void (*task_fn)(void*), void *arg, int stacksize) +{ + static int i = 0; + if (stacksize == 0) { stacksize = 1024*1024; } + Task_T* task = calloc(1, sizeof(Task_T)); + task->id = i++; + task->stack_base = calloc(stacksize/sizeof(long), sizeof(long)); + task->stack_top = &task->stack_base[stacksize/sizeof(long)-1]; // top of stack + *(--task->stack_top) = (long)Task_Exit; // coroutine cleanup + *(--task->stack_top) = (long)task_fn; // user's function to run (rop style!) + *(--task->stack_top) = (long)arg; // user's function argument (rdi) + for (int saved = 0; saved < 6; saved++) + { + *(--task->stack_top) = 0xdeadbeef; // initial values for saved registers + } + + /* enqueue the task */ + if (!Queue.curr) + { + Queue.curr = task; + } + else + { + Enqueue(task); + } + printf("created task %d\n", task->id); + Task_Yield(); +} + +void Task(void) +{ + /* create a task object for main */ + Task_Create(0, 0, 0); +} + +/*************************************** +MAIN +***************************************/ + +void task_sub(void *arg) +{ + for (int i = 0; i < 5; i++) + { + Task_Yield(); + printf(" Inside task %d\n", Queue.curr->id); + } +} + +void task_main(void *arg) +{ + for (int i = 0; i < 3; i++) + { + printf("spawning task %d\n", i); + Task_Create(task_sub, 0, 0); + } +} + +int main(int argc, char** argv) { + if (argc != 2) { return 1; } + Task(); + + int count = atoi(argv[1]); + for (int i = 0; i < count; i++) + { + printf("spawning task %d\n", i+1); + int* id = calloc(1, sizeof(int)); + *id = i; + Task_Create(task_sub, id, 0); + } + Task_Exit(); + printf("This is unreachable!\n"); + return 0; +} \ No newline at end of file -- 2.54.0