diff --git a/client/Makefile.djgpp b/client/Makefile.djgpp index e9afa66..6e09864 100644 --- a/client/Makefile.djgpp +++ b/client/Makefile.djgpp @@ -32,7 +32,7 @@ BINDIR = bin ## CHANGE THIS ## # CFLAGS, LDFLAGS, CPPFLAGS, PREFIX can be overriden on CLI -CFLAGS := $(DEBUG) -I$(SRCDIR) -I$(SRCDIR)/dos -I$(SRCDIR)/gui -I$(SRCDIR)/thirdparty -I$(SRCDIR)/thirdparty/memwatch -I$(SRCDIR)/thirdparty/serial +CFLAGS := $(DEBUG) -I$(SRCDIR) -I$(SRCDIR)/system -I$(SRCDIR)/dos -I$(SRCDIR)/gui -I$(SRCDIR)/thirdparty -I$(SRCDIR)/thirdparty/memwatch -I$(SRCDIR)/thirdparty/serial CPPFLAGS := LDFLAGS := PREFIX := /usr/local diff --git a/client/build.sh b/client/build.sh index 3cb3665..12bd7f3 100755 --- a/client/build.sh +++ b/client/build.sh @@ -18,7 +18,7 @@ # along with this program. If not, see . # -mkdir -p bin obj/dos obj/gui obj/thirdparty/memwatch obj/thirdparty/serial +mkdir -p bin obj/system obj/dos obj/gui obj/thirdparty/memwatch obj/thirdparty/serial source /opt/cross/djgpp/setenv make -f Makefile.djgpp rm bin/client diff --git a/client/client.pro b/client/client.pro index aed7ea2..8feeca2 100644 --- a/client/client.pro +++ b/client/client.pro @@ -41,55 +41,57 @@ LINUX_SOURCES = \ INCLUDEPATH += \ $$LINUX_INCLUDES \ $$PWD/src \ + $$PWD/src/system \ $$PWD/src/gui \ $$PWD/src/thirdparty HEADERS = \ $$LINUX_HEADERS \ - src/gui/listbox.h \ - src/gui/terminal.h \ - src/gui/updown.h \ - src/stddclmr.h \ + src/system/memory.h \ + src/system/keyboard.h \ + src/system/task.h \ + src/system/timer.h \ + src/system/array.h \ + src/system/log.h \ + src/system/mouse.h \ + src/system/vesa.h \ + src/system/os.h \ src/thirdparty/stb_ds.h \ src/thirdparty/stb_image.h \ src/thirdparty/memwatch/memwatch.h \ - src/gui/memory.h \ + src/thirdparty/minicoro/minicoro.h \ + src/gui/listbox.h \ + src/gui/terminal.h \ + src/gui/updown.h \ src/gui/button.h \ src/gui/checkbox.h \ src/gui/frame.h \ - src/gui/keyboard.h \ src/gui/label.h \ src/gui/picture.h \ src/gui/radio.h \ src/gui/rect.h \ - src/gui/task.h \ src/gui/textbox.h \ - src/gui/timer.h \ - src/gui/array.h \ src/gui/font.h \ src/gui/desktop.h \ src/gui/gui.h \ src/gui/widget.h \ src/gui/window.h \ - src/gui/log.h \ - src/gui/mouse.h \ - src/gui/vesa.h \ src/gui/image.h \ - src/gui/os.h + src/stddclmr.h SOURCES = \ $$LINUX_SOURCES \ + src/thirdparty/memwatch/memwatch.c \ + src/system/memory.c \ + src/system/array.c \ + src/system/log.c \ + src/system/timer.c \ + src/system/task.c \ src/gui/listbox.c \ src/gui/terminal.c \ src/gui/updown.c \ - src/thirdparty/memwatch/memwatch.c \ - src/gui/memory.c \ - src/gui/array.c \ src/gui/font.c \ src/gui/image.c \ - src/gui/log.c \ - src/gui/timer.c \ - src/gui/task.c \ src/gui/gui.c \ src/gui/desktop.c \ src/gui/widget.c \ diff --git a/client/src/gui/task.c b/client/src/gui/task.c deleted file mode 100644 index 66fc2de..0000000 --- a/client/src/gui/task.c +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Kangaroo Punch MultiPlayer Game Server Mark II - * Copyright (C) 2020-2021 Scott Duensing - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - - -// Based on https://brennan.io/2020/05/24/userspace-cooperative-multitasking/ - - -// https://sourceforge.net/p/predef/wiki/Home/ -#if !defined(__i386__) && !defined(__i486__) && !defined(__i586__) && !defined(__i686__) && !defined(__x86_64__) -#error "Only Intel 386 and later processors are supported." -#endif - -#if !defined(__DJGPP__) && !defined(__GNUC__) -#error "Requires GCC or DJGPP." -#endif - - -#include - -#include "os.h" -#include "task.h" -#include "array.h" - - -typedef struct TaskS { - enum { - STATE_CREATED, - STATE_RUNNING, - STATE_WAITING - } status; - uint32_t id; - jmp_buf buf; - void (*function)(void *); - void *data; - void *stackBottom; - void *stackTop; - uint32_t stackSize; -} TaskT; - -enum { - INIT = 0, - SCHEDULE, - EXIT_TASK, -}; - -typedef struct PrivateS { - jmp_buf buf; - TaskT *current; - TaskT **taskList; -} PrivateT; - - -static PrivateT _private; -static uint16_t _stackSizeInK; - - -static TaskT *taskChoose(void); -static void taskFree(void); -static void taskSchedule(void); - - -static TaskT *taskChoose(void) { - TaskT *task = NULL; - uint32_t len = arrlenu(_private.taskList); - uint32_t x; - - // Find the next task to run. - for (x=0; xstatus == STATE_CREATED || task->status == STATE_RUNNING) { - // Move it to the end of the list. - arrdel(_private.taskList, x); - arrput(_private.taskList, task); - break; - } - } - - return task; -} - - -void taskCreate(void (*function)(void *), void *data) { - static uint32_t id = 1; - TaskT *task = (TaskT *)malloc(sizeof(TaskT)); - - task->status = STATE_CREATED; - task->function = function; - task->data = data; - task->id = id++; - task->stackSize = _stackSizeInK * 1024; - task->stackBottom = malloc(task->stackSize); - task->stackTop = task->stackBottom + task->stackSize; - - arrput(_private.taskList, task); -} - - -void taskExit(void) { - TaskT *task = _private.current; - uint32_t len = arrlenu(_private.taskList); - uint32_t x; - - // Find our task. - for (x=0; xstackBottom); - free(task); -} - - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wimplicit-fallthrough" -void taskRun(void) { - // This is the exit path for the scheduler! - switch (setjmp(_private.buf)) { - case EXIT_TASK: - taskFree(); - - case INIT: - case SCHEDULE: - taskSchedule(); - // If we get here, there's nothing else to do so exit - return; - - default: - fprintf(stderr, "Task scheduler error!\n"); - return; - } -} -#pragma GCC diagnostic pop - - -static void taskSchedule(void) { - TaskT *next = taskChoose(); - - if (!next) return; - - _private.current = next; - - if (next->status == STATE_CREATED) { - // This task has not been started yet. - // Assign a new stack pointer, run the task, and exit it at the end. - register void *top = next->stackTop; -#ifdef __x86_64__ - // 64 bit. - asm volatile( - "mov %[rs], %%rsp \n" - : [ rs ] "+r" (top) :: - ); -#else - // 32 bit. - asm volatile( - "mov %[rs], %%esp \n" - : [ rs ] "+r" (top) :: - ); -#endif - // Run the task. - next->status = STATE_RUNNING; - next->function(next->data); - - // The stack pointer should be back where we set it. Returning would be a very, very bad idea. Let's instead exit. - taskExit(); - } else { - longjmp(next->buf, 1); - } - - // Execution doesn't get to here. -} - - -void taskShutdown(void) { - // Nothing yet. -} - - -void taskStartup(uint16_t stackInK) { - _stackSizeInK = stackInK; - _private.current = NULL; - _private.taskList = NULL; -} - - -void taskYield(void) { - if (setjmp(_private.current->buf)) { - return; - } else { - longjmp(_private.buf, SCHEDULE); - } -} diff --git a/client/src/gui/terminal.c b/client/src/gui/terminal.c index c88600a..b2702a0 100644 --- a/client/src/gui/terminal.c +++ b/client/src/gui/terminal.c @@ -25,6 +25,9 @@ #include "terminal.h" +#define termWrite(...) logWrite(__VA_ARGS__) + + #define TERMINAL_CELL_GET_FLAG(t,x,y,f) (((t)->cells[(x)][(y)].flags & (1 << (f))) != 0) #define TERMINAL_CELL_SET_FLAG(t,x,y,f) ((t)->cells[(x)][(y)].flags |= (1 << (f))) #define TERMINAL_CELL_CLEAR_FLAG(t,x,y,f) ((t)->cells[(x)][(y)].flags &= (~(1 << (f)))) @@ -32,7 +35,9 @@ static int16_t terminalConvertToInt(char *number, int16_t defaultValue); static void terminalDel(WidgetT **widget); +static void terminalFocusEvent(WidgetT *widget, uint8_t focused); static void terminalMouseEvent(WidgetT *widget, MouseT *mouse, uint16_t x, uint16_t y, uint8_t event); +static void terminalKeyboardEvent(WidgetT *widget, uint8_t ascii, uint8_t extended, uint8_t scancode, uint8_t shift, uint8_t control, uint8_t alt); static void terminalPaint(WidgetT *widget, RectT pos); static void terminalSequenceReset(TerminalT *terminal); @@ -94,6 +99,24 @@ static void terminalDel(WidgetT **widget) { } +static void terminalFocusEvent(WidgetT *widget, uint8_t focused) { + TerminalT *t = (TerminalT *)widget; + uint16_t x; + uint16_t y; + + (void)focused; + + // When focus changes, we need to mark ALL the cells dirty so they get redrawn when the window is redrawn. + for (y=0; yrows; y++) { + for (x=0; xcols; x++) { + TERMINAL_CELL_SET_FLAG(t, x, y, TERMINAL_FLAG_DIRTY); + } + } + + GUI_SET_FLAG(widget, WIDGET_FLAG_DIRTY); +} + + void terminalForegroundSet(TerminalT *terminal, uint8_t color) { terminal->foreground = color; terminal->bold = (color > 7); @@ -105,26 +128,28 @@ WidgetT *terminalInit(WidgetT *widget, uint16_t cols, uint16_t rows) { uint16_t x; uint16_t y; - t->base.delMethod = terminalDel; - t->base.paintMethod = terminalPaint; - t->base.mouseEventMethod = terminalMouseEvent; - t->font = _guiFont8; // Default font. Also see terminalNew. - t->cols = cols; - t->rows = rows; - t->cursorX = 1; - t->cursorY = 1; - t->saveX = 1; - t->saveY = 1; - t->bold = 0; - t->blink = 0; - t->reverse = 0; - t->background = TERMINAL_COLOR_BLACK; - t->foreground = TERMINAL_COLOR_LIGHT_GRAY; - t->escFound = 0; - t->inEscape = 0; - t->numberIndex = 0; - t->number[0] = 0; - t->parameters = NULL; + t->base.delMethod = terminalDel; + t->base.focusMethod = terminalFocusEvent; + t->base.paintMethod = terminalPaint; + t->base.keyboardEventMethod = terminalKeyboardEvent; + t->base.mouseEventMethod = terminalMouseEvent; + t->font = _guiFont8; // Default font. Also see terminalNew. + t->cols = cols; + t->rows = rows; + t->cursorX = 1; + t->cursorY = 1; + t->saveX = 1; + t->saveY = 1; + t->bold = 0; + t->blink = 0; + t->reverse = 0; + t->background = TERMINAL_COLOR_BLACK; + t->foreground = TERMINAL_COLOR_LIGHT_GRAY; + t->escFound = 0; + t->inEscape = 0; + t->numberIndex = 0; + t->number[0] = 0; + t->parameters = NULL; // Allocate space for column data. t->cells = (CellT **)malloc(sizeof(CellT *) * cols); @@ -174,6 +199,11 @@ WidgetT *terminalInit(WidgetT *widget, uint16_t cols, uint16_t rows) { } +static void terminalKeyboardEvent(WidgetT *widget, uint8_t ascii, uint8_t extended, uint8_t scancode, uint8_t shift, uint8_t control, uint8_t alt) { + +} + + static void terminalMouseEvent(WidgetT *widget, MouseT *mouse, uint16_t x, uint16_t y, uint8_t event) { TerminalT *t = (TerminalT *)widget; @@ -273,6 +303,7 @@ void terminalPrintChar(TerminalT *terminal, uint8_t c) { // Is this a sequence that is ESC and then not [ ? if (terminal->escFound && !terminal->inEscape) { + termWrite("Unexpected character: '%c' %d\n", c, c); terminalSequenceReset(terminal); return; } @@ -282,6 +313,7 @@ void terminalPrintChar(TerminalT *terminal, uint8_t c) { case '[': case 27: + termWrite("Escape found inside sequence\n"); // ESC sequence inside sequence. Invalid ANSIBBS. //***TODO*** Can stick custom sequences here. break; @@ -296,6 +328,7 @@ void terminalPrintChar(TerminalT *terminal, uint8_t c) { // Cursor up. case 'A': y = terminalConvertToInt(terminal->number, 1); + termWrite("Moving cursor up %d\n", y); terminalCursorMove(terminal, terminal->cursorX, terminal->cursorY - y); terminalSequenceReset(terminal); break; @@ -303,6 +336,7 @@ void terminalPrintChar(TerminalT *terminal, uint8_t c) { // Cursor down. case 'B': y = terminalConvertToInt(terminal->number, 1); + termWrite("Moving cursor down %d\n", y); terminalCursorMove(terminal, terminal->cursorX, terminal->cursorY + y); terminalSequenceReset(terminal); break; @@ -310,6 +344,7 @@ void terminalPrintChar(TerminalT *terminal, uint8_t c) { // Cursor forward. case 'C': x = terminalConvertToInt(terminal->number, 1); + termWrite("Moving cursor forward %d\n", x); terminalCursorMove(terminal, terminal->cursorX + x, terminal->cursorY); terminalSequenceReset(terminal); break; @@ -317,6 +352,7 @@ void terminalPrintChar(TerminalT *terminal, uint8_t c) { // Cursor backward. case 'D': x = terminalConvertToInt(terminal->number, 1); + termWrite("Moving cursor backward %d\n", x); terminalCursorMove(terminal, terminal->cursorX - x, terminal->cursorY); terminalSequenceReset(terminal); break; @@ -324,6 +360,7 @@ void terminalPrintChar(TerminalT *terminal, uint8_t c) { // Cursor line down. case 'E': y = terminalConvertToInt(terminal->number, 1); + termWrite("Moving cursor down %d lines\n", y); terminalCursorMove(terminal, 1, terminal->cursorY + y); //***TODO*** This should allow scrolling. terminalSequenceReset(terminal); @@ -332,6 +369,7 @@ void terminalPrintChar(TerminalT *terminal, uint8_t c) { // Cursor line up. case 'F': y = terminalConvertToInt(terminal->number, 1); + termWrite("Moving cursor up %d lines\n", y); terminalCursorMove(terminal, 1, terminal->cursorY - y); //***TODO*** This should allow scrolling. terminalSequenceReset(terminal); @@ -340,6 +378,7 @@ void terminalPrintChar(TerminalT *terminal, uint8_t c) { // Cursor horizontal absolute. case 'G': x = terminalConvertToInt(terminal->number, 1); + termWrite("Moving cursor horizontal absolute %d\n", x); terminalCursorMove(terminal, x, terminal->cursorY); terminalSequenceReset(terminal); break; @@ -351,18 +390,21 @@ void terminalPrintChar(TerminalT *terminal, uint8_t c) { // Cursor vertical absolute. Kinda. Moves X to 1. case 0: y = terminalConvertToInt(terminal->number, 1); + termWrite("Moving cursor kinda vertictal absolute %d\n", y); terminalCursorMove(terminal, 1, y); break; // Absolute position. case 1: - x = terminalConvertToInt(terminal->parameters[0], 1); - y = terminalConvertToInt(terminal->number, 1); + x = terminalConvertToInt(terminal->number, 1); + y = terminalConvertToInt(terminal->parameters[0], 1); + termWrite("Moving cursor absolute %d %d\n", x, y); terminalCursorMove(terminal, x, y); break; // Invalid nonsense. default: + termWrite("Unknown cursor move: '%c' %d\n", c, c); break; } terminalSequenceReset(terminal); @@ -371,8 +413,12 @@ void terminalPrintChar(TerminalT *terminal, uint8_t c) { // Clear display. case 'J': if (arrlen(terminal->parameters) == 0 && terminalConvertToInt(terminal->number, 1) == 2) { + termWrite("Clear display\n"); terminalScreenClear(terminal); terminalCursorMove(terminal, 1, 1); + } else { + //***TODO*** Unimplemented screen clear. + termWrite("Unimplemented clear display\n"); } terminalSequenceReset(terminal); break; @@ -381,6 +427,7 @@ void terminalPrintChar(TerminalT *terminal, uint8_t c) { case 'K': x = terminalConvertToInt(terminal->number, 0); if (x == 0) { + termWrite("Clear to end of line\n"); for (y=terminal->cursorX-1; ycols; y++) { terminal->cells[y][terminal->cursorY - 1].character = ' '; TERMINAL_CELL_SET_FLAG(terminal, y, terminal->cursorY - 1, TERMINAL_FLAG_DIRTY); @@ -388,6 +435,7 @@ void terminalPrintChar(TerminalT *terminal, uint8_t c) { } } else { //***TODO*** Unimplemented line clear. + termWrite("Unimplemented line clear\n"); } terminalSequenceReset(terminal); break; @@ -395,35 +443,41 @@ void terminalPrintChar(TerminalT *terminal, uint8_t c) { // Insert lines. case 'L': //***TODO*** Unimplemented. + termWrite("Unimplemented insert lines\n"); terminalSequenceReset(terminal); break; // Delete lines. case 'M': //***TODO*** Unimplemented. + termWrite("Unimplemented delete lines\n"); terminalSequenceReset(terminal); break; // Delete characters. case 'P': //***TODO*** Unimplemented. + termWrite("Unimplemented delete characters\n"); terminalSequenceReset(terminal); break; // Scroll up. case 'S': //***TODO*** Unimplemented. + termWrite("Unimplemented scroll up\n"); terminalSequenceReset(terminal); break; // Scroll down. case 'T': //***TODO*** Unimplemented. + termWrite("Unimplemented scroll down\n"); terminalSequenceReset(terminal); break; // Clear screen with normal attribute. case 'U': + termWrite("Clear screen with normal attribute\n"); x = terminal->background; terminal->background = TERMINAL_COLOR_BLACK; terminalScreenClear(terminal); @@ -435,6 +489,7 @@ void terminalPrintChar(TerminalT *terminal, uint8_t c) { // Back-TAB. case 'Z': //***TODO*** Unimplemented. + termWrite("Unimplemented back-TAB\n"); terminalSequenceReset(terminal); break; @@ -446,6 +501,7 @@ void terminalPrintChar(TerminalT *terminal, uint8_t c) { switch (x) { // Reset. case 0: + termWrite("Attribute reset\n"); terminal->bold = 0; terminal->blink = 0; terminal->reverse = 0; @@ -455,54 +511,71 @@ void terminalPrintChar(TerminalT *terminal, uint8_t c) { // Bold. case 1: + termWrite("Bold\n"); terminal->bold = 1; break; // Blink slow. case 5: + termWrite("Blink slow\n"); terminal->blink = TERMINAL_FLAG_BLINK_SLOW; break; // Blink fast. case 6: + termWrite("Blink fast\n"); terminal->blink = TERMINAL_FLAG_BLINK_FAST; break; // Reverse. case 7: if (!terminal->reverse) { + termWrite("Reverse\n"); x = terminal->foreground; terminal->foreground = terminal->background; terminal->background = x; terminal->reverse = 1; + } else { + termWrite("Already reversed\n"); } break; // Normal intensity. (22 is the actual code, unsure exactly what 21 is.) case 21: case 22: + termWrite("Normal intensity\n"); terminal->bold = 0; break; // Steady. case 25: + termWrite("Steady\n"); terminal->blink = 0; break; - // Un-reverse? + // Normal case 27: if (terminal->reverse) { + termWrite("Normal\n"); x = terminal->foreground; terminal->foreground = terminal->background; terminal->background = x; terminal->reverse = 0; + } else { + termWrite("Already normal\n"); } break; // Color change. default: - if (x > 29 && x < 38) terminal->foreground = x - 30; - if (x > 39 && x < 48) terminal->background = x - 40; + if (x > 29 && x < 38) { + terminal->foreground = x - 30; + termWrite("Foreground %d\n", terminal->foreground); + } + if (x > 39 && x < 48) { + terminal->background = x - 40; + termWrite("Background %d\n", terminal->background); + } break; } // switch (x) @@ -513,6 +586,7 @@ void terminalPrintChar(TerminalT *terminal, uint8_t c) { // Define scroll region. case 'r': //***TODO*** Unimplemented. + termWrite("Unimplemented define scroll region\n"); terminalSequenceReset(terminal); break; @@ -520,11 +594,13 @@ void terminalPrintChar(TerminalT *terminal, uint8_t c) { case 's': terminal->saveX = terminal->cursorX; terminal->saveY = terminal->cursorY; + termWrite("Cursor save %d %d\n", terminal->saveX, terminal->saveX); terminalSequenceReset(terminal); break; // Cursor restore. case 'u': + termWrite("Cursor restore %d %d\n", terminal->saveX, terminal->saveX); terminalCursorMove(terminal, terminal->saveX, terminal->saveY); terminalSequenceReset(terminal); break; @@ -537,6 +613,7 @@ void terminalPrintChar(TerminalT *terminal, uint8_t c) { terminal->number[terminal->numberIndex] = 0; } else { // Unknown sequence. + termWrite("Unknown sequence: '%c' %d\n", c, c); terminalSequenceReset(terminal); } @@ -549,31 +626,37 @@ void terminalPrintChar(TerminalT *terminal, uint8_t c) { // Bell. case 7: //***TODO*** Unimplemented. + termWrite("Unimplemented bell\n"); break; // Backspace. case 8: case 128: //***TODO*** Unimplemented. + termWrite("Unimplemented backspace\n"); break; // TAB case 9: //***TODO*** Unimplemented. + termWrite("Unimplemented TAB\n"); break; // Line feed. case 10: terminal->cursorY++; + termWrite("Line feed\n"); if (terminal->cursorY > terminal->rows) { terminal->cursorY--; //***TODO*** Scroll. + termWrite("Unimplemented line feed scroll\n"); } terminalCursorMove(terminal, terminal->cursorX, terminal->cursorY); break; - // Clear screen. + // Clear screen (form feed). case 12: + termWrite("Form feed\n"); x = terminal->background; terminal->background = TERMINAL_COLOR_BLACK; terminalScreenClear(terminal); @@ -583,6 +666,7 @@ void terminalPrintChar(TerminalT *terminal, uint8_t c) { // Carrage return. case 13: + termWrite("Carrage return\n"); terminalCursorMove(terminal, 1, terminal->cursorY); break; @@ -605,6 +689,7 @@ void terminalPrintChar(TerminalT *terminal, uint8_t c) { if (terminal->cursorY > terminal->rows) { terminal->cursorY--; //***TODO*** Scroll. + termWrite("Unimplemented render scroll\n"); } } terminalCursorMove(terminal, terminal->cursorX, terminal->cursorY); @@ -621,11 +706,12 @@ void terminalScreenClear(TerminalT *terminal) { uint16_t y; // Does not move cursor. - // Should this clear the blink flags? for (y=0; yrows; y++) { for (x=0; xcols; x++) { terminal->cells[x][y].character = ' '; + TERMINAL_CELL_CLEAR_FLAG(terminal, x, y, TERMINAL_FLAG_BLINK_SLOW); + TERMINAL_CELL_CLEAR_FLAG(terminal, x, y, TERMINAL_FLAG_BLINK_FAST); TERMINAL_CELL_SET_FLAG(terminal, x, y, TERMINAL_FLAG_DIRTY); } } diff --git a/client/src/main.c b/client/src/main.c index c9f7458..2effdd3 100644 --- a/client/src/main.c +++ b/client/src/main.c @@ -62,6 +62,10 @@ #include "terminal.h" +static TerminalT *t1 = NULL; +static uint8_t lastKey = 0; + + void buttonClick(WidgetT *widget) { logWrite("'%s' was clicked.\n", ((ButtonT *)widget)->title); } @@ -107,6 +111,7 @@ void mainLoop(void *data) { mouse = mouseRead(); if (keyHit()) { key = keyASCII(); + lastKey = key; //***DEBUG*** guiProcessKeyboard(keyASCII(), keyExtended(), keyScanCode(), keyShift(), keyControl(), keyAlt()); //logWrite("Key '%d' Extended '%d' Scancode '%d' Shift '%d' Control '%d' Alt '%d'\n", key, keyExtended(), keyScanCode(), keyShift(), keyControl(), keyAlt()); } else { @@ -132,6 +137,40 @@ void mainLoop(void *data) { } +void terminalTest(void *data) { + FILE *in = NULL; + char *buffer = NULL; + uint16_t length = 0; + uint16_t x = 0; + + (void)data; + + // Load ANSI file for terminal test. + in = fopen("kanga.ans", "rt"); + fseek(in, 0, SEEK_END); + length = ftell(in); + fseek(in, 0, SEEK_SET); + buffer = (char *)malloc(length + 1); + fread(buffer, 1, length, in); + fclose(in); + buffer[length] = 0; + + logWrite("ANSI loaded.\n"); + + while (x < length) { + if (lastKey == 27) break; +// if (lastKey == ' ') { + lastKey = 0; + terminalPrintChar(t1, buffer[x]); + x++; +// } + taskYield(); + } + + free(buffer); +} + + void test(void *data) { DesktopT *desktop = (DesktopT *)guiRootGet(); WindowT *w1 = NULL; @@ -153,10 +192,6 @@ void test(void *data) { TextboxT *tb2 = NULL; UpdownT *u1 = NULL; ListboxT *lb1 = NULL; - TerminalT *t1 = NULL; - FILE *in = NULL; - char *buffer = NULL; - uint16_t length = 0; (void)data; @@ -224,16 +259,8 @@ void test(void *data) { // Window 4 - Terminal t1 = terminalNew(0, 0, 80, 24); guiAttach(W(w4), W(t1)); - in = fopen("kanga.ans", "rt"); - fseek(in, 0, SEEK_END); - length = ftell(in); - fseek(in, 0, SEEK_SET); - buffer = (char *)malloc(length + 1); - fread(buffer, 1, length, in); - fclose(in); - buffer[length] = 0; - terminalPrint(t1, buffer); - free(buffer); + + taskCreate(terminalTest, NULL); } @@ -252,6 +279,7 @@ int main(int argc, char *argv[]) { // 12345678901234567890123456789012345678901234567890123456789012345678901234567890 printf("Kangaroo Punch MultiPlayer DOS Game Client Mark II\n"); printf("Copyright (C) 2020-2021 Scott Duensing scott@kangaroopunch.com\n\n"); + fflush(stdout); // Find last portion of filename. while (x > 0) { @@ -271,6 +299,7 @@ int main(int argc, char *argv[]) { // Command line needs to have the desired resolution and color depth on it. if (argc != 4) { vbeShowInfo(); + fflush(stdout); memoryShutdown(); return 0; } @@ -280,6 +309,7 @@ int main(int argc, char *argv[]) { // Do we have the video mode they asked for? if (vbeStartup(xResolution, yResolution, colorDepth)) { + fflush(stdout); memoryShutdown(); return 1; } @@ -287,7 +317,7 @@ int main(int argc, char *argv[]) { mouseStartup(); timerStartup(); guiStartup(); - taskStartup(64); + taskStartup(); taskCreate(test, NULL); taskCreate(mainLoop, NULL); diff --git a/client/src/gui/array.c b/client/src/system/array.c similarity index 100% rename from client/src/gui/array.c rename to client/src/system/array.c diff --git a/client/src/gui/array.h b/client/src/system/array.h similarity index 100% rename from client/src/gui/array.h rename to client/src/system/array.h diff --git a/client/src/gui/keyboard.h b/client/src/system/keyboard.h similarity index 100% rename from client/src/gui/keyboard.h rename to client/src/system/keyboard.h diff --git a/client/src/gui/log.c b/client/src/system/log.c similarity index 100% rename from client/src/gui/log.c rename to client/src/system/log.c diff --git a/client/src/gui/log.h b/client/src/system/log.h similarity index 100% rename from client/src/gui/log.h rename to client/src/system/log.h diff --git a/client/src/gui/memory.c b/client/src/system/memory.c similarity index 100% rename from client/src/gui/memory.c rename to client/src/system/memory.c diff --git a/client/src/gui/memory.h b/client/src/system/memory.h similarity index 100% rename from client/src/gui/memory.h rename to client/src/system/memory.h diff --git a/client/src/gui/mouse.h b/client/src/system/mouse.h similarity index 100% rename from client/src/gui/mouse.h rename to client/src/system/mouse.h diff --git a/client/src/gui/os.h b/client/src/system/os.h similarity index 100% rename from client/src/gui/os.h rename to client/src/system/os.h diff --git a/client/src/system/task.c b/client/src/system/task.c new file mode 100644 index 0000000..e8d42b6 --- /dev/null +++ b/client/src/system/task.c @@ -0,0 +1,115 @@ +/* + * Kangaroo Punch MultiPlayer Game Server Mark II + * Copyright (C) 2020-2021 Scott Duensing + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + + +#define MINICORO_IMPL +#define MCO_USE_ASM +#include "minicoro/minicoro.h" + + +#include "array.h" +#include "task.h" + + +typedef struct TaskS { + mco_coro *coroutine; + void (*function)(void *); + void *data; +} TaskT; + + +static TaskT **_taskList = NULL; + + +static void taskHandle(mco_coro* coroutine); + + +uint8_t taskCreate(void (*function)(void *), void *data) { + mco_desc desc = mco_desc_init(taskHandle, 0); + mco_result res = 0; + TaskT *task = (TaskT *)malloc(sizeof(TaskT)); + + if (task) { + task->function = function; + task->data = data; + desc.user_data = task; + res = mco_create(&task->coroutine, &desc); + if (res != MCO_SUCCESS) { + mco_destroy(task->coroutine); + free(task); + return 1; // Failed + } + arrput(_taskList, task); + } + + return 1; // Failed +} + + +static void taskHandle(mco_coro* coroutine) { + TaskT *task = (TaskT *)mco_get_user_data(coroutine); + task->function(task->data); +} + + +void taskRun(void) { + uint16_t taskIndex = 0; + + // Run until there are no more tasks. + while (arrlen(_taskList) > 0) { + + // Run each task in order. + taskIndex = 0; + while (taskIndex < arrlen(_taskList)) { + + // Run task. + mco_resume(_taskList[taskIndex]->coroutine); + // Did it finish? + if (mco_status(_taskList[taskIndex]->coroutine) == MCO_DEAD) { + // Task ended. Remove it. + mco_destroy(_taskList[taskIndex]->coroutine); + free(_taskList[taskIndex]); + arrdel(_taskList, taskIndex); + } else { + // Next task. + taskIndex++; + } + + } // while each task + + } // while tasks exist + + //arrfree(_taskList); + _taskList = NULL; +} + + +void taskShutdown(void) { + // Nada +} + + +void taskStartup(void) { + // Nada +} + + +void taskYield(void) { + mco_yield(mco_running()); +} diff --git a/client/src/gui/task.h b/client/src/system/task.h similarity index 80% rename from client/src/gui/task.h rename to client/src/system/task.h index ed1052c..543fab2 100644 --- a/client/src/gui/task.h +++ b/client/src/system/task.h @@ -22,15 +22,14 @@ #define TASK_H -#include "os.h" +#include -void taskCreate(void (*function)(void *), void *data); -void taskExit(void); -void taskRun(void); -void taskShutdown(void); -void taskStartup(uint16_t stackInK); -void taskYield(void); +uint8_t taskCreate(void (*function)(void *), void *data); +void taskRun(void); +void taskShutdown(void); +void taskStartup(void); +void taskYield(void); #endif // TASK_H diff --git a/client/src/gui/timer.c b/client/src/system/timer.c similarity index 100% rename from client/src/gui/timer.c rename to client/src/system/timer.c diff --git a/client/src/gui/timer.h b/client/src/system/timer.h similarity index 100% rename from client/src/gui/timer.h rename to client/src/system/timer.h diff --git a/client/src/gui/vesa.h b/client/src/system/vesa.h similarity index 100% rename from client/src/gui/vesa.h rename to client/src/system/vesa.h diff --git a/client/src/thirdparty/minicoro/LICENSE b/client/src/thirdparty/minicoro/LICENSE new file mode 100644 index 0000000..1e5d195 --- /dev/null +++ b/client/src/thirdparty/minicoro/LICENSE @@ -0,0 +1,47 @@ +This software is available as a choice of the following licenses. Choose +whichever you prefer. + +=============================================================================== +ALTERNATIVE 1 - Public Domain (www.unlicense.org) +=============================================================================== +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. + +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to + +=============================================================================== +ALTERNATIVE 2 - MIT No Attribution +=============================================================================== +Copyright (c) 2021 Eduardo Bart (https://github.com/edubart/minicoro) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/client/src/thirdparty/minicoro/README.md b/client/src/thirdparty/minicoro/README.md new file mode 100644 index 0000000..f5093aa --- /dev/null +++ b/client/src/thirdparty/minicoro/README.md @@ -0,0 +1,350 @@ +# minicoro + +Minicoro is single-file library for using asymmetric coroutines in C. +The API is inspired by [Lua coroutines](https://www.lua.org/manual/5.4/manual.html#6.2) but with C use in mind. + +The project is being developed mainly to be a coroutine backend +for the [Nelua](https://github.com/edubart/nelua-lang) programming language. + +The library assembly implementation is inspired by [Lua Coco](https://coco.luajit.org/index.html) by Mike Pall. + +# Features + +- Stackful asymmetric coroutines. +- Supports nesting coroutines (resuming a coroutine from another coroutine). +- Supports custom allocators. +- Storage system to allow passing values between yield and resume. +- Customizable stack size. +- Coroutine API design inspired by Lua with C use in mind. +- Yield across any C function. +- Made to work in multithread applications. +- Cross platform. +- Minimal, self contained and no external dependencies. +- Readable sources and documented. +- Implemented via assembly, ucontext or fibers. +- Lightweight and very efficient. +- Works in most C89 compilers. +- Error prone API, returning proper error codes on misuse. +- Support running with Valgrind, ASan (AddressSanitizer) and TSan (ThreadSanitizer). + +# Supported Platforms + +Most platforms are supported through different methods: + +| Platform | Assembly Method | Fallback Method | +|--------------|------------------|-------------------| +| Android | ARM/ARM64 | N/A | +| iOS | ARM/ARM64 | N/A | +| Windows | x86_64 | Windows fibers | +| Linux | x86_64/i686 | ucontext | +| Mac OS X | x86_64 | ucontext | +| WebAssembly | N/A | Emscripten fibers | +| Raspberry Pi | ARM | ucontext | +| RISC-V | rv64/rv32 | ucontext | + +The assembly method is used by default if supported by the compiler and CPU, +otherwise ucontext or fiber method is used as a fallback. + +The assembly method is very efficient, it just take a few cycles +to create, resume, yield or destroy a coroutine. + +# Caveats + +- Don't use coroutines with C++ exceptions, this is not supported. +- When using C++ RAII (i.e. destructors) you must resume the coroutine until it dies to properly execute all destructors. +- To use in multithread applications, you must compile with C compiler that supports `thread_local` qualifier. +- Some unsupported sanitizers for C may trigger false warnings when using coroutines. +- The `mco_coro` object is not thread safe, you should lock each coroutine into a thread. +- Stack space is fixed, it cannot grow. By default it has about 56KB of space, this can be changed on coroutine creation. +- Take care to not cause stack overflows (run out of stack space), otherwise your program may crash or not, the behavior is undefined. +- On WebAssembly you must compile with emscripten flag `-s ASYNCIFY=1`. + +# Introduction + +A coroutine represents an independent "green" thread of execution. +Unlike threads in multithread systems, however, +a coroutine only suspends its execution by explicitly calling a yield function. + +You create a coroutine by calling `mco_create`. +Its sole argument is a `mco_desc` structure with a description for the coroutine. +The `mco_create` function only creates a new coroutine and returns a handle to it, it does not start the coroutine. + +You execute a coroutine by calling `mco_resume`. +When calling a resume function the coroutine starts its execution by calling its body function. +After the coroutine starts running, it runs until it terminates or yields. + +A coroutine yields by calling `mco_yield`. +When a coroutine yields, the corresponding resume returns immediately, +even if the yield happens inside nested function calls (that is, not in the main function). +The next time you resume the same coroutine, it continues its execution from the point where it yielded. + +To associate a persistent value with the coroutine, +you can optionally set `user_data` on its creation and later retrieve with `mco_get_user_data`. + +To pass values between resume and yield, +you can optionally use `mco_push` and `mco_pop` APIs, +they are intended to pass temporary values using a LIFO (Last In, First Out) style buffer. +The storage system can also be used to send and receive initial values on coroutine creation or before it finishes. + +# Usage + +To use minicoro, do the following in one .c file: + +```c +#define MINICORO_IMPL +#include "minicoro.h" +``` + +You can do `#include "minicoro.h"` in other parts of the program just like any other header. + +## Minimal Example + +The following simple example demonstrates on how to use the library: + +```c +#define MINICORO_IMPL +#include "minicoro.h" +#include + +// Coroutine entry function. +void coro_entry(mco_coro* co) { + printf("coroutine 1\n"); + mco_yield(co); + printf("coroutine 2\n"); +} + +int main() { + // First initialize a `desc` object through `mco_desc_init`. + mco_desc desc = mco_desc_init(coro_entry, 0); + // Configure `desc` fields when needed (e.g. customize user_data or allocation functions). + desc.user_data = NULL; + // Call `mco_create` with the output coroutine pointer and `desc` pointer. + mco_coro* co; + mco_result res = mco_create(&co, &desc); + assert(res == MCO_SUCCESS); + // The coroutine should be now in suspended state. + assert(mco_status(co) == MCO_SUSPENDED); + // Call `mco_resume` to start for the first time, switching to its context. + res = mco_resume(co); // Should print "coroutine 1". + assert(res == MCO_SUCCESS); + // We get back from coroutine context in suspended state (because it's unfinished). + assert(mco_status(co) == MCO_SUSPENDED); + // Call `mco_resume` to resume for a second time. + res = mco_resume(co); // Should print "coroutine 2". + assert(res == MCO_SUCCESS); + // The coroutine finished and should be now dead. + assert(mco_status(co) == MCO_DEAD); + // Call `mco_destroy` to destroy the coroutine. + res = mco_destroy(co); + assert(res == MCO_SUCCESS); + return 0; +} +``` + +_NOTE_: In case you don't want to use the minicoro allocator system you should +allocate a coroutine object yourself using `mco_desc.coro_size` and call `mco_init`, +then later to destroy call `mco_uninit` and deallocate it. + +## Yielding from anywhere + +You can yield the current running coroutine from anywhere +without having to pass `mco_coro` pointers around, +to this just use `mco_yield(mco_running())`. + +## Passing data between yield and resume + +The library has the storage interface to assist passing data between yield and resume. +It's usage is straightforward, +use `mco_push` to send data before a `mco_resume` or `mco_yield`, +then later use `mco_pop` after a `mco_resume` or `mco_yield` to receive data. +Take care to not mismatch a push and pop, otherwise these functions will return +an error. + +## Error handling + +The library return error codes in most of its API in case of misuse or system error, +the user is encouraged to handle them properly. + +## Library customization + +The following can be defined to change the library behavior: + +- `MCO_API` - Public API qualifier. Default is `extern`. +- `MCO_MIN_STACK_SIZE` - Minimum stack size when creating a coroutine. Default is 32768. +- `MCO_DEFAULT_STORAGE_SIZE` - Size of coroutine storage buffer. Default is 1024. +- `MCO_DEFAULT_STACK_SIZE` - Default stack size when creating a coroutine. Default is 57344. +- `MCO_MALLOC` - Default allocation function. Default is `malloc`. +- `MCO_FREE` - Default deallocation function. Default is `free`. +- `MCO_DEBUG` - Enable debug mode, logging any runtime error to stdout. Defined automatically unless `NDEBUG` or `MCO_NO_DEBUG` is defined. +- `MCO_NO_DEBUG` - Disable debug mode. +- `MCO_NO_MULTITHREAD` - Disable multithread usage. Multithread is supported when `thread_local` is supported. +- `MCO_NO_DEFAULT_ALLOCATORS` - Disable the default allocator using `MCO_MALLOC` and `MCO_FREE`. +- `MCO_ZERO_MEMORY` - Zero memory of stack for new coroutines and when poping storage, intended for garbage collected environments. +- `MCO_USE_ASM` - Force use of assembly context switch implementation. +- `MCO_USE_UCONTEXT` - Force use of ucontext context switch implementation. +- `MCO_USE_FIBERS` - Force use of fibers context switch implementation. +- `MCO_USE_VALGRIND` - Define if you want run with valgrind to fix accessing memory errors. + +# Benchmarks + +The coroutine library was benchmarked for x86_64 counting CPU cycles +for context switch (triggered in resume or yield) and initialization. + +| CPU Arch | OS | Method | Context switch | Initialize | Uninitialize | +|----------|----------|----------|----------------|--------------|--------------| +| x86_64 | Linux | assembly | 9 cycles | 31 cycles | 14 cycles | +| x86_64 | Linux | ucontext | 352 cycles | 383 cycles | 14 cycles | +| x86_64 | Windows | fibers | 69 cycles | 10564 cycles | 11167 cycles | +| x86_64 | Windows | assembly | 33 cycles | 74 cycles | 14 cycles | + +_NOTE_: Tested on Intel Core i7-8750H CPU @ 2.20GHz with pre allocated coroutines. + +# Cheatsheet + +Here is a list of all library functions for quick reference: + +```c +/* Structure used to initialize a coroutine. */ +typedef struct mco_desc { + void (*func)(mco_coro* co); /* Entry point function for the coroutine. */ + void* user_data; /* Coroutine user data, can be get with `mco_get_user_data`. */ + /* Custom allocation interface. */ + void* (*malloc_cb)(size_t size, void* allocator_data); /* Custom allocation function. */ + void (*free_cb)(void* ptr, void* allocator_data); /* Custom deallocation function. */ + void* allocator_data; /* User data pointer passed to `malloc`/`free` allocation functions. */ + size_t storage_size; /* Coroutine storage size, to be used with the storage APIs. */ + /* These must be initialized only through `mco_init_desc`. */ + size_t coro_size; /* Coroutine structure size. */ + size_t stack_size; /* Coroutine stack size. */ +} mco_desc; + +/* Coroutine functions. */ +mco_desc mco_desc_init(void (*func)(mco_coro* co), size_t stack_size); /* Initialize description of a coroutine. When stack size is 0 then MCO_DEFAULT_STACK_SIZE is used. */ +mco_result mco_init(mco_coro* co, mco_desc* desc); /* Initialize the coroutine. */ +mco_result mco_uninit(mco_coro* co); /* Uninitialize the coroutine, may fail if it's not dead or suspended. */ +mco_result mco_create(mco_coro** out_co, mco_desc* desc); /* Allocates and initializes a new coroutine. */ +mco_result mco_destroy(mco_coro* co); /* Uninitialize and deallocate the coroutine, may fail if it's not dead or suspended. */ +mco_result mco_resume(mco_coro* co); /* Starts or continues the execution of the coroutine. */ +mco_result mco_yield(mco_coro* co); /* Suspends the execution of a coroutine. */ +mco_state mco_status(mco_coro* co); /* Returns the status of the coroutine. */ +void* mco_get_user_data(mco_coro* co); /* Get coroutine user data supplied on coroutine creation. */ + +/* Storage interface functions, used to pass values between yield and resume. */ +mco_result mco_push(mco_coro* co, const void* src, size_t len); /* Push bytes to the coroutine storage. Use to send values between yield and resume. */ +mco_result mco_pop(mco_coro* co, void* dest, size_t len); /* Pop bytes from the coroutine storage. Use to get values between yield and resume. */ +mco_result mco_peek(mco_coro* co, void* dest, size_t len); /* Like `mco_pop` but it does not consumes the storage. */ +size_t mco_get_bytes_stored(mco_coro* co); /* Get the available bytes that can be retrieved with a `mco_pop`. */ +size_t mco_get_storage_size(mco_coro* co); /* Get the total storage size. */ + +/* Misc functions. */ +mco_coro* mco_running(void); /* Returns the running coroutine for the current thread. */ +const char* mco_result_description(mco_result res); /* Get the description of a result. */ +``` + +# Complete Example + +The following is a more complete example, generating Fibonacci numbers: + +```c +#define MINICORO_IMPL +#include "minicoro.h" +#include + +static void fail(const char* message, mco_result res) { + printf("%s: %s\n", message, mco_result_description(res)); + exit(-1); +} + +static void fibonacci_coro(mco_coro* co) { + unsigned long m = 1; + unsigned long n = 1; + + /* Retrieve max value. */ + unsigned long max; + mco_result res = mco_pop(co, &max, sizeof(max)); + if(res != MCO_SUCCESS) + fail("Failed to retrieve coroutine storage", res); + + while(1) { + /* Yield the next Fibonacci number. */ + mco_push(co, &m, sizeof(m)); + res = mco_yield(co); + if(res != MCO_SUCCESS) + fail("Failed to yield coroutine", res); + + unsigned long tmp = m + n; + m = n; + n = tmp; + if(m >= max) + break; + } + + /* Yield the last Fibonacci number. */ + mco_push(co, &m, sizeof(m)); +} + +int main() { + /* Create the coroutine. */ + mco_coro* co; + mco_desc desc = mco_desc_init(fibonacci_coro, 0); + mco_result res = mco_create(&co, &desc); + if(res != MCO_SUCCESS) + fail("Failed to create coroutine", res); + + /* Set storage. */ + unsigned long max = 1000000000; + mco_push(co, &max, sizeof(max)); + + int counter = 1; + while(mco_status(co) == MCO_SUSPENDED) { + /* Resume the coroutine. */ + res = mco_resume(co); + if(res != MCO_SUCCESS) + fail("Failed to resume coroutine", res); + + /* Retrieve storage set in last coroutine yield. */ + unsigned long ret = 0; + res = mco_pop(co, &ret, sizeof(ret)); + if(res != MCO_SUCCESS) + fail("Failed to retrieve coroutine storage", res); + printf("fib %d = %lu\n", counter, ret); + counter = counter + 1; + } + + /* Destroy the coroutine. */ + res = mco_destroy(co); + if(res != MCO_SUCCESS) + fail("Failed to destroy coroutine", res); + return 0; +} +``` + +# Updates + +- **01-Sep-2021**: Added support for DOSBox (MS-DOS Emulator). +- **30-Aug-2021**: Fix stack overflow crash on Windows 32 bits. +- **22-Aug-2021**: Added checks for stack overflow and iOS support (thanks @srberg). +- **12-Mar-2021**: Added support for RISC-V RV32. +- **19-Jan-2021**: Fix compilation and issues on Mac OS X, release v0.1.1. +- **19-Jan-2021**: First release, v0.1.0. +- **18-Jan-2021**: Fix issues when using Clang on Linux. +- **17-Jan-2021**: Add support for RISC-V 64 bits. +- **16-Jan-2021**: Add support for Mac OS X x86_64, thanks @RandyGaul for testing, debugging and researching about it. +- **15-Jan-2021**: Make assembly method the default one on Windows x86_64. Redesigned the storage API, thanks @RandyGaul for the suggestion. +- **14-Jan-2021**: Add support for running with ASan (AddressSanitizer) and TSan (ThreadSanitizer). +- **13-Jan-2021**: Add support for ARM and WebAssembly. Add Public Domain and MIT No Attribution license. +- **12-Jan-2021**: Some API changes and improvements. +- **11-Jan-2021**: Support valgrind and add benchmarks. +- **10-Jan-2021**: Minor API improvements and document more. +- **09-Jan-2021**: Library created. + +# Donation + +I'm a full-time open source developer. +Any amount of the donation will be appreciated and could bring me encouragement to keep supporting this and other open source projects. + +[![Become a Patron](https://c5.patreon.com/external/logo/become_a_patron_button.png)](https://www.patreon.com/edubart) + +# License + +Your choice of either Public Domain or MIT No Attribution, see LICENSE file. diff --git a/client/src/thirdparty/minicoro/minicoro.h b/client/src/thirdparty/minicoro/minicoro.h new file mode 100644 index 0000000..9081805 --- /dev/null +++ b/client/src/thirdparty/minicoro/minicoro.h @@ -0,0 +1,1846 @@ +/* +Minimal asymmetric stackful cross-platform coroutine library in pure C. +minicoro - v0.1.2 - 02/Sep/2021 +Eduardo Bart - edub4rt@gmail.com +https://github.com/edubart/minicoro + +Minicoro is single file library for using asymmetric coroutines in C. +The API is inspired by Lua coroutines but with C use in mind. + +# Features + +- Stackful asymmetric coroutines. +- Supports nesting coroutines (resuming a coroutine from another coroutine). +- Supports custom allocators. +- Storage system to allow passing values between yield and resume. +- Customizable stack size. +- Coroutine API design inspired by Lua with C use in mind. +- Yield across any C function. +- Made to work in multithread applications. +- Cross platform. +- Minimal, self contained and no external dependencies. +- Readable sources and documented. +- Implemented via assembly, ucontext or fibers. +- Lightweight and very efficient. +- Works in most C89 compilers. +- Error prone API, returning proper error codes on misuse. +- Support running with Valgrind, ASan (AddressSanitizer) and TSan (ThreadSanitizer). + +# Supported Platforms + +Most platforms are supported through different methods: + +| Platform | Assembly Method | Fallback Method | +|--------------|------------------|-------------------| +| Android | ARM/ARM64 | N/A | +| iOS | ARM/ARM64 | N/A | +| Windows | x86_64 | Windows fibers | +| Linux | x86_64/i686 | ucontext | +| Mac OS X | x86_64 | ucontext | +| WebAssembly | N/A | Emscripten fibers | +| Raspberry Pi | ARM | ucontext | +| RISC-V | rv64/rv32 | ucontext | + +The assembly method is used by default if supported by the compiler and CPU, +otherwise ucontext or fiber method is used as a fallback. + +The assembly method is very efficient, it just take a few cycles +to create, resume, yield or destroy a coroutine. + +# Caveats + +- Don't use coroutines with C++ exceptions, this is not supported. +- When using C++ RAII (i.e. destructors) you must resume the coroutine until it dies to properly execute all destructors. +- To use in multithread applications, you must compile with C compiler that supports `thread_local` qualifier. +- Some unsupported sanitizers for C may trigger false warnings when using coroutines. +- The `mco_coro` object is not thread safe, you should lock each coroutine into a thread. +- Stack space is fixed, it cannot grow. By default it has about 56KB of space, this can be changed on coroutine creation. +- Take care to not cause stack overflows (run out of stack space), otherwise your program may crash or not, the behavior is undefined. +- On WebAssembly you must compile with emscripten flag `-s ASYNCIFY=1`. + +# Introduction + +A coroutine represents an independent "green" thread of execution. +Unlike threads in multithread systems, however, +a coroutine only suspends its execution by explicitly calling a yield function. + +You create a coroutine by calling `mco_create`. +Its sole argument is a `mco_desc` structure with a description for the coroutine. +The `mco_create` function only creates a new coroutine and returns a handle to it, it does not start the coroutine. + +You execute a coroutine by calling `mco_resume`. +When calling a resume function the coroutine starts its execution by calling its body function. +After the coroutine starts running, it runs until it terminates or yields. + +A coroutine yields by calling `mco_yield`. +When a coroutine yields, the corresponding resume returns immediately, +even if the yield happens inside nested function calls (that is, not in the main function). +The next time you resume the same coroutine, it continues its execution from the point where it yielded. + +To associate a persistent value with the coroutine, +you can optionally set `user_data` on its creation and later retrieve with `mco_get_user_data`. + +To pass values between resume and yield, +you can optionally use `mco_push` and `mco_pop` APIs, +they are intended to pass temporary values using a LIFO style buffer. +The storage system can also be used to send and receive initial values on coroutine creation or before it finishes. + +# Usage + +To use minicoro, do the following in one .c file: + +```c +#define MINICORO_IMPL +#include "minicoro.h" +``` + +You can do `#include "minicoro.h"` in other parts of the program just like any other header. + +## Minimal Example + +The following simple example demonstrates on how to use the library: + +```c +#define MINICORO_IMPL +#include "minicoro.h" +#include + +// Coroutine entry function. +void coro_entry(mco_coro* co) { + printf("coroutine 1\n"); + mco_yield(co); + printf("coroutine 2\n"); +} + +int main() { + // First initialize a `desc` object through `mco_desc_init`. + mco_desc desc = mco_desc_init(coro_entry, 0); + // Configure `desc` fields when needed (e.g. customize user_data or allocation functions). + desc.user_data = NULL; + // Call `mco_create` with the output coroutine pointer and `desc` pointer. + mco_coro* co; + mco_result res = mco_create(&co, &desc); + assert(res == MCO_SUCCESS); + // The coroutine should be now in suspended state. + assert(mco_status(co) == MCO_SUSPENDED); + // Call `mco_resume` to start for the first time, switching to its context. + res = mco_resume(co); // Should print "coroutine 1". + assert(res == MCO_SUCCESS); + // We get back from coroutine context in suspended state (because it's unfinished). + assert(mco_status(co) == MCO_SUSPENDED); + // Call `mco_resume` to resume for a second time. + res = mco_resume(co); // Should print "coroutine 2". + assert(res == MCO_SUCCESS); + // The coroutine finished and should be now dead. + assert(mco_status(co) == MCO_DEAD); + // Call `mco_destroy` to destroy the coroutine. + res = mco_destroy(co); + assert(res == MCO_SUCCESS); + return 0; +} +``` + +_NOTE_: In case you don't want to use the minicoro allocator system you should +allocate a coroutine object yourself using `mco_desc.coro_size` and call `mco_init`, +then later to destroy call `mco_uninit` and deallocate it. + +## Yielding from anywhere + +You can yield the current running coroutine from anywhere +without having to pass `mco_coro` pointers around, +to this just use `mco_yield(mco_running())`. + +## Passing data between yield and resume + +The library has the storage interface to assist passing data between yield and resume. +It's usage is straightforward, +use `mco_push` to send data before a `mco_resume` or `mco_yield`, +then later use `mco_pop` after a `mco_resume` or `mco_yield` to receive data. +Take care to not mismatch a push and pop, otherwise these functions will return +an error. + +## Error handling + +The library return error codes in most of its API in case of misuse or system error, +the user is encouraged to handle them properly. + +## Library customization + +The following can be defined to change the library behavior: + +- `MCO_API` - Public API qualifier. Default is `extern`. +- `MCO_MIN_STACK_SIZE` - Minimum stack size when creating a coroutine. Default is 32768. +- `MCO_DEFAULT_STORAGE_SIZE` - Size of coroutine storage buffer. Default is 1024. +- `MCO_DEFAULT_STACK_SIZE` - Default stack size when creating a coroutine. Default is 57344. +- `MCO_MALLOC` - Default allocation function. Default is `malloc`. +- `MCO_FREE` - Default deallocation function. Default is `free`. +- `MCO_DEBUG` - Enable debug mode, logging any runtime error to stdout. Defined automatically unless `NDEBUG` or `MCO_NO_DEBUG` is defined. +- `MCO_NO_DEBUG` - Disable debug mode. +- `MCO_NO_MULTITHREAD` - Disable multithread usage. Multithread is supported when `thread_local` is supported. +- `MCO_NO_DEFAULT_ALLOCATORS` - Disable the default allocator using `MCO_MALLOC` and `MCO_FREE`. +- `MCO_ZERO_MEMORY` - Zero memory of stack for new coroutines and when poping storage, intended for garbage collected environments. +- `MCO_USE_ASM` - Force use of assembly context switch implementation. +- `MCO_USE_UCONTEXT` - Force use of ucontext context switch implementation. +- `MCO_USE_FIBERS` - Force use of fibers context switch implementation. +- `MCO_USE_VALGRIND` - Define if you want run with valgrind to fix accessing memory errors. + +# License + +Your choice of either Public Domain or MIT No Attribution, see end of file. +*/ + + +#ifndef MINICORO_H +#define MINICORO_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* Public API qualifier. */ +#ifndef MCO_API +#define MCO_API extern +#endif + +/* Size of coroutine storage buffer. */ +#ifndef MCO_DEFAULT_STORAGE_SIZE +#define MCO_DEFAULT_STORAGE_SIZE 1024 +#endif + +#include /* for size_t */ + +/* ---------------------------------------------------------------------------------------------- */ + +/* Coroutine states. */ +typedef enum mco_state { + MCO_DEAD = 0, /* The coroutine has finished normally or was uninitialized before finishing. */ + MCO_NORMAL, /* The coroutine is active but not running (that is, it has resumed another coroutine). */ + MCO_RUNNING, /* The coroutine is active and running. */ + MCO_SUSPENDED /* The coroutine is suspended (in a call to yield, or it has not started running yet). */ +} mco_state; + +/* Coroutine result codes. */ +typedef enum mco_result { + MCO_SUCCESS = 0, + MCO_GENERIC_ERROR, + MCO_INVALID_POINTER, + MCO_INVALID_COROUTINE, + MCO_NOT_SUSPENDED, + MCO_NOT_RUNNING, + MCO_MAKE_CONTEXT_ERROR, + MCO_SWITCH_CONTEXT_ERROR, + MCO_NOT_ENOUGH_SPACE, + MCO_OUT_OF_MEMORY, + MCO_INVALID_ARGUMENTS, + MCO_INVALID_OPERATION, + MCO_STACK_OVERFLOW, +} mco_result; + +/* Coroutine structure. */ +typedef struct mco_coro mco_coro; +struct mco_coro { + void* context; + mco_state state; + void (*func)(mco_coro* co); + mco_coro* prev_co; + void* user_data; + void* allocator_data; + void (*free_cb)(void* ptr, void* allocator_data); + void* stack_base; /* Stack base address, can be used to scan memory in a garbage collector. */ + size_t stack_size; + unsigned char* storage; + size_t bytes_stored; + size_t storage_size; + void* asan_prev_stack; /* Used by address sanitizer. */ + void* tsan_prev_fiber; /* Used by thread sanitizer. */ + void* tsan_fiber; /* Used by thread sanitizer. */ + size_t magic_number; /* Used to check stack overflow. */ +}; + +/* Structure used to initialize a coroutine. */ +typedef struct mco_desc { + void (*func)(mco_coro* co); /* Entry point function for the coroutine. */ + void* user_data; /* Coroutine user data, can be get with `mco_get_user_data`. */ + /* Custom allocation interface. */ + void* (*malloc_cb)(size_t size, void* allocator_data); /* Custom allocation function. */ + void (*free_cb)(void* ptr, void* allocator_data); /* Custom deallocation function. */ + void* allocator_data; /* User data pointer passed to `malloc`/`free` allocation functions. */ + size_t storage_size; /* Coroutine storage size, to be used with the storage APIs. */ + /* These must be initialized only through `mco_init_desc`. */ + size_t coro_size; /* Coroutine structure size. */ + size_t stack_size; /* Coroutine stack size. */ +} mco_desc; + +/* Coroutine functions. */ +MCO_API mco_desc mco_desc_init(void (*func)(mco_coro* co), size_t stack_size); /* Initialize description of a coroutine. When stack size is 0 then MCO_DEFAULT_STACK_SIZE is used. */ +MCO_API mco_result mco_init(mco_coro* co, mco_desc* desc); /* Initialize the coroutine. */ +MCO_API mco_result mco_uninit(mco_coro* co); /* Uninitialize the coroutine, may fail if it's not dead or suspended. */ +MCO_API mco_result mco_create(mco_coro** out_co, mco_desc* desc); /* Allocates and initializes a new coroutine. */ +MCO_API mco_result mco_destroy(mco_coro* co); /* Uninitialize and deallocate the coroutine, may fail if it's not dead or suspended. */ +MCO_API mco_result mco_resume(mco_coro* co); /* Starts or continues the execution of the coroutine. */ +MCO_API mco_result mco_yield(mco_coro* co); /* Suspends the execution of a coroutine. */ +MCO_API mco_state mco_status(mco_coro* co); /* Returns the status of the coroutine. */ +MCO_API void* mco_get_user_data(mco_coro* co); /* Get coroutine user data supplied on coroutine creation. */ + +/* Storage interface functions, used to pass values between yield and resume. */ +MCO_API mco_result mco_push(mco_coro* co, const void* src, size_t len); /* Push bytes to the coroutine storage. Use to send values between yield and resume. */ +MCO_API mco_result mco_pop(mco_coro* co, void* dest, size_t len); /* Pop bytes from the coroutine storage. Use to get values between yield and resume. */ +MCO_API mco_result mco_peek(mco_coro* co, void* dest, size_t len); /* Like `mco_pop` but it does not consumes the storage. */ +MCO_API size_t mco_get_bytes_stored(mco_coro* co); /* Get the available bytes that can be retrieved with a `mco_pop`. */ +MCO_API size_t mco_get_storage_size(mco_coro* co); /* Get the total storage size. */ + +/* Misc functions. */ +MCO_API mco_coro* mco_running(void); /* Returns the running coroutine for the current thread. */ +MCO_API const char* mco_result_description(mco_result res); /* Get the description of a result. */ + +#ifdef __cplusplus +} +#endif + +#endif /* MINICORO_H */ + +#ifdef MINICORO_IMPL + +#ifdef __cplusplus +extern "C" { +#endif + +/* ---------------------------------------------------------------------------------------------- */ + +/* Minimum stack size when creating a coroutine. */ +#ifndef MCO_MIN_STACK_SIZE +#define MCO_MIN_STACK_SIZE 32768 +#endif + +/* Default stack size when creating a coroutine. */ +#ifndef MCO_DEFAULT_STACK_SIZE +#define MCO_DEFAULT_STACK_SIZE 57344 /* Don't use multiples of 64K to avoid D-cache aliasing conflicts. */ +#endif + +/* Number used only to assist checking for stack overflows. */ +#define MCO_MAGIC_NUMBER 0x7E3CB1A9 + +/* Detect implementation based on OS, arch and compiler. */ +#if !defined(MCO_USE_UCONTEXT) && !defined(MCO_USE_FIBERS) && !defined(MCO_USE_ASM) + #if defined(_WIN32) + #if (defined(__GNUC__) && defined(__x86_64__)) || (defined(_MSC_VER) && defined(_M_X64)) + #define MCO_USE_ASM + #else + #define MCO_USE_FIBERS + #endif + #elif defined(__CYGWIN__) /* MSYS */ + #define MCO_USE_UCONTEXT + #elif defined(__EMSCRIPTEN__) + #define MCO_USE_FIBERS + #else + #if __GNUC__ >= 3 /* Assembly extension supported. */ + #if defined(__x86_64__) || \ + defined(__i386) || defined(__i386__) || \ + defined(__ARM_EABI__) || defined(__aarch64__) || \ + defined(__riscv) + #define MCO_USE_ASM + #else + #define MCO_USE_UCONTEXT + #endif + #else + #define MCO_USE_UCONTEXT + #endif + #endif +#endif + +#define _MCO_UNUSED(x) (void)(x) + +#if !defined(MCO_NO_DEBUG) && !defined(NDEBUG) && !defined(MCO_DEBUG) +#define MCO_DEBUG +#endif + +#ifndef MCO_LOG + #ifdef MCO_DEBUG + #include + #define MCO_LOG(s) puts(s) + #else + #define MCO_LOG(s) + #endif +#endif + +#ifndef MCO_ASSERT + #ifdef MCO_DEBUG + #include + #define MCO_ASSERT(c) assert(c) + #else + #define MCO_ASSERT(c) + #endif +#endif + +#ifndef MCO_THREAD_LOCAL + #ifdef MCO_NO_MULTITHREAD + #define MCO_THREAD_LOCAL + #else + #ifdef thread_local + #define MCO_THREAD_LOCAL thread_local + #elif __STDC_VERSION__ >= 201112 && !defined(__STDC_NO_THREADS__) + #define MCO_THREAD_LOCAL _Thread_local + #elif defined(_WIN32) && (defined(_MSC_VER) || defined(__ICL) || defined(__DMC__) || defined(__BORLANDC__)) + #define MCO_THREAD_LOCAL __declspec(thread) + #elif defined(__GNUC__) || defined(__SUNPRO_C) || defined(__xlC__) + #define MCO_THREAD_LOCAL __thread + #else /* No thread local support, `mco_running` will be thread unsafe. */ + #define MCO_THREAD_LOCAL + #define MCO_NO_MULTITHREAD + #endif + #endif +#endif + +#ifndef MCO_FORCE_INLINE + #ifdef _MSC_VER + #define MCO_FORCE_INLINE __forceinline + #elif defined(__GNUC__) + #if defined(__STRICT_ANSI__) + #define MCO_FORCE_INLINE __inline__ __attribute__((always_inline)) + #else + #define MCO_FORCE_INLINE inline __attribute__((always_inline)) + #endif + #elif defined(__BORLANDC__) || defined(__DMC__) || defined(__SC__) || defined(__WATCOMC__) || defined(__LCC__) || defined(__DECC) + #define MCO_FORCE_INLINE __inline + #else /* No inline support. */ + #define MCO_FORCE_INLINE + #endif +#endif + +#ifndef MCO_NO_DEFAULT_ALLOCATORS +#ifndef MCO_MALLOC + #include + #define MCO_MALLOC malloc + #define MCO_FREE free +#endif +static void* mco_malloc(size_t size, void* allocator_data) { + _MCO_UNUSED(allocator_data); + return MCO_MALLOC(size); +} +static void mco_free(void* ptr, void* allocator_data) { + _MCO_UNUSED(allocator_data); + MCO_FREE(ptr); +} +#endif /* MCO_NO_DEFAULT_ALLOCATORS */ + +#if defined(__has_feature) + #if __has_feature(address_sanitizer) + #define _MCO_USE_ASAN + #endif + #if __has_feature(thread_sanitizer) + #define _MCO_USE_TSAN + #endif +#endif +#if defined(__SANITIZE_ADDRESS__) + #define _MCO_USE_ASAN +#endif +#if defined(__SANITIZE_THREAD__) + #define _MCO_USE_TSAN +#endif +#ifdef _MCO_USE_ASAN +void __sanitizer_start_switch_fiber(void** fake_stack_save, const void *bottom, size_t size); +void __sanitizer_finish_switch_fiber(void* fake_stack_save, const void **bottom_old, size_t *size_old); +#endif +#ifdef _MCO_USE_TSAN +void* __tsan_get_current_fiber(void); +void* __tsan_create_fiber(unsigned flags); +void __tsan_destroy_fiber(void* fiber); +void __tsan_switch_to_fiber(void* fiber, unsigned flags); +#endif + +#include /* For memcpy and memset. */ + +/* Utility for aligning addresses. */ +static MCO_FORCE_INLINE size_t _mco_align_forward(size_t addr, size_t align) { + return (addr + (align-1)) & ~(align-1); +} + +/* Variable holding the current running coroutine per thread. */ +static MCO_THREAD_LOCAL mco_coro* mco_current_co = NULL; + +static MCO_FORCE_INLINE void _mco_prepare_jumpin(mco_coro* co) { + /* Set the old coroutine to normal state and update it. */ + mco_coro* prev_co = mco_running(); /* Must access through `mco_running`. */ + MCO_ASSERT(co->prev_co == NULL); + co->prev_co = prev_co; + if(prev_co) { + MCO_ASSERT(prev_co->state == MCO_RUNNING); + prev_co->state = MCO_NORMAL; + } + mco_current_co = co; +#ifdef _MCO_USE_ASAN + if(prev_co) { + void* bottom_old = NULL; + size_t size_old = 0; + __sanitizer_finish_switch_fiber(prev_co->asan_prev_stack, (const void**)&bottom_old, &size_old); + prev_co->asan_prev_stack = NULL; + } + __sanitizer_start_switch_fiber(&co->asan_prev_stack, co->stack_base, co->stack_size); +#endif +#ifdef _MCO_USE_TSAN + co->tsan_prev_fiber = __tsan_get_current_fiber(); + __tsan_switch_to_fiber(co->tsan_fiber, 0); +#endif +} + +static MCO_FORCE_INLINE void _mco_prepare_jumpout(mco_coro* co) { + /* Switch back to the previous running coroutine. */ + MCO_ASSERT(mco_running() == co); + mco_coro* prev_co = co->prev_co; + co->prev_co = NULL; + if(prev_co) { + MCO_ASSERT(prev_co->state == MCO_NORMAL); + prev_co->state = MCO_RUNNING; + } + mco_current_co = prev_co; +#ifdef _MCO_USE_ASAN + void* bottom_old = NULL; + size_t size_old = 0; + __sanitizer_finish_switch_fiber(co->asan_prev_stack, (const void**)&bottom_old, &size_old); + co->asan_prev_stack = NULL; + if(prev_co) { + __sanitizer_start_switch_fiber(&prev_co->asan_prev_stack, bottom_old, size_old); + } +#endif +#ifdef _MCO_USE_TSAN + void* tsan_prev_fiber = co->tsan_prev_fiber; + co->tsan_prev_fiber = NULL; + __tsan_switch_to_fiber(tsan_prev_fiber, 0); +#endif +} + +static void _mco_jumpin(mco_coro* co); +static void _mco_jumpout(mco_coro* co); + +static void _mco_main(mco_coro* co) { + co->func(co); /* Run the coroutine function. */ + co->state = MCO_DEAD; /* Coroutine finished successfully, set state to dead. */ + _mco_jumpout(co); /* Jump back to the old context .*/ +} + +/* ---------------------------------------------------------------------------------------------- */ + +#if defined(MCO_USE_UCONTEXT) || defined(MCO_USE_ASM) + +/* +Some of the following assembly code is taken from LuaCoco by Mike Pall. +See https://coco.luajit.org/index.html + +MIT license + +Copyright (C) 2004-2016 Mike Pall. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifdef MCO_USE_ASM + +#if defined(__x86_64__) || defined(_M_X64) + +#ifdef _WIN32 + +typedef struct _mco_ctxbuf { + void *rip, *rsp, *rbp, *rbx, *r12, *r13, *r14, *r15, *rdi, *rsi; + void* xmm[20]; /* xmm6, xmm7, xmm8, xmm9, xmm10, xmm11, xmm12, xmm13, xmm14, xmm15 */ + void* fiber_storage; + void* dealloc_stack; + void* stack_limit; + void* stack_base; +} _mco_ctxbuf; + +#if defined(__GNUC__) +#define _MCO_ASM_BLOB __attribute__((section(".text"))) +#elif defined(_MSC_VER) +#define _MCO_ASM_BLOB __declspec(allocate(".text")) +#pragma section(".text") +#endif + +_MCO_ASM_BLOB static unsigned char _mco_wrap_main_code[] = { + 0x4c, 0x89, 0xe9, /* mov %r13,%rcx */ + 0x41, 0xff, 0xe4, /* jmpq *%r12 */ + 0xc3, /* retq */ + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 /* nop */ +}; + +_MCO_ASM_BLOB static unsigned char _mco_switch_code[] = { + 0x48, 0x8d, 0x05, 0x52, 0x01, 0x00, 0x00, /* lea 0x152(%rip),%rax */ + 0x48, 0x89, 0x01, /* mov %rax,(%rcx) */ + 0x48, 0x89, 0x61, 0x08, /* mov %rsp,0x8(%rcx) */ + 0x48, 0x89, 0x69, 0x10, /* mov %rbp,0x10(%rcx) */ + 0x48, 0x89, 0x59, 0x18, /* mov %rbx,0x18(%rcx) */ + 0x4c, 0x89, 0x61, 0x20, /* mov %r12,0x20(%rcx) */ + 0x4c, 0x89, 0x69, 0x28, /* mov %r13,0x28(%rcx) */ + 0x4c, 0x89, 0x71, 0x30, /* mov %r14,0x30(%rcx) */ + 0x4c, 0x89, 0x79, 0x38, /* mov %r15,0x38(%rcx) */ + 0x48, 0x89, 0x79, 0x40, /* mov %rdi,0x40(%rcx) */ + 0x48, 0x89, 0x71, 0x48, /* mov %rsi,0x48(%rcx) */ + 0x66, 0x0f, 0xd6, 0x71, 0x50, /* movq %xmm6,0x50(%rcx) */ + 0x66, 0x0f, 0xd6, 0x79, 0x60, /* movq %xmm7,0x60(%rcx) */ + 0x66, 0x44, 0x0f, 0xd6, 0x41, 0x70, /* movq %xmm8,0x70(%rcx) */ + 0x66, 0x44, 0x0f, 0xd6, 0x89, 0x80, 0x00, 0x00, 0x00, /* movq %xmm9,0x80(%rcx) */ + 0x66, 0x44, 0x0f, 0xd6, 0x91, 0x90, 0x00, 0x00, 0x00, /* movq %xmm10,0x90(%rcx) */ + 0x66, 0x44, 0x0f, 0xd6, 0x99, 0xa0, 0x00, 0x00, 0x00, /* movq %xmm11,0xa0(%rcx) */ + 0x66, 0x44, 0x0f, 0xd6, 0xa1, 0xb0, 0x00, 0x00, 0x00, /* movq %xmm12,0xb0(%rcx) */ + 0x66, 0x44, 0x0f, 0xd6, 0xa9, 0xc0, 0x00, 0x00, 0x00, /* movq %xmm13,0xc0(%rcx) */ + 0x66, 0x44, 0x0f, 0xd6, 0xb1, 0xd0, 0x00, 0x00, 0x00, /* movq %xmm14,0xd0(%rcx) */ + 0x66, 0x44, 0x0f, 0xd6, 0xb9, 0xe0, 0x00, 0x00, 0x00, /* movq %xmm15,0xe0(%rcx) */ + 0x65, 0x4c, 0x8b, 0x14, 0x25, 0x30, 0x00, 0x00, 0x00, /* mov %gs:0x30,%r10 */ + 0x49, 0x8b, 0x42, 0x20, /* mov 0x20(%r10),%rax */ + 0x48, 0x89, 0x81, 0xf0, 0x00, 0x00, 0x00, /* mov %rax,0xf0(%rcx) */ + 0x49, 0x8b, 0x82, 0x78, 0x14, 0x00, 0x00, /* mov 0x1478(%r10),%rax */ + 0x48, 0x89, 0x81, 0xf8, 0x00, 0x00, 0x00, /* mov %rax,0xf8(%rcx) */ + 0x49, 0x8b, 0x42, 0x10, /* mov 0x10(%r10),%rax */ + 0x48, 0x89, 0x81, 0x00, 0x01, 0x00, 0x00, /* mov %rax,0x100(%rcx) */ + 0x49, 0x8b, 0x42, 0x08, /* mov 0x8(%r10),%rax */ + 0x48, 0x89, 0x81, 0x08, 0x01, 0x00, 0x00, /* mov %rax,0x108(%rcx) */ + 0x48, 0x8b, 0x82, 0x08, 0x01, 0x00, 0x00, /* mov 0x108(%rdx),%rax */ + 0x49, 0x89, 0x42, 0x08, /* mov %rax,0x8(%r10) */ + 0x48, 0x8b, 0x82, 0x00, 0x01, 0x00, 0x00, /* mov 0x100(%rdx),%rax */ + 0x49, 0x89, 0x42, 0x10, /* mov %rax,0x10(%r10) */ + 0x48, 0x8b, 0x82, 0xf8, 0x00, 0x00, 0x00, /* mov 0xf8(%rdx),%rax */ + 0x49, 0x89, 0x82, 0x78, 0x14, 0x00, 0x00, /* mov %rax,0x1478(%r10) */ + 0x48, 0x8b, 0x82, 0xf0, 0x00, 0x00, 0x00, /* mov 0xf0(%rdx),%rax */ + 0x49, 0x89, 0x42, 0x20, /* mov %rax,0x20(%r10) */ + 0xf3, 0x44, 0x0f, 0x7e, 0xba, 0xe0, 0x00, 0x00, 0x00, /* movq 0xe0(%rdx),%xmm15 */ + 0xf3, 0x44, 0x0f, 0x7e, 0xb2, 0xd0, 0x00, 0x00, 0x00, /* movq 0xd0(%rdx),%xmm14 */ + 0xf3, 0x44, 0x0f, 0x7e, 0xaa, 0xc0, 0x00, 0x00, 0x00, /* movq 0xc0(%rdx),%xmm13 */ + 0xf3, 0x44, 0x0f, 0x7e, 0xa2, 0xb0, 0x00, 0x00, 0x00, /* movq 0xb0(%rdx),%xmm12 */ + 0xf3, 0x44, 0x0f, 0x7e, 0x9a, 0xa0, 0x00, 0x00, 0x00, /* movq 0xa0(%rdx),%xmm11 */ + 0xf3, 0x44, 0x0f, 0x7e, 0x92, 0x90, 0x00, 0x00, 0x00, /* movq 0x90(%rdx),%xmm10 */ + 0xf3, 0x44, 0x0f, 0x7e, 0x8a, 0x80, 0x00, 0x00, 0x00, /* movq 0x80(%rdx),%xmm9 */ + 0xf3, 0x44, 0x0f, 0x7e, 0x42, 0x70, /* movq 0x70(%rdx),%xmm8 */ + 0xf3, 0x0f, 0x7e, 0x7a, 0x60, /* movq 0x60(%rdx),%xmm7 */ + 0xf3, 0x0f, 0x7e, 0x72, 0x50, /* movq 0x50(%rdx),%xmm6 */ + 0x48, 0x8b, 0x72, 0x48, /* mov 0x48(%rdx),%rsi */ + 0x48, 0x8b, 0x7a, 0x40, /* mov 0x40(%rdx),%rdi */ + 0x4c, 0x8b, 0x7a, 0x38, /* mov 0x38(%rdx),%r15 */ + 0x4c, 0x8b, 0x72, 0x30, /* mov 0x30(%rdx),%r14 */ + 0x4c, 0x8b, 0x6a, 0x28, /* mov 0x28(%rdx),%r13 */ + 0x4c, 0x8b, 0x62, 0x20, /* mov 0x20(%rdx),%r12 */ + 0x48, 0x8b, 0x5a, 0x18, /* mov 0x18(%rdx),%rbx */ + 0x48, 0x8b, 0x6a, 0x10, /* mov 0x10(%rdx),%rbp */ + 0x48, 0x8b, 0x62, 0x08, /* mov 0x8(%rdx),%rsp */ + 0xff, 0x22, /* jmpq *(%rdx) */ + 0xc3, /* retq */ + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, /* nop */ +}; + +void (*_mco_wrap_main)(void) = (void(*)(void))(void*)_mco_wrap_main_code; +void (*_mco_switch)(_mco_ctxbuf* from, _mco_ctxbuf* to) = (void(*)(_mco_ctxbuf* from, _mco_ctxbuf* to))(void*)_mco_switch_code; + +static mco_result _mco_makectx(mco_coro* co, _mco_ctxbuf* ctx, void* stack_base, size_t stack_size) { + stack_size = stack_size - 32; /* Reserve 32 bytes for the shadow space. */ + void** stack_high_ptr = (void**)((size_t)stack_base + stack_size - sizeof(size_t)); + stack_high_ptr[0] = (void*)(0xdeaddeaddeaddead); /* Dummy return address. */ + ctx->rip = (void*)(_mco_wrap_main); + ctx->rsp = (void*)(stack_high_ptr); + ctx->r12 = (void*)(_mco_main); + ctx->r13 = (void*)(co); + void* stack_top = (void*)((size_t)stack_base + stack_size); + ctx->stack_base = stack_top; + ctx->stack_limit = stack_base; + ctx->dealloc_stack = stack_base; + return MCO_SUCCESS; +} + +#else /* not _WIN32 */ + +typedef struct _mco_ctxbuf { + void *rip, *rsp, *rbp, *rbx, *r12, *r13, *r14, *r15; +} _mco_ctxbuf; + +void _mco_wrap_main(void); +int _mco_switch(_mco_ctxbuf* from, _mco_ctxbuf* to); + +__asm__( + ".text\n" +#ifdef __MACH__ /* Mac OS X assembler */ + ".globl __mco_wrap_main\n" + "__mco_wrap_main:\n" +#else /* Linux assembler */ + ".globl _mco_wrap_main\n" + ".type _mco_wrap_main @function\n" + ".hidden _mco_wrap_main\n" + "_mco_wrap_main:\n" +#endif + " movq %r13, %rdi\n" + " jmpq *%r12\n" +#ifndef __MACH__ + ".size _mco_wrap_main, .-_mco_wrap_main\n" +#endif +); + +__asm__( + ".text\n" +#ifdef __MACH__ /* Mac OS assembler */ + ".globl __mco_switch\n" + "__mco_switch:\n" +#else /* Linux assembler */ + ".globl _mco_switch\n" + ".type _mco_switch @function\n" + ".hidden _mco_switch\n" + "_mco_switch:\n" +#endif + " leaq 0x3d(%rip), %rax\n" + " movq %rax, (%rdi)\n" + " movq %rsp, 8(%rdi)\n" + " movq %rbp, 16(%rdi)\n" + " movq %rbx, 24(%rdi)\n" + " movq %r12, 32(%rdi)\n" + " movq %r13, 40(%rdi)\n" + " movq %r14, 48(%rdi)\n" + " movq %r15, 56(%rdi)\n" + " movq 56(%rsi), %r15\n" + " movq 48(%rsi), %r14\n" + " movq 40(%rsi), %r13\n" + " movq 32(%rsi), %r12\n" + " movq 24(%rsi), %rbx\n" + " movq 16(%rsi), %rbp\n" + " movq 8(%rsi), %rsp\n" + " jmpq *(%rsi)\n" + " ret\n" +#ifndef __MACH__ + ".size _mco_switch, .-_mco_switch\n" +#endif +); + +static mco_result _mco_makectx(mco_coro* co, _mco_ctxbuf* ctx, void* stack_base, size_t stack_size) { + stack_size = stack_size - 128; /* Reserve 128 bytes for the Red Zone space (System V AMD64 ABI). */ + void** stack_high_ptr = (void**)((size_t)stack_base + stack_size - sizeof(size_t)); + stack_high_ptr[0] = (void*)(0xdeaddeaddeaddead); /* Dummy return address. */ + ctx->rip = (void*)(_mco_wrap_main); + ctx->rsp = (void*)(stack_high_ptr); + ctx->r12 = (void*)(_mco_main); + ctx->r13 = (void*)(co); + return MCO_SUCCESS; +} + +#endif /* not _WIN32 */ + +#elif defined(__riscv) + +typedef struct _mco_ctxbuf { + void* s[12]; /* s0-s11 */ + void* ra; + void* pc; + void* sp; +#ifdef __riscv_flen +#if __riscv_flen == 64 + double fs[12]; /* fs0-fs11 */ +#elif __riscv_flen == 32 + float fs[12]; /* fs0-fs11 */ +#endif +#endif /* __riscv_flen */ +} _mco_ctxbuf; + +void _mco_wrap_main(void); +int _mco_switch(_mco_ctxbuf* from, _mco_ctxbuf* to); + +__asm__( + ".text\n" + ".globl _mco_wrap_main\n" + ".type _mco_wrap_main @function\n" + ".hidden _mco_wrap_main\n" + "_mco_wrap_main:\n" + " mv a0, s0\n" + " jr s1\n" + ".size _mco_wrap_main, .-_mco_wrap_main\n" +); + +__asm__( + ".text\n" + ".globl _mco_switch\n" + ".type _mco_switch @function\n" + ".hidden _mco_switch\n" + "_mco_switch:\n" + #if __riscv_xlen == 64 + " sd s0, 0x00(a0)\n" + " sd s1, 0x08(a0)\n" + " sd s2, 0x10(a0)\n" + " sd s3, 0x18(a0)\n" + " sd s4, 0x20(a0)\n" + " sd s5, 0x28(a0)\n" + " sd s6, 0x30(a0)\n" + " sd s7, 0x38(a0)\n" + " sd s8, 0x40(a0)\n" + " sd s9, 0x48(a0)\n" + " sd s10, 0x50(a0)\n" + " sd s11, 0x58(a0)\n" + " sd ra, 0x60(a0)\n" + " sd ra, 0x68(a0)\n" /* pc */ + " sd sp, 0x70(a0)\n" + #ifdef __riscv_flen + #if __riscv_flen == 64 + " fsd fs0, 0x78(a0)\n" + " fsd fs1, 0x80(a0)\n" + " fsd fs2, 0x88(a0)\n" + " fsd fs3, 0x90(a0)\n" + " fsd fs4, 0x98(a0)\n" + " fsd fs5, 0xa0(a0)\n" + " fsd fs6, 0xa8(a0)\n" + " fsd fs7, 0xb0(a0)\n" + " fsd fs8, 0xb8(a0)\n" + " fsd fs9, 0xc0(a0)\n" + " fsd fs10, 0xc8(a0)\n" + " fsd fs11, 0xd0(a0)\n" + " fld fs0, 0x78(a1)\n" + " fld fs1, 0x80(a1)\n" + " fld fs2, 0x88(a1)\n" + " fld fs3, 0x90(a1)\n" + " fld fs4, 0x98(a1)\n" + " fld fs5, 0xa0(a1)\n" + " fld fs6, 0xa8(a1)\n" + " fld fs7, 0xb0(a1)\n" + " fld fs8, 0xb8(a1)\n" + " fld fs9, 0xc0(a1)\n" + " fld fs10, 0xc8(a1)\n" + " fld fs11, 0xd0(a1)\n" + #else + #error "Unsupported RISC-V FLEN" + #endif + #endif /* __riscv_flen */ + " ld s0, 0x00(a1)\n" + " ld s1, 0x08(a1)\n" + " ld s2, 0x10(a1)\n" + " ld s3, 0x18(a1)\n" + " ld s4, 0x20(a1)\n" + " ld s5, 0x28(a1)\n" + " ld s6, 0x30(a1)\n" + " ld s7, 0x38(a1)\n" + " ld s8, 0x40(a1)\n" + " ld s9, 0x48(a1)\n" + " ld s10, 0x50(a1)\n" + " ld s11, 0x58(a1)\n" + " ld ra, 0x60(a1)\n" + " ld a2, 0x68(a1)\n" /* pc */ + " ld sp, 0x70(a1)\n" + " jr a2\n" + #elif __riscv_xlen == 32 + " sw s0, 0x00(a0)\n" + " sw s1, 0x04(a0)\n" + " sw s2, 0x08(a0)\n" + " sw s3, 0x0c(a0)\n" + " sw s4, 0x10(a0)\n" + " sw s5, 0x14(a0)\n" + " sw s6, 0x18(a0)\n" + " sw s7, 0x1c(a0)\n" + " sw s8, 0x20(a0)\n" + " sw s9, 0x24(a0)\n" + " sw s10, 0x28(a0)\n" + " sw s11, 0x2c(a0)\n" + " sw ra, 0x30(a0)\n" + " sw ra, 0x34(a0)\n" /* pc */ + " sw sp, 0x38(a0)\n" + #ifdef __riscv_flen + #if __riscv_flen == 64 + " fsd fs0, 0x3c(a0)\n" + " fsd fs1, 0x44(a0)\n" + " fsd fs2, 0x4c(a0)\n" + " fsd fs3, 0x54(a0)\n" + " fsd fs4, 0x5c(a0)\n" + " fsd fs5, 0x64(a0)\n" + " fsd fs6, 0x6c(a0)\n" + " fsd fs7, 0x74(a0)\n" + " fsd fs8, 0x7c(a0)\n" + " fsd fs9, 0x84(a0)\n" + " fsd fs10, 0x8c(a0)\n" + " fsd fs11, 0x94(a0)\n" + " fld fs0, 0x3c(a1)\n" + " fld fs1, 0x44(a1)\n" + " fld fs2, 0x4c(a1)\n" + " fld fs3, 0x54(a1)\n" + " fld fs4, 0x5c(a1)\n" + " fld fs5, 0x64(a1)\n" + " fld fs6, 0x6c(a1)\n" + " fld fs7, 0x74(a1)\n" + " fld fs8, 0x7c(a1)\n" + " fld fs9, 0x84(a1)\n" + " fld fs10, 0x8c(a1)\n" + " fld fs11, 0x94(a1)\n" + #elif __riscv_flen == 32 + " fsw fs0, 0x3c(a0)\n" + " fsw fs1, 0x40(a0)\n" + " fsw fs2, 0x44(a0)\n" + " fsw fs3, 0x48(a0)\n" + " fsw fs4, 0x4c(a0)\n" + " fsw fs5, 0x50(a0)\n" + " fsw fs6, 0x54(a0)\n" + " fsw fs7, 0x58(a0)\n" + " fsw fs8, 0x5c(a0)\n" + " fsw fs9, 0x60(a0)\n" + " fsw fs10, 0x64(a0)\n" + " fsw fs11, 0x68(a0)\n" + " flw fs0, 0x3c(a1)\n" + " flw fs1, 0x40(a1)\n" + " flw fs2, 0x44(a1)\n" + " flw fs3, 0x48(a1)\n" + " flw fs4, 0x4c(a1)\n" + " flw fs5, 0x50(a1)\n" + " flw fs6, 0x54(a1)\n" + " flw fs7, 0x58(a1)\n" + " flw fs8, 0x5c(a1)\n" + " flw fs9, 0x60(a1)\n" + " flw fs10, 0x64(a1)\n" + " flw fs11, 0x68(a1)\n" + #else + #error "Unsupported RISC-V FLEN" + #endif + #endif /* __riscv_flen */ + " lw s0, 0x00(a1)\n" + " lw s1, 0x04(a1)\n" + " lw s2, 0x08(a1)\n" + " lw s3, 0x0c(a1)\n" + " lw s4, 0x10(a1)\n" + " lw s5, 0x14(a1)\n" + " lw s6, 0x18(a1)\n" + " lw s7, 0x1c(a1)\n" + " lw s8, 0x20(a1)\n" + " lw s9, 0x24(a1)\n" + " lw s10, 0x28(a1)\n" + " lw s11, 0x2c(a1)\n" + " lw ra, 0x30(a1)\n" + " lw a2, 0x34(a1)\n" /* pc */ + " lw sp, 0x38(a1)\n" + " jr a2\n" + #else + #error "Unsupported RISC-V XLEN" + #endif /* __riscv_xlen */ + ".size _mco_switch, .-_mco_switch\n" +); + +static mco_result _mco_makectx(mco_coro* co, _mco_ctxbuf* ctx, void* stack_base, size_t stack_size) { + ctx->s[0] = (void*)(co); + ctx->s[1] = (void*)(_mco_main); + ctx->pc = (void*)(_mco_wrap_main); +#if __riscv_xlen == 64 + ctx->ra = (void*)(0xdeaddeaddeaddead); +#elif __riscv_xlen == 32 + ctx->ra = (void*)(0xdeaddead); +#endif + ctx->sp = (void*)((size_t)stack_base + stack_size); + return MCO_SUCCESS; +} + +#elif defined(__i386) || defined(__i386__) + +typedef struct _mco_ctxbuf { + void *eip, *esp, *ebp, *ebx, *esi, *edi; +} _mco_ctxbuf; + +void _mco_switch(_mco_ctxbuf* from, _mco_ctxbuf* to); + +__asm__( +#ifdef __DJGPP__ /* DOS compiler */ + "__mco_switch:\n" +#else + ".text\n" + ".globl _mco_switch\n" + ".type _mco_switch @function\n" + ".hidden _mco_switch\n" + "_mco_switch:\n" +#endif + " call 1f\n" + " 1:\n" + " popl %ecx\n" + " addl $(2f-1b), %ecx\n" + " movl 4(%esp), %eax\n" + " movl 8(%esp), %edx\n" + " movl %ecx, (%eax)\n" + " movl %esp, 4(%eax)\n" + " movl %ebp, 8(%eax)\n" + " movl %ebx, 12(%eax)\n" + " movl %esi, 16(%eax)\n" + " movl %edi, 20(%eax)\n" + " movl 20(%edx), %edi\n" + " movl 16(%edx), %esi\n" + " movl 12(%edx), %ebx\n" + " movl 8(%edx), %ebp\n" + " movl 4(%edx), %esp\n" + " jmp *(%edx)\n" + " 2:\n" + " ret\n" +#ifndef __DJGPP__ + ".size _mco_switch, .-_mco_switch\n" +#endif +); + +static mco_result _mco_makectx(mco_coro* co, _mco_ctxbuf* ctx, void* stack_base, size_t stack_size) { + void** stack_high_ptr = (void**)((size_t)stack_base + stack_size - 16 - 1*sizeof(size_t)); + stack_high_ptr[0] = (void*)(0xdeaddead); /* Dummy return address. */ + stack_high_ptr[1] = (void*)(co); + ctx->eip = (void*)(_mco_main); + ctx->esp = (void*)(stack_high_ptr); + return MCO_SUCCESS; +} + +#elif defined(__ARM_EABI__) + +typedef struct _mco_ctxbuf { +#ifndef __SOFTFP__ + void* f[16]; +#endif + void *d[4]; /* d8-d15 */ + void *r[4]; /* r4-r11 */ + void *lr; + void *sp; +} _mco_ctxbuf; + +void _mco_wrap_main(void); +int _mco_switch(_mco_ctxbuf* from, _mco_ctxbuf* to); + +__asm__( + ".text\n" +#ifdef __APPLE__ + ".globl __mco_switch\n" + "__mco_switch:\n" +#else + ".globl _mco_switch\n" + ".type _mco_switch #function\n" + ".hidden _mco_switch\n" + "_mco_switch:\n" +#endif +#ifndef __SOFTFP__ + " vstmia r0!, {d8-d15}\n" +#endif + " stmia r0, {r4-r11, lr}\n" + " str sp, [r0, #9*4]\n" +#ifndef __SOFTFP__ + " vldmia r1!, {d8-d15}\n" +#endif + " ldr sp, [r1, #9*4]\n" + " ldmia r1, {r4-r11, pc}\n" +#ifndef __APPLE__ + ".size _mco_switch, .-_mco_switch\n" +#endif +); + +__asm__( + ".text\n" +#ifdef __APPLE__ + ".globl __mco_wrap_main\n" + "__mco_wrap_main:\n" +#else + ".globl _mco_wrap_main\n" + ".type _mco_wrap_main #function\n" + ".hidden _mco_wrap_main\n" + "_mco_wrap_main:\n" +#endif + " mov r0, r4\n" + " mov ip, r5\n" + " mov lr, r6\n" + " bx ip\n" +#ifndef __APPLE__ + ".size _mco_wrap_main, .-_mco_wrap_main\n" +#endif +); + +static mco_result _mco_makectx(mco_coro* co, _mco_ctxbuf* ctx, void* stack_base, size_t stack_size) { + ctx->d[0] = (void*)(co); + ctx->d[1] = (void*)(_mco_main); + ctx->d[2] = (void*)(0xdeaddead); /* Dummy return address. */ + ctx->lr = (void*)(_mco_wrap_main); + ctx->sp = (void*)((size_t)stack_base + stack_size); + return MCO_SUCCESS; +} + +#elif defined(__aarch64__) + +typedef struct _mco_ctxbuf { + void *x[12]; /* x19-x30 */ + void *sp; + void *lr; + void *d[8]; /* d8-d15 */ +} _mco_ctxbuf; + +void _mco_wrap_main(void); +int _mco_switch(_mco_ctxbuf* from, _mco_ctxbuf* to); + +__asm__( + ".text\n" +#ifdef __APPLE__ + ".globl __mco_switch\n" + "__mco_switch:\n" +#else + ".globl _mco_switch\n" + ".type _mco_switch #function\n" + ".hidden _mco_switch\n" + "_mco_switch:\n" +#endif + + " mov x10, sp\n" + " mov x11, x30\n" + " stp x19, x20, [x0, #(0*16)]\n" + " stp x21, x22, [x0, #(1*16)]\n" + " stp d8, d9, [x0, #(7*16)]\n" + " stp x23, x24, [x0, #(2*16)]\n" + " stp d10, d11, [x0, #(8*16)]\n" + " stp x25, x26, [x0, #(3*16)]\n" + " stp d12, d13, [x0, #(9*16)]\n" + " stp x27, x28, [x0, #(4*16)]\n" + " stp d14, d15, [x0, #(10*16)]\n" + " stp x29, x30, [x0, #(5*16)]\n" + " stp x10, x11, [x0, #(6*16)]\n" + " ldp x19, x20, [x1, #(0*16)]\n" + " ldp x21, x22, [x1, #(1*16)]\n" + " ldp d8, d9, [x1, #(7*16)]\n" + " ldp x23, x24, [x1, #(2*16)]\n" + " ldp d10, d11, [x1, #(8*16)]\n" + " ldp x25, x26, [x1, #(3*16)]\n" + " ldp d12, d13, [x1, #(9*16)]\n" + " ldp x27, x28, [x1, #(4*16)]\n" + " ldp d14, d15, [x1, #(10*16)]\n" + " ldp x29, x30, [x1, #(5*16)]\n" + " ldp x10, x11, [x1, #(6*16)]\n" + " mov sp, x10\n" + " br x11\n" +#ifndef __APPLE__ + ".size _mco_switch, .-_mco_switch\n" +#endif +); + +__asm__( + ".text\n" +#ifdef __APPLE__ + ".globl __mco_wrap_main\n" + "__mco_wrap_main:\n" +#else + ".globl _mco_wrap_main\n" + ".type _mco_wrap_main #function\n" + ".hidden _mco_wrap_main\n" + "_mco_wrap_main:\n" +#endif + " mov x0, x19\n" + " mov x30, x21\n" + " br x20\n" +#ifndef __APPLE__ + ".size _mco_wrap_main, .-_mco_wrap_main\n" +#endif +); + +static mco_result _mco_makectx(mco_coro* co, _mco_ctxbuf* ctx, void* stack_base, size_t stack_size) { + ctx->x[0] = (void*)(co); + ctx->x[1] = (void*)(_mco_main); + ctx->x[2] = (void*)(0xdeaddeaddeaddead); /* Dummy return address. */ + ctx->sp = (void*)((size_t)stack_base + stack_size); + ctx->lr = (void*)(_mco_wrap_main); + return MCO_SUCCESS; +} + +#else + +#error "Unsupported architecture for assembly method." + +#endif /* ARCH */ + +#elif defined(MCO_USE_UCONTEXT) + +#include + +typedef ucontext_t _mco_ctxbuf; + +#if defined(_LP64) || defined(__LP64__) +static void _mco_wrap_main(unsigned int lo, unsigned int hi) { + mco_coro* co = (mco_coro*)(((size_t)lo) | (((size_t)hi) << 32)); /* Extract coroutine pointer. */ + _mco_main(co); +} +#else +static void _mco_wrap_main(unsigned int lo) { + mco_coro* co = (mco_coro*)((size_t)lo); /* Extract coroutine pointer. */ + _mco_main(co); +} +#endif + +static MCO_FORCE_INLINE void _mco_switch(_mco_ctxbuf* from, _mco_ctxbuf* to) { + int res = swapcontext(from, to); + _MCO_UNUSED(res); + MCO_ASSERT(res == 0); +} + +static mco_result _mco_makectx(mco_coro* co, _mco_ctxbuf* ctx, void* stack_base, size_t stack_size) { + /* Initialize ucontext. */ + if(getcontext(ctx) != 0) { + MCO_LOG("failed to get ucontext"); + return MCO_MAKE_CONTEXT_ERROR; + } + ctx->uc_link = NULL; /* We never exit from _mco_wrap_main. */ + ctx->uc_stack.ss_sp = stack_base; + ctx->uc_stack.ss_size = stack_size; + unsigned int lo = (unsigned int)((size_t)co); +#if defined(_LP64) || defined(__LP64__) + unsigned int hi = (unsigned int)(((size_t)co)>>32); + makecontext(ctx, (void (*)(void))_mco_wrap_main, 2, lo, hi); +#else + makecontext(ctx, (void (*)(void))_mco_wrap_main, 1, lo); +#endif + return MCO_SUCCESS; +} + +#endif /* defined(MCO_USE_UCONTEXT) */ + +#ifdef MCO_USE_VALGRIND +#include +#endif + +typedef struct _mco_context { +#ifdef MCO_USE_VALGRIND + unsigned int valgrind_stack_id; +#endif + _mco_ctxbuf ctx; + _mco_ctxbuf back_ctx; +} _mco_context; + +static void _mco_jumpin(mco_coro* co) { + _mco_context* context = (_mco_context*)co->context; + _mco_prepare_jumpin(co); + _mco_switch(&context->back_ctx, &context->ctx); /* Do the context switch. */ +} + +static void _mco_jumpout(mco_coro* co) { + _mco_context* context = (_mco_context*)co->context; + _mco_prepare_jumpout(co); + _mco_switch(&context->ctx, &context->back_ctx); /* Do the context switch. */ +} + +static mco_result _mco_create_context(mco_coro* co, mco_desc* desc) { + /* Determine the context and stack address. */ + size_t co_addr = (size_t)co; + size_t context_addr = _mco_align_forward(co_addr + sizeof(mco_coro), 16); + size_t storage_addr = _mco_align_forward(context_addr + sizeof(_mco_context), 16); + size_t stack_addr = _mco_align_forward(storage_addr + desc->storage_size, 16); + /* Initialize context. */ + _mco_context* context = (_mco_context*)context_addr; + memset(context, 0, sizeof(_mco_context)); + /* Initialize storage. */ + unsigned char* storage = (unsigned char*)storage_addr; + memset(storage, 0, desc->storage_size); + /* Initialize stack. */ + void *stack_base = (void*)stack_addr; + size_t stack_size = desc->stack_size; +#ifdef MCO_ZERO_MEMORY + memset(stack_base, 0, stack_size); +#endif + /* Make the context. */ + mco_result res = _mco_makectx(co, &context->ctx, stack_base, stack_size); + if(res != MCO_SUCCESS) { + return res; + } +#ifdef MCO_USE_VALGRIND + context->valgrind_stack_id = VALGRIND_STACK_REGISTER(stack_addr, stack_addr + stack_size); +#endif + co->context = context; + co->stack_base = stack_base; + co->stack_size = stack_size; + co->storage = storage; + co->storage_size = desc->storage_size; + return MCO_SUCCESS; +} + +static void _mco_destroy_context(mco_coro* co) { +#ifdef MCO_USE_VALGRIND + _mco_context* context = (_mco_context*)co->context; + if(context && context->valgrind_stack_id != 0) { + VALGRIND_STACK_DEREGISTER(context->valgrind_stack_id); + context->valgrind_stack_id = 0; + } +#else + _MCO_UNUSED(co); +#endif +} + +static MCO_FORCE_INLINE void _mco_init_desc_sizes(mco_desc* desc, size_t stack_size) { + desc->coro_size = _mco_align_forward(sizeof(mco_coro), 16) + + _mco_align_forward(sizeof(_mco_context), 16) + + _mco_align_forward(desc->storage_size, 16) + + stack_size + 16; + desc->stack_size = stack_size; /* This is just a hint, it won't be the real one. */ +} + +#endif /* defined(MCO_USE_UCONTEXT) || defined(MCO_USE_ASM) */ + +/* ---------------------------------------------------------------------------------------------- */ + +#ifdef MCO_USE_FIBERS + +#ifdef _WIN32 + +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0400 +#endif +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include + +typedef struct _mco_context { + void* fib; + void* back_fib; +} _mco_context; + +static void _mco_jumpin(mco_coro* co) { + void *cur_fib = GetCurrentFiber(); + if(!cur_fib || cur_fib == (void*)0x1e00) { /* See http://blogs.msdn.com/oldnewthing/archive/2004/12/31/344799.aspx */ + cur_fib = ConvertThreadToFiber(NULL); + } + MCO_ASSERT(cur_fib != NULL); + _mco_context* context = (_mco_context*)co->context; + context->back_fib = cur_fib; + _mco_prepare_jumpin(co); + SwitchToFiber(context->fib); +} + +static void CALLBACK _mco_wrap_main(void* co) { + _mco_main((mco_coro*)co); +} + +static void _mco_jumpout(mco_coro* co) { + _mco_context* context = (_mco_context*)co->context; + void* back_fib = context->back_fib; + MCO_ASSERT(back_fib != NULL); + context->back_fib = NULL; + _mco_prepare_jumpout(co); + SwitchToFiber(back_fib); +} + +/* Reverse engineered Fiber struct, used to get stack base. */ +typedef struct _mco_fiber { + LPVOID param; /* fiber param */ + void* except; /* saved exception handlers list */ + void* stack_base; /* top of fiber stack */ + void* stack_limit; /* fiber stack low-water mark */ + void* stack_allocation; /* base of the fiber stack allocation */ + CONTEXT context; /* fiber context */ + DWORD flags; /* fiber flags */ + LPFIBER_START_ROUTINE start; /* start routine */ + void **fls_slots; /* fiber storage slots */ +} _mco_fiber; + +static mco_result _mco_create_context(mco_coro* co, mco_desc* desc) { + /* Determine the context address. */ + size_t co_addr = (size_t)co; + size_t context_addr = _mco_align_forward(co_addr + sizeof(mco_coro), 16); + size_t storage_addr = _mco_align_forward(context_addr + sizeof(_mco_context), 16); + /* Initialize context. */ + _mco_context* context = (_mco_context*)context_addr; + memset(context, 0, sizeof(_mco_context)); + /* Initialize storage. */ + unsigned char* storage = (unsigned char*)storage_addr; + memset(storage, 0, desc->storage_size); + /* Create the fiber. */ + _mco_fiber* fib = (_mco_fiber*)CreateFiberEx(desc->stack_size, desc->stack_size, FIBER_FLAG_FLOAT_SWITCH, _mco_wrap_main, co); + if(!fib) { + MCO_LOG("failed to create fiber"); + return MCO_MAKE_CONTEXT_ERROR; + } + context->fib = fib; + co->context = context; + co->stack_base = (void*)((size_t)fib->stack_base - desc->stack_size); + co->stack_size = desc->stack_size; + co->storage = storage; + co->storage_size = desc->storage_size; + return MCO_SUCCESS; +} + +static void _mco_destroy_context(mco_coro* co) { + _mco_context* context = (_mco_context*)co->context; + if(context && context->fib) { + DeleteFiber(context->fib); + context->fib = NULL; + } +} + +static MCO_FORCE_INLINE void _mco_init_desc_sizes(mco_desc* desc, size_t stack_size) { + desc->coro_size = _mco_align_forward(sizeof(mco_coro), 16) + + _mco_align_forward(sizeof(_mco_context), 16) + + _mco_align_forward(desc->storage_size, 16) + + 16; + desc->stack_size = stack_size; +} + +#elif defined(__EMSCRIPTEN__) + +#include + +#ifndef MCO_ASYNCFY_STACK_SIZE +#define MCO_ASYNCFY_STACK_SIZE 16384 +#endif + +typedef struct _mco_context { + emscripten_fiber_t fib; + emscripten_fiber_t* back_fib; +} _mco_context; + +static emscripten_fiber_t* running_fib = NULL; +static unsigned char main_asyncify_stack[MCO_ASYNCFY_STACK_SIZE]; +static emscripten_fiber_t main_fib; + +static void _mco_wrap_main(void* co) { + _mco_main((mco_coro*)co); +} + +static void _mco_jumpin(mco_coro* co) { + _mco_context* context = (_mco_context*)co->context; + emscripten_fiber_t* back_fib = running_fib; + if(!back_fib) { + back_fib = &main_fib; + emscripten_fiber_init_from_current_context(back_fib, main_asyncify_stack, MCO_ASYNCFY_STACK_SIZE); + } + running_fib = &context->fib; + context->back_fib = back_fib; + _mco_prepare_jumpin(co); + emscripten_fiber_swap(back_fib, &context->fib); /* Do the context switch. */ +} + +static void _mco_jumpout(mco_coro* co) { + _mco_context* context = (_mco_context*)co->context; + running_fib = context->back_fib; + _mco_prepare_jumpout(co); + emscripten_fiber_swap(&context->fib, context->back_fib); /* Do the context switch. */ +} + +static mco_result _mco_create_context(mco_coro* co, mco_desc* desc) { + if(emscripten_has_asyncify() != 1) { + MCO_LOG("failed to create fiber because ASYNCIFY is not enabled"); + return MCO_MAKE_CONTEXT_ERROR; + } + /* Determine the context address. */ + size_t co_addr = (size_t)co; + size_t context_addr = _mco_align_forward(co_addr + sizeof(mco_coro), 16); + size_t storage_addr = _mco_align_forward(context_addr + sizeof(_mco_context), 16); + size_t stack_addr = _mco_align_forward(storage_addr + desc->storage_size, 16); + size_t asyncify_stack_addr = _mco_align_forward(stack_addr + desc->stack_size, 16); + /* Initialize context. */ + _mco_context* context = (_mco_context*)context_addr; + memset(context, 0, sizeof(_mco_context)); + /* Initialize storage. */ + unsigned char* storage = (unsigned char*)storage_addr; + memset(storage, 0, desc->storage_size); + /* Initialize stack. */ + void *stack_base = (void*)stack_addr; + size_t stack_size = asyncify_stack_addr - stack_addr; + void *asyncify_stack_base = (void*)asyncify_stack_addr; + size_t asyncify_stack_size = co_addr + desc->coro_size - asyncify_stack_addr; +#ifdef MCO_ZERO_MEMORY + memset(stack_base, 0, stack_size); + memset(asyncify_stack_base, 0, asyncify_stack_size); +#endif + /* Create the fiber. */ + emscripten_fiber_init(&context->fib, _mco_wrap_main, co, stack_base, stack_size, asyncify_stack_base, asyncify_stack_size); + co->context = context; + co->stack_base = stack_base; + co->stack_size = stack_size; + co->storage = storage; + co->storage_size = desc->storage_size; + return MCO_SUCCESS; +} + +static void _mco_destroy_context(mco_coro* co) { + /* Nothing to do. */ + _MCO_UNUSED(co); +} + +static MCO_FORCE_INLINE void _mco_init_desc_sizes(mco_desc* desc, size_t stack_size) { + desc->coro_size = _mco_align_forward(sizeof(mco_coro), 16) + + _mco_align_forward(sizeof(_mco_context), 16) + + _mco_align_forward(desc->storage_size, 16) + + _mco_align_forward(stack_size, 16) + + _mco_align_forward(MCO_ASYNCFY_STACK_SIZE, 16) + + 16; + desc->stack_size = stack_size; /* This is just a hint, it won't be the real one. */ +} + +#else + +#error "Unsupported architecture for fibers method." + +#endif + +#endif /* MCO_USE_FIBERS */ + +/* ---------------------------------------------------------------------------------------------- */ + +mco_desc mco_desc_init(void (*func)(mco_coro* co), size_t stack_size) { + if(stack_size != 0) { + /* Stack size should be at least `MCO_MIN_STACK_SIZE`. */ + if(stack_size < MCO_MIN_STACK_SIZE) { + stack_size = MCO_MIN_STACK_SIZE; + } + } else { + stack_size = MCO_DEFAULT_STACK_SIZE; + } + stack_size = _mco_align_forward(stack_size, 16); /* Stack size should be aligned to 16 bytes. */ + mco_desc desc; + memset(&desc, 0, sizeof(mco_desc)); +#ifndef MCO_NO_DEFAULT_ALLOCATORS + /* Set default allocators. */ + desc.malloc_cb = mco_malloc; + desc.free_cb = mco_free; +#endif + desc.func = func; + desc.storage_size = MCO_DEFAULT_STORAGE_SIZE; + _mco_init_desc_sizes(&desc, stack_size); + return desc; +} + +static mco_result _mco_validate_desc(mco_desc* desc) { + if(!desc) { + MCO_LOG("coroutine description is NULL"); + return MCO_INVALID_ARGUMENTS; + } + if(!desc->func) { + MCO_LOG("coroutine function in invalid"); + return MCO_INVALID_ARGUMENTS; + } + if(desc->stack_size < MCO_MIN_STACK_SIZE) { + MCO_LOG("coroutine stack size is too small"); + return MCO_INVALID_ARGUMENTS; + } + if(desc->coro_size < sizeof(mco_coro)) { + MCO_LOG("coroutine size is invalid"); + return MCO_INVALID_ARGUMENTS; + } + return MCO_SUCCESS; +} + +mco_result mco_init(mco_coro* co, mco_desc* desc) { + if(!co) { + MCO_LOG("attempt to initialize an invalid coroutine"); + return MCO_INVALID_COROUTINE; + } + memset(co, 0, sizeof(mco_coro)); + /* Validate coroutine description. */ + mco_result res = _mco_validate_desc(desc); + if(res != MCO_SUCCESS) + return res; + /* Create the coroutine. */ + res = _mco_create_context(co, desc); + if(res != MCO_SUCCESS) + return res; + co->state = MCO_SUSPENDED; /* We initialize in suspended state. */ + co->free_cb = desc->free_cb; + co->allocator_data = desc->allocator_data; + co->func = desc->func; + co->user_data = desc->user_data; +#ifdef _MCO_USE_TSAN + co->tsan_fiber = __tsan_create_fiber(0); +#endif + co->magic_number = MCO_MAGIC_NUMBER; + return MCO_SUCCESS; +} + +mco_result mco_uninit(mco_coro* co) { + if(!co) { + MCO_LOG("attempt to uninitialize an invalid coroutine"); + return MCO_INVALID_COROUTINE; + } + /* Cannot uninitialize while running. */ + if(!(co->state == MCO_SUSPENDED || co->state == MCO_DEAD)) { + MCO_LOG("attempt to uninitialize a coroutine that is not dead or suspended"); + return MCO_INVALID_OPERATION; + } + /* The coroutine is now dead and cannot be used anymore. */ + co->state = MCO_DEAD; +#ifdef _MCO_USE_TSAN + if(co->tsan_fiber != NULL) { + __tsan_destroy_fiber(co->tsan_fiber); + co->tsan_fiber = NULL; + } +#endif + _mco_destroy_context(co); + return MCO_SUCCESS; +} + +mco_result mco_create(mco_coro** out_co, mco_desc* desc) { + /* Validate input. */ + if(!out_co) { + MCO_LOG("coroutine output pointer is NULL"); + return MCO_INVALID_POINTER; + } + if(!desc || !desc->malloc_cb || !desc->free_cb) { + *out_co = NULL; + MCO_LOG("coroutine allocator description is not set"); + return MCO_INVALID_ARGUMENTS; + } + /* Allocate the coroutine. */ + mco_coro* co = (mco_coro*)desc->malloc_cb(desc->coro_size, desc->allocator_data); + if(!co) { + MCO_LOG("coroutine allocation failed"); + *out_co = NULL; + return MCO_OUT_OF_MEMORY; + } + /* Initialize the coroutine. */ + mco_result res = mco_init(co, desc); + if(res != MCO_SUCCESS) { + desc->free_cb(co, desc->allocator_data); + *out_co = NULL; + return res; + } + *out_co = co; + return MCO_SUCCESS; +} + +mco_result mco_destroy(mco_coro* co) { + if(!co) { + MCO_LOG("attempt to destroy an invalid coroutine"); + return MCO_INVALID_COROUTINE; + } + /* Uninitialize the coroutine first. */ + mco_result res = mco_uninit(co); + if(res != MCO_SUCCESS) + return res; + /* Free the coroutine. */ + if(!co->free_cb) { + MCO_LOG("attempt destroy a coroutine that has no free callback"); + return MCO_INVALID_POINTER; + } + co->free_cb(co, co->allocator_data); + return MCO_SUCCESS; +} + +mco_result mco_resume(mco_coro* co) { + if(!co) { + MCO_LOG("attempt to resume an invalid coroutine"); + return MCO_INVALID_COROUTINE; + } + if(co->state != MCO_SUSPENDED) { /* Can only resume coroutines that are suspended. */ + MCO_LOG("attempt to resume a coroutine that is not suspended"); + return MCO_NOT_SUSPENDED; + } + co->state = MCO_RUNNING; /* The coroutine is now running. */ + _mco_jumpin(co); + return MCO_SUCCESS; +} + +mco_result mco_yield(mco_coro* co) { + if(!co) { + MCO_LOG("attempt to yield an invalid coroutine"); + return MCO_INVALID_COROUTINE; + } + /* This check happens when the stack overflow already happened, but better later than never. */ + volatile size_t dummy; + size_t stack_addr = (size_t)&dummy; + size_t stack_min = (size_t)co->stack_base; + size_t stack_max = stack_min + co->stack_size; + if(co->magic_number != MCO_MAGIC_NUMBER || stack_addr < stack_min || stack_addr > stack_max) { /* Stack overflow. */ + MCO_LOG("coroutine stack overflow, try increasing the stack size"); + return MCO_STACK_OVERFLOW; + } + if(co->state != MCO_RUNNING) { /* Can only yield coroutines that are running. */ + MCO_LOG("attempt to yield a coroutine that is not running"); + return MCO_NOT_RUNNING; + } + co->state = MCO_SUSPENDED; /* The coroutine is now suspended. */ + _mco_jumpout(co); + return MCO_SUCCESS; +} + +mco_state mco_status(mco_coro* co) { + if(co != NULL) { + return co->state; + } + return MCO_DEAD; +} + +void* mco_get_user_data(mco_coro* co) { + if(co != NULL) { + return co->user_data; + } + return NULL; +} + +mco_result mco_push(mco_coro* co, const void* src, size_t len) { + if(!co) { + MCO_LOG("attempt to use an invalid coroutine"); + return MCO_INVALID_COROUTINE; + } else if(len > 0) { + size_t bytes_stored = co->bytes_stored + len; + if(bytes_stored > co->storage_size) { + MCO_LOG("attempt to push bytes too many bytes into coroutine storage"); + return MCO_NOT_ENOUGH_SPACE; + } + if(!src) { + MCO_LOG("attempt push a null pointer into coroutine storage"); + return MCO_INVALID_POINTER; + } + memcpy(&co->storage[co->bytes_stored], src, len); + co->bytes_stored = bytes_stored; + } + return MCO_SUCCESS; +} + +mco_result mco_pop(mco_coro* co, void* dest, size_t len) { + if(!co) { + MCO_LOG("attempt to use an invalid coroutine"); + return MCO_INVALID_COROUTINE; + } else if(len > 0) { + if(len > co->bytes_stored) { + MCO_LOG("attempt to pop too many bytes from coroutine storage"); + return MCO_NOT_ENOUGH_SPACE; + } + size_t bytes_stored = co->bytes_stored - len; + if(dest) { + memcpy(dest, &co->storage[bytes_stored], len); + } + co->bytes_stored = bytes_stored; +#ifdef MCO_ZERO_MEMORY + /* Clear garbage in the discarded storage. */ + memset(&co->storage[bytes_stored], 0, len); +#endif + } + return MCO_SUCCESS; +} + +mco_result mco_peek(mco_coro* co, void* dest, size_t len) { + if(!co) { + MCO_LOG("attempt to use an invalid coroutine"); + return MCO_INVALID_COROUTINE; + } else if(len > 0) { + if(len > co->bytes_stored) { + MCO_LOG("attempt to peek too many bytes from coroutine storage"); + return MCO_NOT_ENOUGH_SPACE; + } + if(!dest) { + MCO_LOG("attempt peek into a null pointer"); + return MCO_INVALID_POINTER; + } + memcpy(dest, &co->storage[co->bytes_stored - len], len); + } + return MCO_SUCCESS; +} + +size_t mco_get_bytes_stored(mco_coro* co) { + if(co == NULL) { + return 0; + } + return co->bytes_stored; +} + +size_t mco_get_storage_size(mco_coro* co) { + if(co == NULL) { + return 0; + } + return co->storage_size; +} + +#ifdef MCO_NO_MULTITHREAD +mco_coro* mco_running(void) { + return mco_current_co; +} +#else +static mco_coro* _mco_running(void) { + return mco_current_co; +} +mco_coro* mco_running(void) { + /* + Compilers aggressively optimize the use of TLS by caching loads. + Since fiber code can migrate between threads it’s possible for the load to be stale. + To prevent this from happening we avoid inline functions. + */ + mco_coro* (*volatile func)(void) = _mco_running; + return func(); +} +#endif + +const char* mco_result_description(mco_result res) { + switch(res) { + case MCO_SUCCESS: + return "No error"; + case MCO_GENERIC_ERROR: + return "Generic error"; + case MCO_INVALID_POINTER: + return "Invalid pointer"; + case MCO_INVALID_COROUTINE: + return "Invalid coroutine"; + case MCO_NOT_SUSPENDED: + return "Coroutine not suspended"; + case MCO_NOT_RUNNING: + return "Coroutine not running"; + case MCO_MAKE_CONTEXT_ERROR: + return "Make context error"; + case MCO_SWITCH_CONTEXT_ERROR: + return "Switch context error"; + case MCO_NOT_ENOUGH_SPACE: + return "Not enough space"; + case MCO_OUT_OF_MEMORY: + return "Out of memory"; + case MCO_INVALID_ARGUMENTS: + return "Invalid arguments"; + case MCO_INVALID_OPERATION: + return "Invalid operation"; + case MCO_STACK_OVERFLOW: + return "Stack overflow"; + } + return "Unknown error"; +} + +#ifdef __cplusplus +} +#endif + +#endif /* MINICORO_IMPL */ + +/* +This software is available as a choice of the following licenses. Choose +whichever you prefer. + +=============================================================================== +ALTERNATIVE 1 - Public Domain (www.unlicense.org) +=============================================================================== +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. + +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to + +=============================================================================== +ALTERNATIVE 2 - MIT No Attribution +=============================================================================== +Copyright (c) 2021 Eduardo Bart (https://github.com/edubart/minicoro) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/