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#ifndef LIBCONTEXT_HAS_OWN_STACK
384 assert( !m_stack );
385
386 const std::size_t systemPageSize = SystemPageSize();
387
388 // calculate the correct number of pages to allocate based on request stack size
389 std::size_t pages = ( m_stacksize + systemPageSize - 1 ) / systemPageSize;
390
391 // we allocate an extra page for the guard
392 stackSize = ( pages + 1 ) * systemPageSize;
393
394 m_stack.reset( static_cast<char*>( MapMemory( stackSize ) ) );
395 m_stack.get_deleter().SetSize( stackSize );
396
397 // now configure the first page (by only specifying a single page_size from vp)
398 // that will act as the guard page
399 // the stack will grow from the end and hopefully never into this guarded region
400 GuardMemory( m_stack.get(), systemPageSize );
401
402 sp = static_cast<char*>( m_stack.get() ) + stackSize;
403
404#ifdef KICAD_USE_VALGRIND
405 m_valgrind_stack = VALGRIND_STACK_REGISTER( sp, m_stack.get() );
406#endif
407#endif
408
409#ifdef KICAD_SANITIZE_THREADS
410 // Create a new fiber to go with the new context
411 m_callee.tsan_fiber = __tsan_create_fiber( 0 );
412 m_callee.own_tsan_fiber = true;
413
414 __tsan_set_fiber_name( m_callee.tsan_fiber, "Coroutine fiber" );
415#endif
416
417 wxLogTrace( kicadTraceCoroutineStack, wxT( "COROUTINE::doCall" ) );
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 if( !mem )
465 throw std::bad_alloc();
466#else
467 void* mem = ::mmap( 0, aAllocSize, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0 );
468 if( mem == (void*) -1 )
469 throw std::bad_alloc();
470#endif
471 return mem;
472 }
473
475 static inline void GuardMemory( void* aAddress, size_t aGuardSize )
476 {
477#ifdef _WIN32
478 DWORD old_prot; // dummy var since the arg cannot be NULL
479 BOOL res = ::VirtualProtect( aAddress, aGuardSize,
480 PAGE_READWRITE | PAGE_GUARD, &old_prot );
481#else
482 bool res = ( 0 == ::mprotect( aAddress, aGuardSize, PROT_NONE ) );
483#endif
484 if( !res )
485 wxLogTrace( kicadTraceCoroutineStack, wxT( "COROUTINE::GuardMemory has failes" ) );
486 }
487#endif // LIBCONTEXT_HAS_OWN_STACK
488
490 {
491 return jumpIn( args );
492 }
493
494 /* real entry point of the coroutine */
495 static void callerStub( intptr_t aData )
496 {
497 INVOCATION_ARGS& args = *reinterpret_cast<INVOCATION_ARGS*>( aData );
498
499 // get pointer to self
500 COROUTINE* cor = args.destination;
501 cor->m_callContext = args.context;
502
503 if( args.type == INVOCATION_ARGS::FROM_ROOT )
504 cor->m_callContext->SetMainStack( &cor->m_caller );
505
506 // call the coroutine method
507 cor->m_retVal = cor->m_func( *(cor->m_args) );
508 cor->m_running = false;
509
510 // go back to wherever we came from.
511 cor->jumpOut();
512 }
513
515 {
516#ifdef KICAD_SANITIZE_THREADS
517 // Tell TSAN we are changing fibers to the callee
518 __tsan_switch_to_fiber( m_callee.tsan_fiber, 0 );
519#endif
520
521 wxLogTrace( kicadTraceCoroutineStack, wxT( "COROUTINE::jumpIn" ) );
522
523 args = reinterpret_cast<INVOCATION_ARGS*>(
524 libcontext::jump_fcontext( &( m_caller.ctx ), m_callee.ctx,
525 reinterpret_cast<intptr_t>( args ) )
526 );
527
528 return args;
529 }
530
531 void jumpOut()
532 {
533 INVOCATION_ARGS args{ INVOCATION_ARGS::FROM_ROUTINE, nullptr, nullptr };
534 INVOCATION_ARGS* ret;
535
536#ifdef KICAD_SANITIZE_THREADS
537 // Tell TSAN we are changing fibers back to the caller
538 __tsan_switch_to_fiber( m_caller.tsan_fiber, 0 );
539#endif
540
541 wxLogTrace( kicadTraceCoroutineStack, wxT( "COROUTINE::jumpOut" ) );
542
543 ret = reinterpret_cast<INVOCATION_ARGS*>(
544 libcontext::jump_fcontext( &( m_callee.ctx ), m_caller.ctx,
545 reinterpret_cast<intptr_t>( &args ) )
546 );
547
548 m_callContext = ret->context;
549
550 if( ret->type == INVOCATION_ARGS::FROM_ROOT )
551 {
553 }
554 }
555
556#ifndef LIBCONTEXT_HAS_OWN_STACK
558 std::unique_ptr<char[], struct STACK_DELETER> m_stack;
559#endif
560
562
563 std::function<ReturnType( ArgType )> m_func;
564
566
569 typename std::remove_reference<ArgType>::type* m_args;
570
573
576
579
580 ReturnType m_retVal;
581
582#ifdef KICAD_USE_VALGRIND
583 uint32_t m_valgrind_stack;
584#endif
585#ifdef KICAD_SANITIZE_ADDRESS
586 void* asan_stack;
587#endif
588};
589
590#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:575
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:572
bool Resume()
Resume execution of a previously yielded coroutine.
Definition: coroutine.h:318
ReturnType m_retVal
Definition: coroutine.h:580
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:495
~COROUTINE()
Definition: coroutine.h:215
CONTEXT_T m_callee
Definition: coroutine.h:578
INVOCATION_ARGS * jumpIn(INVOCATION_ARGS *args)
Definition: coroutine.h:514
std::unique_ptr< char[], struct STACK_DELETER > m_stack
Definition: coroutine.h:558
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:489
bool m_running
pointer to coroutine entry arguments.
Definition: coroutine.h:565
int m_stacksize
Definition: coroutine.h:561
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:563
static void GuardMemory(void *aAddress, size_t aGuardSize)
Definition: coroutine.h:475
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:531
std::remove_reference< ArgType >::type * m_args
saved caller context
Definition: coroutine.h:569
int m_CoroutineStackSize
Set the stack size for coroutines.
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::@34 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.