~aleteoryx/muditaos

d6aa2287261a7f9cd936724628a7c44979135211 — Marcin Smoczyński 4 years ago 649cacd
[EGD-7770] Fix atomic stability

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 <aleksander.rudnik@mudita.com>
Co-authored-by: Lucjan Bryndza <lucjan.bryndza@mudita.com>
Tested-by: Alek Rudnik <aleksander.rudnik@mudita.com>
Tested-by: Tomasz Krosnowski <tomasz.krosnowski@mudita.com>
Signed-off-by: Marcin Smoczyński <smoczynski.marcin@gmail.com>
M CMakeLists.txt => CMakeLists.txt +2 -0
@@ 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})

M Target_RT1051.cmake => Target_RT1051.cmake +2 -0
@@ 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(

M board/rt1051/CMakeLists.txt => board/rt1051/CMakeLists.txt +1 -0
@@ 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

A board/rt1051/gthr.h => board/rt1051/gthr.h +298 -0
@@ 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
<http://www.gnu.org/licenses/>.  */

#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 */

A board/rt1051/newlib/cxx_guards.cpp => board/rt1051/newlib/cxx_guards.cpp +218 -0
@@ 0,0 1,218 @@
/*
 * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <stdlib.h>
#include <assert.h>
#include <cxxabi.h>
#include <stdint.h>
#include <limits.h>
#include <algorithm>
#include <sys/lock.h>
#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<void>(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<guard_t*>(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<void>(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<void>(result);
    }
    return ret;
}

extern "C" void __cxa_guard_release(__guard* pg)
{
    guard_t* g = reinterpret_cast<guard_t*>(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<void>(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<void>(result);
    }
}

extern "C" void __cxa_guard_abort(__guard* pg)
{
    guard_t* g = reinterpret_cast<guard_t*>(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<void>(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<void>(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)
{
}

D board/rt1051/newlib/include/ext/atomicity.h => board/rt1051/newlib/include/ext/atomicity.h +0 -98
@@ 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
// <http://www.gnu.org/licenses/>.

/** @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 <bits/c++config.h>
#include <bits/gthr.h>
#include <bits/atomic_word.h>

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

M config/format-config.sh => config/format-config.sh +2 -0
@@ 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/'

M third-party/CrashDebug/CrashCatcher/CMakeLists.txt => third-party/CrashDebug/CrashCatcher/CMakeLists.txt +3 -3
@@ 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(