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 0000000..c32b223 Binary files /dev/null and b/doc/images/event_model.dia differ diff --git a/doc/images/event_model.png b/doc/images/event_model.png new file mode 100644 index 0000000..f52b22f Binary files /dev/null and b/doc/images/event_model.png differ diff --git a/doc/images/foo_bar.png b/doc/images/foo_bar.png new file mode 100644 index 0000000..d01a9c0 Binary files /dev/null and b/doc/images/foo_bar.png differ diff --git a/doc/images/foo_bar_seq.dia b/doc/images/foo_bar_seq.dia new file mode 100644 index 0000000..c31f2ce Binary files /dev/null and b/doc/images/foo_bar_seq.dia differ diff --git a/doc/images/foo_bar_seq.png b/doc/images/foo_bar_seq.png new file mode 100644 index 0000000..33ab044 Binary files /dev/null and b/doc/images/foo_bar_seq.png differ diff --git a/doc/images/same_fringe.dia b/doc/images/same_fringe.dia new file mode 100644 index 0000000..ad85cba Binary files /dev/null and b/doc/images/same_fringe.dia differ diff --git a/doc/images/same_fringe.png b/doc/images/same_fringe.png new file mode 100644 index 0000000..2346e1c Binary files /dev/null and b/doc/images/same_fringe.png differ 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; +}