├── .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 |
--------------------------------------------------------------------------------