Skip to content

Commit 59f69a6

Browse files
committed
Improve locking for properties.
This fixes the issue where two addresses that hash to the same address could deadlock and also fixes a performance issue due to using spinlocks with no OS-mediated sleep. Initially this makes the spinlocks.h consumers compile as C++ so that they can use C++ abstractions over futexes and similar. A lot of this code called calloc, so now has some nicer C++ wrappers for that. This also includes some fixes to work around older C++ standard libraries. Also includes some small tweaks to Cirrus, though Cirrus is going away soon so we'll need an alternative for FreeBSD CI.
1 parent b67709a commit 59f69a6

File tree

12 files changed

+283
-164
lines changed

12 files changed

+283
-164
lines changed

.cirrus.yml

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
libcxxrt_freebsd_task:
22
matrix:
33
- freebsd_instance:
4-
image_family: freebsd-13-5
4+
image_family: freebsd-15-0-amd64-zfs
55
- freebsd_instance:
6-
image_family: freebsd-15-0-snap
7-
- freebsd_instance:
8-
image_family: freebsd-14-2
6+
image_family: freebsd-14-3
97

108
install_script: pkg install -y cmake ninja git
119

@@ -31,7 +29,7 @@ libcxxrt_freebsd_task:
3129

3230
libcxxrt_master_task:
3331
freebsd_instance:
34-
image_family: freebsd-14-2
32+
image_family: freebsd-14-3
3533
install_script: pkg install -y cmake ninja git
3634

3735
clone_script: |

