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