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
81
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#ifdef KICAD_SANITIZE_ADDRESS
163 void* fake_stack_save = nullptr;
164 __sanitizer_start_switch_fiber( &fake_stack_save, nullptr, 0 );
165#endif
166
167 libcontext::jump_fcontext( &( aCor->m_callee.ctx ), m_mainStackContext->ctx,
168 reinterpret_cast<intptr_t>( &args ) );
169
170#ifdef KICAD_SANITIZE_ADDRESS
171 __sanitizer_finish_switch_fiber( fake_stack_save, nullptr, nullptr );
172#endif
173 }
174
176 {
178 {
181 args = args->destination->doResume( args );
182 }
183 }
184
185 private:
187 std::function<void()> m_mainStackFunction;
188 };
189
190public:
192 COROUTINE( nullptr )
193 {
194 }
195
199 template <class T>
200 COROUTINE( T* object, ReturnType(T::*ptr)( ArgType ) ) :
201 COROUTINE( std::bind( ptr, object, std::placeholders::_1 ) )
202 {
203 }
204
208 COROUTINE( std::function<ReturnType( ArgType )> aEntry ) :
209 m_func( std::move( aEntry ) ),
210 m_running( false ),
211 m_args( nullptr ),
212 m_caller(),
213 m_callContext( nullptr ),
214 m_callee(),
215 m_retVal( 0 )
216#ifdef KICAD_USE_VALGRIND
217 ,m_valgrind_stack( 0 )
218#endif
219#ifdef KICAD_SANITIZE_ADDRESS
220 ,m_asanStackBottom( nullptr )
221 ,m_asanStackSize( 0 )
222 ,m_asanCallerStackBottom( nullptr )
223 ,m_asanCallerStackSize( 0 )
224#endif
225 {
227 }
228
230 {
231#ifdef KICAD_USE_VALGRIND
232 VALGRIND_STACK_DEREGISTER( m_valgrind_stack );
233#endif
234
235 if( m_caller.ctx )
236 libcontext::release_fcontext( m_caller.ctx );
237
238 if( m_callee.ctx )
239 libcontext::release_fcontext( m_callee.ctx );
240 }
241
242public:
249 void KiYield()
250 {
251 jumpOut();
252 }
253
259 void KiYield( ReturnType& aRetVal )
260 {
261 m_retVal = aRetVal;
262 jumpOut();
263 }
264
272 void RunMainStack( std::function<void()> func )
273 {
274 assert( m_callContext );
275 m_callContext->RunMainStack( this, std::move( func ) );
276 }
277
286 bool Call( ArgType aArg )
287 {
288 CALL_CONTEXT ctx;
289 INVOCATION_ARGS args{ INVOCATION_ARGS::FROM_ROOT, this, &ctx };
290
291#ifdef KICAD_SANITIZE_THREADS
292 // Get the TSAN fiber for the current stack here
293 m_caller.tsan_fiber = __tsan_get_current_fiber();
294 m_caller.own_tsan_fiber = false;
295#endif
296
297 wxLogTrace( kicadTraceCoroutineStack, "COROUTINE::Call (from root)" );
298
299 ctx.Continue( doCall( &args, aArg ) );
300
301 return Running();
302 }
303
312 bool Call( const COROUTINE& aCor, ArgType aArg )
313 {
314 INVOCATION_ARGS args{ INVOCATION_ARGS::FROM_ROUTINE, this, aCor.m_callContext };
315
316 wxLogTrace( kicadTraceCoroutineStack, wxT( "COROUTINE::Call (from routine)" ) );
317
318 doCall( &args, aArg );
319
320 // we will not be asked to continue
321 return Running();
322 }
323
332 bool Resume()
333 {
334 CALL_CONTEXT ctx;
335 INVOCATION_ARGS args{ INVOCATION_ARGS::FROM_ROOT, this, &ctx };
336
337#ifdef KICAD_SANITIZE_THREADS
338 // Get the TSAN fiber for the current stack here
339 m_caller.tsan_fiber = __tsan_get_current_fiber();
340 m_caller.own_tsan_fiber = false;
341#endif
342
343 wxLogTrace( kicadTraceCoroutineStack, wxT( "COROUTINE::Resume (from root)" ) );
344
345 ctx.Continue( doResume( &args ) );
346
347 return Running();
348 }
349
358 bool Resume( const COROUTINE& aCor )
359 {
360 INVOCATION_ARGS args{ INVOCATION_ARGS::FROM_ROUTINE, this, aCor.m_callContext };
361
362 wxLogTrace( kicadTraceCoroutineStack, wxT( "COROUTINE::Resume (from routine)" ) );
363
364 doResume( &args );
365
366 // we will not be asked to continue
367 return Running();
368 }
369
373 const ReturnType& ReturnValue() const
374 {
375 return m_retVal;
376 }
377
381 bool Running() const
382 {
383 return m_running;
384 }
385
386private:
387 INVOCATION_ARGS* doCall( INVOCATION_ARGS* aInvArgs, ArgType aArgs )
388 {
389 assert( m_func );
390 assert( !( m_callee.ctx ) );
391
392 m_args = &aArgs;
393
394 std::size_t stackSize = m_stacksize;
395 void* sp = nullptr;
396
397 wxLogTrace( kicadTraceCoroutineStack, wxT( "COROUTINE::doCall" ) );
398
399#ifndef LIBCONTEXT_HAS_OWN_STACK
400 assert( !m_stack );
401
402 const std::size_t systemPageSize = SystemPageSize();
403
404 // calculate the correct number of pages to allocate based on request stack size
405 std::size_t pages = ( m_stacksize + systemPageSize - 1 ) / systemPageSize;
406
407 // we allocate an extra page for the guard
408 stackSize = ( pages + 1 ) * systemPageSize;
409
410 m_stack.reset( static_cast<char*>( MapMemory( stackSize ) ) );
411 m_stack.get_deleter().SetSize( stackSize );
412
413 // now configure the first page (by only specifying a single page_size from vp)
414 // that will act as the guard page
415 // the stack will grow from the end and hopefully never into this guarded region
416 GuardMemory( m_stack.get(), systemPageSize );
417
418 sp = static_cast<char*>( m_stack.get() ) + stackSize;
419
420#ifdef KICAD_USE_VALGRIND
421 m_valgrind_stack = VALGRIND_STACK_REGISTER( sp, m_stack.get() );
422#endif
423#endif
424
425#ifdef KICAD_SANITIZE_THREADS
426 // Create a new fiber to go with the new context
427 m_callee.tsan_fiber = __tsan_create_fiber( 0 );
428 m_callee.own_tsan_fiber = true;
429
430 __tsan_set_fiber_name( m_callee.tsan_fiber, "Coroutine fiber" );
431#endif
432
433#if defined( KICAD_SANITIZE_ADDRESS ) && !defined( LIBCONTEXT_HAS_OWN_STACK )
434 m_asanStackBottom = m_stack.get();
435 m_asanStackSize = stackSize;
436#endif
437
438 m_callee.ctx = libcontext::make_fcontext( sp, stackSize, callerStub );
439 m_running = true;
440
441 // off we go!
442 return jumpIn( aInvArgs );
443 }
444
445#ifndef LIBCONTEXT_HAS_OWN_STACK
448 {
449#ifdef _WIN32
450 void SetSize( std::size_t ) {}
451 void operator()( void* aMem ) noexcept { ::VirtualFree( aMem, 0, MEM_RELEASE ); }
452#else
453 std::size_t m_size = 0;
454
455 void SetSize( std::size_t aSize ) { m_size = aSize; }
456 void operator()( void* aMem ) noexcept { ::munmap( aMem, m_size ); }
457#endif
458 };
459
461 static inline size_t SystemPageSize()
462 {
463 static std::optional<size_t> systemPageSize;
464
465 if( !systemPageSize.has_value() )
466 {
467#ifdef _WIN32
468 SYSTEM_INFO si;
469 ::GetSystemInfo( &si );
470 systemPageSize = static_cast<size_t>( si.dwPageSize );
471#else
472 int size = getpagesize();
473 systemPageSize = static_cast<size_t>( size );
474#endif
475 }
476
477 return systemPageSize.value();
478 }
479
481 static inline void* MapMemory( size_t aAllocSize )
482 {
483#ifdef _WIN32
484 void* mem = ::VirtualAlloc( 0, aAllocSize, MEM_COMMIT, PAGE_READWRITE );
485
486 if( !mem )
487 throw std::bad_alloc();
488#else
489 void* mem = ::mmap( 0, aAllocSize, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0 );
490
491 if( mem == (void*) -1 )
492 throw std::bad_alloc();
493#endif
494 return mem;
495 }
496
498 static inline void GuardMemory( void* aAddress, size_t aGuardSize )
499 {
500#ifdef _WIN32
501 DWORD old_prot; // dummy var since the arg cannot be NULL
502 BOOL res = ::VirtualProtect( aAddress, aGuardSize,
503 PAGE_READWRITE | PAGE_GUARD, &old_prot );
504#else
505 bool res = ( 0 == ::mprotect( aAddress, aGuardSize, PROT_NONE ) );
506#endif
507 if( !res )
508 wxLogTrace( kicadTraceCoroutineStack, wxT( "COROUTINE::GuardMemory has failed" ) );
509 }
510#endif // LIBCONTEXT_HAS_OWN_STACK
511
512 INVOCATION_ARGS* doResume( INVOCATION_ARGS* args )
513 {
514 return jumpIn( args );
515 }
516
517 // real entry point of the coroutine
518 static void callerStub( intptr_t aData )
519 {
520#ifdef KICAD_SANITIZE_ADDRESS
521 const void* caller_bottom = nullptr;
522 size_t caller_size = 0;
523 __sanitizer_finish_switch_fiber( nullptr, &caller_bottom, &caller_size );
524#endif
525
526 INVOCATION_ARGS& args = *reinterpret_cast<INVOCATION_ARGS*>( aData );
527
528 // get pointer to self
529 COROUTINE* cor = args.destination;
530
531#ifdef KICAD_SANITIZE_ADDRESS
532 cor->m_asanCallerStackBottom = caller_bottom;
533 cor->m_asanCallerStackSize = caller_size;
534#endif
535 cor->m_callContext = args.context;
536
537 if( args.type == INVOCATION_ARGS::FROM_ROOT )
538 cor->m_callContext->SetMainStack( &cor->m_caller );
539
540 // call the coroutine method
541 cor->m_retVal = cor->m_func( *(cor->m_args) );
542 cor->m_running = false;
543
544 // go back to wherever we came from.
545 cor->jumpOut();
546 }
547
548 INVOCATION_ARGS* jumpIn( INVOCATION_ARGS* args )
549 {
550#ifdef KICAD_SANITIZE_THREADS
551 // Tell TSAN we are changing fibers to the callee
552 __tsan_switch_to_fiber( m_callee.tsan_fiber, 0 );
553#endif
554
555#ifdef KICAD_SANITIZE_ADDRESS
556 void* fake_stack_save = nullptr;
557 __sanitizer_start_switch_fiber( &fake_stack_save, m_asanStackBottom, m_asanStackSize );
558#endif
559
560 wxLogTrace( kicadTraceCoroutineStack, wxT( "COROUTINE::jumpIn" ) );
561
562 args = reinterpret_cast<INVOCATION_ARGS*>(
563 libcontext::jump_fcontext( &( m_caller.ctx ), m_callee.ctx,
564 reinterpret_cast<intptr_t>( args ) )
565 );
566
567#ifdef KICAD_SANITIZE_ADDRESS
568 __sanitizer_finish_switch_fiber( fake_stack_save, nullptr, nullptr );
569#endif
570
571 return args;
572 }
573
574 void jumpOut()
575 {
576 INVOCATION_ARGS args{ INVOCATION_ARGS::FROM_ROUTINE, nullptr, nullptr };
577 INVOCATION_ARGS* ret;
578
579#ifdef KICAD_SANITIZE_THREADS
580 // Tell TSAN we are changing fibers back to the caller
581 __tsan_switch_to_fiber( m_caller.tsan_fiber, 0 );
582#endif
583
584#ifdef KICAD_SANITIZE_ADDRESS
585 void* fake_stack_save = nullptr;
586 __sanitizer_start_switch_fiber( &fake_stack_save,
587 m_asanCallerStackBottom, m_asanCallerStackSize );
588#endif
589
590 wxLogTrace( kicadTraceCoroutineStack, wxT( "COROUTINE::jumpOut" ) );
591
592 ret = reinterpret_cast<INVOCATION_ARGS*>(
593 libcontext::jump_fcontext( &( m_callee.ctx ), m_caller.ctx,
594 reinterpret_cast<intptr_t>( &args ) )
595 );
596
597#ifdef KICAD_SANITIZE_ADDRESS
598 __sanitizer_finish_switch_fiber( fake_stack_save,
599 &m_asanCallerStackBottom, &m_asanCallerStackSize );
600#endif
601
602 m_callContext = ret->context;
603
604 if( ret->type == INVOCATION_ARGS::FROM_ROOT )
605 {
606 m_callContext->SetMainStack( &m_caller );
607 }
608 }
609
610#ifndef LIBCONTEXT_HAS_OWN_STACK
612 std::unique_ptr<char[], struct STACK_DELETER> m_stack;
613#endif
614
616
617 std::function<ReturnType( ArgType )> m_func;
618
620
622 typename std::remove_reference<ArgType>::type* m_args;
623
625 CONTEXT_T m_caller;
626
628 CALL_CONTEXT* m_callContext;
629
631 CONTEXT_T m_callee;
632
633 ReturnType m_retVal;
634
635#ifdef KICAD_USE_VALGRIND
636 uint32_t m_valgrind_stack;
637#endif
638
639#ifdef KICAD_SANITIZE_ADDRESS
640 const void* m_asanStackBottom;
641 size_t m_asanStackSize;
642 const void* m_asanCallerStackBottom;
643 size_t m_asanCallerStackSize;
644#endif
645};
646
647#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:186
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:175
std::function< void()> m_mainStackFunction
Definition coroutine.h:187
bool Call(ArgType aArg)
Start execution of a coroutine, passing args as its arguments.
Definition coroutine.h:286
CALL_CONTEXT * m_callContext
Definition coroutine.h:628
void KiYield(ReturnType &aRetVal)
KiYield with a value.
Definition coroutine.h:259
void KiYield()
Stop execution of the coroutine and returns control to the caller.
Definition coroutine.h:249
bool Resume()
Resume execution of a previously yielded coroutine.
Definition coroutine.h:332
COROUTINE(std::function< ReturnType(ArgType)> aEntry)
Create a coroutine from a delegate object.
Definition coroutine.h:208
static void callerStub(intptr_t aData)
Definition coroutine.h:518
CONTEXT_T m_callee
Saved coroutine context.
Definition coroutine.h:631
INVOCATION_ARGS * jumpIn(INVOCATION_ARGS *args)
Definition coroutine.h:548
std::unique_ptr< char[], struct STACK_DELETER > m_stack
Definition coroutine.h:612
const ReturnType & ReturnValue() const
Return the yielded value (the argument KiYield() was called with).
Definition coroutine.h:373
INVOCATION_ARGS * doResume(INVOCATION_ARGS *args)
Definition coroutine.h:512
static void * MapMemory(size_t aAllocSize)
Map a page-aligned memory region into our address space.
Definition coroutine.h:481
static size_t SystemPageSize()
The size of the mappable memory page size.
Definition coroutine.h:461
INVOCATION_ARGS * doCall(INVOCATION_ARGS *aInvArgs, ArgType aArgs)
Definition coroutine.h:387
bool Call(const COROUTINE &aCor, ArgType aArg)
Start execution of a coroutine, passing args as its arguments.
Definition coroutine.h:312
std::function< int(int)> m_func
Definition coroutine.h:617
static void GuardMemory(void *aAddress, size_t aGuardSize)
Change protection of memory page(s) to act as stack guards.
Definition coroutine.h:498
bool Resume(const COROUTINE &aCor)
Resume execution of a previously yielded coroutine.
Definition coroutine.h:358
COROUTINE(T *object, ReturnType(T::*ptr)(ArgType))
Create a coroutine from a member method of an object.
Definition coroutine.h:200
bool Running() const
Definition coroutine.h:381
void RunMainStack(std::function< void()> func)
Run a functor inside the application main stack context.
Definition coroutine.h:272
void jumpOut()
Definition coroutine.h:574
std::remove_reference< int >::type * m_args
Definition coroutine.h:622
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::@361002175157160365344154346100200253026233123042 type
A functor that frees the stack.
Definition coroutine.h:448
void SetSize(std::size_t aSize)
Definition coroutine.h:455
void operator()(void *aMem) noexcept
Definition coroutine.h:456
VECTOR3I res
wxLogTrace helper definitions.