GCC Code Coverage Report


Directory: ./
File: lib/geogram/delaunay/delaunay_sync.h
Date: 2026-09-07 02:37:58
Exec Total Coverage
Lines: 64 64 100.0%
Functions: 13 13 100.0%
Branches: 26 74 35.1%

Line Branch Exec Source
1 /*
2 * Copyright (c) 2000-2022 Inria
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * * Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above copyright notice,
11 * this list of conditions and the following disclaimer in the documentation
12 * and/or other materials provided with the distribution.
13 * * Neither the name of the ALICE Project-Team nor the names of its
14 * contributors may be used to endorse or promote products derived from this
15 * software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
21 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 * POSSIBILITY OF SUCH DAMAGE.
28 *
29 * Contact: Bruno Levy
30 *
31 * https://www.inria.fr/fr/bruno-levy
32 *
33 * Inria,
34 * Domaine de Voluceau,
35 * 78150 Le Chesnay - Rocquencourt
36 * FRANCE
37 *
38 */
39
40 #ifndef DELAUNAY_SYNC_H
41 #define DELAUNAY_SYNC_H
42
43 #include <geogram/basic/common.h>
44 #include <geogram/basic/assert.h>
45 #include <atomic>
46
47 // In GARGANTUA mode (64-bit indices), we also enable
48 // more than 127 concurrent threads (16-bits cell status,
49 // that contain owner thread id).
50 #ifdef GARGANTUA
51 #define GEO_CONNECTION_MACHINE
52 #endif
53
54 /**
55 * \file geogram/delaunay/delaunay_sync.h
56 * \brief Synchronization primitives for parallel Delaunay
57 */
58
59 namespace GEO {
60
61 /**
62 * \brief An array of cell status codes associates to each tetrahedron
63 * in a Delaunay tetrahedralization
64 * \details Each item can be atomically accessed to implement fine-grained
65 * resource control in a multithreaded context. It is used to memorize
66 * for each tetrahedron the thread that owns it as well as a couple of
67 * flags.
68 */
69 class CellStatusArray {
70 public:
71 #ifdef GEO_CONNECTION_MACHINE
72 // For machines that can run more than 127 concurrent threads
73 typedef uint16_t thread_index_t;
74 typedef uint16_t cell_status_t;
75 static constexpr cell_status_t FREE_CELL = 32767;
76 static constexpr cell_status_t THREAD_MASK = 32767;
77 static constexpr cell_status_t CONFLICT_MASK = 32768;
78 #else
79 typedef uint8_t thread_index_t;
80 typedef uint8_t cell_status_t;
81 static constexpr cell_status_t FREE_CELL = 127;
82 static constexpr cell_status_t THREAD_MASK = 127;
83 static constexpr cell_status_t CONFLICT_MASK = 128;
84 #endif
85
86 static constexpr index_t MAX_THREADS = index_t(THREAD_MASK)-1;
87
88 /**
89 * \brief Creates an empty CellStatusArray
90 */
91 5 CellStatusArray() : cell_status_(nullptr), size_(0), capacity_(0) {
92 5 }
93
94 /**
95 * \brief Creates a CellStatusArray
96 * \param[in] size_in number of cells in the CellStatusArray
97 */
98 CellStatusArray(index_t size_in) :
99 cell_status_(nullptr), size_(0), capacity_(0) {
100 resize(size_in,size_in);
101 }
102
103 /**
104 * \brief CellStatusArray destructor
105 * \details It is illegal to destroy a CellStatusArray if
106 * - threads are still running
107 * - there exists a cell with a status different from FREE_CELL
108 */
109 5 ~CellStatusArray() {
110 5 clear();
111 5 }
112
113 /**
114 * \brief Forbids copy
115 */
116 CellStatusArray(const CellStatusArray& rhs) = delete;
117
118 /**
119 * \brief Forbids copy
120 */
121 CellStatusArray& operator=(const CellStatusArray& rhs) = delete;
122
123 /**
124 * \brief Tentatively acquires a cell.
125 * \param[in] cell the index of the cell
126 * \param[in] status the status to be written in the cell if acquisition
127 * is successful, that is, if the current status of the cell is
128 * FREE_CELL
129 * \return FREE_CELL if acquisition was successful, or the id of the
130 * thread that owns \p cell otherwise.
131 */
132 168136 cell_status_t acquire_cell(index_t cell, cell_status_t status) {
133
1/6
✗ Branch 0 not taken.
✓ Branch 1 taken 168136 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
168136 geo_debug_assert(cell < size_);
134 168136 cell_status_t expected = FREE_CELL;
135 // strong: acquire_cell is not used in a spinlock-like
136 // spinning loop (so we do not want to have "false negatives")
137 168136 cell_status_[cell].compare_exchange_strong(
138 expected,status,
139 std::memory_order_acquire,std::memory_order_acquire
140 ); // this one could probably be relaxed ----^
141 // if compare_exchange was not sucessful, expected contains
142 // the current stored value.
143 168136 return (expected & THREAD_MASK);
144 }
145
146 /**
147 * \brief Releases a cell
148 * \param[in] cell the index of the cell
149 * \pre the cell is owned by the current thread
150 */
151 271027 void release_cell(index_t cell) {
152
1/6
✗ Branch 0 not taken.
✓ Branch 1 taken 271027 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
271027 geo_debug_assert(cell < size_);
153 271027 cell_status_[cell].store(FREE_CELL, std::memory_order_release);
154 271027 }
155
156 /**
157 * \brief Gets the thread that acquired a cell
158 * \param[in] cell the cell
159 * \return the index of the thread that acquired the cell, or
160 * FREE_CELL if the cell is free
161 */
162 3839195 cell_status_t cell_thread(index_t cell) const {
163
1/6
✗ Branch 0 not taken.
✓ Branch 1 taken 3839195 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
3839195 geo_debug_assert(cell < size_);
164 return (
165 3839195 cell_status_[cell].load(std::memory_order_relaxed) &
166 THREAD_MASK
167 3839195 );
168 }
169
170 /**
171 * \brief Tests whether a cell is marked as conflict
172 * \param[in] cell the cell
173 * \retval true if \p cell is marked as conflict
174 * \retval false otherwise
175 * \see mark_cell_as_conflict()
176 */
177 388261 bool cell_is_marked_as_conflict(index_t cell) const {
178
1/6
✗ Branch 0 not taken.
✓ Branch 1 taken 388261 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
388261 geo_debug_assert(cell < size_);
179 return(
180 (
181 388261 cell_status_[cell].load(std::memory_order_relaxed) &
182 CONFLICT_MASK
183 388261 ) != 0
184 388261 );
185 }
186
187 /**
188 * \brief Marks a cell as conflict
189 * \param[in] cell the cell
190 * \pre the cell is owned by the current thread
191 */
192 77597 void mark_cell_as_conflict(index_t cell) {
193 // memory_order_relaxed because this function is always called from
194 // a thread that previously acquired the cell.
195 77597 cell_status_[cell].fetch_or(
196 CONFLICT_MASK, std::memory_order_relaxed
197 );
198 77597 }
199
200 /**
201 * \brief Sets the status of a cell
202 * \param[in] cell the index of the cell
203 * \details uses relaxed memory ordering
204 * \pre the cell is owned by the current thread
205 */
206 102891 void set_cell_status(index_t cell, cell_status_t status) {
207
1/6
✗ Branch 0 not taken.
✓ Branch 1 taken 102891 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
102891 geo_debug_assert(cell < size_);
208 102891 cell_status_[cell].store(status, std::memory_order_relaxed);
209 102891 }
210
211 /**
212 * \brief Resizes this CellStatusArray
213 * \param[in] size_in number of cells
214 * \param[in] capacity_in total number of allocated cells
215 * \pre \p capacity_in >= \p size_in and
216 * no concurrent thread is currently running
217 */
218 18306 void resize(index_t size_in, index_t capacity_in) {
219
1/6
✗ Branch 0 not taken.
✓ Branch 1 taken 18306 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
18306 geo_debug_assert(capacity_in >= size_in);
220
1/6
✗ Branch 1 not taken.
✓ Branch 2 taken 18306 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
18306 geo_debug_assert(!Process::is_running_threads());
221
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 18296 times.
18306 if(capacity_in > capacity_) {
222 10 capacity_ = capacity_in;
223 10 std::atomic<cell_status_t>* old_cell_status = cell_status_;
224
3/4
✓ Branch 1 taken 85218 times.
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 10 times.
85228 cell_status_ = new std::atomic<cell_status_t>[capacity_];
225
2/2
✓ Branch 0 taken 85218 times.
✓ Branch 1 taken 10 times.
85228 for(index_t i=0; i<capacity_; ++i) {
226
2/2
✓ Branch 0 taken 28406 times.
✓ Branch 1 taken 56812 times.
85218 cell_status_t val = (i < size_) ?
227 56812 old_cell_status[i].load(std::memory_order_relaxed) :
228 56812 FREE_CELL;
229 85218 cell_status_[i].store(val, std::memory_order_relaxed);
230 }
231
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 5 times.
10 delete[] old_cell_status;
232 }
233 18306 size_ = size_in;
234 static_assert(std::atomic<cell_status_t>::is_always_lock_free);
235 18306 }
236
237 /**
238 * \brief Resizes this CellStatusArray
239 * \param[in] size_in number of cells
240 * \pre all cells are free and no concurrent thread is currently running
241 */
242 5 void resize(index_t size_in) {
243 5 resize(size_in, size_in);
244 5 }
245
246 /**
247 * \brief Reserves additional space
248 * \param[in] new_capacity on exit, this CellStatusArray will have at
249 * least sufficient space for \p new_capacity elements without needing
250 * to reallocate. Size is not modified. Operates like its std::vector
251 * counterpart.
252 */
253 void reserve(index_t new_capacity) {
254 if(new_capacity > capacity_) {
255 resize(size_, new_capacity);
256 }
257 }
258
259 /**
260 * \brief Increases the size of the array for one additional element
261 * \details capacity is doubled each time additional space is needed
262 */
263 18301 void grow() {
264
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 18296 times.
18301 if(size_+1 >= capacity_) {
265
1/2
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
5 resize(size_+1, std::max(capacity_*2,size_+1));
266 } else {
267 18296 resize(size_+1, capacity_);
268 }
269 18301 }
270
271 /**
272 * \brief Gets the size of this CellStatusArray
273 * \return the number of cells
274 */
275 18301 index_t size() const {
276 18301 return size_;
277 }
278
279 /**
280 * \brief Clears this CellStatusArray
281 * \details Deallocates all memory
282 * \pre all the cells are free and no concurrent thread is running
283 */
284 5 void clear() {
285
1/6
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
5 geo_debug_assert(!Process::is_running_threads());
286 #ifdef GEO_DEBUG
287
2/2
✓ Branch 0 taken 46707 times.
✓ Branch 1 taken 5 times.
46712 for(index_t i=0; i<size_; ++i) {
288
1/6
✗ Branch 1 not taken.
✓ Branch 2 taken 46707 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
46707 geo_debug_assert(cell_thread(i) == FREE_CELL);
289 }
290 #endif
291
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 delete[] cell_status_;
292 5 cell_status_ = nullptr;
293 5 size_ = 0;
294 5 capacity_ = 0;
295 5 }
296
297 private:
298 std::atomic<cell_status_t>* cell_status_;
299 index_t size_;
300 index_t capacity_;
301 };
302 }
303
304 #endif
305