├── .gitignore ├── .ycm_extra_conf.py.in ├── CMakeLists.txt ├── LICENSE ├── README.md ├── examples ├── CMakeLists.txt ├── backend │ ├── CMakeLists.txt │ ├── asymmetric_coroutine.cpp │ ├── context.cpp │ ├── context_pool.cpp │ └── coroutine.cpp └── chaining_coroutines.cpp ├── include └── coroutine │ ├── asymmetric_coroutine.hpp │ ├── backend │ ├── asymmetric_coroutine.hpp │ ├── context.hpp │ ├── context_pool.hpp │ ├── coroutine.hpp │ ├── detail │ │ └── aligned_union.hpp │ ├── linux │ │ ├── context.hpp │ │ └── stack.hpp │ └── windows │ │ └── context.hpp │ ├── callback.hpp │ ├── context.hpp │ └── sized_memory_block.hpp └── src ├── backend ├── context_pool.cpp ├── linux │ └── context.cpp └── windows │ └── context.cpp └── sized_memory_block.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | # Build directory 35 | build/ 36 | 37 | # YCM config 38 | *.pyc 39 | .ycm_extra_conf.py 40 | 41 | # Visual Studio stuff 42 | .vs/ 43 | -------------------------------------------------------------------------------- /.ycm_extra_conf.py.in: -------------------------------------------------------------------------------- 1 | 2 | # This file is NOT licensed under the GPLv3, which is the license for the rest 3 | # of YouCompleteMe. 4 | # 5 | # Here's the license text for this file: 6 | # 7 | # This is free and unencumbered software released into the public domain. 8 | # 9 | # Anyone is free to copy, modify, publish, use, compile, sell, or 10 | # distribute this software, either in source code form or as a compiled 11 | # binary, for any purpose, commercial or non-commercial, and by any 12 | # means. 13 | # 14 | # In jurisdictions that recognize copyright laws, the author or authors 15 | # of this software dedicate any and all copyright interest in the 16 | # software to the public domain. We make this dedication for the benefit 17 | # of the public at large and to the detriment of our heirs and 18 | # successors. We intend this dedication to be an overt act of 19 | # relinquishment in perpetuity of all present and future rights to this 20 | # software under copyright law. 21 | # 22 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 23 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 24 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 25 | # IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 26 | # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 27 | # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 28 | # OTHER DEALINGS IN THE SOFTWARE. 29 | # 30 | # For more information, please refer to 31 | 32 | import os 33 | import ycm_core 34 | 35 | # These are the compilation flags that will be used in case there's no 36 | # compilation database set (by default, one is not set). 37 | # CHANGE THIS LIST OF FLAGS. YES, THIS IS THE DROID YOU HAVE BEEN LOOKING FOR. 38 | flags = [ 39 | ] 40 | 41 | SOURCEDIR = '${CMAKE_SOURCE_DIR}' 42 | SRC_DIR = os.path.join(SOURCEDIR, 'src') 43 | INCLUDE_DIR = os.path.join(SOURCEDIR, 'include', '${CMAKE_PROJECT_NAME}') 44 | BINARY_DIR = '${CMAKE_BINARY_DIR}' 45 | 46 | # Set this to the absolute path to the folder (NOT the file!) containing the 47 | # compile_commands.json file to use that instead of 'flags'. See here for 48 | # more details: http://clang.llvm.org/docs/JSONCompilationDatabase.html 49 | # 50 | # You can get CMake to generate this file for you by adding: 51 | # set( CMAKE_EXPORT_COMPILE_COMMANDS 1 ) 52 | # to your CMakeLists.txt file. 53 | # 54 | # Most projects will NOT need to set this to anything; you can just change the 55 | # 'flags' list of compilation flags. Notice that YCM itself uses that approach. 56 | compilation_database_folder = '${CMAKE_BINARY_DIR}' 57 | 58 | if os.path.exists( compilation_database_folder ): 59 | database = ycm_core.CompilationDatabase( compilation_database_folder ) 60 | else: 61 | database = None 62 | 63 | SOURCE_EXTENSIONS = [ '.cpp', '.cxx', '.cc', '.c', '.m', '.mm' ] 64 | 65 | def DirectoryOfThisScript(): 66 | return os.path.dirname( os.path.abspath( __file__ ) ) 67 | 68 | 69 | def MakeRelativePathsInFlagsAbsolute( flags, working_directory ): 70 | if not working_directory: 71 | return list( flags ) 72 | new_flags = [] 73 | make_next_absolute = False 74 | path_flags = [ '-isystem', '-I', '-iquote', '--sysroot=' ] 75 | for flag in flags: 76 | new_flag = flag 77 | 78 | if make_next_absolute: 79 | make_next_absolute = False 80 | if not flag.startswith( '/' ): 81 | new_flag = os.path.join( working_directory, flag ) 82 | 83 | for path_flag in path_flags: 84 | if flag == path_flag: 85 | make_next_absolute = True 86 | break 87 | 88 | if flag.startswith( path_flag ): 89 | path = flag[ len( path_flag ): ] 90 | new_flag = path_flag + os.path.join( working_directory, path ) 91 | break 92 | 93 | if new_flag: 94 | new_flags.append( new_flag ) 95 | return new_flags 96 | 97 | 98 | def IsHeaderFile( filename ): 99 | extension = os.path.splitext( filename )[ 1 ] 100 | return extension in [ '.h', '.hxx', '.hpp', '.hh' ] 101 | 102 | def ReplacementFile( headerfile, source_extension ): 103 | file_path, file_name = os.path.split(headerfile) 104 | name, _ = os.path.splitext(file_name) 105 | relative_path = os.path.relpath(file_path, INCLUDE_DIR) 106 | return os.path.join(SRC_DIR, relative_path, name + source_extension) 107 | 108 | def GetCompilationInfoForFile( filename ): 109 | # The compilation_commands.json file generated by CMake does not have entries 110 | # for header files. So we do our best by asking the db for flags for a 111 | # corresponding source file, if any. If one exists, the flags for that file 112 | # should be good enough. 113 | if IsHeaderFile( filename ): 114 | for extension in SOURCE_EXTENSIONS: 115 | replacement_file = ReplacementFile(filename, extension) 116 | if os.path.exists( replacement_file ): 117 | compilation_info = database.GetCompilationInfoForFile( 118 | replacement_file ) 119 | if compilation_info.compiler_flags_: 120 | return compilation_info 121 | return None 122 | return database.GetCompilationInfoForFile( filename ) 123 | 124 | 125 | def FlagsForFile( filename, **kwargs ): 126 | if database: 127 | # Bear in mind that compilation_info.compiler_flags_ does NOT return a 128 | # python list, but a "list-like" StringVec object 129 | compilation_info = GetCompilationInfoForFile( filename ) 130 | if not compilation_info: 131 | return None 132 | 133 | final_flags = MakeRelativePathsInFlagsAbsolute( 134 | compilation_info.compiler_flags_, 135 | compilation_info.compiler_working_dir_ ) 136 | 137 | # NOTE: This is just for YouCompleteMe; it's highly likely that your project 138 | # does NOT need to remove the stdlib flag. DO NOT USE THIS IN YOUR 139 | # ycm_extra_conf IF YOU'RE NOT 100% SURE YOU NEED IT. 140 | try: 141 | final_flags.remove( '-stdlib=libc++' ) 142 | except ValueError: 143 | pass 144 | else: 145 | relative_to = DirectoryOfThisScript() 146 | final_flags = MakeRelativePathsInFlagsAbsolute( flags, relative_to ) 147 | 148 | print final_flags 149 | 150 | return { 151 | 'flags': final_flags, 152 | 'do_cache': False 153 | } 154 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.1) 2 | project(coroutine) 3 | 4 | set(CMAKE_CXX_STANDARD 11) 5 | set(includedir "${CMAKE_SOURCE_DIR}/include/coroutine") 6 | set(srcdir "${CMAKE_SOURCE_DIR}/src") 7 | 8 | set(linux_sources 9 | ${srcdir}/backend/linux/context.cpp 10 | ) 11 | set(linux_headers 12 | ${includedir}/backend/linux/context.hpp 13 | ) 14 | set(windows_sources 15 | ${srcdir}/backend/windows/context.cpp 16 | ) 17 | set(windows_headers 18 | ${includedir}/backend/windows/context.hpp 19 | ) 20 | set(sources 21 | ${srcdir}/sized_memory_block.cpp 22 | ${srcdir}/backend/context_pool.cpp 23 | ) 24 | set(headers 25 | ${includedir}/sized_memory_block.hpp 26 | ${includedir}/callback.hpp 27 | ${includedir}/backend/context.hpp 28 | ${includedir}/backend/context_pool.hpp 29 | ) 30 | 31 | add_library(coroutine 32 | ${sources} 33 | ${headers} 34 | ) 35 | 36 | target_include_directories(coroutine PUBLIC "${CMAKE_SOURCE_DIR}/include/") 37 | 38 | if(UNIX AND NOT APPLE) 39 | message(STATUS "Using linux sources") 40 | target_sources(coroutine PUBLIC ${linux_headers} ${linux_sources}) 41 | elseif(WIN32) 42 | message(STATUS "Using windows sources") 43 | target_sources(coroutine PUBLIC ${windows_headers} ${windows_sources}) 44 | endif() 45 | 46 | option(COROUTINE_BUILD_EXAMPLES "Build coroutine examples" ON) 47 | 48 | if(COROUTINE_BUILD_EXAMPLES) 49 | add_subdirectory(examples) 50 | endif() 51 | 52 | configure_file( 53 | "${CMAKE_SOURCE_DIR}/.ycm_extra_conf.py.in" 54 | "${CMAKE_SOURCE_DIR}/.ycm_extra_conf.py" 55 | ) 56 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Manu Sánchez 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # coroutine 2 | A simple stackful coroutine implementation in C++11, using Linux ucontext and Windows Fibers 3 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(backend) 2 | 3 | add_executable(examples-backend-chaining_coroutines chaining_coroutines.cpp) 4 | target_link_libraries(examples-backend-chaining_coroutines PRIVATE coroutine) 5 | -------------------------------------------------------------------------------- /examples/backend/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(examples-backend-context context.cpp) 2 | target_link_libraries(examples-backend-context PRIVATE coroutine) 3 | 4 | add_executable(examples-backend-context-pool context_pool.cpp) 5 | target_link_libraries(examples-backend-context-pool PRIVATE coroutine) 6 | 7 | add_executable(examples-backend-coroutine coroutine.cpp) 8 | target_link_libraries(examples-backend-coroutine PRIVATE coroutine) 9 | 10 | add_executable(examples-backend-asymmetric_coroutine asymmetric_coroutine.cpp) 11 | target_link_libraries(examples-backend-asymmetric_coroutine PRIVATE coroutine) 12 | -------------------------------------------------------------------------------- /examples/backend/asymmetric_coroutine.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using pull_t = coro::back::asymmetric_coroutine::pull_type; 5 | using push_t = coro::back::asymmetric_coroutine::push_type; 6 | 7 | int main() 8 | { 9 | pull_t source{[&](push_t& sink) 10 | { 11 | for(int i = 0; i < 3; ++i) 12 | { 13 | std::cout << "source " << i << "\n"; 14 | sink(i); 15 | } 16 | 17 | std::cout << "end of source\n"; 18 | }}; 19 | 20 | for(int i : source) 21 | { 22 | std::cout << "main thread " << i << "\n"; 23 | } 24 | 25 | std::cout << "end of main thread\n"; 26 | } 27 | -------------------------------------------------------------------------------- /examples/backend/context.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | void foo(int n); 5 | void bar(int n); 6 | 7 | coro::back::context main_ctx, foo_ctx, bar_ctx; 8 | 9 | char foo_stack[1024]; 10 | char bar_stack[1024]; 11 | 12 | auto foo_call = coro::make_stateful_callback([] 13 | { 14 | foo(42); 15 | }); 16 | auto bar_call = coro::make_stateful_callback([] 17 | { 18 | bar(42); 19 | }); 20 | 21 | void foo(int n) 22 | { 23 | for(int i = 0; i < n; ++i) 24 | { 25 | std::cout << "foo " << i << "\n"; 26 | coro::back::swap_context(foo_ctx, bar_ctx); 27 | } 28 | 29 | std::cout << "end foo\n"; 30 | coro::back::swap_context(foo_ctx, main_ctx); 31 | } 32 | 33 | void bar(int n) 34 | { 35 | for(int i = 0; i < n; ++i) 36 | { 37 | std::cout << "bar " << i << "\n"; 38 | coro::back::swap_context(bar_ctx, foo_ctx); 39 | } 40 | 41 | std::cout << "end bar\n"; 42 | coro::back::swap_context(foo_ctx, main_ctx); 43 | } 44 | 45 | int main() 46 | { 47 | main_ctx = coro::back::get_current_context(); 48 | foo_ctx = coro::back::make_context(foo_call, coro::static_block(foo_stack), main_ctx); 49 | bar_ctx = coro::back::make_context(bar_call, coro::static_block(bar_stack), main_ctx); 50 | 51 | coro::back::swap_context(main_ctx, foo_ctx); 52 | std::cout << "end of main thread\n"; 53 | } 54 | -------------------------------------------------------------------------------- /examples/backend/context_pool.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | void foo(int n); 5 | void bar(int n); 6 | 7 | auto foo_call = coro::make_stateful_callback([] 8 | { 9 | foo(42); 10 | }); 11 | auto bar_call = coro::make_stateful_callback([] 12 | { 13 | bar(42); 14 | }); 15 | 16 | std::size_t foo_id = 0; 17 | std::size_t bar_id = 0; 18 | 19 | void foo(int n) 20 | { 21 | for(int i = 0; i < n; ++i) 22 | { 23 | std::cout << "foo " << i << "\n"; 24 | coro::back::get_context_pool().switch_to(bar_id); 25 | } 26 | 27 | std::cout << "end foo\n"; 28 | } 29 | 30 | void bar(int n) 31 | { 32 | for(int i = 0; i < n; ++i) 33 | { 34 | std::cout << "bar " << i << "\n"; 35 | coro::back::get_context_pool().yield(); 36 | } 37 | 38 | std::cout << "end bar\n"; 39 | } 40 | 41 | int main() 42 | { 43 | foo_id = coro::back::get_context_pool().make_context(foo_call, "foo"); 44 | bar_id = coro::back::get_context_pool().make_context(bar_call, "bar"); 45 | coro::back::get_context_pool().switch_to(foo_id); 46 | 47 | std::cout << "end of main thread\n"; 48 | } 49 | -------------------------------------------------------------------------------- /examples/backend/coroutine.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using coro_t = coro::back::coroutine; 5 | 6 | int main() 7 | { 8 | coro_t* a_ref; 9 | coro_t* b_ref; 10 | 11 | coro_t a{[&](coro_t& coro) 12 | { 13 | auto n = coro.get(); 14 | for(int i = 0; i < n; ++i) 15 | { 16 | std::cout << "a " << i << "\n"; 17 | coro.switch_to(*b_ref, i); 18 | } 19 | 20 | std::cout << "end of a\n"; 21 | }}; 22 | 23 | coro_t b{[&](coro_t& coro) 24 | { 25 | while(*a_ref) 26 | { 27 | std::cout << "b " << a_ref->get() << "\n"; 28 | coro.switch_to(*a_ref); 29 | } 30 | 31 | std::cout << "end of b\n"; 32 | }}; 33 | 34 | a_ref = &a; 35 | b_ref = &b; 36 | 37 | a(42); 38 | 39 | std::cout << "end of main thread\n"; 40 | } 41 | -------------------------------------------------------------------------------- /examples/chaining_coroutines.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | typedef coro::asymmetric_coroutine coro_t; 9 | using namespace std::placeholders; 10 | 11 | // deliver each line of input stream to sink as a separate string 12 | void readlines(coro_t::push_type& sink, std::istream& in){ 13 | std::string line; 14 | while(std::getline(in,line)) 15 | sink(line); 16 | } 17 | 18 | void tokenize(coro_t::push_type& sink, coro_t::pull_type& source){ 19 | // This tokenizer doesn't happen to be stateful: you could reasonably 20 | // implement it with a single call to push each new token downstream. But 21 | // I've worked with stateful tokenizers, in which the meaning of input 22 | // characters depends in part on their position within the input line. 23 | for(std::string line : source){ 24 | std::string::size_type pos = 0; 25 | while(pos < line.length()){ 26 | if (line[pos] == '"'){ 27 | std::string token; 28 | ++pos; // skip open quote 29 | while (pos < line.length() && line[pos] != '"') 30 | token += line[pos++]; 31 | ++pos; // skip close quote 32 | sink(token); // pass token downstream 33 | } else if (std::isspace(line[pos])){ 34 | ++pos; // outside quotes, ignore whitespace 35 | } else if (std::isalpha(line[pos])){ 36 | std::string token; 37 | while (pos < line.length() && std::isalpha(line[pos])) 38 | token += line[pos++]; 39 | sink(token); // pass token downstream 40 | } else { // punctuation 41 | sink(std::string(1,line[pos++])); 42 | } 43 | } 44 | } 45 | } 46 | 47 | void only_words(coro_t::push_type& sink,coro_t::pull_type& source){ 48 | for(std::string token : source){ 49 | if (!token.empty() && std::isalpha(token[0])) 50 | sink(token); 51 | } 52 | } 53 | 54 | void trace(coro_t::push_type& sink, coro_t::pull_type& source){ 55 | for(std::string token : source){ 56 | std::cout << "trace: '" << token << "'\n"; 57 | sink(token); 58 | } 59 | } 60 | 61 | struct FinalEOL{ 62 | ~FinalEOL(){ 63 | std::cout << std::endl; 64 | } 65 | }; 66 | 67 | void layout(coro_t::pull_type& source,int num,int width){ 68 | // Finish the last line when we leave by whatever means 69 | FinalEOL eol; 70 | 71 | // Pull values from upstream, lay them out 'num' to a line 72 | for (;;){ 73 | for (int i = 0; i < num; ++i){ 74 | // when we exhaust the input, stop 75 | if (!source) return; 76 | 77 | std::cout << std::setw(width) << source.get(); 78 | // now that we've handled this item, advance to next 79 | source(); 80 | } 81 | // after 'num' items, line break 82 | std::cout << std::endl; 83 | } 84 | } 85 | 86 | int main() 87 | { 88 | // For example purposes, instead of having a separate text file in the 89 | // local filesystem, construct an istringstream to read. 90 | std::string data( 91 | "This is the first line.\n" 92 | "This, the second.\n" 93 | "The third has \"a phrase\"!\n" 94 | ); 95 | 96 | { 97 | std::cout << "\nfilter:\n"; 98 | std::istringstream infile(data); 99 | coro_t::pull_type reader(std::bind(readlines, _1, std::ref(infile))); 100 | coro_t::pull_type tokenizer(std::bind(tokenize, _1, std::ref(reader))); 101 | coro_t::pull_type filter(std::bind(only_words, _1, std::ref(tokenizer))); 102 | coro_t::pull_type tracer(std::bind(trace, _1, std::ref(filter))); 103 | for(std::string token : tracer){ 104 | // just iterate, we're already pulling through tracer 105 | } 106 | } 107 | 108 | { 109 | std::cout << "\nlayout() as coroutine::push_type:\n"; 110 | std::istringstream infile(data); 111 | coro_t::pull_type reader(std::bind(readlines, _1, std::ref(infile))); 112 | coro_t::pull_type tokenizer(std::bind(tokenize, _1, std::ref(reader))); 113 | coro_t::pull_type filter(std::bind(only_words, _1, std::ref(tokenizer))); 114 | coro_t::push_type writer(std::bind(layout, _1, 5, 15)); 115 | for(std::string token : filter){ 116 | writer(token); 117 | } 118 | } 119 | 120 | { 121 | std::cout << "\nfiltering output:\n"; 122 | std::istringstream infile(data); 123 | coro_t::pull_type reader(std::bind(readlines,_1,std::ref(infile))); 124 | coro_t::pull_type tokenizer(std::bind(tokenize,_1,std::ref(reader))); 125 | coro_t::push_type writer(std::bind(layout,_1,5,15)); 126 | // Because of the symmetry of the API, we can use any of these 127 | // chaining functions in a push_type coroutine chain as well. 128 | coro_t::push_type filter(std::bind(only_words,std::ref(writer),_1)); 129 | for(std::string token : tokenizer){ 130 | filter(token); 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /include/coroutine/asymmetric_coroutine.hpp: -------------------------------------------------------------------------------- 1 | #ifndef COROUTINE_ASYMMETRIC_COROUTINE_HPP 2 | #define COROUTINE_ASYMMETRIC_COROUTINE_HPP 3 | 4 | #include 5 | 6 | namespace coro 7 | { 8 | 9 | template 10 | struct asymmetric_coroutine 11 | { 12 | using pull_type = coro::back::asymmetric_coroutine::pull_type; 13 | using push_type = coro::back::asymmetric_coroutine::push_type; 14 | }; 15 | 16 | } 17 | 18 | #endif // COROUTINE_ASYMMETRIC_COROUTINE_HPP 19 | -------------------------------------------------------------------------------- /include/coroutine/backend/asymmetric_coroutine.hpp: -------------------------------------------------------------------------------- 1 | #ifndef COROUTINE_BACKEND_ASYMMETRIC_COROUTINE_HPP 2 | #define COROUTINE_BACKEND_ASYMMETRIC_COROUTINE_HPP 3 | 4 | #include 5 | #include 6 | 7 | namespace coro 8 | { 9 | 10 | namespace back 11 | { 12 | 13 | namespace asymmetric_coroutine 14 | { 15 | 16 | template 17 | class push_type; 18 | 19 | template 20 | class coroutine_proxy 21 | { 22 | public: 23 | enum class proxy_mode 24 | { 25 | instance, 26 | reference 27 | } mode; 28 | 29 | using instance_t = coro::back::coroutine; 30 | using reference_t = coro::back::coroutine*; 31 | 32 | using storage_t = typename coro::back::detail::aligned_union< 33 | 0, 34 | reference_t, 35 | instance_t 36 | >::type; 37 | 38 | template 39 | coroutine_proxy(Function&& function) : 40 | mode{proxy_mode::instance} 41 | { 42 | new(&storage) instance_t{std::forward(function)}; 43 | } 44 | 45 | coroutine_proxy(coro::back::coroutine* reference) : 46 | mode{proxy_mode::reference} 47 | { 48 | new (&storage) reference_t{reference}; 49 | } 50 | 51 | coroutine_proxy(const coroutine_proxy&) = delete; 52 | coroutine_proxy(coroutine_proxy&&) = delete; 53 | coroutine_proxy& operator=(const coroutine_proxy&) = delete; 54 | coroutine_proxy& operator=(coroutine_proxy&&) = delete; 55 | 56 | ~coroutine_proxy() 57 | { 58 | if(mode == proxy_mode::instance) 59 | { 60 | get_coro()->~instance_t(); 61 | } 62 | } 63 | 64 | coro::back::coroutine* get_coro() 65 | { 66 | switch(mode) 67 | { 68 | case proxy_mode::instance: 69 | return reinterpret_cast(&storage); 70 | case proxy_mode::reference: 71 | return *reinterpret_cast(&storage); 72 | } 73 | } 74 | 75 | const coro::back::coroutine* get_coro() const 76 | { 77 | switch(mode) 78 | { 79 | case proxy_mode::instance: 80 | return reinterpret_cast(&storage); 81 | case proxy_mode::reference: 82 | return *reinterpret_cast(&storage); 83 | } 84 | } 85 | 86 | storage_t storage; 87 | }; 88 | 89 | template 90 | class pull_type 91 | { 92 | using proxy = coro::back::asymmetric_coroutine::coroutine_proxy; 93 | using coroutine = coro::back::coroutine; 94 | using push_type = coro::back::asymmetric_coroutine::push_type; 95 | 96 | friend push_type; 97 | 98 | public: 99 | template 100 | pull_type(Function coro_function) : 101 | _proxy{[this, coro_function](coroutine& coro) 102 | { 103 | push_type push{this}; 104 | coro_function(push); 105 | }} 106 | {} 107 | 108 | private: 109 | pull_type(push_type* push) : 110 | _proxy{push->_proxy.get_coro()} 111 | {} 112 | 113 | public: 114 | bool alive() const 115 | { 116 | return _proxy.get_coro()->alive(); 117 | } 118 | 119 | operator bool() const 120 | { 121 | return alive(); 122 | } 123 | 124 | const T& get() const 125 | { 126 | return _proxy.get_coro()->get(); 127 | } 128 | 129 | pull_type& operator()() 130 | { 131 | if(alive()) 132 | { 133 | _proxy.get_coro()->asymmetric_switch(); 134 | } 135 | return *this; 136 | } 137 | 138 | pull_type& yield() 139 | { 140 | if(alive()) 141 | { 142 | _proxy.get_coro()->asymmetric_yield(); 143 | } 144 | return *this; 145 | } 146 | 147 | class iterator 148 | { 149 | private: 150 | void fetch() 151 | { 152 | _value = _self->get(); 153 | } 154 | 155 | void increment() 156 | { 157 | (*_self)(); 158 | fetch(); 159 | } 160 | 161 | public: 162 | iterator() : 163 | _self{nullptr} 164 | {} 165 | 166 | iterator(pull_type* self) : 167 | _self{self} 168 | { 169 | increment(); 170 | } 171 | 172 | iterator& operator++() 173 | { 174 | increment(); 175 | return *this; 176 | } 177 | 178 | const T& operator*() const 179 | { 180 | return _value; 181 | } 182 | 183 | friend bool operator==(const iterator& lhs, const iterator& rhs) 184 | { 185 | return lhs._self != nullptr && !lhs._self->alive(); 186 | } 187 | 188 | friend bool operator!=(const iterator& lhs, const iterator& rhs) 189 | { 190 | return !(lhs == rhs); 191 | } 192 | 193 | private: 194 | pull_type* _self; 195 | T _value; 196 | }; 197 | 198 | using const_iterator = iterator; 199 | 200 | iterator begin() 201 | { 202 | return {this}; 203 | } 204 | 205 | iterator end() 206 | { 207 | return {}; 208 | } 209 | 210 | const_iterator begin() const 211 | { 212 | return {this}; 213 | } 214 | 215 | const_iterator end() const 216 | { 217 | return {}; 218 | } 219 | 220 | private: 221 | proxy _proxy; 222 | }; 223 | 224 | template 225 | class push_type 226 | { 227 | using proxy = coro::back::asymmetric_coroutine::coroutine_proxy; 228 | using coroutine = coro::back::coroutine; 229 | using pull_type = coro::back::asymmetric_coroutine::pull_type; 230 | 231 | friend pull_type; 232 | 233 | public: 234 | template 235 | push_type(Function coro_function) : 236 | _proxy{[this, coro_function](coroutine& coro) 237 | { 238 | pull_type pull{this}; 239 | coro_function(pull); 240 | }} 241 | {} 242 | 243 | private: 244 | push_type(pull_type* pull) : 245 | _proxy{pull->_proxy.get_coro()} 246 | {} 247 | 248 | public: 249 | bool alive() const 250 | { 251 | return _proxy.get_coro()->alive(); 252 | } 253 | 254 | operator bool() const 255 | { 256 | return alive(); 257 | } 258 | 259 | template 260 | push_type& set(Value&& value) 261 | { 262 | _proxy.get_coro()->set(std::forward(value)); 263 | return *this; 264 | } 265 | 266 | template 267 | push_type& operator()(Value&& value) 268 | { 269 | return yield(std::forward(value)); 270 | } 271 | 272 | push_type& operator()() 273 | { 274 | return yield(); 275 | } 276 | 277 | push_type& yield() 278 | { 279 | _proxy.get_coro()->assymetric_yield(); 280 | return *this; 281 | } 282 | 283 | template 284 | push_type& yield(Value&& value) 285 | { 286 | set(std::forward(value)); 287 | return yield(); 288 | } 289 | 290 | class iterator 291 | { 292 | public: 293 | iterator() : 294 | _self{nullptr} 295 | {} 296 | 297 | iterator(push_type* self) : 298 | _self{self} 299 | {} 300 | 301 | iterator& operator++() 302 | { 303 | return *this; 304 | } 305 | 306 | iterator& operator*() 307 | { 308 | return *this; 309 | } 310 | 311 | template 312 | iterator& operator=(Value&& value) 313 | { 314 | (*_self)(std::forward(value)); 315 | return *this; 316 | } 317 | 318 | friend bool operator==(const iterator& lhs, const iterator& rhs) 319 | { 320 | return lhs._self == rhs._self && lhs._self != nullptr && !lhs._self->alive(); 321 | } 322 | 323 | friend bool operator!=(const iterator& lhs, const iterator& rhs) 324 | { 325 | return !(lhs == rhs); 326 | } 327 | 328 | private: 329 | push_type* _self; 330 | }; 331 | 332 | using const_iterator = iterator; 333 | 334 | iterator begin() 335 | { 336 | return {this}; 337 | } 338 | 339 | iterator end() 340 | { 341 | return {}; 342 | } 343 | 344 | const_iterator begin() const 345 | { 346 | return {this}; 347 | } 348 | 349 | const_iterator end() const 350 | { 351 | return {}; 352 | } 353 | 354 | private: 355 | proxy _proxy; 356 | }; 357 | 358 | } 359 | 360 | } 361 | 362 | } 363 | 364 | #endif // COROUTINE_BACKEND_ASYMMETRIC_COROUTINE_HPP 365 | -------------------------------------------------------------------------------- /include/coroutine/backend/context.hpp: -------------------------------------------------------------------------------- 1 | #ifndef COROUTINE_BACKEND_CONTEXT_HPP 2 | #define COROUTINE_BACKEND_CONTEXT_HPP 3 | 4 | #ifdef _WIN32 5 | #include 6 | #endif // _WIN32 7 | 8 | #ifdef linux 9 | #include 10 | #endif // linux 11 | 12 | #endif // COROUTINE_BACKEND_CONTEXT_HPP -------------------------------------------------------------------------------- /include/coroutine/backend/context_pool.hpp: -------------------------------------------------------------------------------- 1 | #ifndef COROUTINE_BACKEND_CONTEXT_POOL_HPP 2 | #define COROUTINE_BACKEND_CONTEXT_POOL_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace coro 9 | { 10 | 11 | namespace back 12 | { 13 | 14 | class context_pool 15 | { 16 | public: 17 | struct context_data 18 | { 19 | enum class context_state 20 | { 21 | idle, ready, active, io_wait, dead 22 | }; 23 | context_data* caller_context; 24 | context_data* return_context; 25 | context_pool* pool; 26 | const coro::callback* callback; 27 | coro::back::context last_context; 28 | coro::sized_memory_block stack; 29 | const char* description; 30 | std::size_t id; 31 | context_state state; 32 | 33 | bool unused() const 34 | { 35 | return pool == nullptr; 36 | } 37 | }; 38 | 39 | context_pool(); 40 | ~context_pool(); 41 | 42 | std::size_t make_context(const callback& callback, const char* description = ""); 43 | void switch_to(std::size_t context); 44 | void yield(); 45 | void yield(std::size_t callee); 46 | 47 | const context_data& main_context() const; 48 | const context_data& current_context() const; 49 | const context_data* context(std::size_t id) const; 50 | bool is_current_context(std::size_t id) const; 51 | 52 | private: 53 | context_data _pool[128]; 54 | context_data* _main_context; 55 | context_data* _current_context; 56 | coro::callback _pool_runner; 57 | 58 | void remove_context(std::size_t id); 59 | void release_context(context_data& context); 60 | static void pool_function(void* pool); 61 | context_data* find_context(std::size_t id); 62 | const context_data* find_context(std::size_t id) const; 63 | context_data* find_unused_context(); 64 | void switch_to(context_pool::context_data* context); 65 | }; 66 | 67 | context_pool& get_context_pool(); 68 | const context_pool::context_data& get_context_data(); 69 | 70 | } 71 | 72 | } 73 | 74 | #endif // COROUTINE_BACKEND_CONTEXT_POOL_HPP 75 | -------------------------------------------------------------------------------- /include/coroutine/backend/coroutine.hpp: -------------------------------------------------------------------------------- 1 | #ifndef COROUTINE_BACKEND_COROUTINE_HPP 2 | #define COROUTINE_BACKEND_COROUTINE_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace coro 9 | { 10 | 11 | namespace back 12 | { 13 | 14 | template 15 | class value_channel 16 | { 17 | public: 18 | template 19 | const T& set(Value&& value) 20 | { 21 | return _value = std::forward(value); 22 | } 23 | 24 | const T& get() const 25 | { 26 | return _value; 27 | } 28 | 29 | private: 30 | T _value; 31 | }; 32 | 33 | template 34 | class coroutine 35 | { 36 | public: 37 | using function = std::function; 38 | 39 | coroutine(function coro_function) : 40 | _pool{&get_context_pool()}, 41 | _function{[this, coro_function] 42 | { 43 | coro_function(*this); 44 | }} 45 | { 46 | _context = _pool->make_context(_function); 47 | } 48 | 49 | coroutine() : 50 | _pool{&get_context_pool()}, 51 | _context{_pool->current_context().id} 52 | {} 53 | 54 | std::size_t context() const 55 | { 56 | return _context; 57 | } 58 | 59 | context_pool* pool() const 60 | { 61 | return _pool; 62 | } 63 | 64 | coroutine& switch_to() 65 | { 66 | return switch_to(*this); 67 | } 68 | 69 | coroutine& switch_to(const coroutine& coro) 70 | { 71 | assert(_pool == coro.pool()); 72 | _pool->switch_to(coro.context()); 73 | return *this; 74 | } 75 | 76 | template 77 | coroutine& switch_to(const coroutine& coro, Value&& value) 78 | { 79 | set(std::forward(value)); 80 | return switch_to(coro); 81 | } 82 | 83 | coroutine& asymmetric_switch() 84 | { 85 | if(is_current()) 86 | { 87 | return yield(); 88 | } 89 | else 90 | { 91 | return switch_to(); 92 | } 93 | } 94 | 95 | coroutine& yield() 96 | { 97 | _pool->yield(_context); 98 | return *this; 99 | } 100 | 101 | coroutine& assymetric_yield() 102 | { 103 | if(is_current()) 104 | { 105 | yield(); 106 | } 107 | 108 | return *this; 109 | } 110 | 111 | template 112 | coroutine& yield(Value&& value) 113 | { 114 | set(std::forward(value)); 115 | return yield(); 116 | } 117 | 118 | const T& get() const 119 | { 120 | return _channel.get(); 121 | } 122 | 123 | template 124 | const T& set(Value&& value) 125 | { 126 | return _channel.set(std::forward(value)); 127 | } 128 | 129 | coroutine& operator()() 130 | { 131 | return switch_to(*this); 132 | } 133 | 134 | template 135 | coroutine& operator()(Value&& value) 136 | { 137 | return switch_to(*this, std::forward(value)); 138 | } 139 | 140 | coroutine& operator()(const coroutine& coro) 141 | { 142 | return switch_to(coro); 143 | } 144 | 145 | template 146 | coroutine& operator()(const coroutine& coro, Value&& value) 147 | { 148 | return switch_to(coro, std::forward(value)); 149 | } 150 | 151 | bool alive() const 152 | { 153 | auto* context = _pool->context(_context); 154 | 155 | return context != nullptr && 156 | context->state != context_pool::context_data::context_state::dead && 157 | context->state != context_pool::context_data::context_state::idle; 158 | } 159 | 160 | bool is_current() const 161 | { 162 | return _pool->is_current_context(_context); 163 | } 164 | 165 | operator bool() const 166 | { 167 | return alive(); 168 | } 169 | 170 | private: 171 | context_pool* _pool; 172 | std::size_t _context; 173 | value_channel _channel; 174 | coro::stateful_callback> _function; 175 | }; 176 | 177 | } 178 | 179 | } 180 | 181 | #endif // COROUTINE_BACKEND_COROUTINE_HPP 182 | -------------------------------------------------------------------------------- /include/coroutine/backend/detail/aligned_union.hpp: -------------------------------------------------------------------------------- 1 | #ifndef COROUTINE_BACKEND_DETAIL_ALIGNED_UNION_HPP 2 | #define COROUTINE_BACKEND_DETAIL_ALIGNED_UNION_HPP 3 | 4 | #include 5 | 6 | namespace coro 7 | { 8 | 9 | namespace back 10 | { 11 | 12 | namespace detail 13 | { 14 | 15 | namespace 16 | { 17 | template 18 | constexpr T max(T lhs, T rhs) 19 | { 20 | return (lhs >= rhs) ? lhs : rhs; 21 | } 22 | 23 | template 24 | constexpr T max(T value) 25 | { 26 | return value; 27 | } 28 | 29 | template 30 | constexpr Head max(Head head, Second second, Tail... tail) 31 | { 32 | return max(head, max(second, tail...)); 33 | } 34 | 35 | } 36 | 37 | template 38 | using aligned_union = std::aligned_storage; 39 | 40 | } 41 | 42 | } 43 | 44 | } 45 | 46 | #endif // COROUTINE_BACKEND_DETAIL_ALIGNED_UNION_HPP 47 | -------------------------------------------------------------------------------- /include/coroutine/backend/linux/context.hpp: -------------------------------------------------------------------------------- 1 | #ifndef COROUTINE_BACKEND_LINUX_CONTEXT_HPP 2 | #define COROUTINE_BACKEND_LINUX_CONTEXT_HPP 3 | 4 | #include 5 | #ifndef SIGSTKSZ 6 | #include 7 | #endif // SIGSTKSZ 8 | #include 9 | #include 10 | 11 | namespace coro 12 | { 13 | 14 | namespace back 15 | { 16 | 17 | using context = ::ucontext_t; 18 | 19 | context get_current_context(); 20 | void swap_context(context& from, const context& to); 21 | context make_context(const callback& callback, const sized_memory_block& stack, const context& return_context); 22 | std::size_t context_id(const context& context); 23 | inline void release_context(const context& context) {} 24 | constexpr std::size_t stack_size() 25 | { 26 | return SIGSTKSZ*2; 27 | } 28 | coro::sized_memory_block allocate_stack(std::size_t bytes); 29 | void free_stack(sized_memory_block& stack); 30 | 31 | } 32 | 33 | } 34 | 35 | #endif // COROUTINE_BACKEND_LINUX_CONTEXT_HPPM#M# 36 | -------------------------------------------------------------------------------- /include/coroutine/backend/linux/stack.hpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Manu343726/coroutine/dbf7ed9ef161351535ea835ccb0ed80bf6a3c5a9/include/coroutine/backend/linux/stack.hpp -------------------------------------------------------------------------------- /include/coroutine/backend/windows/context.hpp: -------------------------------------------------------------------------------- 1 | #ifndef COROUTINE_BACKEND_WINDOWS_CONTEXT_HPP 2 | #define COROUTINE_BACKEND_WINDOWS_CONTEXT_HPP 3 | 4 | #define NOMINMAX 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace coro 11 | { 12 | 13 | namespace back 14 | { 15 | struct context 16 | { 17 | LPVOID handle; 18 | HANDLE thread; 19 | const context* next_context; 20 | }; 21 | 22 | context get_current_context(); 23 | void swap_context(context& from, const context& to); 24 | context make_context(const callback& callback, const sized_memory_block& stack, const context& return_context); 25 | void release_context(const context& context); 26 | coro::sized_memory_block allocate_stack(std::size_t bytes); 27 | void free_stack(const coro::sized_memory_block& stack); 28 | constexpr std::size_t stack_size() 29 | { 30 | return 0; 31 | } 32 | } 33 | 34 | } 35 | 36 | #endif // COROUTINE_BACKEND_WINDOWS_CONTEXT_HPP -------------------------------------------------------------------------------- /include/coroutine/callback.hpp: -------------------------------------------------------------------------------- 1 | #ifndef COROUTINE_CALLBACK_HPP 2 | #define COROUTINE_CALLBACK_HPP 3 | 4 | #include 5 | 6 | namespace coro 7 | { 8 | 9 | struct callback 10 | { 11 | void(*function)(void*); 12 | void* context; 13 | void invoke() const 14 | { 15 | function(context); 16 | } 17 | }; 18 | 19 | template 20 | struct stateful_callback : public callback 21 | { 22 | stateful_callback() = default; 23 | stateful_callback(Function function) : 24 | stateful_function{std::move(function)} 25 | { 26 | callback::function = static_cast([](void* context) 27 | { 28 | auto& stateful_function = *reinterpret_cast(context); 29 | stateful_function(); 30 | }); 31 | callback::context = &stateful_function; 32 | } 33 | 34 | Function stateful_function; 35 | }; 36 | 37 | template 38 | stateful_callback make_stateful_callback(Function function) 39 | { 40 | return {std::move(function)}; 41 | } 42 | 43 | } 44 | 45 | #endif // COROUTINE_CALLBACK_HPP 46 | -------------------------------------------------------------------------------- /include/coroutine/context.hpp: -------------------------------------------------------------------------------- 1 | #ifndef COROUTINE_CONTEXT_HPP 2 | #define COROUTINE_CONTEXT_HPP 3 | 4 | namespace coro 5 | { 6 | 7 | class Context 8 | { 9 | public: 10 | struct ContextDeleter 11 | { 12 | void operator()(void* contextHandle); 13 | }; 14 | 15 | /** 16 | * \brief Returns the currently active context 17 | */ 18 | static Context get(); 19 | 20 | 21 | 22 | private: 23 | Context(void* contextHandle); 24 | 25 | using Handle = std::unique_ptr; 26 | 27 | Handle _contextHandle; 28 | }; 29 | 30 | } 31 | 32 | #endif // COROUTINE_CONTEXT_HPP 33 | -------------------------------------------------------------------------------- /include/coroutine/sized_memory_block.hpp: -------------------------------------------------------------------------------- 1 | #ifndef COROUTINE_SIZED_MEMORY_BLOCK_HPP 2 | #define COROUTINE_SIZED_MEMORY_BLOCK_HPP 3 | 4 | #include 5 | 6 | namespace coro 7 | { 8 | 9 | struct sized_memory_block 10 | { 11 | void* start; 12 | std::size_t size; 13 | void* end() const; 14 | }; 15 | 16 | sized_memory_block malloc(std::size_t bytes); 17 | sized_memory_block aligned_malloc(std::size_t bytes, std::size_t alignment); 18 | void free(const sized_memory_block& block); 19 | void aligned_free(const sized_memory_block& block); 20 | 21 | template 22 | sized_memory_block static_block(const char (&block)[N]) 23 | { 24 | return {reinterpret_cast(const_cast(&block[0])), N}; 25 | } 26 | 27 | } 28 | 29 | #endif // COROUTINE_SIZED_MEMORY_BLOCK_HPP 30 | -------------------------------------------------------------------------------- /src/backend/context_pool.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace coro::back; 4 | 5 | void context_pool::pool_function(void* data) 6 | { 7 | auto* pool = reinterpret_cast(data); 8 | auto* context = pool->_current_context; 9 | 10 | if(context->callback != nullptr) 11 | { 12 | context->callback->invoke(); 13 | } 14 | 15 | context->state = context_pool::context_data::context_state::dead; 16 | 17 | auto* caller_context = context->caller_context; 18 | auto* return_context = context->return_context; 19 | 20 | if(caller_context != nullptr) 21 | { 22 | pool->switch_to(caller_context); 23 | } 24 | else if(return_context != nullptr) 25 | { 26 | pool->switch_to(return_context); 27 | } 28 | 29 | pool->switch_to(pool->_main_context); 30 | } 31 | 32 | context_pool::context_pool() : 33 | _pool{} 34 | { 35 | for(std::size_t i = 0; i < 128; ++i) 36 | { 37 | _pool[i].stack = coro::back::allocate_stack(coro::back::stack_size()); 38 | _pool[i].id = i; 39 | _pool[i].description = "dead"; 40 | _pool[i].state = context_pool::context_data::context_state::idle; 41 | } 42 | 43 | _pool_runner.function = &context_pool::pool_function; 44 | _pool_runner.context = this; 45 | 46 | auto main_context = coro::back::get_current_context(); 47 | auto* main_context_data = find_unused_context(); 48 | main_context_data->last_context = main_context; 49 | main_context_data->caller_context = nullptr; 50 | main_context_data->return_context = nullptr; 51 | main_context_data->pool = this; 52 | main_context_data->callback = nullptr; 53 | main_context_data->description = "main context"; 54 | main_context_data->state = context_pool::context_data::context_state::active; 55 | _main_context = main_context_data; 56 | _current_context = _main_context; 57 | } 58 | 59 | context_pool::~context_pool() 60 | { 61 | for(auto& context : _pool) 62 | { 63 | coro::back::free_stack(context.stack); 64 | } 65 | } 66 | 67 | std::size_t context_pool::make_context(const coro::callback& callback, const char* description) 68 | { 69 | auto* context_data = find_unused_context(); 70 | 71 | if(context_data == nullptr) 72 | { 73 | return 0; 74 | } 75 | 76 | context_data->last_context = coro::back::make_context( 77 | _pool_runner, 78 | context_data->stack, 79 | current_context().last_context 80 | ); 81 | context_data->return_context = _current_context; 82 | context_data->caller_context = _current_context; 83 | context_data->pool = this; 84 | context_data->callback = &callback; 85 | context_data->description = description; 86 | context_data->state = context_pool::context_data::context_state::ready; 87 | return context_data->id; 88 | } 89 | 90 | void context_pool::switch_to(std::size_t context_id) 91 | { 92 | switch_to(find_context(context_id)); 93 | } 94 | 95 | void context_pool::switch_to(context_pool::context_data* context) 96 | { 97 | if(context != nullptr && context->state == context_pool::context_data::context_state::ready && 98 | context != _current_context) 99 | { 100 | auto& caller = _current_context->last_context; 101 | auto* old_caller_context = context->caller_context; 102 | auto* old_current_context = _current_context; 103 | 104 | context->caller_context = _current_context; 105 | 106 | if(_current_context->state == context_pool::context_data::context_state::active) 107 | { 108 | _current_context->state = context_pool::context_data::context_state::ready; 109 | } 110 | _current_context = context; 111 | context->state = context_pool::context_data::context_state::active; 112 | coro::back::swap_context(caller, context->last_context); 113 | context->caller_context = old_caller_context; 114 | 115 | if (context->state == context_pool::context_data::context_state::active) 116 | { 117 | context->state = context_pool::context_data::context_state::ready; 118 | } 119 | else if (context->state == context_pool::context_data::context_state::dead) 120 | { 121 | // Dead contexts are released here to make sure we never release an active 122 | // context. This is not critical with ucontext since coro::back::release_context() 123 | // is basically a noop there, but we have to be careful when releasing Win32 fibers. 124 | release_context(*context); 125 | } 126 | } 127 | } 128 | 129 | void context_pool::yield() 130 | { 131 | switch_to(current_context().caller_context); 132 | } 133 | 134 | void context_pool::yield(std::size_t callee) 135 | { 136 | auto* context = find_context(callee); 137 | 138 | if(context != nullptr) 139 | { 140 | switch_to(context->caller_context); 141 | } 142 | } 143 | 144 | const context_pool::context_data& context_pool::current_context() const 145 | { 146 | return *_current_context; 147 | } 148 | 149 | const context_pool::context_data& context_pool::main_context() const 150 | { 151 | return *_main_context; 152 | } 153 | 154 | const context_pool::context_data* context_pool::context(std::size_t id) const 155 | { 156 | return find_context(id); 157 | } 158 | 159 | bool context_pool::is_current_context(std::size_t id) const 160 | { 161 | return id == _current_context->id; 162 | } 163 | 164 | void context_pool::remove_context(std::size_t id) 165 | { 166 | auto* context = find_context(id); 167 | 168 | if(context != nullptr) 169 | { 170 | release_context(*context); 171 | } 172 | } 173 | 174 | void context_pool::release_context(context_pool::context_data& context) 175 | { 176 | coro::back::release_context(context.last_context); 177 | context.pool = nullptr; 178 | context.caller_context = nullptr; 179 | context.return_context = nullptr; 180 | context.description = "dead"; 181 | context.state = context_pool::context_data::context_state::idle; 182 | } 183 | 184 | context_pool::context_data* context_pool::find_unused_context() 185 | { 186 | for(auto& context : _pool) 187 | { 188 | if(context.unused()) 189 | { 190 | return &context; 191 | } 192 | } 193 | 194 | return nullptr; 195 | } 196 | 197 | context_pool::context_data* context_pool::find_context(std::size_t id) 198 | { 199 | for(auto& context : _pool) 200 | { 201 | if(!context.unused() && context.id == id) 202 | { 203 | return &context; 204 | } 205 | } 206 | 207 | return nullptr; 208 | } 209 | 210 | const context_pool::context_data* context_pool::find_context(std::size_t id) const 211 | { 212 | for(auto& context : _pool) 213 | { 214 | if(!context.unused() && context.id == id) 215 | { 216 | return &context; 217 | } 218 | } 219 | 220 | return nullptr; 221 | } 222 | 223 | namespace coro 224 | { 225 | 226 | namespace back 227 | { 228 | 229 | context_pool& get_context_pool() 230 | { 231 | thread_local context_pool pool; 232 | return pool; 233 | } 234 | 235 | const context_pool::context_data& get_context_data() 236 | { 237 | return get_context_pool().current_context(); 238 | } 239 | 240 | } 241 | 242 | } 243 | -------------------------------------------------------------------------------- /src/backend/linux/context.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | static_assert(std::is_same::value, "int must be 32 bits wide"); 7 | 8 | template 9 | int pointer_high(const T* ptr) 10 | { 11 | return reinterpret_cast(const_cast(ptr)) >> 32; 12 | } 13 | 14 | template 15 | int pointer_low(const T* ptr) 16 | { 17 | return static_cast(reinterpret_cast(const_cast(ptr)) & 0xFFFFFFFF); 18 | } 19 | 20 | template 21 | const T* pointer(int high, int low) 22 | { 23 | return reinterpret_cast( 24 | (static_cast(high) << 32) | (static_cast(low) & 0xFFFFFFFF) 25 | ); 26 | } 27 | 28 | extern "C" 29 | { 30 | 31 | void coroutine_context_func_64(int high, int low) 32 | { 33 | const auto* callback = pointer(high, low); 34 | callback->function(callback->context); 35 | } 36 | 37 | void coroutine_context_func_32(int callback_ptr) 38 | { 39 | const auto* callback = reinterpret_cast( 40 | callback_ptr 41 | ); 42 | 43 | callback->function(callback->context); 44 | } 45 | 46 | } 47 | 48 | namespace coro 49 | { 50 | 51 | namespace back 52 | { 53 | 54 | context get_current_context() 55 | { 56 | context result{}; 57 | ::getcontext(&result); 58 | return result; 59 | } 60 | 61 | void swap_context(context& from, const context& to) 62 | { 63 | ::swapcontext(&from, &to); 64 | } 65 | 66 | context make_context(const callback& callback, const sized_memory_block& stack, const context& return_context) 67 | { 68 | context result = get_current_context(); 69 | 70 | result.uc_stack.ss_sp = stack.start; 71 | result.uc_stack.ss_size = stack.size; 72 | result.uc_link = const_cast(&return_context); 73 | 74 | #if INTPTR_MAX == INT32_MAX 75 | ::makecontext( 76 | &result, 77 | reinterpret_cast(::coroutine_context_func_32), 78 | 1, 79 | reinterpret_cast(const_cast(&callback)) 80 | ); 81 | #elif INTPTR_MAX == INT64_MAX 82 | ::makecontext( 83 | &result, 84 | reinterpret_cast(::coroutine_context_func_64), 85 | 2, 86 | pointer_high(&callback), pointer_low(&callback) 87 | ); 88 | #endif // INTPTR_MAX == INTXX_MAX 89 | 90 | return result; 91 | } 92 | 93 | std::size_t context_id(const context& context) 94 | { 95 | return reinterpret_cast(context.uc_stack.ss_sp); 96 | } 97 | 98 | coro::sized_memory_block allocate_stack(std::size_t bytes) 99 | { 100 | return { 101 | ::mmap(NULL, bytes, PROT_EXEC | PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0), 102 | bytes 103 | }; 104 | } 105 | 106 | void free_stack(sized_memory_block& stack) 107 | { 108 | ::munmap(stack.start, stack.size); 109 | } 110 | 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /src/backend/windows/context.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | extern "C" 4 | { 5 | VOID __stdcall coroutine_fiber_func(LPVOID fiberParam) 6 | { 7 | const auto* callback = reinterpret_cast(fiberParam); 8 | callback->function(callback->context); 9 | } 10 | } 11 | 12 | namespace coro 13 | { 14 | 15 | namespace back 16 | { 17 | context get_current_context() 18 | { 19 | thread_local LPVOID thread_fiber = ::ConvertThreadToFiber(nullptr); 20 | auto fiber = ::GetCurrentFiber(); 21 | 22 | if(fiber != nullptr) 23 | { 24 | return {fiber, ::GetCurrentThread()}; 25 | } 26 | else 27 | { 28 | return {thread_fiber, ::GetCurrentThread()}; 29 | } 30 | } 31 | 32 | void swap_context(context&, const context& to) 33 | { 34 | ::SwitchToFiber(to.handle); 35 | } 36 | 37 | context make_context(const callback& callback, const sized_memory_block& stack, const context& return_context) 38 | { 39 | coro::back::context context; 40 | context.handle = ::CreateFiber(0, &::coroutine_fiber_func, const_cast(&callback)); 41 | context.thread = ::GetCurrentThread(); 42 | context.next_context = &return_context; 43 | 44 | return context; 45 | } 46 | 47 | coro::sized_memory_block allocate_stack(std::size_t bytes) 48 | { 49 | return { nullptr, 0 }; 50 | } 51 | 52 | void free_stack(const coro::sized_memory_block& stack) 53 | {} 54 | 55 | void release_context(const context& context) 56 | { 57 | ::DeleteFiber(context.handle); 58 | } 59 | 60 | } 61 | 62 | } -------------------------------------------------------------------------------- /src/sized_memory_block.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace coro 5 | { 6 | 7 | sized_memory_block malloc(std::size_t bytes) 8 | { 9 | sized_memory_block result{}; 10 | 11 | if((result.start = std::malloc(bytes))) 12 | { 13 | result.size = bytes; 14 | } 15 | 16 | return result; 17 | } 18 | 19 | void free(const sized_memory_block& block) 20 | { 21 | std::free(block.start); 22 | } 23 | 24 | } 25 | --------------------------------------------------------------------------------