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, see <https://www.gnu.org/licenses/>.
21 */
22
23#ifndef __COROUTINE_H
24#define __COROUTINE_H
25
26#include <cassert>
27#include <cstdlib>
28#include <type_traits>
29
30#ifdef KICAD_USE_VALGRIND
31#include <valgrind/valgrind.h>
32#endif
33#ifdef KICAD_SANITIZE_THREADS
34#include <sanitizer/tsan_interface.h>
35#endif
36#ifdef KICAD_SANITIZE_ADDRESS
37#include <sanitizer/asan_interface.h>
38#endif
39
40#include <libcontext.h>
41#include <functional>
42#include <optional>
43#include <memory>
44#include <advanced_config.h>
45
46#include <trace_helpers.h>
47#include <wx/log.h>
48
49#ifdef _WIN32
50#include <windows.h>
51#else // Linux, BSD, MacOS
52#include <unistd.h> // getpagesize
53#include <sys/mman.h> // mmap, mprotect, munmap
54#endif
55
77
78template <typename ReturnType, typename ArgType>
80{
81private:
82 class CALL_CONTEXT;
83
85 {
86 enum
87 {
88 FROM_ROOT, // a stub was called/a coroutine was resumed from the main-stack context
89 FROM_ROUTINE, // a stub was called/a coroutine was resumed from a coroutine context
90 CONTINUE_AFTER_ROOT // a function sent a request to invoke a function on the main
91 // stack context
92 } type; // invocation type
93
94 COROUTINE* destination; // stores the coroutine pointer for the stub OR the coroutine
95 // ptr for the coroutine to be resumed if a
96 // root(main-stack)-call-was initiated.
97 CALL_CONTEXT* context; // pointer to the call context of the current callgraph this
98 // call context holds a reference to the main stack context
99 };
100
102 {
103 libcontext::fcontext_t ctx; // The context itself
104
105#ifdef KICAD_SANITIZE_THREADS
106 void* tsan_fiber; // The TSAN fiber for this context
107 bool own_tsan_fiber; // Do we own this TSAN fiber? (we only delete fibers we own)
108#endif
109
111 ctx( nullptr )
112#ifdef KICAD_SANITIZE_THREADS
113 ,tsan_fiber( nullptr )
114 ,own_tsan_fiber( true )
115#endif
116 {}
117
119 {
120#ifdef KICAD_SANITIZE_THREADS
121 // Only destroy the fiber when we own it
122 if( own_tsan_fiber )
123 __tsan_destroy_fiber( tsan_fiber );
124#endif
125 }
126 };
127
129 {
130 public:
132 m_mainStackContext( nullptr )
133 {
134 }
135
137 {
139 libcontext::release_fcontext( m_mainStackContext->ctx );
140 }
141
142
143 void SetMainStack( CONTEXT_T* aStack )
144 {
145 m_mainStackContext = aStack;
146 }
147
148 void RunMainStack( COROUTINE* aCor, std::function<void()> aFunc )
149 {
150 m_mainStackFunction = std::move( aFunc );
152
153#ifdef KICAD_SANITIZE_THREADS
154 // Tell TSAN we are changing fibers
155 __tsan_switch_to_fiber( m_mainStackContext->tsan_fiber, 0 );
156#endif
157
158#ifdef KICAD_SANITIZE_ADDRESS
159 void* fake_stack_save = nullptr;
160 __sanitizer_start_switch_fiber( &fake_stack_save, nullptr, 0 );
161#endif
162
163 libcontext::jump_fcontext( &( aCor->m_callee.ctx ), m_mainStackContext->ctx,
164 reinterpret_cast<intptr_t>( &args ) );
165
166#ifdef KICAD_SANITIZE_ADDRESS
167 __sanitizer_finish_switch_fiber( fake_stack_save, nullptr, nullptr );
168#endif
169 }
170
172 {
174 {
177 args = args->destination->doResume( args );
178 }
179 }
180
181 private:
183 std::function<void()> m_mainStackFunction;
184 };
185
186public:
188 COROUTINE( nullptr )
189 {
190 }
191
195 template <class T>
196 COROUTINE( T* object, ReturnType(T::*ptr)( ArgType ) ) :
197 COROUTINE( std::bind( ptr, object, std::placeholders::_1 ) )
198 {
199 }
200
204 COROUTINE( std::function<ReturnType( ArgType )> aEntry ) :
205 m_func( std::move( aEntry ) ),
206 m_running( false ),
207 m_args( nullptr ),
208 m_caller(),
209 m_callContext( nullptr ),
210 m_callee(),
211 m_retVal( 0 )
212#ifdef KICAD_USE_VALGRIND
213 ,m_valgrind_stack( 0 )
214#endif
215#ifdef KICAD_SANITIZE_ADDRESS
216 ,m_asanStackBottom( nullptr )
217 ,m_asanStackSize( 0 )
218 ,m_asanCallerStackBottom( nullptr )
219 ,m_asanCallerStackSize( 0 )
220#endif
221 {
223 }
224
226 {
227#ifdef KICAD_USE_VALGRIND
228 VALGRIND_STACK_DEREGISTER( m_valgrind_stack );
229#endif
230
231 if( m_caller.ctx )
232 libcontext::release_fcontext( m_caller.ctx );
233
234 if( m_callee.ctx )
235 libcontext::release_fcontext( m_callee.ctx );
236 }
237
238public:
245 void KiYield()
246 {
247 jumpOut();
248 }
249
255 void KiYield( ReturnType& aRetVal )
256 {
257 m_retVal = aRetVal;
258 jumpOut();
259 }
260
268 void RunMainStack( std::function<void()> func )
269 {
270 assert( m_callContext );
271 m_callContext->RunMainStack( this, std::move( func ) );
272 }
273
282 bool Call( ArgType aArg )
283 {
284 CALL_CONTEXT ctx;
285 INVOCATION_ARGS args{ INVOCATION_ARGS::FROM_ROOT, this, &ctx };
286
287#ifdef KICAD_SANITIZE_THREADS
288 // Get the TSAN fiber for the current stack here
289 m_caller.tsan_fiber = __tsan_get_current_fiber();
290 m_caller.own_tsan_fiber = false;
291#endif
292
293 wxLogTrace( kicadTraceCoroutineStack, "COROUTINE::Call (from root)" );
294
295 ctx.Continue( doCall( &args, aArg ) );
296
297 return Running();
298 }
299
308 bool Call( const COROUTINE& aCor, ArgType aArg )
309 {
310 INVOCATION_ARGS args{ INVOCATION_ARGS::FROM_ROUTINE, this, aCor.m_callContext };
311
312 wxLogTrace( kicadTraceCoroutineStack, wxT( "COROUTINE::Call (from routine)" ) );
313
314 doCall( &args, aArg );
315
316 // we will not be asked to continue
317 return Running();
318 }
319
328 bool Resume()
329 {
330 CALL_CONTEXT ctx;
331 INVOCATION_ARGS args{ INVOCATION_ARGS::FROM_ROOT, this, &ctx };
332
333#ifdef KICAD_SANITIZE_THREADS
334 // Get the TSAN fiber for the current stack here
335 m_caller.tsan_fiber = __tsan_get_current_fiber();
336 m_caller.own_tsan_fiber = false;
337#endif
338
339 wxLogTrace( kicadTraceCoroutineStack, wxT( "COROUTINE::Resume (from root)" ) );
340
341 ctx.Continue( doResume( &args ) );
342
343 return Running();
344 }
345
354 bool Resume( const COROUTINE& aCor )
355 {
356 INVOCATION_ARGS args{ INVOCATION_ARGS::FROM_ROUTINE, this, aCor.m_callContext };
357
358 wxLogTrace( kicadTraceCoroutineStack, wxT( "COROUTINE::Resume (from routine)" ) );
359
360 doResume( &args );
361
362 // we will not be asked to continue
363 return Running();
364 }
365
369 const ReturnType& ReturnValue() const
370 {
371 return m_retVal;
372 }
373
377 bool Running() const
378 {
379 return m_running;
380 }
381
382private:
383 INVOCATION_ARGS* doCall( INVOCATION_ARGS* aInvArgs, ArgType aArgs )
384 {
385 assert( m_func );
386 assert( !( m_callee.ctx ) );
387
388 m_args = &aArgs;
389
390 std::size_t stackSize = m_stacksize;
391 void* sp = nullptr;
392
393 wxLogTrace( kicadTraceCoroutineStack, wxT( "COROUTINE::doCall" ) );
394
395#ifndef LIBCONTEXT_HAS_OWN_STACK
396 assert( !m_stack );
397
398 const std::size_t systemPageSize = SystemPageSize();
399
400 // calculate the correct number of pages to allocate based on request stack size
401 std::size_t pages = ( m_stacksize + systemPageSize - 1 ) / systemPageSize;
402
403 // we allocate an extra page for the guard
404 stackSize = ( pages + 1 ) * systemPageSize;
405
406 m_stack.reset( static_cast<char*>( MapMemory( stackSize ) ) );
407 m_stack.get_deleter().SetSize( stackSize );
408
409 // now configure the first page (by only specifying a single page_size from vp)
410 // that will act as the guard page
411 // the stack will grow from the end and hopefully never into this guarded region
412 GuardMemory( m_stack.get(), systemPageSize );
413
414 sp = static_cast<char*>( m_stack.get() ) + stackSize;
415
416#ifdef KICAD_USE_VALGRIND
417 m_valgrind_stack = VALGRIND_STACK_REGISTER( sp, m_stack.get() );
418#endif
419#endif
420
421#ifdef KICAD_SANITIZE_THREADS
422 // Create a new fiber to go with the new context
423 m_callee.tsan_fiber = __tsan_create_fiber( 0 );
424 m_callee.own_tsan_fiber = true;
425
426 __tsan_set_fiber_name( m_callee.tsan_fiber, "Coroutine fiber" );
427#endif
428
429#if defined( KICAD_SANITIZE_ADDRESS ) && !defined( LIBCONTEXT_HAS_OWN_STACK )
430 m_asanStackBottom = m_stack.get();
431 m_asanStackSize = stackSize;
432#endif
433
434 m_callee.ctx = libcontext::make_fcontext( sp, stackSize, callerStub );
435 m_running = true;
436
437 // off we go!
438 return jumpIn( aInvArgs );
439 }
440
441#ifndef LIBCONTEXT_HAS_OWN_STACK
444 {
445#ifdef _WIN32
446 void SetSize( std::size_t ) {}
447 void operator()( void* aMem ) noexcept { ::VirtualFree( aMem, 0, MEM_RELEASE ); }
448#else
449 std::size_t m_size = 0;
450
451 void SetSize( std::size_t aSize ) { m_size = aSize; }
452 void operator()( void* aMem ) noexcept { ::munmap( aMem, m_size ); }
453#endif
454 };
455
457 static inline size_t SystemPageSize()
458 {
459 static std::optional<size_t> systemPageSize;
460
461 if( !systemPageSize.has_value() )
462 {
463#ifdef _WIN32
464 SYSTEM_INFO si;
465 ::GetSystemInfo( &si );
466 systemPageSize = static_cast<size_t>( si.dwPageSize );
467#else
468 int size = getpagesize();
469 systemPageSize = static_cast<size_t>( size );
470#endif
471 }
472
473 return systemPageSize.value();
474 }
475
477 static inline void* MapMemory( size_t aAllocSize )
478 {
479#ifdef _WIN32
480 void* mem = ::VirtualAlloc( 0, aAllocSize, MEM_COMMIT, PAGE_READWRITE );
481
482 if( !mem )
483 throw std::bad_alloc();
484#else
485 void* mem = ::mmap( 0, aAllocSize, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0 );
486
487 if( mem == (void*) -1 )
488 throw std::bad_alloc();
489#endif
490 return mem;
491 }
492
494 static inline void GuardMemory( void* aAddress, size_t aGuardSize )
495 {
496#ifdef _WIN32
497 DWORD old_prot; // dummy var since the arg cannot be NULL
498 BOOL res = ::VirtualProtect( aAddress, aGuardSize,
499 PAGE_READWRITE | PAGE_GUARD, &old_prot );
500#else
501 bool res = ( 0 == ::mprotect( aAddress, aGuardSize, PROT_NONE ) );
502#endif
503 if( !res )
504 wxLogTrace( kicadTraceCoroutineStack, wxT( "COROUTINE::GuardMemory has failed" ) );
505 }
506#endif // LIBCONTEXT_HAS_OWN_STACK
507
508 INVOCATION_ARGS* doResume( INVOCATION_ARGS* args )
509 {
510 return jumpIn( args );
511 }
512
513 // real entry point of the coroutine
514 static void callerStub( intptr_t aData )
515 {
516#ifdef KICAD_SANITIZE_ADDRESS
517 const void* caller_bottom = nullptr;
518 size_t caller_size = 0;
519 __sanitizer_finish_switch_fiber( nullptr, &caller_bottom, &caller_size );
520#endif
521
522 INVOCATION_ARGS& args = *reinterpret_cast<INVOCATION_ARGS*>( aData );
523
524 // get pointer to self
525 COROUTINE* cor = args.destination;
526
527#ifdef KICAD_SANITIZE_ADDRESS
528 cor->m_asanCallerStackBottom = caller_bottom;
529 cor->m_asanCallerStackSize = caller_size;
530#endif
531 cor->m_callContext = args.context;
532
533 if( args.type == INVOCATION_ARGS::FROM_ROOT )
534 cor->m_callContext->SetMainStack( &cor->m_caller );
535
536 // call the coroutine method
537 cor->m_retVal = cor->m_func( *(cor->m_args) );
538 cor->m_running = false;
539
540 // go back to wherever we came from.
541 cor->jumpOut();
542 }
543
544 INVOCATION_ARGS* jumpIn( INVOCATION_ARGS* args )
545 {
546#ifdef KICAD_SANITIZE_THREADS
547 // Tell TSAN we are changing fibers to the callee
548 __tsan_switch_to_fiber( m_callee.tsan_fiber, 0 );
549#endif
550
551#ifdef KICAD_SANITIZE_ADDRESS
552 void* fake_stack_save = nullptr;
553 __sanitizer_start_switch_fiber( &fake_stack_save, m_asanStackBottom, m_asanStackSize );
554#endif
555
556 wxLogTrace( kicadTraceCoroutineStack, wxT( "COROUTINE::jumpIn" ) );
557
558 args = reinterpret_cast<INVOCATION_ARGS*>(
559 libcontext::jump_fcontext( &( m_caller.ctx ), m_callee.ctx,
560 reinterpret_cast<intptr_t>( args ) )
561 );
562
563#ifdef KICAD_SANITIZE_ADDRESS
564 __sanitizer_finish_switch_fiber( fake_stack_save, nullptr, nullptr );
565#endif
566
567 return args;
568 }
569
570 void jumpOut()
571 {
572 INVOCATION_ARGS args{ INVOCATION_ARGS::FROM_ROUTINE, nullptr, nullptr };
573 INVOCATION_ARGS* ret;
574
575#ifdef KICAD_SANITIZE_THREADS
576 // Tell TSAN we are changing fibers back to the caller
577 __tsan_switch_to_fiber( m_caller.tsan_fiber, 0 );
578#endif
579
580#ifdef KICAD_SANITIZE_ADDRESS
581 void* fake_stack_save = nullptr;
582 __sanitizer_start_switch_fiber( &fake_stack_save,
583 m_asanCallerStackBottom, m_asanCallerStackSize );
584#endif
585
586 wxLogTrace( kicadTraceCoroutineStack, wxT( "COROUTINE::jumpOut" ) );
587
588 ret = reinterpret_cast<INVOCATION_ARGS*>(
589 libcontext::jump_fcontext( &( m_callee.ctx ), m_caller.ctx,
590 reinterpret_cast<intptr_t>( &args ) )
591 );
592
593#ifdef KICAD_SANITIZE_ADDRESS
594 __sanitizer_finish_switch_fiber( fake_stack_save,
595 &m_asanCallerStackBottom, &m_asanCallerStackSize );
596#endif
597
598 m_callContext = ret->context;
599
600 if( ret->type == INVOCATION_ARGS::FROM_ROOT )
601 {
602 m_callContext->SetMainStack( &m_caller );
603 }
604 }
605
606#ifndef LIBCONTEXT_HAS_OWN_STACK
608 std::unique_ptr<char[], struct STACK_DELETER> m_stack;
609#endif
610
612
613 std::function<ReturnType( ArgType )> m_func;
614
616
618 typename std::remove_reference<ArgType>::type* m_args;
619
621 CONTEXT_T m_caller;
622
624 CALL_CONTEXT* m_callContext;
625
627 CONTEXT_T m_callee;
628
629 ReturnType m_retVal;
630
631#ifdef KICAD_USE_VALGRIND
632 uint32_t m_valgrind_stack;
633#endif
634
635#ifdef KICAD_SANITIZE_ADDRESS
636 const void* m_asanStackBottom;
637 size_t m_asanStackSize;
638 const void* m_asanCallerStackBottom;
639 size_t m_asanCallerStackSize;
640#endif
641};
642
643#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:182
void SetMainStack(CONTEXT_T *aStack)
Definition coroutine.h:143
void RunMainStack(COROUTINE *aCor, std::function< void()> aFunc)
Definition coroutine.h:148
void Continue(INVOCATION_ARGS *args)
Definition coroutine.h:171
std::function< void()> m_mainStackFunction
Definition coroutine.h:183
bool Call(ArgType aArg)
Start execution of a coroutine, passing args as its arguments.
Definition coroutine.h:282
CALL_CONTEXT * m_callContext
Definition coroutine.h:624
void KiYield(ReturnType &aRetVal)
KiYield with a value.
Definition coroutine.h:255
void KiYield()
Stop execution of the coroutine and returns control to the caller.
Definition coroutine.h:245
bool Resume()
Resume execution of a previously yielded coroutine.
Definition coroutine.h:328
COROUTINE(std::function< ReturnType(ArgType)> aEntry)
Create a coroutine from a delegate object.
Definition coroutine.h:204
static void callerStub(intptr_t aData)
Definition coroutine.h:514
CONTEXT_T m_callee
Saved coroutine context.
Definition coroutine.h:627
INVOCATION_ARGS * jumpIn(INVOCATION_ARGS *args)
Definition coroutine.h:544
std::unique_ptr< char[], struct STACK_DELETER > m_stack
Definition coroutine.h:608
const ReturnType & ReturnValue() const
Return the yielded value (the argument KiYield() was called with).
Definition coroutine.h:369
INVOCATION_ARGS * doResume(INVOCATION_ARGS *args)
Definition coroutine.h:508
static void * MapMemory(size_t aAllocSize)
Map a page-aligned memory region into our address space.
Definition coroutine.h:477
static size_t SystemPageSize()
The size of the mappable memory page size.
Definition coroutine.h:457
INVOCATION_ARGS * doCall(INVOCATION_ARGS *aInvArgs, ArgType aArgs)
Definition coroutine.h:383
bool Call(const COROUTINE &aCor, ArgType aArg)
Start execution of a coroutine, passing args as its arguments.
Definition coroutine.h:308
std::function< int(int)> m_func
Definition coroutine.h:613
static void GuardMemory(void *aAddress, size_t aGuardSize)
Change protection of memory page(s) to act as stack guards.
Definition coroutine.h:494
bool Resume(const COROUTINE &aCor)
Resume execution of a previously yielded coroutine.
Definition coroutine.h:354
COROUTINE(T *object, ReturnType(T::*ptr)(ArgType))
Create a coroutine from a member method of an object.
Definition coroutine.h:196
bool Running() const
Definition coroutine.h:377
void RunMainStack(std::function< void()> func)
Run a functor inside the application main stack context.
Definition coroutine.h:268
void jumpOut()
Definition coroutine.h:570
std::remove_reference< int >::type * m_args
Definition coroutine.h:618
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:103
enum COROUTINE::INVOCATION_ARGS::@361002175157160365344154346100200253026233123042 type
CALL_CONTEXT * context
Definition coroutine.h:97
A functor that frees the stack.
Definition coroutine.h:444
void SetSize(std::size_t aSize)
Definition coroutine.h:451
void operator()(void *aMem) noexcept
Definition coroutine.h:452
VECTOR3I res
wxLogTrace helper definitions.