From ac77ee40c5a8372558758c3a98138dd9da47dec7 Mon Sep 17 00:00:00 2001 From: Daniel Thornburgh Date: Sun, 28 Jan 2024 12:25:33 -0800 Subject: [PATCH] Implement quick_exit family --- mos-platform/common/c/CMakeLists.txt | 4 +- mos-platform/common/c/atexit-impl.cc | 42 +++++++++++ mos-platform/common/c/atexit-impl.h | 58 ++++++++++++++++ mos-platform/common/c/cxa-atexit.cc | 100 ++------------------------- mos-platform/common/c/quick-exit.cc | 29 ++++++++ mos-platform/common/include/stdlib.h | 14 ++-- 6 files changed, 145 insertions(+), 102 deletions(-) create mode 100644 mos-platform/common/c/atexit-impl.cc create mode 100644 mos-platform/common/c/atexit-impl.h create mode 100644 mos-platform/common/c/quick-exit.cc diff --git a/mos-platform/common/c/CMakeLists.txt b/mos-platform/common/c/CMakeLists.txt index c1fe08ea3..5bfcb5fbc 100644 --- a/mos-platform/common/c/CMakeLists.txt +++ b/mos-platform/common/c/CMakeLists.txt @@ -28,6 +28,7 @@ add_platform_library(common-c malloc.cc malloc.s new.cc + quick-exit.cc # string.h mem.c @@ -38,8 +39,9 @@ add_platform_library(common-c exception.cc # Itanium ABI implementation - cxa-abi.cc + atexit-impl.cc cxa-atexit.cc + cxa-abi.cc private-typeinfo.cc ) # Prevent the implementation of libcalls from being reduced to a call of those libcalls. diff --git a/mos-platform/common/c/atexit-impl.cc b/mos-platform/common/c/atexit-impl.cc new file mode 100644 index 000000000..d215692fb --- /dev/null +++ b/mos-platform/common/c/atexit-impl.cc @@ -0,0 +1,42 @@ +#include "atexit-impl.h" + +#include + +using namespace __impl; + +bool RegistrationList::push_front(const ExitFunctionStorage &new_exit) { + auto ¤t_block = *m_list; + + if (!current_block.full()) { + current_block.push_front(new_exit); + return true; + } + + const auto next_block = new (std::nothrow) FnNode{m_list}; + if (!next_block) { + // not enough memory to allocate another exit function + return false; + } + + // link new block to front of list. + next_block->push_front(new_exit); + m_list = next_block; + return true; +} + +void RegistrationList::run_all_exits() { + while (m_list != &m_tail || !m_list->empty()) { + if (!m_list->empty()) { + // Note: fn may itself call atexit, so pop first. + ExitFunctionStorage fn = m_list->back(); + m_list->pop_back(); + fn(); + } else { + const auto current_node_ptr = static_cast(m_list); + const auto next_block = current_node_ptr->m_next; + // current_node_ptr is leaked here. We are shutting down. + m_list = next_block; + } + } +} + diff --git a/mos-platform/common/c/atexit-impl.h b/mos-platform/common/c/atexit-impl.h new file mode 100644 index 000000000..992641b32 --- /dev/null +++ b/mos-platform/common/c/atexit-impl.h @@ -0,0 +1,58 @@ +#ifndef ATEXIT_IMPL_H +#define ATEXIT_IMPL_H + +#include + +namespace __impl { + +struct ExitFunctionStorage { + void (*m_functionptr)(void *); + void *m_userdata; + + void operator()() const { m_functionptr(m_userdata); } +}; + +/* Exit functions are registered in a singly-linked list of blocks of + * registrations. Each block contains 32 exit registrations, and additional + * space for registrations is allocated on the heap, as needed.*/ +class RegistrationList { + // FnBlock is an array of function pointers and their arguments. + // The logical "front" of the block is the last item appended to + // the array. + struct FnBlock { + static constexpr std::uint8_t BLOCK_SZ = 32; + + ExitFunctionStorage m_funcs[BLOCK_SZ]; + std::uint8_t m_sz; + + bool empty() const { return !m_sz; } + bool full() const { return m_sz == BLOCK_SZ; } + + void push_front(const ExitFunctionStorage &newfn) { + m_funcs[m_sz++] = newfn; + } + + const ExitFunctionStorage &back() const { return m_funcs[m_sz - 1]; } + void pop_back() { m_sz--; } + }; + + struct FnNode : public FnBlock { + FnNode(FnBlock *next) : m_next{next} {} + + FnBlock *const m_next = nullptr; + }; + + // The initial base node allows 32 exit registrations (1 block) + // of exit functions, without allocating. So the minumum required + // 32 exit functions will always be available. + FnBlock m_tail; + FnBlock *m_list = &m_tail; + +public: + bool push_front(const ExitFunctionStorage &new_exit); + void run_all_exits(); +}; + +} // namespace __impl + +#endif // not ATEXIT_IMPL_H diff --git a/mos-platform/common/c/cxa-atexit.cc b/mos-platform/common/c/cxa-atexit.cc index 2768a20ca..8c96e472c 100644 --- a/mos-platform/common/c/cxa-atexit.cc +++ b/mos-platform/common/c/cxa-atexit.cc @@ -1,112 +1,22 @@ -#include -#include +#include "atexit-impl.h" -namespace { +using namespace __impl; -struct ExitFunctionStorage { - void (*m_functionptr)(void *); - void *m_userdata; - - void operator()() const { m_functionptr(m_userdata); } -}; - -/* Exit functions are registered in a singly-linked list of blocks of - * registrations. Each block contains 32 exit registrations, and additional - * space for registrations is allocated on the heap, as needed.*/ -class RegistrationList { -private: - // FnBlock is an array of function pointers and their arguments. - // The logical "front" of the block is the last item appended to - // the array. - struct FnBlock { - static constexpr std::uint8_t BLOCK_SZ = 32; - - ExitFunctionStorage m_funcs[BLOCK_SZ]; - std::uint8_t m_sz; - - bool empty() const { return !m_sz; } - bool full() const { return m_sz == BLOCK_SZ; } - - void push_front(const ExitFunctionStorage &newfn) { - m_funcs[m_sz++] = newfn; - } - - const ExitFunctionStorage &back() const { return m_funcs[m_sz - 1]; } - void pop_back() { m_sz--; } - }; - - struct FnNode : public FnBlock { - - FnNode(FnBlock *next) : m_next{next} {} - - FnBlock *const m_next = nullptr; - }; - -public: - static bool push_front(const ExitFunctionStorage &new_exit) { - - auto ¤t_block = *m_list; - - if (!current_block.full()) { - current_block.push_front(new_exit); - return true; - } else { - const auto next_block = new (std::nothrow) FnNode{m_list}; - if (!next_block) { - // not enough memory to allocate another exit function - return false; - } - - // link new block to front of list. - next_block->push_front(new_exit); - m_list = next_block; - return true; - } - } - - static void run_all_exits() { - while (m_list != &m_tail || !m_list->empty()) { - if (!m_list->empty()) { - // Note: fn may itself call atexit, so pop first. - ExitFunctionStorage fn = m_list->back(); - m_list->pop_back(); - fn(); - } else { - const auto current_node_ptr = static_cast(m_list); - const auto next_block = current_node_ptr->m_next; - // current_node_ptr is leaked here. We are shutting down. - m_list = next_block; - } - } - } - -private: - // The initial base node allows 32 exit registrations (1 block) - // of exit functions, without allocating. So the minumum required - // 32 exit functions will always be available. - static FnBlock m_tail; - static FnBlock *m_list; -}; - -// Static allocation of registration list. -RegistrationList::FnBlock RegistrationList::m_tail{}; -RegistrationList::FnBlock *RegistrationList::m_list = &m_tail; - -} // namespace +static RegistrationList atexit_list; extern "C" { asm(".section .fini.100,\"axR\",@progbits\n" "jsr __do_atexit\n"); -void __do_atexit() { RegistrationList::run_all_exits(); } +void __do_atexit() { atexit_list.run_all_exits(); } // atexit / finalize are implemented under the assumption that there is only a // single loaded binary, with no dynamic loading. Therefore; the mechanism for // holding a DSO handle (the third parameter to _cxa_atexit), is ignored. int __cxa_atexit(void (*f)(void *), void *p, void * /* dso_handle */) { // Return values equal to C/C++ atexit() return value. - return !RegistrationList::push_front(ExitFunctionStorage{f, p}); + return !atexit_list.push_front(ExitFunctionStorage{f, p}); } int atexit(void (*function)(void)) { diff --git a/mos-platform/common/c/quick-exit.cc b/mos-platform/common/c/quick-exit.cc new file mode 100644 index 000000000..f95435818 --- /dev/null +++ b/mos-platform/common/c/quick-exit.cc @@ -0,0 +1,29 @@ +#include "atexit-impl.h" + +#include + +using namespace __impl; + +static RegistrationList quick_exit_list; + +extern "C" { + +// Implemented under the assumption that there is only a single loaded binary, +// with no dynamic loading. Accordingly, the mechanism for holding a DSO handle +// (the third parameter) is ignored. +int __cxa_at_quick_exit(void (*f)(void *), void *p, void * /* dso_handle */) { + // Return values equal to C/C++ atexit() return value. + return !quick_exit_list.push_front(ExitFunctionStorage{f, p}); +} + +int at_quick_exit(void (*function)(void)) { + return __cxa_at_quick_exit(reinterpret_cast(function), + nullptr, nullptr); +} + +_Noreturn void quick_exit(int status) { + quick_exit_list.run_all_exits(); + _Exit(status); +} + +} // extern "C" diff --git a/mos-platform/common/include/stdlib.h b/mos-platform/common/include/stdlib.h index 35fe0edbe..a825d3e10 100644 --- a/mos-platform/common/include/stdlib.h +++ b/mos-platform/common/include/stdlib.h @@ -18,12 +18,14 @@ extern "C" { #define MB_CUR_MAX ((size_t)1) -void exit(int status); -__attribute__((leaf)) void abort(void); -__attribute__((leaf)) void _exit(int status); -__attribute__((leaf)) void _Exit(int status); - -int atexit(void (*function)(void)); +// Communication with the environment +__attribute__((leaf)) _Noreturn void abort(void); +int atexit(void (*func)(void)); +int at_quick_exit(void (*func)(void)); +_Noreturn void exit(int status); +__attribute__((leaf)) _Noreturn void _exit(int status); +__attribute__((leaf)) _Noreturn void _Exit(int status); +_Noreturn void quick_exit(int status); int abs(int i); long labs(long i);