KiCad PCB EDA Suite
Loading...
Searching...
No Matches
coroutine.h
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2013 CERN
5 * Copyright (C) 2016-2020 KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * @author Tomasz Wlostowski <[email protected]>
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License
11 * as published by the Free Software Foundation; either version 2
12 * of the License, or (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, you may find one here:
21 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
22 * or you may search the http://www.gnu.org website for the version 2 license,
23 * or you may write to the Free Software Foundation, Inc.,
24 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
25 */
26
27#ifndef __COROUTINE_H
28#define __COROUTINE_H
29
30#include <cassert>
31#include <cstdlib>
32#include <type_traits>
33
34#ifdef KICAD_USE_VALGRIND
35#include <valgrind/valgrind.h>
36#endif
37#ifdef KICAD_SANITIZE_THREADS
38#include <sanitizer/tsan_interface.h>
39#endif
40#ifdef KICAD_SANITIZE_ADDRESS
41#include <sanitizer/asan_interface.h>
42#endif
43
44#include <libcontext.h>
45#include <functional>
46#include <optional>
47#include <memory>
48#include <advanced_config.h>
49
50#include <trace_helpers.h>
51#include <wx/log.h>
52
53#ifdef _WIN32
54#include <windows.h>
55#else // Linux, BSD, MacOS
56#include <unistd.h> // getpagesize
57#include <sys/mman.h> // mmap, mprotect, munmap
58#endif
59
82template <typename ReturnType, typename ArgType>
84{
85private:
86 class CALL_CONTEXT;
87
89 {
90 enum
91 {
92 FROM_ROOT, // a stub was called/a coroutine was resumed from the main-stack context
93 FROM_ROUTINE, // a stub was called/a coroutine was resumed from a coroutine context
94 CONTINUE_AFTER_ROOT // a function sent a request to invoke a function on the main
95 // stack context
96 } type; // invocation type
97 COROUTINE* destination; // stores the coroutine pointer for the stub OR the coroutine
98 // ptr for the coroutine to be resumed if a
99 // root(main-stack)-call-was initiated.
100 CALL_CONTEXT* context; // pointer to the call context of the current callgraph this
101 // call context holds a reference to the main stack context
102 };
103
105 {
106 libcontext::fcontext_t ctx; // The context itself
107#ifdef KICAD_SANITIZE_THREADS
108 void* tsan_fiber; // The TSAN fiber for this context
109 bool own_tsan_fiber; // Do we own this TSAN fiber? (we only delete fibers we own)
110#endif
111
113 ctx( nullptr )
114#ifdef KICAD_SANITIZE_THREADS
115 ,tsan_fiber( nullptr )
116 ,own_tsan_fiber( true )
117#endif
118 {}
119
121 {
122#ifdef KICAD_SANITIZE_THREADS
123 // Only destroy the fiber when we own it
124 if( own_tsan_fiber )
125 __tsan_destroy_fiber( tsan_fiber );
126#endif
127 }
128 };
129
131 {
132 public:
134 m_mainStackContext( nullptr )
135 {
136 }
137
139 {
141 libcontext::release_fcontext( m_mainStackContext->ctx );
142 }
143
144
145 void SetMainStack( CONTEXT_T* aStack )
146 {
147 m_mainStackContext = aStack;
148 }
149
150 void RunMainStack( COROUTINE* aCor, std::function<void()> aFunc )
151 {
152 m_mainStackFunction = std::move( aFunc );
154
155#ifdef KICAD_SANITIZE_THREADS
156 // Tell TSAN we are changing fibers
157 __tsan_switch_to_fiber( m_mainStackContext->tsan_fiber, 0 );
158#endif
159
160 libcontext::jump_fcontext( &( aCor->m_callee.ctx ), m_mainStackContext->ctx,
161 reinterpret_cast<intptr_t>( &args ) );
162 }
163
165 {
167 {
170 args = args->destination->doResume( args );
171 }
172 }
173
174 private:
176 std::function<void()> m_mainStackFunction;
177 };
178
179public:
181 COROUTINE( nullptr )
182 {
183 }
184
188 template <class T>
189 COROUTINE( T* object, ReturnType(T::*ptr)( ArgType ) ) :
190 COROUTINE( std::bind( ptr, object, std::placeholders::_1 ) )
191 {
192 }
193
197 COROUTINE( std::function<ReturnType( ArgType )> aEntry ) :
198 m_func( std::move( aEntry ) ),
199 m_running( false ),
200 m_args( nullptr ),
201 m_caller(),
202 m_callContext( nullptr ),
203 m_callee(),
204 m_retVal( 0 )
205#ifdef KICAD_USE_VALGRIND
206 ,m_valgrind_stack( 0 )
207#endif
208#ifdef KICAD_SANITIZE_ADDRESS
209 ,asan_stack( nullptr )
210#endif
211 {
213 }
214
216 {
217#ifdef KICAD_USE_VALGRIND
218 VALGRIND_STACK_DEREGISTER( m_valgrind_stack );
219#endif
220
221 if( m_caller.ctx )
222 libcontext::release_fcontext( m_caller.ctx );
223
224 if( m_callee.ctx )
225 libcontext::release_fcontext( m_callee.ctx );
226 }
227
228public:
235 void KiYield()
236 {
237 jumpOut();
238 }
239
245 void KiYield( ReturnType& aRetVal )
246 {
247 m_retVal = aRetVal;
248 jumpOut();
249 }
250
258 void RunMainStack( std::function<void()> func )
259 {
260 assert( m_callContext );
261 m_callContext->RunMainStack( this, std::move( func ) );
262 }
263
272 bool Call( ArgType aArg )
273 {
274 CALL_CONTEXT ctx;
276
277#ifdef KICAD_SANITIZE_THREADS
278 // Get the TSAN fiber for the current stack here
279 m_caller.tsan_fiber = __tsan_get_current_fiber();
280 m_caller.own_tsan_fiber = false;
281#endif
282
283 wxLogTrace( kicadTraceCoroutineStack, "COROUTINE::Call (from root)" );
284
285 ctx.Continue( doCall( &args, aArg ) );
286
287 return Running();
288 }
289
298 bool Call( const COROUTINE& aCor, ArgType aArg )
299 {
301
302 wxLogTrace( kicadTraceCoroutineStack, wxT( "COROUTINE::Call (from routine)" ) );
303
304 doCall( &args, aArg );
305 // we will not be asked to continue
306
307 return Running();
308 }
309
318 bool Resume()
319 {
320 CALL_CONTEXT ctx;
322
323#ifdef KICAD_SANITIZE_THREADS
324 // Get the TSAN fiber for the current stack here
325 m_caller.tsan_fiber = __tsan_get_current_fiber();
326 m_caller.own_tsan_fiber = false;
327#endif
328
329 wxLogTrace( kicadTraceCoroutineStack, wxT( "COROUTINE::Resume (from root)" ) );
330
331 ctx.Continue( doResume( &args ) );
332
333 return Running();
334 }
335
344 bool Resume( const COROUTINE& aCor )
345 {
347
348 wxLogTrace( kicadTraceCoroutineStack, wxT( "COROUTINE::Resume (from routine)" ) );
349
350 doResume( &args );
351 // we will not be asked to continue
352
353 return Running();
354 }
355
359 const ReturnType& ReturnValue() const
360 {
361 return m_retVal;
362 }
363
367 bool Running() const
368 {
369 return m_running;
370 }
371
372private:
373 INVOCATION_ARGS* doCall( INVOCATION_ARGS* aInvArgs, ArgType aArgs )
374 {
375 assert( m_func );
376 assert( !( m_callee.ctx ) );
377
378 m_args = &aArgs;
379
380 std::size_t stackSize = m_stacksize;
381 void* sp = nullptr;
382
383 wxLogTrace( kicadTraceCoroutineStack, wxT( "COROUTINE::doCall" ) );
384
385#ifndef LIBCONTEXT_HAS_OWN_STACK
386 assert( !m_stack );
387
388 const std::size_t systemPageSize = SystemPageSize();
389
390 // calculate the correct number of pages to allocate based on request stack size
391 std::size_t pages = ( m_stacksize + systemPageSize - 1 ) / systemPageSize;
392
393 // we allocate an extra page for the guard
394 stackSize = ( pages + 1 ) * systemPageSize;
395
396 m_stack.reset( static_cast<char*>( MapMemory( stackSize ) ) );
397 m_stack.get_deleter().SetSize( stackSize );
398
399 // now configure the first page (by only specifying a single page_size from vp)
400 // that will act as the guard page
401 // the stack will grow from the end and hopefully never into this guarded region
402 GuardMemory( m_stack.get(), systemPageSize );
403
404 sp = static_cast<char*>( m_stack.get() ) + stackSize;
405
406#ifdef KICAD_USE_VALGRIND
407 m_valgrind_stack = VALGRIND_STACK_REGISTER( sp, m_stack.get() );
408#endif
409#endif
410
411#ifdef KICAD_SANITIZE_THREADS
412 // Create a new fiber to go with the new context
413 m_callee.tsan_fiber = __tsan_create_fiber( 0 );
414 m_callee.own_tsan_fiber = true;
415
416 __tsan_set_fiber_name( m_callee.tsan_fiber, "Coroutine fiber" );
417#endif
418
419 m_callee.ctx = libcontext::make_fcontext( sp, stackSize, callerStub );
420 m_running = true;
421
422 // off we go!
423 return jumpIn( aInvArgs );
424 }
425
426#ifndef LIBCONTEXT_HAS_OWN_STACK
429 {
430#ifdef _WIN32
431 void SetSize( std::size_t ) {}
432 void operator()( void* aMem ) noexcept { ::VirtualFree( aMem, 0, MEM_RELEASE ); }
433#else
434 std::size_t m_size = 0;
435
436 void SetSize( std::size_t aSize ) { m_size = aSize; }
437 void operator()( void* aMem ) noexcept { ::munmap( aMem, m_size ); }
438#endif
439 };
440
442 static inline size_t SystemPageSize()
443 {
444 static std::optional<size_t> systemPageSize;
445 if( !systemPageSize.has_value() )
446 {
447#ifdef _WIN32
448 SYSTEM_INFO si;
449 ::GetSystemInfo( &si );
450 systemPageSize = static_cast<size_t>( si.dwPageSize );
451#else
452 int size = getpagesize();
453 systemPageSize = static_cast<size_t>( size );
454#endif
455 }
456 return systemPageSize.value();
457 }
458
460 static inline void* MapMemory( size_t aAllocSize )
461 {
462#ifdef _WIN32
463 void* mem = ::VirtualAlloc( 0, aAllocSize, MEM_COMMIT, PAGE_READWRITE );
464
465 if( !mem )
466 throw std::bad_alloc();
467#else
468 void* mem = ::mmap( 0, aAllocSize, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0 );
469
470 if( mem == (void*) -1 )
471 throw std::bad_alloc();
472#endif
473 return mem;
474 }
475
477 static inline void GuardMemory( void* aAddress, size_t aGuardSize )
478 {
479#ifdef _WIN32
480 DWORD old_prot; // dummy var since the arg cannot be NULL
481 BOOL res = ::VirtualProtect( aAddress, aGuardSize,
482 PAGE_READWRITE | PAGE_GUARD, &old_prot );
483#else
484 bool res = ( 0 == ::mprotect( aAddress, aGuardSize, PROT_NONE ) );
485#endif
486 if( !res )
487 wxLogTrace( kicadTraceCoroutineStack, wxT( "COROUTINE::GuardMemory has failed" ) );
488 }
489#endif // LIBCONTEXT_HAS_OWN_STACK
490
492 {
493 return jumpIn( args );
494 }
495
496 /* real entry point of the coroutine */
497 static void callerStub( intptr_t aData )
498 {
499 INVOCATION_ARGS& args = *reinterpret_cast<INVOCATION_ARGS*>( aData );
500
501 // get pointer to self
502 COROUTINE* cor = args.destination;
503 cor->m_callContext = args.context;
504
505 if( args.type == INVOCATION_ARGS::FROM_ROOT )
506 cor->m_callContext->SetMainStack( &cor->m_caller );
507
508 // call the coroutine method
509 cor->m_retVal = cor->m_func( *(cor->m_args) );
510 cor->m_running = false;
511
512 // go back to wherever we came from.
513 cor->jumpOut();
514 }
515
517 {
518#ifdef KICAD_SANITIZE_THREADS
519 // Tell TSAN we are changing fibers to the callee
520 __tsan_switch_to_fiber( m_callee.tsan_fiber, 0 );
521#endif
522
523 wxLogTrace( kicadTraceCoroutineStack, wxT( "COROUTINE::jumpIn" ) );
524
525 args = reinterpret_cast<INVOCATION_ARGS*>(
526 libcontext::jump_fcontext( &( m_caller.ctx ), m_callee.ctx,
527 reinterpret_cast<intptr_t>( args ) )
528 );
529
530 return args;
531 }
532
533 void jumpOut()
534 {
535 INVOCATION_ARGS args{ INVOCATION_ARGS::FROM_ROUTINE, nullptr, nullptr };
536 INVOCATION_ARGS* ret;
537
538#ifdef KICAD_SANITIZE_THREADS
539 // Tell TSAN we are changing fibers back to the caller
540 __tsan_switch_to_fiber( m_caller.tsan_fiber, 0 );
541#endif
542
543 wxLogTrace( kicadTraceCoroutineStack, wxT( "COROUTINE::jumpOut" ) );
544
545 ret = reinterpret_cast<INVOCATION_ARGS*>(
546 libcontext::jump_fcontext( &( m_callee.ctx ), m_caller.ctx,
547 reinterpret_cast<intptr_t>( &args ) )
548 );
549
550 m_callContext = ret->context;
551
552 if( ret->type == INVOCATION_ARGS::FROM_ROOT )
553 {
555 }
556 }
557
558#ifndef LIBCONTEXT_HAS_OWN_STACK
560 std::unique_ptr<char[], struct STACK_DELETER> m_stack;
561#endif
562
564
565 std::function<ReturnType( ArgType )> m_func;
566
568
571 typename std::remove_reference<ArgType>::type* m_args;
572
575
578
581
582 ReturnType m_retVal;
583
584#ifdef KICAD_USE_VALGRIND
585 uint32_t m_valgrind_stack;
586#endif
587#ifdef KICAD_SANITIZE_ADDRESS
588 void* asan_stack;
589#endif
590};
591
592#endif
static const ADVANCED_CFG & GetCfg()
Get the singleton instance's config, which is shared by all consumers.
CONTEXT_T * m_mainStackContext
Definition: coroutine.h:175
void SetMainStack(CONTEXT_T *aStack)
Definition: coroutine.h:145
void RunMainStack(COROUTINE *aCor, std::function< void()> aFunc)
Definition: coroutine.h:150
void Continue(INVOCATION_ARGS *args)
Definition: coroutine.h:164
std::function< void()> m_mainStackFunction
Definition: coroutine.h:176
Implement a coroutine.
Definition: coroutine.h:84
bool Call(ArgType aArg)
Start execution of a coroutine, passing args as its arguments.
Definition: coroutine.h:272
CALL_CONTEXT * m_callContext
saved coroutine context
Definition: coroutine.h:577
void KiYield(ReturnType &aRetVal)
KiYield with a value.
Definition: coroutine.h:245
void KiYield()
Stop execution of the coroutine and returns control to the caller.
Definition: coroutine.h:235
CONTEXT_T m_caller
main stack information
Definition: coroutine.h:574
bool Resume()
Resume execution of a previously yielded coroutine.
Definition: coroutine.h:318
ReturnType m_retVal
Definition: coroutine.h:582
COROUTINE(std::function< ReturnType(ArgType)> aEntry)
Create a coroutine from a delegate object.
Definition: coroutine.h:197
static void callerStub(intptr_t aData)
Definition: coroutine.h:497
~COROUTINE()
Definition: coroutine.h:215
CONTEXT_T m_callee
Definition: coroutine.h:580
INVOCATION_ARGS * jumpIn(INVOCATION_ARGS *args)
Definition: coroutine.h:516
std::unique_ptr< char[], struct STACK_DELETER > m_stack
Definition: coroutine.h:560
const ReturnType & ReturnValue() const
Return the yielded value (the argument KiYield() was called with).
Definition: coroutine.h:359
INVOCATION_ARGS * doResume(INVOCATION_ARGS *args)
Definition: coroutine.h:491
bool m_running
pointer to coroutine entry arguments.
Definition: coroutine.h:567
int m_stacksize
Definition: coroutine.h:563
static void * MapMemory(size_t aAllocSize)
Change protection of memory page(s) to act as stack guards.
Definition: coroutine.h:460
static size_t SystemPageSize()
Map a page-aligned memory region into our address space.
Definition: coroutine.h:442
INVOCATION_ARGS * doCall(INVOCATION_ARGS *aInvArgs, ArgType aArgs)
A functor that frees the stack.
Definition: coroutine.h:373
bool Call(const COROUTINE &aCor, ArgType aArg)
Start execution of a coroutine, passing args as its arguments.
Definition: coroutine.h:298
std::function< ReturnType(ArgType)> m_func
Definition: coroutine.h:565
static void GuardMemory(void *aAddress, size_t aGuardSize)
Definition: coroutine.h:477
bool Resume(const COROUTINE &aCor)
Resume execution of a previously yielded coroutine.
Definition: coroutine.h:344
COROUTINE(T *object, ReturnType(T::*ptr)(ArgType))
Create a coroutine from a member method of an object.
Definition: coroutine.h:189
bool Running() const
Definition: coroutine.h:367
void RunMainStack(std::function< void()> func)
Run a functor inside the application main stack context.
Definition: coroutine.h:258
void jumpOut()
coroutine stack
Definition: coroutine.h:533
std::remove_reference< ArgType >::type * m_args
saved caller context
Definition: coroutine.h:571
int m_CoroutineStackSize
Configure the coroutine stack size in bytes.
const wxChar *const kicadTraceCoroutineStack
Flag to enable tracing of the coroutine call stack.
STL namespace.
libcontext::fcontext_t ctx
Definition: coroutine.h:106
enum COROUTINE::INVOCATION_ARGS::@35 type
CALL_CONTEXT * context
Definition: coroutine.h:100
The size of the mappable memory page size.
Definition: coroutine.h:429
void SetSize(std::size_t aSize)
Definition: coroutine.h:436
void operator()(void *aMem) noexcept
Definition: coroutine.h:437
VECTOR3I res
wxLogTrace helper definitions.