From 81b2255ddf6e1d3fe9ecb54368017bbf5f575d6c Mon Sep 17 00:00:00 2001 From: Oliver Kowalke Date: Fri, 23 Jan 2015 23:15:23 +0100 Subject: [PATCH] initial import --- README.md | 11 + build/Jamfile.v2 | 34 + doc/Jamfile.v2 | 33 + doc/acknowledgements.qbk | 17 + doc/architectures.qbk | 13 + doc/asymmetric.qbk | 708 ++++++++++++++++++ doc/attributes.qbk | 107 +++ doc/coro.qbk | 85 +++ doc/coroutine.qbk | 95 +++ doc/images/event_model.dia | Bin 0 -> 2498 bytes doc/images/event_model.png | Bin 0 -> 28915 bytes doc/images/foo_bar.png | Bin 0 -> 12878 bytes doc/images/foo_bar_seq.dia | Bin 0 -> 2549 bytes doc/images/foo_bar_seq.png | Bin 0 -> 19656 bytes doc/images/same_fringe.dia | Bin 0 -> 1541 bytes doc/images/same_fringe.png | Bin 0 -> 5346 bytes doc/intro.qbk | 106 +++ doc/motivation.qbk | 567 ++++++++++++++ doc/overview.qbk | 36 + doc/performance.qbk | 40 + doc/stack.qbk | 289 +++++++ example/Jamfile.v2 | 49 ++ example/fibonacci.cpp | 34 + example/layout.cpp | 53 ++ example/parser.cpp | 109 +++ example/same_fringe.cpp | 176 +++++ example/segmented.cpp | 65 ++ example/simple.cpp | 24 + example/tree.h | 97 +++ include/boost/coroutine2/all.hpp | 15 + include/boost/coroutine2/coroutine.hpp | 39 + include/boost/coroutine2/detail/config.hpp | 49 ++ include/boost/coroutine2/detail/coroutine.hpp | 44 ++ .../boost/coroutine2/detail/forced_unwind.hpp | 33 + .../coroutine2/detail/pull_control_block.hpp | 102 +++ .../coroutine2/detail/pull_control_block.ipp | 259 +++++++ .../coroutine2/detail/pull_coroutine.hpp | 320 ++++++++ .../coroutine2/detail/pull_coroutine.ipp | 226 ++++++ .../coroutine2/detail/push_control_block.hpp | 106 +++ .../coroutine2/detail/push_control_block.ipp | 285 +++++++ .../coroutine2/detail/push_coroutine.hpp | 242 ++++++ .../coroutine2/detail/push_coroutine.ipp | 208 +++++ include/boost/coroutine2/detail/rref.hpp | 57 ++ include/boost/coroutine2/detail/state.hpp | 37 + include/boost/coroutine2/fixedsize_stack.hpp | 33 + .../coroutine2/protected_fixedsize_stack.hpp | 33 + include/boost/coroutine2/segmented_stack.hpp | 37 + index.html | 14 + meta/libraries.json | 14 + performance/Jamfile.v2 | 77 ++ performance/bind_processor.hpp | 12 + performance/bind_processor_aix.cpp | 25 + performance/bind_processor_freebsd.cpp | 29 + performance/bind_processor_hpux.cpp | 31 + performance/bind_processor_linux.cpp | 30 + performance/bind_processor_solaris.cpp | 26 + performance/bind_processor_windows.cpp | 24 + performance/clock.hpp | 45 ++ performance/cycle.hpp | 26 + performance/cycle_i386.hpp | 83 ++ performance/cycle_x86-64.hpp | 79 ++ performance/performance_create_protected.cpp | 107 +++ performance/performance_create_standard.cpp | 107 +++ performance/performance_switch.cpp | 198 +++++ performance/segmented/Jamfile.v2 | 73 ++ .../performance_create_segmented.cpp | 107 +++ performance/segmented/performance_switch.cpp | 204 +++++ test/Jamfile.v2 | 29 + test/test_coroutine.cpp | 599 +++++++++++++++ 69 files changed, 6812 insertions(+) create mode 100644 README.md create mode 100644 build/Jamfile.v2 create mode 100644 doc/Jamfile.v2 create mode 100644 doc/acknowledgements.qbk create mode 100644 doc/architectures.qbk create mode 100644 doc/asymmetric.qbk create mode 100644 doc/attributes.qbk create mode 100644 doc/coro.qbk create mode 100644 doc/coroutine.qbk create mode 100644 doc/images/event_model.dia create mode 100644 doc/images/event_model.png create mode 100644 doc/images/foo_bar.png create mode 100644 doc/images/foo_bar_seq.dia create mode 100644 doc/images/foo_bar_seq.png create mode 100644 doc/images/same_fringe.dia create mode 100644 doc/images/same_fringe.png create mode 100644 doc/intro.qbk create mode 100644 doc/motivation.qbk create mode 100644 doc/overview.qbk create mode 100644 doc/performance.qbk create mode 100644 doc/stack.qbk create mode 100644 example/Jamfile.v2 create mode 100644 example/fibonacci.cpp create mode 100644 example/layout.cpp create mode 100644 example/parser.cpp create mode 100644 example/same_fringe.cpp create mode 100644 example/segmented.cpp create mode 100644 example/simple.cpp create mode 100644 example/tree.h create mode 100644 include/boost/coroutine2/all.hpp create mode 100644 include/boost/coroutine2/coroutine.hpp create mode 100644 include/boost/coroutine2/detail/config.hpp create mode 100644 include/boost/coroutine2/detail/coroutine.hpp create mode 100644 include/boost/coroutine2/detail/forced_unwind.hpp create mode 100644 include/boost/coroutine2/detail/pull_control_block.hpp create mode 100644 include/boost/coroutine2/detail/pull_control_block.ipp create mode 100644 include/boost/coroutine2/detail/pull_coroutine.hpp create mode 100644 include/boost/coroutine2/detail/pull_coroutine.ipp create mode 100644 include/boost/coroutine2/detail/push_control_block.hpp create mode 100644 include/boost/coroutine2/detail/push_control_block.ipp create mode 100644 include/boost/coroutine2/detail/push_coroutine.hpp create mode 100644 include/boost/coroutine2/detail/push_coroutine.ipp create mode 100644 include/boost/coroutine2/detail/rref.hpp create mode 100644 include/boost/coroutine2/detail/state.hpp create mode 100644 include/boost/coroutine2/fixedsize_stack.hpp create mode 100644 include/boost/coroutine2/protected_fixedsize_stack.hpp create mode 100644 include/boost/coroutine2/segmented_stack.hpp create mode 100644 index.html create mode 100644 meta/libraries.json create mode 100644 performance/Jamfile.v2 create mode 100644 performance/bind_processor.hpp create mode 100644 performance/bind_processor_aix.cpp create mode 100644 performance/bind_processor_freebsd.cpp create mode 100644 performance/bind_processor_hpux.cpp create mode 100644 performance/bind_processor_linux.cpp create mode 100644 performance/bind_processor_solaris.cpp create mode 100644 performance/bind_processor_windows.cpp create mode 100644 performance/clock.hpp create mode 100644 performance/cycle.hpp create mode 100644 performance/cycle_i386.hpp create mode 100644 performance/cycle_x86-64.hpp create mode 100644 performance/performance_create_protected.cpp create mode 100644 performance/performance_create_standard.cpp create mode 100644 performance/performance_switch.cpp create mode 100644 performance/segmented/Jamfile.v2 create mode 100644 performance/segmented/performance_create_segmented.cpp create mode 100644 performance/segmented/performance_switch.cpp create mode 100644 test/Jamfile.v2 create mode 100644 test/test_coroutine.cpp diff --git a/README.md b/README.md new file mode 100644 index 0000000..26558c1 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +boost.coroutine +=============== + +boost.coroutine provides templates for generalized subroutines which allow multiple entry points for +suspending and resuming execution at certain locations. It preserves the local state of execution and +allows re-entering subroutines more than once (useful if state must be kept across function calls). + +Coroutines can be viewed as a language-level construct providing a special kind of control flow. + +In contrast to threads, which are pre-emptive, coroutines switches are cooperative (programmer controls +when a switch will happen). The kernel is not involved in the coroutine switches. \ No newline at end of file diff --git a/build/Jamfile.v2 b/build/Jamfile.v2 new file mode 100644 index 0000000..7904827 --- /dev/null +++ b/build/Jamfile.v2 @@ -0,0 +1,34 @@ + +# Copyright Oliver Kowalke 2014. +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +import feature ; +import modules ; +import toolset ; + +feature.feature valgrind : on : optional propagated composite ; +feature.compose on : BOOST_USE_VALGRIND ; + +project boost/coroutine2 + : requirements + /boost/context//boost_context + gcc,on:-fsplit-stack + gcc,on:-DBOOST_USE_SEGMENTED_STACKS + clang,on:-fsplit-stack + clang,on:-DBOOST_USE_SEGMENTED_STACKS + shared:BOOST_COROUTINES2_DYN_LINK=1 + BOOST_COROUTINES2_SOURCE + multi + : usage-requirements + shared:BOOST_COROUTINES2_DYN_LINK=1 + : source-location ../src + ; + +lib boost_coroutine2 + : + : shared:../../context/build//boost_context + ; + +boost-install boost_coroutine2 ; diff --git a/doc/Jamfile.v2 b/doc/Jamfile.v2 new file mode 100644 index 0000000..ff28385 --- /dev/null +++ b/doc/Jamfile.v2 @@ -0,0 +1,33 @@ +# (C) Copyright 2008 Anthony Williams +# +# Distributed under the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +project coroutine/doc ; + +import boostbook ; +import quickbook ; +import modules ; + +path-constant here : . ; + +boostbook coro + : + coro.qbk + : + # Path for links to Boost: + boost.root=../../../.. + # HTML options first: + # How far down we chunk nested sections, basically all of them: + chunk.section.depth=3 + # Don't put the first section on the same page as the TOC: + chunk.first.sections=1 + # How far down sections get TOC's + toc.section.depth=10 + # Max depth in each TOC: + toc.max.depth=3 + # How far down we go with TOC's + generate.section.toc.level=10 + # Absolute path for images: + pdf:img.src.path=$(here)/html/ + ; diff --git a/doc/acknowledgements.qbk b/doc/acknowledgements.qbk new file mode 100644 index 0000000..528c179 --- /dev/null +++ b/doc/acknowledgements.qbk @@ -0,0 +1,17 @@ +[/ + Copyright Oliver Kowalke 2014. + Distributed under the Boost Software License, Version 1.0. + (See accompanying file LICENSE_1_0.txt or copy at + http://www.boost.org/LICENSE_1_0.txt +] + +[section:acknowledgements Acknowledgments] + +I'd like to thank Alex Hagen-Zanker, Christopher Kormanyos, Conrad Poelman, +Eugene Yakubovich, Giovanni Piero Deretta, Hartmut Kaiser, Jeffrey Lee Hellrung, +[*Nat Goodspeed], Robert Stewart, Vicente J. Botet Escriba and Yuriy Krasnoschek. + +Especially Eugene Yakubovich, Giovanni Piero Deretta and Vicente J. Botet +Escriba contributed many good ideas during the review. + +[endsect] diff --git a/doc/architectures.qbk b/doc/architectures.qbk new file mode 100644 index 0000000..94b5f75 --- /dev/null +++ b/doc/architectures.qbk @@ -0,0 +1,13 @@ +[/ + Copyright Oliver Kowalke 2014. + Distributed under the Boost Software License, Version 1.0. + (See accompanying file LICENSE_1_0.txt or copy at + http://www.boost.org/LICENSE_1_0.txt +] + +[section:architectures Architectures] + +__boost_coroutine__ depends on __boost_context__ which supports these +[@boost:/libs/context/doc/html/context/architectures.html architectures]. + +[endsect] diff --git a/doc/asymmetric.qbk b/doc/asymmetric.qbk new file mode 100644 index 0000000..2af6b3c --- /dev/null +++ b/doc/asymmetric.qbk @@ -0,0 +1,708 @@ +[/ + Copyright Oliver Kowalke 2014. + Distributed under the Boost Software License, Version 1.0. + (See accompanying file LICENSE_1_0.txt or copy at + http://www.boost.org/LICENSE_1_0.txt +] + +[section:asymmetric Asymmetric coroutine] + +Two asymmetric coroutine types - __push_coro__ and __pull_coro__ - provide a +unidirectional transfer of data. + + +[heading __pull_coro__] +__pull_coro__ transfers data from another execution context (== pulled-from). +The template parameter defines the transferred parameter type. +The constructor of __pull_coro__ takes a function (__coro_fn__) accepting a +reference to an __push_coro__ as argument. Instantiating an __pull_coro__ passes +the control of execution to __coro_fn__ and a complementary __push_coro__ is +synthesized by the library and passed as reference to __coro_fn__. + +This kind of coroutine provides __pull_coro_op__. This method only switches +context; it transfers no data. + +__pull_coro__ provides input iterators (__pull_coro_it__) and __begin__/__end__ +are overloaded. The increment-operation switches the context and transfers data. + + typedef boost::coroutines2::asymmetric_coroutine coro_t; + + coro_t::pull_type source( + [&](coro_t::push_type& sink){ + int first=1,second=1; + sink(first); + sink(second); + for(int i=0;i<8;++i){ + int third=first+second; + first=second; + second=third; + sink(third); + } + }); + + for(auto i:source) + std::cout << i << " "; + + output: + 1 1 2 3 5 8 13 21 34 55 + +In this example an __pull_coro__ is created in the main execution context taking +a lambda function (== __coro_fn__) which calculates Fibonacci numbers in a +simple ['for]-loop. +The __coro_fn__ is executed in a newly created execution context which is +managed by the instance of __pull_coro__. +An __push_coro__ is automatically generated by the library and passed as +reference to the lambda function. Each time the lambda function calls +__push_coro_op__ with another Fibonacci number, __push_coro__ transfers it back +to the main execution context. The local state of __coro_fn__ is preserved and +will be restored upon transferring execution control back to __coro_fn__ +to calculate the next Fibonacci number. +Because __pull_coro__ provides input iterators and __begin__/__end__ are +overloaded, a ['range-based for]-loop can be used to iterate over the generated +Fibonacci numbers. + + +[heading __push_coro__] +__push_coro__ transfers data to the other execution context (== pushed-to). +The template parameter defines the transferred parameter type. +The constructor of __push_coro__ takes a function (__coro_fn__) accepting a +reference to an __pull_coro__ as argument. In contrast to __pull_coro__, +instantiating an __push_coro__ does not pass the control of execution to +__coro_fn__ - instead the first call of __push_coro_op__ synthesizes a +complementary __pull_coro__ and passes it as reference to __coro_fn__. + +The __push_coro__ interface does not contain a ['get()]-function: you can not retrieve +values from another execution context with this kind of coroutine. + +__push_coro__ provides output iterators (__push_coro_it__) and +__begin__/__end__ are overloaded. The increment-operation switches the context +and transfers data. + + typedef boost::coroutines2::asymmetric_coroutine coro_t; + + struct FinalEOL{ + ~FinalEOL(){ + std::cout << std::endl; + } + }; + + const int num=5, width=15; + coro_t::push_type writer( + [&](coro_t::pull_type& in){ + // finish the last line when we leave by whatever means + FinalEOL eol; + // pull values from upstream, lay them out 'num' to a line + for (;;){ + for(int i=0;i words{ + "peas", "porridge", "hot", "peas", + "porridge", "cold", "peas", "porridge", + "in", "the", "pot", "nine", + "days", "old" }; + + std::copy(begin(words),end(words),begin(writer)); + + output: + peas porridge hot peas porridge + cold peas porridge in the + pot nine days old + +In this example an __push_coro__ is created in the main execution context +accepting a lambda function (== __coro_fn__) which requests strings and lays out +'num' of them on each line. +This demonstrates the inversion of control permitted by coroutines. Without +coroutines, a utility function to perform the same job would necessarily +accept each new value as a function parameter, returning after processing that +single value. That function would depend on a static state variable. A +__coro_fn__, however, can request each new value as if by calling a function +-- even though its caller also passes values as if by calling a function. +The __coro_fn__ is executed in a newly created execution context which is +managed by the instance of __push_coro__. +The main execution context passes the strings to the __coro_fn__ by calling +__push_coro_op__. +An __pull_coro__ instance is automatically generated by the library and passed as +reference to the lambda function. The __coro_fn__ accesses the strings passed +from the main execution context by calling __pull_coro_get__ and lays those +strings out on ['std::cout] according the parameters 'num' and 'width'. +The local state of __coro_fn__ is preserved and will be restored after +transferring execution control back to __coro_fn__. +Because __push_coro__ provides output iterators and __begin__/__end__ are +overloaded, the ['std::copy] algorithm can be used to iterate over the vector +containing the strings and pass them one by one to the coroutine. + + +[heading coroutine-function] +The __coro_fn__ returns ['void] and takes its counterpart-coroutine as +argument, so that using the coroutine passed as argument to __coro_fn__ is the +only way to transfer data and execution control back to the caller. +Both coroutine types take the same template argument. +For __pull_coro__ the __coro_fn__ is entered at __pull_coro__ construction. +For __push_coro__ the __coro_fn__ is not entered at __push_coro__ construction +but entered by the first invocation of __push_coro_op__. +After execution control is returned from __coro_fn__ the state of the +coroutine can be checked via __pull_coro_bool__ returning `true` if the +coroutine is still valid (__coro_fn__ has not terminated). Unless the first +template parameter is `void`, `true` also implies that a data value is +available. + + +[heading passing data from a pull-coroutine to main-context] +In order to transfer data from an __pull_coro__ to the main-context the framework +synthesizes an __push_coro__ associated with the __pull_coro__ instance in the +main-context. The synthesized __push_coro__ is passed as argument to __coro_fn__. +The __coro_fn__ must call this __push_coro_op__ in order to transfer each +data value back to the main-context. +In the main-context, the __pull_coro_bool__ determines whether the coroutine is +still valid and a data value is available or __coro_fn__ has terminated +(__pull_coro__ is invalid; no data value available). Access to the transferred +data value is given by __pull_coro_get__. + + typedef boost::coroutines2::asymmetric_coroutine coro_t; + + coro_t::pull_type source( // constructor enters coroutine-function + [&](coro_t::push_type& sink){ + sink(1); // push {1} back to main-context + sink(1); // push {1} back to main-context + sink(2); // push {2} back to main-context + sink(3); // push {3} back to main-context + sink(5); // push {5} back to main-context + sink(8); // push {8} back to main-context + }); + + while(source){ // test if pull-coroutine is valid + int ret=source.get(); // access data value + source(); // context-switch to coroutine-function + } + + +[heading passing data from main-context to a push-coroutine] +In order to transfer data to an __push_coro__ from the main-context the framework +synthesizes an __pull_coro__ associated with the __push_coro__ instance in the +main-context. The synthesized __pull_coro__ is passed as argument to __coro_fn__. +The main-context must call this __push_coro_op__ in order to transfer each data +value into the __coro_fn__. +Access to the transferred data value is given by __pull_coro_get__. + + typedef boost::coroutines2::asymmetric_coroutine coro_t; + + coro_t::push_type sink( // constructor does NOT enter coroutine-function + [&](coro_t::pull_type& source){ + for (int i:source) { + std::cout << i << " "; + } + }); + + std::vector v{1,1,2,3,5,8,13,21,34,55}; + for( int i:v){ + sink(i); // push {i} to coroutine-function + } + + +[heading accessing parameters] +Parameters returned from or transferred to the __coro_fn__ can be accessed with +__pull_coro_get__. + +Splitting-up the access of parameters from context switch function enables to +check if __pull_coro__ is valid after return from __pull_coro_op__, e.g. +__pull_coro__ has values and __coro_fn__ has not terminated. + + typedef boost::coroutines2::asymmetric_coroutine> coro_t; + + coro_t::push_type sink( + [&](coro_t::pull_type& source){ + // access tuple {7,11}; x==7 y==1 + int x,y; + boost::tie(x,y)=source.get(); + }); + + sink(boost::make_tuple(7,11)); + + +[heading exceptions] +An exception thrown inside an __pull_coro__'s __coro_fn__ before its first call +to __push_coro_op__ will be re-thrown by the __pull_coro__ constructor. After an +__pull_coro__'s __coro_fn__'s first call to __push_coro_op__, any subsequent +exception inside that __coro_fn__ will be re-thrown by __pull_coro_op__. +__pull_coro_get__ does not throw. + +An exception thrown inside an __push_coro__'s __coro_fn__ will be re-thrown by +__push_coro_op__. + +[important Code executed by __coro_fn__ must not prevent the propagation of the +__forced_unwind__ exception. Absorbing that exception will cause stack +unwinding to fail. Thus, any code that catches all exceptions must re-throw any +pending __forced_unwind__ exception.] + + try { + // code that might throw + } catch(const boost::coroutines2::detail::forced_unwind&) { + throw; + } catch(...) { + // possibly not re-throw pending exception + } + +[important Do not jump from inside a catch block and than re-throw the +exception in another execution context.] + + +[heading Stack unwinding] +Sometimes it is necessary to unwind the stack of an unfinished coroutine to +destroy local stack variables so they can release allocated resources (RAII +pattern). The `attributes` argument of the coroutine constructor +indicates whether the destructor should unwind the stack (stack is unwound by +default). + +Stack unwinding assumes the following preconditions: + +* The coroutine is not __not_a_coro__ +* The coroutine is not complete +* The coroutine is not running +* The coroutine owns a stack + +After unwinding, a __coro__ is complete. + + struct X { + X(){ + std::cout<<"X()"<::push_type coro_t; + + coro_t::push_type sink( + [&](coro_t::pull_type& source){ + X x; + for(int=0;;++i){ + std::cout<<"fn(): "< coro_t; + + int number=2,exponent=8; + coro_t::pull_type source( + [&](coro_t::push_type & sink){ + int counter=0,result=1; + while(counter++::pull_type::iterator::operator++()] corresponds to +__pull_coro_op__; ['asymmetric_coroutine<>::pull_type::iterator::operator*()] +roughly corresponds to __pull_coro_get__. An iterator originally obtained from +__begin__ of an __pull_coro__ compares equal to an iterator obtained from +__end__ of that same __pull_coro__ instance when its __pull_coro_bool__ would +return `false`]. + +[note If `T` is a move-only type, then +['asymmetric_coroutine::pull_type::iterator] may only be dereferenced once +before it is incremented again.] + +Output-iterators can be created from __push_coro__. + + typedef boost::coroutines2::asymmetric_coroutine coro_t; + + coro_t::push_type sink( + [&](coro_t::pull_type& source){ + while(source){ + std::cout << source.get() << " "; + source(); + } + }); + + std::vector v{1,1,2,3,5,8,13,21,34,55}; + std::copy(begin(v),end(v),begin(sink)); + +['asymmetric_coroutine<>::push_type::iterator::operator*()] roughly +corresponds to __push_coro_op__. An iterator originally obtained from +__begin__ of an __push_coro__ compares equal to an iterator obtained from +__end__ of that same __push_coro__ instance when its __push_coro_bool__ would +return `false`. + + +[heading Exit a __coro_fn__] +__coro_fn__ is exited with a simple return statement jumping back to the calling +routine. The __pull_coro__, __push_coro__ becomes complete, e.g. __pull_coro_bool__, +__push_coro_bool__ will return `false`. + +[important After returning from __coro_fn__ the __coro__ is complete (can not +resumed with __push_coro_op__, __pull_coro_op__).] + + + +[section:pull_coro Class `asymmetric_coroutine<>::pull_type`] + + #include + + template< typename R > + class asymmetric_coroutine<>::pull_type + { + public: + pull_type() noexcept; + + template< typename Fn > + pull_type( Fn && fn, attributes const& attr = attributes() ); + + template< typename Fn, typename StackAllocator > + pull_type( Fn && fn, attributes const& attr, StackAllocator stack_alloc); + + pull_type( pull_type const& other)=delete; + + pull_type & operator=( pull_type const& other)=delete; + + ~pull_type(); + + pull_type( pull_type && other) noexcept; + + pull_type & operator=( pull_type && other) noexcept; + + operator unspecified-bool-type() const noexcept; + + bool operator!() const noexcept; + + void swap( pull_type & other) noexcept; + + pull_type & operator()(); + + R get() const; + }; + + template< typename R > + void swap( pull_type< R > & l, pull_type< R > & r); + + template< typename R > + range_iterator< pull_type< R > >::type begin( pull_type< R > &); + + template< typename R > + range_iterator< pull_type< R > >::type end( pull_type< R > &); + +[heading `pull_type()`] +[variablelist +[[Effects:] [Creates a coroutine representing __not_a_coro__.]] +[[Throws:] [Nothing.]] +] + +[heading `template< typename Fn > + pull_type( Fn && fn, attributes const& attr)`] +[variablelist +[[Preconditions:] [`size` >= minimum_stacksize(), `size` <= maximum_stacksize() +when ! is_stack_unbounded().]] +[[Effects:] [Creates a coroutine which will execute `fn`, and enters it. +Argument `attr` determines stack clean-up and preserving floating-point +registers.]] +[[Throws:] [Exceptions thrown inside __coro_fn__.]] +] + +[heading `template< typename Fn, typename StackAllocator > + pull_type( Fn && fn, attributes const& attr, StackAllocator const& stack_alloc)`] +[variablelist +[[Preconditions:] [`size` >= minimum_stacksize(), `size` <= maximum_stacksize() +when ! is_stack_unbounded().]] +[[Effects:] [Creates a coroutine which will execute `fn`. Argument `attr` +determines stack clean-up and preserving floating-point registers. +For allocating/deallocating the stack `stack_alloc` is used.]] +[[Throws:] [Exceptions thrown inside __coro_fn__.]] +] + +[heading `~pull_type()`] +[variablelist +[[Effects:] [Destroys the context and deallocates the stack.]] +] + +[heading `pull_type( pull_type && other)`] +[variablelist +[[Effects:] [Moves the internal data of `other` to `*this`. +`other` becomes __not_a_coro__.]] +[[Throws:] [Nothing.]] +] + +[heading `pull_type & operator=( pull_type && other)`] +[variablelist +[[Effects:] [Destroys the internal data of `*this` and moves the +internal data of `other` to `*this`. `other` becomes __not_a_coro__.]] +[[Throws:] [Nothing.]] +] + +[heading `operator unspecified-bool-type() const`] +[variablelist +[[Returns:] [If `*this` refers to __not_a_coro__ or the coroutine-function +has returned (completed), the function returns `false`. Otherwise `true`.]] +[[Throws:] [Nothing.]] +] + +[heading `bool operator!() const`] +[variablelist +[[Returns:] [If `*this` refers to __not_a_coro__ or the coroutine-function +has returned (completed), the function returns `true`. Otherwise `false`.]] +[[Throws:] [Nothing.]] +] + +[heading `pull_type<> & operator()()`] +[variablelist +[[Preconditions:] [`*this` is not a __not_a_coro__.]] +[[Effects:] [Execution control is transferred to __coro_fn__ (no parameter is +passed to the coroutine-function).]] +[[Throws:] [Exceptions thrown inside __coro_fn__.]] +] + +[heading `R get()`] + + R asymmetric_coroutine::pull_type::get(); + R& asymmetric_coroutine::pull_type::get(); + void asymmetric_coroutine::pull_type::get()=delete; + +[variablelist +[[Preconditions:] [`*this` is not a __not_a_coro__.]] +[[Returns:] [Returns data transferred from coroutine-function via +__push_coro_op__.]] +[[Throws:] [`invalid_result`]] +[[Note:] [If `R` is a move-only type, you may only call `get()` once before +the next __pull_coro_op__ call.]] +] + +[heading `void swap( pull_type & other)`] +[variablelist +[[Effects:] [Swaps the internal data from `*this` with the values +of `other`.]] +[[Throws:] [Nothing.]] +] + +[heading Non-member function `swap()`] + + template< typename R > + void swap( pull_type< R > & l, pull_type< R > & r); + +[variablelist +[[Effects:] [As if 'l.swap( r)'.]] +] + +[heading Non-member function `begin( pull_type< R > &)`] + template< typename R > + range_iterator< pull_type< R > >::type begin( pull_type< R > &); + +[variablelist +[[Returns:] [Returns a range-iterator (input-iterator).]] +] + +[heading Non-member function `end( pull_type< R > &)`] + template< typename R > + range_iterator< pull_type< R > >::type end( pull_type< R > &); + +[variablelist +[[Returns:] [Returns an end range-iterator (input-iterator).]] +[[Note:] [When first obtained from `begin( pull_type< R > &)`, or after some +number of increment operations, an iterator will compare equal to the iterator +returned by `end( pull_type< R > &)` when the corresponding __pull_coro_bool__ +would return `false`.]] +] + +[endsect] + + +[section:push_coro Class `asymmetric_coroutine<>::push_type`] + + #include + + template< typename Arg > + class asymmetric_coroutine<>::push_type + { + public: + push_type() noexcept; + + template< typename Fn > + push_type( Fn && fn, attributes const& attr = attributes() ); + + template< typename Fn, typename StackAllocator > + push_type( Fn && fn, attributes const& attr, StackAllocator stack_alloc); + + push_type( push_type const& other)=delete; + + push_type & operator=( push_type const& other)=delete; + + ~push_type(); + + push_type( push_type && other) noexcept; + + push_type & operator=( push_type && other) noexcept; + + operator unspecified-bool-type() const noexcept; + + bool operator!() const noexcept; + + void swap( push_type & other) noexcept; + + push_type & operator()( Arg arg); + }; + + template< typename Arg > + void swap( push_type< Arg > & l, push_type< Arg > & r); + + template< typename Arg > + range_iterator< push_type< Arg > >::type begin( push_type< Arg > &); + + template< typename Arg > + range_iterator< push_type< Arg > >::type end( push_type< Arg > &); + +[heading `push_type()`] +[variablelist +[[Effects:] [Creates a coroutine representing __not_a_coro__.]] +[[Throws:] [Nothing.]] +] + +[heading `template< typename Fn > + push_type( Fn && fn, attributes const& attr)`] +[variablelist +[[Preconditions:] [`size` >= minimum_stacksize(), `size` <= maximum_stacksize() +when ! is_stack_unbounded().]] +[[Effects:] [Creates a coroutine which will execute `fn`. Argument `attr` +determines stack clean-up and preserving floating-point registers.]] +] + +[heading `template< typename Fn, typename StackAllocator > + push_type( Fn && fn, attributes const& attr, StackAllocator const& stack_alloc)`] +[variablelist +[[Preconditions:] [`size` >= minimum_stacksize(), `size` <= maximum_stacksize() +when ! is_stack_unbounded().]] +[[Effects:] [Creates a coroutine which will execute `fn`. Argument `attr` +determines stack clean-up and preserving floating-point registers. +For allocating/deallocating the stack `stack_alloc` is used.]] +] + +[heading `~push_type()`] +[variablelist +[[Effects:] [Destroys the context and deallocates the stack.]] +] + +[heading `push_type( push_type && other)`] +[variablelist +[[Effects:] [Moves the internal data of `other` to `*this`. +`other` becomes __not_a_coro__.]] +[[Throws:] [Nothing.]] +] + +[heading `push_type & operator=( push_type && other)`] +[variablelist +[[Effects:] [Destroys the internal data of `*this` and moves the +internal data of `other` to `*this`. `other` becomes __not_a_coro__.]] +[[Throws:] [Nothing.]] +] + +[heading `operator unspecified-bool-type() const`] +[variablelist +[[Returns:] [If `*this` refers to __not_a_coro__ or the coroutine-function +has returned (completed), the function returns `false`. Otherwise `true`.]] +[[Throws:] [Nothing.]] +] + +[heading `bool operator!() const`] +[variablelist +[[Returns:] [If `*this` refers to __not_a_coro__ or the coroutine-function +has returned (completed), the function returns `true`. Otherwise `false`.]] +[[Throws:] [Nothing.]] +] + +[heading `push_type & operator()(Arg arg)`] + + push_type& asymmetric_coroutine::push_type::operator()(Arg); + push_type& asymmetric_coroutine::push_type::operator()(Arg&); + push_type& asymmetric_coroutine::push_type::operator()(); + +[variablelist +[[Preconditions:] [operator unspecified-bool-type() returns `true` for `*this`.]] +[[Effects:] [Execution control is transferred to __coro_fn__ and the argument +`arg` is passed to the coroutine-function.]] +[[Throws:] [Exceptions thrown inside __coro_fn__.]] +] + +[heading `void swap( push_type & other)`] +[variablelist +[[Effects:] [Swaps the internal data from `*this` with the values +of `other`.]] +[[Throws:] [Nothing.]] +] + +[heading Non-member function `swap()`] + + template< typename Arg > + void swap( push_type< Arg > & l, push_type< Arg > & r); + +[variablelist +[[Effects:] [As if 'l.swap( r)'.]] +] + +[heading Non-member function `begin( push_type< Arg > &)`] + template< typename Arg > + range_iterator< push_type< Arg > >::type begin( push_type< Arg > &); + +[variablelist +[[Returns:] [Returns a range-iterator (output-iterator).]] +] + +[heading Non-member function `end( push_type< Arg > &)`] + template< typename Arg > + range_iterator< push_type< Arg > >::type end( push_type< Arg > &); + +[variablelist +[[Returns:] [Returns a end range-iterator (output-iterator).]] +[[Note:] [When first obtained from `begin( push_type< R > &)`, or after some +number of increment operations, an iterator will compare equal to the iterator +returned by `end( push_type< R > &)` when the corresponding __push_coro_bool__ +would return `false`.]] +] + +[endsect] + + + +[endsect] diff --git a/doc/attributes.qbk b/doc/attributes.qbk new file mode 100644 index 0000000..194d910 --- /dev/null +++ b/doc/attributes.qbk @@ -0,0 +1,107 @@ +[/ + Copyright Oliver Kowalke 2014. + Distributed under the Boost Software License, Version 1.0. + (See accompanying file LICENSE_1_0.txt or copy at + http://www.boost.org/LICENSE_1_0.txt +] + +[section:attributes Attributes] + +Class `attributes` is used to specify parameters required to setup a +coroutine's context. + + enum flag_unwind_t + { + stack_unwind, + no_stack_unwind + }; + + enum flag_fpu_t + { + fpu_preserved, + fpu_not_preserved + }; + + struct attributes + { + std::size_t size; + flag_unwind_t do_unwind; + flag_fpu_t preserve_fpu; + + attributes() noexcept; + + explicit attributes( std::size_t size_) noexcept; + + explicit attributes( flag_unwind_t do_unwind_) noexcept; + + explicit attributes( flag_fpu_t preserve_fpu_) noexcept; + + explicit attributes( std::size_t size_, flag_unwind_t do_unwind_) noexcept; + + explicit attributes( std::size_t size_, flag_fpu_t preserve_fpu_) noexcept; + + explicit attributes( flag_unwind_t do_unwind_, flag_fpu_t preserve_fpu_) noexcept; + + explicit attributes( std::size_t size_, flag_unwind_t do_unwind_, flag_fpu_t preserve_fpu_) noexcept; + }; + +[heading `attributes()`] +[variablelist +[[Effects:] [Default constructor using `boost::context::default_stacksize()`, does unwind +the stack after coroutine/generator is complete and preserves FPU registers.]] +[[Throws:] [Nothing.]] +] + +[heading `attributes( std::size_t size)`] +[variablelist +[[Effects:] [Argument `size` defines stack size of the new coroutine. +Stack unwinding after termination and preserving FPU registers is set by +default.]] +[[Throws:] [Nothing.]] +] + +[heading `attributes( flag_unwind_t do_unwind)`] +[variablelist +[[Effects:] [Argument `do_unwind` determines if stack will be unwound after +termination or not. The default stacksize is used for the new coroutine +and FPU registers are preserved.]] +[[Throws:] [Nothing.]] +] + +[heading `attributes( flag_fpu_t preserve_fpu)`] +[variablelist +[[Effects:] [Argument `preserve_fpu` determines if FPU register have to be +preserved across context switches. The default stacksize is used for the +new coroutine and its stack will be unwound after termination.]] +[[Throws:] [Nothing.]] +] + +[heading `attributes( std::size_t size, flag_unwind_t do_unwind)`] +[variablelist +[[Effects:] [Arguments `size` and `do_unwind` are given by the user. +FPU registers are preserved across each context switch.]] +[[Throws:] [Nothing.]] +] + +[heading `attributes( std::size_t size, flag_fpu_t preserve_fpu)`] +[variablelist +[[Effects:] [Arguments `size` and `preserve_fpu` are given by the user. +The stack is automatically unwound after coroutine/generator terminates.]] +[[Throws:] [Nothing.]] +] + +[heading `attributes( flag_unwind_t do_unwind, flag_fpu_t preserve_fpu)`] +[variablelist +[[Effects:] [Arguments `do_unwind` and `preserve_fpu` are given by the user. +The stack gets a default value of `boost::context::default_stacksize()`.]] +[[Throws:] [Nothing.]] +] + +[heading `attributes( std::size_t size, flag_unwind_t do_unwind, flag_fpu_t preserve_fpu)`] +[variablelist +[[Effects:] [Arguments `size`, `do_unwind` and `preserve_fpu` are given by the +user.]] +[[Throws:] [Nothing.]] +] + +[endsect] diff --git a/doc/coro.qbk b/doc/coro.qbk new file mode 100644 index 0000000..b2c199c --- /dev/null +++ b/doc/coro.qbk @@ -0,0 +1,85 @@ +[/ + Copyright Oliver Kowalke 2014. + Distributed under the Boost Software License, Version 1.0. + (See accompanying file LICENSE_1_0.txt or copy at + http://www.boost.org/LICENSE_1_0.txt +] + +[library Coroutine2 + [quickbook 1.5] + [authors [Kowalke, Oliver]] + [copyright 2014 Oliver Kowalke] + [purpose C++11 Library providing coroutine facility] + [id coroutine2] + [category text] + [license + Distributed under the Boost Software License, Version 1.0. + (See accompanying file LICENSE_1_0.txt or copy at + [@http://www.boost.org/LICENSE_1_0.txt]) + ] +] + + +[def __boost_asio__ [*Boost.Asio]] +[def __boost_build__ [*Boost.Build]] +[def __boost_context__ [*Boost.Context]] +[def __boost_coroutine__ [*Boost.Coroutine2]] + +[def __coro__ ['coroutine]] +[def __coro_fn__ ['coroutine-function]] +[def __coros__ ['coroutines]] +[def __ctx__ ['context]] +[def __not_a_coro__ ['not-a-coroutine]] +[def __segmented_stack__ ['segmented-stack]] +[def __signature__ ['Signature]] +[def __stack_allocator_concept__ ['stack-allocator concept]] +[def __stack_allocator__ ['stack-allocator]] +[def __stack_traits__ ['stack-traits]] +[def __stack__ ['stack]] +[def __tls__ ['thread-local-storage]] + +[def __acoro__ ['asymmetric_coroutine<>]] +[def __attrs__ ['attributes]] +[def __begin__ ['std::begin()]] +[def __bind__ ['boost::bind()]] +[def __coro_allocator__ ['stack_allocator]] +[def __coro_ns__ ['boost::coroutines2]] +[def __econtext__ ['execution_context]] +[def __end__ ['std::end()]] +[def __fcontext__ ['boost::contexts::fcontext_t]] +[def __fetch__ ['inbuf::fetch()]] +[def __fixedsize__ ['fixedsize_stack]] +[def __forced_unwind__ ['detail::forced_unwind]] +[def __getline__ ['std::getline()]] +[def __handle_read__ ['session::handle_read()]] +[def __io_service__ ['boost::asio::io_sevice]] +[def __protected_allocator__ ['protected_fixedsize]] +[def __protected_fixedsize__ ['protected_fixedsize_stack]] +[def __pull_coro__ ['asymmetric_coroutine<>::pull_type]] +[def __pull_coro_bool__ ['asymmetric_coroutine<>::pull_type::operator bool]] +[def __pull_coro_get__ ['asymmetric_coroutine<>::pull_type::get()]] +[def __pull_coro_it__ ['asymmetric_coroutine<>::pull_type::iterator]] +[def __pull_coro_op__ ['asymmetric_coroutine<>::pull_type::operator()]] +[def __push_coro__ ['asymmetric_coroutine<>::push_type]] +[def __push_coro_bool__ ['asymmetric_coroutine<>::push_type::operator bool]] +[def __push_coro_it__ ['asymmetric_coroutine<>::push_type::iterator]] +[def __push_coro_op__ ['asymmetric_coroutine<>::push_type::operator()]] +[def __segmented_allocator__ ['segmented]] +[def __segmented__ ['segmented_stack]] +[def __server__ ['server]] +[def __session__ ['session]] +[def __stack_context__ ['stack_context]] +[def __standard_allocator__ ['fixedsize]] +[def __start__ ['session::start()]] +[def __terminate__ ['std::terminate()]] +[def __underflow__ ['stream_buf::underflow()]] +[def __yield_context__ ['boost::asio::yield_context]] + +[include overview.qbk] +[include intro.qbk] +[include motivation.qbk] +[include coroutine.qbk] +[include stack.qbk] +[include performance.qbk] +[include architectures.qbk] +[include acknowledgements.qbk] diff --git a/doc/coroutine.qbk b/doc/coroutine.qbk new file mode 100644 index 0000000..b09a634 --- /dev/null +++ b/doc/coroutine.qbk @@ -0,0 +1,95 @@ +[/ + Copyright Oliver Kowalke 2014. + Distributed under the Boost Software License, Version 1.0. + (See accompanying file LICENSE_1_0.txt or copy at + http://www.boost.org/LICENSE_1_0.txt +] + +[section:coroutine Coroutine] + +__boost_coroutine__ provides asymmetric coroutines. + +Implementations that produce sequences of values typically use asymmetric +coroutines. +[footnote Moura, Ana Lucia De and Ierusalimschy, Roberto. +"Revisiting coroutines". ACM Trans. Program. Lang. Syst., Volume 31 Issue 2, +February 2014, Article No. 6] + + +[heading stackful] +Each instance of a coroutine has its own stack. + +In contrast to stackless coroutines, stackful coroutines allow invoking the +suspend operation out of arbitrary sub-stackframes, enabling escape-and-reenter +recursive operations. + + +[heading move-only] +A coroutine is moveable-only. + +If it were copyable, then its stack with all the objects allocated on it +would be copied too. That would force undefined behaviour if some of these +objects were RAII-classes (manage a resource via RAII pattern). When the first +of the coroutine copies terminates (unwinds its stack), the RAII class +destructors will release their managed resources. When the second copy +terminates, the same destructors will try to doubly-release the same resources, +leading to undefined behaviour. + + +[heading clean-up] +On coroutine destruction the associated stack will be unwound. + +The constructor of coroutine allows you to pass a customized ['stack-allocator]. +['stack-allocator] is free to deallocate the stack or cache it for future usage +(for coroutines created later). + + +[heading segmented stack] +__push_coro__ and __pull_coro__ support segmented stacks (growing on demand). + +It is not always possible to accurately estimate the required stack size - in +most cases too much memory is allocated (waste of virtual address-space). + +At construction a coroutine starts with a default (minimal) stack size. This +minimal stack size is the maximum of page size and the canonical size for signal +stack (macro SIGSTKSZ on POSIX). + +At this time of writing only GCC (4.7) +[footnote [@http://gcc.gnu.org/wiki/SplitStacks Ian Lance Taylor, Split Stacks in GCC]] +is known to support segmented stacks. With version 1.54 __boost_coroutine__ +provides support for segmented stacks. + +The destructor releases the associated stack. The implementer is free to +deallocate the stack or to cache it for later usage. + + +[heading context switch] +A coroutine saves and restores registers according to the underlying ABI on +each context switch (using __boost_context__). + +Some applications do not use floating-point registers and can disable preserving +FPU registers for performance reasons. + +[note According to the calling convention the FPU registers are preserved by +default.] + +On POSIX systems, the coroutine context switch does not preserve signal masks +for performance reasons. + +A context switch is done via __push_coro_op__ and __pull_coro_op__. + +[warning Calling __push_coro_op__ and __pull_coro_op__ from inside the [_same] +coroutine results in undefined behaviour.] + +As an example, the code below will result in undefined behaviour: + + boost::coroutines2::asymmetric_coroutine::push_type coro( + [&](boost::coroutines2::asymmetric_coroutine::pull_type& yield){ + coro(); + }); + coro(); + + +[include asymmetric.qbk] + +[endsect] diff --git a/doc/images/event_model.dia b/doc/images/event_model.dia new file mode 100644 index 0000000000000000000000000000000000000000..c32b223ad452122152783f79128f562595a096c3 GIT binary patch literal 2498 zcmV;z2|e~7iwFP!000021MOYgZsRx-eebU@+^=bd_se+FgBf5K`>?D81VJe}Bd zkuBqNbv6C>pMU?Pr@wsu@bfZ`euAG>9^Fph6Y0jiyPDn<#rmhq%ZG;tlss;tBFj+{ z-=U4Y{9lwL(IqsxoPPc=nY`{`85NN`RvnFsB9G^Hg`K3)t-YGgqs7-%p53L(Y1yi3 zx5$z#pWH{u)%4>R|C(M_-CS<AL%TEgzZ?4x_UgS|+>_g78EU{5qq89m`?T)iqL!;ZVvqM?_Ecv;+Be+#r+W$%xHdcTi1@jS70qmR>K zu3;X@c=mqzcu#96dux=3rMAL~7ccGR4Kv$r)6mmR)$H=ERo`vbc45jx zJ7Vo2lIYRqRrmjZQ)ly4?sR%mo#BddCefmZ@1NeiS`;Vi>#Zfo=KryaqMHA|iYQ%1 z`Ev4U@>}+8`Vy8vP!cb%roZF2)&{x*a@_3O**bf+G%Ar17YcrLw`hkmpVxWg0_Lzv z>Y@Wn=a?M@wOlkWv58;pYS*m${D{vighgvjL9*!Cx*Q(1RpJ4D?YNFL|2j@QD$FfS^=Z#vnPu5D#eTq>f;% z!p8##=3CPkf(K4oHioHi;~Ikyi|wh@HpavaL$+uPW7fXsmW^383|LY9gT@195ZmWK zr_wJK6@84at?fVIaU?NQGcYd&I^ys-m2?aU#(?11-GOEc9s`223J99`?{7&GuQ#?v z{t4c$c6^%w_rghzsKVxg_`h1}fo&bYWB z0!b0l;3^o7jPXSz9D7hrR7cny^=zlG?e(=rU6=7@9Tkh40|I-91zU>iAkup5IBI3} zSW!I|bYD>08D|%)Ivs7LqQ?R?Dd=JSY*xa0?L5X&_H?sZHA6BL?afpMaR{!QMiE75 zNV2Msh;jRbbjC4KqEtslnR@?x^$^9y_vEO3iu!$?#Q)vSNQ1Ey-hT1mEY)O~BT=0r zQL(;61#}^#qYsU6SfYAHQiyw^5{MBXDnO=ZK~zs6)x<|wBW0b6vKmcvq^xtKEZ#e| z5^mqh6Nxc0Xl(U@#4({W5Kke9w?xAA8Bi9?$Hw<|WULbhSxpu?GS+)$tY7m*-BzjS zO;`$n(fLS+2~EQ8Tp1}m5T(ZP6_%X3U>_6Rktr8hnqJ#=IXyFvTPBqm;F{21(ig)#p(bs<%U;ihZ$~tFP10*6w3*nqV z8XYtdp-5}YJrdQ@RYGIPL|iclBqE`8y=j^?O{sfoz+MIPF~b6TU#xdWQ}-mIqb`f4 z%TVt1+;t1*5`7`wbfuOE?%acgfj?~aGSR31R0TTTVT!D*8JN8M`)VroETQJZr24Sj z{w&#l^3l$S26A%UThTe9?he`TPVVY)j8LVM8$NS)?@-3PldJHG-ZanJyC99=e7~}B z^z$Fl&($%0uD_F?n*kzs;%|iCdU!{O%e6{M2ZOm%?rJU9M!H%PrYR2}wb#tCz8w|) zs-8ywey{#bw_Q{o^2g+ATQTHfTJ~GwFRP8KtWU6yys^0aSkf2arMm8*L zOy!W6yNpWwz4J2xF_o$`lTtG>)yP!sEOTV4b7ZRig`5Nl7h_Y>qinHVO!b1qw9Eb& zlGMGY?qQB+kp0E!efHIkRCThTs>w7*syaui>R*T|-D82Iw}-IlpF?u#A0Sbe{&9^2 zcgPgsvq=9&sycC0)nu9@Rh{46F|>CTEqC9)>Vj+){O%ZqTur#UXa*YTD(1Ct*U*$J zOc$ZA*fmYFc7udeO=;&9X?@uaSldWNsG^ccp}N-djRo`PQ!rn?_g@(L`NP&Xihlm^ Mf6X)6ly!Un0PJ+;FaQ7m literal 0 HcmV?d00001 diff --git a/doc/images/event_model.png b/doc/images/event_model.png new file mode 100644 index 0000000000000000000000000000000000000000..f52b22f5b1d0d26ce35a9fb42ea4a267d8cf00ca GIT binary patch literal 28915 zcmbrmc|4VE`#pRqgv>;lw}fP>%tP45%A913N+ppYMamFcrjQhwDnrqLWC$r!kz`KE zl&MTnGNe*|Yj=OYf4uMK_x|pYM1IM%V&I<6>lQ$rReJ|==7Sauuf zSrWt=W`dw`XIO*(BDZ|@7XEMTY4UC>1_p+{VY4Cp$mnHcdzv8DwNw8`QxP+c_lOX? z^>nQK)4vQHci7i7xbE9-1`>O~&V@Kh5l0T`ha+oV_inqLH_?0E>CAxx@1wJ_LOnLP z=bc=u&n?}Pl*JI5m6aE1H7RptM_bIfQ?dtWXfEzf`MQ4BZk6#dzpIJ!T4ye;VX$Cl zXZKo|u;|~KTISl3wVqdY$W-UTx(w6RZ^LV6xmUkVcI6fyYpKf!U4Cworqt2CnJGL} znWn(Wnt{K#{g_66@7cTe1VJ!`*NxA6N03sN zetZmGM@u_oIu}@5TYIE2?qx=(>zd@|?yj!-j4Vb|Q_~1mL41vC@2|z_LZ@n0;`Xp9 zIg;_{(W5*u;p%i9w6e)D>f|nNZtgE%zIf$|q~5)MKU&Cq?z0F(s0d}#R!&+ScD-Vw z1nQ5e`qB7_wnN%d3_nF4OAy6RItnq|jG-b7S++Sw_=&wmR3ATuy7K%#e|5`M{liON zK5#R=Y;R}xV<%fB=tqj!?G}rnT*-UzpkDAIyN3%0r_9v~j}99B(6;ffABr1$dsAzj zCAV&6;a8HDkzx6>KJcz)zydq7#@R16OyQkRJU=EGmXwy>F_%mfH?gY@dX|)w^!V}j z7g1cz&CMOv=JR#JT0uGHlH5#N6ciTvtdvjndwF|XSXdmA-}vj-uj-ZWdreJQ1G;YC z7TLCK8+}-2Uf#s)Y|z(1%~~62o)~hbw9O+zGehl+R5-cl(IeON*YDr^{rR;h&C}f9 zFF+6^W)_w+US3xc6BCn@pFDYD$mM=$HnQ-Fexx*6Z~9PA*==*lZ|mNqt8x+A*&Uad zHyu8Dl#X!Tk+NyiCiXfCg?53b?WSfJyS26Tj*ZMjO{Q*yolr+Z13e+KWy_t+%#K_$ zVTA(8NUR^^ zCY)Ve^Bz8ACT?5a!);C?0g_FfEo zM7_?=zPQ4>iT%&c^Ahr|?YD(f7~&gsyX_1N3@j|XC)zXb-Mcs8l-H+@Ppz#v(r_7< ztFHb;L-bduxM_O%sR1>fm^(Q+3Ww?_k00--t>)8p>N=2>o&9@h=E1{<(wjGD=jM*j z%>0=fHC(v+)bDFqNeS-R!rXkLhX1#Lr+#!tGG8A1yYh45L}E;Ae0*EwnQ8oU_rZhp zq_O#*Bob+9Y3cFf$HwAat*xvkkHRZXCN?EXtR+k{)E2Y#MYnBRzv$*S^>*vEzQIAE z7B)V@V{L&$1t)o5NNKcu8i{X@~*Po z)vJGFf}&l084evhcu-$Ie8^<{P0Cu5w49vs_U$x8Pj7ElZTo?6>n)E`3is^UGxhh+ zZ+5a$+MlHvdj%a&&#%3`b_|Dh?`}#{_I&=ksN`l3FXLl|GfSdk1^S`x_P$eP`VkwV zb{=mHxy#_Tyfo8CLm+rB>e}$FFgbyRJC}{iHK-nHzaY5NFPuSy*^l{q6PZ zJv!@D)YQ~&-n@x*aF9Ll?QXn~CRtb3_A%`fKuahwG=;q{_HEZ5>chi~ex3X#v!}6^z^?9nQs^TaW6%`R7 zC%W#I9@AUcpSjM`((<;9qvkeANwF=kt7yTV}GQq^67DUlgS_8Y1Z5EA;(7OUw4Kd;4+=>`J9XMGyCt zd2n!W5Fs7ehTFDo)i*RmBoWR-H5Xm;J>1-w2uW#a_oGsK_wMD1*>7%MbGXRY*Y{z5 zzTxGK+fRRf({O@@z!ua;u(2^Q?J+U&aCLP(`eXFZ?;l@3R%;R31_t6=w=$Y|eRzBn zF+&hYy!-ce7awm6@|*3QGK`^QXKxO0K6b3kty^kAMMfsSq=Z}ZU1#U_Z{H&0;%+LQ zuU~h1e&J_AMWTWWhiFWDyhtC9iRq)TCo*bdFBFN}o*$nN4i4gotRq(D8ihk? zT5oB|`4@f+UVWyxIcs2>uN4is-2KB2A|y4HFCbv-6^BArN{a2RU4esD=jQk4ym@2H z6n@ylV+N;hzD+CJGhq5%K6@QXSGKo_vGLotZwbOmYO%NUWb8)&_khJC?P| z>VctFkwdR;S@2gR{Bb1dE^}+*;<*M=N*t?%lzP z(}v%;1=IB+SXh{umoNPFGju;!q!YoTsG!iE^Bmi-I9$K3b{`uX8&T6!bcD_`+SKHL zwRLJr%72S?#80+Cu+0uUeTS=fduUut zgrXtj`E~G_l9CcLeOQxAYFe7vtzCZ**`YK&?n+bp(#V-E$B#cb_9ChJer2Vau&}U> zhu-*IzHL%csq%+H@7>#)*mNy1ai_BK$47_OCO3z(`&9I2sQaPBC7R}<{H!i?nR$DA zhjQRI#7$Cj@Va-e!spW-Vc}a}|JMDy@V7Q>-OQIS8<{Vqr>Dop#t!j?C21vDU!{oZ z@MM}gQeHeZ(thmG(bUHyXe$1X=Y|bJLPAeY4KT5> z8O~&K>Dk-b+CKB28~N0n3_MVip|9bslK%a>PybdmW=eZ&tE`;dS8O(tbor>S7#$s5 zW?%WTceX(+3d6gXF}%AIM0(eGs77_2qC84SN_yAcUXzhkuMzxLLr%`j(C|32$;gwQ z$Tpfy7q=g2FxtI)pz7Q{FXLE|UP{~eT1&A=>zK=zX$fXQH7n`lP1~h8`S|$Ou48p6 z{&1l1;LD6~_T4UKp{`sd_cv`nOgk%*L1vDjC7efIL=hp8kqrGh3<-MCv>gFIfBrG}VJyz& zm((g+rXOmWoBdlXPEh@l`g+pgS2t-p_T-w~q9>Mr{TgBIjKU`YkFNm~&o#}sUs%|P zW!M5lm_PTqC9R}HK~Yf=N4MWrEkT8s5oil3FFKmZYKAfE`m3muS&FQJYP7@+`9o_m zOdB)qa7F!WbH)`BA?@wvSTGCnl33)|5ib>d_!`1EO^F2;wdJ$^;g#h%WSOR>rro=D zQ!hR&y_&h#LZfV0Bqq>{4govtY z1+uu5loXJu73z~<1p8TOo(GQ}J<7{7O8=!HqPEA;G2`aVgTMnQJMJDH-i?#KBh?Z# zgqM#GpNf~Wt?dWYKJVisJ>o;6h@IE3g~_W|uRag_y@wo5a9WBH#I^$kw8WReXKDR5 zh8K7%Dx+LkDL^Xh|KMN0ZDnK*H=~@KoV|Tte}Dg@NAmr)42KM{zMoN}tAhU6nVETh z=_x8N_T|{Um6Mz4=uB@Z5#rHTmN}yr>dLy6Q}1;9sTW!LF*$eccpsk>y-NK~;5r~= z|JA?1X^prz3Lg@ZtEG?6cO*sb4I7TPq=u{SeV=gq7EL($15zg8JpZ|cg@vVjl)WzI z%Wig)-p5DR6V9kS-@kvycMC?9(^`kLjraBT-p$R$3Gr%8SH+vj$P)KZn;$>E>L72z zfa|R^j2FU>!n-b9$vc_7#no&Lp?${^pqGK@DLJvv!$W3Pg>psoJm-ri?+TsRNa?Dl z@9OPm5ILd0hLHCed$lX@*Vy=Y;=A<7&p2-vKHPaUi(NB7Y&%hJv*j;IYtk) z{&;J8)99So_+UWRqEWE`Mcdf;4b~LZ*ZQH&MfLSUoOW3M;J-g}@85SbHa51h;zj^^ zkt*dqBI}2T;&d4}(8-M<=b#}$4aSbt zXVCFC$#4=O6{pAT?Cq=Pnf?vrUuo=gSf9EmgS!tEs*2MxQsmQ5lDiC|K{S~_J*3%Lp zd+v}Kn#^>sJ`4KO+u3=QoQY~_V{bni7nZ2^b+?H6*)O>8YQL{SCj^tUMJO((b*}0# z1piq)AQZ2Byj6g)kUiwfZV})@^nr(Wq@b`TzS*0}NW(z7zUPkRJq;l?Kb|%5ToR_p z9I-DCBQfkbEa%3FLrM_x-Xkw=1^v2r_pU)!YLrB(o~e;hd31C%ipyH!?AO8ACuC*; zm2VRr*&El2=j-Nv=# z8>y*oD0bV|l7kpmsf7jGNrcd?=i%n%9gWlV%}NRmaxo)cefjcbhU)1{O)h0VT)V`@ z8=9J;nq2VJe~`xrLN>YCSsq86wtiuz??=tWwI;00L}YaI4rOIFX67&N3vb26u~w3@ z%geE3Pb(_}aCk3Wx&-hcsP!O!U6espRTV+#USb}e|5;vaRLQS=3QfW5rlyp%G(&xT z9R^i_xXYJ~4fT+kk)Ou61ulTqbYeZLn4K4~@T~u7$CF+;#-n+LtV3CQuk%QyVkt1EU z#l1XN_lwapZ}bA3*EO_ya-Ehjnb2-_5`U972RVc(5(L z@%L;EkmKvuuU#j}moHxiC|E~I-*NQy)h&BPDDFyq?d{(hV)s zC0@OLeYW)t2U$v79PPV=#1E9ccg(?yQ~S4W-5O?KwSPZwH4~}7^knkWr|J|hKxwpP zGHmY7&R(MpED9~Ck@R7;!(Jj3Y!xFb>ugSn2Uq4c&T`fr3QFkAiIDJceYAEGtx_G# zRa#na3uO|UzJ#i7q38t#?V7q)vN-(-%UV%huJE(4r^g2UkCj!<{rkVb6!6f?%E-_W z$itbrC`Vrho+<}C%*>oaP%E|VmS8UsC5bgIPfbm=3o0O!Pd>EWk{a=2{J_3_Yl$n# z$*Ub7MD%EHwf*?^jUfC30?PinxY*gHrl&g?8#Bi@V(n3YkwPY%!o$P=0gz0Yw>x(9wmPrt6Y#(9+S-5NJK;zklylIBa8cBRrfgvB_%RK7Mrn)9(xERu321prgYP zl3^3rxKZW8vRADGtDzAP2Lk3O<@-rO;bHBPSm`3kyBDq zVzQYt3&np8aXTl6qR!AUiG{ly8%smP@X9r0==(EvwD`uw#ew%w_8bHUvADQsc2ncr z+z8z-;KOG)28a!1q#fY5GWl;P^S)=!bf`)a+I#jyKYNB0(KeoZ{knHWzmk6M6VF?T ziCy45E(8WzBqvdN!p4rNH4&tRxw)p+*0Q3a4;7~+W>v_{e%F5qll?EtNl2`_F7S4f z8fgh(PY^&y31o`vMWj)ZVq(@;%G7`SxQc!WB{^fg-~q9Sv==?- zq9u%P)O_-JpKos{^};Vfv_lGacT?RDIc#xh=`WT1RMC?N9ZRR!AZn-t9B|IJ;ri9( zQMP_>Jz^_oeyQKrgJx!Jb#-+l-AlLEJ2MpNMN>A8&i~{RHwNa{|1mqzmZ5IQNAYC& zzNwTa)<7MH1Is%2JW$GPBq>N_(u{l||KKyV9Xl*dOlClRQo*x8hjtL7$L+kl zje>#$`SwqM1Wj&gq&RSIOL}#aQqIK4NL|Ye(`w(zt5H#mvnrxt+)-s69UW0IG5ahm zD!hj4knz16Noqo>-ZDuAj!(icO=lPr5}phyvHcPb?3Hi4-j%F?>szZL4N}J zy(3u-mc(9|9X^Z>fa{#bATmy_aZ0FqTqH?9OVh#8QO@Z(_|0eM=09J#!jAXH;f`;d z`}zITqeJ!BjFmrsnyeks40MItr!^|Ow=xuv!G_TD}KQLr+A9rc0{u3DC2 zdUiTG7t_-0&z>#2bLZ1c-xE5=X7(9_XuSv&9LDwQvpZZ*0#Q}@Z?6!x*Gu4fpS2CN zdUy9C8oObGVyVH_AsgL z8z{mzahH{qO-@dt(>2<=ce*RLZGODXTS7E7>DH~)Zj0K_==}u_s$@DY0ki;D8R+XH z8xNa`ox<-*0|+4ZOujD^t{k3!eSKRw7l(1^2 z{qK>N=s&Mt-$0RaDy7=CCiHd;Rg_Eh`3+H=3`YKfeN3mOHw;6;)MJtRiK#qxIf*w6_}>8>cIrumpRE z^oC36Z*LD#=cXYDA0Ho_3(xOPzG{K!tQ^xLmaTPl>qF_*qd5m$f*&N>h*#dtp&=B$)FJ~LZV?Auq zp3`r$erRN5B)@(8xj(;3V`EMH{Z(f5XrVsrO_u#LrN3!Y%EzGJEPI8>BI4qAQJA8l zqEIo|gm*DBFw|riJ-#A(F(Nzr$gyLek+vS(zmGHo?9g_s*gN8?FIt7)_f;dn1x`;- zkG|GLi3a?A-`tEhy1mV_0G;aR5-Ag&>~nPilNtyjg1|A7J&8t;B{c@NHs7@ zM_>*S62&F)d3=1-RE)Y%Xb^d2>?rDyFNVET(2MX^Haj{wtpZ}=jIm!;F9>J3)dq5~ z{$0L(g+l?rlC$#=qGxw;_YCBs1AsXW4lB3?poh-R&aA8~plnoM{2BB<{tQ$in+$}C z)~9iN{^x~V@r~H_#DoN$$)`u+n44p>^E2a@L5 zYhq#og5Jywokzri+j~}|1*izdd-rCWOIH8=H4QEYh|oJilpsR584pqEOrRvxE+Pbw z11bdyWUTtNEB7BheAC*B;$8pXKw)%D%<0pou|4*7cE6y>I5aCIHYuQ4c=yiwzyW1t z<@b2KeG8bRATKYn_}5;5^>}lV4Ej@vTW!ipO3!^hMVFQ;&YHgnP7ozkp$B-1x{3-& z5I{I6?%Rg{c<3?LMoWF#dmO?5v&1B1`Q3Vpr=#uu-5pwqE1=H?Uc^3$-HU{F(2 zQ$s`7gw>i`S>Z+!!Tnvoey3|vfiKn|+u|OG#^_(UVeDvDPFFYx#RJ*aA1hWBFtxSa z6ySgt7Ruf}Et&c{0Co{TGdcM^`NwCdCrH1biLoc($V!Tfv3FmuvF(Ktgm^xXZ@1sZ z1|0pf3qQXvPQPz{{d%NTe}&CbjA94k9ASw%gRlT9dx8!MhhT4O2eWMQZ!9K&(Ve?@ zkD+deiw8aZi#`=)>DaMjNZAk=U%b%6R+^aPV-swY`bZSeg6Gbi`|#ldpf02rTqO8I z{8&*@@$qlJjdT(!G>$7;Oz`zG($ZpMzXKPiu2)wFTUv4wHFb3(w}Sr`m6W^?jCNhK zl~Z&GB@-z^dw^$hdfKzMq^YgVO!~kUP%;QBMn*;uY6uu;2!GrNh;V=pOK2BxH4P07 zs739aopqe0f?z&T;;7Ben>TMBK75EWe$2(i3XvdWfPJd1&BOvgfkTSoZMOfpEBJ5K zv6j?*xu!Y{VA$5IT`OUds>k|*Q3eIQ0kj5kAZjrU@$TI_Y8hHuY7o4?)=MQF3VKd% zt`_>UVT1icD2?S zOsy~4a^T%Z5NsAdp#H9E`4s=J7XZ=x8+S21HMNns0gWn>VgBvgA?myx;M4RNZ?$Pb zIs$i$Lcu`D@7&pkKZEV_8h6{t8ZnQp?CI&jH)3Iw4l=Z~w5($lSne{@!WQARyO;i$ ziAf6*Gdc@!CxOFt;SI5+l9H0>r0|Lp);c)W>hkErm!To*o&dy|OI`z@av!X+_4+7E zdRw`YWj|tA^x;e3?QXn?X>-Y+*Y%)I|pOmY`5iAY1x?GpTLpz$1=jQ6# zQ*k=iIi|CzX%eeJWlsvkAuD?hRF=-2iG^U?+Is3`^aj*@&ek{N3G6}yoAAwDfofs3 z>=&+DNu7Z*37875s8R6SNie@p&(3hBD@=9enmITK%$fsOiIRFz>-=W>w@&PDL2Lr0 zqD{)*gep9mkt#zX9Rk>k^^Cfjlq3Wy51ALGTYWMACI79qYLr~z;FaNt2}u~5u?T#fz?=xLxrI#zJPZod(DWu|6^Xe7DAMp*` z+>oD^1}?3ECWpF<&P83x_|&Oc@aQSVcW*0!yehCM5|x!LsQ(!As;!NUg=KMJL9b;v z({1?Er!&||){puzv9bFtE%$&mRc(7{TkNX9$3*SdCMG80Vs(7CTZ+9{hiks@^YLv( zWFsZ?eLQ?M|0vL!zP_jT?@u*;dS*zYR|efPh1}85tc_AIoQ~GD=|AMT!wr zJ|*B5z(BMk0C9vboTnJJ2Zm>42v)W|2%LrJ2`@!$cM6(Pl!R?3GFYfeWP5u%$7TbD z`=w=NOBdI!>v2E(TRgItOZQ~3vhtI{zs<+pSV430Hi-h}MEGW=77LbsLk z@B=VlO;@*sPDkP_VKcL{v+3+ZfdNAmpB5CHLOYXo*U8?VAMS!|L$=yYR_OJR8B5O} z)$@HgGBUFNfi;b`a|i@q78#J1ljP>m_(2sa3m;LgiMEDer9G7@`Y!igOQ0z zcSpwwpE+}D>zmt;7+kn;;pCD&!*-l?%E+8QN-ltsyN^DV3!j>DQ*b$WaPab`o$b>g z)xq3|7$)fnR{8&!^YcgWoajQV0U{ z;`4L!$I!sKe$_EGrRECAG3EQX1SbK8LWNEv^^2WEo+_Sg?3$tR9Eb{ zW0Y{kTt`Po#)_fjL*(nV8KzE^zV~{rt+V1oif`Y?&(BZoOyvfx*4TXS2R^T@r3w5R z`gct5@(`FJHZk1U`7O((;^NcIo3rZW5T*`KP6@`mL@+_(CfnMk z3^~PyQz#eG?pIayzki?Hr38M?_F$UT99z4L_163E#EmSi#jnC_n$zNt>kR*_G zf%=B%&*=Qa8&gwCH$T3+V7_bDF7T!JCk-h?{kaZUIA6#?69#)79S0gro;?2Wg+ieK zhhQgUot~%n^OG16Nzx_2lbd7n5sD$PJILf-IFY=!0}Lcz66h-}O-MSFu+IVh2B;e%5N0|&Cp*U0oLjuDgbcTeswRIVA_9qUyZ7l%O45X=|?#@n56Rw%t8#iuL zNS;EKcU9zNXD3L~l9J~8_MI#+TKWC8+Ep`Va6K7l`GAE5X`}JJeO!*41d!azKWDzH zh>3|o9Q%O;UnsnBO{m4r(J9DJTAd_xJ-sbXB<-74_+0s;bLG8ws#O-SR$l`HMbLA_;U`+kDZ($ZoI zC*QH`s9;&(g|CH(j*JXpH_+1~2!nW`{G6PH0S+1hnCZ*Ae14+l?OSfb9BKBo6_vfd zcS%LPW*0qbDG+^Zg!k#w#l^*-q`(V@{`6>B*G*N{fZhmN z6RAH8Qr9cd?*l2Ts;iHWkK^`(kuxXWD3Meljx%l$5U4_bap@90dI~UqSx0yUMMXrk z+xwUa?e_RN5LUszXP%%70%`#DPt=s0=(5`>ioZO~p;ph_H@N-j?Wc6LVV+z6KYUrT zj+NXjHN#%*Vfd0-8>|M1k4x-ve2Q-U<=!YLuJ36a`PxHyD!=kL?7dVrQl0vdXICE2 zsT@ybP0eReVCm@T3!JKV$;*F3m3&{|$R!XZ`a)RVQ{q*$oVe~9+Es-+X2j1TfQ!*(y zEh8h-5w^VS&&$jEAN2lw*V@ssWqf}1*Rp?pPY$R*)3%D4mh?C^A0YFxTV!!o(cV1?w#k5q(en3S(ng!fHV$p=$-O5B@$D z)Jw1VJp=*Zj3dn=D(Z2n}SRt4SlT-z$ocHtc-lQs? z1dnz*h3h>~0$6@|MMWf1@MQR8D9G9ao(%M8xgMaWfB5hVN;#chCP43NgwwBVI=U6M~=+TYN%~IXIgr8#?ER8ET&)92v{?YE_n(N<6xxzK%Nq^BdoWz&_% zxCBE5PF-byX!QhGM6KQX)9VeEq)6eh+1Z;nZopD_BR&1g)YLB+*7~C&TglOs<-T(7 z&6R0tDAhx?VWF~n8Al8eiqHM$>N+~MLoq;Aov`8}1-%*;-~I#)6bdWgLWWjw5JC?$ z{|E|4Lw)@ytRKL`AnDxu7fB?cR|a=f`T+WoSM#i%Btbw!wS{1j67icCVwGZxRb#=qu z217kV+CVD=3o*p=Ni;Uo(?2FUbXWLdW+X4{`q@wzgWMtr+&-AYOMiEl!sMuA1R0h1%qK!u*3oP<~gWf_SEZY`W#`Y@f` zt_la)_(ljnAPqsn|Mz$J>;GsEpm_t9aa((Pb#Ng#fp&IhJ6ld>9OX;{?OF8Mj}%cI_<=6l(+*H+PBu+~I!jN><7@L^R$;&A7|oGND$6g@xhvaXG+8 z&!7KgGhGEf!9w^a4Go9RUX|~Qi*)}y)D49U9cZ_1`W@)Iy!5RI$LtBK!yMiuOa@f%$xPwLT|!{ zXy=<{C*{GuWaO@N9<`R4{zx5#5!+rqTH=HTsK4SgcniNTJr{pzZvg2v0PN%Ld48!0kpyOiGI=i|yi;9|`>=DIrnnoW8lmZjs z_MJN+N`LcvTEKM%2xJ9rH1IFaXO4uiZz+Gk-9ZBgOhX{MHwfm`zj*o5*3OPqNMjj0 z2s>02qE{!39{m95zv0%-3N+H@+eXlszQ_o7l>kjO2wm%+xfurqzYu~>Wp|=@%8eTi z6+U!u%bi(S_C9lFk&#RMpXQWg%FTyS8q^KZ7>ZXnA z=a(MF$H!MxoP!DDM3vsy6*=RQnZalL#3NwUgaY=2goLwa&xWmI`-{<+E%{`Ekd~I_ zJq_ zz;mDhNb)cqdo|X6fvACI$k)#g9&rpvz(bk?Y7P==<>*KTI9y_Lu)eJZRP+V8SU6V9%2?;;werTnKsUNbH zGbv*9snNZr+=2 zeGm2|m>0H{F>;Ut=s5PjFLYW*L>}JvhT4drlKlPi!wJnVir;%Ptv}ap+)#m|4cO>* zL!kZu$`|r6+N7nQ^T!1R1tH+RgSReg3a1-WSaNdxI=#n`6|hbC6|oH?Pq96g`}Vb^ zt3qA4uQ{yQZTVpUR=Tx-fIuMQUWW6xqi^+878&0dF9 ziFrN4;UN3It*x-Ud>LID+F(fIoZxi;^8=C6Utimb9s_#<(YRc4r?N7tYx1po%np^|pyo`M;MzIR#7N)jC0nNbe(_0Dx|LdHefiB=EK!Gs9Ss%;2k0=O&{r7R+p8e%Wd?ip0fd@+L;-jLxeS9Flp>Z<^JM;eD<4jeH$GSWL&Q91-4Y4?II2P=6Sy`edPD}#y=`f%+V#uX;&RbDJLj$t7xvj0Lh=?;trG0meFR~jL z8m6VDX3>DIf*1s)rNLzfNRm|;1rVYj0ssL48v#jaX=%UtYcP<~T9$tNfa-XBM+#a5 z04rBto)`?GAf4ldpe9lNgIb4nq#$HqbO>b!oCcXiyKWu-rcK*Z-oSdHl(K{bf&Toz z#sh*NJDD{C2PpBz4atoSA3mG|#A{(;BZ1{pfCdDK1V9nmU_fD^PuWUl(Fb<`WxiNz zK&e5tn-gw1Mv*x;Zfqm~N7wdlpGDu2Ym##$mm#oV{A z3xV`BLd`WOhQ^!h$0JCfn^8 zVeniBZ^n(%(#0ooN~&HElf%hdWn~S_Fu-9eZEazZ01%2RvfGGz@$?C8VTtEE&;Dir z3@KGx7Xopl5wMW=i8-Hp(u!?dpSHQ0eZg?DH*_zO=WJ;L??vzEVxP+AMhaOP}ERVE} z{P`t!22y+@NG}hWycPtl(I}G|yn1MvmQ9NE0AQg6cjHX|`W1lO0P|q>d%%uO%nhK_ zQ65lfu#6o{egHFIb#&|h-Q5hlR5)ZdHjy`PYMiq)=cZH3`BfJuSp#eA#NV4p8DCkA z86d#xbt5Ix5NHEE__oQ*dqE*{OA!#3w%592f&$Pz#l}mxVX5n_`d%f+A_>k zAL-#2kquog|GcHK2a;Q!?LB!_LA6#q0yhVlAwsE}M5+F>L^U5=z1j&!R^LKY;57CX z0gmE>+ybTul~wz}V#CYt5Q~5cM*g+3@aSehKS9Dong0AHrT&u-IMVTniE>aB42O)2 zB4GjLo5q*mxS@1MeB>?@Z(hRH^6|a|#?Q{`v5By9T5kEQug>J<;sW#k1}icK97y-e z9b{u(t|2teeW&%~i2{elhXaV#2tJ<{47Unuop6u_$^bVED_mi2E|q9Nu}6mi{gg_m zg9Af8fT2<^Sqar1c-P*>hFM6%9}W(r#n2r~`w#stb4S*C>TU7mV|DdyNu?#6nxNuy z9dTP^|d4xfMY3LIxXcC0}R+L3Y%|rOfkVoH~OLa3n@w zGBZgSFB@Rm6UpSWGlSuu(RK)o=EOlK@(may>}+j^O)s{f%JjIGA?oh2i$-uWa=ZgD zL_-Aif$3cKZcNt5$e0|Cp^d2j@WfM?pjH!D^F0h$m7~f~g3q5nKRq!~b^eDbsD3|z zP**bOIG74SC&F^bknssK_5VE=U@JEhEUJB1G#NXTd?(sr0|HF^*`BG3kJIC7_ZTBW zpd?&jB1Hp%ctF9y(hwo&L9MNjysyMaP4j20|)?i>!SKAKWV#?Jm@3ehH8-?>i_Ew zU+5tIcw`-3ee!|zR8-^SSbslkFuWWb+oAuyE#BT?0UPLXPfyQNr{dI&_MRH7+Qh@t z;4BYjEfnoj5QY&x>h+Xqi_K(L}X_3Nab0So}q207+!{kB<^7xAL7KAQjt!!w1% zC2n?8Ku9P~os}S7G&Y)H$Q)-DXQ#Y4vJ6e`-o4GZ!MC5TvH$uu9F~#cc<$U!WMLqz zXsi(pVoDcP8^!TnVIe$FJJpErjKadgqM|vpk6!CIJaDSvBU#VHR41527RuD1M2$2gHs`B$?W~H;HuuarP{`G6lR)b1p zLqud(XD8evi||2;Cq}7rbZj@e$7?n_JB!(IBE+`%SjInxFCbqaVhglK%$_5hb$6?( zE`Ys-#e%DqsdjV@8WJiAntN&`lcK0#4rzqS*+O-L@`|FX^Cxsz5sEit1IMRlhgn4f z8sI8CTx1^BSBGa$yzc8m1Ns}(KR7v5HuyIu%>CsGMAi7Gm}2+bxv6%B$&bgz(z5Pw z(Z6P2NAC^Z56K*U67O{!pX)MSIG&kS!ww#TlDws zo5#+ShecjSF_dRCHjw|<`&|*o6wD(VNUI<#4;~c4Zy!gmUF7=>V0*^{7Ya7kO@g?=a0+Z&=&u_xJ_W;^grO}x zL&JTN*I>{{IDdZ&&4EEZfTs`QNU9L9}4g^y7mYbeuy)%Tf)3v zx3%Fx1M>?D++|GQ7hT=m1q2223krhfKbu&R#OMaI9cg7j8SBVwMK=a%a5x&V1ib)C z->Vcev2EL$Yig)7XXRll@i^VE+GW@8!3DxIQHKao%P}%40FKQv*nz4FX7eQs%3D4_ zGysQ-h4P|f=R^a2e%mhF*@&Vd5?`KR@Hjl@RsjV9Xq2yRrot;JSGG)~Y{>ff*CoL;n#L z9kMThTD%8pufULmA*bNrRd_3;X}qvrn=r)*4-;B=@8drUvBZenwFJga;h%W5_?j~Y zGpr%7=Odc{M-e0t8`YToflB-3d-EW;b0mPR+|jV~0&MVBtMjnefv>_dKtMminv#9K z>k-)E8{kh69vGZ~$_D&b9=sZaH}qF97|Zi+CEtLe*!l7$oyKG2O%}*71zJ&7U8{px$rUz8E|5t;16fJ&$k+5U^XXOA0z5o7>I;mw+w10by&9si(mkRR{(qXV1R#fuk#RH#T_N~%t99hm`{ z4rdtx)y3zh(4P3DZ0gvkDxssG1Lz7_pK$i{WFu63#v-Glnn2WO1^#+_eLEHsj~T%% z398^BIFkAJULPxdxI6J-9pU5FWz<1>g!CRNUM-ct%T< zw>=Hnf3kCny82Tb5BjQb*02U2wv& zQ2$&j@z<_i9r^U>&i(t9aKpmvTEON75ddVrE^dx0%Atgu*4-7b@T#Wf_M=DaWE)3E zK^`6@|8LGP7gCP{Z_%hv1y%0Mzp$175e-iTI`)5Ku#X-;=B(Dep!XCH?71+5=MErl z;7|fu4dtDmpQl!cJ#^Ri2;#l~Gb?xXAtwJ~A}25~5TqlF*LQ#w&oH4`154(LU-kI+ zAP7jsuwMY87JqQZ$Q|A-o`~YLiI+E`Av!KjNm23tJu&#L`^A6rFZZn6u(+7u zw0ZMR@Zw;eB3h2E4psR@0+Yhf;^=knDk2h>OZNfLucAVga7LN&Kh0SOIcFpo(ZO#}2WAd-G{8z9m2^BU08B$>krKEURICLRQPH}FhR=Ol$3J~q zoauuh2E)i5lMBG6U_+|P%gc2?|7b)42Ly}6xct}FH*cgxMMwJjzQ8;A@5w*;D;kVN zhS9WhT6fKEu@YXszV5>zx&Q$HN?XhGgflL^^kHTqFMt2kj4(95A+H1G7d9G@2osm$ zKT{`BaYP|EW8gp&EtR%5L22*kn8W*>SW;3_Fzps8gr&7>zu9?z{{w)ArS2LqoM_Ul zq&AdB-a&>J8^5C}(25j|{Mw$ZpJ|RK6Zv3dj>kY}AQQ%%$IWEUy31JNYItdw2|V7Y z`OTY^*&0wtm=*gqnR|z2pXbYmX|b)-22x_M~9|z@NR{o z^J$pZ?i#0%vpd>f8^NWBsSq$a{s$6TQb0WbgyV$ZfkeWZ0b`ih4p^7~b;5AS(()?i zXWiYUr-sk`*L8-m!$)o*$z6MKGYd;Bd=8~6vUrdSjt+Fwul*?}_JD74cD@b0>9#+3 ztD7p`StbMT{*5@{5ll8X^5-n5{39(5i2|*(uaBP?8XoR=eqBASg?0UU=-V5^$vV^G z2my2gilURH!tab@~N1Z8b{~i>u*IaLGIRO7zXAb2tdCH zsW#!cPpiF8zn0%^T?!sL5JF*iOv}y9@+w-iStnswlGQ3YI&{6OV?2XooSR^~lwBz~ z|5vwl;8vc}==^M+^<}drS`d?BD(P)=^EzX1e-+qlw&B~8f#Vp~^7M?%D`0uxyDtSpR`i)Gh5{@|{qRSl(? z&AkZ^mx3+kkqw^R@TFi+3^xa>Q#4X&du{E-w(-@K`8Fg7$I@PO@<@KvS;cMJVuCOj zef0ZS6U;}{hIF7_a*8&=$QOoR=x{(sNB|w47)@x#w}bpoG}|?KzjYa`RPgd}1bPX$ z`E)zA^tiUE z@C-o7=7`cm4=1dAD*a8_Hl7%azxiXp3B?RuTa=z?lh~0Xqeztq+aTEDgaa26#%eXU z=zKhV9RUvS73ShjY$voqg@gCPC1Dhq8LZBSu}sO=us*=p1NKpCtqmMplhV@;8(Qd55o)vgtam-xvV2L2kJF>o`<&4PH)OLBAR zqem0(rez$_rI+O7h{3alZUBTVWF}?hA1&701d=0c4Y6=vfwclFp0JRm^Nu=9@}&o^ z_ho$a(UW~7Y0f=U0H_FsqeV7=&5%WNMymw%Es+0_(KzB8o7>u|&rBzoORoHI2gQXb zquNLs8W8A8|A<4Z&z4SJKZFqyIpP|+9$3pcD672O+!%)hb-SWog`)v;GpK$jz@Y8{ z-M7^LeRUn5gW(eLLr25}*EMnTFdoIM`_bda57^mNg5*REO^EBF0oH-K68L>= zH^g;(5gq{c$7=rRXEZb$<()(DxF6Wr0g9pN0bngGE&_Go?Bm~f{?iTPyTBSTFSCAA zp96JNe&f}&7j$bb;=z6~FY)9gkT~SbN04y-je?XS98k%z9#~$yaJundU}SeZWMySF zvG+K-2Iz%+J6t03^Yh1cpHck*-RS(eb1?FaL#dr<-a{8d*#X;uCgeuwEU2BClJDIH z^(DEXjDDn=!<(A^n=0Q5Mzx>-2?I~6yA~*f=MTVOf|L(C2TYSTc6OZu+ITRZF>3hI zxymbeIvrptj96T}yoPG)uL@!Z$3b-g&!Usk5Q?uKz7p3JOS?t~L^U)@tP@lvl*xnD zK?5Lc@G*{#GVBaEE`WA;G78=aV_>*|zJs{0{_nM9s+e#Qo9sfod z!{^Wc2I@o6uBp-P^u7`b?+2z{Gw$3u8W2#K6Kwmoc=jP^a`?ct;w+Gc0WfKM0zyM7u-K0uh-=V!^ zTg>^u8M1TZWu%Hj;o2FWG1UT43~2}DuhaI5wDVr_){?R^HC0t4$OsBWK*cM>S0JYc z4%Z3?;Q#Lbj9o^JIs@zNIrqRv0y1Qvw-;8}ft+HI@Z-+T+QAY<;5xuOcY2%us7umo z^P7Grg9jAhw~;w4vJDdA!UEPrXMuai6aj$h((J%qHP$Hu8?k_Lbn#dikSwpTmXQqj zh&Hr{I`K=?LvLtki2vYM+iarQAH884R5+vrz;GkA^-~sjo=<&Uot|h1@Y-qz+vcg= zkG6Cq{((UdIUU;?FRX><8{!}>Dj={y`vsyrqE08t*c;&6dGPHIJ3B4WYJfe^2(kD< z9ZCmGz_GzoRv=?juQ9;C;@Ad8&z;5U0?F>ty;OVQ2$v_!4Kty?_bJ?lr2g(w$<)c^FPzn zw3dE`cnW~nrN^V}I4setHm}I7udm0IPP52ENC=5_yT!|=Pw%eILE&x~wO_}QuRT=Q zjL4O8NCPz0%-@)TV)4e#eWGn}dU>5l8azQ}{ic6u6l1&s@B_7GVJPg*}Z~cl& zPLx|{O%MErU%?H}O`a)OQ!HaHe-&5c7g8Jef4;T9sI61%D)Gmy#cd}o6|ElV7n`0j zJH+-mUnWa2LaBeO)B~u!P|{dex0xZ8|8bZ%F{#W*0w{e{U2Se@>1LwI8T^V)^7QG5 zv{S==J}?(s(Z!478N<>j(Cwxf8Jn6ueg0gGo79hKqIU;?vYCcn`_EEcGa`SEOh#l? zio2Kt$wmQMQ*t5sRaI0njAk22L=GT5U8cFk*Vk%uogj6fG&8>9gw4y($M-+R@-qY? z`rNj^k-D-4;5y^dP7y!_m(1`43K%BC&wTT)UDE{ECwg`J3-YO|2I>(__6EdDJS&*Y z7u8yQDJsyC#DYVkmQ-HYW1|;W`e$h=QJu_tuzyITpbt)Pjy}Ap?JZA6p%ql0xp?uR zl6nWpC79hzzn;#tH|G~qDtJjv9X zO5jKPb|)6VX1Dzsa7fcqHx{OA_Zgd0eaK8w5coLi{z-RZB#++u8rs^T`}HtQoiBNo zr9tq*1b{Y$#%m530DB85IlZat3-O|YmxXWwN0NPJ;UaZqxaD4idZC~7~+zMw}t z=NE5eZlb;^)hKf!Adej`jANFi`r0$B@Hv`bVL3*(^w1R55b6I$OB#Jv!%hgxSFT)0 z6*;XfJ@pd;SA;nn%9tHu%0kRlckW*7*fulWCiNqnKOIeay>6SPd%)+#kZ)J72DrFT zHivKyPW%|Leoqw{!)j`}YD%0wSvI-_ml#%Hz1BIH50h8vykG=}rQSK0OY1;iJX;hV zFVmwwT4Ar7MNpVAx1zH0epwkfPfuHM+n_uH$s_vlFCS{*+U<`|i_nlOor*UcwE}#q zNa}KdJ(sF0iwLo->^wF;I(phuU2VnGc5%wX*d2}xrJdMUbIc6#vNQS}VH{HpxI(Rk zgGNSzdu(UTlCwqvy>;tEZe$fWB9^g8$(=h~Uy|7t8y-HcFO~=WgfeekV z%;Z30^#}x84Xv$n%~fpNhU@Ex-1-YM3^UwtAI&zM?FDv$qg`^FMEB*a-%voKM`EW{ z9SX!GA|^Jpw7h9(7&Le=80U5GrRv@-jAdcQo|TuI?f7H>eES!{F~6QYeTpzx{A~RZub1yv!D{!YrCd4 zn<;I>tudfK)*EI?plKs%96blxka|G8J!`B!bELbHE9W*FhLvCaQ zZJfMrtxCY!SAX zxoLD+$oJ{bW1~k}CA$+UkNGF)%20LnPxKQ=pqX;BF$a|HRdXh+W$@&65`nMt#mkr7 ztJck#)92{J;W2Yw@U&+@agaFh`QQ|^AutV!Fy?fBfaKW^&`VF6l*@3>%#22$cNr~^Dr_!<<^stjqKrx{Xuv@SLx8gsu-*zYWjbq_R<_sp378P}A{**_$fJY; z9wK1CZy8szWcM85cbJmsTm|)ffwrDGmNFDeAa=TMN zF+P(F_qe-05Os!PegQ3rYoU)P?J)d5EO%Rb`v~oRdOI8(9XZ3YV36qcVP!^RWD@A} zV1Z!=WSNW1oeVtw;5>>QK-Bk{Z{K!N@Zeexdl`o{%;UC~xCh*6^yV+PVNxmXXxYEx z+Au*-^A_I`@MM(M4x29@)%UfcMY;OIqwLWmrZ3ERet@HA7%~{>A>qx1GcY!MxLC4&8$RydDW;Okc%a^yK zo5O3snfmU}z0wdRc5qp7)a|2THY-?aG2@Xv{FBPQxajD*ckgNfYri@fgkRU!vp~`R z;fTkI6Vi$&yX){Dq;1bm5I!znze+UAK@B3iMs0U z@zf0QbG7Yakee;#YY7be4p8J4N;7O^D%detPfGwKr0S>Pn!WYe1~PIk0wyM7{X1Af zPS?HS|3ainb|lt-!y!zFAF5cg@}f*FHCpvCFnJ7#R6dahIEjOpg+px1^_yDaF=4D* zVO5derpg%$o}rz(S6ZrbkX z3K!bjA7J7mkRl%9lb|zZ^5jGO@#@w5%vfS{)6Y1Te4~jImXm$q?mpjq&Lgxn`oix7 zf;a1)ieiM)Pr$J#Q*7=?FmaX>z~lzn>HkVj8&-hGQ7ka))0yfW_I{B1HXH!U`1&TB znv#iKQd5&e|23I9OmcuN4-Ny0DIc_LYEnsMxqtr;s6>b%Cb+}2Gbc)#-)D6HSKPgm zHKP;+?2RfsN?$+FeOC_w({o43=2+Pbb}Z|mB{0?4uVJIW0ZC%1Ah2l`SA~ZAESlwl z)`6~H_y|WCU6y7GC{a++QWC)M={YH5#P?c=_O|t&o_a}AXHlJPrX_YbX3Zu^spF*j zlO{=o9~dFLa_Y7gi9ks~pwHL^dhW*P;*+jZ_nI=FkHqRIN?JO!iScY zYL*_oGsU%Xi@9|V7j^0!-ZzfH*M!qHo6Nrdn`|2w@w!-%?w#>3o z8;@6yh9Vs*4E%P`07Z%b) zbJU0tB~?}NK3ayKX@6-;l;lHTo53Zq2ZoK_vB~cC-MdyhKHY}&G%?xlqj{432ctJ! zGm5i!4(!6X&hHN}Yn)Y+KYZi}arv{!08$wJi;{OuEHQs@oRDw`FmBkJg@a0Sa&ym~ zI&~TTfPoU=sIn773V)1#s+Ka@tHnM0XL6kLv$Oq~4YASy5~=wC$A&_~{u&yKU0hmP zTc;7eTIsAty%+5`!nLpe>$BV1+FT_z(puL^jEVIk;eM3-76y6{B*Mo#1KnwQxgG7B z`E}Qp+Sx_0SfMv$vuEokos1LxFAOVs)f66r=@CR;u&=4HL>XI$upCWh&Bef~MtGP$ zg6Lyc-jkl=GwYFsI4y)$>7i?E{D;NU+Qx=_!~QxtWpDpw9trO%TeBkyZk3mNcrDp( zc<>LemwD%S!-)u};1}C=tp9<#0Nvf`#}tuk8V9I}2O|4*(oK8|Lt%AR4+mwt>0A5IpobKVm#PfvNvV*>D(ZnSjz=C&i#K=aL(q@|6QI&h4W%P)WcUwsd0+hzv<;P5(V)fv9b&dF(z)PW~Ib7wgU zOq4H!#;?51_^&%Da#<+sNa&67s;XUE`$?ob{Qc1fR=kx!;tS=#D!P|1Q|39ur_b-j zV+`!}_qgrnX1`&LqTxiw)ISnd6;yRh8WlzAMfmvXQ>=HO!A?y5LKc8+8S#8Qp1<>9^7Q26yRCZi{uxWD zgP6`3jA-x)Q3J&(4f#1|&+bp78y_p?NTfb15M{S@o6X+IpLoW*Dd6c1B+AWWml&*P zM3vG#+SoYlZ+!v9(8;cO12r@%w&KD2J?#|88|3fT6w1$u==lcq7WVxb;8J4G#4`;j&c4`GHzo@n;@BM6DUm+J%lPbBeroD&t262mwhK2NxwEpXUTRVOVusLU zn;gLWQ3&Szd7P*n?Fur<>{jtzX*6kC`0JAoRMWt{9HE?WmOfX@^42NZVhTD*600scwXMzmG*>4oEHzcHrpHrev+1r4 zNOP%UIr;fP2xj1;M@G8juB{l6yi5P^4f2&>i-hY$mcmvB~ zaaFylhAx+0Qc(enqUVsp#xRaifvQIH#jd%48chWEMG-7>(s;us??_GMl zxVRe@0>|sD_V(Zg&E!Y&t|UQT+{2rm zXY~-_n@RGobEjh7X~WZDqS?oyZ(}08!t)7-dIOxt>DkMc5~Ru70sJ zjVDTc1rOrff8JPb2#@FYU8p@6IGNd|q%FVbIu zkFV7yCs}iJc5W{2rjyAZZ9OVBG$1audtuQpR*@B>PGSi*_ir<5Hc|Sz%c`qaQQD+o z)*Fj2Cs3F~jb}(EX00|g$)Kfz;T}4_ex()_li?OPsYi^M z^}u7xq$%%TkG)r7{$>@wc630~BbCUnooUdr(dVZZC{0F%-8_u5IWBGy3Se@tWNRGO z$?dKqM7r0Wb+dxMOe zg!G7{3l~i_*axGA>=%A}qWI zdLA2j9!Vm>7M#2{Z`u^Dax|F3MxYX$XLg4aq2_3K+Jayf;&M@u=xRKF{=k?!vIK}h zK4K24uaS!~AT7)pb$Y_FF>vrvy(UXAicI@?CDX{OD;Fy*i07Y+x~SXX{k=?!m^f+ywP7O(C!`eY2mZ@vJ#= z0+VG>8{z^?P_xXV)H^^YP#1(FbMf=c`*bdV=wy$q-LTQo1=;w@MP>WF8`hsCD7pA~cxi0kg^ zYBD%u>29NiF+wm5_5VUoOjr$#>*#3ku-r|i7q)jV7Km~fi?Wi!=J7n0*!fUwcXD-f ze}k2OF~B@E!>=jK<1T*p3zI(^;JW~tYinQW5uh}B^f^?Nk56nd(&{xzT`5(yoyQd$ na^=W)86XT~B+G0{?^di&yne68AVmdUTPWB`?QPH7ED!lVhI=t! literal 0 HcmV?d00001 diff --git a/doc/images/foo_bar.png b/doc/images/foo_bar.png new file mode 100644 index 0000000000000000000000000000000000000000..d01a9c078d61b5ce56e792652e83934415c9766d GIT binary patch literal 12878 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 literal 0 HcmV?d00001 diff --git a/doc/images/same_fringe.dia b/doc/images/same_fringe.dia new file mode 100644 index 0000000000000000000000000000000000000000..ad85cba4724ae5e1a7f4ebeb0beda4f919697db6 GIT binary patch literal 1541 zcmV+g2KxCQiwFP!000021MQqmZ{s!)hVS_mg7(^_`Au=XNzopP9t!l(?Kz+=Mpjpr z3`t4Wm;UyWls55)bkfrya(vyEx zmZ^zpGzo96f?(4?tZHSiwO3VLm+9=j)J8<+KFi{?JU;LMW)ueY|Z~} zZk;ZZ(yr4%dxu-(1ZrNV58pp+OiD5PN6+haw13qqk5w55AA>LYTODphAz_fl*WsUV zd%AW9Xsj6$?#EPN<$};=m)=$MYhuyFA(J!JS&B+1hkS>yU84-anCMo=E2*(!LBmjxG>KcqopL$xR6%ZgrAq%sA=E zlZllKjHZYH%%EEr36mEPEzLcf&cmi&O?pIKAfm_#5GAY}kjbtv8<$XxBMw-@ClWcg zMB)pGonp_Yv+=1@kRDGLh$lK2Ppl24$7K_1lR5(?4zLhSQg;zx(jH7_0#lb1J(b=w zmHtR`y+I||Qi%zGl&e6`!3u0yQ17|o0XZXK@rRd=N6SXGNm=SjWTzlqPBVsPt) z&{-Bq_jj8X(jd`O+-=XJt#)M_FiB{Aclg;u6>pm0w6vj zn5-K?sok!iM&(nxk?M9Uc*Ollk0;YXHF`2#Aeo{Qkm+e>-Mo(rr|+jdR7AwW>FKlw z9-9~V)1Ln)pSm^a5p{uxl7kV&fWQ%Pk+d8aQLQJtt4b`QIKWPbwpiv85akbi&V{Hx zO?pIKVhP{i!eShOh`KsoJm$`mb%RZU=PG7wf+qpN*!iXC5^_9$@N+Iebqdnc=>kuF z(BKMVdl*8c4QmL+bWA=$z(my6K_D@;I}KdAV`_>B8u{#}Z&N~5F*%7)^cjx6!sVRrph4yUfE<@m8P;zfQ!kqlT+*>4m7Y;{mMAhCsn~8|;vv#tf^j)^FmByYX}9aGz?sW7 zIj3g{#XAPs8HzK`+=IuYSNJ3b=6maxv r+TLMyZa6ZN54&nw;;U&-De~sC`(Gu-?#XO~^uuT8}WA6Ei literal 0 HcmV?d00001 diff --git a/doc/images/same_fringe.png b/doc/images/same_fringe.png new file mode 100644 index 0000000000000000000000000000000000000000..2346e1c07ff0dabdaa4a2dec8531c21ac7f9d17f GIT binary patch literal 5346 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 L3=nT`+J^og=|*xg literal 0 HcmV?d00001 diff --git a/doc/intro.qbk b/doc/intro.qbk new file mode 100644 index 0000000..0714180 --- /dev/null +++ b/doc/intro.qbk @@ -0,0 +1,106 @@ +[/ + Copyright Oliver Kowalke 2014. + Distributed under the Boost Software License, Version 1.0. + (See accompanying file LICENSE_1_0.txt or copy at + http://www.boost.org/LICENSE_1_0.txt +] + +[section:intro Introduction] + +[heading Definition] + +In computer science routines are defined as a sequence of operations. The +execution of routines forms a parent-child relationship and the child terminates +always before the parent. Coroutines (the term was introduced by Melvin +Conway [footnote Conway, Melvin E.. "Design of a Separable Transition-Diagram Compiler". +Commun. ACM, Volume 6 Issue 7, July 1963, Article No. 7]), +are a generalization of routines (Donald Knuth [footnote Knuth, Donald Ervin (1997). +"Fundamental Algorithms. The Art of Computer Programming 1", (3rd ed.)]. +The principal difference between coroutines and routines +is that a coroutine enables explicit suspend and resume of its progress via +additional operations by preserving execution state and thus provides an +[*enhanced control flow] (maintaining the execution context). + + +[heading How it works] + +Functions foo() and bar() are supposed to alternate their execution (leave and +enter function body). + +[$../../../../libs/coroutine2/doc/images/foo_bar.png [align center]] + +If coroutines were called exactly like routines, the stack would grow with +every call and would never be popped. A jump into the middle of a coroutine +would not be possible, because the return address would be on top of +stack entries. + +The solution is that each coroutine has its own stack and control-block +(__fcontext__ from __boost_context__). +Before the coroutine gets suspended, the non-volatile registers (including stack +and instruction/program pointer) of the currently active coroutine are stored in +the coroutine's control-block. +The registers of the newly activated coroutine must be restored from its +associated control-block before it is resumed. + +The context switch requires no system privileges and provides cooperative +multitasking convenient to C++. Coroutines provide quasi parallelism. +When a program is supposed to do several things at the same time, coroutines +help to do this much more simply and elegantly than with only a single flow of +control. +The advantages can be seen particularly clearly with the use of a recursive +function, such as traversal of binary trees (see example 'same fringe'). + + +[heading characteristics] +Characteristics [footnote Moura, Ana Lucia De and Ierusalimschy, Roberto. +"Revisiting coroutines". ACM Trans. Program. Lang. Syst., Volume 31 Issue 2, +February 2014, Article No. 6] of a coroutine are: + +* values of local data persist between successive calls (context switches) +* execution is suspended as control leaves coroutine and is resumed at certain time later +* symmetric or asymmetric control-transfer mechanism; see below +* first-class object (can be passed as argument, returned by procedures, + stored in a data structure to be used later or freely manipulated by + the developer) +* stackful or stackless + +Coroutines are useful in simulation, artificial intelligence, concurrent +programming, text processing and data manipulation, supporting +the implementation of components such as cooperative tasks (fibers), iterators, +generators, infinite lists, pipes etc. + + +[heading execution-transfer mechanism] +Two categories of coroutines exist: symmetric and asymmetric coroutines. + +An asymmetric coroutine knows its invoker, using a special operation to +implicitly yield control specifically to its invoker. By contrast, all symmetric +coroutines are equivalent; one symmetric coroutine may pass control to any +other symmetric coroutine. Because of this, a symmetric coroutine ['must] +specify the coroutine to which it intends to yield control. + +[$../../../../libs/coroutine2/doc/images/foo_bar_seq.png [align center]] + +Both concepts are equivalent and a fully-general coroutine library can provide +either symmetric or asymmetric coroutines. + + +[heading stackfulness] +In contrast to a stackless coroutine a stackful coroutine can be suspended +from within a nested stackframe. Execution resumes at exactly the same point +in the code where it was suspended before. +With a stackless coroutine, only the top-level routine may be suspended. Any +routine called by that top-level routine may not itself suspend. This prohibits +providing suspend/resume operations in routines within a general-purpose library. + +[heading first-class continuation] +A first-class continuation can be passed as an argument, returned by a +function and stored in a data structure to be used later. +In some implementations (for instance C# ['yield]) the continuation can +not be directly accessed or directly manipulated. + +Without stackfulness and first-class semantics, some useful execution control +flows cannot be supported (for instance cooperative multitasking or +checkpointing). + +[endsect] diff --git a/doc/motivation.qbk b/doc/motivation.qbk new file mode 100644 index 0000000..3268c88 --- /dev/null +++ b/doc/motivation.qbk @@ -0,0 +1,567 @@ +[/ + Copyright Oliver Kowalke 2014. + Distributed under the Boost Software License, Version 1.0. + (See accompanying file LICENSE_1_0.txt or copy at + http://www.boost.org/LICENSE_1_0.txt +] + +[section:motivation Motivation] + +In order to support a broad range of execution control behaviour the coroutine +types of __acoro__ can be used to ['escape-and-reenter] loops, to +['escape-and-reenter] recursive computations and for ['cooperative] multitasking +helping to solve problems in a much simpler and more elegant way than with only +a single flow of control. + + +[heading event-driven model] + +The event-driven model is a programming paradigm where the flow of a program is +determined by events. The events are generated by multiple independent sources +and an event-dispatcher, waiting on all external sources, triggers callback +functions (event-handlers) whenever one of those events is detected (event-loop). +The application is divided into event selection (detection) and event handling. + +[$../../../../libs/coroutine2/doc/images/event_model.png [align center]] + +The resulting applications are highly scalable, flexible, have high +responsiveness and the components are loosely coupled. This makes the event-driven +model suitable for user interface applications, rule-based productions systems +or applications dealing with asynchronous I/O (for instance network servers). + + +[heading event-based asynchronous paradigm] + +A classic synchronous console program issues an I/O request (e.g. for user +input or filesystem data) and blocks until the request is complete. + +In contrast, an asynchronous I/O function initiates the physical operation but +immediately returns to its caller, even though the operation is not yet +complete. A program written to leverage this functionality does not block: it +can proceed with other work (including other I/O requests in parallel) while +the original operation is still pending. When the operation completes, the +program is notified. Because asynchronous applications spend less overall time +waiting for operations, they can outperform synchronous programs. + +Events are one of the paradigms for asynchronous execution, but +not all asynchronous systems use events. +Although asynchronous programming can be done using threads, they come with +their own costs: + +* hard to program (traps for the unwary) +* memory requirements are high +* large overhead with creation and maintenance of thread state +* expensive context switching between threads + +The event-based asynchronous model avoids those issues: + +* simpler because of the single stream of instructions +* much less expensive context switches + +The downside of this paradigm consists in a sub-optimal program +structure. An event-driven program is required to split its code into +multiple small callback functions, i.e. the code is organized in a sequence of +small steps that execute intermittently. An algorithm that would usually be expressed +as a hierarchy of functions and loops must be transformed into callbacks. The +complete state has to be stored into a data structure while the control flow +returns to the event-loop. +As a consequence, event-driven applications are often tedious and confusing to +write. Each callback introduces a new scope, error callback etc. The +sequential nature of the algorithm is split into multiple callstacks, +making the application hard to debug. Exception handlers are restricted to +local handlers: it is impossible to wrap a sequence of events into a single +try-catch block. +The use of local variables, while/for loops, recursions etc. together with the +event-loop is not possible. The code becomes less expressive. + +In the past, code using asio's ['asynchronous operations] was convoluted by +callback functions. + + class session + { + public: + session(boost::asio::io_service& io_service) : + socket_(io_service) // construct a TCP-socket from io_service + {} + + tcp::socket& socket(){ + return socket_; + } + + void start(){ + // initiate asynchronous read; handle_read() is callback-function + socket_.async_read_some(boost::asio::buffer(data_,max_length), + boost::bind(&session::handle_read,this, + boost::asio::placeholders::error, + boost::asio::placeholders::bytes_transferred)); + } + + private: + void handle_read(const boost::system::error_code& error, + size_t bytes_transferred){ + if (!error) + // initiate asynchronous write; handle_write() is callback-function + boost::asio::async_write(socket_, + boost::asio::buffer(data_,bytes_transferred), + boost::bind(&session::handle_write,this, + boost::asio::placeholders::error)); + else + delete this; + } + + void handle_write(const boost::system::error_code& error){ + if (!error) + // initiate asynchronous read; handle_read() is callback-function + socket_.async_read_some(boost::asio::buffer(data_,max_length), + boost::bind(&session::handle_read,this, + boost::asio::placeholders::error, + boost::asio::placeholders::bytes_transferred)); + else + delete this; + } + + boost::asio::ip::tcp::socket socket_; + enum { max_length=1024 }; + char data_[max_length]; + }; + +In this example, a simple echo server, the logic is split into three member +functions - local state (such as data buffer) is moved to member variables. + +__boost_asio__ provides with its new ['asynchronous result] feature a new +framework combining event-driven model and coroutines, hiding the complexity +of event-driven programming and permitting the style of classic sequential code. +The application is not required to pass callback functions to asynchronous +operations and local state is kept as local variables. Therefore the code +is much easier to read and understand. +[footnote Christopher Kohlhoff, +[@ http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3964.pdf +N3964 - Library Foundations for Asynchronous Operations, Revision 1]]. + + void session(boost::asio::io_service& io_service){ + // construct TCP-socket from io_service + boost::asio::ip::tcp::socket socket(io_service); + + try{ + for(;;){ + // local data-buffer + char data[max_length]; + + boost::system::error_code ec; + + // read asynchronous data from socket + // execution context will be suspended until + // some bytes are read from socket + std::size_t length=socket.async_read_some( + boost::asio::buffer(data), + boost::asio::yield[ec]); + if (ec==boost::asio::error::eof) + break; //connection closed cleanly by peer + else if(ec) + throw boost::system::system_error(ec); //some other error + + // write some bytes asynchronously + boost::asio::async_write( + socket, + boost::asio::buffer(data,length), + boost::asio::yield[ec]); + if (ec==boost::asio::error::eof) + break; //connection closed cleanly by peer + else if(ec) + throw boost::system::system_error(ec); //some other error + } + } catch(std::exception const& e){ + std::cerr<<"Exception: "< cb; + + char pull(){ + return std::char_traits::to_char_type(is.get()); + } + + void scan(){ + do{ + next=pull(); + } + while(isspace(next)); + } + + public: + Parser(std::istream& is_,std::function cb_) : + next(), is(is_), cb(cb_) + {} + + void run() { + scan(); + E(); + } + + private: + void E(){ + T(); + while (next=='+'||next=='-'){ + cb(next); + scan(); + T(); + } + } + + void T(){ + S(); + while (next=='*'||next=='/'){ + cb(next); + scan(); + S(); + } + } + + void S(){ + if (std::isdigit(next)){ + cb(next); + scan(); + } + else if(next=='('){ + cb(next); + scan(); + E(); + if (next==')'){ + cb(next); + scan(); + }else{ + throw parser_error(); + } + } + else{ + throw parser_error(); + } + } + }; + + typedef boost::coroutines2::asymmetric_coroutine< char > coro_t; + + int main() { + std::istringstream is("1+1"); + // invert control flow + coro_t::pull_type seq( + boost::coroutines2::fixedsize_stack(), + [&is](coro_t::push_type & yield) { + // create parser with callback function + Parser p( is, + [&yield](char ch){ + // resume user-code + yield(ch); + }); + // start recursive parsing + p.run(); + }); + + // user-code pulls parsed data from parser + // invert control flow + for(char c:seq){ + printf("Parsed: %c\n",c); + } + } + +This problem does not map at all well to communicating between independent +threads. It makes no sense for either side to proceed independently of the +other. You want them to pass control back and forth. + +There's yet another advantage to using coroutines. This recursive descent parser +throws an exception when parsing fails. With a coroutine implementation, you +need only wrap the calling code in try/catch. + +With communicating threads, you would have to arrange to catch the exception +and pass along the exception pointer on the same queue you're using to deliver +the other events. You would then have to rethrow the exception to unwind the +recursive document processing. + +The coroutine solution maps very naturally to the problem space. + + +[heading 'same fringe' problem] + +The advantages of suspending at an arbitrary call depth can be seen +particularly clearly with the use of a recursive function, such as traversal +of trees. +If traversing two different trees in the same deterministic order produces the +same list of leaf nodes, then both trees have the same fringe. + +[$../../../../libs/coroutine2/doc/images/same_fringe.png [align center]] + +Both trees in the picture have the same fringe even though the structure of the +trees is different. + +The same fringe problem could be solved using coroutines by iterating over the +leaf nodes and comparing this sequence via ['std::equal()]. The range of data +values is generated by function ['traverse()] which recursively traverses the +tree and passes each node's data value to its __push_coro__. +__push_coro__ suspends the recursive computation and transfers the data value to +the main execution context. +__pull_coro_it__, created from __pull_coro__, steps over those data values and +delivers them to ['std::equal()] for comparison. Each increment of +__pull_coro_it__ resumes ['traverse()]. Upon return from +['iterator::operator++()], either a new data value is available, or tree +traversal is finished (iterator is invalidated). + +In effect, the coroutine iterator presents a flattened view of the recursive +data structure. + + struct node{ + typedef std::shared_ptr ptr_t; + + // Each tree node has an optional left subtree, + // an optional right subtree and a value of its own. + // The value is considered to be between the left + // subtree and the right. + ptr_t left,right; + std::string value; + + // construct leaf + node(const std::string& v): + left(),right(),value(v) + {} + // construct nonleaf + node(ptr_t l,const std::string& v,ptr_t r): + left(l),right(r),value(v) + {} + + static ptr_t create(const std::string& v){ + return ptr_t(new node(v)); + } + + static ptr_t create(ptr_t l,const std::string& v,ptr_t r){ + return ptr_t(new node(l,v,r)); + } + }; + + node::ptr_t create_left_tree_from(const std::string& root){ + /* -------- + root + / \ + b e + / \ + a c + -------- */ + return node::create( + node::create( + node::create("a"), + "b", + node::create("c")), + root, + node::create("e")); + } + + node::ptr_t create_right_tree_from(const std::string& root){ + /* -------- + root + / \ + a d + / \ + c e + -------- */ + return node::create( + node::create("a"), + root, + node::create( + node::create("c"), + "d", + node::create("e"))); + } + + typedef boost::coroutines2::asymmetric_coroutine coro_t; + + // recursively walk the tree, delivering values in order + void traverse(node::ptr_t n, + coro_t::push_type& out){ + if(n->left) traverse(n->left,out); + out(n->value); + if(n->right) traverse(n->right,out); + } + + // evaluation + { + node::ptr_t left_d(create_left_tree_from("d")); + coro_t::pull_type left_d_reader([&](coro_t::push_type & out){ + traverse(left_d,out); + }); + + node::ptr_t right_b(create_right_tree_from("b")); + coro_t::pull_type right_b_reader([&](coro_t::push_type & out){ + traverse(right_b,out); + }); + + std::cout << "left tree from d == right tree from b? " + << std::boolalpha + << std::equal(begin(left_d_reader), + end(left_d_reader), + begin(right_b_reader)) + << std::endl; + } + { + node::ptr_t left_d(create_left_tree_from("d")); + coro_t::pull_type left_d_reader([&](coro_t::push_type & out){ + traverse(left_d,out); + }); + + node::ptr_t right_x(create_right_tree_from("x")); + coro_t::pull_type right_x_reader([&](coro_t::push_type & out){ + traverse(right_x,out); + }); + + std::cout << "left tree from d == right tree from x? " + << std::boolalpha + << std::equal(begin(left_d_reader), + end(left_d_reader), + begin(right_x_reader)) + << std::endl; + } + std::cout << "Done" << std::endl; + + output: + left tree from d == right tree from b? true + left tree from d == right tree from x? false + Done + + +[heading chaining coroutines] + +This code shows how coroutines could be chained. + + typedef boost::coroutines2::asymmetric_coroutine coro_t; + + // deliver each line of input stream to sink as a separate string + void readlines(coro_t::push_type& sink,std::istream& in){ + std::string line; + while(std::getline(in,line)) + sink(line); + } + + void tokenize(coro_t::push_type& sink, coro_t::pull_type& source){ + // This tokenizer doesn't happen to be stateful: you could reasonably + // implement it with a single call to push each new token downstream. But + // I've worked with stateful tokenizers, in which the meaning of input + // characters depends in part on their position within the input line. + for(std::string line:source){ + std::string::size_type pos=0; + while(pos + +which includes all the other headers in turn. + +All functions and classes are contained in the namespace __coro_ns__. + +[note __boost_coroutine__ is C++11-only!] + +[endsect] diff --git a/doc/performance.qbk b/doc/performance.qbk new file mode 100644 index 0000000..ccd4411 --- /dev/null +++ b/doc/performance.qbk @@ -0,0 +1,40 @@ +[/ + Copyright Oliver Kowalke 2014. + Distributed under the Boost Software License, Version 1.0. + (See accompanying file LICENSE_1_0.txt or copy at + http://www.boost.org/LICENSE_1_0.txt +] + +[section:performance Performance] + +Performance of __boost_coroutine__ was measured on the platforms shown in the +following table. Performance measurements were taken using `rdtsc` and +`boost::chrono::high_resolution_clock`, with overhead corrections, on x86 +platforms. In each case, cache warm-up was accounted for, and the one +running thread was pinned to a single CPU. + +[table Performance of asymmetric coroutines + [ + [Platform] + [switch] + [construction (protected stack-allocator)] + [construction (preallocated stack-allocator)] + [construction (standard stack-allocator)] + ] + [ + [i386 (AMD Athlon 64 DualCore 4400+, Linux 32bit)] + [49 ns / 50 cycles] + [51 \u00b5s / 51407 cycles] + [14 \u00b5s / 15231 cycles] + [14 \u00b5s / 15216 cycles] + ] + [ + [x86_64 (Intel Core2 Q6700, Linux 64bit)] + [12 ns / 39 cycles] + [16 \u00b5s / 41802 cycles] + [6 \u00b5s / 10350 cycles] + [6 \u00b5s / 18817 cycles] + ] +] + +[endsect] diff --git a/doc/stack.qbk b/doc/stack.qbk new file mode 100644 index 0000000..4910d2b --- /dev/null +++ b/doc/stack.qbk @@ -0,0 +1,289 @@ +[/ + Copyright Oliver Kowalke 2014. + Distributed under the Boost Software License, Version 1.0. + (See accompanying file LICENSE_1_0.txt or copy at + http://www.boost.org/LICENSE_1_0.txt +] + +[section:stack Stack allocation] + +The memory used by the stack is allocated/deallocated via a __stack_allocator__ +which is required to model a __stack_allocator_concept__. + + +[heading __stack_allocator_concept__] +A __stack_allocator__ must satisfy the __stack_allocator_concept__ requirements +shown in the following table, in which `a` is an object of a +__stack_allocator__ type, `sctx` is a `stack_context`, and `size` is a `std::size_t`: + +[table + [[expression][return type][notes]] + [ + [`a(size)`] + [] + [creates a stack allocator] + ] + [ + [`a.allocate()`] + [`stack_context`] + [creates a stack] + ] + [ + [`a.deallocate( sctx)`] + [`void`] + [deallocates the stack created by `a.allocate()`] + ] +] + +[important The implementation of `allocate()` might include logic to protect +against exceeding the context's available stack size rather than leaving it as +undefined behaviour.] + +[important Calling `deallocate()` with a `stack_context` not set by `allocate()` +results in undefined behaviour.] + +[note The stack is not required to be aligned; alignment takes place inside +__econtext__.] + +[note Depending on the architecture `allocate()` stores an address from the +top of the stack (growing downwards) or the bottom of the stack (growing +upwards).] + + +[section:protected_fixedsize Class ['protected_fixedsize]] + +__boost_coroutine__ provides the class __protected_fixedsize__ which models +the __stack_allocator_concept__. +It appends a guard page at the end of each stack to protect against exceeding +the stack. If the guard page is accessed (read or write operation) a +segmentation fault/access violation is generated by the operating system. + +[important Using __protected_fixedsize__ is expensive. That is, launching a +new coroutine with a new stack is expensive; the allocated stack is just as +efficient to use as any other stack.] + +[note The appended `guard page` is [*not] mapped to physical memory, only +virtual addresses are used.] + + #include + + template< typename traitsT > + struct basic_protected_fixedsize + { + typedef traitT traits_type; + + basic_protected_fixesize(std::size_t size = traits_type::default_size()); + + stack_context allocate(); + + void deallocate( stack_context &); + } + + typedef basic_protected_fixedsize< stack_traits > protected_fixedsize + +[heading `stack_context allocate()`] +[variablelist +[[Preconditions:] [`traits_type::minimum:size() <= size` and +`! traits_type::is_unbounded() && ( traits_type::maximum:size() >= size)`.]] +[[Effects:] [Allocates memory of at least `size` Bytes and stores a pointer +to the stack and its actual size in `sctx`. Depending +on the architecture (the stack grows downwards/upwards) the stored address is +the highest/lowest address of the stack.]] +] + +[heading `void deallocate( stack_context & sctx)`] +[variablelist +[[Preconditions:] [`sctx.sp` is valid, `traits_type::minimum:size() <= sctx.size` and +`! traits_type::is_unbounded() && ( traits_type::maximum:size() >= sctx.size)`.]] +[[Effects:] [Deallocates the stack space.]] +] + +[endsect] + + +[section:fixedsize Class ['fixedsize_stack]] + +__boost_coroutine__ provides the class __fixedsize__ which models +the __stack_allocator_concept__. +In contrast to __protected_fixedsize__ it does not append a guard page at the +end of each stack. The memory is simply managed by `std::malloc()` and +`std::free()`. + + #include + + template< typename traitsT > + struct basic_fixedsize_stack + { + typedef traitT traits_type; + + basic_fixesize_stack(std::size_t size = traits_type::default_size()); + + stack_context allocate(); + + void deallocate( stack_context &); + } + + typedef basic_fixedsize_stack< stack_traits > fixedsize_stack; + +[heading `stack_context allocate()`] +[variablelist +[[Preconditions:] [`traits_type::minimum:size() <= size` and +`! traits_type::is_unbounded() && ( traits_type::maximum:size() >= size)`.]] +[[Effects:] [Allocates memory of at least `size` Bytes and stores a pointer to +the stack and its actual size in `sctx`. Depending on the architecture (the +stack grows downwards/upwards) the stored address is the highest/lowest +address of the stack.]] +] + +[heading `void deallocate( stack_context & sctx)`] +[variablelist +[[Preconditions:] [`sctx.sp` is valid, `traits_type::minimum:size() <= sctx.size` and +`! traits_type::is_unbounded() && ( traits_type::maximum:size() >= sctx.size)`.]] +[[Effects:] [Deallocates the stack space.]] +] + +[endsect] + + +[section:segmented Class ['segmented_stack]] + +__boost_coroutine__ supports usage of a __segmented__, e. g. the size of +the stack grows on demand. The coroutine is created with a minimal stack size +and will be increased as required. +Class __segmented__ models the __stack_allocator_concept__. +In contrast to __protected_fixedsize__ and __fixedsize__ it creates a +stack which grows on demand. + +[note Segmented stacks are currently only supported by [*gcc] from version +[*4.7] [*clang] from version [*3.4] onwards. In order to use a +__segmented_stack__ __boost_coroutine__ must be built with +[*toolset=gcc segmented-stacks=on] at b2/bjam command-line. Applications +must be compiled with compiler-flags +[*-fsplit-stack -DBOOST_USE_SEGMENTED_STACKS].] + + #include + + template< typename traitsT > + struct basic_segmented_stack + { + typedef traitT traits_type; + + basic_segmented_stack(std::size_t size = traits_type::default_size()); + + stack_context allocate(); + + void deallocate( stack_context &); + } + + typedef basic_segmented_stack< stack_traits > segmented_stack; + +[heading `stack_context allocate()`] +[variablelist +[[Preconditions:] [`traits_type::minimum:size() <= size` and +`! traits_type::is_unbounded() && ( traits_type::maximum:size() >= size)`.]] +[[Effects:] [Allocates memory of at least `size` Bytes and stores a pointer to +the stack and its actual size in `sctx`. Depending on the architecture (the +stack grows downwards/upwards) the stored address is the highest/lowest +address of the stack.]] +] + +[heading `void deallocate( stack_context & sctx)`] +[variablelist +[[Preconditions:] [`sctx.sp` is valid, `traits_type::minimum:size() <= sctx.size` and +`! traits_type::is_unbounded() && ( traits_type::maximum:size() >= sctx.size)`.]] +[[Effects:] [Deallocates the stack space.]] +] + +[endsect] + + +[section:stack_traits Class ['stack_traits]] + +['stack_traits] models a __stack_traits__ providing a way to access certain +properites defined by the enironment. Stack allocators use __stack_traits__ to +allocate stacks. + + struct stack_traits + { + static bool is_unbounded() noexcept; + + static std::size_t page_size() noexcept; + + static std::size_t default_size() noexcept; + + static std::size_t minimum_size() noexcept; + + static std::size_t maximum_size() noexcept; + } + + +[heading `static bool is_unbounded()`] +[variablelist +[[Returns:] [Returns `true` if the environment defines no limit for the size of +a stack.]] +[[Throws:] [Nothing.]] +] + +[heading `static std::size_t page_size()`] +[variablelist +[[Returns:] [Returns the page size in bytes.]] +[[Throws:] [Nothing.]] +] + +[heading `static std::size_t default_size()`] +[variablelist +[[Returns:] [Returns a default stack size, which may be platform specific. +If the stack is unbounded then the present implementation returns the maximum of +`64 kB` and `minimum_size()`.]] +[[Throws:] [Nothing.]] +] + +[heading `static std::size_t minimum_size()`] +[variablelist +[[Returns:] [Returns the minimum size in bytes of stack defined by the +environment (Win32 4kB/Win64 8kB, defined by rlimit on POSIX).]] +[[Throws:] [Nothing.]] +] + +[heading `static std::size_t maximum_size()`] +[variablelist +[[Preconditions:] [`is_unbounded()` returns `false`.]] +[[Returns:] [Returns the maximum size in bytes of stack defined by the +environment.]] +[[Throws:] [Nothing.]] +] + + +[endsect] + + +[section:stack_context Class ['stack_context]] + +__boost_coroutine__ provides the class __stack_context__ which will contain +the stack pointer and the size of the stack. +In case of a __segmented__, __stack_context__ contains some extra control +structures. + + struct stack_context + { + void * sp; + std::size_t size; + + // might contain additional control structures + // for segmented stacks + } + +[heading `void * sp`] +[variablelist +[[Value:] [Pointer to the beginning of the stack.]] +] + +[heading `std::size_t size`] +[variablelist +[[Value:] [Actual size of the stack.]] +] + +[endsect] + + +[endsect] diff --git a/example/Jamfile.v2 b/example/Jamfile.v2 new file mode 100644 index 0000000..5c5599d --- /dev/null +++ b/example/Jamfile.v2 @@ -0,0 +1,49 @@ + +# Copyright Oliver Kowalke 2014. +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +# For more information, see http://www.boost.org/ + +import common ; +import feature ; +import indirect ; +import modules ; +import os ; +import toolset ; + +project boost/coroutine2/example + : requirements + /boost/context//boost_context + gcc,on:-fsplit-stack + gcc,on:-DBOOST_USE_SEGMENTED_STACKS + clang,on:-fsplit-stack + clang,on:-DBOOST_USE_SEGMENTED_STACKS + static + multi + ; + +exe simple + : simple.cpp + ; + +exe fibonacci + : fibonacci.cpp + ; + +exe same_fringe + : same_fringe.cpp + ; + +exe layout + : layout.cpp + ; + +exe parser + : parser.cpp + ; + +exe segmented + : segmented.cpp + ; diff --git a/example/fibonacci.cpp b/example/fibonacci.cpp new file mode 100644 index 0000000..2cca0c1 --- /dev/null +++ b/example/fibonacci.cpp @@ -0,0 +1,34 @@ + +// Copyright Oliver Kowalke 2014. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#include +#include + +#include + +int main() +{ + boost::coroutines2::coroutine< int >::pull_type source( + [&]( boost::coroutines2::coroutine< int >::push_type & sink) { + int first = 1, second = 1; + sink( first); + sink( second); + for ( int i = 0; i < 8; ++i) + { + int third = first + second; + first = second; + second = third; + sink( third); + } + }); + + for ( auto i : source) + std::cout << i << " "; + + std::cout << "\nDone" << std::endl; + + return EXIT_SUCCESS; +} diff --git a/example/layout.cpp b/example/layout.cpp new file mode 100644 index 0000000..3033271 --- /dev/null +++ b/example/layout.cpp @@ -0,0 +1,53 @@ + +// Copyright Nat Goodspeed 2013. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include +#include +#include + +#include + +struct FinalEOL{ + ~FinalEOL(){ + std::cout << std::endl; + } +}; + +int main(int argc,char* argv[]){ + using std::begin; + using std::end; + + std::vector words{ + "peas", "porridge", "hot", "peas", + "porridge", "cold", "peas", "porridge", + "in", "the", "pot", "nine", + "days", "old" }; + + int num=5,width=15; + boost::coroutines2::coroutine::push_type writer( + [&](boost::coroutines2::coroutine::pull_type& in){ + // finish the last line when we leave by whatever means + FinalEOL eol; + // pull values from upstream, lay them out 'num' to a line + for (;;){ + for(int i=0;i +#include +#include +#include +#include + +#include + +/* + * grammar: + * P ---> E '\0' + * E ---> T {('+'|'-') T} + * T ---> S {('*'|'/') S} + * S ---> digit | '(' E ')' + */ +class Parser{ + char next; + std::istream& is; + std::function cb; + + char pull(){ + return std::char_traits::to_char_type(is.get()); + } + + void scan(){ + do{ + next=pull(); + } + while(isspace(next)); + } + +public: + Parser(std::istream& is_,std::function cb_) : + next(), is(is_), cb(cb_) + {} + + void run() { + scan(); + E(); + } + +private: + void E(){ + T(); + while (next=='+'||next=='-'){ + cb(next); + scan(); + T(); + } + } + + void T(){ + S(); + while (next=='*'||next=='/'){ + cb(next); + scan(); + S(); + } + } + + void S(){ + if (std::isdigit(next)){ + cb(next); + scan(); + } + else if(next=='('){ + cb(next); + scan(); + E(); + if (next==')'){ + cb(next); + scan(); + }else{ + exit(2); + } + } + else{ + exit(3); + } + } +}; + +typedef boost::coroutines2::asymmetric_coroutine< char > coro_t; + +int main() { + std::istringstream is("1+1"); + // invert control flow + coro_t::pull_type seq( + boost::coroutines2::fixedsize_stack(), + [&is]( coro_t::push_type & yield) { + Parser p( is, + [&yield](char ch){ + yield(ch); + }); + p.run(); + }); + + // user-code pulls parsed data from parser + for(char c:seq){ + printf("Parsed: %c\n",c); + } +} + diff --git a/example/same_fringe.cpp b/example/same_fringe.cpp new file mode 100644 index 0000000..78956e1 --- /dev/null +++ b/example/same_fringe.cpp @@ -0,0 +1,176 @@ + +// Copyright Nat Goodspeed 2013. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSEstd::placeholders::_1_0.txt or copy at +// http://www.boost.org/LICENSEstd::placeholders::_1_0.txt) + +#include +#include +#include +#include +#include +#include + +#include + +struct node +{ + typedef std::shared_ptr< node > ptr_t; + + // Each tree node has an optional left subtree, an optional right subtree + // and a value of its own. The value is considered to be between the left + // subtree and the right. + ptr_t left, right; + std::string value; + + // construct leaf + node(const std::string& v): + left(),right(),value(v) + {} + // construct nonleaf + node(ptr_t l, const std::string& v, ptr_t r): + left(l),right(r),value(v) + {} + + static ptr_t create(const std::string& v) + { + return ptr_t(new node(v)); + } + + static ptr_t create(ptr_t l, const std::string& v, ptr_t r) + { + return ptr_t(new node(l, v, r)); + } +}; + +node::ptr_t create_left_tree_from(const std::string& root) +{ + /* -------- + root + / \ + b e + / \ + a c + -------- */ + + return node::create( + node::create( + node::create("a"), + "b", + node::create("c")), + root, + node::create("e")); +} + +node::ptr_t create_right_tree_from(const std::string& root) +{ + /* -------- + root + / \ + a d + / \ + c e + -------- */ + + return node::create( + node::create("a"), + root, + node::create( + node::create("c"), + "d", + node::create("e"))); +} + +// recursively walk the tree, delivering values in order +void traverse(node::ptr_t n, boost::coroutines2::coroutine::push_type& out) +{ + if (n->left) + traverse(n->left,out); + out(n->value); + if (n->right) + traverse(n->right,out); +} + +int main() +{ + { + node::ptr_t left_d(create_left_tree_from("d")); + boost::coroutines2::coroutine::pull_type left_d_reader( + [&]( boost::coroutines2::coroutine::push_type & out) { + traverse(left_d,out); + }); + std::cout << "left tree from d:\n"; + std::copy(begin(left_d_reader), + end(left_d_reader), + std::ostream_iterator(std::cout, " ")); + std::cout << std::endl; + + node::ptr_t right_b(create_right_tree_from("b")); + boost::coroutines2::coroutine::pull_type right_b_reader( + [&]( boost::coroutines2::coroutine::push_type & out) { + traverse(right_b,out); + }); + std::cout << "right tree from b:\n"; + std::copy(begin(right_b_reader), + end(right_b_reader), + std::ostream_iterator(std::cout, " ")); + std::cout << std::endl; + + node::ptr_t right_x(create_right_tree_from("x")); + boost::coroutines2::coroutine::pull_type right_x_reader( + [&]( boost::coroutines2::coroutine::push_type & out) { + traverse(right_x,out); + }); + std::cout << "right tree from x:\n"; + std::copy(begin(right_x_reader), + end(right_x_reader), + std::ostream_iterator(std::cout, " ")); + std::cout << std::endl; + } + + { + node::ptr_t left_d(create_left_tree_from("d")); + boost::coroutines2::coroutine::pull_type left_d_reader( + [&]( boost::coroutines2::coroutine::push_type & out) { + traverse(left_d,out); + }); + + node::ptr_t right_b(create_right_tree_from("b")); + boost::coroutines2::coroutine::pull_type right_b_reader( + [&]( boost::coroutines2::coroutine::push_type & out) { + traverse(right_b,out); + }); + + std::cout << "left tree from d == right tree from b? " + << std::boolalpha + << std::equal(begin(left_d_reader), + end(left_d_reader), + begin(right_b_reader)) + << std::endl; + } + + { + node::ptr_t left_d(create_left_tree_from("d")); + boost::coroutines2::coroutine::pull_type left_d_reader( + [&]( boost::coroutines2::coroutine::push_type & out) { + traverse(left_d,out); + }); + + node::ptr_t right_x(create_right_tree_from("x")); + boost::coroutines2::coroutine::pull_type right_x_reader( + [&]( boost::coroutines2::coroutine::push_type & out) { + traverse(right_x,out); + }); + + std::cout << "left tree from d == right tree from x? " + << std::boolalpha + << std::equal(begin(left_d_reader), + end(left_d_reader), + begin(right_x_reader)) + << std::endl; + } + + std::cout << "Done" << std::endl; + + return EXIT_SUCCESS; +} diff --git a/example/segmented.cpp b/example/segmented.cpp new file mode 100644 index 0000000..eb24309 --- /dev/null +++ b/example/segmented.cpp @@ -0,0 +1,65 @@ + +// Copyright Oliver Kowalke 2014. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#include +#include + +#include + +#include + +#ifdef BOOST_MSVC //MS VisualStudio +__declspec(noinline) void access( char *buf); +#else // GCC +void access( char *buf) __attribute__ ((noinline)); +#endif +void access( char *buf) +{ + buf[0] = '\0'; +} + +void bar( int i) +{ + char buf[4 * 1024]; + + if ( i > 0) + { + access( buf); + std::cout << i << ". iteration" << std::endl; + bar( i - 1); + } +} + +int main() { + int count = 384; + +#if defined(BOOST_USE_SEGMENTED_STACKS) + std::cout << "using segmented_stack stacks: allocates " << count << " * 4kB == " << 4 * count << "kB on stack, "; + std::cout << "initial stack size = " << boost::context::segmented_stack::traits_type::default_size() / 1024 << "kB" << std::endl; + std::cout << "application should not fail" << std::endl; +#else + std::cout << "using standard stacks: allocates " << count << " * 4kB == " << 4 * count << "kB on stack, "; + std::cout << "initial stack size = " << boost::context::fixedsize_stack::traits_type::default_size() / 1024 << "kB" << std::endl; + std::cout << "application might fail" << std::endl; +#endif + + boost::coroutines2::coroutine< void >::push_type sink( +#if defined(BOOST_USE_SEGMENTED_STACKS) + boost::coroutines2::segmented_stack(), +#else + boost::coroutines2::fixedsize_stack(), +#endif + [&]( boost::coroutines2::coroutine< void >::pull_type & source) { + bar( count); + source(); + }); + + sink(); + + std::cout << "main: done" << std::endl; + + return 0; +} diff --git a/example/simple.cpp b/example/simple.cpp new file mode 100644 index 0000000..bcf4bd2 --- /dev/null +++ b/example/simple.cpp @@ -0,0 +1,24 @@ + +// Copyright Oliver Kowalke 2014. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#include +#include + +#include + +int main() +{ + int i = 0; + boost::coroutines2::coroutine< void >::push_type sink( + [&]( boost::coroutines2::coroutine< void >::pull_type & source) { + std::cout << "inside coroutine-fn" << std::endl; + }); + sink(); + + std::cout << "\nDone" << std::endl; + + return EXIT_SUCCESS; +} diff --git a/example/tree.h b/example/tree.h new file mode 100644 index 0000000..28df527 --- /dev/null +++ b/example/tree.h @@ -0,0 +1,97 @@ + +// Copyright Oliver Kowalke 2014. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef TREE_H +#define TREE_H + +#include +#include + +#include +#include +#include + +#if defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable:4355) +#endif + +struct branch; +struct leaf; + +struct visitor +{ + virtual ~visitor() {}; + + virtual void visit( branch & b) = 0; + virtual void visit( leaf & l) = 0; +}; + +struct node +{ + typedef boost::intrusive_ptr< node > ptr_t; + + std::size_t use_count; + + node() : + use_count( 0) + {} + + virtual ~node() {} + + virtual void accept( visitor & v) = 0; + + friend inline void intrusive_ptr_add_ref( node * p) + { ++p->use_count; } + + friend inline void intrusive_ptr_release( node * p) + { if ( 0 == --p->use_count) delete p; } +}; + +struct branch : public node +{ + node::ptr_t left; + node::ptr_t right; + + static ptr_t create( node::ptr_t left_, node::ptr_t right_) + { return ptr_t( new branch( left_, right_) ); } + + branch( node::ptr_t left_, node::ptr_t right_) : + left( left_), right( right_) + {} + + void accept( visitor & v) + { v.visit( * this); } +}; + +struct leaf : public node +{ + std::string value; + + static ptr_t create( std::string const& value_) + { return ptr_t( new leaf( value_) ); } + + leaf( std::string const& value_) : + value( value_) + {} + + void accept( visitor & v) + { v.visit( * this); } +}; + +inline +bool operator==( leaf const& l, leaf const& r) +{ return l.value == r.value; } + +inline +bool operator!=( leaf const& l, leaf const& r) +{ return l.value != r.value; } + +#if defined(_MSC_VER) +# pragma warning(pop) +#endif + +#endif // TREE_H diff --git a/include/boost/coroutine2/all.hpp b/include/boost/coroutine2/all.hpp new file mode 100644 index 0000000..f54b456 --- /dev/null +++ b/include/boost/coroutine2/all.hpp @@ -0,0 +1,15 @@ + +// Copyright Oliver Kowalke 2014. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_COROUTINES2_ALL_H +#define BOOST_COROUTINES2_ALL_H + +#include +#include +#include +#include + +#endif // BOOST_COROUTINES2_ALL_H diff --git a/include/boost/coroutine2/coroutine.hpp b/include/boost/coroutine2/coroutine.hpp new file mode 100644 index 0000000..c579dfa --- /dev/null +++ b/include/boost/coroutine2/coroutine.hpp @@ -0,0 +1,39 @@ + +// Copyright Oliver Kowalke 2014. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_COROUTINES2_COROUTINE_H +#define BOOST_COROUTINES2_COROUTINE_H + +#include + +#include +#include + +#include + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_PREFIX +#endif + +namespace boost { +namespace coroutines2 { + +template< typename T > +struct asymmetric_coroutine { + typedef detail::pull_coroutine< T > pull_type; + typedef detail::push_coroutine< T > push_type; +}; + +template< typename T > +using coroutine = asymmetric_coroutine< T >; + +}} + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_SUFFIX +#endif + +#endif // BOOST_COROUTINES2_COROUTINE_H diff --git a/include/boost/coroutine2/detail/config.hpp b/include/boost/coroutine2/detail/config.hpp new file mode 100644 index 0000000..3588128 --- /dev/null +++ b/include/boost/coroutine2/detail/config.hpp @@ -0,0 +1,49 @@ + +// Copyright Oliver Kowalke 2014. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_COROUTINES2_DETAIL_CONFIG_H +#define BOOST_COROUTINES2_DETAIL_CONFIG_H + +#include +#include + +#ifdef BOOST_COROUTINES2_DECL +# undef BOOST_COROUTINES2_DECL +#endif + +#if (defined(BOOST_ALL_DYN_LINK) || defined(BOOST_COROUTINES2_DYN_LINK) ) && ! defined(BOOST_COROUTINES2_STATIC_LINK) +# if defined(BOOST_COROUTINES2_SOURCE) +# define BOOST_COROUTINES2_DECL BOOST_SYMBOL_EXPORT +# define BOOST_COROUTINES2_BUILD_DLL +# else +# define BOOST_COROUTINES2_DECL BOOST_SYMBOL_IMPORT +# endif +#endif + +#if ! defined(BOOST_COROUTINES2_DECL) +# define BOOST_COROUTINES2_DECL +#endif + +#if ! defined(BOOST_COROUTINES2_SOURCE) && ! defined(BOOST_ALL_NO_LIB) && ! defined(BOOST_COROUTINES2_NO_LIB) +# define BOOST_LIB_NAME boost_coroutine +# if defined(BOOST_ALL_DYN_LINK) || defined(BOOST_COROUTINES2_DYN_LINK) +# define BOOST_DYN_LINK +# endif +# include +#endif + +#if defined(BOOST_USE_SEGMENTED_STACKS) +# if ! ( (defined(__GNUC__) && __GNUC__ > 3 && __GNUC_MINOR__ > 6) || \ + (defined(__clang__) && __clang_major__ > 2 && __clang_minor__ > 3) ) +# error "compiler does not support segmented_stack stacks" +# endif +# define BOOST_COROUTINES2_SEGMENTS 10 +#endif + +#define BOOST_COROUTINES2_UNIDIRECT +#define BOOST_COROUTINES2_SYMMETRIC + +#endif // BOOST_COROUTINES2_DETAIL_CONFIG_H diff --git a/include/boost/coroutine2/detail/coroutine.hpp b/include/boost/coroutine2/detail/coroutine.hpp new file mode 100644 index 0000000..55641e4 --- /dev/null +++ b/include/boost/coroutine2/detail/coroutine.hpp @@ -0,0 +1,44 @@ + +// Copyright Oliver Kowalke 2014. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_COROUTINES2_DETAIL_COROUTINE_HPP +#define BOOST_COROUTINES2_DETAIL_COROUTINE_HPP + +#include + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_PREFIX +#endif + +namespace boost { +namespace coroutines2 { +namespace detail { + +template< typename T > +class pull_coroutine; + +template< typename T > +class push_coroutine; + +}}} + +#include +#include + +#include +#include + +#include +#include + +#include +#include + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_SUFFIX +#endif + +#endif // BOOST_COROUTINES2_DETAIL_COROUTINE_HPP diff --git a/include/boost/coroutine2/detail/forced_unwind.hpp b/include/boost/coroutine2/detail/forced_unwind.hpp new file mode 100644 index 0000000..e04eb72 --- /dev/null +++ b/include/boost/coroutine2/detail/forced_unwind.hpp @@ -0,0 +1,33 @@ + +// Copyright Oliver Kowalke 2014. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_COROUTINES2_DETAIL_FORCED_UNWIND_HPP +#define BOOST_COROUTINES2_DETAIL_FORCED_UNWIND_HPP + +#include + +#include +#include + +#include + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_PREFIX +#endif + +namespace boost { +namespace coroutines2 { +namespace detail { + +struct forced_unwind {}; + +}}} + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_SUFFIX +#endif + +#endif // BOOST_COROUTINES2_DETAIL_FORCED_UNWIND_HPP diff --git a/include/boost/coroutine2/detail/pull_control_block.hpp b/include/boost/coroutine2/detail/pull_control_block.hpp new file mode 100644 index 0000000..70c7cd2 --- /dev/null +++ b/include/boost/coroutine2/detail/pull_control_block.hpp @@ -0,0 +1,102 @@ + +// Copyright Oliver Kowalke 2014. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_COROUTINES2_DETAIL_PULL_CONTROL_BLOCK_HPP +#define BOOST_COROUTINES2_DETAIL_PULL_CONTROL_BLOCK_HPP + +#include + +#include +#include + +#include + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_PREFIX +#endif + +namespace boost { +namespace coroutines2 { +namespace detail { + +template< typename T > +struct pull_coroutine< T >::control_block { + typename push_coroutine< T >::control_block * other; + boost::context::execution_context caller; + boost::context::execution_context callee; + bool preserve_fpu; + int state; + std::exception_ptr except; + + template< typename StackAllocator, typename Fn > + control_block( context::preallocated, StackAllocator, rref< Fn >, bool); + + explicit control_block( typename push_coroutine< T >::control_block *); + + ~control_block(); + + control_block( control_block &) = delete; + control_block & operator=( control_block &) = delete; + + void jump_to(); + + bool valid() const noexcept; +}; + +template< typename T > +struct pull_coroutine< T & >::control_block { + typename push_coroutine< T & >::control_block * other; + boost::context::execution_context caller; + boost::context::execution_context callee; + bool preserve_fpu; + int state; + std::exception_ptr except; + + template< typename StackAllocator, typename Fn > + control_block( context::preallocated, StackAllocator, rref< Fn >, bool); + + explicit control_block( typename push_coroutine< T & >::control_block *); + + ~control_block(); + + control_block( control_block &) = delete; + control_block & operator=( control_block &) = delete; + + void jump_to(); + + bool valid() const noexcept; +}; + +struct pull_coroutine< void >::control_block { + typename push_coroutine< void >::control_block * other; + boost::context::execution_context caller; + boost::context::execution_context callee; + bool preserve_fpu; + int state; + std::exception_ptr except; + + template< typename StackAllocator, typename Fn > + control_block( context::preallocated, StackAllocator, rref< Fn >, bool); + + explicit control_block( typename push_coroutine< void >::control_block *); + + ~control_block(); + + control_block( control_block &) = delete; + control_block & operator=( control_block &) = delete; + + void jump_to(); + + bool valid() const noexcept; +}; + +}}} + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_SUFFIX +#endif + +#endif // BOOST_COROUTINES2_DETAIL_PULL_CONTROL_BLOCK_HPP diff --git a/include/boost/coroutine2/detail/pull_control_block.ipp b/include/boost/coroutine2/detail/pull_control_block.ipp new file mode 100644 index 0000000..c3a287f --- /dev/null +++ b/include/boost/coroutine2/detail/pull_control_block.ipp @@ -0,0 +1,259 @@ + +// Copyright Oliver Kowalke 2014. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_COROUTINES2_DETAIL_PULL_CONTROL_BLOCK_IPP +#define BOOST_COROUTINES2_DETAIL_PULL_CONTROL_BLOCK_IPP + +#include + +#include +#include + +#include + +#include +#include +#include +#include + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_PREFIX +#endif + +namespace boost { +namespace coroutines2 { +namespace detail { + +// pull_coroutine< T > + +template< typename T > +template< typename StackAllocator, typename Fn > +pull_coroutine< T >::control_block::control_block( context::preallocated palloc, StackAllocator salloc, + rref< Fn > rr, bool preserve_fpu_) : + other( nullptr), + caller( boost::context::execution_context::current() ), + callee( palloc, salloc, + [=] () mutable { + try { + // create synthesized push_coroutine< T > + typename push_coroutine< T >::control_block synthesized_cb( this); + push_coroutine< T > synthesized( & synthesized_cb); + other = & synthesized_cb; + // call coroutine-fn with synthesized push_coroutine as argument + rr( synthesized); + } catch ( forced_unwind const&) { + // do nothing for unwinding exception + } catch (...) { + // store other exceptions in exception-pointer + except = std::current_exception(); + } + // set termination flags + state |= static_cast< int >( state_t::complete); + // jump back to caller + caller.jump_to( preserve_fpu); + BOOST_ASSERT_MSG( false, "pull_coroutine is complete"); + }), + preserve_fpu( preserve_fpu_), + state( static_cast< int >( state_t::unwind) ), + except() { + callee.jump_to( preserve_fpu); +} + +template< typename T > +pull_coroutine< T >::control_block::control_block( typename push_coroutine< T >::control_block * cb) : + other( cb), + caller( other->callee), + callee( other->caller), + preserve_fpu( other->preserve_fpu), + state( 0), + except() { +} + +template< typename T > +pull_coroutine< T >::control_block::~control_block() { + if ( 0 == ( state & static_cast< int >( state_t::complete ) ) && + 0 != ( state & static_cast< int >( state_t::unwind) ) ) { + // set early-exit flag + state |= static_cast< int >( state_t::early_exit); + callee.jump_to( preserve_fpu); + } +} + +template< typename T > +void +pull_coroutine< T >::control_block::jump_to() { + callee.jump_to( preserve_fpu); + if ( except) { + std::rethrow_exception( except); + } + // test early-exit-flag + if ( 0 != ( ( other->state) & static_cast< int >( state_t::early_exit) ) ) { + throw forced_unwind(); + } +} + +template< typename T > +bool +pull_coroutine< T >::control_block::valid() const noexcept { + return nullptr != other && nullptr != other->t && 0 == ( state & static_cast< int >( state_t::complete) ); +} + + +// pull_coroutine< T & > + +template< typename T > +template< typename StackAllocator, typename Fn > +pull_coroutine< T & >::control_block::control_block( context::preallocated palloc, StackAllocator salloc, + rref< Fn > rr, bool preserve_fpu_) : + other( nullptr), + caller( boost::context::execution_context::current() ), + callee( palloc, salloc, + [=] () mutable { + try { + // create synthesized push_coroutine< T > + typename push_coroutine< T & >::control_block synthesized_cb( this); + push_coroutine< T & > synthesized( & synthesized_cb); + other = & synthesized_cb; + // call coroutine-fn with synthesized push_coroutine as argument + rr( synthesized); + } catch ( forced_unwind const&) { + // do nothing for unwinding exception + } catch (...) { + // store other exceptions in exception-pointer + except = std::current_exception(); + } + // set termination flags + state |= static_cast< int >( state_t::complete); + // jump back to caller + caller.jump_to( preserve_fpu); + BOOST_ASSERT_MSG( false, "pull_coroutine is complete"); + }), + preserve_fpu( preserve_fpu_), + state( static_cast< int >( state_t::unwind) ), + except() { + callee.jump_to( preserve_fpu); +} + +template< typename T > +pull_coroutine< T & >::control_block::control_block( typename push_coroutine< T & >::control_block * cb) : + other( cb), + caller( other->callee), + callee( other->caller), + preserve_fpu( other->preserve_fpu), + state( 0), + except() { +} + +template< typename T > +pull_coroutine< T & >::control_block::~control_block() { + if ( 0 == ( state & static_cast< int >( state_t::complete ) ) && + 0 != ( state & static_cast< int >( state_t::unwind) ) ) { + // set early-exit flag + state |= static_cast< int >( state_t::early_exit); + callee.jump_to( preserve_fpu); + } +} + +template< typename T > +void +pull_coroutine< T & >::control_block::jump_to() { + callee.jump_to( preserve_fpu); + if ( except) { + std::rethrow_exception( except); + } + // test early-exit-flag + if ( 0 != ( ( other->state) & static_cast< int >( state_t::early_exit) ) ) { + throw forced_unwind(); + } +} + +template< typename T > +bool +pull_coroutine< T & >::control_block::valid() const noexcept { + return nullptr != other && nullptr != other->t && 0 == ( state & static_cast< int >( state_t::complete) ); +} + + +// pull_coroutine< void > + +template< typename StackAllocator, typename Fn > +pull_coroutine< void >::control_block::control_block( context::preallocated palloc, StackAllocator salloc, + rref< Fn > rr, bool preserve_fpu_) : + other( nullptr), + caller( boost::context::execution_context::current() ), + callee( palloc, salloc, + [=] () mutable { + try { + // create synthesized push_coroutine< T > + typename push_coroutine< void >::control_block synthesized_cb( this); + push_coroutine< void > synthesized( & synthesized_cb); + other = & synthesized_cb; + // call coroutine-fn with synthesized push_coroutine as argument + rr( synthesized); + } catch ( forced_unwind const&) { + // do nothing for unwinding exception + } catch (...) { + // store other exceptions in exception-pointer + except = std::current_exception(); + } + // set termination flags + state |= static_cast< int >( state_t::complete); + // jump back to caller + caller.jump_to( preserve_fpu); + BOOST_ASSERT_MSG( false, "pull_coroutine is complete"); + }), + preserve_fpu( preserve_fpu_), + state( static_cast< int >( state_t::unwind) ), + except() { + callee.jump_to( preserve_fpu); +} + +inline +pull_coroutine< void >::control_block::control_block( typename push_coroutine< void >::control_block * cb) : + other( cb), + caller( other->callee), + callee( other->caller), + preserve_fpu( other->preserve_fpu), + state( 0), + except() { +} + +inline +pull_coroutine< void >::control_block::~control_block() { + if ( 0 == ( state & static_cast< int >( state_t::complete ) ) && + 0 != ( state & static_cast< int >( state_t::unwind) ) ) { + // set early-exit flag + state |= static_cast< int >( state_t::early_exit); + callee.jump_to( preserve_fpu); + } +} + +inline +void +pull_coroutine< void >::control_block::jump_to() { + callee.jump_to( preserve_fpu); + if ( except) { + std::rethrow_exception( except); + } + // test early-exit-flag + if ( 0 != ( ( other->state) & static_cast< int >( state_t::early_exit) ) ) { + throw forced_unwind(); + } +} + +inline +bool +pull_coroutine< void >::control_block::valid() const noexcept { + return nullptr != other && 0 == ( state & static_cast< int >( state_t::complete) ); +} + +}}} + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_SUFFIX +#endif + +#endif // BOOST_COROUTINES2_DETAIL_PULL_CONTROL_BLOCK_IPP diff --git a/include/boost/coroutine2/detail/pull_coroutine.hpp b/include/boost/coroutine2/detail/pull_coroutine.hpp new file mode 100644 index 0000000..bda4cec --- /dev/null +++ b/include/boost/coroutine2/detail/pull_coroutine.hpp @@ -0,0 +1,320 @@ + +// Copyright Oliver Kowalke 2014. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_COROUTINES2_DETAIL_PULL_COROUTINE_HPP +#define BOOST_COROUTINES2_DETAIL_PULL_COROUTINE_HPP + +#include +#include + +#include +#include +#include + +#include + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_PREFIX +#endif + +namespace boost { +namespace coroutines2 { +namespace detail { + +template< typename T > +class pull_coroutine { +private: + template< typename X > + friend class push_coroutine; + + struct control_block; + + control_block * cb_; + + explicit pull_coroutine( control_block *); + + bool has_result_() const; + +public: + template< typename Fn > + explicit pull_coroutine( Fn &&, bool = false); + + template< typename StackAllocator, typename Fn > + explicit pull_coroutine( StackAllocator, Fn &&, bool = false); + + ~pull_coroutine(); + + pull_coroutine( pull_coroutine const&) = delete; + pull_coroutine & operator=( pull_coroutine const&) = delete; + + pull_coroutine( pull_coroutine &&); + + pull_coroutine & operator=( pull_coroutine && other) { + if ( this != & other) { + cb_ = other.cb_; + other.cb_ = nullptr; + } + return * this; + } + + pull_coroutine & operator()(); + + explicit operator bool() const noexcept; + + bool operator!() const noexcept; + + T get() const noexcept; + + class iterator : public std::iterator< std::input_iterator_tag, typename std::remove_reference< T >::type > { + private: + pull_coroutine< T > * c_; + + void fetch_() { + BOOST_ASSERT( nullptr != c_); + if ( ! ( * c_) ) { + c_ = nullptr; + return; + } + } + + void increment_() { + BOOST_ASSERT( nullptr != c_); + BOOST_ASSERT( * c_); + ( * c_)(); + fetch_(); + } + + public: + typedef typename iterator::pointer pointer_t; + typedef typename iterator::reference reference_t; + + iterator() : + c_( nullptr) { + } + + explicit iterator( pull_coroutine< T > * c) : + c_( c) { + fetch_(); + } + + iterator( iterator const& other) : + c_( other.c_) { + } + + iterator & operator=( iterator const& other) { + if ( this == & other) return * this; + c_ = other.c_; + return * this; + } + + bool operator==( iterator const& other) const { + return other.c_ == c_; + } + + bool operator!=( iterator const& other) const { + return other.c_ != c_; + } + + iterator & operator++() { + increment_(); + return * this; + } + + iterator operator++( int) { + iterator tmp( * this); + ++*this; + return tmp; + } + + reference_t operator*() const { + return * c_->cb_->other->t; + } + + pointer_t operator->() const { + return c_->cb_->other->t; + } + }; + + friend class iterator; +}; + +template< typename T > +class pull_coroutine< T & > { +private: + template< typename X > + friend class push_coroutine; + + struct control_block; + + control_block * cb_; + + explicit pull_coroutine( control_block *); + + bool has_result_() const; + +public: + template< typename Fn > + explicit pull_coroutine( Fn &&, bool = false); + + template< typename StackAllocator, typename Fn > + explicit pull_coroutine( StackAllocator, Fn &&, bool = false); + + ~pull_coroutine(); + + pull_coroutine( pull_coroutine const&) = delete; + pull_coroutine & operator=( pull_coroutine const&) = delete; + + pull_coroutine( pull_coroutine &&); + + pull_coroutine & operator=( pull_coroutine && other) { + if ( this != & other) { + cb_ = other.cb_; + other.cb_ = nullptr; + } + return * this; + } + + pull_coroutine & operator()(); + + explicit operator bool() const noexcept; + + bool operator!() const noexcept; + + T & get() const noexcept; + + class iterator : public std::iterator< std::input_iterator_tag, typename std::remove_reference< T >::type > { + private: + pull_coroutine< T & > * c_; + + void fetch_() { + BOOST_ASSERT( nullptr != c_); + if ( ! ( * c_) ) { + c_ = nullptr; + return; + } + } + + void increment_() { + BOOST_ASSERT( nullptr != c_); + BOOST_ASSERT( * c_); + ( * c_)(); + fetch_(); + } + + public: + typedef typename iterator::pointer pointer_t; + typedef typename iterator::reference reference_t; + + iterator() : + c_( nullptr) { + } + + explicit iterator( pull_coroutine< T & > * c) : + c_( c) { + fetch_(); + } + + iterator( iterator const& other) : + c_( other.c_) { + } + + iterator & operator=( iterator const& other) { + if ( this == & other) return * this; + c_ = other.c_; + return * this; + } + + bool operator==( iterator const& other) const { + return other.c_ == c_; + } + + bool operator!=( iterator const& other) const { + return other.c_ != c_; + } + + iterator & operator++() { + increment_(); + return * this; + } + + iterator operator++( int) { + iterator tmp( * this); + ++*this; + return tmp; + } + + reference_t operator*() const { + return * c_->cb_->other->t; + } + + pointer_t operator->() const { + return c_->cb_->other->t; + } + }; + + friend class iterator; +}; + +template<> +class pull_coroutine< void > { +private: + template< typename X > + friend class push_coroutine; + + struct control_block; + + control_block * cb_; + + explicit pull_coroutine( control_block *); + +public: + template< typename Fn > + explicit pull_coroutine( Fn &&, bool = false); + + template< typename StackAllocator, typename Fn > + explicit pull_coroutine( StackAllocator, Fn &&, bool = false); + + ~pull_coroutine(); + + pull_coroutine( pull_coroutine const&) = delete; + pull_coroutine & operator=( pull_coroutine const&) = delete; + + pull_coroutine( pull_coroutine &&); + + pull_coroutine & operator=( pull_coroutine && other) { + if ( this != & other) { + cb_ = other.cb_; + other.cb_ = nullptr; + } + return * this; + } + + pull_coroutine & operator()(); + + explicit operator bool() const noexcept; + + bool operator!() const noexcept; +}; + +template< typename T > +typename pull_coroutine< T >::iterator +begin( pull_coroutine< T > & c) { + return typename pull_coroutine< T >::iterator( & c); +} + +template< typename T > +typename pull_coroutine< T >::iterator +end( pull_coroutine< T > &) { + return typename pull_coroutine< T >::iterator(); +} + +}}} + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_SUFFIX +#endif + +#endif // BOOST_COROUTINES2_DETAIL_PULL_COROUTINE_HPP diff --git a/include/boost/coroutine2/detail/pull_coroutine.ipp b/include/boost/coroutine2/detail/pull_coroutine.ipp new file mode 100644 index 0000000..551c7df --- /dev/null +++ b/include/boost/coroutine2/detail/pull_coroutine.ipp @@ -0,0 +1,226 @@ + +// Copyright Oliver Kowalke 2014. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_COROUTINES2_DETAIL_PULL_COROUTINE_IPP +#define BOOST_COROUTINES2_DETAIL_PULL_COROUTINE_IPP + +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_PREFIX +#endif + +namespace boost { +namespace coroutines2 { +namespace detail { + +// pull_coroutine< T > + +template< typename T > +pull_coroutine< T >::pull_coroutine( control_block * cb) : + cb_( cb) { +} + +template< typename T > +bool +pull_coroutine< T >::has_result_() const { + return nullptr != cb_->other->t; +} + +template< typename T > +template< typename Fn > +pull_coroutine< T >::pull_coroutine( Fn && fn, bool preserve_fpu) : + pull_coroutine( fixedsize_stack(), std::forward< Fn >( fn), preserve_fpu) { +} + +template< typename T > +template< typename StackAllocator, typename Fn > +pull_coroutine< T >::pull_coroutine( StackAllocator salloc, Fn && fn, bool preserve_fpu) : + cb_( nullptr) { + context::stack_context sctx( salloc.allocate() ); + void * sp = static_cast< char * >( sctx.sp) - sizeof( control_block); + std::size_t size = sctx.size - sizeof( control_block); + cb_ = new ( sp) control_block( context::preallocated( sp, size, sctx), + salloc, rref< Fn >( std::forward< Fn >( fn) ), preserve_fpu); +} + +template< typename T > +pull_coroutine< T >::~pull_coroutine() { + if ( nullptr != cb_) { + cb_->~control_block(); + } +} + +template< typename T > +pull_coroutine< T >::pull_coroutine( pull_coroutine && other) : + cb_( nullptr) { + std::swap( cb_, other.cb_); +} + +template< typename T > +pull_coroutine< T > & +pull_coroutine< T >::operator()() { + cb_->jump_to(); + return * this; +} + +template< typename T > +pull_coroutine< T >::operator bool() const noexcept { + return nullptr != cb_ && cb_->valid(); +} + +template< typename T > +bool +pull_coroutine< T >::operator!() const noexcept { + return nullptr == cb_ || ! cb_->valid(); +} + +template< typename T > +T +pull_coroutine< T >::get() const noexcept { + return std::move( * cb_->other->t); +} + + +// pull_coroutine< T & > + +template< typename T > +pull_coroutine< T & >::pull_coroutine( control_block * cb) : + cb_( cb) { +} + +template< typename T > +bool +pull_coroutine< T & >::has_result_() const { + return nullptr != cb_->other->t; +} + +template< typename T > +template< typename Fn > +pull_coroutine< T & >::pull_coroutine( Fn && fn, bool preserve_fpu) : + pull_coroutine( fixedsize_stack(), std::forward< Fn >( fn), preserve_fpu) { +} + +template< typename T > +template< typename StackAllocator, typename Fn > +pull_coroutine< T & >::pull_coroutine( StackAllocator salloc, Fn && fn, bool preserve_fpu) : + cb_( nullptr) { + context::stack_context sctx( salloc.allocate() ); + void * sp = static_cast< char * >( sctx.sp) - sizeof( control_block); + std::size_t size = sctx.size - sizeof( control_block); + cb_ = new ( sp) control_block( context::preallocated( sp, size, sctx), + salloc, rref< Fn >( std::forward< Fn >( fn) ), preserve_fpu); +} + +template< typename T > +pull_coroutine< T & >::~pull_coroutine() { + if ( nullptr != cb_) { + cb_->~control_block(); + } +} + +template< typename T > +pull_coroutine< T & >::pull_coroutine( pull_coroutine && other) : + cb_( nullptr) { + std::swap( cb_, other.cb_); +} + +template< typename T > +pull_coroutine< T & > & +pull_coroutine< T & >::operator()() { + cb_->jump_to(); + return * this; +} + +template< typename T > +pull_coroutine< T & >::operator bool() const noexcept { + return nullptr != cb_ && cb_->valid(); +} + +template< typename T > +bool +pull_coroutine< T & >::operator!() const noexcept { + return nullptr == cb_ || ! cb_->valid(); +} + +template< typename T > +T & +pull_coroutine< T & >::get() const noexcept { + return * cb_->other->t; +} + + +// pull_coroutine< void > + +inline +pull_coroutine< void >::pull_coroutine( control_block * cb) : + cb_( cb) { +} + +template< typename Fn > +pull_coroutine< void >::pull_coroutine( Fn && fn, bool preserve_fpu) : + pull_coroutine( fixedsize_stack(), std::forward< Fn >( fn), preserve_fpu) { +} + +template< typename StackAllocator, typename Fn > +pull_coroutine< void >::pull_coroutine( StackAllocator salloc, Fn && fn, bool preserve_fpu) : + cb_( nullptr) { + context::stack_context sctx( salloc.allocate() ); + void * sp = static_cast< char * >( sctx.sp) - sizeof( control_block); + std::size_t size = sctx.size - sizeof( control_block); + cb_ = new ( sp) control_block( context::preallocated( sp, size, sctx), + salloc, rref< Fn >( std::forward< Fn >( fn) ), preserve_fpu); +} + +inline +pull_coroutine< void >::~pull_coroutine() { + if ( nullptr != cb_) { + cb_->~control_block(); + } +} + +inline +pull_coroutine< void >::pull_coroutine( pull_coroutine && other) : + cb_( nullptr) { + std::swap( cb_, other.cb_); +} + +inline +pull_coroutine< void > & +pull_coroutine< void >::operator()() { + cb_->jump_to(); + return * this; +} + +inline +pull_coroutine< void >::operator bool() const noexcept { + return nullptr != cb_ && cb_->valid(); +} + +inline +bool +pull_coroutine< void >::operator!() const noexcept { + return nullptr == cb_ || ! cb_->valid(); +} + +}}} + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_SUFFIX +#endif + +#endif // BOOST_COROUTINES2_DETAIL_PULL_COROUTINE_IPP diff --git a/include/boost/coroutine2/detail/push_control_block.hpp b/include/boost/coroutine2/detail/push_control_block.hpp new file mode 100644 index 0000000..de9390f --- /dev/null +++ b/include/boost/coroutine2/detail/push_control_block.hpp @@ -0,0 +1,106 @@ + +// Copyright Oliver Kowalke 2014. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_COROUTINES2_DETAIL_PUSH_CONTROL_BLOCK_HPP +#define BOOST_COROUTINES2_DETAIL_PUSH_CONTROL_BLOCK_HPP + +#include + +#include +#include + +#include + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_PREFIX +#endif + +namespace boost { +namespace coroutines2 { +namespace detail { + +template< typename T > +struct push_coroutine< T >::control_block { + typename pull_coroutine< T >::control_block * other; + boost::context::execution_context caller; + boost::context::execution_context callee; + bool preserve_fpu; + int state; + std::exception_ptr except; + T * t; + + template< typename StackAllocator, typename Fn > + control_block( context::preallocated, StackAllocator, rref< Fn >, bool); + + explicit control_block( typename pull_coroutine< T >::control_block *); + + ~control_block(); + + control_block( control_block &) = delete; + control_block & operator=( control_block &) = delete; + + void jump_to( T const&); + + void jump_to( T &&); + + bool valid() const noexcept; +}; + +template< typename T > +struct push_coroutine< T & >::control_block { + typename pull_coroutine< T & >::control_block * other; + boost::context::execution_context caller; + boost::context::execution_context callee; + bool preserve_fpu; + int state; + std::exception_ptr except; + T * t; + + template< typename StackAllocator, typename Fn > + control_block( context::preallocated, StackAllocator, rref< Fn >, bool); + + explicit control_block( typename pull_coroutine< T & >::control_block *); + + ~control_block(); + + control_block( control_block &) = delete; + control_block & operator=( control_block &) = delete; + + void jump_to( T &); + + bool valid() const noexcept; +}; + +struct push_coroutine< void >::control_block { + typename pull_coroutine< void >::control_block * other; + boost::context::execution_context caller; + boost::context::execution_context callee; + bool preserve_fpu; + int state; + std::exception_ptr except; + + template< typename StackAllocator, typename Fn > + control_block( context::preallocated, StackAllocator, rref< Fn >, bool); + + explicit control_block( typename pull_coroutine< void >::control_block *); + + ~control_block(); + + control_block( control_block &) = delete; + control_block & operator=( control_block &) = delete; + + void jump_to(); + + bool valid() const noexcept; +}; + +}}} + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_SUFFIX +#endif + +#endif // BOOST_COROUTINES2_DETAIL_PUSH_CONTROL_BLOCK_HPP diff --git a/include/boost/coroutine2/detail/push_control_block.ipp b/include/boost/coroutine2/detail/push_control_block.ipp new file mode 100644 index 0000000..eabaa27 --- /dev/null +++ b/include/boost/coroutine2/detail/push_control_block.ipp @@ -0,0 +1,285 @@ + +// Copyright Oliver Kowalke 2014. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_COROUTINES2_DETAIL_PUSH_CONTROL_BLOCK_IPP +#define BOOST_COROUTINES2_DETAIL_PUSH_CONTROL_BLOCK_IPP + +#include +#include + +#include +#include + +#include + +#include +#include +#include +#include + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_PREFIX +#endif + +namespace boost { +namespace coroutines2 { +namespace detail { + +// push_coroutine< T > + +template< typename T > +template< typename StackAllocator, typename Fn > +push_coroutine< T >::control_block::control_block( context::preallocated palloc, StackAllocator salloc, + rref< Fn > rr, bool preserve_fpu_) : + other( nullptr), + caller( boost::context::execution_context::current() ), + callee( palloc, salloc, + [=] () mutable { + try { + // create synthesized pull_coroutine< T > + typename pull_coroutine< T >::control_block synthesized_cb( this); + pull_coroutine< T > synthesized( & synthesized_cb); + other = & synthesized_cb; + // call coroutine-fn with synthesized pull_coroutine as argument + rr( synthesized); + } catch ( forced_unwind const&) { + // do nothing for unwinding exception + } catch (...) { + // store other exceptions in exception-pointer + except = std::current_exception(); + } + // set termination flags + state |= static_cast< int >( state_t::complete); + // jump back to caller + caller.jump_to( preserve_fpu_); + BOOST_ASSERT_MSG( false, "push_coroutine is complete"); + }), + preserve_fpu( preserve_fpu_), + state( static_cast< int >( state_t::unwind) ), + except(), + t( nullptr) { +} + +template< typename T > +push_coroutine< T >::control_block::control_block( typename pull_coroutine< T >::control_block * cb) : + other( cb), + caller( other->callee), + callee( other->caller), + preserve_fpu( other->preserve_fpu), + state( 0), + except(), + t( nullptr) { +} + +template< typename T > +push_coroutine< T >::control_block::~control_block() { + if ( 0 == ( state & static_cast< int >( state_t::complete ) ) && + 0 != ( state & static_cast< int >( state_t::unwind) ) ) { + // set early-exit flag + state |= static_cast< int >( state_t::early_exit); + callee.jump_to( preserve_fpu); + } +} + +template< typename T > +void +push_coroutine< T >::control_block::jump_to( T const& t_) { + // store data on this stack + // pass an pointer (address of tmp) to other context + T tmp( t_); + t = & tmp; + callee.jump_to( preserve_fpu); + t = nullptr; + if ( except) { + std::rethrow_exception( except); + } + // test early-exit-flag + if ( 0 != ( ( other->state) & static_cast< int >( state_t::early_exit) ) ) { + throw forced_unwind(); + } +} + +template< typename T > +void +push_coroutine< T >::control_block::jump_to( T && t_) { + // store data on this stack + // pass an pointer (address of tmp) to other context + T tmp( std::move( t_) ); + t = & tmp; + callee.jump_to( preserve_fpu); + t = nullptr; + if ( except) { + std::rethrow_exception( except); + } + // test early-exit-flag + if ( 0 != ( ( other->state) & static_cast< int >( state_t::early_exit) ) ) { + throw forced_unwind(); + } +} + +template< typename T > +bool +push_coroutine< T >::control_block::valid() const noexcept { + return 0 == ( state & static_cast< int >( state_t::complete) ); +} + + +// push_coroutine< T & > + +template< typename T > +template< typename StackAllocator, typename Fn > +push_coroutine< T & >::control_block::control_block( context::preallocated palloc, StackAllocator salloc, + rref< Fn > rr, bool preserve_fpu_) : + other( nullptr), + caller( boost::context::execution_context::current() ), + callee( palloc, salloc, + [=] () mutable { + try { + // create synthesized pull_coroutine< T > + typename pull_coroutine< T & >::control_block synthesized_cb( this); + pull_coroutine< T & > synthesized( & synthesized_cb); + other = & synthesized_cb; + // call coroutine-fn with synthesized pull_coroutine as argument + rr( synthesized); + } catch ( forced_unwind const&) { + // do nothing for unwinding exception + } catch (...) { + // store other exceptions in exception-pointer + except = std::current_exception(); + } + // set termination flags + state |= static_cast< int >( state_t::complete); + // jump back to caller + caller.jump_to( preserve_fpu_); + BOOST_ASSERT_MSG( false, "push_coroutine is complete"); + }), + preserve_fpu( preserve_fpu_), + state( static_cast< int >( state_t::unwind) ), + except(), + t( nullptr) { +} + +template< typename T > +push_coroutine< T & >::control_block::control_block( typename pull_coroutine< T & >::control_block * cb) : + other( cb), + caller( other->callee), + callee( other->caller), + preserve_fpu( other->preserve_fpu), + state( 0), + except(), + t( nullptr) { +} + +template< typename T > +push_coroutine< T & >::control_block::~control_block() { + if ( 0 == ( state & static_cast< int >( state_t::complete ) ) && + 0 != ( state & static_cast< int >( state_t::unwind) ) ) { + // set early-exit flag + state |= static_cast< int >( state_t::early_exit); + callee.jump_to( preserve_fpu); + } +} + +template< typename T > +void +push_coroutine< T & >::control_block::jump_to( T & t_) { + t = & t_; + callee.jump_to( preserve_fpu); + t = nullptr; + if ( except) { + std::rethrow_exception( except); + } + // test early-exit-flag + if ( 0 != ( ( other->state) & static_cast< int >( state_t::early_exit) ) ) { + throw forced_unwind(); + } +} + +template< typename T > +bool +push_coroutine< T & >::control_block::valid() const noexcept { + return 0 == ( state & static_cast< int >( state_t::complete) ); +} + + +// push_coroutine< void > + +template< typename StackAllocator, typename Fn > +push_coroutine< void >::control_block::control_block( context::preallocated palloc, StackAllocator salloc, rref< Fn > rr, bool preserve_fpu_) : + other( nullptr), + caller( boost::context::execution_context::current() ), + callee( palloc, salloc, + [=] () mutable { + try { + // create synthesized pull_coroutine< T > + typename pull_coroutine< void >::control_block synthesized_cb( this); + pull_coroutine< void > synthesized( & synthesized_cb); + other = & synthesized_cb; + // call coroutine-fn with synthesized pull_coroutine as argument + rr( synthesized); + } catch ( forced_unwind const&) { + // do nothing for unwinding exception + } catch (...) { + // store other exceptions in exception-pointer + except = std::current_exception(); + } + // set termination flags + state |= static_cast< int >( state_t::complete); + // jump back to caller + caller.jump_to( preserve_fpu_); + BOOST_ASSERT_MSG( false, "push_coroutine is complete"); + }), + preserve_fpu( preserve_fpu_), + state( static_cast< int >( state_t::unwind) ), + except() { +} + +inline +push_coroutine< void >::control_block::control_block( typename pull_coroutine< void >::control_block * cb) : + other( cb), + caller( other->callee), + callee( other->caller), + preserve_fpu( other->preserve_fpu), + state( 0), + except() { +} + +inline +push_coroutine< void >::control_block::~control_block() { + if ( 0 == ( state & static_cast< int >( state_t::complete ) ) && + 0 != ( state & static_cast< int >( state_t::unwind) ) ) { + // set early-exit flag + state |= static_cast< int >( state_t::early_exit); + callee.jump_to( preserve_fpu); + } +} + +inline +void +push_coroutine< void >::control_block::jump_to() { + callee.jump_to( preserve_fpu); + if ( except) { + std::rethrow_exception( except); + } + // test early-exit-flag + if ( 0 != ( ( other->state) & static_cast< int >( state_t::early_exit) ) ) { + throw forced_unwind(); + } +} + +inline +bool +push_coroutine< void >::control_block::valid() const noexcept { + return 0 == ( state & static_cast< int >( state_t::complete) ); +} + +}}} + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_SUFFIX +#endif + +#endif // BOOST_COROUTINES2_DETAIL_PUSH_CONTROL_BLOCK_IPP diff --git a/include/boost/coroutine2/detail/push_coroutine.hpp b/include/boost/coroutine2/detail/push_coroutine.hpp new file mode 100644 index 0000000..1a2d01f --- /dev/null +++ b/include/boost/coroutine2/detail/push_coroutine.hpp @@ -0,0 +1,242 @@ + +// Copyright Oliver Kowalke 2014. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_COROUTINES2_DETAIL_PUSH_COROUTINE_HPP +#define BOOST_COROUTINES2_DETAIL_PUSH_COROUTINE_HPP + +#include +#include + +#include +#include +#include + +#include + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_PREFIX +#endif + +namespace boost { +namespace coroutines2 { +namespace detail { + +template< typename T > +class push_coroutine { +private: + template< typename X > + friend class pull_coroutine; + + struct control_block; + + control_block * cb_; + + explicit push_coroutine( control_block *); + +public: + template< typename Fn > + explicit push_coroutine( Fn &&, bool = false); + + template< typename StackAllocator, typename Fn > + explicit push_coroutine( StackAllocator, Fn &&, bool = false); + + ~push_coroutine(); + + push_coroutine( push_coroutine const&) = delete; + push_coroutine & operator=( push_coroutine const&) = delete; + + push_coroutine( push_coroutine &&); + + push_coroutine & operator=( push_coroutine && other) { + if ( this != & other) { + cb_ = other.cb_; + other.cb_ = nullptr; + } + return * this; + } + + push_coroutine & operator()( T const&); + + push_coroutine & operator()( T &&); + + explicit operator bool() const noexcept; + + bool operator!() const noexcept; + + class iterator : public std::iterator< std::output_iterator_tag, void, void, void, void > { + private: + push_coroutine< T > * c_; + + public: + iterator() : + c_( nullptr) { + } + + explicit iterator( push_coroutine< T > * c) : + c_( c) { + } + + iterator & operator=( T t) { + BOOST_ASSERT( c_); + if ( ! ( * c_)( t) ) c_ = 0; + return * this; + } + + bool operator==( iterator const& other) const { + return other.c_ == c_; + } + + bool operator!=( iterator const& other) const { + return other.c_ != c_; + } + + iterator & operator*() { + return * this; + } + + iterator & operator++() { + return * this; + } + }; +}; + +template< typename T > +class push_coroutine< T & > { +private: + template< typename X > + friend class pull_coroutine; + + struct control_block; + + control_block * cb_; + + explicit push_coroutine( control_block *); + +public: + template< typename Fn > + explicit push_coroutine( Fn &&, bool = false); + + template< typename StackAllocator, typename Fn > + explicit push_coroutine( StackAllocator, Fn &&, bool = false); + + ~push_coroutine(); + + push_coroutine( push_coroutine const&) = delete; + push_coroutine & operator=( push_coroutine const&) = delete; + + push_coroutine( push_coroutine &&); + + push_coroutine & operator=( push_coroutine && other) { + if ( this != & other) { + cb_ = other.cb_; + other.cb_ = nullptr; + } + return * this; + } + + push_coroutine & operator()( T &); + + explicit operator bool() const noexcept; + + bool operator!() const noexcept; + + class iterator : public std::iterator< std::output_iterator_tag, void, void, void, void > { + private: + push_coroutine< T & > * c_; + + public: + iterator() : + c_( nullptr) { + } + + explicit iterator( push_coroutine< T & > * c) : + c_( c) { + } + + iterator & operator=( T & t) { + BOOST_ASSERT( c_); + if ( ! ( * c_)( t) ) c_ = 0; + return * this; + } + + bool operator==( iterator const& other) const { + return other.c_ == c_; + } + + bool operator!=( iterator const& other) const { + return other.c_ != c_; + } + + iterator & operator*() { + return * this; + } + + iterator & operator++() { + return * this; + } + }; +}; + +template<> +class push_coroutine< void > { +private: + template< typename X > + friend class pull_coroutine; + + struct control_block; + + control_block * cb_; + + explicit push_coroutine( control_block *); + +public: + template< typename Fn > + explicit push_coroutine( Fn &&, bool = false); + + template< typename StackAllocator, typename Fn > + explicit push_coroutine( StackAllocator, Fn &&, bool = false); + + ~push_coroutine(); + + push_coroutine( push_coroutine const&) = delete; + push_coroutine & operator=( push_coroutine const&) = delete; + + push_coroutine( push_coroutine &&); + + push_coroutine & operator=( push_coroutine && other) { + if ( this != & other) { + cb_ = other.cb_; + other.cb_ = nullptr; + } + return * this; + } + + push_coroutine & operator()(); + + explicit operator bool() const noexcept; + + bool operator!() const noexcept; +}; + +template< typename T > +typename push_coroutine< T >::iterator +begin( push_coroutine< T > & c) { + return typename push_coroutine< T >::iterator( & c); +} + +template< typename T > +typename push_coroutine< T >::iterator +end( push_coroutine< T > &) { + return typename push_coroutine< T >::iterator(); +} + +}}} + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_SUFFIX +#endif + +#endif // BOOST_COROUTINES2_DETAIL_PUSH_COROUTINE_HPP diff --git a/include/boost/coroutine2/detail/push_coroutine.ipp b/include/boost/coroutine2/detail/push_coroutine.ipp new file mode 100644 index 0000000..6613b97 --- /dev/null +++ b/include/boost/coroutine2/detail/push_coroutine.ipp @@ -0,0 +1,208 @@ + +// Copyright Oliver Kowalke 2014. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_COROUTINES2_DETAIL_PUSH_COROUTINE_IPP +#define BOOST_COROUTINES2_DETAIL_PUSH_COROUTINE_IPP + +#include + +#include +#include + +#include +#include + +#include +#include +#include + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_PREFIX +#endif + +namespace boost { +namespace coroutines2 { +namespace detail { + +// push_coroutine< T > + +template< typename T > +push_coroutine< T >::push_coroutine( control_block * cb) : + cb_( cb) { +} + +template< typename T > +template< typename Fn > +push_coroutine< T >::push_coroutine( Fn && fn, bool preserve_fpu) : + push_coroutine( fixedsize_stack(), std::forward< Fn >( fn), preserve_fpu) { +} + +template< typename T > +template< typename StackAllocator, typename Fn > +push_coroutine< T >::push_coroutine( StackAllocator salloc, Fn && fn, bool preserve_fpu) : + cb_( nullptr) { + context::stack_context sctx( salloc.allocate() ); + void * sp = static_cast< char * >( sctx.sp) - sizeof( control_block); + std::size_t size = sctx.size - sizeof( control_block); + cb_= new ( sp) control_block( context::preallocated( sp, size, sctx), + salloc, rref< Fn >( std::forward< Fn >( fn) ), preserve_fpu); +} + +template< typename T > +push_coroutine< T >::~push_coroutine() { + if ( nullptr != cb_) { + cb_->~control_block(); + } +} + +template< typename T > +push_coroutine< T >::push_coroutine( push_coroutine && other) : + cb_( nullptr) { + std::swap( cb_, other.cb_); +} + +template< typename T > +push_coroutine< T > & +push_coroutine< T >::operator()( T const& t) { + cb_->jump_to( t); + return * this; +} + +template< typename T > +push_coroutine< T > & +push_coroutine< T >::operator()( T && t) { + cb_->jump_to( std::forward< T >( t) ); + return * this; +} + +template< typename T > +push_coroutine< T >::operator bool() const noexcept { + return nullptr != cb_ && cb_->valid(); +} + +template< typename T > +bool +push_coroutine< T >::operator!() const noexcept { + return nullptr == cb_ || ! cb_->valid(); +} + + +// push_coroutine< T & > + +template< typename T > +push_coroutine< T & >::push_coroutine( control_block * cb) : + cb_( cb) { +} + +template< typename T > +template< typename Fn > +push_coroutine< T & >::push_coroutine( Fn && fn, bool preserve_fpu) : + push_coroutine( fixedsize_stack(), std::forward< Fn >( fn), preserve_fpu) { +} + +template< typename T > +template< typename StackAllocator, typename Fn > +push_coroutine< T & >::push_coroutine( StackAllocator salloc, Fn && fn, bool preserve_fpu) : + cb_( nullptr) { + context::stack_context sctx( salloc.allocate() ); + void * sp = static_cast< char * >( sctx.sp) - sizeof( control_block); + std::size_t size = sctx.size - sizeof( control_block); + cb_ = new ( sp) control_block( context::preallocated( sp, size, sctx), + salloc, rref< Fn >( std::forward< Fn >( fn) ), preserve_fpu); +} + +template< typename T > +push_coroutine< T & >::~push_coroutine() { + if ( nullptr != cb_) { + cb_->~control_block(); + } +} + +template< typename T > +push_coroutine< T & >::push_coroutine( push_coroutine && other) : + cb_( nullptr) { + std::swap( cb_, other.cb_); +} + +template< typename T > +push_coroutine< T & > & +push_coroutine< T & >::operator()( T & t) { + cb_->jump_to( t); + return * this; +} + +template< typename T > +push_coroutine< T & >::operator bool() const noexcept { + return nullptr != cb_ && cb_->valid(); +} + +template< typename T > +bool +push_coroutine< T & >::operator!() const noexcept { + return nullptr == cb_ || ! cb_->valid(); +} + + +// push_coroutine< void > + +inline +push_coroutine< void >::push_coroutine( control_block * cb) : + cb_( cb) { +} + +template< typename Fn > +push_coroutine< void >::push_coroutine( Fn && fn, bool preserve_fpu) : + push_coroutine( fixedsize_stack(), std::forward< Fn >( fn), preserve_fpu) { +} + +template< typename StackAllocator, typename Fn > +push_coroutine< void >::push_coroutine( StackAllocator salloc, Fn && fn, bool preserve_fpu) : + cb_( nullptr) { + context::stack_context sctx( salloc.allocate() ); + void * sp = static_cast< char * >( sctx.sp) - sizeof( control_block); + std::size_t size = sctx.size - sizeof( control_block); + cb_ = new ( sp) control_block( context::preallocated( sp, size, sctx), + salloc, rref< Fn >( std::forward< Fn >( fn) ), preserve_fpu); +} + +inline +push_coroutine< void >::~push_coroutine() { + if ( nullptr != cb_) { + cb_->~control_block(); + } +} + +inline +push_coroutine< void >::push_coroutine( push_coroutine && other) : + cb_( nullptr) { + std::swap( cb_, other.cb_); +} + +inline +push_coroutine< void > & +push_coroutine< void >::operator()() { + cb_->jump_to(); + return * this; +} + +inline +push_coroutine< void >::operator bool() const noexcept { + return nullptr != cb_ && cb_->valid(); +} + +inline +bool +push_coroutine< void >::operator!() const noexcept { + return nullptr == cb_ || ! cb_->valid(); +} + +}}} + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_SUFFIX +#endif + +#endif // BOOST_COROUTINES2_DETAIL_PUSH_COROUTINE_IPP diff --git a/include/boost/coroutine2/detail/rref.hpp b/include/boost/coroutine2/detail/rref.hpp new file mode 100644 index 0000000..fd327c3 --- /dev/null +++ b/include/boost/coroutine2/detail/rref.hpp @@ -0,0 +1,57 @@ + +// Copyright Oliver Kowalke 2013. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_COROUTINES2_DETAIL_RREF_H +#define BOOST_COROUTINES2_DETAIL_RREF_H + +#include + +#include + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_PREFIX +#endif + +namespace boost { +namespace coroutines2 { +namespace detail { + +// helper class for capture move-only objects +// generalized lambda captures are supported by C++14 +template< typename Fn > +class rref { +public: + rref( Fn && fn) : + fn_( std::forward< Fn >( fn) ) { + } + + rref( rref & other) : + fn_( std::forward< Fn >( other.fn_) ) { + } + + rref( rref && other) : + fn_( std::forward< Fn >( other.fn_) ) { + } + + rref( rref const& other) = delete; + rref & operator=( rref const& other) = delete; + + template< typename S > + void operator()( S & s) { + return fn_( s); + } + +private: + Fn fn_; +}; + +}}} + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_SUFFIX +#endif + +#endif // BOOST_COROUTINES2_DETAIL_RREF_H diff --git a/include/boost/coroutine2/detail/state.hpp b/include/boost/coroutine2/detail/state.hpp new file mode 100644 index 0000000..714548f --- /dev/null +++ b/include/boost/coroutine2/detail/state.hpp @@ -0,0 +1,37 @@ + +// Copyright Oliver Kowalke 2014. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_COROUTINES2_DETAIL_ASYMMETRIC_COROUTINE_HPP +#define BOOST_COROUTINES2_DETAIL_ASYMMETRIC_COROUTINE_HPP + +#include + +#include +#include + +#include + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_PREFIX +#endif + +namespace boost { +namespace coroutines2 { +namespace detail { + +enum class state_t : unsigned int { + complete = 1 << 1, + unwind = 1 << 2, + early_exit = 1 << 3 +}; + +}}} + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_SUFFIX +#endif + +#endif // BOOST_COROUTINES2_DETAIL_ASYMMETRIC_COROUTINE_HPP diff --git a/include/boost/coroutine2/fixedsize_stack.hpp b/include/boost/coroutine2/fixedsize_stack.hpp new file mode 100644 index 0000000..56523ab --- /dev/null +++ b/include/boost/coroutine2/fixedsize_stack.hpp @@ -0,0 +1,33 @@ + +// Copyright Oliver Kowalke 2014. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_COROUTINES2_FIXEDSIZE_H +#define BOOST_COROUTINES2_FIXEDSIZE_H + +#include + +#include +#include +#include + +#include + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_PREFIX +#endif + +namespace boost { +namespace coroutines2 { + +typedef boost::context::fixedsize_stack fixedsize_stack; + +}} + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_SUFFIX +#endif + +#endif // BOOST_COROUTINES2_FIXEDSIZE_H diff --git a/include/boost/coroutine2/protected_fixedsize_stack.hpp b/include/boost/coroutine2/protected_fixedsize_stack.hpp new file mode 100644 index 0000000..d2ee279 --- /dev/null +++ b/include/boost/coroutine2/protected_fixedsize_stack.hpp @@ -0,0 +1,33 @@ + +// Copyright Oliver Kowalke 2014. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_COROUTINES2_PROTECTED_FIXEDSIZE_H +#define BOOST_COROUTINES2_PROTECTED_FIXEDSIZE_H + +#include + +#include +#include +#include + +#include + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_PREFIX +#endif + +namespace boost { +namespace coroutines2 { + +typedef boost::context::protected_fixedsize_stack protected_fixedsize_stack; + +}} + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_SUFFIX +#endif + +#endif // BOOST_COROUTINES2_PROTECTED_FIXEDSIZE_H diff --git a/include/boost/coroutine2/segmented_stack.hpp b/include/boost/coroutine2/segmented_stack.hpp new file mode 100644 index 0000000..a252ae4 --- /dev/null +++ b/include/boost/coroutine2/segmented_stack.hpp @@ -0,0 +1,37 @@ + +// Copyright Oliver Kowalke 2014. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_COROUTINES2_SEGMENTED_H +#define BOOST_COROUTINES2_SEGMENTED_H + +#include + +#include +#include +#include + +#include + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_PREFIX +#endif + +namespace boost { +namespace coroutines2 { + +#if defined(BOOST_USE_SEGMENTED_STACKS) +# if ! defined(BOOST_WINDOWS) +typedef boost::context::segmented_stack segmented_stack; +# endif +#endif + +}} + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_SUFFIX +#endif + +#endif // BOOST_COROUTINES2_SEGMENTED_H diff --git a/index.html b/index.html new file mode 100644 index 0000000..0ade6cb --- /dev/null +++ b/index.html @@ -0,0 +1,14 @@ + + + + + +Automatic redirection failed, please go to +doc/html/index.html +
+

