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