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 The 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
98 COROUTINE* destination; // stores the coroutine pointer for the stub OR the coroutine
99 // ptr for the coroutine to be resumed if a
100 // root(main-stack)-call-was initiated.
101 CALL_CONTEXT* context; // pointer to the call context of the current callgraph this
102 // call context holds a reference to the main stack context
103 };
104
106 {
107 libcontext::fcontext_t ctx; // The context itself
108
109#ifdef KICAD_SANITIZE_THREADS
110 void* tsan_fiber; // The TSAN fiber for this context
111 bool own_tsan_fiber; // Do we own this TSAN fiber? (we only delete fibers we own)
112#endif
113
115 ctx( nullptr )
116#ifdef KICAD_SANITIZE_THREADS
117 ,tsan_fiber( nullptr )
118 ,own_tsan_fiber( true )
119#endif
120 {}
121
123 {
124#ifdef KICAD_SANITIZE_THREADS
125 // Only destroy the fiber when we own it
126 if( own_tsan_fiber )
127 __tsan_destroy_fiber( tsan_fiber );
128#endif
129 }
130 };
131
133 {
134 public:
136 m_mainStackContext( nullptr )
137 {
138 }
139
141 {
143 libcontext::release_fcontext( m_mainStackContext->ctx );
144 }
145
146
147 void SetMainStack( CONTEXT_T* aStack )
148 {
149 m_mainStackContext = aStack;
150 }
151
152 void RunMainStack( COROUTINE* aCor, std::function<void()> aFunc )
153 {
154 m_mainStackFunction = std::move( aFunc );
156
157#ifdef KICAD_SANITIZE_THREADS
158 // Tell TSAN we are changing fibers
159 __tsan_switch_to_fiber( m_mainStackContext->tsan_fiber, 0 );
160#endif
161
162 libcontext::jump_fcontext( &( aCor->m_callee.ctx ), m_mainStackContext->ctx,
163 reinterpret_cast<intptr_t>( &args ) );
164 }
165
167 {
169 {
172 args = args->destination->doResume( args );
173 }
174 }
175
176 private:
178 std::function<void()> m_mainStackFunction;
179 };
180
181public:
183 COROUTINE( nullptr )
184 {
185 }
186
190 template <class T>
191 COROUTINE( T* object, ReturnType(T::*ptr)( ArgType ) ) :
192 COROUTINE( std::bind( ptr, object, std::placeholders::_1 ) )
193 {
194 }
195
199 COROUTINE( std::function<ReturnType( ArgType )> aEntry ) :
200 m_func( std::move( aEntry ) ),
201 m_running( false ),
202 m_args( nullptr ),
203 m_caller(),
204 m_callContext( nullptr ),
205 m_callee(),
206 m_retVal( 0 )
207#ifdef KICAD_USE_VALGRIND
208 ,m_valgrind_stack( 0 )
209#endif
210#ifdef KICAD_SANITIZE_ADDRESS
211 ,asan_stack( nullptr )
212#endif
213 {
215 }
216
218 {
219#ifdef KICAD_USE_VALGRIND
220 VALGRIND_STACK_DEREGISTER( m_valgrind_stack );
221#endif
222
223 if( m_caller.ctx )
224 libcontext::release_fcontext( m_caller.ctx );
225
226 if( m_callee.ctx )
227 libcontext::release_fcontext( m_callee.ctx );
228 }
229
230public:
237 void KiYield()
238 {
239 jumpOut();
240 }
241
247 void KiYield( ReturnType& aRetVal )
248 {
249 m_retVal = aRetVal;
250 jumpOut();
251 }
252
260 void RunMainStack( std::function<void()> func )
261 {
262 assert( m_callContext );
263 m_callContext->RunMainStack( this, std::move( func ) );
264 }
265
274 bool Call( ArgType aArg )
275 {
276 CALL_CONTEXT ctx;
278
279#ifdef KICAD_SANITIZE_THREADS
280 // Get the TSAN fiber for the current stack here
281 m_caller.tsan_fiber = __tsan_get_current_fiber();
282 m_caller.own_tsan_fiber = false;
283#endif
284
285 wxLogTrace( kicadTraceCoroutineStack, "COROUTINE::Call (from root)" );
286
287 ctx.Continue( doCall( &args, aArg ) );
288
289 return Running();
290 }
291
300 bool Call( const COROUTINE& aCor, ArgType aArg )
301 {
303
304 wxLogTrace( kicadTraceCoroutineStack, wxT( "COROUTINE::Call (from routine)" ) );
305
306 doCall( &args, aArg );
307
308 // we will not be asked to continue
309 return Running();
310 }
311
320 bool Resume()
321 {
322 CALL_CONTEXT ctx;
324
325#ifdef KICAD_SANITIZE_THREADS
326 // Get the TSAN fiber for the current stack here
327 m_caller.tsan_fiber = __tsan_get_current_fiber();
328 m_caller.own_tsan_fiber = false;
329#endif
330
331 wxLogTrace( kicadTraceCoroutineStack, wxT( "COROUTINE::Resume (from root)" ) );
332
333 ctx.Continue( doResume( &args ) );
334
335 return Running();
336 }
337
346 bool Resume( const COROUTINE& aCor )
347 {
349
350 wxLogTrace( kicadTraceCoroutineStack, wxT( "COROUTINE::Resume (from routine)" ) );
351
352 doResume( &args );
353
354 // we will not be asked to continue
355 return Running();
356 }
357
361 const ReturnType& ReturnValue() const
362 {
363 return m_retVal;
364 }
365
369 bool Running() const
370 {
371 return m_running;
372 }
373
374private:
375 INVOCATION_ARGS* doCall( INVOCATION_ARGS* aInvArgs, ArgType aArgs )
376 {
377 assert( m_func );
378 assert( !( m_callee.ctx ) );
379
380 m_args = &aArgs;
381
382 std::size_t stackSize = m_stacksize;
383 void* sp = nullptr;
384
385 wxLogTrace( kicadTraceCoroutineStack, wxT( "COROUTINE::doCall" ) );
386
387#ifndef LIBCONTEXT_HAS_OWN_STACK
388 assert( !m_stack );
389
390 const std::size_t systemPageSize = SystemPageSize();
391
392 // calculate the correct number of pages to allocate based on request stack size
393 std::size_t pages = ( m_stacksize + systemPageSize - 1 ) / systemPageSize;
394
395 // we allocate an extra page for the guard
396 stackSize = ( pages + 1 ) * systemPageSize;
397
398 m_stack.reset( static_cast<char*>( MapMemory( stackSize ) ) );
399 m_stack.get_deleter().SetSize( stackSize );
400
401 // now configure the first page (by only specifying a single page_size from vp)
402 // that will act as the guard page
403 // the stack will grow from the end and hopefully never into this guarded region
404 GuardMemory( m_stack.get(), systemPageSize );
405
406 sp = static_cast<char*>( m_stack.get() ) + stackSize;
407
408#ifdef KICAD_USE_VALGRIND
409 m_valgrind_stack = VALGRIND_STACK_REGISTER( sp, m_stack.get() );
410#endif
411#endif
412
413#ifdef KICAD_SANITIZE_THREADS
414 // Create a new fiber to go with the new context
415 m_callee.tsan_fiber = __tsan_create_fiber( 0 );
416 m_callee.own_tsan_fiber = true;
417
418 __tsan_set_fiber_name( m_callee.tsan_fiber, "Coroutine fiber" );
419#endif
420
421 m_callee.ctx = libcontext::make_fcontext( sp, stackSize, callerStub );
422 m_running = true;
423
424 // off we go!
425 return jumpIn( aInvArgs );
426 }
427
428#ifndef LIBCONTEXT_HAS_OWN_STACK
431 {
432#ifdef _WIN32
433 void SetSize( std::size_t ) {}
434 void operator()( void* aMem ) noexcept { ::VirtualFree( aMem, 0, MEM_RELEASE ); }
435#else
436 std::size_t m_size = 0;
437
438 void SetSize( std::size_t aSize ) { m_size = aSize; }
439 void operator()( void* aMem ) noexcept { ::munmap( aMem, m_size ); }
440#endif
441 };
442
444 static inline size_t SystemPageSize()
445 {
446 static std::optional<size_t> systemPageSize;
447
448 if( !systemPageSize.has_value() )
449 {
450#ifdef _WIN32
451 SYSTEM_INFO si;
452 ::GetSystemInfo( &si );
453 systemPageSize = static_cast<size_t>( si.dwPageSize );
454#else
455 int size = getpagesize();
456 systemPageSize = static_cast<size_t>( size );
457#endif
458 }
459
460 return systemPageSize.value();
461 }
462
464 static inline void* MapMemory( size_t aAllocSize )
465 {
466#ifdef _WIN32
467 void* mem = ::VirtualAlloc( 0, aAllocSize, MEM_COMMIT, PAGE_READWRITE );
468
469 if( !mem )
470 throw std::bad_alloc();
471#else
472 void* mem = ::mmap( 0, aAllocSize, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0 );
473
474 if( mem == (void*) -1 )
475 throw std::bad_alloc();
476#endif
477 return mem;
478 }
479
481 static inline void GuardMemory( void* aAddress, size_t aGuardSize )
482 {
483#ifdef _WIN32
484 DWORD old_prot; // dummy var since the arg cannot be NULL
485 BOOL res = ::VirtualProtect( aAddress, aGuardSize,
486 PAGE_READWRITE | PAGE_GUARD, &old_prot );
487#else
488 bool res = ( 0 == ::mprotect( aAddress, aGuardSize, PROT_NONE ) );
489#endif
490 if( !res )
491 wxLogTrace( kicadTraceCoroutineStack, wxT( "COROUTINE::GuardMemory has failed" ) );
492 }
493#endif // LIBCONTEXT_HAS_OWN_STACK
494
496 {
497 return jumpIn( args );
498 }
499
500 // real entry point of the coroutine
501 static void callerStub( intptr_t aData )
502 {
503 INVOCATION_ARGS& args = *reinterpret_cast<INVOCATION_ARGS*>( aData );
504
505 // get pointer to self
506 COROUTINE* cor = args.destination;
507 cor->m_callContext = args.context;
508
509 if( args.type == INVOCATION_ARGS::FROM_ROOT )
510 cor->m_callContext->SetMainStack( &cor->m_caller );
511
512 // call the coroutine method
513 cor->m_retVal = cor->m_func( *(cor->m_args) );
514 cor->m_running = false;
515
516 // go back to wherever we came from.
517 cor->jumpOut();
518 }
519
521 {
522#ifdef KICAD_SANITIZE_THREADS
523 // Tell TSAN we are changing fibers to the callee
524 __tsan_switch_to_fiber( m_callee.tsan_fiber, 0 );
525#endif
526
527 wxLogTrace( kicadTraceCoroutineStack, wxT( "COROUTINE::jumpIn" ) );
528
529 args = reinterpret_cast<INVOCATION_ARGS*>(
530 libcontext::jump_fcontext( &( m_caller.ctx ), m_callee.ctx,
531 reinterpret_cast<intptr_t>( args ) )
532 );
533
534 return args;
535 }
536
537 void jumpOut()
538 {
539 INVOCATION_ARGS args{ INVOCATION_ARGS::FROM_ROUTINE, nullptr, nullptr };
540 INVOCATION_ARGS* ret;
541
542#ifdef KICAD_SANITIZE_THREADS
543 // Tell TSAN we are changing fibers back to the caller
544 __tsan_switch_to_fiber( m_caller.tsan_fiber, 0 );
545#endif
546
547 wxLogTrace( kicadTraceCoroutineStack, wxT( "COROUTINE::jumpOut" ) );
548
549 ret = reinterpret_cast<INVOCATION_ARGS*>(
550 libcontext::jump_fcontext( &( m_callee.ctx ), m_caller.ctx,
551 reinterpret_cast<intptr_t>( &args ) )
552 );
553
554 m_callContext = ret->context;
555
556 if( ret->type == INVOCATION_ARGS::FROM_ROOT )
557 {
559 }
560 }
561
562#ifndef LIBCONTEXT_HAS_OWN_STACK
564 std::unique_ptr<char[], struct STACK_DELETER> m_stack;
565#endif
566
568
569 std::function<ReturnType( ArgType )> m_func;
570
572
574 typename std::remove_reference<ArgType>::type* m_args;
575
578
581
584
585 ReturnType m_retVal;
586
587#ifdef KICAD_USE_VALGRIND
588 uint32_t m_valgrind_stack;
589#endif
590
591#ifdef KICAD_SANITIZE_ADDRESS
592 void* asan_stack;
593#endif
594};
595
596#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:177
void SetMainStack(CONTEXT_T *aStack)
Definition: coroutine.h:147
void RunMainStack(COROUTINE *aCor, std::function< void()> aFunc)
Definition: coroutine.h:152
void Continue(INVOCATION_ARGS *args)
Definition: coroutine.h:166
std::function< void()> m_mainStackFunction
Definition: coroutine.h:178
Implement a coroutine.
Definition: coroutine.h:84
bool Call(ArgType aArg)
Start execution of a coroutine, passing args as its arguments.
Definition: coroutine.h:274
CALL_CONTEXT * m_callContext
Main stack information.
Definition: coroutine.h:580
void KiYield(ReturnType &aRetVal)
KiYield with a value.
Definition: coroutine.h:247
void KiYield()
Stop execution of the coroutine and returns control to the caller.
Definition: coroutine.h:237
CONTEXT_T m_caller
Saved caller context.
Definition: coroutine.h:577
bool Resume()
Resume execution of a previously yielded coroutine.
Definition: coroutine.h:320
ReturnType m_retVal
Definition: coroutine.h:585
COROUTINE(std::function< ReturnType(ArgType)> aEntry)
Create a coroutine from a delegate object.
Definition: coroutine.h:199
static void callerStub(intptr_t aData)
Definition: coroutine.h:501
~COROUTINE()
Definition: coroutine.h:217
CONTEXT_T m_callee
Saved coroutine context.
Definition: coroutine.h:583
INVOCATION_ARGS * jumpIn(INVOCATION_ARGS *args)
Definition: coroutine.h:520
std::unique_ptr< char[], struct STACK_DELETER > m_stack
Coroutine stack.
Definition: coroutine.h:564
const ReturnType & ReturnValue() const
Return the yielded value (the argument KiYield() was called with).
Definition: coroutine.h:361
INVOCATION_ARGS * doResume(INVOCATION_ARGS *args)
Definition: coroutine.h:495
bool m_running
Definition: coroutine.h:571
int m_stacksize
Definition: coroutine.h:567
static void * MapMemory(size_t aAllocSize)
Map a page-aligned memory region into our address space.
Definition: coroutine.h:464
static size_t SystemPageSize()
The size of the mappable memory page size.
Definition: coroutine.h:444
INVOCATION_ARGS * doCall(INVOCATION_ARGS *aInvArgs, ArgType aArgs)
Definition: coroutine.h:375
bool Call(const COROUTINE &aCor, ArgType aArg)
Start execution of a coroutine, passing args as its arguments.
Definition: coroutine.h:300
std::function< ReturnType(ArgType)> m_func
Definition: coroutine.h:569
static void GuardMemory(void *aAddress, size_t aGuardSize)
Change protection of memory page(s) to act as stack guards.
Definition: coroutine.h:481
bool Resume(const COROUTINE &aCor)
Resume execution of a previously yielded coroutine.
Definition: coroutine.h:346
COROUTINE(T *object, ReturnType(T::*ptr)(ArgType))
Create a coroutine from a member method of an object.
Definition: coroutine.h:191
bool Running() const
Definition: coroutine.h:369
void RunMainStack(std::function< void()> func)
Run a functor inside the application main stack context.
Definition: coroutine.h:260
void jumpOut()
Definition: coroutine.h:537
std::remove_reference< ArgType >::type * m_args
Pointer to coroutine entry arguments stripped of references to avoid compiler errors.
Definition: coroutine.h:574
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:107
enum COROUTINE::INVOCATION_ARGS::@34 type
CALL_CONTEXT * context
Definition: coroutine.h:101
A functor that frees the stack.
Definition: coroutine.h:431
void SetSize(std::size_t aSize)
Definition: coroutine.h:438
void operator()(void *aMem) noexcept
Definition: coroutine.h:439
VECTOR3I res
wxLogTrace helper definitions.