1 | //===-- Implementation of atexit ------------------------------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "src/stdlib/atexit.h" |
10 | #include "src/__support/blockstore.h" |
11 | #include "src/__support/common.h" |
12 | #include "src/__support/fixedvector.h" |
13 | #include "src/__support/threads/mutex.h" |
14 | |
15 | namespace LIBC_NAMESPACE { |
16 | |
17 | namespace { |
18 | |
19 | Mutex handler_list_mtx(false, false, false); |
20 | |
21 | using AtExitCallback = void(void *); |
22 | using StdCAtExitCallback = void(void); |
23 | |
24 | struct AtExitUnit { |
25 | AtExitCallback *callback = nullptr; |
26 | void *payload = nullptr; |
27 | constexpr AtExitUnit() = default; |
28 | constexpr AtExitUnit(AtExitCallback *c, void *p) : callback(c), payload(p) {} |
29 | }; |
30 | |
31 | #if defined(LIBC_TARGET_ARCH_IS_GPU) |
32 | // The GPU build cannot handle the potentially recursive definitions required by |
33 | // the BlockStore class. Additionally, the liklihood that someone exceeds this |
34 | // while executing on the GPU is extremely small. |
35 | // FIXME: It is not generally safe to use 'atexit' on the GPU because the |
36 | // mutexes simply passthrough. We will need a lock free stack. |
37 | using ExitCallbackList = FixedVector<AtExitUnit, 64>; |
38 | #elif defined(LIBC_COPT_PUBLIC_PACKAGING) |
39 | using ExitCallbackList = ReverseOrderBlockStore<AtExitUnit, 32>; |
40 | #else |
41 | // BlockStore uses dynamic memory allocation. To avoid dynamic memory |
42 | // allocation in tests, we use a fixed size callback list when built for |
43 | // tests. |
44 | // If we use BlockStore, then we will have to pull in malloc etc into |
45 | // the tests. While this is not bad, the problem we have currently is |
46 | // that LLVM libc' allocator is SCUDO. So, we will end up pulling SCUDO's |
47 | // deps also (some of which are not yet available in LLVM libc) into the |
48 | // integration tests. |
49 | using ExitCallbackList = FixedVector<AtExitUnit, CALLBACK_LIST_SIZE_FOR_TESTS>; |
50 | #endif // LIBC_COPT_PUBLIC_PACKAGING |
51 | |
52 | constinit ExitCallbackList exit_callbacks; |
53 | |
54 | void stdc_at_exit_func(void *payload) { |
55 | reinterpret_cast<StdCAtExitCallback *>(payload)(); |
56 | } |
57 | |
58 | void call_exit_callbacks() { |
59 | handler_list_mtx.lock(); |
60 | while (!exit_callbacks.empty()) { |
61 | AtExitUnit &unit = exit_callbacks.back(); |
62 | exit_callbacks.pop_back(); |
63 | handler_list_mtx.unlock(); |
64 | unit.callback(unit.payload); |
65 | handler_list_mtx.lock(); |
66 | } |
67 | ExitCallbackList::destroy(store: &exit_callbacks); |
68 | } |
69 | |
70 | int add_atexit_unit(const AtExitUnit &unit) { |
71 | MutexLock lock(&handler_list_mtx); |
72 | if (exit_callbacks.push_back(unit)) |
73 | return 0; |
74 | return -1; |
75 | } |
76 | |
77 | } // namespace |
78 | |
79 | extern "C" { |
80 | |
81 | // TODO: Handle the last dso handle argument. |
82 | int __cxa_atexit(AtExitCallback *callback, void *payload, void *) { |
83 | return add_atexit_unit(unit: {callback, payload}); |
84 | } |
85 | |
86 | // TODO: Handle the dso handle argument. call_exit_callbacks should only invoke |
87 | // the callbacks from this DSO. Requires adding support for __dso_handle. |
88 | void __cxa_finalize(void *dso) { |
89 | if (!dso) |
90 | call_exit_callbacks(); |
91 | } |
92 | |
93 | } // extern "C" |
94 | |
95 | LLVM_LIBC_FUNCTION(int, atexit, (StdCAtExitCallback * callback)) { |
96 | return add_atexit_unit( |
97 | unit: {&stdc_at_exit_func, reinterpret_cast<void *>(callback)}); |
98 | } |
99 | |
100 | } // namespace LIBC_NAMESPACE |
101 | |