]> git.mdlowis.com Git - proto/cerise-os.git/commitdiff
add coroutine experiment
authorMichael D. Lowis <mike.lowis@gentex.com>
Sun, 4 Sep 2022 00:28:23 +0000 (20:28 -0400)
committerMichael D. Lowis <mike.lowis@gentex.com>
Sun, 4 Sep 2022 00:28:23 +0000 (20:28 -0400)
experiments/ctxswitch.c [new file with mode: 0644]

diff --git a/experiments/ctxswitch.c b/experiments/ctxswitch.c
new file mode 100644 (file)
index 0000000..06bf48c
--- /dev/null
@@ -0,0 +1,184 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+/* 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