Skip to content

Commit

Permalink
Implement quick_exit family
Browse files Browse the repository at this point in the history
  • Loading branch information
mysterymath committed Jan 28, 2024
1 parent ff19661 commit ac77ee4
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 102 deletions.
4 changes: 3 additions & 1 deletion mos-platform/common/c/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ add_platform_library(common-c
malloc.cc
malloc.s
new.cc
quick-exit.cc

# string.h
mem.c
Expand All @@ -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.
Expand Down
42 changes: 42 additions & 0 deletions mos-platform/common/c/atexit-impl.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#include "atexit-impl.h"

#include <new>

using namespace __impl;

bool RegistrationList::push_front(const ExitFunctionStorage &new_exit) {
auto &current_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<FnNode *>(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;
}
}
}

58 changes: 58 additions & 0 deletions mos-platform/common/c/atexit-impl.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#ifndef ATEXIT_IMPL_H
#define ATEXIT_IMPL_H

#include <cstdint>

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
100 changes: 5 additions & 95 deletions mos-platform/common/c/cxa-atexit.cc
Original file line number Diff line number Diff line change
@@ -1,112 +1,22 @@
#include <cstdint>
#include <new>
#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 &current_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<FnNode *>(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)) {
Expand Down
29 changes: 29 additions & 0 deletions mos-platform/common/c/quick-exit.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#include "atexit-impl.h"

#include <stdlib.h>

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<void (*)(void *)>(function),
nullptr, nullptr);
}

_Noreturn void quick_exit(int status) {
quick_exit_list.run_all_exits();
_Exit(status);
}

} // extern "C"
14 changes: 8 additions & 6 deletions mos-platform/common/include/stdlib.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit ac77ee4

Please sign in to comment.