© Copyright Beman Dawes, 2001

+

Distributed under the Boost Software +License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at +www.boost.org/LICENSE_1_0.txt)

+ + diff --git a/meta/libraries.json b/meta/libraries.json new file mode 100644 index 0000000..053972e --- /dev/null +++ b/meta/libraries.json @@ -0,0 +1,14 @@ +{ + "key": "coroutine", + "name": "Coroutine", + "authors": [ + "Oliver Kowalke" + ], + "description": "Coroutine library.", + "category": [ + "Concurrent" + ], + "maintainers": [ + "Oliver Kowalke " + ] +} diff --git a/performance/Jamfile.v2 b/performance/Jamfile.v2 new file mode 100644 index 0000000..fa38f05 --- /dev/null +++ b/performance/Jamfile.v2 @@ -0,0 +1,77 @@ + +# Copyright Oliver Kowalke 2014. +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +# For more information, see http://www.boost.org/ + +import common ; +import feature ; +import indirect ; +import modules ; +import os ; +import toolset ; + +project boost/coroutine2/performance + : requirements + /boost/chrono//boost_chrono + /boost/context//boost_context + /boost/program_options//boost_program_options + gcc,on:-fsplit-stack + gcc,on:-DBOOST_USE_SEGMENTED_STACKS + clang,on:-fsplit-stack + clang,on:-DBOOST_USE_SEGMENTED_STACKS + static + multi + -DBOOST_DISABLE_ASSERTS + speed + release + ; + +alias sources + : bind_processor_aix.cpp + : aix + ; + +alias sources + : bind_processor_freebsd.cpp + : freebsd + ; + +alias sources + : bind_processor_hpux.cpp + : hpux + ; + +alias sources + : bind_processor_linux.cpp + : linux + ; + +alias sources + : bind_processor_solaris.cpp + : solaris + ; + +alias sources + : bind_processor_windows.cpp + : windows + ; + +explicit sources ; + +exe performance_create_protected + : sources + performance_create_protected.cpp + ; + +exe performance_create_standard + : sources + performance_create_standard.cpp + ; + +exe performance_switch + : sources + performance_switch.cpp + ; diff --git a/performance/bind_processor.hpp b/performance/bind_processor.hpp new file mode 100644 index 0000000..bdde48a --- /dev/null +++ b/performance/bind_processor.hpp @@ -0,0 +1,12 @@ + +// Copyright Oliver Kowalke 2014. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BIND_TO_PROCESSOR_H +#define BIND_TO_PROCESSOR_H + +void bind_to_processor( unsigned int n); + +#endif // BIND_TO_PROCESSOR_H diff --git a/performance/bind_processor_aix.cpp b/performance/bind_processor_aix.cpp new file mode 100644 index 0000000..423fad7 --- /dev/null +++ b/performance/bind_processor_aix.cpp @@ -0,0 +1,25 @@ + +// Copyright Oliver Kowalke 2014. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#include "bind_processor.hpp" + +extern "C" +{ +#include +#include +} + +#include + +#include + +void bind_to_processor( unsigned int n) +{ + if ( ::bindprocessor( BINDTHREAD, ::thread_yield(), static_cast< cpu_t >( n) ) == -1) + throw std::runtime_error("::bindprocessor() failed"); +} + +#include diff --git a/performance/bind_processor_freebsd.cpp b/performance/bind_processor_freebsd.cpp new file mode 100644 index 0000000..3fe8f66 --- /dev/null +++ b/performance/bind_processor_freebsd.cpp @@ -0,0 +1,29 @@ + +// Copyright Oliver Kowalke 2014. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#include "bind_processor.hpp" + +extern "C" +{ +#include +#include +} + +#include + +#include + +void bind_to_processor( unsigned int n) +{ + cpuset_t cpuset; + CPU_ZERO( & cpuset); + CPU_SET( n, & cpuset); + + if ( ::cpuset_setaffinity( CPU_LEVEL_WHICH, CPU_WHICH_TID, -1, sizeof( cpuset), & cpuset) == -1) + throw std::runtime_error("::cpuset_setaffinity() failed"); +} + +#include diff --git a/performance/bind_processor_hpux.cpp b/performance/bind_processor_hpux.cpp new file mode 100644 index 0000000..fe9d052 --- /dev/null +++ b/performance/bind_processor_hpux.cpp @@ -0,0 +1,31 @@ + +// Copyright Oliver Kowalke 2014. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#include "bind_processor.hpp" + +extern "C" +{ +#include +} + +#include + +#include + +void bind_to_processor( unsigned int n) +{ + ::pthread_spu_t spu; + int errno_( + ::pthread_processor_bind_np( + PTHREAD_BIND_FORCED_NP, + & spu, + static_cast< pthread_spu_t >( n), + PTHREAD_SELFTID_NP) ); + if ( errno_ != 0) + throw std::runtime_error("::pthread_processor_bind_np() failed"); +} + +#include diff --git a/performance/bind_processor_linux.cpp b/performance/bind_processor_linux.cpp new file mode 100644 index 0000000..a68ca68 --- /dev/null +++ b/performance/bind_processor_linux.cpp @@ -0,0 +1,30 @@ + +// Copyright Oliver Kowalke 2014. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#include "bind_processor.hpp" + +extern "C" +{ +#include +#include +} + +#include + +#include + +void bind_to_processor( unsigned int n) +{ + cpu_set_t cpuset; + CPU_ZERO( & cpuset); + CPU_SET( n, & cpuset); + + int errno_( ::pthread_setaffinity_np( ::pthread_self(), sizeof( cpuset), & cpuset) ); + if ( errno_ != 0) + throw std::runtime_error("::pthread_setaffinity_np() failed"); +} + +#include diff --git a/performance/bind_processor_solaris.cpp b/performance/bind_processor_solaris.cpp new file mode 100644 index 0000000..3724a3c --- /dev/null +++ b/performance/bind_processor_solaris.cpp @@ -0,0 +1,26 @@ + +// Copyright Oliver Kowalke 2014. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#include "bind_processor.hpp" + +extern "C" +{ +#include +#include +#include +} + +#include + +#include + +void bind_to_processor( unsigned int n) +{ + if ( ::processor_bind( P_LWPID, P_MYID, static_cast< processorid_t >( n), 0) == -1) + throw std::runtime_error("::processor_bind() failed"); +} + +#include diff --git a/performance/bind_processor_windows.cpp b/performance/bind_processor_windows.cpp new file mode 100644 index 0000000..7e5b930 --- /dev/null +++ b/performance/bind_processor_windows.cpp @@ -0,0 +1,24 @@ + +// Copyright Oliver Kowalke 2014. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#include "bind_processor.hpp" + +extern "C" +{ +#include +} + +#include + +#include + +void bind_to_processor( unsigned int n) +{ + if ( ::SetThreadAffinityMask( ::GetCurrentThread(), ( DWORD_PTR)1 << n) == 0) + throw std::runtime_error("::SetThreadAffinityMask() failed"); +} + +#include diff --git a/performance/clock.hpp b/performance/clock.hpp new file mode 100644 index 0000000..b897bb5 --- /dev/null +++ b/performance/clock.hpp @@ -0,0 +1,45 @@ + +// Copyright Oliver Kowalke 2014. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +#ifndef CLOCK_H +#define CLOCK_H + +#include +#include +#include +#include + +#include +#include +#include + +typedef boost::chrono::high_resolution_clock clock_type; +typedef clock_type::duration duration_type; +typedef clock_type::time_point time_point_type; + +struct clock_overhead +{ + boost::uint64_t operator()() + { + time_point_type start( clock_type::now() ); + return ( clock_type::now() - start).count(); + } +}; + +inline +duration_type overhead_clock() +{ + std::size_t iterations( 10); + std::vector< boost::uint64_t > overhead( iterations, 0); + for ( std::size_t i = 0; i < iterations; ++i) + std::generate( + overhead.begin(), overhead.end(), + clock_overhead() ); + BOOST_ASSERT( overhead.begin() != overhead.end() ); + return duration_type( std::accumulate( overhead.begin(), overhead.end(), 0) / iterations); +} + +#endif // CLOCK_H diff --git a/performance/cycle.hpp b/performance/cycle.hpp new file mode 100644 index 0000000..74d59dd --- /dev/null +++ b/performance/cycle.hpp @@ -0,0 +1,26 @@ + +// Copyright Oliver Kowalke 2014. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef CYCLE_H +#define CYCLE_H + +// x86_64 +// test x86_64 before i386 because icc might +// define __i686__ for x86_64 too +#if defined(__x86_64__) || defined(__x86_64) \ + || defined(__amd64__) || defined(__amd64) \ + || defined(_M_X64) || defined(_M_AMD64) +# include "cycle_x86-64.hpp" +// i386 +#elif defined(i386) || defined(__i386__) || defined(__i386) \ + || defined(__i486__) || defined(__i586__) || defined(__i686__) \ + || defined(__X86__) || defined(_X86_) || defined(__THW_INTEL__) \ + || defined(__I86__) || defined(__INTEL__) || defined(__IA32__) \ + || defined(_M_IX86) || defined(_I86_) +# include "cycle_i386.hpp" +#endif + +#endif // CYCLE_H diff --git a/performance/cycle_i386.hpp b/performance/cycle_i386.hpp new file mode 100644 index 0000000..0ea2cce --- /dev/null +++ b/performance/cycle_i386.hpp @@ -0,0 +1,83 @@ + +// Copyright Oliver Kowalke 2014. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef CYCLE_I386_H +#define CYCLE_I386_H + +#include +#include +#include +#include + +#include +#include +#include + +#define BOOST_CONTEXT_CYCLE + +typedef boost::uint64_t cycle_type; + +#if _MSC_VER +inline +cycle_type cycles() +{ + cycle_type c; + __asm { + cpuid + rdtsc + mov dword ptr [c + 0], eax + mov dword ptr [c + 4], edx + } + return c; +} +#elif defined(__GNUC__) || \ + defined(__INTEL_COMPILER) || defined(__ICC) || defined(_ECC) || defined(__ICL) +inline +cycle_type cycles() +{ + boost::uint32_t lo, hi; + + __asm__ __volatile__ ( + "xorl %%eax, %%eax\n" + "cpuid\n" + ::: "%eax", "%ebx", "%ecx", "%edx" + ); + __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi) ); + __asm__ __volatile__ ( + "xorl %%eax, %%eax\n" + "cpuid\n" + ::: "%eax", "%ebx", "%ecx", "%edx" + ); + + return ( cycle_type)hi << 32 | lo; +} +#else +# error "this compiler is not supported" +#endif + +struct cycle_overhead +{ + cycle_type operator()() + { + cycle_type start( cycles() ); + return cycles() - start; + } +}; + +inline +cycle_type overhead_cycle() +{ + std::size_t iterations( 10); + std::vector< cycle_type > overhead( iterations, 0); + for ( std::size_t i( 0); i < iterations; ++i) + std::generate( + overhead.begin(), overhead.end(), + cycle_overhead() ); + BOOST_ASSERT( overhead.begin() != overhead.end() ); + return std::accumulate( overhead.begin(), overhead.end(), 0) / iterations; +} + +#endif // CYCLE_I386_H diff --git a/performance/cycle_x86-64.hpp b/performance/cycle_x86-64.hpp new file mode 100644 index 0000000..6250285 --- /dev/null +++ b/performance/cycle_x86-64.hpp @@ -0,0 +1,79 @@ + +// Copyright Oliver Kowalke 2014. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef CYCLE_X86_64_H +#define CYCLE_X86_64_H + +#include +#include +#include +#include + +#include +#include +#include + +#define BOOST_CONTEXT_CYCLE + +typedef boost::uint64_t cycle_type; + +#if _MSC_VER >= 1400 +# include +# pragma intrinsic(__rdtsc) +inline +cycle_type cycles() +{ return __rdtsc(); } +#elif defined(__INTEL_COMPILER) || defined(__ICC) || defined(_ECC) || defined(__ICL) +inline +cycle_type cycles() +{ return __rdtsc(); } +#elif defined(__GNUC__) || defined(__SUNPRO_C) +inline +cycle_type cycles() +{ + boost::uint32_t lo, hi; + + __asm__ __volatile__ ( + "xorl %%eax, %%eax\n" + "cpuid\n" + ::: "%rax", "%rbx", "%rcx", "%rdx" + ); + __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi) ); + __asm__ __volatile__ ( + "xorl %%eax, %%eax\n" + "cpuid\n" + ::: "%rax", "%rbx", "%rcx", "%rdx" + ); + + return ( cycle_type)hi << 32 | lo; +} +#else +# error "this compiler is not supported" +#endif + +struct cycle_overhead +{ + cycle_type operator()() + { + cycle_type start( cycles() ); + return cycles() - start; + } +}; + +inline +cycle_type overhead_cycle() +{ + std::size_t iterations( 10); + std::vector< cycle_type > overhead( iterations, 0); + for ( std::size_t i( 0); i < iterations; ++i) + std::generate( + overhead.begin(), overhead.end(), + cycle_overhead() ); + BOOST_ASSERT( overhead.begin() != overhead.end() ); + return std::accumulate( overhead.begin(), overhead.end(), 0) / iterations; +} + +#endif // CYCLE_X86_64_H diff --git a/performance/performance_create_protected.cpp b/performance/performance_create_protected.cpp new file mode 100644 index 0000000..3262e1f --- /dev/null +++ b/performance/performance_create_protected.cpp @@ -0,0 +1,107 @@ + +// Copyright Oliver Kowalke 2014. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include + +#include +#include +#include +#include + +#include "bind_processor.hpp" +#include "clock.hpp" +#include "cycle.hpp" + +typedef boost::coroutines2::protected_fixedsize_stack stack_allocator; +typedef boost::coroutines2::coroutine< void > coro_type; + +bool preserve = false; +boost::uint64_t jobs = 1000; + +void fn( coro_type::pull_type & c) +{ while ( true) c(); } + +duration_type measure_time( duration_type overhead) +{ + stack_allocator stack_alloc; + + time_point_type start( clock_type::now() ); + for ( std::size_t i = 0; i < jobs; ++i) { + coro_type::push_type c( stack_alloc, fn, preserve); + } + duration_type total = clock_type::now() - start; + total -= overhead_clock(); // overhead of measurement + total /= jobs; // loops + + return total; +} + +# ifdef BOOST_CONTEXT_CYCLE +cycle_type measure_cycles( cycle_type overhead) +{ + stack_allocator stack_alloc; + + cycle_type start( cycles() ); + for ( std::size_t i = 0; i < jobs; ++i) { + coro_type::push_type c( stack_alloc, fn, preserve); + } + cycle_type total = cycles() - start; + total -= overhead; // overhead of measurement + total /= jobs; // loops + + return total; +} +# endif + +int main( int argc, char * argv[]) +{ + try + { + bool bind = false; + boost::program_options::options_description desc("allowed options"); + desc.add_options() + ("help", "help message") + ("bind,b", boost::program_options::value< bool >( & bind), "bind thread to CPU") + ("fpu,f", boost::program_options::value< bool >( & preserve), "preserve FPU registers") + ("jobs,j", boost::program_options::value< boost::uint64_t >( & jobs), "jobs to run"); + + boost::program_options::variables_map vm; + boost::program_options::store( + boost::program_options::parse_command_line( + argc, + argv, + desc), + vm); + boost::program_options::notify( vm); + + if ( vm.count("help") ) { + std::cout << desc << std::endl; + return EXIT_SUCCESS; + } + + if ( bind) bind_to_processor( 0); + + duration_type overhead_c = overhead_clock(); + std::cout << "overhead " << overhead_c.count() << " nano seconds" << std::endl; + boost::uint64_t res = measure_time( overhead_c).count(); + std::cout << "average of " << res << " nano seconds" << std::endl; +#ifdef BOOST_CONTEXT_CYCLE + cycle_type overhead_y = overhead_cycle(); + std::cout << "overhead " << overhead_y << " cpu cycles" << std::endl; + res = measure_cycles( overhead_y); + std::cout << "average of " << res << " cpu cycles" << std::endl; +#endif + + return EXIT_SUCCESS; + } + catch ( std::exception const& e) + { std::cerr << "exception: " << e.what() << std::endl; } + catch (...) + { std::cerr << "unhandled exception" << std::endl; } + return EXIT_FAILURE; +} diff --git a/performance/performance_create_standard.cpp b/performance/performance_create_standard.cpp new file mode 100644 index 0000000..58c1ce4 --- /dev/null +++ b/performance/performance_create_standard.cpp @@ -0,0 +1,107 @@ + +// Copyright Oliver Kowalke 2014. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include + +#include +#include +#include +#include + +#include "bind_processor.hpp" +#include "clock.hpp" +#include "cycle.hpp" + +typedef boost::coroutines2::fixedsize_stack stack_allocator; +typedef boost::coroutines2::coroutine< void > coro_type; + +bool preserve = false; +boost::uint64_t jobs = 1000; + +void fn( coro_type::pull_type & c) +{ while ( true) c(); } + +duration_type measure_time( duration_type overhead) +{ + stack_allocator stack_alloc; + + time_point_type start( clock_type::now() ); + for ( std::size_t i = 0; i < jobs; ++i) { + coro_type::push_type c( stack_alloc, fn, preserve); + } + duration_type total = clock_type::now() - start; + total -= overhead_clock(); // overhead of measurement + total /= jobs; // loops + + return total; +} + +# ifdef BOOST_CONTEXT_CYCLE +cycle_type measure_cycles( cycle_type overhead) +{ + stack_allocator stack_alloc; + + cycle_type start( cycles() ); + for ( std::size_t i = 0; i < jobs; ++i) { + coro_type::push_type c( stack_alloc, fn, preserve); + } + cycle_type total = cycles() - start; + total -= overhead; // overhead of measurement + total /= jobs; // loops + + return total; +} +# endif + +int main( int argc, char * argv[]) +{ + try + { + bool bind = false; + boost::program_options::options_description desc("allowed options"); + desc.add_options() + ("help", "help message") + ("bind,b", boost::program_options::value< bool >( & bind), "bind thread to CPU") + ("fpu,f", boost::program_options::value< bool >( & preserve), "preserve FPU registers") + ("jobs,j", boost::program_options::value< boost::uint64_t >( & jobs), "jobs to run"); + + boost::program_options::variables_map vm; + boost::program_options::store( + boost::program_options::parse_command_line( + argc, + argv, + desc), + vm); + boost::program_options::notify( vm); + + if ( vm.count("help") ) { + std::cout << desc << std::endl; + return EXIT_SUCCESS; + } + + if ( bind) bind_to_processor( 0); + + duration_type overhead_c = overhead_clock(); + std::cout << "overhead " << overhead_c.count() << " nano seconds" << std::endl; + boost::uint64_t res = measure_time( overhead_c).count(); + std::cout << "average of " << res << " nano seconds" << std::endl; +#ifdef BOOST_CONTEXT_CYCLE + cycle_type overhead_y = overhead_cycle(); + std::cout << "overhead " << overhead_y << " cpu cycles" << std::endl; + res = measure_cycles( overhead_y); + std::cout << "average of " << res << " cpu cycles" << std::endl; +#endif + + return EXIT_SUCCESS; + } + catch ( std::exception const& e) + { std::cerr << "exception: " << e.what() << std::endl; } + catch (...) + { std::cerr << "unhandled exception" << std::endl; } + return EXIT_FAILURE; +} diff --git a/performance/performance_switch.cpp b/performance/performance_switch.cpp new file mode 100644 index 0000000..41b6ab0 --- /dev/null +++ b/performance/performance_switch.cpp @@ -0,0 +1,198 @@ + +// Copyright Oliver Kowalke 2014. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "bind_processor.hpp" +#include "clock.hpp" +#include "cycle.hpp" + +bool preserve = false; +boost::uint64_t jobs = 1000; + +struct X +{ + std::string str; + + X( std::string const& str_) : + str( str_) + {} +}; + +const X x("abc"); + +void fn_void( boost::coroutines2::coroutine< void >::push_type & c) +{ while ( true) c(); } + +void fn_int( boost::coroutines2::coroutine< int >::push_type & c) +{ while ( true) c( 7); } + +void fn_x( boost::coroutines2::coroutine< X >::push_type & c) +{ + while ( true) c( x); +} + +duration_type measure_time_void( duration_type overhead) +{ + boost::coroutines2::coroutine< void >::pull_type c( fn_void, preserve); + + time_point_type start( clock_type::now() ); + for ( std::size_t i = 0; i < jobs; ++i) { + c(); + } + duration_type total = clock_type::now() - start; + total -= overhead_clock(); // overhead of measurement + total /= jobs; // loops + total /= 2; // 2x jump_fcontext + + return total; +} + +duration_type measure_time_int( duration_type overhead) +{ + boost::coroutines2::coroutine< int >::pull_type c( fn_int, preserve); + + time_point_type start( clock_type::now() ); + for ( std::size_t i = 0; i < jobs; ++i) { + c(); + } + duration_type total = clock_type::now() - start; + total -= overhead_clock(); // overhead of measurement + total /= jobs; // loops + total /= 2; // 2x jump_fcontext + + return total; +} + +duration_type measure_time_x( duration_type overhead) +{ + boost::coroutines2::coroutine< X >::pull_type c( fn_x, preserve); + + time_point_type start( clock_type::now() ); + for ( std::size_t i = 0; i < jobs; ++i) { + c(); + } + duration_type total = clock_type::now() - start; + total -= overhead_clock(); // overhead of measurement + total /= jobs; // loops + total /= 2; // 2x jump_fcontext + + return total; +} + +# ifdef BOOST_CONTEXT_CYCLE +cycle_type measure_cycles_void( cycle_type overhead) +{ + boost::coroutines2::coroutine< void >::pull_type c( fn_void, preserve); + + cycle_type start( cycles() ); + for ( std::size_t i = 0; i < jobs; ++i) { + c(); + } + cycle_type total = cycles() - start; + total -= overhead; // overhead of measurement + total /= jobs; // loops + total /= 2; // 2x jump_fcontext + + return total; +} + +cycle_type measure_cycles_int( cycle_type overhead) +{ + boost::coroutines2::coroutine< int >::pull_type c( fn_int, preserve); + + cycle_type start( cycles() ); + for ( std::size_t i = 0; i < jobs; ++i) { + c(); + } + cycle_type total = cycles() - start; + total -= overhead; // overhead of measurement + total /= jobs; // loops + total /= 2; // 2x jump_fcontext + + return total; +} + +cycle_type measure_cycles_x( cycle_type overhead) +{ + boost::coroutines2::coroutine< X >::pull_type c( fn_x, preserve); + + cycle_type start( cycles() ); + for ( std::size_t i = 0; i < jobs; ++i) { + c(); + } + cycle_type total = cycles() - start; + total -= overhead; // overhead of measurement + total /= jobs; // loops + total /= 2; // 2x jump_fcontext + + return total; +} +# endif + +int main( int argc, char * argv[]) +{ + try + { + bool bind = false; + boost::program_options::options_description desc("allowed options"); + desc.add_options() + ("help", "help message") + ("bind,b", boost::program_options::value< bool >( & bind), "bind thread to CPU") + ("fpu,f", boost::program_options::value< bool >( & preserve), "preserve FPU registers") + ("jobs,j", boost::program_options::value< boost::uint64_t >( & jobs), "jobs to run"); + + boost::program_options::variables_map vm; + boost::program_options::store( + boost::program_options::parse_command_line( + argc, + argv, + desc), + vm); + boost::program_options::notify( vm); + + if ( vm.count("help") ) { + std::cout << desc << std::endl; + return EXIT_SUCCESS; + } + + if ( bind) bind_to_processor( 0); + + duration_type overhead_c = overhead_clock(); + std::cout << "overhead " << overhead_c.count() << " nano seconds" << std::endl; + boost::uint64_t res = measure_time_void( overhead_c).count(); + std::cout << "void: average of " << res << " nano seconds" << std::endl; + res = measure_time_int( overhead_c).count(); + std::cout << "int: average of " << res << " nano seconds" << std::endl; + res = measure_time_x( overhead_c).count(); + std::cout << "X: average of " << res << " nano seconds" << std::endl; +#ifdef BOOST_CONTEXT_CYCLE + cycle_type overhead_y = overhead_cycle(); + std::cout << "overhead " << overhead_y << " cpu cycles" << std::endl; + res = measure_cycles_void( overhead_y); + std::cout << "void: average of " << res << " cpu cycles" << std::endl; + res = measure_cycles_int( overhead_y); + std::cout << "int: average of " << res << " cpu cycles" << std::endl; + res = measure_cycles_x( overhead_y); + std::cout << "X: average of " << res << " cpu cycles" << std::endl; +#endif + + return EXIT_SUCCESS; + } + catch ( std::exception const& e) + { std::cerr << "exception: " << e.what() << std::endl; } + catch (...) + { std::cerr << "unhandled exception" << std::endl; } + return EXIT_FAILURE; +} diff --git a/performance/segmented/Jamfile.v2 b/performance/segmented/Jamfile.v2 new file mode 100644 index 0000000..d459354 --- /dev/null +++ b/performance/segmented/Jamfile.v2 @@ -0,0 +1,73 @@ + +# Copyright Oliver Kowalke 2014. +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +# For more information, see http://www.boost.org/ + +import common ; +import feature ; +import indirect ; +import modules ; +import os ; +import toolset ; + +project boost/coroutine2/performance/segmented_stack + : requirements + /boost/chrono//boost_chrono + /boost/coroutine//boost_coroutine + /boost/program_options//boost_program_options + on + gcc,on:-fsplit-stack + gcc,on:-DBOOST_USE_SEGMENTED_STACKS + clang,on:-fsplit-stack + clang,on:-DBOOST_USE_SEGMENTED_STACKS + static + multi + -DBOOST_DISABLE_ASSERTS + speed + release + ; + +alias sources + : ../bind_processor_aix.cpp + : aix + ; + +alias sources + : ../bind_processor_freebsd.cpp + : freebsd + ; + +alias sources + : ../bind_processor_hpux.cpp + : hpux + ; + +alias sources + : ../bind_processor_linux.cpp + : linux + ; + +alias sources + : ../bind_processor_solaris.cpp + : solaris + ; + +alias sources + : ../bind_processor_windows.cpp + : windows + ; + +explicit sources ; + +exe performance_create_segmented + : sources + performance_create_segmented.cpp + ; + +exe performance_switch + : sources + performance_switch.cpp + ; diff --git a/performance/segmented/performance_create_segmented.cpp b/performance/segmented/performance_create_segmented.cpp new file mode 100644 index 0000000..f0d1e30 --- /dev/null +++ b/performance/segmented/performance_create_segmented.cpp @@ -0,0 +1,107 @@ + +// Copyright Oliver Kowalke 2014. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include + +#include +#include +#include +#include + +#include "../bind_processor.hpp" +#include "../clock.hpp" +#include "../cycle.hpp" + +typedef boost::coroutines2::segmented_stack stack_allocator; +typedef boost::coroutines2::coroutine< void > coro_type; + +bool preserve = false; +boost::uint64_t jobs = 1000; + +void fn( coro_type::push_type & c) +{ while ( true) c(); } + +duration_type measure_time( duration_type overhead) +{ + stack_allocator stack_alloc; + + time_point_type start( clock_type::now() ); + for ( std::size_t i = 0; i < jobs; ++i) { + coro_type::pull_type c( stack_alloc, fn, preserve); + } + duration_type total = clock_type::now() - start; + total -= overhead_clock(); // overhead of measurement + total /= jobs; // loops + + return total; +} + +# ifdef BOOST_CONTEXT_CYCLE +cycle_type measure_cycles( cycle_type overhead) +{ + stack_allocator stack_alloc; + + cycle_type start( cycles() ); + for ( std::size_t i = 0; i < jobs; ++i) { + coro_type::pull_type c( stack_alloc, fn, preserve); + } + cycle_type total = cycles() - start; + total -= overhead; // overhead of measurement + total /= jobs; // loops + + return total; +} +# endif + +int main( int argc, char * argv[]) +{ + try + { + bool preserve = false, unwind = true, bind = false; + boost::program_options::options_description desc("allowed options"); + desc.add_options() + ("help", "help message") + ("bind,b", boost::program_options::value< bool >( & bind), "bind thread to CPU") + ("fpu,f", boost::program_options::value< bool >( & preserve), "preserve FPU registers") + ("jobs,j", boost::program_options::value< boost::uint64_t >( & jobs), "jobs to run"); + + boost::program_options::variables_map vm; + boost::program_options::store( + boost::program_options::parse_command_line( + argc, + argv, + desc), + vm); + boost::program_options::notify( vm); + + if ( vm.count("help") ) { + std::cout << desc << std::endl; + return EXIT_SUCCESS; + } + + if ( bind) bind_to_processor( 0); + + duration_type overhead_c = overhead_clock(); + std::cout << "overhead " << overhead_c.count() << " nano seconds" << std::endl; + boost::uint64_t res = measure_time( overhead_c).count(); + std::cout << "average of " << res << " nano seconds" << std::endl; +#ifdef BOOST_CONTEXT_CYCLE + cycle_type overhead_y = overhead_cycle(); + std::cout << "overhead " << overhead_y << " cpu cycles" << std::endl; + res = measure_cycles( overhead_y); + std::cout << "average of " << res << " cpu cycles" << std::endl; +#endif + + return EXIT_SUCCESS; + } + catch ( std::exception const& e) + { std::cerr << "exception: " << e.what() << std::endl; } + catch (...) + { std::cerr << "unhandled exception" << std::endl; } + return EXIT_FAILURE; +} diff --git a/performance/segmented/performance_switch.cpp b/performance/segmented/performance_switch.cpp new file mode 100644 index 0000000..828c004 --- /dev/null +++ b/performance/segmented/performance_switch.cpp @@ -0,0 +1,204 @@ + +// Copyright Oliver Kowalke 2014. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "../bind_processor.hpp" +#include "../clock.hpp" +#include "../cycle.hpp" + +bool preserve = false; +boost::uint64_t jobs = 1000; + +struct X +{ + std::string str; + + X( std::string const& str_) : + str( str_) + {} +}; + +const X x("abc"); + +void fn_void( boost::coroutines2::coroutine< void >::push_type & c) +{ while ( true) c(); } + +void fn_int( boost::coroutines2::coroutine< int >::push_type & c) +{ while ( true) c( 7); } + +void fn_x( boost::coroutines2::coroutine< X >::push_type & c) +{ + while ( true) c( x); +} + +duration_type measure_time_void( duration_type overhead) +{ + boost::coroutines2::segmented_stack stack_alloc; + boost::coroutines2::coroutine< void >::pull_type c( stack_alloc, fn_void, preserve); + + time_point_type start( clock_type::now() ); + for ( std::size_t i = 0; i < jobs; ++i) { + c(); + } + duration_type total = clock_type::now() - start; + total -= overhead_clock(); // overhead of measurement + total /= jobs; // loops + total /= 2; // 2x jump_fcontext + + return total; +} + +duration_type measure_time_int( duration_type overhead) +{ + boost::coroutines2::segmented_stack stack_alloc; + boost::coroutines2::coroutine< int >::pull_type c( stack_alloc, fn_int, preserve); + + time_point_type start( clock_type::now() ); + for ( std::size_t i = 0; i < jobs; ++i) { + c(); + } + duration_type total = clock_type::now() - start; + total -= overhead_clock(); // overhead of measurement + total /= jobs; // loops + total /= 2; // 2x jump_fcontext + + return total; +} + +duration_type measure_time_x( duration_type overhead) +{ + boost::coroutines2::segmented_stack stack_alloc; + boost::coroutines2::coroutine< X >::pull_type c( stack_alloc, fn_x, preserve); + + time_point_type start( clock_type::now() ); + for ( std::size_t i = 0; i < jobs; ++i) { + c(); + } + duration_type total = clock_type::now() - start; + total -= overhead_clock(); // overhead of measurement + total /= jobs; // loops + total /= 2; // 2x jump_fcontext + + return total; +} + +# ifdef BOOST_CONTEXT_CYCLE +cycle_type measure_cycles_void( cycle_type overhead) +{ + boost::coroutines2::segmented_stack stack_alloc; + boost::coroutines2::coroutine< void >::pull_type c( stack_alloc, fn_void, preserve); + + cycle_type start( cycles() ); + for ( std::size_t i = 0; i < jobs; ++i) { + c(); + } + cycle_type total = cycles() - start; + total -= overhead; // overhead of measurement + total /= jobs; // loops + total /= 2; // 2x jump_fcontext + + return total; +} + +cycle_type measure_cycles_int( cycle_type overhead) +{ + boost::coroutines2::segmented_stack stack_alloc; + boost::coroutines2::coroutine< int >::pull_type c( stack_alloc, fn_int, preserve); + + cycle_type start( cycles() ); + for ( std::size_t i = 0; i < jobs; ++i) { + c(); + } + cycle_type total = cycles() - start; + total -= overhead; // overhead of measurement + total /= jobs; // loops + total /= 2; // 2x jump_fcontext + + return total; +} + +cycle_type measure_cycles_x( cycle_type overhead) +{ + boost::coroutines2::segmented_stack stack_alloc; + boost::coroutines2::coroutine< X >::pull_type c( stack_alloc, fn_x, preserve); + + cycle_type start( cycles() ); + for ( std::size_t i = 0; i < jobs; ++i) { + c(); + } + cycle_type total = cycles() - start; + total -= overhead; // overhead of measurement + total /= jobs; // loops + total /= 2; // 2x jump_fcontext + + return total; +} +# endif + +int main( int argc, char * argv[]) +{ + try + { + bool bind = false; + boost::program_options::options_description desc("allowed options"); + desc.add_options() + ("help", "help message") + ("bind,b", boost::program_options::value< bool >( & bind), "bind thread to CPU") + ("fpu,f", boost::program_options::value< bool >( & preserve), "preserve FPU registers") + ("jobs,j", boost::program_options::value< boost::uint64_t >( & jobs), "jobs to run"); + + boost::program_options::variables_map vm; + boost::program_options::store( + boost::program_options::parse_command_line( + argc, + argv, + desc), + vm); + boost::program_options::notify( vm); + + if ( vm.count("help") ) { + std::cout << desc << std::endl; + return EXIT_SUCCESS; + } + + if ( bind) bind_to_processor( 0); + + duration_type overhead_c = overhead_clock(); + std::cout << "overhead " << overhead_c.count() << " nano seconds" << std::endl; + boost::uint64_t res = measure_time_void( overhead_c).count(); + std::cout << "void: average of " << res << " nano seconds" << std::endl; + res = measure_time_int( overhead_c).count(); + std::cout << "int: average of " << res << " nano seconds" << std::endl; + res = measure_time_x( overhead_c).count(); + std::cout << "X: average of " << res << " nano seconds" << std::endl; +#ifdef BOOST_CONTEXT_CYCLE + cycle_type overhead_y = overhead_cycle(); + std::cout << "overhead " << overhead_y << " cpu cycles" << std::endl; + res = measure_cycles_void( overhead_y); + std::cout << "void: average of " << res << " cpu cycles" << std::endl; + res = measure_cycles_int( overhead_y); + std::cout << "int: average of " << res << " cpu cycles" << std::endl; + res = measure_cycles_x( overhead_y); + std::cout << "X: average of " << res << " cpu cycles" << std::endl; +#endif + + return EXIT_SUCCESS; + } + catch ( std::exception const& e) + { std::cerr << "exception: " << e.what() << std::endl; } + catch (...) + { std::cerr << "unhandled exception" << std::endl; } + return EXIT_FAILURE; +} diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 new file mode 100644 index 0000000..aea762d --- /dev/null +++ b/test/Jamfile.v2 @@ -0,0 +1,29 @@ + +# Copyright Oliver Kowalke 2014. +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +import common ; +import feature ; +import indirect ; +import modules ; +import os ; +import testing ; +import toolset ; + +project boost/coroutine2/test + : requirements + ../../test/build//boost_unit_test_framework + /boost/context//boost_context + gcc,on:-fsplit-stack + gcc,on:-DBOOST_USE_SEGMENTED_STACKS + clang,on:-fsplit-stack + clang,on:-DBOOST_USE_SEGMENTED_STACKS + static + multi + ; + +test-suite "coroutine2" : + [ run test_coroutine.cpp ] + ; diff --git a/test/test_coroutine.cpp b/test/test_coroutine.cpp new file mode 100644 index 0000000..ea41548 --- /dev/null +++ b/test/test_coroutine.cpp @@ -0,0 +1,599 @@ + +// Copyright Oliver Kowalke 2014. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +namespace coro = boost::coroutines2; + +int value1 = 0; +std::string value2 = ""; +bool value3 = false; +double value4 = .0; +int * value5 = 0; +int& value6 = value1; +int& value7 = value1; +int value8 = 0; +int value9 = 0; + +struct X : private boost::noncopyable +{ + X() { value1 = 7; } + ~X() { value1 = 0; } +}; + +class copyable +{ +public: + bool state; + + copyable() : + state( false) + {} + + copyable( int) : + state( true) + {} + + void operator()( coro::coroutine< int >::push_type &) + { value3 = state; } +}; + +class moveable +{ +public: + bool state; + + moveable() : + state( false) + {} + + moveable( int) : + state( true) + {} + + moveable( moveable const&) = delete; + moveable & operator=( moveable const&) = delete; + + moveable( moveable && other) : + state( false) + { std::swap( state, other.state); } + + moveable & operator=( moveable && other) + { + if ( this != & other) { + state = other.state; + other.state = false; + } + return * this; + } + + void operator()( coro::coroutine< int >::push_type &) + { value3 = state; } +}; + +class movedata +{ +public: + int i; + + movedata( int i_) : + i( i_) + {} + + movedata( movedata const&) = delete; + movedata & operator=( movedata const&) = delete; + + movedata( movedata && other) : + i( 0) + { std::swap( i, other.i); } + + movedata & operator=( movedata && other) + { + if ( this != & other) { + i = other.i; + other.i = 0; + } + return * this; + } +}; + +struct my_exception {}; + +void f1( coro::coroutine< void >::push_type & c) +{ + while ( c) + c(); +} + +void f2( coro::coroutine< void >::push_type &) +{ ++value1; } + +void f3( coro::coroutine< void >::push_type & c) +{ + ++value1; + c(); + ++value1; +} + +void f4( coro::coroutine< int >::push_type & c) +{ + c( 3); + c( 7); +} + +void f5( coro::coroutine< std::string >::push_type & c) +{ + std::string res("abc"); + c( res); + res = "xyz"; + c( res); +} + +void f6( coro::coroutine< int >::pull_type & c) +{ value1 = c.get(); } + +void f7( coro::coroutine< std::string >::pull_type & c) +{ value2 = c.get(); } + +void f8( coro::coroutine< std::tuple< double, double > >::pull_type & c) +{ + double x = 0, y = 0; + std::tie( x, y) = c.get(); + value4 = x + y; + c(); + std::tie( x, y) = c.get(); + value4 = x + y; +} + +void f9( coro::coroutine< int * >::pull_type & c) +{ value5 = c.get(); } + +void f91( coro::coroutine< int const* >::pull_type & c) +{ value5 = const_cast< int * >( c.get() ); } + +void f10( coro::coroutine< int & >::pull_type & c) +{ + int const& i = c.get(); + value5 = const_cast< int * >( & i); +} + +void f101( coro::coroutine< int const& >::pull_type & c) +{ + int const& i = c.get(); + value5 = const_cast< int * >( & i); +} + +void f11( coro::coroutine< std::tuple< int, int > >::pull_type & c) +{ + std::tie( value8, value9) = c.get(); +} + +void f12( coro::coroutine< void >::pull_type & c) +{ + X x_; + c(); + c(); +} + +template< typename E > +void f14( coro::coroutine< void >::pull_type &, E const& e) +{ throw e; } + +void f16( coro::coroutine< int >::push_type & c) +{ + c( 1); + c( 2); + c( 3); + c( 4); + c( 5); +} + +void f17( coro::coroutine< int >::pull_type & c, std::vector< int > & vec) +{ + int x = c.get(); + while ( 5 > x) + { + vec.push_back( x); + x = c().get(); + } +} + +void f19( coro::coroutine< int& >::push_type & c, int & i1, int & i2, int & i3) +{ + c( i1); + c( i2); + c( i3); +} + +void f20( coro::coroutine< int >::push_type &) +{} + +void f21( coro::coroutine< int >::pull_type & c) +{ + while ( c) + { + value1 = c.get(); + c(); + } +} + +void f22( coro::coroutine< movedata >::pull_type & c) +{ + movedata mv( c.get() ); + value1 = mv.i; +} + +void test_move() +{ + { + coro::coroutine< int >::pull_type coro1( f20); + coro::coroutine< int >::pull_type coro2( f16); + BOOST_CHECK( ! coro1); + BOOST_CHECK( coro2); + BOOST_CHECK_EQUAL( 1, coro2.get() ); + coro2(); + BOOST_CHECK_EQUAL( 2, coro2.get() ); + coro1 = std::move( coro2); + BOOST_CHECK( coro1); + BOOST_CHECK( ! coro2); + coro1(); + BOOST_CHECK_EQUAL( 3, coro1.get() ); + BOOST_CHECK( ! coro2); + } + + { + value3 = false; + copyable cp( 3); + BOOST_CHECK( cp.state); + BOOST_CHECK( ! value3); + coro::coroutine< int >::pull_type coro( cp); + BOOST_CHECK( cp.state); + BOOST_CHECK( value3); + } + + { + value3 = false; + moveable mv( 7); + BOOST_CHECK( mv.state); + BOOST_CHECK( ! value3); + coro::coroutine< int >::pull_type coro( std::move( mv) ); + BOOST_CHECK( ! mv.state); + BOOST_CHECK( value3); + } + + { + value1 = 0; + movedata mv( 7); + BOOST_CHECK_EQUAL( 0, value1); + BOOST_CHECK_EQUAL( 7, mv.i); + coro::coroutine< movedata >::push_type coro( f22); + coro( std::move( mv) ); + BOOST_CHECK_EQUAL( 7, value1); + BOOST_CHECK_EQUAL( 0, mv.i); + } +} + +void test_complete() +{ + value1 = 0; + + coro::coroutine< void >::pull_type coro( f2); + BOOST_CHECK( ! coro); + BOOST_CHECK_EQUAL( ( int)1, value1); +} + +void test_jump() +{ + value1 = 0; + + coro::coroutine< void >::pull_type coro( f3); + BOOST_CHECK( coro); + BOOST_CHECK_EQUAL( ( int)1, value1); + coro(); + BOOST_CHECK( ! coro); + BOOST_CHECK_EQUAL( ( int)2, value1); +} + +void test_result_int() +{ + coro::coroutine< int >::pull_type coro( f4); + BOOST_CHECK( coro); + int result = coro.get(); + BOOST_CHECK( coro); + BOOST_CHECK_EQUAL( 3, result); + result = coro().get(); + BOOST_CHECK( coro); + BOOST_CHECK_EQUAL( 7, result); + coro(); + BOOST_CHECK( ! coro); +} + +void test_result_string() +{ + coro::coroutine< std::string >::pull_type coro( f5); + BOOST_CHECK( coro); + std::string result = coro.get(); + BOOST_CHECK( coro); + BOOST_CHECK_EQUAL( std::string("abc"), result); + result = coro().get(); + BOOST_CHECK( coro); + BOOST_CHECK_EQUAL( std::string("xyz"), result); + coro(); + BOOST_CHECK( ! coro); +} + +void test_arg_int() +{ + value1 = 0; + + coro::coroutine< int >::push_type coro( f6); + BOOST_CHECK( coro); + coro( 3); + BOOST_CHECK( ! coro); + BOOST_CHECK_EQUAL( 3, value1); +} + +void test_arg_string() +{ + value2 = ""; + + coro::coroutine< std::string >::push_type coro( f7); + BOOST_CHECK( coro); + coro( std::string("abc") ); + BOOST_CHECK( ! coro); + BOOST_CHECK_EQUAL( std::string("abc"), value2); +} + +void test_fp() +{ + value4 = 0; + + coro::coroutine< std::tuple< double, double > >::push_type coro( f8); + BOOST_CHECK( coro); + coro( std::make_tuple( 7.35, 3.14) ); + BOOST_CHECK( coro); + BOOST_CHECK_EQUAL( ( double) 10.49, value4); + + value4 = 0; + coro( std::make_tuple( 1.15, 3.14) ); + BOOST_CHECK( ! coro); + BOOST_CHECK_EQUAL( ( double) 4.29, value4); +} + +void test_ptr() +{ + value5 = 0; + + int a = 3; + coro::coroutine< int * >::push_type coro( f9); + BOOST_CHECK( coro); + coro( & a); + BOOST_CHECK( ! coro); + BOOST_CHECK_EQUAL( & a, value5); +} + +void test_const_ptr() +{ + value5 = 0; + + int a = 3; + coro::coroutine< int const* >::push_type coro( f91); + BOOST_CHECK( coro); + coro( & a); + BOOST_CHECK( ! coro); + BOOST_CHECK_EQUAL( & a, value5); +} + +void test_ref() +{ + value5 = 0; + + int a = 3; + coro::coroutine< int & >::push_type coro( f10); + BOOST_CHECK( coro); + coro( a); + BOOST_CHECK( ! coro); + BOOST_CHECK_EQUAL( & a, value5); +} + +void test_const_ref() +{ + value5 = 0; + + int a = 3; + coro::coroutine< int const& >::push_type coro( f101); + BOOST_CHECK( coro); + coro( a); + BOOST_CHECK( ! coro); + BOOST_CHECK_EQUAL( & a, value5); +} + +void test_tuple() +{ + value8 = 0; + value9 = 0; + + int a = 3, b = 7; + std::tuple< int, int > tpl( a, b); + BOOST_CHECK_EQUAL( a, std::get< 0 >( tpl) ); + BOOST_CHECK_EQUAL( b, std::get< 1 >( tpl) ); + coro::coroutine< std::tuple< int, int > >::push_type coro( f11); + BOOST_CHECK( coro); + coro( tpl); + BOOST_CHECK( ! coro); + BOOST_CHECK_EQUAL( a, value8); + BOOST_CHECK_EQUAL( b, value9); +} + +void test_unwind() +{ + value1 = 0; + { + coro::coroutine< void >::push_type coro( f12); + BOOST_CHECK( coro); + BOOST_CHECK_EQUAL( ( int) 0, value1); + coro(); + BOOST_CHECK( coro); + BOOST_CHECK_EQUAL( ( int) 7, value1); + coro(); + BOOST_CHECK_EQUAL( ( int) 7, value1); + } + BOOST_CHECK_EQUAL( ( int) 0, value1); +} + +void test_exceptions() +{ + bool thrown = false; + std::runtime_error ex("abc"); + try + { + coro::coroutine< void >::push_type coro( + std::bind( f14< std::runtime_error >, std::placeholders::_1, ex) ); + BOOST_CHECK( coro); + coro(); + BOOST_CHECK( ! coro); + BOOST_CHECK( false); + } + catch ( std::runtime_error const&) + { thrown = true; } + catch ( std::exception const&) + {} + catch (...) + {} + BOOST_CHECK( thrown); +} + +void test_input_iterator() +{ + { + using std::begin; + using std::end; + + std::vector< int > vec; + coro::coroutine< int >::pull_type coro( f16); + coro::coroutine< int >::pull_type::iterator e = end( coro); + for ( + coro::coroutine< int >::pull_type::iterator i = begin( coro); + i != e; ++i) + { vec.push_back( * i); } + BOOST_CHECK_EQUAL( ( std::size_t)5, vec.size() ); + BOOST_CHECK_EQUAL( ( int)1, vec[0] ); + BOOST_CHECK_EQUAL( ( int)2, vec[1] ); + BOOST_CHECK_EQUAL( ( int)3, vec[2] ); + BOOST_CHECK_EQUAL( ( int)4, vec[3] ); + BOOST_CHECK_EQUAL( ( int)5, vec[4] ); + } + { + std::vector< int > vec; + coro::coroutine< int >::pull_type coro( f16); + for ( auto i : coro) + { vec.push_back( i); } + BOOST_CHECK_EQUAL( ( std::size_t)5, vec.size() ); + BOOST_CHECK_EQUAL( ( int)1, vec[0] ); + BOOST_CHECK_EQUAL( ( int)2, vec[1] ); + BOOST_CHECK_EQUAL( ( int)3, vec[2] ); + BOOST_CHECK_EQUAL( ( int)4, vec[3] ); + BOOST_CHECK_EQUAL( ( int)5, vec[4] ); + } + { + int i1 = 1, i2 = 2, i3 = 3; + coro::coroutine< int& >::pull_type coro( + std::bind( f19, std::placeholders::_1, std::ref( i1), std::ref( i2), std::ref( i3) ) ); + int counter = 1; + for ( int & i : coro) { + switch ( counter) { + case 1: + BOOST_CHECK_EQUAL( & i1, & i); + break; + case 2: + BOOST_CHECK_EQUAL( & i2, & i); + break; + case 3: + BOOST_CHECK_EQUAL( & i3, & i); + break; + default: + BOOST_ASSERT( false); + } + ++counter; + } + } +} + +void test_output_iterator() +{ + using std::begin; + using std::end; + + int counter = 0; + std::vector< int > vec; + coro::coroutine< int >::push_type coro( + [&vec]( coro::coroutine< int >::pull_type & c) { + int x = c.get(); + while ( 5 > x) + { + vec.push_back( x); + x = c().get(); + } + }); +// coro::coroutine< int >::push_type coro( +// std::bind( f17, std::placeholders::_1, std::ref( vec) ) ); + coro::coroutine< int >::push_type::iterator e( end( coro) ); + for ( coro::coroutine< int >::push_type::iterator i( begin( coro) ); + i != e; ++i) + { + i = ++counter; + } + BOOST_CHECK_EQUAL( ( std::size_t)4, vec.size() ); + BOOST_CHECK_EQUAL( ( int)1, vec[0] ); + BOOST_CHECK_EQUAL( ( int)2, vec[1] ); + BOOST_CHECK_EQUAL( ( int)3, vec[2] ); + BOOST_CHECK_EQUAL( ( int)4, vec[3] ); +} + +void test_no_result() +{ + coro::coroutine< int >::pull_type coro( f20); + BOOST_CHECK( ! coro); +} + +boost::unit_test::test_suite * init_unit_test_suite( int, char* []) +{ + boost::unit_test::test_suite * test = + BOOST_TEST_SUITE("Boost.coroutine: coroutine test suite"); + + test->add( BOOST_TEST_CASE( & test_move) ); + test->add( BOOST_TEST_CASE( & test_complete) ); + test->add( BOOST_TEST_CASE( & test_jump) ); + test->add( BOOST_TEST_CASE( & test_result_int) ); + test->add( BOOST_TEST_CASE( & test_result_string) ); + test->add( BOOST_TEST_CASE( & test_arg_int) ); + test->add( BOOST_TEST_CASE( & test_arg_string) ); + test->add( BOOST_TEST_CASE( & test_fp) ); + test->add( BOOST_TEST_CASE( & test_ptr) ); + test->add( BOOST_TEST_CASE( & test_const_ptr) ); + test->add( BOOST_TEST_CASE( & test_no_result) ); + test->add( BOOST_TEST_CASE( & test_ref) ); + test->add( BOOST_TEST_CASE( & test_const_ref) ); + test->add( BOOST_TEST_CASE( & test_tuple) ); + test->add( BOOST_TEST_CASE( & test_unwind) ); + test->add( BOOST_TEST_CASE( & test_exceptions) ); + test->add( BOOST_TEST_CASE( & test_input_iterator) ); + test->add( BOOST_TEST_CASE( & test_output_iterator) ); + + return test; +}