Index: lib/tsan/rtl/tsan_libdispatch_mac.cc =================================================================== --- lib/tsan/rtl/tsan_libdispatch_mac.cc +++ lib/tsan/rtl/tsan_libdispatch_mac.cc @@ -33,6 +33,35 @@ dispatch_function_t orig_work; } tsan_block_context_t; +// The offsets of different fields of the dispatch_queue_t structure, exported +// by libdispatch.dylib. +extern "C" struct dispatch_queue_offsets_s { + const uint16_t dqo_version; + const uint16_t dqo_label; + const uint16_t dqo_label_size; + const uint16_t dqo_flags; + const uint16_t dqo_flags_size; + const uint16_t dqo_serialnum; + const uint16_t dqo_serialnum_size; + const uint16_t dqo_width; + const uint16_t dqo_width_size; + const uint16_t dqo_running; + const uint16_t dqo_running_size; + const uint16_t dqo_suspend_cnt; + const uint16_t dqo_suspend_cnt_size; + const uint16_t dqo_target_queue; + const uint16_t dqo_target_queue_size; + const uint16_t dqo_priority; + const uint16_t dqo_priority_size; +} dispatch_queue_offsets; + +static bool IsQueueSerial(dispatch_queue_t q) { + CHECK_EQ(dispatch_queue_offsets.dqo_width_size, 2); + uptr width = *(uint16_t *)(((uptr)q) + dispatch_queue_offsets.dqo_width); + CHECK_NE(width, 0); + return width == 1; +} + static tsan_block_context_t *AllocContext(ThreadState *thr, uptr pc, dispatch_queue_t queue, void *orig_context, @@ -49,7 +78,13 @@ SCOPED_INTERCEPTOR_RAW(dispatch_async_f_callback_wrap); tsan_block_context_t *context = (tsan_block_context_t *)param; Acquire(thr, pc, (uptr)context); + // In serial queues, work items can be executed on different threads, we need + // to explicitly synchronize on the queue itself. + if (IsQueueSerial(context->queue)) + Acquire(thr, pc, (uptr)context->queue); context->orig_work(context->orig_context); + if (IsQueueSerial(context->queue)) + Release(thr, pc, (uptr)context->queue); user_free(thr, pc, context); } Index: test/tsan/Darwin/gcd-serial-queue-norace.mm =================================================================== --- test/tsan/Darwin/gcd-serial-queue-norace.mm +++ test/tsan/Darwin/gcd-serial-queue-norace.mm @@ -0,0 +1,40 @@ +// RUN: %clang_tsan %s -o %t -framework Foundation +// RUN: %run %t 2>&1 + +#import + +#import "../test.h" + +long global; + +int main() { + NSLog(@"Hello world."); + NSLog(@"addr=%p\n", &global); + + dispatch_queue_t q1 = dispatch_queue_create("my.queue1", DISPATCH_QUEUE_CONCURRENT); + dispatch_queue_t q2 = dispatch_queue_create("my.queue2", DISPATCH_QUEUE_SERIAL); + + global = 42; + for (int i = 0; i < 10; i++) { + dispatch_async(q1, ^{ + for (int i = 0; i < 100; i++) { + dispatch_sync(q2, ^{ + global++; + }); + } + }); + } + + dispatch_barrier_async(q1, ^{ + dispatch_sync(dispatch_get_main_queue(), ^{ + CFRunLoopStop(CFRunLoopGetCurrent()); + }); + }); + + CFRunLoopRun(); + NSLog(@"Done."); +} + +// CHECK: Hello world. +// CHECK: Done. +// CHECK-NOT: WARNING: ThreadSanitizer