├── .editorconfig ├── .gitattributes ├── .gitignore ├── README.md ├── examples └── simple.v ├── icon.png ├── include ├── minicoro.h └── simple.c ├── minicoro.c.v ├── minicoro_test.v ├── minicoro_v_api_test.v └── v.mod /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset = utf-8 3 | end_of_line = lf 4 | insert_final_newline = true 5 | trim_trailing_whitespace = true 6 | 7 | [*.v] 8 | indent_style = tab 9 | indent_size = 4 10 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.v linguist-language=V text=auto eol=lf 2 | *.vv linguist-language=V text=auto eol=lf 3 | *.vsh linguist-language=V text=auto eol=lf 4 | **/v.mod linguist-language=V text=auto eol=lf 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | main 3 | minicoro 4 | *.exe 5 | *.exe~ 6 | *.so 7 | *.dylib 8 | *.dll 9 | vls.log 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # minicoro.v 2 | ![minicoro.v icon](icon.png) 3 | 4 | ## WIP. See issues. 5 | 6 | 7 | Not a fork! This isn't a fork of edubart's minicoro [https://github.com/edubart/minicoro] but a wrapper built from the ground up with cross compatibility in mind. 8 | minocoro.v is a binding for minicoro in V with an aim for 100% parity with the C library. 9 | 10 | 11 | ## Installation 12 | Just do `v install lazalong.minicoro` 13 | ## Example Program main.v: 14 | ``` 15 | /********************************************************\ 16 | Example minicoro program. 17 | \********************************************************/ 18 | module main 19 | 20 | import lazalong.minicoro 21 | 22 | [callconv: "fastcall"] 23 | pub fn coro_entry(co &C.mco_coro) { 24 | println(" Coroutine 1") 25 | C.mco_yield(co) 26 | println(" Coroutine 2") 27 | } 28 | 29 | [console] 30 | fn main () { 31 | println('Simple Minicoro Test') 32 | 33 | // First initialize a `desc` object through `mco_desc_init`. 34 | fct := voidptr(&coro_entry) 35 | mut desc := C.mco_desc_init(fct, 0) 36 | 37 | // Configure `desc` fields when needed (e.g. customize user_data or allocation functions). 38 | desc.user_data = voidptr(0) 39 | mut co := &minicoro.Coro{} 40 | 41 | // Call `mco_create` with the output coroutine pointer and `desc` pointer. 42 | mut res := C.mco_create(&co, &desc) 43 | assert res == minicoro.Result.success 44 | // println(res) 45 | 46 | // The coroutine should be now in suspended state. 47 | assert C.mco_status(co) == minicoro.State.suspended 48 | 49 | // Call `mco_resume` to start for the first time, switching to its context. 50 | res = C.mco_resume(co) // Should print "coroutine 1". 51 | assert res == minicoro.Result.success 52 | 53 | // We get back from coroutine context in suspended state 54 | // because the coro_entry method yields after the first print 55 | assert C.mco_status(co) == minicoro.State.suspended 56 | 57 | // Call `mco_resume` to resume for a second time. 58 | res = C.mco_resume(co) // Should print "coroutine 2". 59 | assert res == minicoro.Result.success 60 | 61 | // The coroutine finished and should be now dead. 62 | assert C.mco_status(co) == minicoro.State.dead 63 | 64 | // Call `mco_destroy` to destroy the coroutine. 65 | res = C.mco_destroy(co) 66 | assert res == minicoro.Result.success 67 | } 68 | 69 | ``` 70 | ## Roadmap 71 | - [x] Support most common minicoro.h functions 72 | - [x] Support all minicoro.h functions 73 | - [x] Support all minicoro.h types 74 | - [x] Support all minicoro.h enums 75 | - [x] Add in #defines 76 | - [ ] Fully complete minicoro.h wrapper 77 | - [ ] minicoro.c.v documentation 78 | - [ ] Simple Examples 79 | - [ ] Other examples 80 | - [x] Windows support 81 | - [ ] iOS support 82 | - [x] Android support 83 | - [x] Linux support 84 | - [ ] Mac OS X support 85 | - [ ] WebAssembly support 86 | - [ ] Raspberry Pi support 87 | - [ ] RISC-V support 88 | - [ ] Add CI tests 89 | -------------------------------------------------------------------------------- /examples/simple.v: -------------------------------------------------------------------------------- 1 | module main 2 | 3 | import lazalong.minicoro 4 | 5 | [callconv: 'fastcall'] 6 | pub fn coro_entry(co &minicoro.Coro) { 7 | println(' Coroutine 1 - magic nb: $co.magic_number') 8 | res := minicoro.yield(co) 9 | str_res := unsafe { cstring_to_vstring(minicoro.result_description(res)) } 10 | println(' Coroutine 2 - magic nb: $co.magic_number "$str_res"') 11 | } 12 | 13 | [console] 14 | fn main() { 15 | println('Simple Minicoro Example') 16 | 17 | // First initialize a `desc` object through `mco_desc_init`. 18 | fct := voidptr(&coro_entry) 19 | mut desc := minicoro.desc_init(fct, 0) 20 | 21 | // Configure `desc` fields when needed (e.g. customize user_data or allocation functions). 22 | desc.user_data = voidptr(0) 23 | mut co := &minicoro.Coro{} 24 | 25 | // Call `mco_create` with the output coroutine pointer and `desc` pointer. 26 | mut res := minicoro.create(&co, &desc) 27 | assert res == .success 28 | // println(res) 29 | 30 | // The coroutine should be now in suspended state. 31 | assert minicoro.status(co) == .suspended 32 | 33 | // Call `mco_resume` to start for the first time, switching to its context. 34 | res = minicoro.resume(co) // Should print "coroutine 1". 35 | assert res == .success 36 | 37 | // We get back from coroutine context in suspended state 38 | // because the coro_entry method yields after the first print 39 | assert minicoro.status(co) == .suspended 40 | 41 | // Call `mco_resume` to resume for a second time. 42 | res = minicoro.resume(co) // Should print "coroutine 2". 43 | assert res == .success 44 | 45 | // The coroutine finished and should be now dead. 46 | assert minicoro.status(co) == .dead 47 | 48 | // Call `mco_destroy` to destroy the coroutine. 49 | res = minicoro.destroy(co) 50 | assert res == .success 51 | } 52 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazalong/minicoro/549bc10443b077d749d591ffd7f4e9cf01bc58c5/icon.png -------------------------------------------------------------------------------- /include/minicoro.h: -------------------------------------------------------------------------------- 1 | /* 2 | Minimal asymmetric stackful cross-platform coroutine library in pure C. 3 | minicoro - v0.1.3 - 27/Jan/2022 4 | Eduardo Bart - edub4rt@gmail.com 5 | https://github.com/edubart/minicoro 6 | 7 | Minicoro is single file library for using asymmetric coroutines in C. 8 | The API is inspired by Lua coroutines but with C use in mind. 9 | 10 | # Features 11 | 12 | - Stackful asymmetric coroutines. 13 | - Supports nesting coroutines (resuming a coroutine from another coroutine). 14 | - Supports custom allocators. 15 | - Storage system to allow passing values between yield and resume. 16 | - Customizable stack size. 17 | - Coroutine API design inspired by Lua with C use in mind. 18 | - Yield across any C function. 19 | - Made to work in multithread applications. 20 | - Cross platform. 21 | - Minimal, self contained and no external dependencies. 22 | - Readable sources and documented. 23 | - Implemented via assembly, ucontext or fibers. 24 | - Lightweight and very efficient. 25 | - Works in most C89 compilers. 26 | - Error prone API, returning proper error codes on misuse. 27 | - Support running with Valgrind, ASan (AddressSanitizer) and TSan (ThreadSanitizer). 28 | 29 | # Supported Platforms 30 | 31 | Most platforms are supported through different methods: 32 | 33 | | Platform | Assembly Method | Fallback Method | 34 | |--------------|------------------|-------------------| 35 | | Android | ARM/ARM64 | N/A | 36 | | iOS | ARM/ARM64 | N/A | 37 | | Windows | x86_64 | Windows fibers | 38 | | Linux | x86_64/i686 | ucontext | 39 | | Mac OS X | x86_64/ARM/ARM64 | ucontext | 40 | | WebAssembly | N/A | Emscripten fibers / Binaryen asyncify | 41 | | Raspberry Pi | ARM | ucontext | 42 | | RISC-V | rv64/rv32 | ucontext | 43 | 44 | The assembly method is used by default if supported by the compiler and CPU, 45 | otherwise ucontext or fiber method is used as a fallback. 46 | 47 | The assembly method is very efficient, it just take a few cycles 48 | to create, resume, yield or destroy a coroutine. 49 | 50 | # Caveats 51 | 52 | - Don't use coroutines with C++ exceptions, this is not supported. 53 | - When using C++ RAII (i.e. destructors) you must resume the coroutine until it dies to properly execute all destructors. 54 | - To use in multithread applications, you must compile with C compiler that supports `thread_local` qualifier. 55 | - Some unsupported sanitizers for C may trigger false warnings when using coroutines. 56 | - The `mco_coro` object is not thread safe, you should lock each coroutine into a thread. 57 | - Stack space is fixed, it cannot grow. By default it has about 56KB of space, this can be changed on coroutine creation. 58 | - Take care to not cause stack overflows (run out of stack space), otherwise your program may crash or not, the behavior is undefined. 59 | - On WebAssembly you must compile with Emscripten flag `-s ASYNCIFY=1`. 60 | - The WebAssembly Binaryen asyncify method can be used when explicitly enabled, 61 | you may want to do this only to use minicoro with WebAssembly native interpreters 62 | (no Web browser). This method is confirmed to work well with Emscripten toolchain, 63 | however it fails on other WebAssembly toolchains like WASI SDK. 64 | 65 | # Introduction 66 | 67 | A coroutine represents an independent "green" thread of execution. 68 | Unlike threads in multithread systems, however, 69 | a coroutine only suspends its execution by explicitly calling a yield function. 70 | 71 | You create a coroutine by calling `mco_create`. 72 | Its sole argument is a `mco_desc` structure with a description for the coroutine. 73 | The `mco_create` function only creates a new coroutine and returns a handle to it, it does not start the coroutine. 74 | 75 | You execute a coroutine by calling `mco_resume`. 76 | When calling a resume function the coroutine starts its execution by calling its body function. 77 | After the coroutine starts running, it runs until it terminates or yields. 78 | 79 | A coroutine yields by calling `mco_yield`. 80 | When a coroutine yields, the corresponding resume returns immediately, 81 | even if the yield happens inside nested function calls (that is, not in the main function). 82 | The next time you resume the same coroutine, it continues its execution from the point where it yielded. 83 | 84 | To associate a persistent value with the coroutine, 85 | you can optionally set `user_data` on its creation and later retrieve with `mco_get_user_data`. 86 | 87 | To pass values between resume and yield, 88 | you can optionally use `mco_push` and `mco_pop` APIs, 89 | they are intended to pass temporary values using a LIFO style buffer. 90 | The storage system can also be used to send and receive initial values on coroutine creation or before it finishes. 91 | 92 | # Usage 93 | 94 | To use minicoro, do the following in one .c file: 95 | 96 | ```c 97 | #define MINICORO_IMPL 98 | #include "minicoro.h" 99 | ``` 100 | 101 | You can do `#include "minicoro.h"` in other parts of the program just like any other header. 102 | 103 | ## Minimal Example 104 | 105 | The following simple example demonstrates on how to use the library: 106 | 107 | ```c 108 | #define MINICORO_IMPL 109 | #include "minicoro.h" 110 | #include 111 | 112 | // Coroutine entry function. 113 | void coro_entry(mco_coro* co) { 114 | printf("coroutine 1\n"); 115 | mco_yield(co); 116 | printf("coroutine 2\n"); 117 | } 118 | 119 | int main() { 120 | // First initialize a `desc` object through `mco_desc_init`. 121 | mco_desc desc = mco_desc_init(coro_entry, 0); 122 | // Configure `desc` fields when needed (e.g. customize user_data or allocation functions). 123 | desc.user_data = NULL; 124 | // Call `mco_create` with the output coroutine pointer and `desc` pointer. 125 | mco_coro* co; 126 | mco_result res = mco_create(&co, &desc); 127 | assert(res == MCO_SUCCESS); 128 | // The coroutine should be now in suspended state. 129 | assert(mco_status(co) == MCO_SUSPENDED); 130 | // Call `mco_resume` to start for the first time, switching to its context. 131 | res = mco_resume(co); // Should print "coroutine 1". 132 | assert(res == MCO_SUCCESS); 133 | // We get back from coroutine context in suspended state (because it's unfinished). 134 | assert(mco_status(co) == MCO_SUSPENDED); 135 | // Call `mco_resume` to resume for a second time. 136 | res = mco_resume(co); // Should print "coroutine 2". 137 | assert(res == MCO_SUCCESS); 138 | // The coroutine finished and should be now dead. 139 | assert(mco_status(co) == MCO_DEAD); 140 | // Call `mco_destroy` to destroy the coroutine. 141 | res = mco_destroy(co); 142 | assert(res == MCO_SUCCESS); 143 | return 0; 144 | } 145 | ``` 146 | 147 | _NOTE_: In case you don't want to use the minicoro allocator system you should 148 | allocate a coroutine object yourself using `mco_desc.coro_size` and call `mco_init`, 149 | then later to destroy call `mco_uninit` and deallocate it. 150 | 151 | ## Yielding from anywhere 152 | 153 | You can yield the current running coroutine from anywhere 154 | without having to pass `mco_coro` pointers around, 155 | to this just use `mco_yield(mco_running())`. 156 | 157 | ## Passing data between yield and resume 158 | 159 | The library has the storage interface to assist passing data between yield and resume. 160 | It's usage is straightforward, 161 | use `mco_push` to send data before a `mco_resume` or `mco_yield`, 162 | then later use `mco_pop` after a `mco_resume` or `mco_yield` to receive data. 163 | Take care to not mismatch a push and pop, otherwise these functions will return 164 | an error. 165 | 166 | ## Error handling 167 | 168 | The library return error codes in most of its API in case of misuse or system error, 169 | the user is encouraged to handle them properly. 170 | 171 | ## Library customization 172 | 173 | The following can be defined to change the library behavior: 174 | 175 | - `MCO_API` - Public API qualifier. Default is `extern`. 176 | - `MCO_MIN_STACK_SIZE` - Minimum stack size when creating a coroutine. Default is 32768. 177 | - `MCO_DEFAULT_STORAGE_SIZE` - Size of coroutine storage buffer. Default is 1024. 178 | - `MCO_DEFAULT_STACK_SIZE` - Default stack size when creating a coroutine. Default is 57344. 179 | - `MCO_MALLOC` - Default allocation function. Default is `malloc`. 180 | - `MCO_FREE` - Default deallocation function. Default is `free`. 181 | - `MCO_DEBUG` - Enable debug mode, logging any runtime error to stdout. Defined automatically unless `NDEBUG` or `MCO_NO_DEBUG` is defined. 182 | - `MCO_NO_DEBUG` - Disable debug mode. 183 | - `MCO_NO_MULTITHREAD` - Disable multithread usage. Multithread is supported when `thread_local` is supported. 184 | - `MCO_NO_DEFAULT_ALLOCATORS` - Disable the default allocator using `MCO_MALLOC` and `MCO_FREE`. 185 | - `MCO_ZERO_MEMORY` - Zero memory of stack for new coroutines and when poping storage, intended for garbage collected environments. 186 | - `MCO_USE_ASM` - Force use of assembly context switch implementation. 187 | - `MCO_USE_UCONTEXT` - Force use of ucontext context switch implementation. 188 | - `MCO_USE_FIBERS` - Force use of fibers context switch implementation. 189 | - `MCO_USE_ASYNCIFY` - Force use of Binaryen asyncify context switch implementation. 190 | - `MCO_USE_VALGRIND` - Define if you want run with valgrind to fix accessing memory errors. 191 | 192 | # License 193 | 194 | Your choice of either Public Domain or MIT No Attribution, see end of file. 195 | */ 196 | 197 | 198 | #ifndef MINICORO_H 199 | #define MINICORO_H 200 | 201 | #ifdef __cplusplus 202 | extern "C" { 203 | #endif 204 | 205 | /* Public API qualifier. */ 206 | #ifndef MCO_API 207 | #define MCO_API extern 208 | #endif 209 | 210 | /* Size of coroutine storage buffer. */ 211 | #ifndef MCO_DEFAULT_STORAGE_SIZE 212 | #define MCO_DEFAULT_STORAGE_SIZE 1024 213 | #endif 214 | 215 | #include /* for size_t */ 216 | 217 | /* ---------------------------------------------------------------------------------------------- */ 218 | 219 | /* Coroutine states. */ 220 | typedef enum mco_state { 221 | MCO_DEAD = 0, /* The coroutine has finished normally or was uninitialized before finishing. */ 222 | MCO_NORMAL, /* The coroutine is active but not running (that is, it has resumed another coroutine). */ 223 | MCO_RUNNING, /* The coroutine is active and running. */ 224 | MCO_SUSPENDED /* The coroutine is suspended (in a call to yield, or it has not started running yet). */ 225 | } mco_state; 226 | 227 | /* Coroutine result codes. */ 228 | typedef enum mco_result { 229 | MCO_SUCCESS = 0, 230 | MCO_GENERIC_ERROR, 231 | MCO_INVALID_POINTER, 232 | MCO_INVALID_COROUTINE, 233 | MCO_NOT_SUSPENDED, 234 | MCO_NOT_RUNNING, 235 | MCO_MAKE_CONTEXT_ERROR, 236 | MCO_SWITCH_CONTEXT_ERROR, 237 | MCO_NOT_ENOUGH_SPACE, 238 | MCO_OUT_OF_MEMORY, 239 | MCO_INVALID_ARGUMENTS, 240 | MCO_INVALID_OPERATION, 241 | MCO_STACK_OVERFLOW, 242 | } mco_result; 243 | 244 | /* Coroutine structure. */ 245 | typedef struct mco_coro mco_coro; 246 | struct mco_coro { 247 | void* context; 248 | mco_state state; 249 | void (*func)(mco_coro* co); 250 | mco_coro* prev_co; 251 | void* user_data; 252 | void* allocator_data; 253 | void (*free_cb)(void* ptr, void* allocator_data); 254 | void* stack_base; /* Stack base address, can be used to scan memory in a garbage collector. */ 255 | size_t stack_size; 256 | unsigned char* storage; 257 | size_t bytes_stored; 258 | size_t storage_size; 259 | void* asan_prev_stack; /* Used by address sanitizer. */ 260 | void* tsan_prev_fiber; /* Used by thread sanitizer. */ 261 | void* tsan_fiber; /* Used by thread sanitizer. */ 262 | size_t magic_number; /* Used to check stack overflow. */ 263 | }; 264 | 265 | /* Structure used to initialize a coroutine. */ 266 | typedef struct mco_desc { 267 | void (*func)(mco_coro* co); /* Entry point function for the coroutine. */ 268 | void* user_data; /* Coroutine user data, can be get with `mco_get_user_data`. */ 269 | /* Custom allocation interface. */ 270 | void* (*malloc_cb)(size_t size, void* allocator_data); /* Custom allocation function. */ 271 | void (*free_cb)(void* ptr, void* allocator_data); /* Custom deallocation function. */ 272 | void* allocator_data; /* User data pointer passed to `malloc`/`free` allocation functions. */ 273 | size_t storage_size; /* Coroutine storage size, to be used with the storage APIs. */ 274 | /* These must be initialized only through `mco_init_desc`. */ 275 | size_t coro_size; /* Coroutine structure size. */ 276 | size_t stack_size; /* Coroutine stack size. */ 277 | } mco_desc; 278 | 279 | /* Coroutine functions. */ 280 | 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. */ 281 | MCO_API mco_result mco_init(mco_coro* co, mco_desc* desc); /* Initialize the coroutine. */ 282 | MCO_API mco_result mco_uninit(mco_coro* co); /* Uninitialize the coroutine, may fail if it's not dead or suspended. */ 283 | MCO_API mco_result mco_create(mco_coro** out_co, mco_desc* desc); /* Allocates and initializes a new coroutine. */ 284 | MCO_API mco_result mco_destroy(mco_coro* co); /* Uninitialize and deallocate the coroutine, may fail if it's not dead or suspended. */ 285 | MCO_API mco_result mco_resume(mco_coro* co); /* Starts or continues the execution of the coroutine. */ 286 | MCO_API mco_result mco_yield(mco_coro* co); /* Suspends the execution of a coroutine. */ 287 | MCO_API mco_state mco_status(mco_coro* co); /* Returns the status of the coroutine. */ 288 | MCO_API void* mco_get_user_data(mco_coro* co); /* Get coroutine user data supplied on coroutine creation. */ 289 | 290 | /* Storage interface functions, used to pass values between yield and resume. */ 291 | 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. */ 292 | 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. */ 293 | MCO_API mco_result mco_peek(mco_coro* co, void* dest, size_t len); /* Like `mco_pop` but it does not consumes the storage. */ 294 | MCO_API size_t mco_get_bytes_stored(mco_coro* co); /* Get the available bytes that can be retrieved with a `mco_pop`. */ 295 | MCO_API size_t mco_get_storage_size(mco_coro* co); /* Get the total storage size. */ 296 | 297 | /* Misc functions. */ 298 | MCO_API mco_coro* mco_running(void); /* Returns the running coroutine for the current thread. */ 299 | MCO_API const char* mco_result_description(mco_result res); /* Get the description of a result. */ 300 | 301 | #ifdef __cplusplus 302 | } 303 | #endif 304 | 305 | #endif /* MINICORO_H */ 306 | 307 | #ifdef MINICORO_IMPL 308 | 309 | #ifdef __cplusplus 310 | extern "C" { 311 | #endif 312 | 313 | /* ---------------------------------------------------------------------------------------------- */ 314 | 315 | /* Minimum stack size when creating a coroutine. */ 316 | #ifndef MCO_MIN_STACK_SIZE 317 | #define MCO_MIN_STACK_SIZE 32768 318 | #endif 319 | 320 | /* Default stack size when creating a coroutine. */ 321 | #ifndef MCO_DEFAULT_STACK_SIZE 322 | #define MCO_DEFAULT_STACK_SIZE 57344 /* Don't use multiples of 64K to avoid D-cache aliasing conflicts. */ 323 | #endif 324 | 325 | /* Number used only to assist checking for stack overflows. */ 326 | #define MCO_MAGIC_NUMBER 0x7E3CB1A9 327 | 328 | /* Detect implementation based on OS, arch and compiler. */ 329 | #if !defined(MCO_USE_UCONTEXT) && !defined(MCO_USE_FIBERS) && !defined(MCO_USE_ASM) && !defined(MCO_USE_ASYNCIFY) 330 | #if defined(_WIN32) 331 | #if (defined(__GNUC__) && defined(__x86_64__)) || (defined(_MSC_VER) && defined(_M_X64)) 332 | #define MCO_USE_ASM 333 | #else 334 | #define MCO_USE_FIBERS 335 | #endif 336 | #elif defined(__CYGWIN__) /* MSYS */ 337 | #define MCO_USE_UCONTEXT 338 | #elif defined(__EMSCRIPTEN__) 339 | #define MCO_USE_FIBERS 340 | #elif defined(__wasm__) 341 | #define MCO_USE_ASYNCIFY 342 | #else 343 | #if __GNUC__ >= 3 /* Assembly extension supported. */ 344 | #if defined(__x86_64__) || \ 345 | defined(__i386) || defined(__i386__) || \ 346 | defined(__ARM_EABI__) || defined(__aarch64__) || \ 347 | defined(__riscv) 348 | #define MCO_USE_ASM 349 | #else 350 | #define MCO_USE_UCONTEXT 351 | #endif 352 | #else 353 | #define MCO_USE_UCONTEXT 354 | #endif 355 | #endif 356 | #endif 357 | 358 | #define _MCO_UNUSED(x) (void)(x) 359 | 360 | #if !defined(MCO_NO_DEBUG) && !defined(NDEBUG) && !defined(MCO_DEBUG) 361 | #define MCO_DEBUG 362 | #endif 363 | 364 | #ifndef MCO_LOG 365 | #ifdef MCO_DEBUG 366 | #include 367 | #define MCO_LOG(s) puts(s) 368 | #else 369 | #define MCO_LOG(s) 370 | #endif 371 | #endif 372 | 373 | #ifndef MCO_ASSERT 374 | #ifdef MCO_DEBUG 375 | #include 376 | #define MCO_ASSERT(c) assert(c) 377 | #else 378 | #define MCO_ASSERT(c) 379 | #endif 380 | #endif 381 | 382 | #ifndef MCO_THREAD_LOCAL 383 | #ifdef MCO_NO_MULTITHREAD 384 | #define MCO_THREAD_LOCAL 385 | #else 386 | #ifdef thread_local 387 | #define MCO_THREAD_LOCAL thread_local 388 | #elif __STDC_VERSION__ >= 201112 && !defined(__STDC_NO_THREADS__) 389 | #define MCO_THREAD_LOCAL _Thread_local 390 | #elif defined(_WIN32) && (defined(_MSC_VER) || defined(__ICL) || defined(__DMC__) || defined(__BORLANDC__)) 391 | #define MCO_THREAD_LOCAL __declspec(thread) 392 | #elif defined(__GNUC__) || defined(__SUNPRO_C) || defined(__xlC__) 393 | #define MCO_THREAD_LOCAL __thread 394 | #else /* No thread local support, `mco_running` will be thread unsafe. */ 395 | #define MCO_THREAD_LOCAL 396 | #define MCO_NO_MULTITHREAD 397 | #endif 398 | #endif 399 | #endif 400 | 401 | #ifndef MCO_FORCE_INLINE 402 | #ifdef _MSC_VER 403 | #define MCO_FORCE_INLINE __forceinline 404 | #elif defined(__GNUC__) 405 | #if defined(__STRICT_ANSI__) 406 | #define MCO_FORCE_INLINE __inline__ __attribute__((always_inline)) 407 | #else 408 | #define MCO_FORCE_INLINE inline __attribute__((always_inline)) 409 | #endif 410 | #elif defined(__BORLANDC__) || defined(__DMC__) || defined(__SC__) || defined(__WATCOMC__) || defined(__LCC__) || defined(__DECC) 411 | #define MCO_FORCE_INLINE __inline 412 | #else /* No inline support. */ 413 | #define MCO_FORCE_INLINE 414 | #endif 415 | #endif 416 | 417 | #ifndef MCO_NO_INLINE 418 | #ifdef __GNUC__ 419 | #define MCO_NO_INLINE __attribute__((noinline)) 420 | #elif defined(_MSC_VER) 421 | #define MCO_NO_INLINE __declspec(noinline) 422 | #else 423 | #define MCO_NO_INLINE 424 | #endif 425 | #endif 426 | 427 | #ifndef MCO_NO_DEFAULT_ALLOCATORS 428 | #ifndef MCO_MALLOC 429 | #include 430 | #define MCO_MALLOC malloc 431 | #define MCO_FREE free 432 | #endif 433 | static void* mco_malloc(size_t size, void* allocator_data) { 434 | _MCO_UNUSED(allocator_data); 435 | return MCO_MALLOC(size); 436 | } 437 | static void mco_free(void* ptr, void* allocator_data) { 438 | _MCO_UNUSED(allocator_data); 439 | MCO_FREE(ptr); 440 | } 441 | #endif /* MCO_NO_DEFAULT_ALLOCATORS */ 442 | 443 | #if defined(__has_feature) 444 | #if __has_feature(address_sanitizer) 445 | #define _MCO_USE_ASAN 446 | #endif 447 | #if __has_feature(thread_sanitizer) 448 | #define _MCO_USE_TSAN 449 | #endif 450 | #endif 451 | #if defined(__SANITIZE_ADDRESS__) 452 | #define _MCO_USE_ASAN 453 | #endif 454 | #if defined(__SANITIZE_THREAD__) 455 | #define _MCO_USE_TSAN 456 | #endif 457 | #ifdef _MCO_USE_ASAN 458 | void __sanitizer_start_switch_fiber(void** fake_stack_save, const void *bottom, size_t size); 459 | void __sanitizer_finish_switch_fiber(void* fake_stack_save, const void **bottom_old, size_t *size_old); 460 | #endif 461 | #ifdef _MCO_USE_TSAN 462 | void* __tsan_get_current_fiber(void); 463 | void* __tsan_create_fiber(unsigned flags); 464 | void __tsan_destroy_fiber(void* fiber); 465 | void __tsan_switch_to_fiber(void* fiber, unsigned flags); 466 | #endif 467 | 468 | #include /* For memcpy and memset. */ 469 | 470 | /* Utility for aligning addresses. */ 471 | static MCO_FORCE_INLINE size_t _mco_align_forward(size_t addr, size_t align) { 472 | return (addr + (align-1)) & ~(align-1); 473 | } 474 | 475 | /* Variable holding the current running coroutine per thread. */ 476 | static MCO_THREAD_LOCAL mco_coro* mco_current_co = NULL; 477 | 478 | static MCO_FORCE_INLINE void _mco_prepare_jumpin(mco_coro* co) { 479 | /* Set the old coroutine to normal state and update it. */ 480 | mco_coro* prev_co = mco_running(); /* Must access through `mco_running`. */ 481 | MCO_ASSERT(co->prev_co == NULL); 482 | co->prev_co = prev_co; 483 | if(prev_co) { 484 | MCO_ASSERT(prev_co->state == MCO_RUNNING); 485 | prev_co->state = MCO_NORMAL; 486 | } 487 | mco_current_co = co; 488 | #ifdef _MCO_USE_ASAN 489 | if(prev_co) { 490 | void* bottom_old = NULL; 491 | size_t size_old = 0; 492 | __sanitizer_finish_switch_fiber(prev_co->asan_prev_stack, (const void**)&bottom_old, &size_old); 493 | prev_co->asan_prev_stack = NULL; 494 | } 495 | __sanitizer_start_switch_fiber(&co->asan_prev_stack, co->stack_base, co->stack_size); 496 | #endif 497 | #ifdef _MCO_USE_TSAN 498 | co->tsan_prev_fiber = __tsan_get_current_fiber(); 499 | __tsan_switch_to_fiber(co->tsan_fiber, 0); 500 | #endif 501 | } 502 | 503 | static MCO_FORCE_INLINE void _mco_prepare_jumpout(mco_coro* co) { 504 | /* Switch back to the previous running coroutine. */ 505 | /* MCO_ASSERT(mco_running() == co); */ 506 | mco_coro* prev_co = co->prev_co; 507 | co->prev_co = NULL; 508 | if(prev_co) { 509 | /* MCO_ASSERT(prev_co->state == MCO_NORMAL); */ 510 | prev_co->state = MCO_RUNNING; 511 | } 512 | mco_current_co = prev_co; 513 | #ifdef _MCO_USE_ASAN 514 | void* bottom_old = NULL; 515 | size_t size_old = 0; 516 | __sanitizer_finish_switch_fiber(co->asan_prev_stack, (const void**)&bottom_old, &size_old); 517 | co->asan_prev_stack = NULL; 518 | if(prev_co) { 519 | __sanitizer_start_switch_fiber(&prev_co->asan_prev_stack, bottom_old, size_old); 520 | } 521 | #endif 522 | #ifdef _MCO_USE_TSAN 523 | void* tsan_prev_fiber = co->tsan_prev_fiber; 524 | co->tsan_prev_fiber = NULL; 525 | __tsan_switch_to_fiber(tsan_prev_fiber, 0); 526 | #endif 527 | } 528 | 529 | static void _mco_jumpin(mco_coro* co); 530 | static void _mco_jumpout(mco_coro* co); 531 | 532 | static MCO_NO_INLINE void _mco_main(mco_coro* co) { 533 | co->func(co); /* Run the coroutine function. */ 534 | co->state = MCO_DEAD; /* Coroutine finished successfully, set state to dead. */ 535 | _mco_jumpout(co); /* Jump back to the old context .*/ 536 | } 537 | 538 | /* ---------------------------------------------------------------------------------------------- */ 539 | 540 | #if defined(MCO_USE_UCONTEXT) || defined(MCO_USE_ASM) 541 | 542 | /* 543 | Some of the following assembly code is taken from LuaCoco by Mike Pall. 544 | See https://coco.luajit.org/index.html 545 | 546 | MIT license 547 | 548 | Copyright (C) 2004-2016 Mike Pall. All rights reserved. 549 | 550 | Permission is hereby granted, free of charge, to any person obtaining 551 | a copy of this software and associated documentation files (the 552 | "Software"), to deal in the Software without restriction, including 553 | without limitation the rights to use, copy, modify, merge, publish, 554 | distribute, sublicense, and/or sell copies of the Software, and to 555 | permit persons to whom the Software is furnished to do so, subject to 556 | the following conditions: 557 | 558 | The above copyright notice and this permission notice shall be 559 | included in all copies or substantial portions of the Software. 560 | 561 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 562 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 563 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 564 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 565 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 566 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 567 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 568 | */ 569 | 570 | #ifdef MCO_USE_ASM 571 | 572 | #if defined(__x86_64__) || defined(_M_X64) 573 | 574 | #ifdef _WIN32 575 | 576 | typedef struct _mco_ctxbuf { 577 | void *rip, *rsp, *rbp, *rbx, *r12, *r13, *r14, *r15, *rdi, *rsi; 578 | void* xmm[20]; /* xmm6, xmm7, xmm8, xmm9, xmm10, xmm11, xmm12, xmm13, xmm14, xmm15 */ 579 | void* fiber_storage; 580 | void* dealloc_stack; 581 | void* stack_limit; 582 | void* stack_base; 583 | } _mco_ctxbuf; 584 | 585 | #if defined(__GNUC__) 586 | #define _MCO_ASM_BLOB __attribute__((section(".text"))) 587 | #elif defined(_MSC_VER) 588 | #define _MCO_ASM_BLOB __declspec(allocate(".text")) 589 | #pragma section(".text") 590 | #endif 591 | 592 | _MCO_ASM_BLOB static unsigned char _mco_wrap_main_code[] = { 593 | 0x4c, 0x89, 0xe9, /* mov %r13,%rcx */ 594 | 0x41, 0xff, 0xe4, /* jmpq *%r12 */ 595 | 0xc3, /* retq */ 596 | 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 /* nop */ 597 | }; 598 | 599 | _MCO_ASM_BLOB static unsigned char _mco_switch_code[] = { 600 | 0x48, 0x8d, 0x05, 0x52, 0x01, 0x00, 0x00, /* lea 0x152(%rip),%rax */ 601 | 0x48, 0x89, 0x01, /* mov %rax,(%rcx) */ 602 | 0x48, 0x89, 0x61, 0x08, /* mov %rsp,0x8(%rcx) */ 603 | 0x48, 0x89, 0x69, 0x10, /* mov %rbp,0x10(%rcx) */ 604 | 0x48, 0x89, 0x59, 0x18, /* mov %rbx,0x18(%rcx) */ 605 | 0x4c, 0x89, 0x61, 0x20, /* mov %r12,0x20(%rcx) */ 606 | 0x4c, 0x89, 0x69, 0x28, /* mov %r13,0x28(%rcx) */ 607 | 0x4c, 0x89, 0x71, 0x30, /* mov %r14,0x30(%rcx) */ 608 | 0x4c, 0x89, 0x79, 0x38, /* mov %r15,0x38(%rcx) */ 609 | 0x48, 0x89, 0x79, 0x40, /* mov %rdi,0x40(%rcx) */ 610 | 0x48, 0x89, 0x71, 0x48, /* mov %rsi,0x48(%rcx) */ 611 | 0x66, 0x0f, 0xd6, 0x71, 0x50, /* movq %xmm6,0x50(%rcx) */ 612 | 0x66, 0x0f, 0xd6, 0x79, 0x60, /* movq %xmm7,0x60(%rcx) */ 613 | 0x66, 0x44, 0x0f, 0xd6, 0x41, 0x70, /* movq %xmm8,0x70(%rcx) */ 614 | 0x66, 0x44, 0x0f, 0xd6, 0x89, 0x80, 0x00, 0x00, 0x00, /* movq %xmm9,0x80(%rcx) */ 615 | 0x66, 0x44, 0x0f, 0xd6, 0x91, 0x90, 0x00, 0x00, 0x00, /* movq %xmm10,0x90(%rcx) */ 616 | 0x66, 0x44, 0x0f, 0xd6, 0x99, 0xa0, 0x00, 0x00, 0x00, /* movq %xmm11,0xa0(%rcx) */ 617 | 0x66, 0x44, 0x0f, 0xd6, 0xa1, 0xb0, 0x00, 0x00, 0x00, /* movq %xmm12,0xb0(%rcx) */ 618 | 0x66, 0x44, 0x0f, 0xd6, 0xa9, 0xc0, 0x00, 0x00, 0x00, /* movq %xmm13,0xc0(%rcx) */ 619 | 0x66, 0x44, 0x0f, 0xd6, 0xb1, 0xd0, 0x00, 0x00, 0x00, /* movq %xmm14,0xd0(%rcx) */ 620 | 0x66, 0x44, 0x0f, 0xd6, 0xb9, 0xe0, 0x00, 0x00, 0x00, /* movq %xmm15,0xe0(%rcx) */ 621 | 0x65, 0x4c, 0x8b, 0x14, 0x25, 0x30, 0x00, 0x00, 0x00, /* mov %gs:0x30,%r10 */ 622 | 0x49, 0x8b, 0x42, 0x20, /* mov 0x20(%r10),%rax */ 623 | 0x48, 0x89, 0x81, 0xf0, 0x00, 0x00, 0x00, /* mov %rax,0xf0(%rcx) */ 624 | 0x49, 0x8b, 0x82, 0x78, 0x14, 0x00, 0x00, /* mov 0x1478(%r10),%rax */ 625 | 0x48, 0x89, 0x81, 0xf8, 0x00, 0x00, 0x00, /* mov %rax,0xf8(%rcx) */ 626 | 0x49, 0x8b, 0x42, 0x10, /* mov 0x10(%r10),%rax */ 627 | 0x48, 0x89, 0x81, 0x00, 0x01, 0x00, 0x00, /* mov %rax,0x100(%rcx) */ 628 | 0x49, 0x8b, 0x42, 0x08, /* mov 0x8(%r10),%rax */ 629 | 0x48, 0x89, 0x81, 0x08, 0x01, 0x00, 0x00, /* mov %rax,0x108(%rcx) */ 630 | 0x48, 0x8b, 0x82, 0x08, 0x01, 0x00, 0x00, /* mov 0x108(%rdx),%rax */ 631 | 0x49, 0x89, 0x42, 0x08, /* mov %rax,0x8(%r10) */ 632 | 0x48, 0x8b, 0x82, 0x00, 0x01, 0x00, 0x00, /* mov 0x100(%rdx),%rax */ 633 | 0x49, 0x89, 0x42, 0x10, /* mov %rax,0x10(%r10) */ 634 | 0x48, 0x8b, 0x82, 0xf8, 0x00, 0x00, 0x00, /* mov 0xf8(%rdx),%rax */ 635 | 0x49, 0x89, 0x82, 0x78, 0x14, 0x00, 0x00, /* mov %rax,0x1478(%r10) */ 636 | 0x48, 0x8b, 0x82, 0xf0, 0x00, 0x00, 0x00, /* mov 0xf0(%rdx),%rax */ 637 | 0x49, 0x89, 0x42, 0x20, /* mov %rax,0x20(%r10) */ 638 | 0xf3, 0x44, 0x0f, 0x7e, 0xba, 0xe0, 0x00, 0x00, 0x00, /* movq 0xe0(%rdx),%xmm15 */ 639 | 0xf3, 0x44, 0x0f, 0x7e, 0xb2, 0xd0, 0x00, 0x00, 0x00, /* movq 0xd0(%rdx),%xmm14 */ 640 | 0xf3, 0x44, 0x0f, 0x7e, 0xaa, 0xc0, 0x00, 0x00, 0x00, /* movq 0xc0(%rdx),%xmm13 */ 641 | 0xf3, 0x44, 0x0f, 0x7e, 0xa2, 0xb0, 0x00, 0x00, 0x00, /* movq 0xb0(%rdx),%xmm12 */ 642 | 0xf3, 0x44, 0x0f, 0x7e, 0x9a, 0xa0, 0x00, 0x00, 0x00, /* movq 0xa0(%rdx),%xmm11 */ 643 | 0xf3, 0x44, 0x0f, 0x7e, 0x92, 0x90, 0x00, 0x00, 0x00, /* movq 0x90(%rdx),%xmm10 */ 644 | 0xf3, 0x44, 0x0f, 0x7e, 0x8a, 0x80, 0x00, 0x00, 0x00, /* movq 0x80(%rdx),%xmm9 */ 645 | 0xf3, 0x44, 0x0f, 0x7e, 0x42, 0x70, /* movq 0x70(%rdx),%xmm8 */ 646 | 0xf3, 0x0f, 0x7e, 0x7a, 0x60, /* movq 0x60(%rdx),%xmm7 */ 647 | 0xf3, 0x0f, 0x7e, 0x72, 0x50, /* movq 0x50(%rdx),%xmm6 */ 648 | 0x48, 0x8b, 0x72, 0x48, /* mov 0x48(%rdx),%rsi */ 649 | 0x48, 0x8b, 0x7a, 0x40, /* mov 0x40(%rdx),%rdi */ 650 | 0x4c, 0x8b, 0x7a, 0x38, /* mov 0x38(%rdx),%r15 */ 651 | 0x4c, 0x8b, 0x72, 0x30, /* mov 0x30(%rdx),%r14 */ 652 | 0x4c, 0x8b, 0x6a, 0x28, /* mov 0x28(%rdx),%r13 */ 653 | 0x4c, 0x8b, 0x62, 0x20, /* mov 0x20(%rdx),%r12 */ 654 | 0x48, 0x8b, 0x5a, 0x18, /* mov 0x18(%rdx),%rbx */ 655 | 0x48, 0x8b, 0x6a, 0x10, /* mov 0x10(%rdx),%rbp */ 656 | 0x48, 0x8b, 0x62, 0x08, /* mov 0x8(%rdx),%rsp */ 657 | 0xff, 0x22, /* jmpq *(%rdx) */ 658 | 0xc3, /* retq */ 659 | 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, /* nop */ 660 | }; 661 | 662 | void (*_mco_wrap_main)(void) = (void(*)(void))(void*)_mco_wrap_main_code; 663 | void (*_mco_switch)(_mco_ctxbuf* from, _mco_ctxbuf* to) = (void(*)(_mco_ctxbuf* from, _mco_ctxbuf* to))(void*)_mco_switch_code; 664 | 665 | static mco_result _mco_makectx(mco_coro* co, _mco_ctxbuf* ctx, void* stack_base, size_t stack_size) { 666 | stack_size = stack_size - 32; /* Reserve 32 bytes for the shadow space. */ 667 | void** stack_high_ptr = (void**)((size_t)stack_base + stack_size - sizeof(size_t)); 668 | stack_high_ptr[0] = (void*)(0xdeaddeaddeaddead); /* Dummy return address. */ 669 | ctx->rip = (void*)(_mco_wrap_main); 670 | ctx->rsp = (void*)(stack_high_ptr); 671 | ctx->r12 = (void*)(_mco_main); 672 | ctx->r13 = (void*)(co); 673 | void* stack_top = (void*)((size_t)stack_base + stack_size); 674 | ctx->stack_base = stack_top; 675 | ctx->stack_limit = stack_base; 676 | ctx->dealloc_stack = stack_base; 677 | return MCO_SUCCESS; 678 | } 679 | 680 | #else /* not _WIN32 */ 681 | 682 | typedef struct _mco_ctxbuf { 683 | void *rip, *rsp, *rbp, *rbx, *r12, *r13, *r14, *r15; 684 | } _mco_ctxbuf; 685 | 686 | void _mco_wrap_main(void); 687 | int _mco_switch(_mco_ctxbuf* from, _mco_ctxbuf* to); 688 | 689 | __asm__( 690 | ".text\n" 691 | #ifdef __MACH__ /* Mac OS X assembler */ 692 | ".globl __mco_wrap_main\n" 693 | "__mco_wrap_main:\n" 694 | #else /* Linux assembler */ 695 | ".globl _mco_wrap_main\n" 696 | ".type _mco_wrap_main @function\n" 697 | ".hidden _mco_wrap_main\n" 698 | "_mco_wrap_main:\n" 699 | #endif 700 | " movq %r13, %rdi\n" 701 | " jmpq *%r12\n" 702 | #ifndef __MACH__ 703 | ".size _mco_wrap_main, .-_mco_wrap_main\n" 704 | #endif 705 | ); 706 | 707 | __asm__( 708 | ".text\n" 709 | #ifdef __MACH__ /* Mac OS assembler */ 710 | ".globl __mco_switch\n" 711 | "__mco_switch:\n" 712 | #else /* Linux assembler */ 713 | ".globl _mco_switch\n" 714 | ".type _mco_switch @function\n" 715 | ".hidden _mco_switch\n" 716 | "_mco_switch:\n" 717 | #endif 718 | " leaq 0x3d(%rip), %rax\n" 719 | " movq %rax, (%rdi)\n" 720 | " movq %rsp, 8(%rdi)\n" 721 | " movq %rbp, 16(%rdi)\n" 722 | " movq %rbx, 24(%rdi)\n" 723 | " movq %r12, 32(%rdi)\n" 724 | " movq %r13, 40(%rdi)\n" 725 | " movq %r14, 48(%rdi)\n" 726 | " movq %r15, 56(%rdi)\n" 727 | " movq 56(%rsi), %r15\n" 728 | " movq 48(%rsi), %r14\n" 729 | " movq 40(%rsi), %r13\n" 730 | " movq 32(%rsi), %r12\n" 731 | " movq 24(%rsi), %rbx\n" 732 | " movq 16(%rsi), %rbp\n" 733 | " movq 8(%rsi), %rsp\n" 734 | " jmpq *(%rsi)\n" 735 | " ret\n" 736 | #ifndef __MACH__ 737 | ".size _mco_switch, .-_mco_switch\n" 738 | #endif 739 | ); 740 | 741 | static mco_result _mco_makectx(mco_coro* co, _mco_ctxbuf* ctx, void* stack_base, size_t stack_size) { 742 | stack_size = stack_size - 128; /* Reserve 128 bytes for the Red Zone space (System V AMD64 ABI). */ 743 | void** stack_high_ptr = (void**)((size_t)stack_base + stack_size - sizeof(size_t)); 744 | stack_high_ptr[0] = (void*)(0xdeaddeaddeaddead); /* Dummy return address. */ 745 | ctx->rip = (void*)(_mco_wrap_main); 746 | ctx->rsp = (void*)(stack_high_ptr); 747 | ctx->r12 = (void*)(_mco_main); 748 | ctx->r13 = (void*)(co); 749 | return MCO_SUCCESS; 750 | } 751 | 752 | #endif /* not _WIN32 */ 753 | 754 | #elif defined(__riscv) 755 | 756 | typedef struct _mco_ctxbuf { 757 | void* s[12]; /* s0-s11 */ 758 | void* ra; 759 | void* pc; 760 | void* sp; 761 | #ifdef __riscv_flen 762 | #if __riscv_flen == 64 763 | double fs[12]; /* fs0-fs11 */ 764 | #elif __riscv_flen == 32 765 | float fs[12]; /* fs0-fs11 */ 766 | #endif 767 | #endif /* __riscv_flen */ 768 | } _mco_ctxbuf; 769 | 770 | void _mco_wrap_main(void); 771 | int _mco_switch(_mco_ctxbuf* from, _mco_ctxbuf* to); 772 | 773 | __asm__( 774 | ".text\n" 775 | ".globl _mco_wrap_main\n" 776 | ".type _mco_wrap_main @function\n" 777 | ".hidden _mco_wrap_main\n" 778 | "_mco_wrap_main:\n" 779 | " mv a0, s0\n" 780 | " jr s1\n" 781 | ".size _mco_wrap_main, .-_mco_wrap_main\n" 782 | ); 783 | 784 | __asm__( 785 | ".text\n" 786 | ".globl _mco_switch\n" 787 | ".type _mco_switch @function\n" 788 | ".hidden _mco_switch\n" 789 | "_mco_switch:\n" 790 | #if __riscv_xlen == 64 791 | " sd s0, 0x00(a0)\n" 792 | " sd s1, 0x08(a0)\n" 793 | " sd s2, 0x10(a0)\n" 794 | " sd s3, 0x18(a0)\n" 795 | " sd s4, 0x20(a0)\n" 796 | " sd s5, 0x28(a0)\n" 797 | " sd s6, 0x30(a0)\n" 798 | " sd s7, 0x38(a0)\n" 799 | " sd s8, 0x40(a0)\n" 800 | " sd s9, 0x48(a0)\n" 801 | " sd s10, 0x50(a0)\n" 802 | " sd s11, 0x58(a0)\n" 803 | " sd ra, 0x60(a0)\n" 804 | " sd ra, 0x68(a0)\n" /* pc */ 805 | " sd sp, 0x70(a0)\n" 806 | #ifdef __riscv_flen 807 | #if __riscv_flen == 64 808 | " fsd fs0, 0x78(a0)\n" 809 | " fsd fs1, 0x80(a0)\n" 810 | " fsd fs2, 0x88(a0)\n" 811 | " fsd fs3, 0x90(a0)\n" 812 | " fsd fs4, 0x98(a0)\n" 813 | " fsd fs5, 0xa0(a0)\n" 814 | " fsd fs6, 0xa8(a0)\n" 815 | " fsd fs7, 0xb0(a0)\n" 816 | " fsd fs8, 0xb8(a0)\n" 817 | " fsd fs9, 0xc0(a0)\n" 818 | " fsd fs10, 0xc8(a0)\n" 819 | " fsd fs11, 0xd0(a0)\n" 820 | " fld fs0, 0x78(a1)\n" 821 | " fld fs1, 0x80(a1)\n" 822 | " fld fs2, 0x88(a1)\n" 823 | " fld fs3, 0x90(a1)\n" 824 | " fld fs4, 0x98(a1)\n" 825 | " fld fs5, 0xa0(a1)\n" 826 | " fld fs6, 0xa8(a1)\n" 827 | " fld fs7, 0xb0(a1)\n" 828 | " fld fs8, 0xb8(a1)\n" 829 | " fld fs9, 0xc0(a1)\n" 830 | " fld fs10, 0xc8(a1)\n" 831 | " fld fs11, 0xd0(a1)\n" 832 | #else 833 | #error "Unsupported RISC-V FLEN" 834 | #endif 835 | #endif /* __riscv_flen */ 836 | " ld s0, 0x00(a1)\n" 837 | " ld s1, 0x08(a1)\n" 838 | " ld s2, 0x10(a1)\n" 839 | " ld s3, 0x18(a1)\n" 840 | " ld s4, 0x20(a1)\n" 841 | " ld s5, 0x28(a1)\n" 842 | " ld s6, 0x30(a1)\n" 843 | " ld s7, 0x38(a1)\n" 844 | " ld s8, 0x40(a1)\n" 845 | " ld s9, 0x48(a1)\n" 846 | " ld s10, 0x50(a1)\n" 847 | " ld s11, 0x58(a1)\n" 848 | " ld ra, 0x60(a1)\n" 849 | " ld a2, 0x68(a1)\n" /* pc */ 850 | " ld sp, 0x70(a1)\n" 851 | " jr a2\n" 852 | #elif __riscv_xlen == 32 853 | " sw s0, 0x00(a0)\n" 854 | " sw s1, 0x04(a0)\n" 855 | " sw s2, 0x08(a0)\n" 856 | " sw s3, 0x0c(a0)\n" 857 | " sw s4, 0x10(a0)\n" 858 | " sw s5, 0x14(a0)\n" 859 | " sw s6, 0x18(a0)\n" 860 | " sw s7, 0x1c(a0)\n" 861 | " sw s8, 0x20(a0)\n" 862 | " sw s9, 0x24(a0)\n" 863 | " sw s10, 0x28(a0)\n" 864 | " sw s11, 0x2c(a0)\n" 865 | " sw ra, 0x30(a0)\n" 866 | " sw ra, 0x34(a0)\n" /* pc */ 867 | " sw sp, 0x38(a0)\n" 868 | #ifdef __riscv_flen 869 | #if __riscv_flen == 64 870 | " fsd fs0, 0x3c(a0)\n" 871 | " fsd fs1, 0x44(a0)\n" 872 | " fsd fs2, 0x4c(a0)\n" 873 | " fsd fs3, 0x54(a0)\n" 874 | " fsd fs4, 0x5c(a0)\n" 875 | " fsd fs5, 0x64(a0)\n" 876 | " fsd fs6, 0x6c(a0)\n" 877 | " fsd fs7, 0x74(a0)\n" 878 | " fsd fs8, 0x7c(a0)\n" 879 | " fsd fs9, 0x84(a0)\n" 880 | " fsd fs10, 0x8c(a0)\n" 881 | " fsd fs11, 0x94(a0)\n" 882 | " fld fs0, 0x3c(a1)\n" 883 | " fld fs1, 0x44(a1)\n" 884 | " fld fs2, 0x4c(a1)\n" 885 | " fld fs3, 0x54(a1)\n" 886 | " fld fs4, 0x5c(a1)\n" 887 | " fld fs5, 0x64(a1)\n" 888 | " fld fs6, 0x6c(a1)\n" 889 | " fld fs7, 0x74(a1)\n" 890 | " fld fs8, 0x7c(a1)\n" 891 | " fld fs9, 0x84(a1)\n" 892 | " fld fs10, 0x8c(a1)\n" 893 | " fld fs11, 0x94(a1)\n" 894 | #elif __riscv_flen == 32 895 | " fsw fs0, 0x3c(a0)\n" 896 | " fsw fs1, 0x40(a0)\n" 897 | " fsw fs2, 0x44(a0)\n" 898 | " fsw fs3, 0x48(a0)\n" 899 | " fsw fs4, 0x4c(a0)\n" 900 | " fsw fs5, 0x50(a0)\n" 901 | " fsw fs6, 0x54(a0)\n" 902 | " fsw fs7, 0x58(a0)\n" 903 | " fsw fs8, 0x5c(a0)\n" 904 | " fsw fs9, 0x60(a0)\n" 905 | " fsw fs10, 0x64(a0)\n" 906 | " fsw fs11, 0x68(a0)\n" 907 | " flw fs0, 0x3c(a1)\n" 908 | " flw fs1, 0x40(a1)\n" 909 | " flw fs2, 0x44(a1)\n" 910 | " flw fs3, 0x48(a1)\n" 911 | " flw fs4, 0x4c(a1)\n" 912 | " flw fs5, 0x50(a1)\n" 913 | " flw fs6, 0x54(a1)\n" 914 | " flw fs7, 0x58(a1)\n" 915 | " flw fs8, 0x5c(a1)\n" 916 | " flw fs9, 0x60(a1)\n" 917 | " flw fs10, 0x64(a1)\n" 918 | " flw fs11, 0x68(a1)\n" 919 | #else 920 | #error "Unsupported RISC-V FLEN" 921 | #endif 922 | #endif /* __riscv_flen */ 923 | " lw s0, 0x00(a1)\n" 924 | " lw s1, 0x04(a1)\n" 925 | " lw s2, 0x08(a1)\n" 926 | " lw s3, 0x0c(a1)\n" 927 | " lw s4, 0x10(a1)\n" 928 | " lw s5, 0x14(a1)\n" 929 | " lw s6, 0x18(a1)\n" 930 | " lw s7, 0x1c(a1)\n" 931 | " lw s8, 0x20(a1)\n" 932 | " lw s9, 0x24(a1)\n" 933 | " lw s10, 0x28(a1)\n" 934 | " lw s11, 0x2c(a1)\n" 935 | " lw ra, 0x30(a1)\n" 936 | " lw a2, 0x34(a1)\n" /* pc */ 937 | " lw sp, 0x38(a1)\n" 938 | " jr a2\n" 939 | #else 940 | #error "Unsupported RISC-V XLEN" 941 | #endif /* __riscv_xlen */ 942 | ".size _mco_switch, .-_mco_switch\n" 943 | ); 944 | 945 | static mco_result _mco_makectx(mco_coro* co, _mco_ctxbuf* ctx, void* stack_base, size_t stack_size) { 946 | ctx->s[0] = (void*)(co); 947 | ctx->s[1] = (void*)(_mco_main); 948 | ctx->pc = (void*)(_mco_wrap_main); 949 | #if __riscv_xlen == 64 950 | ctx->ra = (void*)(0xdeaddeaddeaddead); 951 | #elif __riscv_xlen == 32 952 | ctx->ra = (void*)(0xdeaddead); 953 | #endif 954 | ctx->sp = (void*)((size_t)stack_base + stack_size); 955 | return MCO_SUCCESS; 956 | } 957 | 958 | #elif defined(__i386) || defined(__i386__) 959 | 960 | typedef struct _mco_ctxbuf { 961 | void *eip, *esp, *ebp, *ebx, *esi, *edi; 962 | } _mco_ctxbuf; 963 | 964 | void _mco_switch(_mco_ctxbuf* from, _mco_ctxbuf* to); 965 | 966 | __asm__( 967 | #ifdef __DJGPP__ /* DOS compiler */ 968 | "__mco_switch:\n" 969 | #else 970 | ".text\n" 971 | ".globl _mco_switch\n" 972 | ".type _mco_switch @function\n" 973 | ".hidden _mco_switch\n" 974 | "_mco_switch:\n" 975 | #endif 976 | " call 1f\n" 977 | " 1:\n" 978 | " popl %ecx\n" 979 | " addl $(2f-1b), %ecx\n" 980 | " movl 4(%esp), %eax\n" 981 | " movl 8(%esp), %edx\n" 982 | " movl %ecx, (%eax)\n" 983 | " movl %esp, 4(%eax)\n" 984 | " movl %ebp, 8(%eax)\n" 985 | " movl %ebx, 12(%eax)\n" 986 | " movl %esi, 16(%eax)\n" 987 | " movl %edi, 20(%eax)\n" 988 | " movl 20(%edx), %edi\n" 989 | " movl 16(%edx), %esi\n" 990 | " movl 12(%edx), %ebx\n" 991 | " movl 8(%edx), %ebp\n" 992 | " movl 4(%edx), %esp\n" 993 | " jmp *(%edx)\n" 994 | " 2:\n" 995 | " ret\n" 996 | #ifndef __DJGPP__ 997 | ".size _mco_switch, .-_mco_switch\n" 998 | #endif 999 | ); 1000 | 1001 | static mco_result _mco_makectx(mco_coro* co, _mco_ctxbuf* ctx, void* stack_base, size_t stack_size) { 1002 | void** stack_high_ptr = (void**)((size_t)stack_base + stack_size - 16 - 1*sizeof(size_t)); 1003 | stack_high_ptr[0] = (void*)(0xdeaddead); /* Dummy return address. */ 1004 | stack_high_ptr[1] = (void*)(co); 1005 | ctx->eip = (void*)(_mco_main); 1006 | ctx->esp = (void*)(stack_high_ptr); 1007 | return MCO_SUCCESS; 1008 | } 1009 | 1010 | #elif defined(__ARM_EABI__) 1011 | 1012 | typedef struct _mco_ctxbuf { 1013 | #ifndef __SOFTFP__ 1014 | void* f[16]; 1015 | #endif 1016 | void *d[4]; /* d8-d15 */ 1017 | void *r[4]; /* r4-r11 */ 1018 | void *lr; 1019 | void *sp; 1020 | } _mco_ctxbuf; 1021 | 1022 | void _mco_wrap_main(void); 1023 | int _mco_switch(_mco_ctxbuf* from, _mco_ctxbuf* to); 1024 | 1025 | __asm__( 1026 | ".text\n" 1027 | #ifdef __APPLE__ 1028 | ".globl __mco_switch\n" 1029 | "__mco_switch:\n" 1030 | #else 1031 | ".globl _mco_switch\n" 1032 | ".type _mco_switch #function\n" 1033 | ".hidden _mco_switch\n" 1034 | "_mco_switch:\n" 1035 | #endif 1036 | #ifndef __SOFTFP__ 1037 | " vstmia r0!, {d8-d15}\n" 1038 | #endif 1039 | " stmia r0, {r4-r11, lr}\n" 1040 | " str sp, [r0, #9*4]\n" 1041 | #ifndef __SOFTFP__ 1042 | " vldmia r1!, {d8-d15}\n" 1043 | #endif 1044 | " ldr sp, [r1, #9*4]\n" 1045 | " ldmia r1, {r4-r11, pc}\n" 1046 | #ifndef __APPLE__ 1047 | ".size _mco_switch, .-_mco_switch\n" 1048 | #endif 1049 | ); 1050 | 1051 | __asm__( 1052 | ".text\n" 1053 | #ifdef __APPLE__ 1054 | ".globl __mco_wrap_main\n" 1055 | "__mco_wrap_main:\n" 1056 | #else 1057 | ".globl _mco_wrap_main\n" 1058 | ".type _mco_wrap_main #function\n" 1059 | ".hidden _mco_wrap_main\n" 1060 | "_mco_wrap_main:\n" 1061 | #endif 1062 | " mov r0, r4\n" 1063 | " mov ip, r5\n" 1064 | " mov lr, r6\n" 1065 | " bx ip\n" 1066 | #ifndef __APPLE__ 1067 | ".size _mco_wrap_main, .-_mco_wrap_main\n" 1068 | #endif 1069 | ); 1070 | 1071 | static mco_result _mco_makectx(mco_coro* co, _mco_ctxbuf* ctx, void* stack_base, size_t stack_size) { 1072 | ctx->d[0] = (void*)(co); 1073 | ctx->d[1] = (void*)(_mco_main); 1074 | ctx->d[2] = (void*)(0xdeaddead); /* Dummy return address. */ 1075 | ctx->lr = (void*)(_mco_wrap_main); 1076 | ctx->sp = (void*)((size_t)stack_base + stack_size); 1077 | return MCO_SUCCESS; 1078 | } 1079 | 1080 | #elif defined(__aarch64__) 1081 | 1082 | typedef struct _mco_ctxbuf { 1083 | void *x[12]; /* x19-x30 */ 1084 | void *sp; 1085 | void *lr; 1086 | void *d[8]; /* d8-d15 */ 1087 | } _mco_ctxbuf; 1088 | 1089 | void _mco_wrap_main(void); 1090 | int _mco_switch(_mco_ctxbuf* from, _mco_ctxbuf* to); 1091 | 1092 | __asm__( 1093 | ".text\n" 1094 | #ifdef __APPLE__ 1095 | ".globl __mco_switch\n" 1096 | "__mco_switch:\n" 1097 | #else 1098 | ".globl _mco_switch\n" 1099 | ".type _mco_switch #function\n" 1100 | ".hidden _mco_switch\n" 1101 | "_mco_switch:\n" 1102 | #endif 1103 | 1104 | " mov x10, sp\n" 1105 | " mov x11, x30\n" 1106 | " stp x19, x20, [x0, #(0*16)]\n" 1107 | " stp x21, x22, [x0, #(1*16)]\n" 1108 | " stp d8, d9, [x0, #(7*16)]\n" 1109 | " stp x23, x24, [x0, #(2*16)]\n" 1110 | " stp d10, d11, [x0, #(8*16)]\n" 1111 | " stp x25, x26, [x0, #(3*16)]\n" 1112 | " stp d12, d13, [x0, #(9*16)]\n" 1113 | " stp x27, x28, [x0, #(4*16)]\n" 1114 | " stp d14, d15, [x0, #(10*16)]\n" 1115 | " stp x29, x30, [x0, #(5*16)]\n" 1116 | " stp x10, x11, [x0, #(6*16)]\n" 1117 | " ldp x19, x20, [x1, #(0*16)]\n" 1118 | " ldp x21, x22, [x1, #(1*16)]\n" 1119 | " ldp d8, d9, [x1, #(7*16)]\n" 1120 | " ldp x23, x24, [x1, #(2*16)]\n" 1121 | " ldp d10, d11, [x1, #(8*16)]\n" 1122 | " ldp x25, x26, [x1, #(3*16)]\n" 1123 | " ldp d12, d13, [x1, #(9*16)]\n" 1124 | " ldp x27, x28, [x1, #(4*16)]\n" 1125 | " ldp d14, d15, [x1, #(10*16)]\n" 1126 | " ldp x29, x30, [x1, #(5*16)]\n" 1127 | " ldp x10, x11, [x1, #(6*16)]\n" 1128 | " mov sp, x10\n" 1129 | " br x11\n" 1130 | #ifndef __APPLE__ 1131 | ".size _mco_switch, .-_mco_switch\n" 1132 | #endif 1133 | ); 1134 | 1135 | __asm__( 1136 | ".text\n" 1137 | #ifdef __APPLE__ 1138 | ".globl __mco_wrap_main\n" 1139 | "__mco_wrap_main:\n" 1140 | #else 1141 | ".globl _mco_wrap_main\n" 1142 | ".type _mco_wrap_main #function\n" 1143 | ".hidden _mco_wrap_main\n" 1144 | "_mco_wrap_main:\n" 1145 | #endif 1146 | " mov x0, x19\n" 1147 | " mov x30, x21\n" 1148 | " br x20\n" 1149 | #ifndef __APPLE__ 1150 | ".size _mco_wrap_main, .-_mco_wrap_main\n" 1151 | #endif 1152 | ); 1153 | 1154 | static mco_result _mco_makectx(mco_coro* co, _mco_ctxbuf* ctx, void* stack_base, size_t stack_size) { 1155 | ctx->x[0] = (void*)(co); 1156 | ctx->x[1] = (void*)(_mco_main); 1157 | ctx->x[2] = (void*)(0xdeaddeaddeaddead); /* Dummy return address. */ 1158 | ctx->sp = (void*)((size_t)stack_base + stack_size); 1159 | ctx->lr = (void*)(_mco_wrap_main); 1160 | return MCO_SUCCESS; 1161 | } 1162 | 1163 | #else 1164 | 1165 | #error "Unsupported architecture for assembly method." 1166 | 1167 | #endif /* ARCH */ 1168 | 1169 | #elif defined(MCO_USE_UCONTEXT) 1170 | 1171 | #include 1172 | 1173 | typedef ucontext_t _mco_ctxbuf; 1174 | 1175 | #if defined(_LP64) || defined(__LP64__) 1176 | static void _mco_wrap_main(unsigned int lo, unsigned int hi) { 1177 | mco_coro* co = (mco_coro*)(((size_t)lo) | (((size_t)hi) << 32)); /* Extract coroutine pointer. */ 1178 | _mco_main(co); 1179 | } 1180 | #else 1181 | static void _mco_wrap_main(unsigned int lo) { 1182 | mco_coro* co = (mco_coro*)((size_t)lo); /* Extract coroutine pointer. */ 1183 | _mco_main(co); 1184 | } 1185 | #endif 1186 | 1187 | static MCO_FORCE_INLINE void _mco_switch(_mco_ctxbuf* from, _mco_ctxbuf* to) { 1188 | int res = swapcontext(from, to); 1189 | _MCO_UNUSED(res); 1190 | MCO_ASSERT(res == 0); 1191 | } 1192 | 1193 | static mco_result _mco_makectx(mco_coro* co, _mco_ctxbuf* ctx, void* stack_base, size_t stack_size) { 1194 | /* Initialize ucontext. */ 1195 | if(getcontext(ctx) != 0) { 1196 | MCO_LOG("failed to get ucontext"); 1197 | return MCO_MAKE_CONTEXT_ERROR; 1198 | } 1199 | ctx->uc_link = NULL; /* We never exit from _mco_wrap_main. */ 1200 | ctx->uc_stack.ss_sp = stack_base; 1201 | ctx->uc_stack.ss_size = stack_size; 1202 | unsigned int lo = (unsigned int)((size_t)co); 1203 | #if defined(_LP64) || defined(__LP64__) 1204 | unsigned int hi = (unsigned int)(((size_t)co)>>32); 1205 | makecontext(ctx, (void (*)(void))_mco_wrap_main, 2, lo, hi); 1206 | #else 1207 | makecontext(ctx, (void (*)(void))_mco_wrap_main, 1, lo); 1208 | #endif 1209 | return MCO_SUCCESS; 1210 | } 1211 | 1212 | #endif /* defined(MCO_USE_UCONTEXT) */ 1213 | 1214 | #ifdef MCO_USE_VALGRIND 1215 | #include 1216 | #endif 1217 | 1218 | typedef struct _mco_context { 1219 | #ifdef MCO_USE_VALGRIND 1220 | unsigned int valgrind_stack_id; 1221 | #endif 1222 | _mco_ctxbuf ctx; 1223 | _mco_ctxbuf back_ctx; 1224 | } _mco_context; 1225 | 1226 | static void _mco_jumpin(mco_coro* co) { 1227 | _mco_context* context = (_mco_context*)co->context; 1228 | _mco_prepare_jumpin(co); 1229 | _mco_switch(&context->back_ctx, &context->ctx); /* Do the context switch. */ 1230 | } 1231 | 1232 | static void _mco_jumpout(mco_coro* co) { 1233 | _mco_context* context = (_mco_context*)co->context; 1234 | _mco_prepare_jumpout(co); 1235 | _mco_switch(&context->ctx, &context->back_ctx); /* Do the context switch. */ 1236 | } 1237 | 1238 | static mco_result _mco_create_context(mco_coro* co, mco_desc* desc) { 1239 | /* Determine the context and stack address. */ 1240 | size_t co_addr = (size_t)co; 1241 | size_t context_addr = _mco_align_forward(co_addr + sizeof(mco_coro), 16); 1242 | size_t storage_addr = _mco_align_forward(context_addr + sizeof(_mco_context), 16); 1243 | size_t stack_addr = _mco_align_forward(storage_addr + desc->storage_size, 16); 1244 | /* Initialize context. */ 1245 | _mco_context* context = (_mco_context*)context_addr; 1246 | memset(context, 0, sizeof(_mco_context)); 1247 | /* Initialize storage. */ 1248 | unsigned char* storage = (unsigned char*)storage_addr; 1249 | memset(storage, 0, desc->storage_size); 1250 | /* Initialize stack. */ 1251 | void *stack_base = (void*)stack_addr; 1252 | size_t stack_size = desc->stack_size; 1253 | #ifdef MCO_ZERO_MEMORY 1254 | memset(stack_base, 0, stack_size); 1255 | #endif 1256 | /* Make the context. */ 1257 | mco_result res = _mco_makectx(co, &context->ctx, stack_base, stack_size); 1258 | if(res != MCO_SUCCESS) { 1259 | return res; 1260 | } 1261 | #ifdef MCO_USE_VALGRIND 1262 | context->valgrind_stack_id = VALGRIND_STACK_REGISTER(stack_addr, stack_addr + stack_size); 1263 | #endif 1264 | co->context = context; 1265 | co->stack_base = stack_base; 1266 | co->stack_size = stack_size; 1267 | co->storage = storage; 1268 | co->storage_size = desc->storage_size; 1269 | return MCO_SUCCESS; 1270 | } 1271 | 1272 | static void _mco_destroy_context(mco_coro* co) { 1273 | #ifdef MCO_USE_VALGRIND 1274 | _mco_context* context = (_mco_context*)co->context; 1275 | if(context && context->valgrind_stack_id != 0) { 1276 | VALGRIND_STACK_DEREGISTER(context->valgrind_stack_id); 1277 | context->valgrind_stack_id = 0; 1278 | } 1279 | #else 1280 | _MCO_UNUSED(co); 1281 | #endif 1282 | } 1283 | 1284 | static MCO_FORCE_INLINE void _mco_init_desc_sizes(mco_desc* desc, size_t stack_size) { 1285 | desc->coro_size = _mco_align_forward(sizeof(mco_coro), 16) + 1286 | _mco_align_forward(sizeof(_mco_context), 16) + 1287 | _mco_align_forward(desc->storage_size, 16) + 1288 | stack_size + 16; 1289 | desc->stack_size = stack_size; /* This is just a hint, it won't be the real one. */ 1290 | } 1291 | 1292 | #endif /* defined(MCO_USE_UCONTEXT) || defined(MCO_USE_ASM) */ 1293 | 1294 | /* ---------------------------------------------------------------------------------------------- */ 1295 | 1296 | #ifdef MCO_USE_FIBERS 1297 | 1298 | #ifdef _WIN32 1299 | 1300 | #ifndef _WIN32_WINNT 1301 | #define _WIN32_WINNT 0x0400 1302 | #endif 1303 | #ifndef WIN32_LEAN_AND_MEAN 1304 | #define WIN32_LEAN_AND_MEAN 1305 | #endif 1306 | #include 1307 | 1308 | typedef struct _mco_context { 1309 | void* fib; 1310 | void* back_fib; 1311 | } _mco_context; 1312 | 1313 | static void _mco_jumpin(mco_coro* co) { 1314 | void *cur_fib = GetCurrentFiber(); 1315 | if(!cur_fib || cur_fib == (void*)0x1e00) { /* See http://blogs.msdn.com/oldnewthing/archive/2004/12/31/344799.aspx */ 1316 | cur_fib = ConvertThreadToFiber(NULL); 1317 | } 1318 | MCO_ASSERT(cur_fib != NULL); 1319 | _mco_context* context = (_mco_context*)co->context; 1320 | context->back_fib = cur_fib; 1321 | _mco_prepare_jumpin(co); 1322 | SwitchToFiber(context->fib); 1323 | } 1324 | 1325 | static void CALLBACK _mco_wrap_main(void* co) { 1326 | _mco_main((mco_coro*)co); 1327 | } 1328 | 1329 | static void _mco_jumpout(mco_coro* co) { 1330 | _mco_context* context = (_mco_context*)co->context; 1331 | void* back_fib = context->back_fib; 1332 | MCO_ASSERT(back_fib != NULL); 1333 | context->back_fib = NULL; 1334 | _mco_prepare_jumpout(co); 1335 | SwitchToFiber(back_fib); 1336 | } 1337 | 1338 | /* Reverse engineered Fiber struct, used to get stack base. */ 1339 | typedef struct _mco_fiber { 1340 | LPVOID param; /* fiber param */ 1341 | void* except; /* saved exception handlers list */ 1342 | void* stack_base; /* top of fiber stack */ 1343 | void* stack_limit; /* fiber stack low-water mark */ 1344 | void* stack_allocation; /* base of the fiber stack allocation */ 1345 | CONTEXT context; /* fiber context */ 1346 | DWORD flags; /* fiber flags */ 1347 | LPFIBER_START_ROUTINE start; /* start routine */ 1348 | void **fls_slots; /* fiber storage slots */ 1349 | } _mco_fiber; 1350 | 1351 | static mco_result _mco_create_context(mco_coro* co, mco_desc* desc) { 1352 | /* Determine the context address. */ 1353 | size_t co_addr = (size_t)co; 1354 | size_t context_addr = _mco_align_forward(co_addr + sizeof(mco_coro), 16); 1355 | size_t storage_addr = _mco_align_forward(context_addr + sizeof(_mco_context), 16); 1356 | /* Initialize context. */ 1357 | _mco_context* context = (_mco_context*)context_addr; 1358 | memset(context, 0, sizeof(_mco_context)); 1359 | /* Initialize storage. */ 1360 | unsigned char* storage = (unsigned char*)storage_addr; 1361 | memset(storage, 0, desc->storage_size); 1362 | /* Create the fiber. */ 1363 | _mco_fiber* fib = (_mco_fiber*)CreateFiberEx(desc->stack_size, desc->stack_size, FIBER_FLAG_FLOAT_SWITCH, _mco_wrap_main, co); 1364 | if(!fib) { 1365 | MCO_LOG("failed to create fiber"); 1366 | return MCO_MAKE_CONTEXT_ERROR; 1367 | } 1368 | context->fib = fib; 1369 | co->context = context; 1370 | co->stack_base = (void*)((size_t)fib->stack_base - desc->stack_size); 1371 | co->stack_size = desc->stack_size; 1372 | co->storage = storage; 1373 | co->storage_size = desc->storage_size; 1374 | return MCO_SUCCESS; 1375 | } 1376 | 1377 | static void _mco_destroy_context(mco_coro* co) { 1378 | _mco_context* context = (_mco_context*)co->context; 1379 | if(context && context->fib) { 1380 | DeleteFiber(context->fib); 1381 | context->fib = NULL; 1382 | } 1383 | } 1384 | 1385 | static MCO_FORCE_INLINE void _mco_init_desc_sizes(mco_desc* desc, size_t stack_size) { 1386 | desc->coro_size = _mco_align_forward(sizeof(mco_coro), 16) + 1387 | _mco_align_forward(sizeof(_mco_context), 16) + 1388 | _mco_align_forward(desc->storage_size, 16) + 1389 | 16; 1390 | desc->stack_size = stack_size; 1391 | } 1392 | 1393 | #elif defined(__EMSCRIPTEN__) 1394 | 1395 | #include 1396 | 1397 | #ifndef MCO_ASYNCFY_STACK_SIZE 1398 | #define MCO_ASYNCFY_STACK_SIZE 16384 1399 | #endif 1400 | 1401 | typedef struct _mco_context { 1402 | emscripten_fiber_t fib; 1403 | emscripten_fiber_t* back_fib; 1404 | } _mco_context; 1405 | 1406 | static emscripten_fiber_t* running_fib = NULL; 1407 | static unsigned char main_asyncify_stack[MCO_ASYNCFY_STACK_SIZE]; 1408 | static emscripten_fiber_t main_fib; 1409 | 1410 | static void _mco_wrap_main(void* co) { 1411 | _mco_main((mco_coro*)co); 1412 | } 1413 | 1414 | static void _mco_jumpin(mco_coro* co) { 1415 | _mco_context* context = (_mco_context*)co->context; 1416 | emscripten_fiber_t* back_fib = running_fib; 1417 | if(!back_fib) { 1418 | back_fib = &main_fib; 1419 | emscripten_fiber_init_from_current_context(back_fib, main_asyncify_stack, MCO_ASYNCFY_STACK_SIZE); 1420 | } 1421 | running_fib = &context->fib; 1422 | context->back_fib = back_fib; 1423 | _mco_prepare_jumpin(co); 1424 | emscripten_fiber_swap(back_fib, &context->fib); /* Do the context switch. */ 1425 | } 1426 | 1427 | static void _mco_jumpout(mco_coro* co) { 1428 | _mco_context* context = (_mco_context*)co->context; 1429 | running_fib = context->back_fib; 1430 | _mco_prepare_jumpout(co); 1431 | emscripten_fiber_swap(&context->fib, context->back_fib); /* Do the context switch. */ 1432 | } 1433 | 1434 | static mco_result _mco_create_context(mco_coro* co, mco_desc* desc) { 1435 | if(emscripten_has_asyncify() != 1) { 1436 | MCO_LOG("failed to create fiber because ASYNCIFY is not enabled"); 1437 | return MCO_MAKE_CONTEXT_ERROR; 1438 | } 1439 | /* Determine the context address. */ 1440 | size_t co_addr = (size_t)co; 1441 | size_t context_addr = _mco_align_forward(co_addr + sizeof(mco_coro), 16); 1442 | size_t storage_addr = _mco_align_forward(context_addr + sizeof(_mco_context), 16); 1443 | size_t stack_addr = _mco_align_forward(storage_addr + desc->storage_size, 16); 1444 | size_t asyncify_stack_addr = _mco_align_forward(stack_addr + desc->stack_size, 16); 1445 | /* Initialize context. */ 1446 | _mco_context* context = (_mco_context*)context_addr; 1447 | memset(context, 0, sizeof(_mco_context)); 1448 | /* Initialize storage. */ 1449 | unsigned char* storage = (unsigned char*)storage_addr; 1450 | memset(storage, 0, desc->storage_size); 1451 | /* Initialize stack. */ 1452 | void *stack_base = (void*)stack_addr; 1453 | size_t stack_size = asyncify_stack_addr - stack_addr; 1454 | void *asyncify_stack_base = (void*)asyncify_stack_addr; 1455 | size_t asyncify_stack_size = co_addr + desc->coro_size - asyncify_stack_addr; 1456 | #ifdef MCO_ZERO_MEMORY 1457 | memset(stack_base, 0, stack_size); 1458 | memset(asyncify_stack_base, 0, asyncify_stack_size); 1459 | #endif 1460 | /* Create the fiber. */ 1461 | emscripten_fiber_init(&context->fib, _mco_wrap_main, co, stack_base, stack_size, asyncify_stack_base, asyncify_stack_size); 1462 | co->context = context; 1463 | co->stack_base = stack_base; 1464 | co->stack_size = stack_size; 1465 | co->storage = storage; 1466 | co->storage_size = desc->storage_size; 1467 | return MCO_SUCCESS; 1468 | } 1469 | 1470 | static void _mco_destroy_context(mco_coro* co) { 1471 | /* Nothing to do. */ 1472 | _MCO_UNUSED(co); 1473 | } 1474 | 1475 | static MCO_FORCE_INLINE void _mco_init_desc_sizes(mco_desc* desc, size_t stack_size) { 1476 | desc->coro_size = _mco_align_forward(sizeof(mco_coro), 16) + 1477 | _mco_align_forward(sizeof(_mco_context), 16) + 1478 | _mco_align_forward(desc->storage_size, 16) + 1479 | _mco_align_forward(stack_size, 16) + 1480 | _mco_align_forward(MCO_ASYNCFY_STACK_SIZE, 16) + 1481 | 16; 1482 | desc->stack_size = stack_size; /* This is just a hint, it won't be the real one. */ 1483 | } 1484 | 1485 | #else 1486 | 1487 | #error "Unsupported architecture for fibers method." 1488 | 1489 | #endif 1490 | 1491 | #endif /* MCO_USE_FIBERS */ 1492 | 1493 | /* ---------------------------------------------------------------------------------------------- */ 1494 | 1495 | #ifdef MCO_USE_ASYNCIFY 1496 | 1497 | typedef struct _asyncify_stack_region { 1498 | void* start; 1499 | void* limit; 1500 | } _asyncify_stack_region; 1501 | 1502 | typedef struct _mco_context { 1503 | int rewind_id; 1504 | _asyncify_stack_region stack_region; 1505 | } _mco_context; 1506 | 1507 | __attribute__((import_module("asyncify"), import_name("start_unwind"))) void _asyncify_start_unwind(void*); 1508 | __attribute__((import_module("asyncify"), import_name("stop_unwind"))) void _asyncify_stop_unwind(); 1509 | __attribute__((import_module("asyncify"), import_name("start_rewind"))) void _asyncify_start_rewind(void*); 1510 | __attribute__((import_module("asyncify"), import_name("stop_rewind"))) void _asyncify_stop_rewind(); 1511 | 1512 | MCO_NO_INLINE void _mco_jumpin(mco_coro* co) { 1513 | _mco_context* context = (_mco_context*)co->context; 1514 | _mco_prepare_jumpin(co); 1515 | if(context->rewind_id > 0) { /* Begin rewinding until last yield point. */ 1516 | _asyncify_start_rewind(&context->stack_region); 1517 | } 1518 | _mco_main(co); /* Run the coroutine function. */ 1519 | _asyncify_stop_unwind(); /* Stop saving coroutine stack. */ 1520 | } 1521 | 1522 | static MCO_NO_INLINE void _mco_finish_jumpout(mco_coro* co, volatile int rewind_id) { 1523 | _mco_context* context = (_mco_context*)co->context; 1524 | int next_rewind_id = context->rewind_id + 1; 1525 | if(rewind_id == next_rewind_id) { /* Begins unwinding the stack (save locals and call stack to rewind later) */ 1526 | _mco_prepare_jumpout(co); 1527 | context->rewind_id = next_rewind_id; 1528 | _asyncify_start_unwind(&context->stack_region); 1529 | } else if(rewind_id == context->rewind_id) { /* Continue from yield point. */ 1530 | _asyncify_stop_rewind(); 1531 | } 1532 | /* Otherwise, we should be rewinding, let it continue... */ 1533 | } 1534 | 1535 | MCO_NO_INLINE void _mco_jumpout(mco_coro* co) { 1536 | _mco_context* context = (_mco_context*)co->context; 1537 | /* 1538 | Save rewind point into a local, that should be restored when rewinding. 1539 | That is "rewind_id != co->rewind_id + 1" may be true when rewinding. 1540 | Use volatile here just to be safe from compiler optimizing this out. 1541 | */ 1542 | volatile int rewind_id = context->rewind_id + 1; 1543 | _mco_finish_jumpout(co, rewind_id); 1544 | } 1545 | 1546 | static mco_result _mco_create_context(mco_coro* co, mco_desc* desc) { 1547 | /* Determine the context address. */ 1548 | size_t co_addr = (size_t)co; 1549 | size_t context_addr = _mco_align_forward(co_addr + sizeof(mco_coro), 16); 1550 | size_t storage_addr = _mco_align_forward(context_addr + sizeof(_mco_context), 16); 1551 | size_t stack_addr = _mco_align_forward(storage_addr + desc->storage_size, 16); 1552 | /* Initialize context. */ 1553 | _mco_context* context = (_mco_context*)context_addr; 1554 | memset(context, 0, sizeof(_mco_context)); 1555 | /* Initialize storage. */ 1556 | unsigned char* storage = (unsigned char*)storage_addr; 1557 | memset(storage, 0, desc->storage_size); 1558 | /* Initialize stack. */ 1559 | void *stack_base = (void*)stack_addr; 1560 | size_t stack_size = desc->stack_size; 1561 | #ifdef MCO_ZERO_MEMORY 1562 | memset(stack_base, 0, stack_size); 1563 | #endif 1564 | context->stack_region.start = stack_base; 1565 | context->stack_region.limit = (void*)((size_t)stack_base + stack_size); 1566 | co->context = context; 1567 | co->stack_base = stack_base; 1568 | co->stack_size = stack_size; 1569 | co->storage = storage; 1570 | co->storage_size = desc->storage_size; 1571 | return MCO_SUCCESS; 1572 | } 1573 | 1574 | static void _mco_destroy_context(mco_coro* co) { 1575 | /* Nothing to do. */ 1576 | _MCO_UNUSED(co); 1577 | } 1578 | 1579 | static MCO_FORCE_INLINE void _mco_init_desc_sizes(mco_desc* desc, size_t stack_size) { 1580 | desc->coro_size = _mco_align_forward(sizeof(mco_coro), 16) + 1581 | _mco_align_forward(sizeof(_mco_context), 16) + 1582 | _mco_align_forward(desc->storage_size, 16) + 1583 | _mco_align_forward(stack_size, 16) + 1584 | 16; 1585 | desc->stack_size = stack_size; /* This is just a hint, it won't be the real one. */ 1586 | } 1587 | 1588 | #endif /* MCO_USE_ASYNCIFY */ 1589 | 1590 | /* ---------------------------------------------------------------------------------------------- */ 1591 | 1592 | mco_desc mco_desc_init(void (*func)(mco_coro* co), size_t stack_size) { 1593 | if(stack_size != 0) { 1594 | /* Stack size should be at least `MCO_MIN_STACK_SIZE`. */ 1595 | if(stack_size < MCO_MIN_STACK_SIZE) { 1596 | stack_size = MCO_MIN_STACK_SIZE; 1597 | } 1598 | } else { 1599 | stack_size = MCO_DEFAULT_STACK_SIZE; 1600 | } 1601 | stack_size = _mco_align_forward(stack_size, 16); /* Stack size should be aligned to 16 bytes. */ 1602 | mco_desc desc; 1603 | memset(&desc, 0, sizeof(mco_desc)); 1604 | #ifndef MCO_NO_DEFAULT_ALLOCATORS 1605 | /* Set default allocators. */ 1606 | desc.malloc_cb = mco_malloc; 1607 | desc.free_cb = mco_free; 1608 | #endif 1609 | desc.func = func; 1610 | desc.storage_size = MCO_DEFAULT_STORAGE_SIZE; 1611 | _mco_init_desc_sizes(&desc, stack_size); 1612 | return desc; 1613 | } 1614 | 1615 | static mco_result _mco_validate_desc(mco_desc* desc) { 1616 | if(!desc) { 1617 | MCO_LOG("coroutine description is NULL"); 1618 | return MCO_INVALID_ARGUMENTS; 1619 | } 1620 | if(!desc->func) { 1621 | MCO_LOG("coroutine function in invalid"); 1622 | return MCO_INVALID_ARGUMENTS; 1623 | } 1624 | if(desc->stack_size < MCO_MIN_STACK_SIZE) { 1625 | MCO_LOG("coroutine stack size is too small"); 1626 | return MCO_INVALID_ARGUMENTS; 1627 | } 1628 | if(desc->coro_size < sizeof(mco_coro)) { 1629 | MCO_LOG("coroutine size is invalid"); 1630 | return MCO_INVALID_ARGUMENTS; 1631 | } 1632 | return MCO_SUCCESS; 1633 | } 1634 | 1635 | mco_result mco_init(mco_coro* co, mco_desc* desc) { 1636 | if(!co) { 1637 | MCO_LOG("attempt to initialize an invalid coroutine"); 1638 | return MCO_INVALID_COROUTINE; 1639 | } 1640 | memset(co, 0, sizeof(mco_coro)); 1641 | /* Validate coroutine description. */ 1642 | mco_result res = _mco_validate_desc(desc); 1643 | if(res != MCO_SUCCESS) 1644 | return res; 1645 | /* Create the coroutine. */ 1646 | res = _mco_create_context(co, desc); 1647 | if(res != MCO_SUCCESS) 1648 | return res; 1649 | co->state = MCO_SUSPENDED; /* We initialize in suspended state. */ 1650 | co->free_cb = desc->free_cb; 1651 | co->allocator_data = desc->allocator_data; 1652 | co->func = desc->func; 1653 | co->user_data = desc->user_data; 1654 | #ifdef _MCO_USE_TSAN 1655 | co->tsan_fiber = __tsan_create_fiber(0); 1656 | #endif 1657 | co->magic_number = MCO_MAGIC_NUMBER; 1658 | return MCO_SUCCESS; 1659 | } 1660 | 1661 | mco_result mco_uninit(mco_coro* co) { 1662 | if(!co) { 1663 | MCO_LOG("attempt to uninitialize an invalid coroutine"); 1664 | return MCO_INVALID_COROUTINE; 1665 | } 1666 | /* Cannot uninitialize while running. */ 1667 | if(!(co->state == MCO_SUSPENDED || co->state == MCO_DEAD)) { 1668 | MCO_LOG("attempt to uninitialize a coroutine that is not dead or suspended"); 1669 | return MCO_INVALID_OPERATION; 1670 | } 1671 | /* The coroutine is now dead and cannot be used anymore. */ 1672 | co->state = MCO_DEAD; 1673 | #ifdef _MCO_USE_TSAN 1674 | if(co->tsan_fiber != NULL) { 1675 | __tsan_destroy_fiber(co->tsan_fiber); 1676 | co->tsan_fiber = NULL; 1677 | } 1678 | #endif 1679 | _mco_destroy_context(co); 1680 | return MCO_SUCCESS; 1681 | } 1682 | 1683 | mco_result mco_create(mco_coro** out_co, mco_desc* desc) { 1684 | /* Validate input. */ 1685 | if(!out_co) { 1686 | MCO_LOG("coroutine output pointer is NULL"); 1687 | return MCO_INVALID_POINTER; 1688 | } 1689 | if(!desc || !desc->malloc_cb || !desc->free_cb) { 1690 | *out_co = NULL; 1691 | MCO_LOG("coroutine allocator description is not set"); 1692 | return MCO_INVALID_ARGUMENTS; 1693 | } 1694 | /* Allocate the coroutine. */ 1695 | mco_coro* co = (mco_coro*)desc->malloc_cb(desc->coro_size, desc->allocator_data); 1696 | if(!co) { 1697 | MCO_LOG("coroutine allocation failed"); 1698 | *out_co = NULL; 1699 | return MCO_OUT_OF_MEMORY; 1700 | } 1701 | /* Initialize the coroutine. */ 1702 | mco_result res = mco_init(co, desc); 1703 | if(res != MCO_SUCCESS) { 1704 | desc->free_cb(co, desc->allocator_data); 1705 | *out_co = NULL; 1706 | return res; 1707 | } 1708 | *out_co = co; 1709 | return MCO_SUCCESS; 1710 | } 1711 | 1712 | mco_result mco_destroy(mco_coro* co) { 1713 | if(!co) { 1714 | MCO_LOG("attempt to destroy an invalid coroutine"); 1715 | return MCO_INVALID_COROUTINE; 1716 | } 1717 | /* Uninitialize the coroutine first. */ 1718 | mco_result res = mco_uninit(co); 1719 | if(res != MCO_SUCCESS) 1720 | return res; 1721 | /* Free the coroutine. */ 1722 | if(!co->free_cb) { 1723 | MCO_LOG("attempt destroy a coroutine that has no free callback"); 1724 | return MCO_INVALID_POINTER; 1725 | } 1726 | co->free_cb(co, co->allocator_data); 1727 | return MCO_SUCCESS; 1728 | } 1729 | 1730 | mco_result mco_resume(mco_coro* co) { 1731 | if(!co) { 1732 | MCO_LOG("attempt to resume an invalid coroutine"); 1733 | return MCO_INVALID_COROUTINE; 1734 | } 1735 | if(co->state != MCO_SUSPENDED) { /* Can only resume coroutines that are suspended. */ 1736 | MCO_LOG("attempt to resume a coroutine that is not suspended"); 1737 | return MCO_NOT_SUSPENDED; 1738 | } 1739 | co->state = MCO_RUNNING; /* The coroutine is now running. */ 1740 | _mco_jumpin(co); 1741 | return MCO_SUCCESS; 1742 | } 1743 | 1744 | mco_result mco_yield(mco_coro* co) { 1745 | if(!co) { 1746 | MCO_LOG("attempt to yield an invalid coroutine"); 1747 | return MCO_INVALID_COROUTINE; 1748 | } 1749 | #ifdef MCO_USE_ASYNCIFY 1750 | /* Asyncify already checks for stack overflow. */ 1751 | #else 1752 | /* This check happens when the stack overflow already happened, but better later than never. */ 1753 | volatile size_t dummy; 1754 | size_t stack_addr = (size_t)&dummy; 1755 | size_t stack_min = (size_t)co->stack_base; 1756 | size_t stack_max = stack_min + co->stack_size; 1757 | if(co->magic_number != MCO_MAGIC_NUMBER || stack_addr < stack_min || stack_addr > stack_max) { /* Stack overflow. */ 1758 | MCO_LOG("coroutine stack overflow, try increasing the stack size"); 1759 | return MCO_STACK_OVERFLOW; 1760 | } 1761 | #endif 1762 | if(co->state != MCO_RUNNING) { /* Can only yield coroutines that are running. */ 1763 | MCO_LOG("attempt to yield a coroutine that is not running"); 1764 | return MCO_NOT_RUNNING; 1765 | } 1766 | co->state = MCO_SUSPENDED; /* The coroutine is now suspended. */ 1767 | _mco_jumpout(co); 1768 | return MCO_SUCCESS; 1769 | } 1770 | 1771 | mco_state mco_status(mco_coro* co) { 1772 | if(co != NULL) { 1773 | return co->state; 1774 | } 1775 | return MCO_DEAD; 1776 | } 1777 | 1778 | void* mco_get_user_data(mco_coro* co) { 1779 | if(co != NULL) { 1780 | return co->user_data; 1781 | } 1782 | return NULL; 1783 | } 1784 | 1785 | mco_result mco_push(mco_coro* co, const void* src, size_t len) { 1786 | if(!co) { 1787 | MCO_LOG("attempt to use an invalid coroutine"); 1788 | return MCO_INVALID_COROUTINE; 1789 | } else if(len > 0) { 1790 | size_t bytes_stored = co->bytes_stored + len; 1791 | if(bytes_stored > co->storage_size) { 1792 | MCO_LOG("attempt to push too many bytes into coroutine storage"); 1793 | return MCO_NOT_ENOUGH_SPACE; 1794 | } 1795 | if(!src) { 1796 | MCO_LOG("attempt push a null pointer into coroutine storage"); 1797 | return MCO_INVALID_POINTER; 1798 | } 1799 | memcpy(&co->storage[co->bytes_stored], src, len); 1800 | co->bytes_stored = bytes_stored; 1801 | } 1802 | return MCO_SUCCESS; 1803 | } 1804 | 1805 | mco_result mco_pop(mco_coro* co, void* dest, size_t len) { 1806 | if(!co) { 1807 | MCO_LOG("attempt to use an invalid coroutine"); 1808 | return MCO_INVALID_COROUTINE; 1809 | } else if(len > 0) { 1810 | if(len > co->bytes_stored) { 1811 | MCO_LOG("attempt to pop too many bytes from coroutine storage"); 1812 | return MCO_NOT_ENOUGH_SPACE; 1813 | } 1814 | size_t bytes_stored = co->bytes_stored - len; 1815 | if(dest) { 1816 | memcpy(dest, &co->storage[bytes_stored], len); 1817 | } 1818 | co->bytes_stored = bytes_stored; 1819 | #ifdef MCO_ZERO_MEMORY 1820 | /* Clear garbage in the discarded storage. */ 1821 | memset(&co->storage[bytes_stored], 0, len); 1822 | #endif 1823 | } 1824 | return MCO_SUCCESS; 1825 | } 1826 | 1827 | mco_result mco_peek(mco_coro* co, void* dest, size_t len) { 1828 | if(!co) { 1829 | MCO_LOG("attempt to use an invalid coroutine"); 1830 | return MCO_INVALID_COROUTINE; 1831 | } else if(len > 0) { 1832 | if(len > co->bytes_stored) { 1833 | MCO_LOG("attempt to peek too many bytes from coroutine storage"); 1834 | return MCO_NOT_ENOUGH_SPACE; 1835 | } 1836 | if(!dest) { 1837 | MCO_LOG("attempt peek into a null pointer"); 1838 | return MCO_INVALID_POINTER; 1839 | } 1840 | memcpy(dest, &co->storage[co->bytes_stored - len], len); 1841 | } 1842 | return MCO_SUCCESS; 1843 | } 1844 | 1845 | size_t mco_get_bytes_stored(mco_coro* co) { 1846 | if(co == NULL) { 1847 | return 0; 1848 | } 1849 | return co->bytes_stored; 1850 | } 1851 | 1852 | size_t mco_get_storage_size(mco_coro* co) { 1853 | if(co == NULL) { 1854 | return 0; 1855 | } 1856 | return co->storage_size; 1857 | } 1858 | 1859 | #ifdef MCO_NO_MULTITHREAD 1860 | mco_coro* mco_running(void) { 1861 | return mco_current_co; 1862 | } 1863 | #else 1864 | static MCO_NO_INLINE mco_coro* _mco_running(void) { 1865 | return mco_current_co; 1866 | } 1867 | mco_coro* mco_running(void) { 1868 | /* 1869 | Compilers aggressively optimize the use of TLS by caching loads. 1870 | Since fiber code can migrate between threads it’s possible for the load to be stale. 1871 | To prevent this from happening we avoid inline functions. 1872 | */ 1873 | mco_coro* (*volatile func)(void) = _mco_running; 1874 | return func(); 1875 | } 1876 | #endif 1877 | 1878 | const char* mco_result_description(mco_result res) { 1879 | switch(res) { 1880 | case MCO_SUCCESS: 1881 | return "No error"; 1882 | case MCO_GENERIC_ERROR: 1883 | return "Generic error"; 1884 | case MCO_INVALID_POINTER: 1885 | return "Invalid pointer"; 1886 | case MCO_INVALID_COROUTINE: 1887 | return "Invalid coroutine"; 1888 | case MCO_NOT_SUSPENDED: 1889 | return "Coroutine not suspended"; 1890 | case MCO_NOT_RUNNING: 1891 | return "Coroutine not running"; 1892 | case MCO_MAKE_CONTEXT_ERROR: 1893 | return "Make context error"; 1894 | case MCO_SWITCH_CONTEXT_ERROR: 1895 | return "Switch context error"; 1896 | case MCO_NOT_ENOUGH_SPACE: 1897 | return "Not enough space"; 1898 | case MCO_OUT_OF_MEMORY: 1899 | return "Out of memory"; 1900 | case MCO_INVALID_ARGUMENTS: 1901 | return "Invalid arguments"; 1902 | case MCO_INVALID_OPERATION: 1903 | return "Invalid operation"; 1904 | case MCO_STACK_OVERFLOW: 1905 | return "Stack overflow"; 1906 | } 1907 | return "Unknown error"; 1908 | } 1909 | 1910 | #ifdef __cplusplus 1911 | } 1912 | #endif 1913 | 1914 | #endif /* MINICORO_IMPL */ 1915 | 1916 | /* 1917 | This software is available as a choice of the following licenses. Choose 1918 | whichever you prefer. 1919 | 1920 | =============================================================================== 1921 | ALTERNATIVE 1 - Public Domain (www.unlicense.org) 1922 | =============================================================================== 1923 | This is free and unencumbered software released into the public domain. 1924 | 1925 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this 1926 | software, either in source code form or as a compiled binary, for any purpose, 1927 | commercial or non-commercial, and by any means. 1928 | 1929 | In jurisdictions that recognize copyright laws, the author or authors of this 1930 | software dedicate any and all copyright interest in the software to the public 1931 | domain. We make this dedication for the benefit of the public at large and to 1932 | the detriment of our heirs and successors. We intend this dedication to be an 1933 | overt act of relinquishment in perpetuity of all present and future rights to 1934 | this software under copyright law. 1935 | 1936 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 1937 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 1938 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 1939 | AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 1940 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 1941 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 1942 | 1943 | For more information, please refer to 1944 | 1945 | =============================================================================== 1946 | ALTERNATIVE 2 - MIT No Attribution 1947 | =============================================================================== 1948 | Copyright (c) 2021-2022 Eduardo Bart (https://github.com/edubart/minicoro) 1949 | 1950 | Permission is hereby granted, free of charge, to any person obtaining a copy of 1951 | this software and associated documentation files (the "Software"), to deal in 1952 | the Software without restriction, including without limitation the rights to 1953 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 1954 | of the Software, and to permit persons to whom the Software is furnished to do 1955 | so. 1956 | 1957 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 1958 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 1959 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 1960 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 1961 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 1962 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 1963 | SOFTWARE. 1964 | */ 1965 | -------------------------------------------------------------------------------- /include/simple.c: -------------------------------------------------------------------------------- 1 | #define MINICORO_IMPL 2 | #include "minicoro.h" 3 | #include 4 | 5 | // Coroutine entry function. 6 | void coro_entry(mco_coro* co) { 7 | printf("coroutine 1\n"); 8 | mco_yield(co); 9 | printf("coroutine 2\n"); 10 | } 11 | 12 | int main() { 13 | // First initialize a `desc` object through `mco_desc_init`. 14 | mco_desc desc = mco_desc_init(coro_entry, 0); 15 | // Configure `desc` fields when needed (e.g. customize user_data or allocation functions). 16 | desc.user_data = NULL; 17 | // Call `mco_create` with the output coroutine pointer and `desc` pointer. 18 | mco_coro* co; 19 | mco_result res = mco_create(&co, &desc); 20 | assert(res == MCO_SUCCESS); 21 | // The coroutine should be now in suspended state. 22 | assert(mco_status(co) == MCO_SUSPENDED); 23 | // Call `mco_resume` to start for the first time, switching to its context. 24 | res = mco_resume(co); // Should print "coroutine 1". 25 | assert(res == MCO_SUCCESS); 26 | // We get back from coroutine context in suspended state (because it's unfinished). 27 | assert(mco_status(co) == MCO_SUSPENDED); 28 | // Call `mco_resume` to resume for a second time. 29 | res = mco_resume(co); // Should print "coroutine 2". 30 | assert(res == MCO_SUCCESS); 31 | // The coroutine finished and should be now dead. 32 | assert(mco_status(co) == MCO_DEAD); 33 | // Call `mco_destroy` to destroy the coroutine. 34 | res = mco_destroy(co); 35 | assert(res == MCO_SUCCESS); 36 | return 0; 37 | } 38 | -------------------------------------------------------------------------------- /minicoro.c.v: -------------------------------------------------------------------------------- 1 | /* 2 | * minicoro: Wrapper for minicoro.h 3 | * 4 | * Copyright (c) 2022 Steven 'lazalong' Gay 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a 7 | * copy of this software and associated documentation files (the "Software"), 8 | * to deal in the Software without restriction, including without limitation 9 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | * and/or sell copies of the Software, and to permit persons to whom the 11 | * Software is furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | * IN THE SOFTWARE. 23 | */ 24 | module minicoro 25 | 26 | $if debug { 27 | #define MCO_DEBUG 28 | } 29 | 30 | #define MINICORO_IMPL 31 | #flag -I @VMODROOT/include 32 | #include "minicoro.h" 33 | 34 | [callconv: 'fastcall'] 35 | pub type FuncCoro = fn (co &Coro) 36 | 37 | [callconv: 'fastcall'] 38 | pub type FreeCB = fn (ptr voidptr, allocator_data voidptr) 39 | 40 | [callconv: 'fastcall'] 41 | pub type MallocCB = fn (size usize, allocator_data voidptr) voidptr // [void* (*malloc_cb)(size_t size, void* allocator_data);] 42 | 43 | // CONSTANTS // 44 | pub const ( 45 | minicoro_v_version = '0.0.3' 46 | minicoro_c_version = '0.1.3' 47 | ) 48 | 49 | pub const ( 50 | default_stack_size = C.MCO_DEFAULT_STACK_SIZE 51 | min_stack_size = C.MCO_MIN_STACK_SIZE 52 | ) 53 | 54 | // State represents all coroutine states. 55 | pub enum State { 56 | dead = 0 // The coroutine has finished normally or was uninitialized before finishing. 57 | normal // The coroutine is active but not running (that is, it has resumed another coroutine). 58 | running // The coroutine is active and running. 59 | suspended // The coroutine is suspended (in a call to yield, or it has not started running yet). 60 | } 61 | 62 | // Result holds coroutine result codes. 63 | pub enum Result { 64 | success = 0 65 | generic_error 66 | invalid_pointer 67 | invlid_coroutine 68 | not_suspended 69 | not_running 70 | make_context_error 71 | switch_context_error 72 | not_enough_space 73 | out_of_memory 74 | invalid_arguments 75 | invalid_operation 76 | stack_overflow 77 | } 78 | 79 | [typedef] 80 | struct C.mco_coro { 81 | pub mut: 82 | context voidptr 83 | state State 84 | func FuncCoro // [void (*func)(mco_coro* co);] 85 | prev_co &Coro = 0 86 | user_data voidptr 87 | allocator_data voidptr 88 | free_cb FreeCB // [void (*free_cb)(void* ptr, void* allocator_data);] 89 | stack_base voidptr // Stack base address, can be used to scan memory in a garbage collector. 90 | stack_size usize 91 | storage &byte = 0 92 | bytes_stored usize 93 | storage_size usize 94 | asan_prev_stack voidptr // Used by address sanitizer. 95 | tsan_prev_fiber voidptr // Used by thread sanitizer. 96 | tsan_fiber voidptr // Used by thread sanitizer. 97 | magic_number usize // Used to check stack overflow. 98 | } 99 | 100 | // Coro is the coroutine structure. 101 | pub type Coro = C.mco_coro 102 | 103 | [typedef] 104 | struct C.mco_desc { 105 | pub mut: 106 | func FuncCoro // Entry point function for the coroutine. [void (*func)(mco_coro* co);] 107 | user_data voidptr // Coroutine user data, can be get with `mco_get_user_data`. 108 | // Custom allocation interface. 109 | malloc_cb MallocCB // Custom allocation function. [void* (*malloc_cb)(size_t size, void* allocator_data);] 110 | free_cb FreeCB // Custom deallocation function. [void (*free_cb)(void* ptr, void* allocator_data);] 111 | allocator_data voidptr // User data pointer passed to `malloc`/`free` allocation functions. 112 | storage_size usize // Coroutine storage size, to be used with the storage APIs. 113 | // These must be initialized only through `mco_init_desc`. 114 | coro_size usize // Coroutine structure size. 115 | stack_size usize // Coroutine stack size. 116 | } 117 | 118 | // Desc is a structure used to initialize a coroutine. 119 | pub type Desc = C.mco_desc 120 | 121 | // FUNCTIONS // 122 | // Coroutine functions. 123 | fn C.mco_desc_init(co FuncCoro, stack_size usize) Desc 124 | 125 | // desc_init initializes a description of a coroutine. 126 | // When `stack_size` is 0 then `default_stack_size` is used. 127 | pub fn desc_init(co FuncCoro, stack_size usize) Desc { 128 | return C.mco_desc_init(co, stack_size) 129 | } 130 | 131 | fn C.mco_init(co &Coro, desc &Desc) Result 132 | 133 | // init initializes the coroutine. 134 | pub fn init(co &Coro, desc &Desc) Result { 135 | return C.mco_init(co, desc) 136 | } 137 | 138 | fn C.mco_uninit(co &Coro) Result 139 | 140 | // uninit uninitializes the coroutine `co`, 141 | // may fail if it's not dead or suspended. 142 | pub fn uninit(co &Coro) Result { 143 | return C.mco_uninit(co) 144 | } 145 | 146 | fn C.mco_create(out_co &&Coro, desc &Desc) Result 147 | 148 | // create allocates and initializes a new coroutine. 149 | pub fn create(out_co &&Coro, desc &Desc) Result { 150 | return C.mco_create(out_co, desc) 151 | } 152 | 153 | fn C.mco_destroy(co &Coro) Result 154 | 155 | // destroy uninitializes and deallocates the coroutine, 156 | // the function may fail if it's not dead or suspended. 157 | pub fn destroy(co &Coro) Result { 158 | return C.mco_destroy(co) 159 | } 160 | 161 | fn C.mco_resume(co &Coro) Result 162 | 163 | // resume starts or continues the execution of the coroutine. 164 | pub fn resume(co &Coro) Result { 165 | return C.mco_resume(co) 166 | } 167 | 168 | fn C.mco_yield(co &Coro) Result 169 | 170 | // yield suspends (yields) the execution of a coroutine. 171 | pub fn yield(co &Coro) Result { 172 | return C.mco_yield(co) 173 | } 174 | 175 | fn C.mco_status(co &Coro) State 176 | 177 | // status returns the status of the coroutine. 178 | pub fn status(co &Coro) State { 179 | return C.mco_status(co) 180 | } 181 | 182 | fn C.mco_get_user_data(co &Coro) voidptr 183 | 184 | // get_user_data gets coroutine user data supplied on coroutine creation. 185 | pub fn get_user_data(co &Coro) voidptr { 186 | return C.mco_get_user_data(co) 187 | } 188 | 189 | // Storage interface functions, used to pass values between yield and resume. 190 | fn C.mco_push(co &Coro, src voidptr, len usize) Result 191 | 192 | // push pushes bytes to the coroutine storage. 193 | // Use to send values between yield and resume. 194 | pub fn push(co &Coro, src voidptr, len usize) Result { 195 | return C.mco_push(co, src, len) 196 | } 197 | 198 | fn C.mco_pop(co &Coro, dest voidptr, len usize) Result 199 | 200 | // pop pops bytes from the coroutine storage. 201 | // Use to get values between yield and resume. 202 | pub fn pop(co &Coro, dest voidptr, len usize) Result { 203 | return C.mco_pop(co, dest, len) 204 | } 205 | 206 | fn C.mco_peek(co &Coro, dest voidptr, len usize) Result 207 | 208 | // peek works like `pop` but it does not consume the storage. 209 | pub fn peek(co &Coro, dest voidptr, len usize) Result { 210 | return C.mco_peek(co, dest, len) 211 | } 212 | 213 | fn C.mco_get_bytes_stored(co &Coro) usize 214 | 215 | // get_bytes_stored gets the available bytes that can be retrieved with a `pop`. 216 | pub fn get_bytes_stored(co &Coro) usize { 217 | return C.mco_get_bytes_stored(co) 218 | } 219 | 220 | fn C.mco_get_storage_size(co &Coro) usize 221 | 222 | // get_storage_size gets the total storage size. 223 | pub fn get_storage_size(co &Coro) usize { 224 | return C.mco_get_storage_size(co) 225 | } 226 | 227 | // Misc functions. 228 | fn C.mco_running() &Coro 229 | 230 | // running returns the running coroutine for the current thread. 231 | pub fn running() &Coro { 232 | return C.mco_running() 233 | } 234 | 235 | fn C.mco_result_description(res Result) &char 236 | 237 | // result_description gets the description of a result. 238 | pub fn result_description(res Result) &char { 239 | return C.mco_result_description(res) 240 | } 241 | -------------------------------------------------------------------------------- /minicoro_test.v: -------------------------------------------------------------------------------- 1 | module minicoro 2 | 3 | [callconv: 'fastcall'] 4 | pub fn coro_entry(co &C.mco_coro) { 5 | // println("Coroutine 1") 6 | C.mco_yield(co) 7 | // println("Coroutine 2") 8 | } 9 | 10 | [console] 11 | fn test_simple() { 12 | // First initialize a `desc` object through `mco_desc_init`. 13 | fct := voidptr(&coro_entry) 14 | mut desc := C.mco_desc_init(fct, 0) 15 | 16 | // Configure `desc` fields when needed (e.g. customize user_data or allocation functions). 17 | desc.user_data = voidptr(0) 18 | mut co := &Coro{} 19 | 20 | // Call `mco_create` with the output coroutine pointer and `desc` pointer. 21 | mut res := C.mco_create(&co, &desc) 22 | assert res == .success 23 | // println(res) 24 | 25 | // The coroutine should be now in suspended state. 26 | assert C.mco_status(co) == .suspended 27 | 28 | // Call `mco_resume` to start for the first time, switching to its context. 29 | res = C.mco_resume(co) // Should print "coroutine 1". 30 | assert res == .success 31 | 32 | // We get back from coroutine context in suspended state 33 | // because the coro_entry method yields after the first print 34 | assert C.mco_status(co) == .suspended 35 | 36 | // Call `mco_resume` to resume for a second time. 37 | res = C.mco_resume(co) // Should print "coroutine 2". 38 | assert res == .success 39 | 40 | // The coroutine finished and should be now dead. 41 | assert C.mco_status(co) == .dead 42 | 43 | // Call `mco_destroy` to destroy the coroutine. 44 | res = C.mco_destroy(co) 45 | assert res == .success 46 | } 47 | -------------------------------------------------------------------------------- /minicoro_v_api_test.v: -------------------------------------------------------------------------------- 1 | module minicoro 2 | 3 | [callconv: 'fastcall'] 4 | pub fn coro_entry(co &Coro) { 5 | yield(co) 6 | } 7 | 8 | [console] 9 | fn test_simple() { 10 | // First initialize a `desc` object through `mco_desc_init`. 11 | fct := voidptr(&coro_entry) 12 | mut desc := desc_init(fct, 0) 13 | 14 | // Configure `desc` fields when needed (e.g. customize user_data or allocation functions). 15 | desc.user_data = voidptr(0) 16 | mut co := &Coro{} 17 | 18 | // Call `mco_create` with the output coroutine pointer and `desc` pointer. 19 | mut res := create(&co, &desc) 20 | assert res == .success 21 | // println(res) 22 | 23 | // The coroutine should be now in suspended state. 24 | assert status(co) == .suspended 25 | 26 | // Call `mco_resume` to start for the first time, switching to its context. 27 | res = resume(co) // Should print "coroutine 1". 28 | assert res == .success 29 | 30 | // We get back from coroutine context in suspended state 31 | // because the coro_entry method yields after the first print 32 | assert status(co) == .suspended 33 | 34 | // Call `mco_resume` to resume for a second time. 35 | res = resume(co) // Should print "coroutine 2". 36 | assert res == .success 37 | 38 | // The coroutine finished and should be now dead. 39 | assert status(co) == .dead 40 | 41 | // Call `mco_destroy` to destroy the coroutine. 42 | res = destroy(co) 43 | assert res == .success 44 | } 45 | -------------------------------------------------------------------------------- /v.mod: -------------------------------------------------------------------------------- 1 | Module { 2 | name: 'minicoro' 3 | description: 'A minicoro wrapper written in V' 4 | author: 'lazalong' 5 | version: '0.0.3' 6 | license: 'MIT' 7 | dependencies: [] 8 | } 9 | --------------------------------------------------------------------------------