CMakeLists.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,13 @@ add_compile_definitions(GNUSTEP __OBJC_RUNTIME_INTERNAL__=1 __OBJC_BOOL)
5555
set(libobjc_ASM_SRCS
5656
objc_msgSend.S)
5757
set(libobjc_OBJCXX_SRCS
58+
properties.mm
5859
arc.mm
60+
associate.mm
5961
)
6062
set(libobjc_OBJC_SRCS
6163
NSBlocks.m
62-
associate.m
63-
blocks_runtime_np.m
64-
properties.m)
64+
blocks_runtime_np.m)
6565
set(libobjc_C_SRCS
6666
alias_table.c
6767
builtin_classes.c
@@ -257,7 +257,7 @@ endif ()
257257

258258
add_library(objc SHARED ${libobjc_C_SRCS} ${libobjc_ASM_SRCS} ${libobjc_OBJC_SRCS} ${libobjc_OBJCXX_SRCS} ${libobjc_ASM_OBJS})
259259
target_compile_options(objc PRIVATE "$<$<OR:$<COMPILE_LANGUAGE:OBJC>,$<COMPILE_LANGUAGE:OBJCXX>>:-Wno-gnu-folding-constant;-Wno-deprecated-objc-isa-usage;-Wno-objc-root-class;-fobjc-runtime=gnustep-2.0>$<$<COMPILE_LANGUAGE:C>:-Xclang;-fexceptions;-Wno-gnu-folding-constant>")
260-
target_compile_features(objc PRIVATE cxx_std_17)
260+
target_compile_features(objc PRIVATE cxx_std_20)
261261

262262
list(APPEND libobjc_CXX_SRCS ${libobjcxx_CXX_SRCS})
263263
target_sources(objc PRIVATE ${libobjc_CXX_SRCS})

associate.m renamed to associate.mm

Lines changed: 44 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "selector.h"
1212
#include "lock.h"
1313
#include "gc_ops.h"
14+
#include "helpers.hh"
1415

1516
/**
1617
* A single associative reference. Contains the key, value, and association
@@ -105,7 +106,7 @@ static void cleanupReferenceList(struct reference_list *list)
105106
// Full barrier - ensure that we've zero'd the key before doing
106107
// this!
107108
__sync_synchronize();
108-
objc_release(r->object);
109+
objc_release((id)r->object);
109110
}
110111
r->object = 0;
111112
r->policy = 0;
@@ -117,7 +118,7 @@ static void freeReferenceList(struct reference_list *l)
117118
{
118119
if (NULL == l) { return; }
119120
freeReferenceList(l->next);
120-
gc->free(l);
121+
free(l);
121122
}
122123

123124
static void setReference(struct reference_list *list,
@@ -135,44 +136,45 @@ static void setReference(struct reference_list *list,
135136
break;
136137
case OBJC_ASSOCIATION_RETAIN_NONATOMIC:
137138
case OBJC_ASSOCIATION_RETAIN:
138-
obj = objc_retain(obj);
139+
obj = objc_retain((id)obj);
139140
case OBJC_ASSOCIATION_ASSIGN:
140141
break;
141142
}
142143
// While inserting into the list, we need to lock it temporarily.
143-
volatile int *lock = lock_for_pointer(list);
144-
lock_spinlock(lock);
145144
struct reference *r = findReference(list, key);
146-
// If there's an existing reference, then we can update it, otherwise we
147-
// have to install a new one
148-
if (NULL == r)
149145
{
150-
// Search for an unused slot
151-
r = findReference(list, 0);
146+
auto lock = acquire_locks_for_pointers(list);
147+
// If there's an existing reference, then we can update it, otherwise we
148+
// have to install a new one
152149
if (NULL == r)
153150
{
154-
struct reference_list *l = list;
151+
// Search for an unused slot
152+
r = findReference(list, 0);
153+
if (NULL == r)
154+
{
155+
struct reference_list *l = list;
155156

156-
while (NULL != l->next) { l = l->next; }
157+
while (NULL != l->next) { l = l->next; }
157158

158-
l->next = gc->malloc(sizeof(struct reference_list));
159-
r = &l->next->list[0];
159+
l->next = allocate_zeroed<struct reference_list>();
160+
r = &l->next->list[0];
161+
}
162+
r->key = key;
160163
}
161-
r->key = key;
162164
}
163-
unlock_spinlock(lock);
164165
// Now we only need to lock if the old or new property is atomic
165166
BOOL needLock = isAtomic(r->policy) || isAtomic(policy);
167+
ThinLock *lock;
166168
if (needLock)
167169
{
168170
lock = lock_for_pointer(r);
169-
lock_spinlock(lock);
171+
lock->lock();
170172
}
171173
@try
172174
{
173175
if (OBJC_ASSOCIATION_ASSIGN != r->policy)
174176
{
175-
objc_release(r->object);
177+
objc_release((id)r->object);
176178
}
177179
}
178180
@finally
@@ -182,7 +184,7 @@ static void setReference(struct reference_list *list,
182184
}
183185
if (needLock)
184186
{
185-
unlock_spinlock(lock);
187+
lock->unlock();
186188
}
187189
}
188190

@@ -201,8 +203,8 @@ static inline Class findHiddenClass(id obj)
201203

202204
static Class allocateHiddenClass(Class superclass)
203205
{
204-
Class newClass =
205-
calloc(1, sizeof(struct objc_class) + sizeof(struct reference_list));
206+
struct objc_class *newClass =
207+
allocate_zeroed<struct objc_class>(sizeof(struct reference_list));
206208

207209
if (Nil == newClass) { return Nil; }
208210

@@ -221,9 +223,9 @@ static Class allocateHiddenClass(Class superclass)
221223

222224
LOCK_RUNTIME_FOR_SCOPE();
223225
newClass->sibling_class = superclass->subclass_list;
224-
superclass->subclass_list = newClass;
226+
superclass->subclass_list = (Class)newClass;
225227

226-
return newClass;
228+
return (Class)newClass;
227229
}
228230

229231
static inline Class initHiddenClassForObject(id obj)
@@ -248,7 +250,7 @@ static void deallocHiddenClass(id obj, SEL _cmd)
248250
Class hiddenClass = findHiddenClass(obj);
249251
// After calling [super dealloc], the object will no longer exist.
250252
// Free the hidden class.
251-
struct reference_list *list = object_getIndexedIvars(hiddenClass);
253+
struct reference_list *list = static_cast<struct reference_list *>(object_getIndexedIvars(hiddenClass));
252254
DESTROY_LOCK(&list->lock);
253255
cleanupReferenceList(list);
254256
freeReferenceList(list->next);
@@ -289,38 +291,33 @@ static void deallocHiddenClass(id obj, SEL _cmd)
289291
Class cls = (Class)object;
290292
if ((NULL == cls->extra_data) && create)
291293
{
292-
volatile int *lock = lock_for_pointer(cls);
293-
struct reference_list *list = gc->malloc(sizeof(struct reference_list));
294-
lock_spinlock(lock);
294+
struct reference_list *list = allocate_zeroed<struct reference_list>();
295+
auto guard = acquire_locks_for_pointers(cls);
295296
if (NULL == cls->extra_data)
296297
{
297298
INIT_LOCK(list->lock);
298299
cls->extra_data = list;
299-
unlock_spinlock(lock);
300300
}
301301
else
302302
{
303-
unlock_spinlock(lock);
304-
gc->free(list);
303+
free(list);
305304
}
306305
}
307306
return cls->extra_data;
308307
}
309308
Class hiddenClass = findHiddenClass(object);
310309
if ((NULL == hiddenClass) && create)
311310
{
312-
volatile int *lock = lock_for_pointer(object);
313-
lock_spinlock(lock);
311+
auto guard = acquire_locks_for_pointers(object);
314312
hiddenClass = findHiddenClass(object);
315313
if (NULL == hiddenClass)
316314
{
317315
hiddenClass = initHiddenClassForObject(object);
318-
struct reference_list *list = object_getIndexedIvars(hiddenClass);
316+
struct reference_list *list = static_cast<struct reference_list *>(object_getIndexedIvars(hiddenClass));
319317
INIT_LOCK(list->lock);
320318
}
321-
unlock_spinlock(lock);
322319
}
323-
return hiddenClass ? object_getIndexedIvars(hiddenClass) : NULL;
320+
return hiddenClass ? static_cast<struct reference_list*>(object_getIndexedIvars(hiddenClass)) : nullptr;
324321
}
325322

326323
void objc_setAssociatedObject(id object,
@@ -345,9 +342,9 @@ id objc_getAssociatedObject(id object, const void *key)
345342
// Apple's objc4 retains and autoreleases the object under these policies
346343
if (r->policy & OBJC_ASSOCIATION_RETAIN_NONATOMIC)
347344
{
348-
objc_retainAutorelease(r->object);
345+
objc_retainAutorelease((id)r->object);
349346
}
350-
return r->object;
347+
return (id)r->object;
351348
}
352349
if (class_isMetaClass(object->isa))
353350
{
@@ -363,7 +360,7 @@ id objc_getAssociatedObject(id object, const void *key)
363360
}
364361
if (Nil != cls)
365362
{
366-
struct reference_list *next_list = object_getIndexedIvars(cls);
363+
struct reference_list *next_list = static_cast<struct reference_list *>(object_getIndexedIvars(cls));
367364
if (list != next_list)
368365
{
369366
list = next_list;
@@ -372,9 +369,9 @@ id objc_getAssociatedObject(id object, const void *key)
372369
{
373370
if (r->policy & OBJC_ASSOCIATION_RETAIN_NONATOMIC)
374371
{
375-
objc_retainAutorelease(r->object);
372+
objc_retainAutorelease((id)r->object);
376373
}
377-
return r->object;
374+
return (id)r->object;
378375
}
379376
}
380377
cls = class_getSuperclass(cls);
@@ -433,16 +430,14 @@ static Class hiddenClassForObject(id object)
433430
Class hiddenClass = findHiddenClass(object);
434431
if (NULL == hiddenClass)
435432
{
436-
volatile int *lock = lock_for_pointer(object);
437-
lock_spinlock(lock);
433+
auto guard = acquire_locks_for_pointers(object);
438434
hiddenClass = findHiddenClass(object);
439435
if (NULL == hiddenClass)
440436
{
441437
hiddenClass = initHiddenClassForObject(object);
442-
struct reference_list *list = object_getIndexedIvars(hiddenClass);
438+
struct reference_list *list = static_cast<struct reference_list*>(object_getIndexedIvars(hiddenClass));
443439
INIT_LOCK(list->lock);
444440
}
445-
unlock_spinlock(lock);
446441
}
447442
return hiddenClass;
448443
}
@@ -464,13 +459,13 @@ id object_clone_np(id object)
464459
// Make sure that the prototype has a hidden class, so that methods added
465460
// to it will appear in the clone.
466461
referenceListForObject(object, YES);
467-
id new = class_createInstance(object->isa, 0);
468-
Class hiddenClass = initHiddenClassForObject(new);
469-
struct reference_list *list = object_getIndexedIvars(hiddenClass);
462+
id newInstance = class_createInstance(object->isa, 0);
463+
Class hiddenClass = initHiddenClassForObject(newInstance);
464+
struct reference_list *list = static_cast<struct reference_list*>(object_getIndexedIvars(hiddenClass));
470465
INIT_LOCK(list->lock);
471-
objc_setAssociatedObject(new, &prototypeKey, object,
466+
objc_setAssociatedObject(newInstance, &prototypeKey, object,
472467
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
473-
return new;
468+
return newInstance;
474469
}
475470

476471
id object_getPrototype_np(id object)

class.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#define __OBJC_CLASS_H_INCLUDED
33
#include "visibility.h"
44
#include "objc/runtime.h"
5+
#include "sarray2.h"
56
#include <stdint.h>
67

78
#ifdef __cplusplus
@@ -96,7 +97,7 @@ struct objc_class
9697
* The dispatch table for this class. Intialized and maintained by the
9798
* runtime.
9899
*/
99-
void *dtable;
100+
SparseArray *dtable;
100101
/**
101102
* A pointer to the first subclass for this class. Filled in by the
102103
* runtime.

dtable.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -696,8 +696,8 @@ static void remove_dtable(InitializingDtable* meta_buffer)
696696
LOCK(&initialize_lock);
697697
InitializingDtable *buffer = meta_buffer->next;
698698
// Install the dtable:
699-
meta_buffer->class->dtable = meta_buffer->dtable;
700-
buffer->class->dtable = buffer->dtable;
699+
meta_buffer->owner->dtable = meta_buffer->dtable;
700+
buffer->owner->dtable = buffer->dtable;
701701
// Remove the look-aside buffer entry.
702702
if (temporary_dtables == meta_buffer)
703703
{
@@ -706,7 +706,7 @@ static void remove_dtable(InitializingDtable* meta_buffer)
706706
else
707707
{
708708
InitializingDtable *prev = temporary_dtables;
709-
while (prev->next->class != meta_buffer->class)
709+
while (prev->next->owner != meta_buffer->owner)
710710
{
711711
prev = prev->next;
712712
}

dtable.h

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@
66
#include <stdint.h>
77
#include <stdio.h>
88

9-
#ifdef __OBJC_LOW_MEMORY__
10-
typedef struct objc_dtable* dtable_t;
11-
struct objc_slot* objc_dtable_lookup(dtable_t dtable, uint32_t uid);
12-
#else
139
typedef SparseArray* dtable_t;
14-
# define objc_dtable_lookup SparseArrayLookup
10+
#define objc_dtable_lookup SparseArrayLookup
11+
12+
typedef struct objc_class *Class;
13+
14+
#ifdef __cplusplus
15+
extern "C"
16+
{
1517
#endif
1618

1719
/**
@@ -27,7 +29,7 @@ PRIVATE extern dtable_t uninstalled_dtable;
2729
typedef struct _InitializingDtable
2830
{
2931
/** The class that owns the dtable. */
30-
Class class;
32+
struct objc_class *owner;
3133
/** The dtable for this class. */
3234
dtable_t dtable;
3335
/** The next uninstalled dtable in the list. */
@@ -50,12 +52,13 @@ OBJC_PUBLIC
5052
int objc_sync_enter(id object);
5153
OBJC_PUBLIC
5254
int objc_sync_exit(id object);
55+
5356
/**
5457
* Returns the dtable for a given class. If we are currently in an +initialize
5558
* method then this will block if called from a thread other than the one
5659
* running the +initialize method.
5760
*/
58-
static inline dtable_t dtable_for_class(Class cls)
61+
static inline dtable_t dtable_for_class(struct objc_class *cls)
5962
{
6063
if (classHasInstalledDtable(cls))
6164
{
@@ -77,7 +80,7 @@ static inline dtable_t dtable_for_class(Class cls)
7780
InitializingDtable *buffer = temporary_dtables;
7881
while (NULL != buffer)
7982
{
80-
if (buffer->class == cls)
83+
if (buffer->owner == cls)
8184
{
8285
dtable = buffer->dtable;
8386
break;
@@ -139,3 +142,7 @@ void free_dtable(dtable_t dtable);
139142
* is installed.
140143
*/
141144
void checkARCAccessorsSlow(Class cls);
145+
146+
#ifdef __cplusplus
147+
}
148+
#endif

0 commit comments

Comments
 (0)