Line data Source code
1 : /*
2 : *
3 : * Copyright (c) 2023 Project CHIP Authors
4 : *
5 : * Licensed under the Apache License, Version 2.0 (the "License");
6 : * you may not use this file except in compliance with the License.
7 : * You may obtain a copy of the License at
8 : *
9 : * http://www.apache.org/licenses/LICENSE-2.0
10 : *
11 : * Unless required by applicable law or agreed to in writing, software
12 : * distributed under the License is distributed on an "AS IS" BASIS,
13 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 : * See the License for the specific language governing permissions and
15 : * limitations under the License.
16 : */
17 :
18 : #pragma once
19 :
20 : #include <lib/core/CHIPConfig.h>
21 :
22 : #include <mutex>
23 : #include <new>
24 :
25 : #if CHIP_SYSTEM_CONFIG_USE_DISPATCH
26 : #include <dispatch/dispatch.h>
27 : #endif // CHIP_SYSTEM_CONFIG_USE_DISPATCH
28 :
29 : namespace chip {
30 : namespace detail {
31 :
32 : #if CHIP_CONFIG_GLOBALS_LAZY_INIT
33 :
34 : struct NonAtomicOnce
35 : {
36 : bool mInitialized = false;
37 : void call(void (*func)(void *), void * context)
38 : {
39 : if (!mInitialized)
40 : {
41 : mInitialized = true;
42 : func(context);
43 : }
44 : }
45 : };
46 :
47 : struct AtomicOnce
48 : {
49 : // dispatch_once (if available) is more efficient than std::call_once because
50 : // it takes advantage of the additional assumption that the dispatch_once_t
51 : // is allocated within a static / global.
52 : #if CHIP_SYSTEM_CONFIG_USE_DISPATCH
53 : dispatch_once_t mOnce = 0;
54 : void call(void (*func)(void *), void * context) { dispatch_once_f(&mOnce, context, func); }
55 : #else // CHIP_SYSTEM_CONFIG_USE_DISPATCH
56 : std::once_flag mOnce;
57 : void call(void (*func)(void *), void * context) { std::call_once(mOnce, func, context); }
58 : #endif
59 : };
60 :
61 : #endif // CHIP_CONFIG_GLOBALS_LAZY_INIT
62 :
63 : } // namespace detail
64 :
65 : /**
66 : * A wrapper for global object that enables initialization and destruction to
67 : * be configured by the platform via `CHIP_CONFIG_GLOBALS_*` options.
68 : *
69 : * The contained object of type T is default constructed, possibly lazily.
70 : *
71 : * Values of this type MUST be globals or static class members.
72 : *
73 : * This class is not thread-safe; external synchronization is required.
74 : * @see AtomicGlobal<T> for a thread-safe variant.
75 : */
76 : #if CHIP_CONFIG_GLOBALS_LAZY_INIT
77 : template <class T, class OnceStrategy = detail::NonAtomicOnce>
78 : #else // CHIP_CONFIG_GLOBALS_LAZY_INIT
79 : template <class T>
80 : #endif // CHIP_CONFIG_GLOBALS_LAZY_INIT
81 : class Global
82 : {
83 : public:
84 : /// Returns the global object, initializing it if necessary.
85 : /// NOT thread-safe, external synchronization is required.
86 7095 : T & get() { return _get(); }
87 10 : T * operator->() { return &_get(); }
88 :
89 : // Globals are not copyable or movable
90 : Global(const Global &) = delete;
91 : Global(const Global &&) = delete;
92 : Global & operator=(const Global &) = delete;
93 :
94 : #if CHIP_CONFIG_GLOBALS_LAZY_INIT
95 : public:
96 : constexpr Global() = default;
97 : ~Global() = default;
98 :
99 : private:
100 : // Zero-initialize everything. We should technically leave mStorage uninitialized,
101 : // but that can sometimes cause clang to be unable to constant-initialize the object.
102 : alignas(T) unsigned char mStorage[sizeof(T)] = {};
103 : OnceStrategy mOnce;
104 :
105 : T & _get()
106 : {
107 : T * value = reinterpret_cast<T *>(mStorage);
108 : mOnce.call(&create, value);
109 : return *value;
110 : }
111 : static void create(void * value)
112 : {
113 : new (value) T();
114 : #if !CHIP_CONFIG_GLOBALS_NO_DESTRUCT
115 : CHIP_CXA_ATEXIT(&destroy, value);
116 : #endif // CHIP_CONFIG_GLOBALS_NO_DESTRUCT
117 : }
118 : static void destroy(void * value) { static_cast<T *>(value)->~T(); }
119 :
120 : #else // CHIP_CONFIG_GLOBALS_LAZY_INIT
121 : public:
122 150 : constexpr Global() : mValue() {}
123 :
124 : private:
125 7105 : T & _get() { return mValue; }
126 :
127 : #if CHIP_CONFIG_GLOBALS_NO_DESTRUCT
128 : public:
129 : // For not-trivially-destructible T, hiding it inside a union means the destructor
130 : // won't get called automatically, however our own default destructor will be implicitly
131 : // deleted. So Global<T> is technically not trivially-destructible, however the compiler
132 : // tends to be able to optimize away the empty destructor call in practice. Getting around
133 : // this cleanly requires using the "storage" approach used in the lazy variant, however
134 : // this would require std::construct_at from C++20 to get constexpr initialization.
135 : ~Global() {} // not "= default"
136 :
137 : private:
138 : union
139 : {
140 : T mValue;
141 : };
142 : #else // CHIP_CONFIG_GLOBALS_NO_DESTRUCT
143 : public:
144 339 : ~Global() = default;
145 :
146 : private:
147 : T mValue;
148 : #endif // CHIP_CONFIG_GLOBALS_NO_DESTRUCT
149 : #endif // CHIP_CONFIG_GLOBALS_LAZY_INIT
150 : };
151 :
152 : /**
153 : * A variant of Global<T> that is thread-safe.
154 : */
155 : template <class T>
156 : using AtomicGlobal =
157 : #if CHIP_CONFIG_GLOBALS_LAZY_INIT
158 : Global<T, detail::AtomicOnce>;
159 : #else // CHIP_CONFIG_GLOBALS_LAZY_INIT
160 : Global<T>; // eager globals are already thread-safe
161 : #endif // CHIP_CONFIG_GLOBALS_LAZY_INIT
162 :
163 : } // namespace chip
|