1[/
2          Copyright Oliver Kowalke 2016.
3 Distributed under the Boost Software License, Version 1.0.
4    (See accompanying file LICENSE_1_0.txt or copy at
5          http://www.boost.org/LICENSE_1_0.txt
6]
7
8[#ff]
9[section:ff Context switching with fibers]
10
11[note __fiber__ is the reference implementation of C++ proposal
12[@http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0876r0.pdf P0876R0:
13fibers without scheduler].]
14
15A __fiber__ represents the state of the control flow of a program at a given
16point in time. Fibers can be suspended and resumed later in order to change the
17control flow of a program.
18
19Modern micro-processors are registers machines; the content of processor
20registers represent a fiber of the executed program at a given point in time.
21Operating systems simulate parallel execution of programs on a single processor
22by switching between programs (context switch) by preserving and restoring the
23fiber, e.g. the content of all registers.
24
25
26[heading __fib__]
27
28__fib__ captures the current fiber (the rest of the computation; code after
29__fib__) and triggers a context switch. The context switch is achieved by
30preserving certain registers (including instruction and stack pointer), defined
31by the calling convention of the ABI, of the current fiber and restoring those
32registers of the resumed fiber. The control flow of the resumed fiber continues.
33The current fiber is suspended and passed as argument to the resumed fiber.
34
35__fib__ expects a __context_fn__ with signature `'fiber(fiber && f)'`. The
36parameter `f` represents the current fiber from which this fiber was resumed
37(e.g. that has called __fib__).
38
39On return the __context_fn__ of the current fiber has to specify an __fib__ to
40which the execution control is transferred after termination of the current
41fiber.
42
43If an instance with valid state goes out of scope and the __context_fn__ has not
44yet returned, the stack is traversed in order to access the control structure
45(address stored at the first stack frame) and fiber's stack is deallocated via
46the __stack_allocator__.
47
48[note [link segmented ['Segmented stacks]] are supported by __fib__ using [link
49implementation ['ucontext_t]].]
50
51
52__fib__ represents a __fiber__; it contains the content of preserved registers
53and manages the associated stack (allocation/deallocation). __fib__ is a
54one-shot fiber - it can be used only once, after calling __resume__ or
55__resume_with__ it is invalidated.
56
57__fib__ is  only move-constructible and move-assignable.
58
59As a first-class object __fib__ can be applied to and returned from a function,
60assigned to a variable or stored in a container.
61
62A fiber is continued by calling `resume()`/`resume_with()`.
63
64
65[heading Usage]
66
67        namespace ctx=boost::context;
68        int a;
69        ctx::fiber source{[&a](ctx::fiber&& sink){
70            a=0;
71            int b=1;
72            for(;;){
73                sink=std::move(sink).resume();
74                int next=a+b;
75                a=b;
76                b=next;
77            }
78            return std::move(sink);
79        }};
80        for (int j=0;j<10;++j) {
81            source=std::move(source).resume();
82            std::cout << a << " ";
83        }
84
85        output:
86            0 1 1 2 3 5 8 13 21 34
87
88This simple example demonstrates the basic usage of __fib__ as a ['generator].
89The fiber `sink` represents the ['main]-fiber (function `main()`).
90`sink` is captured (current-fiber) by invoking __fib__ and passed as parameter
91to the lambda.
92
93Because the state is invalidated (one-shot fiber) by each call of __resume__,
94the new state of the __fib__, returned by __resume__, needs to be assigned to
95`sink` after each call. In order to express the invalidation of the resumed fiber,
96the member functions `resume()` and `resume_with()` are rvalue-ref qualified.
97Both functions bind only to rvalues. Thus an lvalue fiber must be casted to an
98rvalue via `std::move()`.
99
100The lambda that calculates the Fibonacci numbers is executed inside the fiber
101represented by `source`. Calculated Fibonacci numbers are transferred between
102the two fibers via variable `a` (lambda capture reference).
103
104The locale variables `b` and ` next` remain their values during each context
105switch. This is possible due `source` has its own stack and the stack is
106exchanged by each context switch.
107
108
109[heading Parameter passing]
110
111Data can be transferred between two fibers via global pointers, calling wrappers
112(like `std::bind`) or lambda captures.
113
114    namespace ctx=boost::context;
115    int i=1;
116    ctx::fiber f1{[&i](ctx::fiber&& f2){
117        std::printf("inside f1,i==%d\n",i);
118        i+=1;
119        return std::move(f2).resume();
120    }};
121    f1=std::move(f1).resume();
122    std::printf("i==%d\n",i);
123
124    output:
125        inside c1,i==1
126        i==2
127
128`f1.resume()` enters the lambda in fiber represented by `f1` with lambda capture
129reference `i=1`. The expression `f2.resume()` resumes the fiber `f2`. On return
130of `f1.resume()`, the variable `i` has the value of `i+1`.
131
132
133[heading Exception handling]
134
135If the function executed inside a __context_fn__ emits ans exception, the
136application is terminated by calling `std::terminate()`. `std::exception_ptr`
137can be used to transfer exceptions between different fibers.
138
139[important Do not jump from inside a catch block and then re-throw the exception
140in another fiber.]
141
142
143[#ff_ontop]
144[heading Executing function on top of a fiber]
145
146Sometimes it is useful to execute a new function on top of a resumed fiber. For
147this purpose __resume_with__ has to be used.
148The function passed as argument must accept a rvalue reference to __fib__ and
149return `void`.
150
151    namespace ctx=boost::context;
152    int data=0;
153    ctx::fiber f1{[&data](ctx::fiber&& f2) {
154        std::cout << "f1: entered first time: " << data << std::endl;
155        data+=1;
156        f2=std::move(f2).resume();
157        std::cout << "f1: entered second time: " << data << std::endl;
158        data+=1;
159        f2=std::move(f2).resume();
160        std::cout << "f1: entered third time: " << data << std::endl;
161        return std::move(f2);
162    }};
163    f1=std::move(f1).resume();
164    std::cout << "f1: returned first time: " << data << std::endl;
165    data+=1;
166    f1=std::move(f1).resume();
167    std::cout << "f1: returned second time: " << data << std::endl;
168    data+=1;
169    f1=f1.resume_with([&data](ctx::fiber&& f2){
170        std::cout << "f2: entered: " << data << std::endl;
171        data=-1;
172        return std::move(f2);
173    });
174    std::cout << "f1: returned third time" << std::endl;
175
176    output:
177        f1: entered first time: 0
178        f1: returned first time: 1
179        f1: entered second time: 2
180        f1: returned second time: 3
181        f2: entered: 4
182        f1: entered third time: -1
183        f1: returned third time
184
185
186The expression `f1.resume_with(...)` executes a lambda on top of fiber `f1`,
187e.g. an additional stack frame is allocated on top of the stack.
188This lambda assigns `-1` to `data` and returns to the second invocation of
189`f1.resume()`.
190
191Another option is to execute a function on top of the fiber that throws
192an exception.
193
194    namespace ctx=boost::context;
195    struct my_exception : public std::runtime_error {
196        ctx::fiber    f;
197        my_exception(ctx::fiber&& f_,std::string const& what) :
198            std::runtime_error{ what },
199            f{ std::move(f_) } {
200        }
201    };
202
203    ctx::fiber f{[](ctx::fiber && f) ->ctx::fiber {
204        std::cout << "entered" << std::endl;
205        try {
206            f=std::move(f).resume();
207        } catch (my_exception & ex) {
208            std::cerr << "my_exception: " << ex.what() << std::endl;
209            return std::move(ex.f);
210        }
211        return {};
212    });
213    f=std::move(f).resume();
214    f=std::move(f).resume_with([](ctx::fiber && f) ->ctx::fiber {
215        throw my_exception(std::move(f),"abc");
216        return {};
217    });
218
219    output:
220        entered
221        my_exception: abc
222
223In this exception `my_exception` is throw from a function invoked on-top of
224fiber `f` and catched inside the `for`-loop.
225
226[heading Stack unwinding]
227On construction of __fib__ a stack is allocated.
228If the __context_fn__ returns the stack will be destructed.
229If the __context_fn__ has not yet returned and the destructor of an valid
230__fib__ instance (e.g. ['fiber::operator bool()] returns
231`true`) is called, the stack will be destructed too.
232
233[important Code executed by __context_fn__ must not prevent the propagation ofs
234the __forced_unwind__ exception.  Absorbing that exception will cause stack
235unwinding to fail.  Thus, any code that catches all exceptions must re-throw any
236pending __forced_unwind__ exception.]
237
238
239[#ff_prealloc]
240[heading Allocating control structures on top of stack]
241Allocating control structures on top of the stack requires to allocated the
242__stack_context__ and create the control structure with placement new before
243__fib__ is created.
244[note The user is responsible for destructing the control structure at the top
245of the stack.]
246
247    namespace ctx=boost::context;
248    // stack-allocator used for (de-)allocating stack
249    fixedsize_stack salloc(4048);
250    // allocate stack space
251    stack_context sctx(salloc.allocate());
252    // reserve space for control structure on top of the stack
253    void * sp=static_cast<char*>(sctx.sp)-sizeof(my_control_structure);
254    std::size_t size=sctx.size-sizeof(my_control_structure);
255    // placement new creates control structure on reserved space
256    my_control_structure * cs=new(sp)my_control_structure(sp,size,sctx,salloc);
257    ...
258    // destructing the control structure
259    cs->~my_control_structure();
260    ...
261    struct my_control_structure  {
262        // captured fiber
263        ctx::fiber   f;
264
265        template< typename StackAllocator >
266        my_control_structure(void * sp,std::size_t size,stack_context sctx,StackAllocator salloc) :
267            // create captured fiber
268            f{std::allocator_arg,preallocated(sp,size,sctx),salloc,entry_func} {
269        }
270        ...
271    };
272
273
274[heading Inverting the control flow]
275
276    namespace ctx=boost::context;
277    /*
278     * grammar:
279     *   P ---> E '\0'
280     *   E ---> T {('+'|'-') T}
281     *   T ---> S {('*'|'/') S}
282     *   S ---> digit | '(' E ')'
283     */
284    class Parser{
285       char next;
286       std::istream& is;
287       std::function<void(char)> cb;
288
289       char pull(){
290            return std::char_traits<char>::to_char_type(is.get());
291       }
292
293       void scan(){
294           do{
295               next=pull();
296           }
297           while(isspace(next));
298       }
299
300    public:
301       Parser(std::istream& is_,std::function<void(char)> cb_) :
302          next(), is(is_), cb(cb_)
303        {}
304
305       void run() {
306          scan();
307          E();
308       }
309
310    private:
311       void E(){
312          T();
313          while (next=='+'||next=='-'){
314             cb(next);
315             scan();
316             T();
317          }
318       }
319
320       void T(){
321          S();
322          while (next=='*'||next=='/'){
323             cb(next);
324             scan();
325             S();
326          }
327       }
328
329       void S(){
330          if (isdigit(next)){
331             cb(next);
332             scan();
333          }
334          else if(next=='('){
335             cb(next);
336             scan();
337             E();
338             if (next==')'){
339                 cb(next);
340                 scan();
341             }else{
342                 throw std::runtime_error("parsing failed");
343             }
344          }
345          else{
346             throw std::runtime_error("parsing failed");
347          }
348       }
349    };
350
351    std::istringstream is("1+1");
352    // user-code pulls parsed data from parser
353    // invert control flow
354    char c;
355    bool done=false;
356    // execute parser in new fiber
357    ctx::fiber source{[&is,&c,&done](ctx::fiber&& sink){
358        // create parser with callback function
359        Parser p(is,
360                 [&sink,&c](char c_){
361                    // resume main fiber
362                    c=c_;
363                    sink=std::move(sink).resume();
364                 });
365            // start recursive parsing
366            p.run();
367            // signal termination
368            done=true;
369            // resume main fiber
370            return std::move(sink);
371    }};
372    source=std::move(source).resume();
373    while(!done){
374        printf("Parsed: %c\n",c);
375        source=std::Move(source).resume();
376    }
377
378    output:
379        Parsed: 1
380        Parsed: +
381        Parsed: 1
382
383In this example a recursive descent parser uses a callback to emit a newly
384passed symbol. Using __fib__ the control flow can be inverted, e.g. the
385user-code pulls parsed symbols from the parser - instead to get pushed from the
386parser (via callback).
387
388The data (character) is transferred between the two fibers.
389
390
391[#implementation]
392[section Implementations: fcontext_t, ucontext_t and WinFiber]
393
394[heading fcontext_t]
395The implementation uses __fcontext__ per default. fcontext_t is based on
396assembler and not available for all platforms. It provides a much better
397performance than __ucontext__
398(the context switch takes two magnitudes of order less CPU cycles; see section
399[link performance ['performance]]) and __winfib__.
400
401[note Because the TIB (thread information block on Windows) is not fully
402described in the MSDN, it might be possible that not all required TIB-parts are
403swapped. Using WinFiber implementation migh be an alternative.]
404
405
406[heading ucontext_t]
407As an alternative, [@https://en.wikipedia.org/wiki/Setcontext __ucontext__]
408can be used by compiling with `BOOST_USE_UCONTEXT` and b2 property
409`context-impl=ucontext`.
410__ucontext__ might be available on a broader range of POSIX-platforms but has
411some [link ucontext ['disadvantages]] (for instance deprecated since
412POSIX.1-2003, not C99 conform).
413
414[note __fib__ supports [link segmented ['Segmented stacks]] only with
415__ucontext__ as its implementation.]
416
417
418[heading WinFiber]
419With `BOOST_USE_WINFIB` and b2 property `context-impl=winfib` Win32-Fibers are
420used as implementation for __fib__.
421
422[note The first call of __fib__ converts the thread into a Windows fiber by
423invoking `ConvertThreadToFiber()`. If desired, `ConvertFiberToThread()` has
424to be called by the user explicitly in order to release resources allocated
425by `ConvertThreadToFiber()` (e.g. after using boost.context). ]
426
427[endsect]
428
429
430[section Class `fiber`]
431
432    #include <boost/context/fiber.hpp>
433
434    class fiber {
435    public:
436        fiber() noexcept;
437
438        template<typename Fn>
439        fiber(Fn && fn);
440
441        template<typename StackAlloc, typename Fn>
442        fiber(std::allocator_arg_t, StackAlloc && salloc, Fn && fn);
443
444        ~fiber();
445
446        fiber(fiber && other) noexcept;
447
448        fiber & operator=(fiber && other) noexcept;
449
450        fiber(fiber const& other) noexcept = delete;
451        fiber & operator=(fiber const& other) noexcept = delete;
452
453        fiber resume() &&;
454
455        template<typename Fn>
456        fiber resume_with(Fn && fn) &&;
457
458        explicit operator bool() const noexcept;
459
460        bool operator!() const noexcept;
461
462        bool operator==(fiber const& other) const noexcept;
463
464        bool operator!=(fiber const& other) const noexcept;
465
466        bool operator<(fiber const& other) const noexcept;
467
468        bool operator>(fiber const& other) const noexcept;
469
470        bool operator<=(fiber const& other) const noexcept;
471
472        bool operator>=(fiber const& other) const noexcept;
473
474        template<typename charT,class traitsT>
475        friend std::basic_ostream<charT,traitsT> &
476        operator<<(std::basic_ostream<charT,traitsT> & os,fiber const& other) {
477
478        void swap(fiber & other) noexcept;
479    };
480
481[constructor_heading ff..constructor1]
482
483    fiber() noexcept;
484
485[variablelist
486[[Effects:] [Creates a invalid fiber.]]
487[[Throws:] [Nothing.]]
488]
489
490[constructor_heading ff..constructor2]
491
492    template<typename Fn>
493    fiber(Fn && fn);
494
495    template<typename StackAlloc, typename Fn>
496    fiber(std::allocator_arg_t, StackAlloc && salloc, Fn && fn);
497
498[variablelist
499[[Effects:] [Creates a new fiber and prepares the context to execute `fn`.
500`fixedsize_stack` is used as default stack allocator
501(stack size == fixedsize_stack::traits::default_size()). The constructor with
502argument type `preallocated`, is used to create a user defined data
503[link ff_prealloc (for instance additional control structures)] on top of the
504stack.]]
505]
506
507[destructor_heading ff..destructor destructor]
508
509    ~fiber();
510
511[variablelist
512[[Effects:] [Destructs the associated stack if `*this` is a valid fiber,
513e.g. ['fiber::operator bool()] returns `true`.]]
514[[Throws:] [Nothing.]]
515]
516
517[move_constructor_heading ff..move constructor]
518
519    fiber(fiber && other) noexcept;
520
521[variablelist
522[[Effects:] [Moves underlying capture fiber to `*this`.]]
523[[Throws:] [Nothing.]]
524]
525
526[move_assignment_heading ff..move assignment]
527
528    fiber & operator=(fiber && other) noexcept;
529
530[variablelist
531[[Effects:] [Moves the state of `other` to `*this` using move semantics.]]
532[[Throws:] [Nothing.]]
533]
534
535[operator_heading ff..operator_call..operator()]
536
537        fiber resume() &&;
538
539        template<typename Fn>
540        fiber resume_with(Fn && fn) &&;
541
542[variablelist
543[[Effects:] [Captures current fiber and resumes `*this`.
544The function `resume_with`, is used to execute function `fn` in the execution context of
545`*this` (e.g. the stack frame of `fn` is allocated on stack of `*this`).]]
546[[Returns:] [The fiber representing the fiber that has been
547suspended.]]
548[[Note:] [Because `*this` gets invalidated, `resume()` and `resume_with()` are rvalue-ref
549qualified and bind only to rvalues.]]
550[[Note:] [Function `fn` needs to return `fiber`.]]
551[[Note:] [The returned fiber indicates if the suspended fiber has
552terminated (return from context-function) via `bool operator()`.]]
553]
554
555[operator_heading ff..operator_bool..operator bool]
556
557    explicit operator bool() const noexcept;
558
559[variablelist
560[[Returns:] [`true` if `*this` points to a captured fiber.]]
561[[Throws:] [Nothing.]]
562]
563
564[operator_heading ff..operator_not..operator!]
565
566    bool operator!() const noexcept;
567
568[variablelist
569[[Returns:] [`true` if `*this` does not point to a captured fiber.]]
570[[Throws:] [Nothing.]]
571]
572
573[operator_heading ff..operator_equal..operator==]
574
575        bool operator==(fiber const& other) const noexcept;
576
577[variablelist
578[[Returns:] [`true` if `*this` and `other` represent the same fiber,
579`false` otherwise.]]
580[[Throws:] [Nothing.]]
581]
582
583[operator_heading ff..operator_notequal..operator!=]
584
585        bool operator!=(fiber const& other) const noexcept;
586
587[variablelist
588[[Returns:] [[`! (other == * this)]]]
589[[Throws:] [Nothing.]]
590]
591
592[operator_heading ff..operator_less..operator<]
593
594        bool operator<(fiber const& other) const noexcept;
595
596[variablelist
597[[Returns:] [`true` if `*this != other` is true and the
598implementation-defined total order of `fiber` values places `*this`
599before `other`, false otherwise.]]
600[[Throws:] [Nothing.]]
601]
602
603[operator_heading ff..operator_greater..operator>]
604
605        bool operator>(fiber const& other) const noexcept;
606
607[variablelist
608[[Returns:] [`other < * this`]]
609[[Throws:] [Nothing.]]
610]
611
612[operator_heading ff..operator_lesseq..operator<=]
613
614        bool operator<=(fiber const& other) const noexcept;
615
616[variablelist
617[[Returns:] [`! (other < * this)`]]
618[[Throws:] [Nothing.]]
619]
620
621[operator_heading ff..operator_greatereq..operator>=]
622
623        bool operator>=(fiber const& other) const noexcept;
624
625[variablelist
626[[Returns:] [`! (* this < other)`]]
627[[Throws:] [Nothing.]]
628]
629
630[hding ff_..Non-member function [`operator<<()]]
631
632        template<typename charT,class traitsT>
633        std::basic_ostream<charT,traitsT> &
634        operator<<(std::basic_ostream<charT,traitsT> & os,fiber const& other);
635
636[variablelist
637[[Effects:] [Writes the representation of `other` to stream `os`.]]
638[[Returns:] [`os`]]
639]
640
641[endsect]
642
643
644[endsect]
645