From d6aa2287261a7f9cd936724628a7c44979135211 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Smoczy=C5=84ski?= Date: Tue, 12 Oct 2021 15:06:01 +0200 Subject: [PATCH] [EGD-7770] Fix atomic stability MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add global __GTHREADS definition to trick standard library into thinking we have a proper threading support and that it should not used single threaded locking policies. As a result shared pointers will use atomic locking policy to synchronize access to reference counter. Add own version of gthr.h to override __gthread_active_p. Add cxa guard and release for globs. Looks like there was conflict when __GTHREADS was set so own version of atomicity.h is removed. Co-authored-by: Alek Rudnik Co-authored-by: Lucjan Bryndza Tested-by: Alek Rudnik Tested-by: Tomasz Krosnowski Signed-off-by: Marcin SmoczyƄski --- CMakeLists.txt | 2 + Target_RT1051.cmake | 2 + board/rt1051/CMakeLists.txt | 1 + board/rt1051/gthr.h | 298 ++++++++++++++++++ board/rt1051/newlib/cxx_guards.cpp | 218 +++++++++++++ board/rt1051/newlib/include/ext/atomicity.h | 98 ------ config/format-config.sh | 2 + .../CrashDebug/CrashCatcher/CMakeLists.txt | 6 +- 8 files changed, 526 insertions(+), 101 deletions(-) create mode 100644 board/rt1051/gthr.h create mode 100644 board/rt1051/newlib/cxx_guards.cpp delete mode 100644 board/rt1051/newlib/include/ext/atomicity.h diff --git a/CMakeLists.txt b/CMakeLists.txt index f4fca3ceb4e5d74f297362ff811fa960c26c680b..a787657bde2cd655dfd7e1778069d9a912e3e651 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,6 +38,8 @@ string(TOLOWER "${PROJECT_TARGET_NAME}" PROJECT_TARGET_NAME) message("Project target name: ${PROJECT_TARGET_NAME}") if (NOT ${PROJECT_TARGET} STREQUAL "TARGET_RT1051") set(ENABLE_TESTS ON) +else() + add_compile_options(-include ${CMAKE_SOURCE_DIR}/board/rt1051/gthr.h) endif() if (${ENABLE_TESTS}) diff --git a/Target_RT1051.cmake b/Target_RT1051.cmake index 61eaeb1e58be6c61714e6a92725cd564aa6c1d74..6fe795ca27fa9b4314b6daa9f3ba8d6494d010d9 100644 --- a/Target_RT1051.cmake +++ b/Target_RT1051.cmake @@ -35,6 +35,8 @@ add_compile_definitions( _HAVE_SQLITE_CONFIG_H CPP_FREERTOS_NO_EXCEPTIONS _GLIBCXX_HAVE_DIRENT_H + __GTHREADS + _GLIBCXX_GCC_GTHR_SINGLE_H ) add_compile_options( diff --git a/board/rt1051/CMakeLists.txt b/board/rt1051/CMakeLists.txt index 6a0bc7383b00bc313c9dcf8baed7bc7e38dfa8af..f856dd820eb01f4b7e37071ae99fbeb5c6452184 100644 --- a/board/rt1051/CMakeLists.txt +++ b/board/rt1051/CMakeLists.txt @@ -17,6 +17,7 @@ target_sources(board memwrap.c newlib/fs_dir.cc newlib/io_syscalls.cpp + newlib/cxx_guards.cpp xip/evkbimxrt1050_flexspi_nor_config.c xip/evkbimxrt1050_sdram_ini_dcd.c xip/fsl_flexspi_nor_boot.c diff --git a/board/rt1051/gthr.h b/board/rt1051/gthr.h new file mode 100644 index 0000000000000000000000000000000000000000..4273ffdae9d648a05f5d839b1b308df86e8fc060 --- /dev/null +++ b/board/rt1051/gthr.h @@ -0,0 +1,298 @@ +/* Threads compatibility routines for libgcc2 and libobjc. */ +/* Compile this one with gcc. */ +/* Copyright (C) 1997-2020 Free Software Foundation, Inc. + +This file is part of GCC. + +GCC 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 3, or (at your option) any later +version. + +GCC 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. + +Under Section 7 of GPL version 3, you are granted additional +permissions described in the GCC Runtime Library Exception, version +3.1, as published by the Free Software Foundation. + +You should have received a copy of the GNU General Public License and +a copy of the GCC Runtime Library Exception along with this program; +see the files COPYING3 and COPYING.RUNTIME respectively. If not, see +. */ + +#if !defined(_GLIBCXX_GCC_GTHR_MUDITA_H) && !defined(__ASSEMBLER__) +#define _GLIBCXX_GCC_GTHR_MUDITA_H + +/* Just provide compatibility for mutex handling. */ + +typedef int __gthread_key_t; +typedef int __gthread_once_t; +typedef int __gthread_mutex_t; +typedef int __gthread_recursive_mutex_t; + +#define __GTHREAD_ONCE_INIT 0 +#define __GTHREAD_MUTEX_INIT 0 +#define __GTHREAD_MUTEX_INIT_FUNCTION(mx) do {} while (0) +#define __GTHREAD_RECURSIVE_MUTEX_INIT 0 + +#define _GLIBCXX_UNUSED __attribute__((__unused__)) + +#ifdef _LIBOBJC + +/* Thread local storage for a single thread */ +static void *thread_local_storage = NULL; + +/* Backend initialization functions */ + +/* Initialize the threads subsystem. */ +static inline int +__gthread_objc_init_thread_system (void) +{ + /* No thread support available */ + return -1; +} + +/* Close the threads subsystem. */ +static inline int +__gthread_objc_close_thread_system (void) +{ + /* No thread support available */ + return -1; +} + +/* Backend thread functions */ + +/* Create a new thread of execution. */ +static inline objc_thread_t +__gthread_objc_thread_detach (void (* func)(void *), void * arg _GLIBCXX_UNUSED) +{ + /* No thread support available */ + return NULL; +} + +/* Set the current thread's priority. */ +static inline int +__gthread_objc_thread_set_priority (int priority _GLIBCXX_UNUSED) +{ + /* No thread support available */ + return -1; +} + +/* Return the current thread's priority. */ +static inline int +__gthread_objc_thread_get_priority (void) +{ + return OBJC_THREAD_INTERACTIVE_PRIORITY; +} + +/* Yield our process time to another thread. */ +static inline void +__gthread_objc_thread_yield (void) +{ + return; +} + +/* Terminate the current thread. */ +static inline int +__gthread_objc_thread_exit (void) +{ + /* No thread support available */ + /* Should we really exit the program */ + /* exit (&__objc_thread_exit_status); */ + return -1; +} + +/* Returns an integer value which uniquely describes a thread. */ +static inline objc_thread_t +__gthread_objc_thread_id (void) +{ + /* No thread support, use 1. */ + return (objc_thread_t) 1; +} + +/* Sets the thread's local storage pointer. */ +static inline int +__gthread_objc_thread_set_data (void *value) +{ + thread_local_storage = value; + return 0; +} + +/* Returns the thread's local storage pointer. */ +static inline void * +__gthread_objc_thread_get_data (void) +{ + return thread_local_storage; +} + +/* Backend mutex functions */ + +/* Allocate a mutex. */ +static inline int +__gthread_objc_mutex_allocate (objc_mutex_t mutex _GLIBCXX_UNUSED) +{ + return 0; +} + +/* Deallocate a mutex. */ +static inline int +__gthread_objc_mutex_deallocate (objc_mutex_t mutex _GLIBCXX_UNUSED) +{ + return 0; +} + +/* Grab a lock on a mutex. */ +static inline int +__gthread_objc_mutex_lock (objc_mutex_t mutex _GLIBCXX_UNUSED) +{ + /* There can only be one thread, so we always get the lock */ + return 0; +} + +/* Try to grab a lock on a mutex. */ +static inline int +__gthread_objc_mutex_trylock (objc_mutex_t mutex _GLIBCXX_UNUSED) +{ + /* There can only be one thread, so we always get the lock */ + return 0; +} + +/* Unlock the mutex */ +static inline int +__gthread_objc_mutex_unlock (objc_mutex_t mutex _GLIBCXX_UNUSED) +{ + return 0; +} + +/* Backend condition mutex functions */ + +/* Allocate a condition. */ +static inline int +__gthread_objc_condition_allocate (objc_condition_t condition _GLIBCXX_UNUSED) +{ + return 0; +} + +/* Deallocate a condition. */ +static inline int +__gthread_objc_condition_deallocate (objc_condition_t condition _GLIBCXX_UNUSED) +{ + return 0; +} + +/* Wait on the condition */ +static inline int +__gthread_objc_condition_wait (objc_condition_t condition _GLIBCXX_UNUSED, + objc_mutex_t mutex _GLIBCXX_UNUSED) +{ + return 0; +} + +/* Wake up all threads waiting on this condition. */ +static inline int +__gthread_objc_condition_broadcast (objc_condition_t condition _GLIBCXX_UNUSED) +{ + return 0; +} + +/* Wake up one thread waiting on this condition. */ +static inline int +__gthread_objc_condition_signal (objc_condition_t condition _GLIBCXX_UNUSED) +{ + return 0; +} + +#else /* _LIBOBJC */ + +static inline int +__gthread_active_p (void) +{ + return 1; +} + +static inline int +__gthread_once (__gthread_once_t *__once _GLIBCXX_UNUSED, void (*__func) (void) _GLIBCXX_UNUSED) +{ + return 0; +} + +static inline int _GLIBCXX_UNUSED +__gthread_key_create (__gthread_key_t *__key _GLIBCXX_UNUSED, void (*__func) (void *) _GLIBCXX_UNUSED) +{ + return 0; +} + +static int _GLIBCXX_UNUSED +__gthread_key_delete (__gthread_key_t __key _GLIBCXX_UNUSED) +{ + return 0; +} + +static inline void * +__gthread_getspecific (__gthread_key_t __key _GLIBCXX_UNUSED) +{ + return 0; +} + +static inline int +__gthread_setspecific (__gthread_key_t __key _GLIBCXX_UNUSED, const void *__v _GLIBCXX_UNUSED) +{ + return 0; +} + +static inline int +__gthread_mutex_destroy (__gthread_mutex_t *__mutex _GLIBCXX_UNUSED) +{ + return 0; +} + +static inline int +__gthread_mutex_lock (__gthread_mutex_t *__mutex _GLIBCXX_UNUSED) +{ + return 0; +} + +static inline int +__gthread_mutex_trylock (__gthread_mutex_t *__mutex _GLIBCXX_UNUSED) +{ + return 0; +} + +static inline int +__gthread_mutex_unlock (__gthread_mutex_t *__mutex _GLIBCXX_UNUSED) +{ + return 0; +} + +static inline int +__gthread_recursive_mutex_lock (__gthread_recursive_mutex_t *__mutex) +{ + return __gthread_mutex_lock (__mutex); +} + +static inline int +__gthread_recursive_mutex_trylock (__gthread_recursive_mutex_t *__mutex) +{ + return __gthread_mutex_trylock (__mutex); +} + +static inline int +__gthread_recursive_mutex_unlock (__gthread_recursive_mutex_t *__mutex) +{ + return __gthread_mutex_unlock (__mutex); +} + +static inline int +__gthread_recursive_mutex_destroy (__gthread_recursive_mutex_t *__mutex) +{ + return __gthread_mutex_destroy (__mutex); +} + +#endif /* _LIBOBJC */ + +#undef _GLIBCXX_UNUSED + +#endif /* ! _GLIBCXX_GCC_GTHR_MUDITA_H */ diff --git a/board/rt1051/newlib/cxx_guards.cpp b/board/rt1051/newlib/cxx_guards.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8238abf1b788aac81f03dab26481eb47df496dc8 --- /dev/null +++ b/board/rt1051/newlib/cxx_guards.cpp @@ -0,0 +1,218 @@ +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include "FreeRTOS.h" +#include "semphr.h" +#include "task.h" + +using __cxxabiv1::__guard; + +static SemaphoreHandle_t s_static_init_mutex = NULL; //!< lock used for the critical section +static SemaphoreHandle_t s_static_init_wait_sem = NULL; //!< counting semaphore used by the waiting tasks +static size_t s_static_init_waiting_count = 0; //!< number of tasks which are waiting for static init guards +#ifndef _NDEBUG +static size_t s_static_init_max_waiting_count = 0; //!< maximum ever value of the above; can be inspected using GDB for debugging purposes +#endif + +extern "C" int __cxa_guard_acquire(__guard* pg); +extern "C" void __cxa_guard_release(__guard* pg); +extern "C" void __cxa_guard_abort(__guard* pg); +extern "C" void __cxa_guard_dummy(void); + +/** + * Layout of the guard object (defined by the ABI). + * + * Compiler will check lower byte before calling guard functions. + */ +typedef struct { + uint8_t ready; //!< nonzero if initialization is done + uint8_t pending; //!< nonzero if initialization is in progress +} guard_t; + +static void static_init_prepare() +{ + portENTER_CRITICAL(); + if (s_static_init_mutex == NULL) { + s_static_init_mutex = xSemaphoreCreateMutex(); + s_static_init_wait_sem = xSemaphoreCreateCounting(INT_MAX, 0); + if (s_static_init_mutex == NULL || s_static_init_wait_sem == NULL) { + // no way to bail out of static initialization without these + abort(); + } + } + portEXIT_CRITICAL(); +} + +/** + * Use s_static_init_wait_sem to wait until guard->pending == 0. + * Preconditions: + * - s_static_init_mutex taken + * - guard.pending == 1 + * Postconditions: + * - s_static_init_mutex taken + * - guard.pending == 0 + */ +static void wait_for_guard_obj(guard_t* g) +{ + s_static_init_waiting_count++; +#ifndef _NDEBUG + s_static_init_max_waiting_count = std::max(s_static_init_waiting_count, + s_static_init_max_waiting_count); +#endif + + do { + auto result = xSemaphoreGive(s_static_init_mutex); + assert(result); + static_cast(result); + /* Task may be preempted here, but this isn't a problem, + * as the semaphore will be given exactly the s_static_init_waiting_count + * number of times; eventually the current task will execute next statement, + * which will immediately succeed. + */ + result = xSemaphoreTake(s_static_init_wait_sem, portMAX_DELAY); + assert(result); + /* At this point the semaphore was given, so all waiting tasks have woken up. + * We take s_static_init_mutex before accessing the state of the guard + * object again. + */ + result = xSemaphoreTake(s_static_init_mutex, portMAX_DELAY); + assert(result); + /* Semaphore may have been given because some other guard object became ready. + * Check the guard object we need and wait again if it is still pending. + */ + } while(g->pending); + s_static_init_waiting_count--; +} + +/** + * Unblock tasks waiting for static initialization to complete. + * Preconditions: + * - s_static_init_mutex taken + * Postconditions: + * - s_static_init_mutex taken + */ +static void signal_waiting_tasks() +{ + auto count = s_static_init_waiting_count; + while (count--) { + xSemaphoreGive(s_static_init_wait_sem); + } +} + +extern "C" int __cxa_guard_acquire(__guard* pg) +{ + guard_t* g = reinterpret_cast(pg); + const auto scheduler_started = xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED; + if (!scheduler_started) { + if (g->pending) { + /* Before the scheduler has started, there we don't support simultaneous + * static initialization. This may be implemented using a spinlock and a + * s32c1i instruction, though. + */ + abort(); + } + } else { + if (s_static_init_mutex == NULL) { + static_init_prepare(); + } + + /* We don't need to use double-checked locking pattern here, as the compiler + * must generate code to check if the first byte of *pg is non-zero, before + * calling __cxa_guard_acquire. + */ + auto result = xSemaphoreTake(s_static_init_mutex, portMAX_DELAY); + assert(result); + static_cast(result); + if (g->pending) { + /* Another task is doing initialization at the moment; wait until it calls + * __cxa_guard_release or __cxa_guard_abort + */ + wait_for_guard_obj(g); + /* At this point there are two scenarios: + * - the task which was doing static initialization has called __cxa_guard_release, + * which means that g->ready is set. We need to return 0. + * - the task which was doing static initialization has called __cxa_guard_abort, + * which means that g->ready is not set; we should acquire the guard and return 1, + * same as for the case if we didn't have to wait. + * Note: actually the second scenario is unlikely to occur in the current + * configuration because exception support is disabled. + */ + } + } + int ret; + if (g->ready) { + /* Static initialization has been done by another task; nothing to do here */ + ret = 0; + } else { + /* Current task can start doing static initialization */ + g->pending = 1; + ret = 1; + } + if (scheduler_started) { + auto result = xSemaphoreGive(s_static_init_mutex); + assert(result); + static_cast(result); + } + return ret; +} + +extern "C" void __cxa_guard_release(__guard* pg) +{ + guard_t* g = reinterpret_cast(pg); + const auto scheduler_started = xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED; + if (scheduler_started) { + auto result = xSemaphoreTake(s_static_init_mutex, portMAX_DELAY); + assert(result); + static_cast(result); + } + assert(g->pending && "tried to release a guard which wasn't acquired"); + g->pending = 0; + /* Initialization was successful */ + g->ready = 1; + if (scheduler_started) { + /* Unblock the tasks waiting for static initialization to complete */ + signal_waiting_tasks(); + auto result = xSemaphoreGive(s_static_init_mutex); + assert(result); + static_cast(result); + } +} + +extern "C" void __cxa_guard_abort(__guard* pg) +{ + guard_t* g = reinterpret_cast(pg); + const auto scheduler_started = xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED; + if (scheduler_started) { + auto result = xSemaphoreTake(s_static_init_mutex, portMAX_DELAY); + assert(result); + static_cast(result); + } + assert(!g->ready && "tried to abort a guard which is ready"); + assert(g->pending && "tried to release a guard which is not acquired"); + g->pending = 0; + if (scheduler_started) { + /* Unblock the tasks waiting for static initialization to complete */ + signal_waiting_tasks(); + auto result = xSemaphoreGive(s_static_init_mutex); + assert(result); + static_cast(result); + } +} + +/** + * Dummy function used to force linking this file instead of the same one in libstdc++. + * This works via -u __cxa_guard_dummy flag in component.mk + */ +extern "C" void __cxa_guard_dummy(void) +{ +} diff --git a/board/rt1051/newlib/include/ext/atomicity.h b/board/rt1051/newlib/include/ext/atomicity.h deleted file mode 100644 index 4c417d548975cfc985a0531e28e54706b1880e71..0000000000000000000000000000000000000000 --- a/board/rt1051/newlib/include/ext/atomicity.h +++ /dev/null @@ -1,98 +0,0 @@ -// Support for atomic operations -*- C++ -*- - -// Copyright (C) 2004-2020 Free Software Foundation, Inc. -// -// This file is part of the GNU ISO C++ Library. This library 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 3, or (at your option) -// any later version. - -// This library 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. - -// Under Section 7 of GPL version 3, you are granted additional -// permissions described in the GCC Runtime Library Exception, version -// 3.1, as published by the Free Software Foundation. - -// You should have received a copy of the GNU General Public License and -// a copy of the GCC Runtime Library Exception along with this program; -// see the files COPYING3 and COPYING.RUNTIME respectively. If not, see -// . - -/** @file ext/atomicity.h - * This file is a GNU extension to the Standard C++ Library. - */ - -#ifndef _GLIBCXX_ATOMICITY_H -#define _GLIBCXX_ATOMICITY_H 1 - -#pragma GCC system_header - -#include -#include -#include - -namespace __gnu_cxx _GLIBCXX_VISIBILITY(default) -{ - _GLIBCXX_BEGIN_NAMESPACE_VERSION - - // Functions for portable atomic access. - // To abstract locking primitives across all thread policies, use: - // __exchange_and_add_dispatch - // __atomic_add_dispatch -#ifdef _GLIBCXX_ATOMIC_BUILTINS - static inline _Atomic_word __attribute__((__always_inline__)) - __exchange_and_add(volatile _Atomic_word * __mem, int __val) - { - return __atomic_add_fetch(__mem, __val, __ATOMIC_ACQ_REL); - } - - static inline void __attribute__((__always_inline__)) __atomic_add(volatile _Atomic_word * __mem, int __val) - { - __atomic_add_fetch(__mem, __val, __ATOMIC_ACQ_REL); - } -#else - _Atomic_word __exchange_and_add(volatile _Atomic_word *, int) _GLIBCXX_NOTHROW; - - void __atomic_add(volatile _Atomic_word *, int) _GLIBCXX_NOTHROW; -#endif - - static inline _Atomic_word __attribute__((__always_inline__)) - __exchange_and_add_single(_Atomic_word * __mem, int __val) - { - return __atomic_add_fetch(__mem, __val, __ATOMIC_ACQ_REL); - } - - static inline void __attribute__((__always_inline__)) __atomic_add_single(_Atomic_word * __mem, int __val) - { - __atomic_add_fetch(__mem, __val, __ATOMIC_ACQ_REL); - } - - static inline _Atomic_word __attribute__((__always_inline__)) - __exchange_and_add_dispatch(_Atomic_word * __mem, int __val) - { - return __exchange_and_add_single(__mem, __val); - } - - static inline void __attribute__((__always_inline__)) __atomic_add_dispatch(_Atomic_word * __mem, int __val) - { - __atomic_add_single(__mem, __val); - } - - _GLIBCXX_END_NAMESPACE_VERSION -} // namespace ) - -// Even if the CPU doesn't need a memory barrier, we need to ensure -// that the compiler doesn't reorder memory accesses across the -// barriers. -#ifndef _GLIBCXX_READ_MEM_BARRIER -#define _GLIBCXX_READ_MEM_BARRIER __atomic_thread_fence(__ATOMIC_ACQUIRE) -#endif -#ifndef _GLIBCXX_WRITE_MEM_BARRIER -#define _GLIBCXX_WRITE_MEM_BARRIER __atomic_thread_fence(__ATOMIC_RELEASE) -#endif - -#endif diff --git a/config/format-config.sh b/config/format-config.sh index ba903452b7cb59034b7c0294cff8ef1bac4c5150..b220779469718af4ee346c15700c45ebc0623211 100644 --- a/config/format-config.sh +++ b/config/format-config.sh @@ -18,6 +18,8 @@ export declare ignore_paths=( '.*/lib/' 'build' 'board/rt1051/xip/' + 'board/rt1051/gthr.h' + 'board/rt1051/newlib/cxx_guards.cpp' 'board/rt1051/newlib/dir-common.h' 'board/rt1051/newlib/include/' 'host-tools/littlefs-fuse/lfsfuse/' diff --git a/third-party/CrashDebug/CrashCatcher/CMakeLists.txt b/third-party/CrashDebug/CrashCatcher/CMakeLists.txt index 61c211a233a852dc358a08f483fe610833615b70..fd3082a77dfaf64369e4afa353b05e2e228a1d5d 100644 --- a/third-party/CrashDebug/CrashCatcher/CMakeLists.txt +++ b/third-party/CrashDebug/CrashCatcher/CMakeLists.txt @@ -20,9 +20,9 @@ target_sources( include/CrashCatcher/CrashCatcher.h ) -set_source_files_properties( - CrashCatcher_armv7m.S - PROPERTIES LANGUAGE C +set_property( + SOURCE CrashCatcher_armv7m.S + APPEND PROPERTY COMPILE_OPTIONS "-x" "assembler-with-cpp" ) target_include_directories(