Skip to content

Commit cc1d7f5

Browse files
committed
Improve the support of atomics
This change: * Starts using GCC's and clang's `__atomic_*` intrinsics instead of the `__sync_*` ones, since the former supercede the latter (and can be safely replaced by their equivalent `__atomic_*` version with the sequentially consistent model). * Makes `git_atomic64`'s value `volatile`. Otherwise, this will make ThreadSanitizer complain. * Adds ways to load the values from atomics. As it turns out, unsynchronized read are okay only in some architectures, but if we want to be correct (and make ThreadSanitizer happy), those loads should also be performed with the atomic builtins. * Fixes two ThreadSanitizer warnings, as a proof-of-concept that this works: - Avoid directly accessing `git_refcount`'s `owner` directly, and instead makes all callers go through the `GIT_REFCOUNT_*()` macros, which also use the atomic utilities. - Makes `pool_system_page_size()` race-free. Part of: libgit2#5592
1 parent 2307a22 commit cc1d7f5

File tree

5 files changed

+152
-32
lines changed

5 files changed

+152
-32
lines changed

src/cache.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ static void *cache_store(git_cache *cache, git_cached_obj *entry)
168168
return entry;
169169

170170
/* soften the load on the cache */
171-
if (git_cache__current_storage.val > git_cache__max_storage)
171+
if (git_atomic_ssize_get(&git_cache__current_storage) > git_cache__max_storage)
172172
cache_evict_entries(cache);
173173

174174
/* not found */

src/odb.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ typedef struct
4444

