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
60#ifdef KICAD_SANITIZE_ADDRESS
61#include <sanitizer/asan_interface.h>
62// ASan fiber switching APIs (available since Clang 3.9/GCC 7)
63extern "C" {
64 void __sanitizer_start_switch_fiber(void** fake_stack_save,
65 const void* bottom, size_t size);
66 void __sanitizer_finish_switch_fiber(void* fake_stack_save,
67 const void** bottom_old,
68 size_t* size_old);
69}
70
71// Thread-local tracking of ASAN fiber switch state
72static bool& GetAsanFiberSwitchState()
73{
74 thread_local bool asan_fiber_switch_active = false;
75 return asan_fiber_switch_active;
76}
77
78// Add runtime ASAN detection
79static bool IsASanEnabled()
80{
81 static std::optional<bool> asan_enabled;
82 if (!asan_enabled.has_value())
83 {
84 asan_enabled = (__has_feature(address_sanitizer) );
85 }
86 return asan_enabled.value();
87}
88#endif
89
112template <typename ReturnType, typename ArgType>
114{
115private:
116 class CALL_CONTEXT;
117
119 {
120 enum
121 {
122 FROM_ROOT, // a stub was called/a coroutine was resumed from the main-stack context
123 FROM_ROUTINE, // a stub was called/a coroutine was resumed from a coroutine context
124 CONTINUE_AFTER_ROOT // a function sent a request to invoke a function on the main
125 // stack context
126 } type; // invocation type
127
128 COROUTINE* destination; // stores the coroutine pointer for the stub OR the coroutine
129 // ptr for the coroutine to be resumed if a
130 // root(main-stack)-call-was initiated.
131 CALL_CONTEXT* context; // pointer to the call context of the current callgraph this
132 // call context holds a reference to the main stack context
133 };
134
136 {
137 libcontext::fcontext_t ctx; // The context itself
138
139#ifdef KICAD_SANITIZE_THREADS
140 void* tsan_fiber; // The TSAN fiber for this context
141 bool own_tsan_fiber; // Do we own this TSAN fiber? (we only delete fibers we own)
142#endif
143#ifdef KICAD_SANITIZE_ADDRESS
144 void* asan_fake_stack; // ASan fake stack for this context
145 const void* asan_stack_bottom; // Stack bottom for ASan
146 size_t asan_stack_size; // Stack size for ASan
147 bool asan_stack_registered; // Track if we registered this stack
148#endif
149
151 ctx( nullptr )
152#ifdef KICAD_SANITIZE_THREADS
153 ,tsan_fiber( nullptr )
154 ,own_tsan_fiber( true )
155#endif
156#ifdef KICAD_SANITIZE_ADDRESS
157 ,asan_fake_stack( nullptr )
158 ,asan_stack_bottom( nullptr )
159 ,asan_stack_size( 0 )
160 ,asan_stack_registered( false )
161#endif
162 {}
163
165 {
166#ifdef KICAD_SANITIZE_THREADS
167 // Only destroy the fiber when we own it
168 if( own_tsan_fiber )
169 __tsan_destroy_fiber( tsan_fiber );
170#endif
171#ifdef KICAD_SANITIZE_ADDRESS
172 // Clean up ASAN registration if we registered it
173 if( asan_stack_registered && IsASanEnabled() )
174 {
175 // Note: ASAN doesn't provide explicit stack deregistration,
176 // but we should reset our tracking
177 asan_stack_registered = false;
178 }
179#endif
180 }
181 };
182
184 {
185 public:
187 m_mainStackContext( nullptr )
188 {
189 }
190
192 {
194 libcontext::release_fcontext( m_mainStackContext->ctx );
195 }
196
197
198 void SetMainStack( CONTEXT_T* aStack )
199 {
200 m_mainStackContext = aStack;
201 }
202
203 void RunMainStack( COROUTINE* aCor, std::function<void()> aFunc )
204 {
205 m_mainStackFunction = std::move( aFunc );
207
208#ifdef KICAD_SANITIZE_THREADS
209 // Tell TSAN we are changing fibers
210 __tsan_switch_to_fiber( m_mainStackContext->tsan_fiber, 0 );
211#endif
212
213 libcontext::jump_fcontext( &( aCor->m_callee.ctx ), m_mainStackContext->ctx,
214 reinterpret_cast<intptr_t>( &args ) );
215 }
216
218 {
220 {
223 args = args->destination->doResume( args );
224 }
225 }
226
227 private:
229 std::function<void()> m_mainStackFunction;
230 };
231
232public:
234 COROUTINE( nullptr )
235 {
236 }
237
241 template <class T>
242 COROUTINE( T* object, ReturnType(T::*ptr)( ArgType ) ) :
243 COROUTINE( std::bind( ptr, object, std::placeholders::_1 ) )
244 {
245 }
246
250 COROUTINE( std::function<ReturnType( ArgType )> aEntry ) :
251 m_func( std::move( aEntry ) ),
252 m_running( false ),
253 m_args( nullptr ),
254 m_caller(),
255 m_callContext( nullptr ),
256 m_callee(),
257 m_retVal( 0 )
258#ifdef KICAD_USE_VALGRIND
259 ,m_valgrind_stack( 0 )
260#endif
261 {
263 }
264
266 {
267#ifdef KICAD_USE_VALGRIND
268 VALGRIND_STACK_DEREGISTER( m_valgrind_stack );
269#endif
270
271 if( m_caller.ctx )
272 libcontext::release_fcontext( m_caller.ctx );
273
274 if( m_callee.ctx )
275 libcontext::release_fcontext( m_callee.ctx );
276 }
277
278public:
285 void KiYield()
286 {
287 jumpOut();
288 }
289
295 void KiYield( ReturnType& aRetVal )
296 {
297 m_retVal = aRetVal;
298 jumpOut();
299 }
300
308 void RunMainStack( std::function<void()> func )
309 {
310 assert( m_callContext );
311 m_callContext->RunMainStack( this, std::move( func ) );
312 }
313
322 bool Call( ArgType aArg )
323 {
324 CALL_CONTEXT ctx;
326
327 wxLogTrace( kicadTraceCoroutineStack, "COROUTINE::Call (from root)" );
328
329 ctx.Continue( doCall( &args, aArg ) );
330
331 return Running();
332 }
333
342 bool Call( const COROUTINE& aCor, ArgType aArg )
343 {
345
346 wxLogTrace( kicadTraceCoroutineStack, wxT( "COROUTINE::Call (from routine)" ) );
347
348 doCall( &args, aArg );
349
350 // we will not be asked to continue
351 return Running();
352 }
353
362 bool Resume()
363 {
364 CALL_CONTEXT ctx;
366
367 wxLogTrace( kicadTraceCoroutineStack, wxT( "COROUTINE::Resume (from root)" ) );
368
369 ctx.Continue( doResume( &args ) );
370
371 return Running();
372 }
373
382 bool Resume( const COROUTINE& aCor )
383 {
385
386 wxLogTrace( kicadTraceCoroutineStack, wxT( "COROUTINE::Resume (from routine)" ) );
387
388 doResume( &args );
389
390 // we will not be asked to continue
391 return Running();
392 }
393
397 const ReturnType& ReturnValue() const
398 {
399 return m_retVal;
400 }
401
405 bool Running() const
406 {
407 return m_running;
408 }
409
410private:
411 INVOCATION_ARGS* doCall( INVOCATION_ARGS* aInvArgs, ArgType aArgs )
412 {
413 assert( m_func );
414 assert( !( m_callee.ctx ) );
415
416#ifdef KICAD_SANITIZE_THREADS
417 // Get the TSAN fiber for the current stack here
418 m_caller.tsan_fiber = __tsan_get_current_fiber();
419 m_caller.own_tsan_fiber = false;
420#endif
421#ifdef KICAD_SANITIZE_ADDRESS
422 if( IsASanEnabled() )
423 {
424 // Initialize caller's ASan context (main stack)
425 m_caller.asan_fake_stack = nullptr;
426 m_caller.asan_stack_bottom = nullptr; // Main stack, managed by ASan
427 m_caller.asan_stack_size = 0;
428 m_caller.asan_stack_registered = false;
429 }
430#endif
431
432 m_args = &aArgs;
433
434 std::size_t stackSize = m_stacksize;
435 void* sp = nullptr;
436
437 wxLogTrace( kicadTraceCoroutineStack, wxT( "COROUTINE::doCall" ) );
438
439#ifndef LIBCONTEXT_HAS_OWN_STACK
440 assert( !m_stack );
441
442 const std::size_t systemPageSize = SystemPageSize();
443
444 // calculate the correct number of pages to allocate based on request stack size
445 std::size_t pages = ( m_stacksize + systemPageSize - 1 ) / systemPageSize;
446
447 // we allocate an extra page for the guard
448 stackSize = ( pages + 1 ) * systemPageSize;
449
450 m_stack.reset( static_cast<char*>( MapMemory( stackSize ) ) );
451 m_stack.get_deleter().SetSize( stackSize );
452
453 // now configure the first page (by only specifying a single page_size from vp)
454 // that will act as the guard page
455 // the stack will grow from the end and hopefully never into this guarded region
456 GuardMemory( m_stack.get(), systemPageSize );
457
458 sp = static_cast<char*>( m_stack.get() ) + stackSize;
459
460#ifdef KICAD_USE_VALGRIND
461 m_valgrind_stack = VALGRIND_STACK_REGISTER( sp, m_stack.get() );
462#endif
463#ifdef KICAD_SANITIZE_ADDRESS
464 if( IsASanEnabled() )
465 {
466 // Register the allocated stack with ASan
467 m_callee.asan_stack_bottom = m_stack.get();
468 m_callee.asan_stack_size = stackSize;
469 m_callee.asan_fake_stack = nullptr;
470 m_callee.asan_stack_registered = true;
471
472 // Poison the guard page for better debugging
473 if( m_stack.get() )
474 {
475 __asan_poison_memory_region( m_stack.get(), SystemPageSize() );
476 }
477 }
478#endif
479#endif
480
481#ifdef KICAD_SANITIZE_THREADS
482 // Create a new fiber to go with the new context
483 m_callee.tsan_fiber = __tsan_create_fiber( 0 );
484 m_callee.own_tsan_fiber = true;
485
486 __tsan_set_fiber_name( m_callee.tsan_fiber, "Coroutine fiber" );
487#endif
488
489 m_callee.ctx = libcontext::make_fcontext( sp, stackSize, callerStub );
490 m_running = true;
491
492 // off we go!
493 return jumpIn( aInvArgs );
494 }
495
496#ifndef LIBCONTEXT_HAS_OWN_STACK
499 {
500#ifdef _WIN32
501 void SetSize( std::size_t ) {}
502 void operator()( void* aMem ) noexcept { ::VirtualFree( aMem, 0, MEM_RELEASE ); }
503#else
504 std::size_t m_size = 0;
505
506 void SetSize( std::size_t aSize ) { m_size = aSize; }
507 void operator()( void* aMem ) noexcept { ::munmap( aMem, m_size ); }
508#endif
509 };
510
512 static inline size_t SystemPageSize()
513 {
514 static std::optional<size_t> systemPageSize;
515
516 if( !systemPageSize.has_value() )
517 {
518#ifdef _WIN32
519 SYSTEM_INFO si;
520 ::GetSystemInfo( &si );
521 systemPageSize = static_cast<size_t>( si.dwPageSize );
522#else
523 int size = getpagesize();
524 systemPageSize = static_cast<size_t>( size );
525#endif
526 }
527
528 return systemPageSize.value();
529 }
530
532 static inline void* MapMemory( size_t aAllocSize )
533 {
534#ifdef _WIN32
535 void* mem = ::VirtualAlloc( 0, aAllocSize, MEM_COMMIT, PAGE_READWRITE );
536
537 if( !mem )
538 throw std::bad_alloc();
539#else
540 void* mem = ::mmap( 0, aAllocSize, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0 );
541
542 if( mem == (void*) -1 )
543 throw std::bad_alloc();
544#endif
545 return mem;
546 }
547
549 static inline void GuardMemory( void* aAddress, size_t aGuardSize )
550 {
551#ifdef _WIN32
552 DWORD old_prot; // dummy var since the arg cannot be NULL
553 BOOL res = ::VirtualProtect( aAddress, aGuardSize,
554 PAGE_READWRITE | PAGE_GUARD, &old_prot );
555#else
556 bool res = ( 0 == ::mprotect( aAddress, aGuardSize, PROT_NONE ) );
557#endif
558 if( !res )
559 wxLogTrace( kicadTraceCoroutineStack, wxT( "COROUTINE::GuardMemory has failed" ) );
560 }
561#endif // LIBCONTEXT_HAS_OWN_STACK
562
564 {
565
566#ifdef KICAD_SANITIZE_THREADS
567 // Get the TSAN fiber for the current stack here
568 m_caller.tsan_fiber = __tsan_get_current_fiber();
569 m_caller.own_tsan_fiber = false;
570#endif
571#ifdef KICAD_SANITIZE_ADDRESS
572 if( IsASanEnabled() )
573 {
574 // Initialize caller's ASan context for resume
575 m_caller.asan_fake_stack = nullptr;
576 m_caller.asan_stack_bottom = nullptr;
577 m_caller.asan_stack_size = 0;
578 }
579#endif
580 return jumpIn( args );
581 }
582
583 // real entry point of the coroutine
584 static void callerStub( intptr_t aData )
585 {
586 INVOCATION_ARGS& args = *reinterpret_cast<INVOCATION_ARGS*>( aData );
587
588 // get pointer to self
589 COROUTINE* cor = args.destination;
590 cor->m_callContext = args.context;
591
592 if( args.type == INVOCATION_ARGS::FROM_ROOT )
593 cor->m_callContext->SetMainStack( &cor->m_caller );
594
595 // call the coroutine method
596 cor->m_retVal = cor->m_func( *(cor->m_args) );
597 cor->m_running = false;
598
599 // go back to wherever we came from.
600 cor->jumpOut();
601 }
602
604 {
605
606 wxLogTrace( kicadTraceCoroutineStack, wxT( "COROUTINE::jumpIn" ) );
607#ifdef KICAD_SANITIZE_ADDRESS
608 // Only use ASAN fiber switching for root-level calls to avoid nesting
609 if( IsASanEnabled() && !GetAsanFiberSwitchState()
610 && ( m_callee.asan_stack_registered || m_callee.asan_stack_bottom ) )
611 {
612 GetAsanFiberSwitchState() = true;
613 __sanitizer_start_switch_fiber( &m_caller.asan_fake_stack,
614 m_callee.asan_stack_bottom,
615 m_callee.asan_stack_size );
616 }
617#endif
618#ifdef KICAD_SANITIZE_THREADS
619 // Tell TSAN we are changing fibers to the callee
620 __tsan_switch_to_fiber( m_callee.tsan_fiber, 0 );
621#endif
622
623 args = reinterpret_cast<INVOCATION_ARGS*>(
624 libcontext::jump_fcontext( &( m_caller.ctx ), m_callee.ctx,
625 reinterpret_cast<intptr_t>( args ) )
626 );
627#ifdef KICAD_SANITIZE_ADDRESS
628 // Complete fiber switch only if we started it
629 if( IsASanEnabled() && GetAsanFiberSwitchState() )
630 {
631 __sanitizer_finish_switch_fiber( m_caller.asan_fake_stack,
632 &m_caller.asan_stack_bottom,
633 &m_caller.asan_stack_size );
634 GetAsanFiberSwitchState() = false;
635 }
636#endif
637
638 return args;
639 }
640
641 void jumpOut()
642 {
643 INVOCATION_ARGS args{ INVOCATION_ARGS::FROM_ROUTINE, nullptr, nullptr };
644 INVOCATION_ARGS* ret;
645
646#ifdef KICAD_SANITIZE_ADDRESS
647 // Only use ASAN fiber switching for root-level calls to avoid nesting
648 if( IsASanEnabled() && !GetAsanFiberSwitchState() )
649 {
650 GetAsanFiberSwitchState() = true;
651 __sanitizer_start_switch_fiber( &m_callee.asan_fake_stack,
652 m_caller.asan_stack_bottom,
653 m_caller.asan_stack_size );
654 }
655#endif
656#ifdef KICAD_SANITIZE_THREADS
657 // Tell TSAN we are changing fibers back to the caller
658 __tsan_switch_to_fiber( m_caller.tsan_fiber, 0 );
659#endif
660
661 wxLogTrace( kicadTraceCoroutineStack, wxT( "COROUTINE::jumpOut" ) );
662
663 ret = reinterpret_cast<INVOCATION_ARGS*>(
664 libcontext::jump_fcontext( &( m_callee.ctx ), m_caller.ctx,
665 reinterpret_cast<intptr_t>( &args ) )
666 );
667
668#ifdef KICAD_SANITIZE_ADDRESS
669 // Complete fiber switch only if we started it
670 if( IsASanEnabled() && GetAsanFiberSwitchState() )
671 {
672 __sanitizer_finish_switch_fiber( m_callee.asan_fake_stack,
673 &m_callee.asan_stack_bottom,
674 &m_callee.asan_stack_size );
675 GetAsanFiberSwitchState() = false;
676 }
677#endif
678 m_callContext = ret->context;
679
680 if( ret->type == INVOCATION_ARGS::FROM_ROOT )
681 {
683 }
684 }
685
686#ifdef KICAD_SANITIZE_ADDRESS
687 // Additional helper for debugging ASAN integration
688 void LogASanStatus() const
689 {
690 if( !IsASanEnabled() )
691 return;
692
693 wxLogTrace( kicadTraceCoroutineStack,
694 wxT("ASAN Coroutine Status: fiber_switch_active=%s, "
695 "caller_bottom=%p, caller_size=%zu, "
696 "callee_bottom=%p, callee_size=%zu, callee_registered=%s"),
697 GetAsanFiberSwitchState() ? "yes" : "no",
698 m_caller.asan_stack_bottom, m_caller.asan_stack_size,
699 m_callee.asan_stack_bottom, m_callee.asan_stack_size,
700 m_callee.asan_stack_registered ? "yes" : "no" );
701 }
702#endif
703
704#ifndef LIBCONTEXT_HAS_OWN_STACK
706 std::unique_ptr<char[], struct STACK_DELETER> m_stack;
707#endif
708
710
711 std::function<ReturnType( ArgType )> m_func;
712
714
716 typename std::remove_reference<ArgType>::type* m_args;
717
720
723
726
727 ReturnType m_retVal;
728
729#ifdef KICAD_USE_VALGRIND
730 uint32_t m_valgrind_stack;
731#endif
732};
733
734#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:228
void SetMainStack(CONTEXT_T *aStack)
Definition: coroutine.h:198
void RunMainStack(COROUTINE *aCor, std::function< void()> aFunc)
Definition: coroutine.h:203
void Continue(INVOCATION_ARGS *args)
Definition: coroutine.h:217
std::function< void()> m_mainStackFunction
Definition: coroutine.h:229
Implement a coroutine.
Definition: coroutine.h:114
bool Call(ArgType aArg)
Start execution of a coroutine, passing args as its arguments.
Definition: coroutine.h:322
CALL_CONTEXT * m_callContext
Main stack information.
Definition: coroutine.h:722
void KiYield(ReturnType &aRetVal)
KiYield with a value.
Definition: coroutine.h:295
void KiYield()
Stop execution of the coroutine and returns control to the caller.
Definition: coroutine.h:285
CONTEXT_T m_caller
Saved caller context.
Definition: coroutine.h:719
bool Resume()
Resume execution of a previously yielded coroutine.
Definition: coroutine.h:362
ReturnType m_retVal
Definition: coroutine.h:727
COROUTINE(std::function< ReturnType(ArgType)> aEntry)
Create a coroutine from a delegate object.
Definition: coroutine.h:250
static void callerStub(intptr_t aData)
Definition: coroutine.h:584
~COROUTINE()
Definition: coroutine.h:265
CONTEXT_T m_callee
Saved coroutine context.
Definition: coroutine.h:725
INVOCATION_ARGS * jumpIn(INVOCATION_ARGS *args)
Definition: coroutine.h:603
std::unique_ptr< char[], struct STACK_DELETER > m_stack
Coroutine stack.
Definition: coroutine.h:706
const ReturnType & ReturnValue() const
Return the yielded value (the argument KiYield() was called with).
Definition: coroutine.h:397
INVOCATION_ARGS * doResume(INVOCATION_ARGS *args)
Definition: coroutine.h:563
bool m_running
Definition: coroutine.h:713
int m_stacksize
Definition: coroutine.h:709
static void * MapMemory(size_t aAllocSize)
Map a page-aligned memory region into our address space.
Definition: coroutine.h:532
static size_t SystemPageSize()
The size of the mappable memory page size.
Definition: coroutine.h:512
INVOCATION_ARGS * doCall(INVOCATION_ARGS *aInvArgs, ArgType aArgs)
Definition: coroutine.h:411
bool Call(const COROUTINE &aCor, ArgType aArg)
Start execution of a coroutine, passing args as its arguments.
Definition: coroutine.h:342
std::function< ReturnType(ArgType)> m_func
Definition: coroutine.h:711
static void GuardMemory(void *aAddress, size_t aGuardSize)
Change protection of memory page(s) to act as stack guards.
Definition: coroutine.h:549
bool Resume(const COROUTINE &aCor)
Resume execution of a previously yielded coroutine.
Definition: coroutine.h:382
COROUTINE(T *object, ReturnType(T::*ptr)(ArgType))
Create a coroutine from a member method of an object.
Definition: coroutine.h:242
bool Running() const
Definition: coroutine.h:405
void RunMainStack(std::function< void()> func)
Run a functor inside the application main stack context.
Definition: coroutine.h:308
void jumpOut()
Definition: coroutine.h:641
std::remove_reference< ArgType >::type * m_args
Pointer to coroutine entry arguments stripped of references to avoid compiler errors.
Definition: coroutine.h:716
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:137
enum COROUTINE::INVOCATION_ARGS::@35 type
CALL_CONTEXT * context
Definition: coroutine.h:131
A functor that frees the stack.
Definition: coroutine.h:499
void SetSize(std::size_t aSize)
Definition: coroutine.h:506
void operator()(void *aMem) noexcept
Definition: coroutine.h:507
VECTOR3I res
wxLogTrace helper definitions.