/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2013 CERN * Copyright (C) 2016-2020 KiCad Developers, see AUTHORS.txt for contributors. * * @author Tomasz Wlostowski * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, you may find one here: * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * or you may search the http://www.gnu.org website for the version 2 license, * or you may write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #ifndef __COROUTINE_H #define __COROUTINE_H #include #include #include #ifdef KICAD_USE_VALGRIND #include #endif #ifdef KICAD_SANITIZE_THREADS #include #endif #ifdef KICAD_SANITIZE_ADDRESS #include #endif #include #include #include #include #include #include #ifdef _WIN32 #include #include #endif /** * Implement a coroutine. * * Wikipedia has a good explanation: * * "Coroutines are computer program components that generalize subroutines to * allow multiple entry points for suspending and resuming execution at certain locations. * Coroutines are well-suited for implementing more familiar program components such as cooperative * tasks, exceptions, event loop, iterators, infinite lists and pipes." * * In other words, a coroutine can be considered a lightweight thread - which can be * preempted only when it deliberately yields the control to the caller. This way, * we avoid concurrency problems such as locking / race conditions. * * Uses libcontext library to do the actual context switching. * * This particular version takes a DELEGATE as an entry point, so it can invoke * methods within a given object as separate coroutines. * * See coroutine_example.cpp for sample code. */ template class COROUTINE { private: class CALL_CONTEXT; struct INVOCATION_ARGS { enum { FROM_ROOT, // a stub was called/a coroutine was resumed from the main-stack context FROM_ROUTINE, // a stub was called/a coroutine was resumed from a coroutine context CONTINUE_AFTER_ROOT // a function sent a request to invoke a function on the main // stack context } type; // invocation type COROUTINE* destination; // stores the coroutine pointer for the stub OR the coroutine // ptr for the coroutine to be resumed if a // root(main-stack)-call-was initiated. CALL_CONTEXT* context; // pointer to the call context of the current callgraph this // call context holds a reference to the main stack context }; struct CONTEXT_T { libcontext::fcontext_t ctx; // The context itself #ifdef KICAD_SANITIZE_THREADS void* tsan_fiber; // The TSAN fiber for this context bool own_tsan_fiber; // Do we own this TSAN fiber? (we only delete fibers we own) #endif CONTEXT_T() : ctx( nullptr ) #ifdef KICAD_SANITIZE_THREADS ,tsan_fiber( nullptr ) ,own_tsan_fiber( true ) #endif {} ~CONTEXT_T() { #ifdef KICAD_SANITIZE_THREADS // Only destroy the fiber when we own it if( own_tsan_fiber ) __tsan_destroy_fiber( tsan_fiber ); #endif } }; class CALL_CONTEXT { public: CALL_CONTEXT() : m_mainStackContext( nullptr ) { } ~CALL_CONTEXT() { if( m_mainStackContext ) libcontext::release_fcontext( m_mainStackContext->ctx ); } void SetMainStack( CONTEXT_T* aStack ) { m_mainStackContext = aStack; } void RunMainStack( COROUTINE* aCor, std::function aFunc ) { m_mainStackFunction = std::move( aFunc ); INVOCATION_ARGS args{ INVOCATION_ARGS::CONTINUE_AFTER_ROOT, aCor, this }; #ifdef KICAD_SANITIZE_THREADS // Tell TSAN we are changing fibers __tsan_switch_to_fiber( m_mainStackContext->tsan_fiber, 0 ); #endif libcontext::jump_fcontext( &( aCor->m_callee.ctx ), m_mainStackContext->ctx, reinterpret_cast( &args ) ); } void Continue( INVOCATION_ARGS* args ) { while( args->type == INVOCATION_ARGS::CONTINUE_AFTER_ROOT ) { m_mainStackFunction(); args->type = INVOCATION_ARGS::FROM_ROOT; args = args->destination->doResume( args ); } } private: CONTEXT_T* m_mainStackContext; std::function m_mainStackFunction; }; public: COROUTINE() : COROUTINE( nullptr ) { } /** * Create a coroutine from a member method of an object. */ template COROUTINE( T* object, ReturnType(T::*ptr)( ArgType ) ) : COROUTINE( std::bind( ptr, object, std::placeholders::_1 ) ) { } /** * Create a coroutine from a delegate object. */ COROUTINE( std::function aEntry ) : #ifdef _WIN32 m_stack( nullptr ), #endif m_func( std::move( aEntry ) ), m_running( false ), m_args( nullptr ), m_caller(), m_callContext( nullptr ), m_callee(), m_retVal( 0 ) #ifdef KICAD_USE_VALGRIND ,m_valgrind_stack( 0 ) #endif #ifdef KICAD_SANITIZE_ADDRESS ,asan_stack( nullptr ) #endif { m_stacksize = ADVANCED_CFG::GetCfg().m_CoroutineStackSize; } ~COROUTINE() { #ifdef KICAD_USE_VALGRIND VALGRIND_STACK_DEREGISTER( m_valgrind_stack ); #endif if( m_caller.ctx ) libcontext::release_fcontext( m_caller.ctx ); if( m_callee.ctx ) libcontext::release_fcontext( m_callee.ctx ); #ifdef _WIN32 if( m_stack ) ::VirtualFree( m_stack, 0, MEM_RELEASE ); #endif } public: /** * Stop execution of the coroutine and returns control to the caller. * * After a yield, Call() or Resume() methods invoked by the caller will * immediately return true, indicating that we are not done yet, just asleep. */ void KiYield() { jumpOut(); } /** * KiYield with a value. * * Passe a value of given type to the caller. Useful for implementing generator objects. */ void KiYield( ReturnType& aRetVal ) { m_retVal = aRetVal; jumpOut(); } /** * Run a functor inside the application main stack context. * * Call this function for example if the operation will spawn a webkit browser instance which * will walk the stack to the upper border of the address space on mac osx systems because * its javascript needs garbage collection (for example if you paste text into an edit box). */ void RunMainStack( std::function func ) { assert( m_callContext ); m_callContext->RunMainStack( this, std::move( func ) ); } /** * Start execution of a coroutine, passing args as its arguments. * * Call this method from the application main stack only. * * @return true if the coroutine has yielded and false if it has finished its * execution (returned). */ bool Call( ArgType aArg ) { CALL_CONTEXT ctx; INVOCATION_ARGS args{ INVOCATION_ARGS::FROM_ROOT, this, &ctx }; #ifdef KICAD_SANITIZE_THREADS // Get the TSAN fiber for the current stack here m_caller.tsan_fiber = __tsan_get_current_fiber(); m_caller.own_tsan_fiber = false; #endif wxLogTrace( kicadTraceCoroutineStack, "COROUTINE::Call (from root)" ); ctx.Continue( doCall( &args, aArg ) ); return Running(); } /** * Start execution of a coroutine, passing args as its arguments. * * Call this method for a nested coroutine invocation. * * @return true if the coroutine has yielded and false if it has finished its * execution (returned). */ bool Call( const COROUTINE& aCor, ArgType aArg ) { INVOCATION_ARGS args{ INVOCATION_ARGS::FROM_ROUTINE, this, aCor.m_callContext }; wxLogTrace( kicadTraceCoroutineStack, wxT( "COROUTINE::Call (from routine)" ) ); doCall( &args, aArg ); // we will not be asked to continue return Running(); } /** * Resume execution of a previously yielded coroutine. * * Call this method only from the main application stack. * * @return true if the coroutine has yielded again and false if it has finished its * execution (returned). */ bool Resume() { CALL_CONTEXT ctx; INVOCATION_ARGS args{ INVOCATION_ARGS::FROM_ROOT, this, &ctx }; #ifdef KICAD_SANITIZE_THREADS // Get the TSAN fiber for the current stack here m_caller.tsan_fiber = __tsan_get_current_fiber(); m_caller.own_tsan_fiber = false; #endif wxLogTrace( kicadTraceCoroutineStack, wxT( "COROUTINE::Resume (from root)" ) ); ctx.Continue( doResume( &args ) ); return Running(); } /** * Resume execution of a previously yielded coroutine. * * Call this method for a nested coroutine invocation. * * @return true if the coroutine has yielded again and false if it has finished its * execution (returned). */ bool Resume( const COROUTINE& aCor ) { INVOCATION_ARGS args{ INVOCATION_ARGS::FROM_ROUTINE, this, aCor.m_callContext }; wxLogTrace( kicadTraceCoroutineStack, wxT( "COROUTINE::Resume (from routine)" ) ); doResume( &args ); // we will not be asked to continue return Running(); } /** * Return the yielded value (the argument KiYield() was called with). */ const ReturnType& ReturnValue() const { return m_retVal; } /** * @return true if the coroutine is active. */ bool Running() const { return m_running; } private: INVOCATION_ARGS* doCall( INVOCATION_ARGS* aInvArgs, ArgType aArgs ) { assert( m_func ); assert( !( m_callee.ctx ) ); m_args = &aArgs; assert( m_stack == nullptr ); size_t stackSize = m_stacksize; void* sp = nullptr; #ifndef LIBCONTEXT_HAS_OWN_STACK #ifdef _WIN32 // kind of a hack to avoid calling into GetSystemInfo constantly for page size static std::optional system_page_size; if( !system_page_size.has_value() ) { // For Windows, we want to use VirtualAlloc and VirtualProtect which let us // trap stack overflows with the guard page, it'll still crash but this // has a chance of preserving stack in dumps SYSTEM_INFO si; ::GetSystemInfo( &si ); system_page_size = static_cast( si.dwPageSize ); } // this shouldn't happen but just incase if( m_stack ) ::VirtualFree( m_stack, 0, MEM_RELEASE ); // calculate the correct number of pages to allocate based on request stack size std::size_t pages = ( m_stacksize + system_page_size.value() - 1 ) / system_page_size.value(); // we allocate an extra page for the guard std::size_t alloc_size = ( pages + 1 ) * system_page_size.value(); m_stack = ::VirtualAlloc( 0, alloc_size, MEM_COMMIT, PAGE_READWRITE ); if( !m_stack ) throw std::bad_alloc(); // now configure the first page (by only specifying a single page_size from vp) // that will act as the guard page // the stack will grow from the end and hopefully never into this guarded region DWORD old_prot; // dummy var since the arg cannot be NULL const BOOL res = ::VirtualProtect( m_stack, system_page_size.value(), PAGE_READWRITE | PAGE_GUARD, &old_prot ); assert( res != false ); stackSize = alloc_size; sp = static_cast( m_stack ) + stackSize; #else // fixme: Clean up stack stuff. Add a guard m_stack.reset( new char[stackSize] ); // align to 16 bytes sp = (void*)((((ptrdiff_t) m_stack.get()) + stackSize - 0xf) & (~0x0f)); // correct the stack size stackSize -= size_t( ( (ptrdiff_t) m_stack.get() + stackSize ) - (ptrdiff_t) sp ); #ifdef KICAD_USE_VALGRIND m_valgrind_stack = VALGRIND_STACK_REGISTER( sp, m_stack.get() ); #endif #endif #endif #ifdef KICAD_SANITIZE_THREADS // Create a new fiber to go with the new context m_callee.tsan_fiber = __tsan_create_fiber( 0 ); m_callee.own_tsan_fiber = true; __tsan_set_fiber_name( m_callee.tsan_fiber, "Coroutine fiber" ); #endif wxLogTrace( kicadTraceCoroutineStack, wxT( "COROUTINE::doCall" ) ); m_callee.ctx = libcontext::make_fcontext( sp, stackSize, callerStub ); m_running = true; // off we go! return jumpIn( aInvArgs ); } INVOCATION_ARGS* doResume( INVOCATION_ARGS* args ) { return jumpIn( args ); } /* real entry point of the coroutine */ static void callerStub( intptr_t aData ) { INVOCATION_ARGS& args = *reinterpret_cast( aData ); // get pointer to self COROUTINE* cor = args.destination; cor->m_callContext = args.context; if( args.type == INVOCATION_ARGS::FROM_ROOT ) cor->m_callContext->SetMainStack( &cor->m_caller ); // call the coroutine method cor->m_retVal = cor->m_func( *(cor->m_args) ); cor->m_running = false; // go back to wherever we came from. cor->jumpOut(); } INVOCATION_ARGS* jumpIn( INVOCATION_ARGS* args ) { #ifdef KICAD_SANITIZE_THREADS // Tell TSAN we are changing fibers to the callee __tsan_switch_to_fiber( m_callee.tsan_fiber, 0 ); #endif wxLogTrace( kicadTraceCoroutineStack, wxT( "COROUTINE::jumpIn" ) ); args = reinterpret_cast( libcontext::jump_fcontext( &( m_caller.ctx ), m_callee.ctx, reinterpret_cast( args ) ) ); return args; } void jumpOut() { INVOCATION_ARGS args{ INVOCATION_ARGS::FROM_ROUTINE, nullptr, nullptr }; INVOCATION_ARGS* ret; #ifdef KICAD_SANITIZE_THREADS // Tell TSAN we are changing fibers back to the caller __tsan_switch_to_fiber( m_caller.tsan_fiber, 0 ); #endif wxLogTrace( kicadTraceCoroutineStack, wxT( "COROUTINE::jumpOut" ) ); ret = reinterpret_cast( libcontext::jump_fcontext( &( m_callee.ctx ), m_caller.ctx, reinterpret_cast( &args ) ) ); m_callContext = ret->context; if( ret->type == INVOCATION_ARGS::FROM_ROOT ) { m_callContext->SetMainStack( &m_caller ); } } ///< coroutine stack #ifdef _WIN32 void* m_stack; #else std::unique_ptr m_stack; #endif int m_stacksize; std::function m_func; bool m_running; ///< pointer to coroutine entry arguments. Stripped of references ///< to avoid compiler errors. typename std::remove_reference::type* m_args; ///< saved caller context CONTEXT_T m_caller; ///< main stack information CALL_CONTEXT* m_callContext; ///< saved coroutine context CONTEXT_T m_callee; ReturnType m_retVal; #ifdef KICAD_USE_VALGRIND uint32_t m_valgrind_stack; #endif #ifdef KICAD_SANITIZE_ADDRESS void* asan_stack; #endif }; #endif