├── .gitignore ├── .travis.yml ├── LICENCE ├── README.md ├── appveyor.yml ├── bam.lua ├── coro.h ├── example └── dialog_example.cpp └── test ├── greatest.h └── test_coro.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | .bam 2 | .cproject 3 | .project 4 | local 5 | 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | os: 3 | - linux 4 | - osx 5 | 6 | sudo: false 7 | 8 | compiler: 9 | - gcc 10 | - clang 11 | 12 | install: 13 | - git clone https://github.com/matricks/bam.git 14 | - cd bam 15 | - ./make_unix.sh 16 | - cd .. 17 | 18 | script: 19 | - bam/bam compiler=$CC config=debug 20 | - bam/bam compiler=$CC config=release 21 | - ./local/debug/linux_x86_64/$CC/coro_tests 22 | - ./local/release/linux_x86_64/$CC/coro_tests 23 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Header implementing "protothreads" but with a stack to support 2 | local-varible state, argument-passing and sub-coroutines. 3 | 4 | version 1.0, november, 2018 5 | 6 | Copyright (C) 2018- Fredrik Kihlander 7 | 8 | https://github.com/wc-duck/coro 9 | 10 | This software is provided 'as-is', without any express or implied 11 | warranty. In no event will the authors be held liable for any damages 12 | arising from the use of this software. 13 | 14 | Permission is granted to anyone to use this software for any purpose, 15 | including commercial applications, and to alter it and redistribute it 16 | freely, subject to the following restrictions: 17 | 18 | 1. The origin of this software must not be misrepresented; you must not 19 | claim that you wrote the original software. If you use this software 20 | in a product, an acknowledgment in the product documentation would be 21 | appreciated but is not required. 22 | 2. Altered source versions must be plainly marked as such, and must not be 23 | misrepresented as being the original software. 24 | 3. This notice may not be removed or altered from any source distribution. 25 | 26 | Fredrik Kihlander -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/wc-duck/coro.svg?branch=master)](https://travis-ci.org/wc-duck/coro) 2 | [![Build status](https://ci.appveyor.com/api/projects/status/8a8cmy36xv0qp78x/branch/master?svg=true)](https://ci.appveyor.com/project/wc-duck/coro/branch/master) 3 | 4 | This is a small header/library implementing coroutines/protothreads/"yieldable functions" 5 | or whatever you want to call it. 6 | 7 | Initial idea comest from here: 8 | [coroutines in c](https://www.chiark.greenend.org.uk/~sgtatham/coroutines.html) 9 | 10 | For more in depth description see implementation or this [blogpost](https://kihlander.net/post/protothreads-with-a-twist/)! 11 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | platform: 2 | - x86 3 | 4 | install: 5 | - git clone https://github.com/matricks/bam.git 6 | - cd bam 7 | - make_win64_msvc.bat 8 | - cd .. 9 | - bam\bam.exe config=debug 10 | - bam\bam.exe config=release 11 | - local\debug\winx64\msvc\coro_tests.exe 12 | - local\release\winx64\msvc\coro_tests.exe 13 | 14 | build: off 15 | 16 | -------------------------------------------------------------------------------- /bam.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Header implementing "protothreads" but with a stack to support 3 | local-varible state, argument-passing and sub-coroutines. 4 | 5 | version 1.0, november, 2018 6 | 7 | Copyright (C) 2018- Fredrik Kihlander 8 | 9 | https://github.com/wc-duck/coro 10 | 11 | This software is provided 'as-is', without any express or implied 12 | warranty. In no event will the authors be held liable for any damages 13 | arising from the use of this software. 14 | 15 | Permission is granted to anyone to use this software for any purpose, 16 | including commercial applications, and to alter it and redistribute it 17 | freely, subject to the following restrictions: 18 | 19 | 1. The origin of this software must not be misrepresented; you must not 20 | claim that you wrote the original software. If you use this software 21 | in a product, an acknowledgment in the product documentation would be 22 | appreciated but is not required. 23 | 2. Altered source versions must be plainly marked as such, and must not be 24 | misrepresented as being the original software. 25 | 3. This notice may not be removed or altered from any source distribution. 26 | 27 | Fredrik Kihlander 28 | --]] 29 | 30 | BUILD_PATH = "local" 31 | 32 | function get_config() 33 | local config = ScriptArgs["config"] 34 | if config == nil then 35 | return "debug" 36 | end 37 | return config 38 | end 39 | 40 | function get_platform() 41 | local platform = ScriptArgs["platform"] 42 | if platform == nil then 43 | if family == "windows" then 44 | platform = "winx64" 45 | else 46 | platform = "linux_x86_64" 47 | end 48 | end 49 | return platform 50 | end 51 | 52 | function get_compiler() 53 | if family == "windows" then 54 | return "msvc" 55 | end 56 | 57 | local compiler = ScriptArgs["compiler"] 58 | if compiler ~= nil then 59 | return compiler 60 | end 61 | 62 | return "gcc" 63 | end 64 | 65 | function get_base_settings() 66 | local settings = {} 67 | 68 | settings._is_settingsobject = true 69 | settings.invoke_count = 0 70 | settings.debug = 0 71 | settings.optimize = 0 72 | SetCommonSettings(settings) 73 | 74 | -- add all tools 75 | for _, tool in pairs(_bam_tools) do 76 | tool(settings) 77 | end 78 | 79 | return settings 80 | end 81 | 82 | function set_compiler( settings, config, compiler ) 83 | InitCommonCCompiler(settings) 84 | if compiler == "msvc" then 85 | SetDriversCL( settings ) 86 | if config == "release" then 87 | settings.cc.flags:Add( "/Ox" ) 88 | settings.cc.flags:Add( "/TP" ) -- forcing c++ compile on windows =/ 89 | end 90 | end 91 | 92 | if compiler == "gcc" or compiler == "clang" then 93 | if compiler == "gcc" then 94 | SetDriversGCC( settings ) 95 | else 96 | SetDriversClang( settings ) 97 | end 98 | settings.cc.flags:Add( "-std=c++11", "-Wconversion", "-Wextra", "-Wall", "-Werror", "-Wstrict-aliasing=2" ) 99 | if config == "release" then 100 | settings.cc.flags:Add( "-O2" ) 101 | end 102 | end 103 | end 104 | 105 | config = get_config() 106 | platform = get_platform() 107 | compiler = get_compiler() 108 | settings = get_base_settings() 109 | set_compiler( settings, config, compiler ) 110 | TableLock( settings ) 111 | 112 | local output_path = PathJoin( BUILD_PATH, PathJoin( PathJoin( config, platform ), compiler ) ) 113 | local output_func = function(settings, path) return PathJoin(output_path, PathFilename(PathBase(path)) .. settings.config_ext) end 114 | settings.cc.Output = output_func 115 | settings.lib.Output = output_func 116 | settings.link.Output = output_func 117 | 118 | settings.link.libpath:Add( 'local/' .. config .. '/' .. platform ) 119 | local tests = Link( settings, 'coro_tests', Compile( settings, 'test/test_coro.cpp' ) ) 120 | 121 | -- examples 122 | local examples = {} 123 | for _, file in ipairs(Collect( "example/*.cpp" )) do 124 | local name = PathFilename(PathBase(file)) 125 | local exe = Link( settings, name, Compile( settings, file ) ) 126 | table.insert( examples, exe ) 127 | end 128 | 129 | test_args = " -v" 130 | if ScriptArgs["test"] then test_args = test_args .. " -t " .. ScriptArgs["test"] end 131 | if ScriptArgs["suite"] then test_args = test_args .. " -s " .. ScriptArgs["suite"] end 132 | 133 | if family == "windows" then 134 | AddJob( "test", "unittest", string.gsub( tests, "/", "\\" ) .. test_args, tests, tests ) 135 | else 136 | AddJob( "test", "unittest", tests .. test_args, tests, tests ) 137 | AddJob( "valgrind", "valgrind", "valgrind -v --leak-check=full --track-origins=yes " .. tests .. test_args, tests, tests ) 138 | end 139 | 140 | PseudoTarget( "examples", examples ) 141 | PseudoTarget( "all", tests ) 142 | DefaultTarget( "all" ) 143 | 144 | -------------------------------------------------------------------------------- /coro.h: -------------------------------------------------------------------------------- 1 | /* 2 | Header implementing "protothreads" but with a stack to support 3 | local-varible state, argument-passing and sub-coroutines. 4 | 5 | version 1.0, november, 2018 6 | 7 | Copyright (C) 2018- Fredrik Kihlander 8 | 9 | https://github.com/wc-duck/coro 10 | 11 | This software is provided 'as-is', without any express or implied 12 | warranty. In no event will the authors be held liable for any damages 13 | arising from the use of this software. 14 | 15 | Permission is granted to anyone to use this software for any purpose, 16 | including commercial applications, and to alter it and redistribute it 17 | freely, subject to the following restrictions: 18 | 19 | 1. The origin of this software must not be misrepresented; you must not 20 | claim that you wrote the original software. If you use this software 21 | in a product, an acknowledgment in the product documentation would be 22 | appreciated but is not required. 23 | 2. Altered source versions must be plainly marked as such, and must not be 24 | misrepresented as being the original software. 25 | 3. This notice may not be removed or altered from any source distribution. 26 | 27 | Fredrik Kihlander 28 | */ 29 | 30 | /** 31 | * This is a small header/library implementing coroutines/protothreads/"yieldable functions" 32 | * or whatever you want to call it. Initial idea comest from here: 33 | * 34 | * https://www.chiark.greenend.org.uk/~sgtatham/coroutines.html 35 | * 36 | * 37 | * BASICS: 38 | * 39 | * To make a function yieldable that function will need to be a 'co_func' and contain 40 | * a co_begin()/co_end(co)-pair. I.e. 41 | * 42 | * void my_coroutine(coro* co, void*, void*) 43 | * { 44 | * co_begin(co); 45 | * 46 | * co_end(co); 47 | * } 48 | * 49 | * And to run that one instance of struct coro need to be created either on stack or 50 | * dynamically. To then run the function use co_resume() and check for completion with 51 | * co_completed() 52 | * 53 | * void run_it() 54 | * { 55 | * coro co; 56 | * co_init(&co, nullptr, 0, my_coroutine); 57 | * 58 | * while(!co_completed(&co)) 59 | * co_resume(&co, nullptr); 60 | * } 61 | * 62 | * 63 | * YIELD-POINTS: 64 | * 65 | * "yield-points" are points in a coroutine where execution might yield and continue from 66 | * on the next call to co_resume(). 67 | * Yield-points are introduced by co_yield(), co_wait() and co_call(). 68 | * 69 | * 70 | * LOCAL VARIABLES: 71 | * 72 | * Local varibles in coroutine-functions are a bit tricky since the coroutine callback 73 | * will be called over and over again, all local variables will be re-initialized at each 74 | * entry. The solution here is to use co_locals_begin()/co_locals_end(). But for this to 75 | * work a stack need to be accociated with the coroutine. 76 | * This stack can be allocated in any way as long as it is valid for the entire runtime of 77 | * the coroutine. 78 | * void my_coroutine(coro* co, void*, void*) 79 | * { 80 | * co_locals_begin(co); 81 | * int var1 = 0; 82 | * float var2 = 13.37f; 83 | * co_locals_end(co); 84 | * 85 | * co_begin(co); 86 | * 87 | * // use locals.var1 or locals.var2 88 | * 89 | * co_end(co); 90 | * } 91 | * 92 | * void run_it() 93 | * { 94 | * uint8_t stack[256]; 95 | * coro co; 96 | * co_init(&co, stack, sizeof(stack), my_coroutine); 97 | * 98 | * while(!co_completed(&co)) 99 | * co_resume(&co, nullptr); 100 | * } 101 | * 102 | * the locals declared will be pointing to the same position in the stack at each call 103 | * so these will be modifiable by the coroutine at will. 104 | * 105 | * 106 | * CALLING OTHER YIELDABLE FUNCTIONS: 107 | * 108 | * When providing a stack to the coroutine you can also call other yieldable functions 109 | * from your coroutine with co_call(). 110 | * 111 | * void my_sub_coroutine(coro* co, void*, void*) 112 | * { 113 | * co_begin(co); 114 | * 115 | * // ... do stuff here ... 116 | * 117 | * co_end(co); 118 | * } 119 | * 120 | * void my_coroutine(coro* co, void*, void*) 121 | * { 122 | * co_begin(co); 123 | * 124 | * co_call(co, my_sub_coroutine); 125 | * 126 | * co_end(co); 127 | * } 128 | * 129 | * The coroutine called by co_call() can now yield etc and is now the part of the code 130 | * that controlls the state of the stack of sub-calls until it exits. 131 | * 132 | * 133 | * ARGUMENTS: 134 | * 135 | * both co_init() and co_call() supports arguments as well, these arguments will be passed 136 | * on the stack via the last argument to co_func. 137 | * 138 | * The arguments will be residing on the stack until the called function has completed so 139 | * it is valid to write to arguments if needed. 140 | * 141 | * Observe that arguments are copied with memcpy and no destructors are run on them! 142 | * 143 | * 144 | * COROUTINES AND WAITS: 145 | * In many cases you might want coroutines that are 'waiting', i.e. suspended until some 146 | * event occurs, such as a timeout or operation-x completed. 147 | * 148 | * However since these waits and updates of coroutines are outside the scope of this lib 149 | * and nothing I can/want to dictate how it should work it will only provide one function 150 | * to help out with that and let the user build something around it with this lib as a 151 | * building-block. 152 | * That function is co_wait(). co_wait() is basically a co_yield() but it will flag the 153 | * coroutine and all its parent-coroutines and 'waiting' ( to be checked with co_waiting() ). 154 | * This flag will be cleared at the next call to co_resume(). 155 | * 156 | * 157 | * RUNNING OUT OF STACK 158 | * 159 | * If your coroutine is running out of stackspace the coroutine will yield and co_stack_overflowed() 160 | * will return true. If this is detected you are free to handle this how you want. 161 | * - Call co_replace_stack() to grow the stack and run co_resume() again? 162 | * - ASSERT()? 163 | * - something else ;) 164 | */ 165 | 166 | #pragma once 167 | 168 | #include 169 | #include // memcpy 170 | #include 171 | 172 | 173 | //////////////////////////////////////////////////////////////// 174 | // CONFIG // 175 | //////////////////////////////////////////////////////////////// 176 | 177 | /** 178 | * Define CORO_LOCALS_NAME to configure name of variable declared 179 | * by co_locals_begin()/co_locals_end() 180 | * Defaults to 'locals' 181 | */ 182 | #if !defined(CORO_LOCALS_NAME) 183 | # define CORO_LOCALS_NAME locals 184 | #endif 185 | 186 | /** 187 | * Define to override how asserts are implemented, defaults to using 188 | * standard assert() from 189 | */ 190 | #if !defined(CORO_ASSERT) 191 | # include 192 | # define CORO_ASSERT(cond, msg) assert((cond) && msg); 193 | #endif 194 | 195 | /** 196 | * If defined to 1 struct coro will have an extra member called stack_use_max 197 | * that will be the maximum amount of stack that has been used by the coro during 198 | * it's lifetime, default to 0 199 | */ 200 | #if !defined(CORO_TRACK_MAX_STACK_USAGE) 201 | # define CORO_TRACK_MAX_STACK_USAGE 0 202 | #endif 203 | 204 | 205 | //////////////////////////////////////////////////////////////// 206 | // PUBLIC API // 207 | //////////////////////////////////////////////////////////////// 208 | 209 | /** 210 | * Signature used by all coroutine-callbacks. 211 | * 212 | * These functions must follow this pattern 213 | * 214 | * void my_func(coro* co, void* userdata, void* arg ) 215 | * { 216 | * // start with declaring locals, this is optional 217 | * co_locals_begin(co) 218 | * // ... 219 | * co_locals_end(co) 220 | * 221 | * co_begin(co); // required! 222 | * 223 | * // ... code for coroutine goes here! ... 224 | * 225 | * co_end(co); // required! 226 | * } 227 | * 228 | * @param co state of current coroutine call, use with all other co_**** functions/macros. 229 | * @param userdata passed to co_resume() at the top-level. 230 | * @param arg argument passed to co_init() or co_call(), data will be preserved between calls 231 | * and can thus be modified etc. Will be nullptr if no argument was passed. 232 | */ 233 | typedef void(*co_func)(struct coro* co, void* userdata, void* arg); 234 | 235 | /** 236 | * State of coroutine. 237 | */ 238 | enum 239 | { 240 | CORO_STATE_CREATED = -1, ///< Coroutine has been initialized but has never been co_resumed(). 241 | CORO_STATE_COMPLETED = -2 ///< Coroutine has completed, calling co_resume() on this is invalid! 242 | }; 243 | 244 | /** 245 | * Internal state used to keep the state of one call to a coroutine-callback. 246 | * Extracted to keep stack-state only in root-'coro' this to save stack-space 247 | * and make stack-replacement simpler. 248 | * 249 | * @note this will be cast to coro* in sub-calls to be able to call a co_func 250 | * with both co_resume() and co_call(). 251 | */ 252 | struct _coro_call_state 253 | { 254 | struct coro* root; ///< pointer to the top-level (and only) coro*. 255 | co_func func; 256 | int32_t state; 257 | uint32_t sub_call; 258 | uint32_t call_locals; 259 | uint32_t call_args; 260 | }; 261 | 262 | /** 263 | * Struct keeping state for one coroutine. 264 | * 265 | * TODO: this struct is bigger than it need to be, replace pointers with offset on stack 266 | * instead or just store bool for sub_call and size of locals and args and "dig" into 267 | * the stack. 268 | * Making this smaller would mean less stackusage as coro:s for sub-calls are placed 269 | * on the stack. 270 | */ 271 | struct coro 272 | { 273 | _coro_call_state call; 274 | 275 | uint32_t waiting : 1; 276 | uint32_t overflow : 1; 277 | uint32_t overflow_in_call : 1; 278 | uint32_t executing : 1; 279 | 280 | int stack_size {0}; 281 | uint8_t* stack_top {nullptr}; 282 | uint8_t* stack {nullptr}; 283 | void* userdata {nullptr}; 284 | 285 | #if CORO_TRACK_MAX_STACK_USAGE 286 | int stack_use_max {0}; 287 | #endif 288 | }; 289 | 290 | /** 291 | * Initialize coroutine. This will not call the coroutine-function, that will be done by 292 | * co_resume(). 293 | * There is no need for the coroutine to have a stack but a stack is required to use 294 | * arguments and co_call(). 295 | * 296 | * @note arguments are copied via memcpy so data used as argument need to support that. 297 | * 298 | * @note stack-overflow is only handled via an CORO_ASSERT(). 299 | * 300 | * @param co coroutine to initialize. 301 | * @param stack ptr to memory-segment to use as stack, can be null. 302 | * @param stack_size size of memory-region pointed to by stack, if stack == null this should be 0. 303 | * @param func coroutine callback. 304 | * @param arg optional pointer to argument to pass to coroutine as the second argument in callback. 305 | * @param arg_size size of data pointed to by arg. 306 | * @param arg_align alignment-requirement for data pointed to by arg. 307 | */ 308 | static inline void co_init( coro* co, 309 | void* stack, 310 | int stack_size, 311 | co_func func, 312 | void* arg, 313 | int arg_size, 314 | int arg_align ); 315 | 316 | /** 317 | * Initialize coroutine without argument. 318 | * @see co_init() for doc. 319 | */ 320 | static inline void co_init( coro* co, 321 | void* stack, 322 | int stack_size, 323 | co_func func ); 324 | 325 | /** 326 | * Initialize coroutine with argument. 327 | * @see co_init() for doc. 328 | */ 329 | template 330 | static inline void co_init( coro* co, void* stack, int stack_size, co_func func, T& arg ); 331 | 332 | /** 333 | * Resume execution of coroutine, this will run the coroutine until it yields or 334 | * exits. 335 | * 336 | * yielding is done by calling co_yeald(), co_wait(), co_call() if the called coro 337 | * does not return on its first co_resume(). 338 | * 339 | * @note it is invalid to call co_resume() on a completed coroutine. 340 | * 341 | * @param userdata passed to all invocation of co_func in coro. 342 | */ 343 | static inline void co_resume( coro* co, void* userdata ); 344 | 345 | /** 346 | * Returns true if the coroutine has completed. 347 | */ 348 | static inline bool co_completed( coro* co ) { return co->call.state == CORO_STATE_COMPLETED; } 349 | 350 | /** 351 | * Return true if the coroutine overflowed the stack at the last co_resume(), flag 352 | * will be cleared at the next call to co_resume() and set again if the stack hasn't grown! 353 | */ 354 | static inline bool co_stack_overflowed( coro* co ) { return co->call.root->overflow == 1; } 355 | 356 | /** 357 | * Returns true if the coroutine or any sub-coroutine has yielded via co_wait() 358 | */ 359 | static inline bool co_waiting( coro* co ) { return co->waiting == 1; } 360 | 361 | /** 362 | * Return the amount of bytes currently used by the stack of the coro, -1 if the coro 363 | * has no stack. 364 | */ 365 | static inline int co_stack_usage( coro* co ); 366 | 367 | /** 368 | * Replace the stack of the coroutine with a new one, will assert if co_stack_usage(co) > stack_size. 369 | */ 370 | static inline void* co_replace_stack( coro* co, void* stack, int stack_size ); 371 | 372 | /** 373 | * Begin coroutine, the system expects a matching co_begin()/co_end() pair in a co_func. 374 | * 375 | * @note if a co_func uses co_locals_begin()/co_locals_end() this is required to be called 376 | * BEFORE co_begin(). 377 | */ 378 | #define co_begin(co) 379 | 380 | /** 381 | * End coroutine, the system expects a matching co_begin()/co_end() pair in a co_func. 382 | */ 383 | #define co_end(co) 384 | 385 | /** 386 | * Early termiation of the current coroutine. This will be the same as the function 387 | * reaching co_end() 388 | */ 389 | #define co_exit(co) 390 | 391 | /** 392 | * Yield execution of coroutine, coroutine will be continued after co_yeald() at the next co_resume() 393 | */ 394 | #define co_yield(co) 395 | 396 | /** 397 | * 398 | */ 399 | #define co_wait(co) 400 | 401 | /** 402 | * Perform a sub-call of another coroutine from current coroutine. 403 | * If coroutine returns without yielding this call will not yeald however if the called function 404 | * yields this will also yield and continue from after co_call() when the sub-call yields. 405 | * 406 | * the sub-call will be resumed whenever the "top-level" coroutine is co_resumed(), i.e. the user 407 | * will only require to co_resume() the toplevel coroutine. 408 | * 409 | * Can be called in 3 different ways 410 | * 411 | * // only call coro_callback as coroutine. 412 | * co_call(co, coro_callback); 413 | * 414 | * // only call coro_callback with argument. 415 | * int my_arg = 0; 416 | * co_call(co, coro_callback, &my_arg, sizeof(int), alignof(int)); 417 | * 418 | * // if compiling as c++ you can just pass the argument. 419 | * int my_arg = 0; 420 | * co_call(co, coro_callback, my_arg); 421 | */ 422 | #define co_call(co, to_call, ...) 423 | 424 | /** 425 | * Declare variables "local" to the coroutine that will be persisted between calls to co_resume() 426 | * for this specific coroutine. 427 | * The "local" variables will be stored in a variable named via the define CORO_LOCALS_NAME that 428 | * default to "locals". 429 | * 430 | * @note It is required to call this BEFORE co_begin(). 431 | * 432 | * @example 433 | * 434 | * void my_coroutine( coro* co ) 435 | * { 436 | * co_locals_begin(co); 437 | * int my_local_int = 1; 438 | * float my_local_float = 13.37f; 439 | * co_locals_end(co); 440 | * 441 | * co_begin(); 442 | * 443 | * use_int_and_float( locals.my_local_int, locals.my_local_float ); 444 | * 445 | * co_end(); 446 | * } 447 | * 448 | * @note ADD NOTE ABOUT IMPLEMENTATION to know the limitations. 449 | */ 450 | #define co_locals_begin(co) 451 | #define co_locals_end(co) 452 | 453 | 454 | 455 | 456 | //////////////////////////////////////////////////////////////// 457 | // IMPLEMENTATION // 458 | //////////////////////////////////////////////////////////////// 459 | 460 | #undef co_begin 461 | #undef co_end 462 | #undef co_exit 463 | #undef co_yield 464 | #undef co_wait 465 | #undef co_call 466 | #undef co_locals_begin 467 | #undef co_locals_end 468 | 469 | static inline int co_stack_usage( coro* co ) 470 | { 471 | coro* root = co->call.root; 472 | if(root->stack == nullptr) 473 | return -1; 474 | return (int)(root->stack_top - root->stack); 475 | } 476 | 477 | static inline void* _co_stack_alloc(_coro_call_state* call, size_t size, size_t align) 478 | { 479 | coro* co = call->root; 480 | 481 | // align up! 482 | uint8_t* ptr = (uint8_t*)( ( (uintptr_t)co->stack_top + ( (uintptr_t)align - 1 ) ) & ~( (uintptr_t)align - 1 ) ); 483 | uint8_t* top = ptr + size; 484 | 485 | if(top > co->stack + co->stack_size) 486 | { 487 | co->overflow = 1; 488 | return nullptr; 489 | } 490 | 491 | co->stack_top = top; 492 | 493 | #if CORO_TRACK_MAX_STACK_USAGE 494 | int stack_use = co_stack_usage(co); 495 | co->stack_use_max = stack_use > co->stack_use_max ? stack_use : co->stack_use_max; 496 | #endif 497 | 498 | return ptr; 499 | } 500 | 501 | static inline void _co_stack_rewind(_coro_call_state* call, void* ptr) 502 | { 503 | coro* co = call->root; 504 | CORO_ASSERT(ptr >= co->stack && ptr < co->stack + co->stack_size, "ptr from outside current coro-stack is used for rewind of stack!!!"); 505 | co->stack_top = (uint8_t*)ptr; 506 | } 507 | 508 | static inline uint32_t _co_ptr_to_stack_offset(_coro_call_state* call, void* ptr) 509 | { 510 | return (uint32_t)((uint8_t*)ptr - call->root->stack); 511 | } 512 | 513 | static inline void* _co_stack_offset_to_ptr(_coro_call_state* call, uint32_t offset) 514 | { 515 | if(offset == 0xFFFFFFFF) 516 | return nullptr; 517 | return call->root->stack + offset; 518 | } 519 | 520 | static inline void _co_init_call_state( _coro_call_state* call, 521 | coro* root, 522 | co_func func, 523 | void* arg, 524 | int arg_size, 525 | int arg_align ) 526 | { 527 | call->state = 0; 528 | call->root = root; 529 | call->func = func; 530 | call->sub_call = 0xFFFFFFFF; 531 | call->call_locals = 0xFFFFFFFF; 532 | call->call_args = 0xFFFFFFFF; 533 | if(arg) 534 | { 535 | CORO_ASSERT(call->root->stack != nullptr, "can't have arguments to a coroutine without a stack!"); 536 | void* call_args = _co_stack_alloc(call, (size_t)arg_size, (size_t)arg_align); 537 | if(call_args != nullptr) 538 | { 539 | memcpy(call_args, arg, (size_t)arg_size); 540 | call->call_args = _co_ptr_to_stack_offset(call, call_args); 541 | } 542 | } 543 | } 544 | 545 | static inline void co_init( coro* co, 546 | void* stack, 547 | int stack_size, 548 | co_func func, 549 | void* arg, 550 | int arg_size, 551 | int arg_align ) 552 | { 553 | co->waiting = 0; 554 | co->overflow = 0; 555 | co->executing = 0; 556 | co->stack = (uint8_t*)stack; 557 | co->stack_top = (uint8_t*)stack; 558 | co->stack_size = stack_size; 559 | co->userdata = nullptr; 560 | 561 | #if CORO_TRACK_MAX_STACK_USAGE 562 | co->stack_use_max = 0; 563 | #endif 564 | 565 | _co_init_call_state(&co->call, co, func, arg, arg_size, arg_align); 566 | CORO_ASSERT(co->overflow == 0, "Out of stack when allocating data for argument in co_init(), can't handle out of stack in a good way here!"); 567 | } 568 | 569 | static inline void co_init( coro* co, 570 | void* stack, 571 | int stack_size, 572 | co_func func ) 573 | { 574 | co_init( co, stack, stack_size, func, nullptr, 0, 0 ); 575 | } 576 | 577 | template 578 | static inline void co_init( coro* co, void* stack, int stack_size, co_func func, T& arg ) 579 | { 580 | co_init( co, stack, stack_size, func, &arg, sizeof(T), alignof(T) ); 581 | } 582 | 583 | static inline void _co_invoke_callback(_coro_call_state* call) 584 | { 585 | call->func((coro*)call, call->root->userdata, _co_stack_offset_to_ptr(call, call->call_args)); 586 | } 587 | 588 | static inline void co_resume(coro* co, void* userdata) 589 | { 590 | CORO_ASSERT(!co_completed(co), "can't resume a completed coroutine!"); 591 | co->waiting = 0; 592 | co->overflow = 0; 593 | co->overflow_in_call = 0; 594 | co->executing = 1; 595 | co->userdata = userdata; 596 | _co_invoke_callback(&co->call); 597 | co->userdata = nullptr; 598 | co->executing = 0; 599 | } 600 | 601 | static inline bool _co_sub_call(_coro_call_state* call) 602 | { 603 | if(call->sub_call != 0xFFFFFFFF) 604 | { 605 | _coro_call_state* sub_call = (_coro_call_state*)_co_stack_offset_to_ptr(call, call->sub_call); 606 | _co_invoke_callback(sub_call); 607 | 608 | if(co_completed((coro*)sub_call)) 609 | { 610 | _co_stack_rewind(call, sub_call); 611 | call->sub_call = 0xFFFFFFFF; 612 | } 613 | } 614 | return call->sub_call != 0xFFFFFFFF; 615 | } 616 | 617 | static inline void* co_replace_stack( coro* co, void* stack, int stack_size ) 618 | { 619 | coro* root = co->call.root; 620 | int stack_usage = co_stack_usage(co); 621 | CORO_ASSERT(root->executing == 0, "Can't replace stack when executing!"); 622 | CORO_ASSERT(stack_usage <= stack_size, "Shrinking stack to less size than current usage!"); 623 | 624 | uint8_t* old_stack = root->stack; 625 | 626 | if(old_stack) 627 | memcpy(stack, old_stack, (size_t)stack_usage); 628 | else 629 | stack_usage = 0; 630 | 631 | root->stack = (uint8_t*)stack; 632 | root->stack_top = (uint8_t*)stack + stack_usage; 633 | root->stack_size = stack_size; 634 | 635 | return old_stack; 636 | } 637 | 638 | #define co_begin(co) \ 639 | if(_co_sub_call(&co->call)) \ 640 | return; \ 641 | switch(co->call.state) \ 642 | { \ 643 | default: 644 | 645 | #define co_exit(co) \ 646 | do{ co->call.state = CORO_STATE_COMPLETED; return; } while(0) 647 | 648 | #define co_end(co) \ 649 | } \ 650 | co_exit(co) 651 | 652 | #define co_yield(co) \ 653 | do { co->call.state = __LINE__; return; case __LINE__: {} } while(0) 654 | 655 | #define co_wait(co) \ 656 | do { co->call.root->waiting = 1; co_yield(co); } while(0) 657 | 658 | static inline bool _co_call(coro* co, co_func to_call, void* arg, int arg_size, int arg_align ) 659 | { 660 | _coro_call_state* sub_call = (_coro_call_state*)_co_stack_alloc(&co->call, sizeof(_coro_call_state), alignof(_coro_call_state)); 661 | if(sub_call != nullptr) 662 | _co_init_call_state(sub_call, co->call.root, to_call, arg, arg_size, arg_align); 663 | 664 | if(co_stack_overflowed(co)) 665 | { 666 | co->call.root->overflow_in_call = 1; 667 | return true; 668 | } 669 | co->call.sub_call = _co_ptr_to_stack_offset(&co->call, sub_call); 670 | return _co_sub_call(&co->call); 671 | } 672 | 673 | template< typename T > 674 | static inline bool _co_call(coro* co, co_func to_call, T& arg ) 675 | { 676 | return _co_call(co, to_call, &arg, sizeof(T), alignof(T)); 677 | } 678 | 679 | 680 | static inline bool _co_call(coro* co, co_func to_call) 681 | { 682 | return _co_call(co, to_call, nullptr, 0, 0); 683 | } 684 | 685 | #define co_call(co, to_call, ...) \ 686 | do{ \ 687 | co->call.state = __LINE__ + 100000; \ 688 | if(_co_call(co, to_call, ##__VA_ARGS__)) \ 689 | { \ 690 | if(co->call.root->overflow_in_call) \ 691 | return; \ 692 | co_yield(co); \ 693 | } \ 694 | } while(0) 695 | 696 | #define co_locals_begin(co) \ 697 | struct _co_locals \ 698 | { 699 | 700 | #define co_locals_end(co) \ 701 | }; \ 702 | if(co->call.call_locals == 0xFFFFFFFF) \ 703 | { \ 704 | void* call_locals = _co_stack_alloc( &co->call, \ 705 | sizeof(_co_locals), \ 706 | alignof(_co_locals)); \ 707 | if(call_locals == nullptr) \ 708 | return; \ 709 | new (call_locals) _co_locals; \ 710 | co->call.call_locals = _co_ptr_to_stack_offset(&co->call, call_locals); \ 711 | } \ 712 | _co_locals& CORO_LOCALS_NAME = *((_co_locals*)_co_stack_offset_to_ptr(&co->call, co->call.call_locals)); \ 713 | 714 | -------------------------------------------------------------------------------- /example/dialog_example.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Header implementing "protothreads" but with a stack to support 3 | local-varible state, argument-passing and sub-coroutines. 4 | 5 | version 1.0, november, 2018 6 | 7 | Copyright (C) 2018- Fredrik Kihlander 8 | 9 | https://github.com/wc-duck/coro 10 | 11 | This software is provided 'as-is', without any express or implied 12 | warranty. In no event will the authors be held liable for any damages 13 | arising from the use of this software. 14 | 15 | Permission is granted to anyone to use this software for any purpose, 16 | including commercial applications, and to alter it and redistribute it 17 | freely, subject to the following restrictions: 18 | 19 | 1. The origin of this software must not be misrepresented; you must not 20 | claim that you wrote the original software. If you use this software 21 | in a product, an acknowledgment in the product documentation would be 22 | appreciated but is not required. 23 | 2. Altered source versions must be plainly marked as such, and must not be 24 | misrepresented as being the original software. 25 | 3. This notice may not be removed or altered from any source distribution. 26 | 27 | Fredrik Kihlander 28 | */ 29 | 30 | /* 31 | Small example showing how to implement a really simple "wait"-system ontop of 32 | 'coro'. 33 | As there is only waiting for timeout here, no more advanced things than this is needed. 34 | */ 35 | 36 | #include "../coro.h" 37 | #include 38 | 39 | #if defined(WIN64) || defined(_WIN64) || defined(WIN32) || defined(_WIN32) 40 | #else 41 | #include // usleep() 42 | #endif 43 | 44 | #include // rand() 45 | 46 | // Define our own co_sleep_ms() that will suspend the thread for x milliseconds. 47 | #define co_sleep_ms(co, ms) \ 48 | do { sleep_time = (ms); co_wait(co); } while(0) 49 | 50 | static void print_char(char c) 51 | { 52 | putc(c, stdout); 53 | fflush(stdout); 54 | } 55 | 56 | static unsigned int sleep_time = 0; 57 | 58 | void print_line(coro* co, void*, const char** args) 59 | { 60 | // all values are returned as a pointer to the type and a line to print 61 | // was passed as argument here. 62 | const char* line = *args; 63 | 64 | // declare loacal state to keep track of what char to print next. 65 | co_locals_begin(co); 66 | // init to 8... it just so happens that name + indent is 8 chars ;) 67 | int curr_char = 8; 68 | co_locals_end(co); 69 | 70 | // mark the begining of the coro-functions executing code. This is required 71 | // by the 'coro' system... and must be matched by a co_end(). 72 | co_begin(co); 73 | 74 | printf("%.8s", line); 75 | 76 | while(line[locals.curr_char] != '\0') 77 | { 78 | print_char(line[locals.curr_char++]); 79 | 80 | // suspend the coroutine and have the outside system wait for x milliseconds! 81 | co_sleep_ms(co, 30 + rand() % 150); 82 | } 83 | print_char('\n'); 84 | co_end(co); 85 | } 86 | 87 | struct print_dialog_arg 88 | { 89 | const char** lines; 90 | size_t line_cnt; 91 | }; 92 | 93 | void print_dialog(coro* co, void*, print_dialog_arg* args) 94 | { 95 | // declare locals to keep track of current line. 96 | co_locals_begin(co); 97 | size_t curr_line = 0; 98 | co_locals_end(co); 99 | 100 | co_begin(co); 101 | 102 | while(locals.curr_line != args->line_cnt) 103 | { 104 | // for each line, call a sub-coro-function with the line to print as argument. 105 | co_call(co, (co_func)print_line, args->lines[locals.curr_line++]); 106 | 107 | // sleep coroutine for x milliseconds between each line. 108 | co_sleep_ms(co, 500 + rand() % 200); 109 | } 110 | 111 | co_end(co); 112 | } 113 | 114 | int main(int, const char**) 115 | { 116 | uint8_t* stack[512]; 117 | 118 | static const char* LINES[] = { 119 | "Bob Yo alice. I heard you like mudkips.", 120 | "Alice No Bob. Not me. Who told you such a thing?", 121 | "Bob Alice please, don't lie to me. We've known each other a long time.", 122 | "Alice We have grown apart. I barely know myself.", 123 | "Bob OK.", 124 | "Alice Good bye Bob. I wish you the best.", 125 | "Bob But do you like mudkips?", 126 | "Alice ", 127 | "Bob Well, I like mudkips :)" 128 | }; 129 | 130 | print_dialog_arg dialog_args { 131 | LINES, 132 | sizeof(LINES) / sizeof(const char*) 133 | }; 134 | 135 | coro co; 136 | co_init(&co, stack, sizeof(stack), (co_func)print_dialog, dialog_args); 137 | 138 | // run until the top-level coroutine has completed... i.e. all lines was printed. 139 | while(!co_completed(&co)) 140 | { 141 | // resume will resume execution where the coroutine left of! 142 | co_resume(&co, nullptr); 143 | 144 | // since we only have one system that can wait() our coroutines in this example, 145 | // and that is sleep, sleep if waiting ;) 146 | if(co_waiting(&co)) 147 | { 148 | #if defined(WIN64) || defined(_WIN64) || defined(WIN32) || defined(_WIN32) 149 | SleepEx( sleep_time, false ); 150 | #else 151 | usleep( sleep_time * 1000 ); 152 | #endif 153 | } 154 | } 155 | 156 | return 0; 157 | } -------------------------------------------------------------------------------- /test/greatest.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Scott Vokes 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #ifndef GREATEST_H 18 | #define GREATEST_H 19 | 20 | #define GREATEST_VERSION_MAJOR 0 21 | #define GREATEST_VERSION_MINOR 9 22 | #define GREATEST_VERSION_PATCH 3 23 | 24 | /* A unit testing system for C, contained in 1 file. 25 | * It doesn't use dynamic allocation or depend on anything 26 | * beyond ANSI C89. */ 27 | 28 | 29 | /********************************************************************* 30 | * Minimal test runner template 31 | *********************************************************************/ 32 | #if 0 33 | 34 | #include "greatest.h" 35 | 36 | TEST foo_should_foo() { 37 | PASS(); 38 | } 39 | 40 | static void setup_cb(void *data) { 41 | printf("setup callback for each test case\n"); 42 | } 43 | 44 | static void teardown_cb(void *data) { 45 | printf("teardown callback for each test case\n"); 46 | } 47 | 48 | SUITE(suite) { 49 | /* Optional setup/teardown callbacks which will be run before/after 50 | * every test case in the suite. 51 | * Cleared when the suite finishes. */ 52 | SET_SETUP(setup_cb, voidp_to_callback_data); 53 | SET_TEARDOWN(teardown_cb, voidp_to_callback_data); 54 | 55 | RUN_TEST(foo_should_foo); 56 | } 57 | 58 | /* Add all the definitions that need to be in the test runner's main file. */ 59 | GREATEST_MAIN_DEFS(); 60 | 61 | int main(int argc, char **argv) { 62 | GREATEST_MAIN_BEGIN(); /* command-line arguments, initialization. */ 63 | RUN_SUITE(suite); 64 | GREATEST_MAIN_END(); /* display results */ 65 | } 66 | 67 | #endif 68 | /*********************************************************************/ 69 | 70 | 71 | #include 72 | #include 73 | #include 74 | #include 75 | 76 | 77 | /*********** 78 | * Options * 79 | ***********/ 80 | 81 | /* Default column width for non-verbose output. */ 82 | #ifndef GREATEST_DEFAULT_WIDTH 83 | #define GREATEST_DEFAULT_WIDTH 72 84 | #endif 85 | 86 | /* FILE *, for test logging. */ 87 | #ifndef GREATEST_STDOUT 88 | #define GREATEST_STDOUT stdout 89 | #endif 90 | 91 | /* Remove GREATEST_ prefix from most commonly used symbols? */ 92 | #ifndef GREATEST_USE_ABBREVS 93 | #define GREATEST_USE_ABBREVS 1 94 | #endif 95 | 96 | 97 | /********* 98 | * Types * 99 | *********/ 100 | 101 | /* Info for the current running suite. */ 102 | typedef struct greatest_suite_info { 103 | unsigned int tests_run; 104 | unsigned int passed; 105 | unsigned int failed; 106 | unsigned int skipped; 107 | 108 | /* timers, pre/post running suite and individual tests */ 109 | clock_t pre_suite; 110 | clock_t post_suite; 111 | clock_t pre_test; 112 | clock_t post_test; 113 | } greatest_suite_info; 114 | 115 | /* Type for a suite function. */ 116 | typedef void (greatest_suite_cb)(void); 117 | 118 | /* Types for setup/teardown callbacks. If non-NULL, these will be run 119 | * and passed the pointer to their additional data. */ 120 | typedef void (greatest_setup_cb)(void *udata); 121 | typedef void (greatest_teardown_cb)(void *udata); 122 | 123 | typedef enum { 124 | GREATEST_FLAG_VERBOSE = 0x01, 125 | GREATEST_FLAG_FIRST_FAIL = 0x02, 126 | GREATEST_FLAG_LIST_ONLY = 0x04 127 | } GREATEST_FLAG; 128 | 129 | typedef struct greatest_run_info { 130 | unsigned int flags; 131 | unsigned int tests_run; /* total test count */ 132 | 133 | /* Overall pass/fail/skip counts. */ 134 | unsigned int passed; 135 | unsigned int failed; 136 | unsigned int skipped; 137 | 138 | /* currently running test suite */ 139 | greatest_suite_info suite; 140 | 141 | /* info to print about the most recent failure */ 142 | const char *fail_file; 143 | unsigned int fail_line; 144 | const char *msg; 145 | 146 | /* current setup/teardown hooks and userdata */ 147 | greatest_setup_cb *setup; 148 | void *setup_udata; 149 | greatest_teardown_cb *teardown; 150 | void *teardown_udata; 151 | 152 | /* formatting info for ".....s...F"-style output */ 153 | unsigned int col; 154 | unsigned int width; 155 | 156 | /* only run a specific suite or test */ 157 | char *suite_filter; 158 | char *test_filter; 159 | 160 | /* overall timers */ 161 | clock_t begin; 162 | clock_t end; 163 | } greatest_run_info; 164 | 165 | /* Global var for the current testing context. 166 | * Initialized by GREATEST_MAIN_DEFS(). */ 167 | extern greatest_run_info greatest_info; 168 | 169 | 170 | /********************** 171 | * Exported functions * 172 | **********************/ 173 | 174 | void greatest_do_pass(const char *name); 175 | void greatest_do_fail(const char *name); 176 | void greatest_do_skip(const char *name); 177 | int greatest_pre_test(const char *name); 178 | void greatest_post_test(const char *name, int res); 179 | void greatest_usage(const char *name); 180 | void GREATEST_SET_SETUP_CB(greatest_setup_cb *cb, void *udata); 181 | void GREATEST_SET_TEARDOWN_CB(greatest_teardown_cb *cb, void *udata); 182 | 183 | 184 | /********** 185 | * Macros * 186 | **********/ 187 | 188 | /* Define a suite. */ 189 | #define GREATEST_SUITE(NAME) void NAME(void) 190 | 191 | /* Start defining a test function. 192 | * The arguments are not included, to allow parametric testing. */ 193 | #define GREATEST_TEST static int 194 | 195 | /* Run a suite. */ 196 | #define GREATEST_RUN_SUITE(S_NAME) greatest_run_suite(S_NAME, #S_NAME) 197 | 198 | /* Run a test in the current suite. */ 199 | #define GREATEST_RUN_TEST(TEST) \ 200 | do { \ 201 | if (greatest_pre_test(#TEST) == 1) { \ 202 | int res = TEST(); \ 203 | greatest_post_test(#TEST, res); \ 204 | } else if (GREATEST_LIST_ONLY()) { \ 205 | fprintf(GREATEST_STDOUT, " %s\n", #TEST); \ 206 | } \ 207 | } while (0) 208 | 209 | /* Run a test in the current suite with one void* argument, 210 | * which can be a pointer to a struct with multiple arguments. */ 211 | #define GREATEST_RUN_TEST1(TEST, ENV) \ 212 | do { \ 213 | if (greatest_pre_test(#TEST) == 1) { \ 214 | int res = TEST(ENV); \ 215 | greatest_post_test(#TEST, res); \ 216 | } else if (GREATEST_LIST_ONLY()) { \ 217 | fprintf(GREATEST_STDOUT, " %s\n", #TEST); \ 218 | } \ 219 | } while (0) 220 | 221 | /* If __VA_ARGS__ (C99) is supported, allow parametric testing 222 | * without needing to manually manage the argument struct. */ 223 | #if __STDC_VERSION__ >= 19901L 224 | #define GREATEST_RUN_TESTp(TEST, ...) \ 225 | do { \ 226 | if (greatest_pre_test(#TEST) == 1) { \ 227 | int res = TEST(__VA_ARGS__); \ 228 | greatest_post_test(#TEST, res); \ 229 | } else if (GREATEST_LIST_ONLY()) { \ 230 | fprintf(GREATEST_STDOUT, " %s\n", #TEST); \ 231 | } \ 232 | } while (0) 233 | #endif 234 | 235 | 236 | /* Check if the test runner is in verbose mode. */ 237 | #define GREATEST_IS_VERBOSE() (greatest_info.flags & GREATEST_FLAG_VERBOSE) 238 | #define GREATEST_LIST_ONLY() (greatest_info.flags & GREATEST_FLAG_LIST_ONLY) 239 | #define GREATEST_FIRST_FAIL() (greatest_info.flags & GREATEST_FLAG_FIRST_FAIL) 240 | #define GREATEST_FAILURE_ABORT() (greatest_info.suite.failed > 0 && GREATEST_FIRST_FAIL()) 241 | 242 | /* Message-less forms. */ 243 | #define GREATEST_PASS() GREATEST_PASSm(NULL) 244 | #define GREATEST_FAIL() GREATEST_FAILm(NULL) 245 | #define GREATEST_SKIP() GREATEST_SKIPm(NULL) 246 | #define GREATEST_ASSERT(COND) GREATEST_ASSERTm(#COND, COND) 247 | #define GREATEST_ASSERT_FALSE(COND) GREATEST_ASSERT_FALSEm(#COND, COND) 248 | #define GREATEST_ASSERT_EQ(EXP, GOT) GREATEST_ASSERT_EQm(#EXP " != " #GOT, EXP, GOT) 249 | #define GREATEST_ASSERT_STR_EQ(EXP, GOT) GREATEST_ASSERT_STR_EQm(#EXP " != " #GOT, EXP, GOT) 250 | 251 | /* The following forms take an additional message argument first, 252 | * to be displayed by the test runner. */ 253 | 254 | /* Fail if a condition is not true, with message. */ 255 | #define GREATEST_ASSERTm(MSG, COND) \ 256 | do { \ 257 | greatest_info.msg = MSG; \ 258 | greatest_info.fail_file = __FILE__; \ 259 | greatest_info.fail_line = __LINE__; \ 260 | if (!(COND)) { return -1; } \ 261 | greatest_info.msg = NULL; \ 262 | } while (0) 263 | 264 | #define GREATEST_ASSERT_FALSEm(MSG, COND) \ 265 | do { \ 266 | greatest_info.msg = MSG; \ 267 | greatest_info.fail_file = __FILE__; \ 268 | greatest_info.fail_line = __LINE__; \ 269 | if ((COND)) { return -1; } \ 270 | greatest_info.msg = NULL; \ 271 | } while (0) 272 | 273 | #define GREATEST_ASSERT_EQm(MSG, EXP, GOT) \ 274 | do { \ 275 | greatest_info.msg = MSG; \ 276 | greatest_info.fail_file = __FILE__; \ 277 | greatest_info.fail_line = __LINE__; \ 278 | if ((EXP) != (GOT)) { return -1; } \ 279 | greatest_info.msg = NULL; \ 280 | } while (0) 281 | 282 | #define GREATEST_ASSERT_STR_EQm(MSG, EXP, GOT) \ 283 | do { \ 284 | const char *exp_s = (EXP); \ 285 | const char *got_s = (GOT); \ 286 | greatest_info.msg = MSG; \ 287 | greatest_info.fail_file = __FILE__; \ 288 | greatest_info.fail_line = __LINE__; \ 289 | if (0 != strcmp(exp_s, got_s)) { \ 290 | fprintf(GREATEST_STDOUT, \ 291 | "Expected:\n####\n%s\n####\n", exp_s); \ 292 | fprintf(GREATEST_STDOUT, \ 293 | "Got:\n####\n%s\n####\n", got_s); \ 294 | return -1; \ 295 | } \ 296 | greatest_info.msg = NULL; \ 297 | } while (0) 298 | 299 | #define GREATEST_PASSm(MSG) \ 300 | do { \ 301 | greatest_info.msg = MSG; \ 302 | return 0; \ 303 | } while (0) 304 | 305 | #define GREATEST_FAILm(MSG) \ 306 | do { \ 307 | greatest_info.fail_file = __FILE__; \ 308 | greatest_info.fail_line = __LINE__; \ 309 | greatest_info.msg = MSG; \ 310 | return -1; \ 311 | } while (0) 312 | 313 | #define GREATEST_SKIPm(MSG) \ 314 | do { \ 315 | greatest_info.msg = MSG; \ 316 | return 1; \ 317 | } while (0) 318 | 319 | #define GREATEST_SET_TIME(NAME) \ 320 | NAME = clock(); \ 321 | if (NAME == (clock_t) -1) { \ 322 | fprintf(GREATEST_STDOUT, \ 323 | "clock error: %s\n", #NAME); \ 324 | exit(EXIT_FAILURE); \ 325 | } 326 | 327 | #define GREATEST_CLOCK_DIFF(C1, C2) \ 328 | fprintf(GREATEST_STDOUT, " (%lu ticks, %.3f sec)", \ 329 | (long unsigned int) (C2) - (long unsigned int)(C1), \ 330 | (double)((C2) - (C1)) / (1.0 * (double)CLOCKS_PER_SEC)) \ 331 | 332 | /* Include several function definitions in the main test file. */ 333 | #define GREATEST_MAIN_DEFS() \ 334 | \ 335 | /* Is FILTER a subset of NAME? */ \ 336 | static int greatest_name_match(const char *name, \ 337 | const char *filter) { \ 338 | size_t offset = 0; \ 339 | size_t filter_len = strlen(filter); \ 340 | while (name[offset] != '\0') { \ 341 | if (name[offset] == filter[0]) { \ 342 | if (0 == strncmp(&name[offset], filter, filter_len)) { \ 343 | return 1; \ 344 | } \ 345 | } \ 346 | offset++; \ 347 | } \ 348 | \ 349 | return 0; \ 350 | } \ 351 | \ 352 | int greatest_pre_test(const char *name) { \ 353 | if (!GREATEST_LIST_ONLY() \ 354 | && (!GREATEST_FIRST_FAIL() || greatest_info.suite.failed == 0) \ 355 | && (greatest_info.test_filter == NULL || \ 356 | greatest_name_match(name, greatest_info.test_filter))) { \ 357 | GREATEST_SET_TIME(greatest_info.suite.pre_test); \ 358 | if (greatest_info.setup) { \ 359 | greatest_info.setup(greatest_info.setup_udata); \ 360 | } \ 361 | return 1; /* test should be run */ \ 362 | } else { \ 363 | return 0; /* skipped */ \ 364 | } \ 365 | } \ 366 | \ 367 | void greatest_post_test(const char *name, int res) { \ 368 | GREATEST_SET_TIME(greatest_info.suite.post_test); \ 369 | if (greatest_info.teardown) { \ 370 | void *udata = greatest_info.teardown_udata; \ 371 | greatest_info.teardown(udata); \ 372 | } \ 373 | \ 374 | if (res < 0) { \ 375 | greatest_do_fail(name); \ 376 | } else if (res > 0) { \ 377 | greatest_do_skip(name); \ 378 | } else if (res == 0) { \ 379 | greatest_do_pass(name); \ 380 | } \ 381 | greatest_info.suite.tests_run++; \ 382 | greatest_info.col++; \ 383 | if (GREATEST_IS_VERBOSE()) { \ 384 | GREATEST_CLOCK_DIFF(greatest_info.suite.pre_test, \ 385 | greatest_info.suite.post_test); \ 386 | fprintf(GREATEST_STDOUT, "\n"); \ 387 | } else if (greatest_info.col % greatest_info.width == 0) { \ 388 | fprintf(GREATEST_STDOUT, "\n"); \ 389 | greatest_info.col = 0; \ 390 | } \ 391 | if (GREATEST_STDOUT == stdout) fflush(stdout); \ 392 | } \ 393 | \ 394 | static void greatest_run_suite(greatest_suite_cb *suite_cb, \ 395 | const char *suite_name) { \ 396 | if (greatest_info.suite_filter && \ 397 | !greatest_name_match(suite_name, greatest_info.suite_filter)) { \ 398 | return; \ 399 | } \ 400 | if (GREATEST_FIRST_FAIL() && greatest_info.failed > 0) { return; } \ 401 | greatest_info.suite.tests_run = 0; \ 402 | greatest_info.suite.failed = 0; \ 403 | greatest_info.suite.passed = 0; \ 404 | greatest_info.suite.skipped = 0; \ 405 | greatest_info.suite.pre_suite = 0; \ 406 | greatest_info.suite.post_suite = 0; \ 407 | greatest_info.suite.pre_test = 0; \ 408 | greatest_info.suite.post_test = 0; \ 409 | greatest_info.col = 0; \ 410 | fprintf(GREATEST_STDOUT, "\n* Suite %s:\n", suite_name); \ 411 | GREATEST_SET_TIME(greatest_info.suite.pre_suite); \ 412 | suite_cb(); \ 413 | GREATEST_SET_TIME(greatest_info.suite.post_suite); \ 414 | if (greatest_info.suite.tests_run > 0) { \ 415 | fprintf(GREATEST_STDOUT, \ 416 | "\n%u tests - %u pass, %u fail, %u skipped", \ 417 | greatest_info.suite.tests_run, \ 418 | greatest_info.suite.passed, \ 419 | greatest_info.suite.failed, \ 420 | greatest_info.suite.skipped); \ 421 | GREATEST_CLOCK_DIFF(greatest_info.suite.pre_suite, \ 422 | greatest_info.suite.post_suite); \ 423 | fprintf(GREATEST_STDOUT, "\n"); \ 424 | } \ 425 | greatest_info.setup = NULL; \ 426 | greatest_info.setup_udata = NULL; \ 427 | greatest_info.teardown = NULL; \ 428 | greatest_info.teardown_udata = NULL; \ 429 | greatest_info.passed += greatest_info.suite.passed; \ 430 | greatest_info.failed += greatest_info.suite.failed; \ 431 | greatest_info.skipped += greatest_info.suite.skipped; \ 432 | greatest_info.tests_run += greatest_info.suite.tests_run; \ 433 | } \ 434 | \ 435 | void greatest_do_pass(const char *name) { \ 436 | if (GREATEST_IS_VERBOSE()) { \ 437 | fprintf(GREATEST_STDOUT, "PASS %s: %s", \ 438 | name, greatest_info.msg ? greatest_info.msg : ""); \ 439 | } else { \ 440 | fprintf(GREATEST_STDOUT, "."); \ 441 | } \ 442 | greatest_info.suite.passed++; \ 443 | } \ 444 | \ 445 | void greatest_do_fail(const char *name) { \ 446 | if (GREATEST_IS_VERBOSE()) { \ 447 | fprintf(GREATEST_STDOUT, \ 448 | "FAIL %s: %s (%s:%u)", \ 449 | name, greatest_info.msg ? greatest_info.msg : "", \ 450 | greatest_info.fail_file, greatest_info.fail_line); \ 451 | } else { \ 452 | fprintf(GREATEST_STDOUT, "F"); \ 453 | greatest_info.col++; \ 454 | /* add linebreak if in line of '.'s */ \ 455 | if (greatest_info.col != 0) { \ 456 | fprintf(GREATEST_STDOUT, "\n"); \ 457 | greatest_info.col = 0; \ 458 | } \ 459 | fprintf(GREATEST_STDOUT, "FAIL %s: %s (%s:%u)\n", \ 460 | name, \ 461 | greatest_info.msg ? greatest_info.msg : "", \ 462 | greatest_info.fail_file, greatest_info.fail_line); \ 463 | } \ 464 | greatest_info.suite.failed++; \ 465 | } \ 466 | \ 467 | void greatest_do_skip(const char *name) { \ 468 | if (GREATEST_IS_VERBOSE()) { \ 469 | fprintf(GREATEST_STDOUT, "SKIP %s: %s", \ 470 | name, \ 471 | greatest_info.msg ? \ 472 | greatest_info.msg : "" ); \ 473 | } else { \ 474 | fprintf(GREATEST_STDOUT, "s"); \ 475 | } \ 476 | greatest_info.suite.skipped++; \ 477 | } \ 478 | \ 479 | void greatest_usage(const char *name) { \ 480 | fprintf(GREATEST_STDOUT, \ 481 | "Usage: %s [-hlfv] [-s SUITE] [-t TEST]\n" \ 482 | " -h print this Help\n" \ 483 | " -l List suites and their tests, then exit\n" \ 484 | " -f Stop runner after first failure\n" \ 485 | " -v Verbose output\n" \ 486 | " -s SUITE only run suite named SUITE\n" \ 487 | " -t TEST only run test named TEST\n", \ 488 | name); \ 489 | } \ 490 | \ 491 | void GREATEST_SET_SETUP_CB(greatest_setup_cb *cb, void *udata) { \ 492 | greatest_info.setup = cb; \ 493 | greatest_info.setup_udata = udata; \ 494 | } \ 495 | \ 496 | void GREATEST_SET_TEARDOWN_CB(greatest_teardown_cb *cb, \ 497 | void *udata) { \ 498 | greatest_info.teardown = cb; \ 499 | greatest_info.teardown_udata = udata; \ 500 | } \ 501 | \ 502 | greatest_run_info greatest_info 503 | 504 | /* Handle command-line arguments, etc. */ 505 | #define GREATEST_MAIN_BEGIN() \ 506 | do { \ 507 | int i = 0; \ 508 | memset(&greatest_info, 0, sizeof(greatest_info)); \ 509 | if (greatest_info.width == 0) { \ 510 | greatest_info.width = GREATEST_DEFAULT_WIDTH; \ 511 | } \ 512 | for (i = 1; i < argc; i++) { \ 513 | if (0 == strcmp("-t", argv[i])) { \ 514 | if (argc <= i + 1) { \ 515 | greatest_usage(argv[0]); \ 516 | exit(EXIT_FAILURE); \ 517 | } \ 518 | greatest_info.test_filter = argv[i+1]; \ 519 | i++; \ 520 | } else if (0 == strcmp("-s", argv[i])) { \ 521 | if (argc <= i + 1) { \ 522 | greatest_usage(argv[0]); \ 523 | exit(EXIT_FAILURE); \ 524 | } \ 525 | greatest_info.suite_filter = argv[i+1]; \ 526 | i++; \ 527 | } else if (0 == strcmp("-f", argv[i])) { \ 528 | greatest_info.flags |= GREATEST_FLAG_FIRST_FAIL; \ 529 | } else if (0 == strcmp("-v", argv[i])) { \ 530 | greatest_info.flags |= GREATEST_FLAG_VERBOSE; \ 531 | } else if (0 == strcmp("-l", argv[i])) { \ 532 | greatest_info.flags |= GREATEST_FLAG_LIST_ONLY; \ 533 | } else if (0 == strcmp("-h", argv[i])) { \ 534 | greatest_usage(argv[0]); \ 535 | exit(EXIT_SUCCESS); \ 536 | } else { \ 537 | fprintf(GREATEST_STDOUT, \ 538 | "Unknown argument '%s'\n", argv[i]); \ 539 | greatest_usage(argv[0]); \ 540 | exit(EXIT_FAILURE); \ 541 | } \ 542 | } \ 543 | } while (0); \ 544 | GREATEST_SET_TIME(greatest_info.begin) 545 | 546 | #define GREATEST_MAIN_END() \ 547 | do { \ 548 | if (!GREATEST_LIST_ONLY()) { \ 549 | GREATEST_SET_TIME(greatest_info.end); \ 550 | fprintf(GREATEST_STDOUT, \ 551 | "\nTotal: %u tests", greatest_info.tests_run); \ 552 | GREATEST_CLOCK_DIFF(greatest_info.begin, \ 553 | greatest_info.end); \ 554 | fprintf(GREATEST_STDOUT, "\n"); \ 555 | fprintf(GREATEST_STDOUT, \ 556 | "Pass: %u, fail: %u, skip: %u.\n", \ 557 | greatest_info.passed, \ 558 | greatest_info.failed, greatest_info.skipped); \ 559 | } \ 560 | return (greatest_info.failed > 0 \ 561 | ? EXIT_FAILURE : EXIT_SUCCESS); \ 562 | } while (0) 563 | 564 | /* Make abbreviations without the GREATEST_ prefix for the 565 | * most commonly used symbols. */ 566 | #if GREATEST_USE_ABBREVS 567 | #define TEST GREATEST_TEST 568 | #define SUITE GREATEST_SUITE 569 | #define RUN_TEST GREATEST_RUN_TEST 570 | #define RUN_TEST1 GREATEST_RUN_TEST1 571 | #define RUN_SUITE GREATEST_RUN_SUITE 572 | #define ASSERT GREATEST_ASSERT 573 | #define ASSERTm GREATEST_ASSERTm 574 | #define ASSERT_FALSE GREATEST_ASSERT_FALSE 575 | #define ASSERT_EQ GREATEST_ASSERT_EQ 576 | #define ASSERT_STR_EQ GREATEST_ASSERT_STR_EQ 577 | #define ASSERT_FALSEm GREATEST_ASSERT_FALSEm 578 | #define ASSERT_EQm GREATEST_ASSERT_EQm 579 | #define ASSERT_STR_EQm GREATEST_ASSERT_STR_EQm 580 | #define PASS GREATEST_PASS 581 | #define FAIL GREATEST_FAIL 582 | #define SKIP GREATEST_SKIP 583 | #define PASSm GREATEST_PASSm 584 | #define FAILm GREATEST_FAILm 585 | #define SKIPm GREATEST_SKIPm 586 | #define SET_SETUP GREATEST_SET_SETUP_CB 587 | #define SET_TEARDOWN GREATEST_SET_TEARDOWN_CB 588 | 589 | #if __STDC_VERSION__ >= 19901L 590 | #endif /* C99 */ 591 | #define RUN_TESTp GREATEST_RUN_TESTp 592 | #endif /* USE_ABBREVS */ 593 | 594 | #endif 595 | -------------------------------------------------------------------------------- /test/test_coro.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Header implementing "protothreads" but with a stack to support 3 | local-varible state, argument-passing and sub-coroutines. 4 | 5 | version 1.0, november, 2018 6 | 7 | Copyright (C) 2018- Fredrik Kihlander 8 | 9 | https://github.com/wc-duck/coro 10 | 11 | This software is provided 'as-is', without any express or implied 12 | warranty. In no event will the authors be held liable for any damages 13 | arising from the use of this software. 14 | 15 | Permission is granted to anyone to use this software for any purpose, 16 | including commercial applications, and to alter it and redistribute it 17 | freely, subject to the following restrictions: 18 | 19 | 1. The origin of this software must not be misrepresented; you must not 20 | claim that you wrote the original software. If you use this software 21 | in a product, an acknowledgment in the product documentation would be 22 | appreciated but is not required. 23 | 2. Altered source versions must be plainly marked as such, and must not be 24 | misrepresented as being the original software. 25 | 3. This notice may not be removed or altered from any source distribution. 26 | 27 | Fredrik Kihlander 28 | */ 29 | 30 | #define CORO_TRACK_MAX_STACK_USAGE 0 31 | 32 | #include "greatest.h" 33 | #include "../coro.h" 34 | 35 | // test that basic yeald and local vars work... 36 | TEST coro_basic() 37 | { 38 | uint8_t stack[1024]; 39 | coro co; 40 | co_init(&co, stack, sizeof(stack), [](coro* co, void*, void*) { 41 | co_locals_begin(co); 42 | int cnt = 0; 43 | co_locals_end(co); 44 | 45 | co_begin(co); 46 | 47 | while((locals.cnt++) < 2) 48 | { 49 | co_yield(co); 50 | } 51 | 52 | co_end(co); 53 | }); 54 | 55 | ASSERT_FALSE(co_completed(&co)); 56 | co_resume(&co, nullptr); 57 | 58 | ASSERT_FALSE(co_completed(&co)); 59 | co_resume(&co, nullptr); 60 | 61 | ASSERT_FALSE(co_completed(&co)); 62 | co_resume(&co, nullptr); 63 | 64 | ASSERT(co_completed(&co)); 65 | 66 | #if CORO_TRACK_MAX_STACK_USAGE 67 | printf("max stack %d\n", co.stack_use_max); 68 | #endif 69 | 70 | return 0; 71 | } 72 | 73 | // test that coro work without locals... 74 | TEST coro_no_stack() 75 | { 76 | coro co; 77 | co_init(&co, nullptr, 0, [](coro* co, void*, void*) { 78 | static int cnt = 0; 79 | co_begin(co); 80 | 81 | while((cnt++) < 2) 82 | { 83 | co_yield(co); 84 | } 85 | 86 | co_end(co); 87 | }); 88 | 89 | ASSERT_FALSE(co_completed(&co)); 90 | co_resume(&co, nullptr); 91 | 92 | ASSERT_FALSE(co_completed(&co)); 93 | co_resume(&co, nullptr); 94 | 95 | ASSERT_FALSE(co_completed(&co)); 96 | co_resume(&co, nullptr); 97 | 98 | ASSERT(co_completed(&co)); 99 | 100 | #if CORO_TRACK_MAX_STACK_USAGE 101 | printf("max stack %d\n", co.stack_use_max); 102 | #endif 103 | 104 | return 0; 105 | } 106 | 107 | TEST coro_sub_call() 108 | { 109 | struct test_state 110 | { 111 | int coro_sub_call_sub_loop = 0; 112 | int coro_sub_call_loop = 0; 113 | } state; 114 | 115 | uint8_t stack[1024]; 116 | coro co; 117 | co_init(&co, stack, sizeof(stack), [](coro* co, void* userdata, void*){ 118 | co_locals_begin(co); 119 | int cnt = 0; 120 | co_locals_end(co); 121 | 122 | co_begin(co); 123 | 124 | for(; locals.cnt < 2; ++locals.cnt) 125 | { 126 | ++((test_state*)userdata)->coro_sub_call_loop; 127 | co_call(co, [](coro* co, void* userdata, void*){ 128 | co_locals_begin(co); 129 | unsigned int cnt = 0; 130 | co_locals_end(co); 131 | 132 | co_begin(co); 133 | 134 | for(; locals.cnt < 2; ++locals.cnt) 135 | { 136 | ++((test_state*)userdata)->coro_sub_call_sub_loop; 137 | co_yield(co); 138 | } 139 | 140 | co_end(co); 141 | }); 142 | } 143 | 144 | co_end(co); 145 | }); 146 | 147 | while(!co_completed(&co)) 148 | co_resume(&co, &state); 149 | 150 | ASSERT_EQ(2, state.coro_sub_call_loop); 151 | ASSERT_EQ(4, state.coro_sub_call_sub_loop); 152 | 153 | #if CORO_TRACK_MAX_STACK_USAGE 154 | printf("max stack %d\n", co.stack_use_max); 155 | #endif 156 | 157 | return 0; 158 | } 159 | 160 | TEST coro_with_args() 161 | { 162 | struct args 163 | { 164 | int input; 165 | int* output; 166 | }; 167 | 168 | int output = 7331; 169 | 170 | args a; 171 | a.input = 1337; 172 | a.output = &output; 173 | 174 | uint8_t stack[1024]; 175 | coro co; 176 | co_init(&co, stack, sizeof(stack), [](coro* co, void*, void* co_args) { 177 | args* arg = (args*)co_args; 178 | 179 | co_begin(co); 180 | 181 | *arg->output = arg->input; 182 | 183 | co_end(co); 184 | }, &a, sizeof(args), alignof(args)); 185 | 186 | co_resume(&co, nullptr); 187 | ASSERT(co_completed(&co)); 188 | 189 | ASSERT_EQ(1337, output); 190 | 191 | #if CORO_TRACK_MAX_STACK_USAGE 192 | printf("max stack %d\n", co.stack_use_max); 193 | #endif 194 | 195 | return 0; 196 | } 197 | 198 | TEST coro_with_args_in_subcall() 199 | { 200 | int coro_with_args_in_subcall_sum = 0; 201 | 202 | uint8_t stack[1024]; 203 | coro co; 204 | co_init(&co, stack, sizeof(stack), [](coro* co, void*, void*) { 205 | co_locals_begin(co); 206 | int cnt = 0; 207 | co_locals_end(co); 208 | 209 | co_begin(co); 210 | 211 | for(; locals.cnt < 2; ++locals.cnt) 212 | { 213 | co_call(co, [](coro* co, void* userdata, void* args){ 214 | int* arg = (int*)args; 215 | 216 | co_begin(co); 217 | 218 | *((int*)userdata) += *arg + 10; 219 | 220 | co_end(co); 221 | }, &locals.cnt, sizeof(int), alignof(int)); 222 | } 223 | 224 | co_end(co); 225 | }); 226 | 227 | co_resume(&co, &coro_with_args_in_subcall_sum); 228 | ASSERT(co_completed(&co)); 229 | 230 | ASSERT_EQ(21, coro_with_args_in_subcall_sum); 231 | 232 | #if CORO_TRACK_MAX_STACK_USAGE 233 | printf("max stack %d\n", co.stack_use_max); 234 | #endif 235 | 236 | return 0; 237 | } 238 | 239 | int coro_yield_without_braces() 240 | { 241 | uint8_t stack[1024]; 242 | coro co; 243 | co_init(&co, stack, sizeof(stack), [](coro* co, void*, void*) { 244 | co_locals_begin(co); 245 | int cnt = 0; 246 | co_locals_end(co); 247 | 248 | co_begin(co); 249 | 250 | while(true) 251 | { 252 | if(locals.cnt++ != 2) 253 | co_yield(co); 254 | else 255 | break; 256 | } 257 | 258 | co_end(co); 259 | }); 260 | 261 | co_resume(&co, nullptr); 262 | ASSERT(!co_completed(&co)); 263 | ASSERT(!co_waiting(&co)); 264 | 265 | co_resume(&co, nullptr); 266 | ASSERT(!co_completed(&co)); 267 | ASSERT(!co_waiting(&co)); 268 | 269 | co_resume(&co, nullptr); 270 | ASSERT(co_completed(&co)); 271 | ASSERT(!co_waiting(&co)); 272 | 273 | return 0; 274 | } 275 | 276 | int coro_wait_without_braces() 277 | { 278 | uint8_t stack[1024]; 279 | coro co; 280 | co_init(&co, stack, sizeof(stack), [](coro* co, void*, void*) { 281 | co_locals_begin(co); 282 | int cnt = 0; 283 | co_locals_end(co); 284 | 285 | co_begin(co); 286 | 287 | while(true) 288 | { 289 | if(locals.cnt++ != 2) 290 | co_wait(co); 291 | else 292 | break; 293 | } 294 | 295 | co_end(co); 296 | }); 297 | 298 | co_resume(&co, nullptr); 299 | ASSERT(!co_completed(&co)); 300 | ASSERT(co_waiting(&co)); 301 | 302 | co_resume(&co, nullptr); 303 | ASSERT(!co_completed(&co)); 304 | ASSERT(co_waiting(&co)); 305 | 306 | co_resume(&co, nullptr); 307 | ASSERT(co_completed(&co)); 308 | ASSERT(!co_waiting(&co)); 309 | 310 | return 0; 311 | } 312 | 313 | int coro_early_exit() 314 | { 315 | coro co; 316 | co_init(&co, nullptr, 0, [](coro* co, void* userdata, void*) { 317 | int* test = (int*)userdata; 318 | 319 | co_begin(co); 320 | 321 | *test = 1; co_yield(co); 322 | *test = 2; co_yield(co); 323 | *test = 3; co_exit(co); 324 | *test = 4; co_yield(co); 325 | 326 | co_end(co); 327 | }); 328 | 329 | int test = 0; 330 | 331 | while(!co_completed(&co)) 332 | co_resume(&co, &test); 333 | 334 | ASSERT_EQ(3, test); 335 | return 0; 336 | } 337 | 338 | int coro_early_exit_without_braces() 339 | { 340 | coro co; 341 | co_init(&co, nullptr, 0, [](coro* co, void* userdata, void*) { 342 | int* test = (int*)userdata; 343 | 344 | co_begin(co); 345 | 346 | while(true) 347 | if(++(*test) == 3) 348 | co_exit(co); 349 | 350 | co_end(co); 351 | }); 352 | 353 | int test = 0; 354 | 355 | while(!co_completed(&co)) 356 | co_resume(&co, &test); 357 | 358 | ASSERT_EQ(3, test); 359 | return 0; 360 | } 361 | 362 | static void alloc_140_bytes(coro* co, void*, void*) 363 | { 364 | co_locals_begin(co); 365 | uint8_t dauta[140]; 366 | co_locals_end(co); 367 | 368 | co_begin(co); 369 | for(int i = 0; i < (int)sizeof(locals.dauta); ++i) 370 | locals.dauta[i] = (uint8_t)i; 371 | 372 | co_yield(co); 373 | 374 | for(int i = 0; i < (int)sizeof(locals.dauta); ++i) 375 | assert(locals.dauta[i] == i); 376 | 377 | co_end(co); 378 | } 379 | 380 | int coro_stack_overflow_locals() 381 | { 382 | uint8_t stack1[128]; 383 | uint8_t stack2[256]; 384 | 385 | coro co; 386 | co_init(&co, stack1, sizeof(stack1), alloc_140_bytes); 387 | 388 | co_resume(&co, nullptr); 389 | ASSERT(co_stack_overflowed(&co)); 390 | 391 | ASSERT_EQ(stack1, co_replace_stack(&co, stack2, sizeof(stack2))); 392 | 393 | co_resume(&co, nullptr); 394 | ASSERT(!co_stack_overflowed(&co)); 395 | ASSERT(!co_completed(&co)); 396 | 397 | co_resume(&co, nullptr); 398 | ASSERT(!co_stack_overflowed(&co)); 399 | ASSERT(co_completed(&co)); 400 | 401 | return 0; 402 | } 403 | 404 | int coro_stack_overflow_locals_in_call() 405 | { 406 | uint8_t stack1[128]; 407 | uint8_t stack2[256]; 408 | 409 | coro co; 410 | co_init(&co, stack1, sizeof(stack1), [](coro* co, void*, void*) 411 | { 412 | co_begin(co); 413 | co_call(co, alloc_140_bytes); 414 | co_end(co); 415 | 416 | }); 417 | 418 | co_resume(&co, nullptr); 419 | ASSERT(co_stack_overflowed(&co)); 420 | 421 | ASSERT_EQ(stack1, co_replace_stack(&co, stack2, sizeof(stack2))); 422 | 423 | co_resume(&co, nullptr); 424 | ASSERT(!co_stack_overflowed(&co)); 425 | ASSERT(!co_completed(&co)); 426 | 427 | co_resume(&co, nullptr); 428 | ASSERT(!co_stack_overflowed(&co)); 429 | ASSERT(co_completed(&co)); 430 | 431 | return 0; 432 | } 433 | 434 | int coro_stack_overflow_args_in_co_call() 435 | { 436 | struct test_arg 437 | { 438 | uint8_t data[80]; 439 | } the_arg; 440 | 441 | for(int i = 0; i < (int)sizeof(the_arg.data); ++i) 442 | the_arg.data[i] = (uint8_t)i; 443 | 444 | uint8_t stack1[128]; 445 | uint8_t stack2[256]; 446 | 447 | coro co; 448 | co_init(&co, stack1, sizeof(stack1), [](coro* co, void*, void* arg){ 449 | test_arg* arg_ptr = (test_arg*)arg; 450 | co_begin(co); 451 | co_call(co, [](coro* co, void*, void* arg){ 452 | test_arg* arg_ptr = (test_arg*)arg; 453 | co_begin(co); 454 | for(int i = 0; i < (int)sizeof(arg_ptr->data); ++i) 455 | assert(arg_ptr->data[i] == (uint8_t)i ); 456 | co_end(co); 457 | }, *arg_ptr); 458 | co_end(co); 459 | }, the_arg); 460 | 461 | co_resume(&co, nullptr); 462 | ASSERT(co_stack_overflowed(&co)); 463 | 464 | ASSERT_EQ(stack1, co_replace_stack(&co, stack2, sizeof(stack2))); 465 | 466 | co_resume(&co, nullptr); 467 | ASSERT(!co_stack_overflowed(&co)); 468 | ASSERT(co_completed(&co)); 469 | 470 | return 0; 471 | } 472 | 473 | static void empty_coro(coro* co, void*, void*) 474 | { 475 | co_begin(co); 476 | co_end(co); 477 | } 478 | 479 | int coro_stack_overflow_call() 480 | { 481 | static const size_t stack1_size = 128; 482 | uint8_t stack1[stack1_size]; 483 | uint8_t stack2[stack1_size*2]; 484 | 485 | coro co; 486 | co_init(&co, stack1, sizeof(stack1), [](coro* co, void*, void*){ 487 | co_locals_begin(co); 488 | uint8_t data[stack1_size]; // filling the stack to the max! 489 | co_locals_end(co); 490 | 491 | co_begin(co); 492 | (void)locals; 493 | 494 | // this call should make stuff overflow! 495 | co_call(co, empty_coro); 496 | co_end(co); 497 | }); 498 | 499 | co_resume(&co, nullptr); 500 | ASSERT(co_stack_overflowed(&co)); 501 | ASSERT(!co_completed(&co)); 502 | 503 | ASSERT_EQ(stack1, co_replace_stack(&co, stack2, sizeof(stack2))); 504 | 505 | co_resume(&co, nullptr); 506 | ASSERT(!co_stack_overflowed(&co)); 507 | ASSERT(co_completed(&co)); 508 | 509 | return 0; 510 | } 511 | 512 | int coro_stack_overflow_call_in_call() 513 | { 514 | static const size_t stack1_size = 128; 515 | uint8_t stack1[stack1_size]; 516 | uint8_t stack2[stack1_size*2]; 517 | 518 | coro co; 519 | co_init(&co, stack1, sizeof(stack1), [](coro* co, void*, void*){ 520 | co_begin(co); 521 | co_call(co, [](coro* co, void*, void*){ 522 | co_locals_begin(co); 523 | uint8_t data[stack1_size]; // filling the stack to the max! 524 | co_locals_end(co); 525 | 526 | co_begin(co); 527 | (void)locals; 528 | 529 | // this call should make stuff overflow! 530 | co_call(co, empty_coro); 531 | co_end(co); 532 | }); 533 | co_end(co); 534 | }); 535 | 536 | co_resume(&co, nullptr); 537 | ASSERT(co_stack_overflowed(&co)); 538 | ASSERT(!co_completed(&co)); 539 | 540 | ASSERT_EQ(stack1, co_replace_stack(&co, stack2, sizeof(stack2))); 541 | 542 | co_resume(&co, nullptr); 543 | ASSERT(!co_stack_overflowed(&co)); 544 | ASSERT(co_completed(&co)); 545 | 546 | return 0; 547 | } 548 | 549 | GREATEST_SUITE( coro_tests ) 550 | { 551 | RUN_TEST( coro_basic ); 552 | RUN_TEST( coro_no_stack ); 553 | RUN_TEST( coro_sub_call ); 554 | RUN_TEST( coro_with_args ); 555 | RUN_TEST( coro_with_args_in_subcall ); 556 | RUN_TEST( coro_yield_without_braces ); 557 | RUN_TEST( coro_wait_without_braces ); 558 | RUN_TEST( coro_early_exit ); 559 | RUN_TEST( coro_early_exit_without_braces ); 560 | RUN_TEST( coro_stack_overflow_locals ); 561 | RUN_TEST( coro_stack_overflow_locals_in_call ); 562 | RUN_TEST( coro_stack_overflow_args_in_co_call ); 563 | RUN_TEST( coro_stack_overflow_call ); 564 | RUN_TEST( coro_stack_overflow_call_in_call ); 565 | } 566 | 567 | GREATEST_MAIN_DEFS(); 568 | 569 | int main( int argc, char **argv ) 570 | { 571 | GREATEST_MAIN_BEGIN(); 572 | RUN_SUITE( coro_tests ); 573 | GREATEST_MAIN_END(); 574 | } 575 | --------------------------------------------------------------------------------