4545
static git_cache *odb_cache(git_odb *odb)
4646
{
47-
if (odb->rc.owner != NULL) {
48-
git_repository *owner = odb->rc.owner;
47+
git_repository *owner = GIT_REFCOUNT_OWNER(odb);
48+
if (owner != NULL) {
4949
return &owner->objects;
5050
}
5151

@@ -664,7 +664,7 @@ int git_odb_open(git_odb **out, const char *objects_dir)
664664
int git_odb__set_caps(git_odb *odb, int caps)
665665
{
666666
if (caps == GIT_ODB_CAP_FROM_OWNER) {
667-
git_repository *repo = odb->rc.owner;
667+
git_repository *repo = GIT_REFCOUNT_OWNER(odb);
668668
int val;
669669

670670
if (!repo) {

src/pool.c

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,18 @@ static void *pool_alloc_page(git_pool *pool, size_t size);
2323

2424
static size_t pool_system_page_size(void)
2525
{
26-
static size_t size = 0;
26+
static git_atomic_ssize cached_size = {0};
27+
size_t page_size = 0;
2728

28-
if (!size) {
29-
size_t page_size;
29+
if ((page_size = (size_t)git_atomic_ssize_get(&cached_size)) == 0) {
3030
if (git__page_size(&page_size) < 0)
3131
page_size = 4096;
3232
/* allow space for malloc overhead */
33-
size = (page_size - (2 * sizeof(void *)) - sizeof(git_pool_page));
33+
page_size -= (2 * sizeof(void *)) + sizeof(git_pool_page);
34+
git_atomic_ssize_set(&cached_size, (int64_t)page_size);
3435
}
3536

36-
return size;
37+
return page_size;
3738
}
3839

3940
#ifndef GIT_DEBUG_POOL

src/thread-utils.h

Lines changed: 140 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,31 @@
77
#ifndef INCLUDE_thread_utils_h__
88
#define INCLUDE_thread_utils_h__
99

10-
#if defined(__GNUC__) && defined(GIT_THREADS)
10+
#if defined(GIT_THREADS)
11+
12+
#if defined(__clang__)
13+
14+
# if (__clang_major__ < 3 || (__clang_major__ == 3 && __clang_minor__ < 1))
15+
# error Atomic primitives do not exist on this version of clang; configure libgit2 with -DTHREADSAFE=OFF
16+
# else
17+
# define GIT_BUILTIN_ATOMIC
18+
# endif
19+
20+
#elif defined(__GNUC__)
21+
1122
# if (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 1))
1223
# error Atomic primitives do not exist on this version of gcc; configure libgit2 with -DTHREADSAFE=OFF
1324
# endif
25+
# if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7))
26+
# define GIT_BUILTIN_ATOMIC
27+
# else
28+
# define GIT_BUILTIN_SYNC
29+
# endif
30+
1431
#endif
1532

33+
#endif /* GIT_THREADS */
34+
1635
/* Common operations even if threading has been disabled */
1736
typedef struct {
1837
#if defined(GIT_WIN32)
@@ -26,21 +45,25 @@ typedef struct {
2645

2746
typedef struct {
2847
#if defined(GIT_WIN32)
29-
__int64 val;
48+
volatile __int64 val;
3049
#else
31-
int64_t val;
50+
volatile int64_t val;
3251
#endif
3352
} git_atomic64;
3453

3554
typedef git_atomic64 git_atomic_ssize;
3655

56+
#define git_atomic_ssize_set git_atomic64_set
3757
#define git_atomic_ssize_add git_atomic64_add
58+
#define git_atomic_ssize_get git_atomic64_get
3859

3960
#else
4061

4162
typedef git_atomic git_atomic_ssize;
4263

64+
#define git_atomic_ssize_set git_atomic_set
4365
#define git_atomic_ssize_add git_atomic_add
66+
#define git_atomic_ssize_get git_atomic_get
4467

4568
#endif
4669

@@ -56,7 +79,9 @@ GIT_INLINE(void) git_atomic_set(git_atomic *a, int val)
5679
{
5780
#if defined(GIT_WIN32)
5881
InterlockedExchange(&a->val, (LONG)val);
59-
#elif defined(__GNUC__)
82+
#elif defined(GIT_BUILTIN_ATOMIC)
83+
__atomic_store_n(&a->val, val, __ATOMIC_SEQ_CST);
84+
#elif defined(GIT_BUILTIN_SYNC)
6085
__sync_lock_test_and_set(&a->val, val);
6186
#else
6287
# error "Unsupported architecture for atomic operations"
@@ -67,7 +92,9 @@ GIT_INLINE(int) git_atomic_inc(git_atomic *a)
6792
{
6893
#if defined(GIT_WIN32)
6994
return InterlockedIncrement(&a->val);
70-
#elif defined(__GNUC__)
95+
#elif defined(GIT_BUILTIN_ATOMIC)
96+
return __atomic_add_fetch(&a->val, 1, __ATOMIC_SEQ_CST);
97+
#elif defined(GIT_BUILTIN_SYNC)
7198
return __sync_add_and_fetch(&a->val, 1);
7299
#else
73100
# error "Unsupported architecture for atomic operations"
@@ -78,7 +105,9 @@ GIT_INLINE(int) git_atomic_add(git_atomic *a, int32_t addend)
78105
{
79106
#if defined(GIT_WIN32)
80107
return InterlockedExchangeAdd(&a->val, addend);
81-
#elif defined(__GNUC__)
108+
#elif defined(GIT_BUILTIN_ATOMIC)
109+
return __atomic_add_fetch(&a->val, addend, __ATOMIC_SEQ_CST);
110+
#elif defined(GIT_BUILTIN_SYNC)
82111
return __sync_add_and_fetch(&a->val, addend);
83112
#else
84113
# error "Unsupported architecture for atomic operations"
@@ -89,34 +118,76 @@ GIT_INLINE(int) git_atomic_dec(git_atomic *a)
89118
{
90119
#if defined(GIT_WIN32)
91120
return InterlockedDecrement(&a->val);
92-
#elif defined(__GNUC__)
121+
#elif defined(GIT_BUILTIN_ATOMIC)
122+
return __atomic_sub_fetch(&a->val, 1, __ATOMIC_SEQ_CST);
123+
#elif defined(GIT_BUILTIN_SYNC)
93124
return __sync_sub_and_fetch(&a->val, 1);
94125
#else
95126
# error "Unsupported architecture for atomic operations"
96127
#endif
97128
}
98129

130+
GIT_INLINE(int) git_atomic_get(git_atomic *a)
131+
{
132+
#if defined(GIT_WIN32)
133+
return (int)InterlockedCompareExchange(&a->val, 0, 0);
134+
#elif defined(GIT_BUILTIN_ATOMIC)
135+
return __atomic_load_n(&a->val, __ATOMIC_SEQ_CST);
136+
#elif defined(GIT_BUILTIN_SYNC)
137+
return __sync_val_compare_and_swap(&a->val, 0, 0);
138+
#else
139+
# error "Unsupported architecture for atomic operations"
140+
#endif
141+
}
142+
99143
GIT_INLINE(void *) git___compare_and_swap(
100144
void * volatile *ptr, void *oldval, void *newval)
101145
{
102-
volatile void *foundval;
103146
#if defined(GIT_WIN32)
147+
volatile void *foundval;
104148
foundval = InterlockedCompareExchangePointer((volatile PVOID *)ptr, newval, oldval);
105-
#elif defined(__GNUC__)
149+
return (foundval == oldval) ? oldval : newval;
150+
#elif defined(GIT_BUILTIN_ATOMIC)
151+
bool success = __atomic_compare_exchange(ptr, &oldval, &newval, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
152+
return success ? oldval : newval;
153+
#elif defined(GIT_BUILTIN_SYNC)
154+
volatile void *foundval;
106155
foundval = __sync_val_compare_and_swap(ptr, oldval, newval);
156+
return (foundval == oldval) ? oldval : newval;
107157
#else
108158
# error "Unsupported architecture for atomic operations"
109159
#endif
110-
return (foundval == oldval) ? oldval : newval;
111160
}
112161

113162
GIT_INLINE(volatile void *) git___swap(
114163
void * volatile *ptr, void *newval)
115164
{
116165
#if defined(GIT_WIN32)
117166
return InterlockedExchangePointer(ptr, newval);
118-
#else
167+
#elif defined(GIT_BUILTIN_ATOMIC)
168+
void * volatile foundval;
169+
__atomic_exchange(ptr, &newval, &foundval, __ATOMIC_SEQ_CST);
170+
return foundval;
171+
#elif defined(GIT_BUILTIN_SYNC)
119172
return __sync_lock_test_and_set(ptr, newval);
173+
#else
174+
# error "Unsupported architecture for atomic operations"
175+
#endif
176+
}
177+
178+
GIT_INLINE(volatile void *) git___load(void * volatile *ptr)
179+
{
180+
#if defined(GIT_WIN32)
181+
void *newval = NULL, *oldval = NULL;
182+
volatile void *foundval = NULL;
183+
foundval = InterlockedCompareExchangePointer((volatile PVOID *)ptr, newval, oldval);
184+
return foundval;
185+
#elif defined(GIT_BUILTIN_ATOMIC)
186+
return (volatile void *)__atomic_load_n(ptr, __ATOMIC_SEQ_CST);
187+
#elif defined(GIT_BUILTIN_SYNC)
188+
return (volatile void *)__sync_val_compare_and_swap(ptr, 0, 0);
189+
#else
190+
# error "Unsupported architecture for atomic operations"
120191
#endif
121192
}
122193

@@ -126,13 +197,41 @@ GIT_INLINE(int64_t) git_atomic64_add(git_atomic64 *a, int64_t addend)
126197
{
127198
#if defined(GIT_WIN32)
128199
return InterlockedExchangeAdd64(&a->val, addend);
129-
#elif defined(__GNUC__)
200+
#elif defined(GIT_BUILTIN_ATOMIC)
201+
return __atomic_add_fetch(&a->val, addend, __ATOMIC_SEQ_CST);
202+
#elif defined(GIT_BUILTIN_SYNC)
130203
return __sync_add_and_fetch(&a->val, addend);
131204
#else
132205
# error "Unsupported architecture for atomic operations"
133206
#endif
134207
}
135208

209+
GIT_INLINE(void) git_atomic64_set(git_atomic64 *a, int64_t val)
210+
{
211+
#if defined(GIT_WIN32)
212+
InterlockedExchange64(&a->val, val);
213+
#elif defined(GIT_BUILTIN_ATOMIC)
214+
__atomic_store_n(&a->val, val, __ATOMIC_SEQ_CST);
215+
#elif defined(GIT_BUILTIN_SYNC)
216+
__sync_lock_test_and_set(&a->val, val);
217+
#else
218+
# error "Unsupported architecture for atomic operations"
219+
#endif
220+
}
221+
222+
GIT_INLINE(int64_t) git_atomic64_get(git_atomic64 *a)
223+
{
224+
#if defined(GIT_WIN32)
225+
return (int64_t)InterlockedCompareExchange64(&a->val, 0, 0);
226+
#elif defined(GIT_BUILTIN_ATOMIC)
227+
return __atomic_load_n(&a->val, __ATOMIC_SEQ_CST);
228+
#elif defined(GIT_BUILTIN_SYNC)
229+
return __sync_val_compare_and_swap(&a->val, 0, 0);
230+
#else
231+
# error "Unsupported architecture for atomic operations"
232+
#endif
233+
}
234+
136235
#endif
137236

138237
#else
@@ -190,6 +289,11 @@ GIT_INLINE(int) git_atomic_dec(git_atomic *a)
190289
return --a->val;
191290
}
192291

292+
GIT_INLINE(int) git_atomic_get(git_atomic *a)
293+
{
294+
return (int)a->val;
295+
}
296+
193297
GIT_INLINE(void *) git___compare_and_swap(
194298
void * volatile *ptr, void *oldval, void *newval)
195299
{
@@ -216,15 +320,20 @@ GIT_INLINE(int64_t) git_atomic64_add(git_atomic64 *a, int64_t addend)
216320
return a->val;
217321
}
218322

219-
#endif
220-
221-
#endif
323+
GIT_INLINE(void) git_atomic64_set(git_atomic64 *a, int64_t val)
324+
{
325+
a->val = val;
326+
}
222327

223-
GIT_INLINE(int) git_atomic_get(git_atomic *a)
328+
GIT_INLINE(int64_t) git_atomic64_get(git_atomic64 *a)
224329
{
225-
return (int)a->val;
330+
return (int64_t)a->val;
226331
}
227332

333+
#endif
334+
335+
#endif
336+
228337
/* Atomically replace oldval with newval
229338
* @return oldval if it was replaced or newval if it was not
230339
*/
@@ -233,14 +342,24 @@ GIT_INLINE(int) git_atomic_get(git_atomic *a)
233342

234343
#define git__swap(ptr, val) (void *)git___swap((void * volatile *)&ptr, val)
235344

345+
#define git__load(ptr) (void *)git___load((void * volatile *)&ptr)
346+
236347
extern int git_online_cpus(void);
237348

238-
#if defined(GIT_THREADS) && defined(_MSC_VER)
239-
# define GIT_MEMORY_BARRIER MemoryBarrier()
240-
#elif defined(GIT_THREADS)
241-
# define GIT_MEMORY_BARRIER __sync_synchronize()
349+
#if defined(GIT_THREADS)
350+
351+
# if defined(GIT_WIN32)
352+
# define GIT_MEMORY_BARRIER MemoryBarrier()
353+
# elif defined(GIT_BUILTIN_ATOMIC)
354+
# define GIT_MEMORY_BARRIER __atomic_thread_fence(__ATOMIC_SEQ_CST)
355+
# elif defined(GIT_BUILTIN_SYNC)
356+
# define GIT_MEMORY_BARRIER __sync_synchronize()
357+
# endif
358+
242359
#else
360+
243361
# define GIT_MEMORY_BARRIER /* noop */
362+
244363
#endif
245364

246365
#endif

src/util.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,10 +186,10 @@ typedef void (*git_refcount_freeptr)(void *r);
186186
}
187187

188188
#define GIT_REFCOUNT_OWN(r, o) { \
189-
(r)->rc.owner = o; \
189+
(void)git__swap((r)->rc.owner, o); \
190190
}
191191

192-
#define GIT_REFCOUNT_OWNER(r) ((r)->rc.owner)
192+
#define GIT_REFCOUNT_OWNER(r) git__load((r)->rc.owner)
193193

194194
#define GIT_REFCOUNT_VAL(r) git_atomic_get((r)->rc.refcount)
195195

0 commit comments

Comments
 (0)