[tsan] Implement basic GCD interceptors for OS X

We need to intercept libdispatch APIs (dispatch_sync, dispatch_async, etc.) to add synchronization between the code that submits the task and the code that gets executed (possibly on a different thread). This patch adds release+acquire semantics for dispatch_sync, and dispatch_async (plus their "_f" and barrier variants). The synchronization is done on malloc'd contexts (separate for each submitted block/callback). Added tests to show usage of dispatch_sync and dispatch_async, for cases where we expect no warnings and for cases where TSan finds races.

Differential Revision: http://reviews.llvm.org/D14745

llvm-svn: 253982
This commit is contained in:
Kuba Brecka 2015-11-24 13:36:06 +00:00
parent 6cb7f7a923
commit 440d08600b
5 changed files with 206 additions and 0 deletions

View File

@ -21,11 +21,77 @@
#include "tsan_platform.h"
#include "tsan_rtl.h"
#include <Block.h>
#include <dispatch/dispatch.h>
#include <pthread.h>
namespace __tsan {
typedef struct {
dispatch_queue_t queue;
void *orig_context;
dispatch_function_t orig_work;
} tsan_block_context_t;
static tsan_block_context_t *AllocContext(ThreadState *thr, uptr pc,
dispatch_queue_t queue,
void *orig_context,
dispatch_function_t orig_work) {
tsan_block_context_t *new_context =
(tsan_block_context_t *)user_alloc(thr, pc, sizeof(tsan_block_context_t));
new_context->queue = queue;
new_context->orig_context = orig_context;
new_context->orig_work = orig_work;
return new_context;
}
static void dispatch_callback_wrap_acquire(void *param) {
SCOPED_INTERCEPTOR_RAW(dispatch_async_f_callback_wrap);
tsan_block_context_t *context = (tsan_block_context_t *)param;
Acquire(thr, pc, (uptr)context);
context->orig_work(context->orig_context);
user_free(thr, pc, context);
}
static void invoke_and_release_block(void *param) {
dispatch_block_t block = (dispatch_block_t)param;
block();
Block_release(block);
}
#define DISPATCH_INTERCEPT_B(name) \
TSAN_INTERCEPTOR(void, name, dispatch_queue_t q, dispatch_block_t block) { \
SCOPED_TSAN_INTERCEPTOR(name, q, block); \
dispatch_block_t heap_block = Block_copy(block); \
tsan_block_context_t *new_context = \
AllocContext(thr, pc, q, heap_block, &invoke_and_release_block); \
Release(thr, pc, (uptr)new_context); \
REAL(name##_f)(q, new_context, dispatch_callback_wrap_acquire); \
}
#define DISPATCH_INTERCEPT_F(name) \
TSAN_INTERCEPTOR(void, name, dispatch_queue_t q, void *context, \
dispatch_function_t work) { \
SCOPED_TSAN_INTERCEPTOR(name, q, context, work); \
tsan_block_context_t *new_context = \
AllocContext(thr, pc, q, context, work); \
Release(thr, pc, (uptr)new_context); \
REAL(name)(q, new_context, dispatch_callback_wrap_acquire); \
}
// We wrap dispatch_async, dispatch_sync and friends where we allocate a new
// context, which is used to synchronize (we release the context before
// submitting, and the callback acquires it before executing the original
// callback).
DISPATCH_INTERCEPT_B(dispatch_async)
DISPATCH_INTERCEPT_B(dispatch_barrier_async)
DISPATCH_INTERCEPT_F(dispatch_async_f)
DISPATCH_INTERCEPT_F(dispatch_barrier_async_f)
DISPATCH_INTERCEPT_B(dispatch_sync)
DISPATCH_INTERCEPT_B(dispatch_barrier_sync)
DISPATCH_INTERCEPT_F(dispatch_sync_f)
DISPATCH_INTERCEPT_F(dispatch_barrier_sync_f)
// GCD's dispatch_once implementation has a fast path that contains a racy read
// and it's inlined into user's code. Furthermore, this fast path doesn't
// establish a proper happens-before relations between the initialization and

View File

@ -0,0 +1,26 @@
// RUN: %clang_tsan %s -o %t -framework Foundation
// RUN: %run %t 2>&1
#import <Foundation/Foundation.h>
long global;
int main() {
NSLog(@"Hello world.");
global = 42;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
global = 43;
dispatch_sync(dispatch_get_main_queue(), ^{
CFRunLoopStop(CFRunLoopGetCurrent());
});
});
CFRunLoopRun();
NSLog(@"Done.");
}
// CHECK: Hello world.
// CHECK: Done.
// CHECK-NOT: WARNING: ThreadSanitizer

View File

@ -0,0 +1,38 @@
// RUN: %clang_tsan %s -o %t -framework Foundation
// RUN: %deflake %run %t 2>&1
#import <Foundation/Foundation.h>
#import "../test.h"
long global;
int main() {
NSLog(@"Hello world.");
NSLog(@"addr=%p\n", &global);
barrier_init(&barrier, 2);
global = 42;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
global = 43;
barrier_wait(&barrier);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
barrier_wait(&barrier);
global = 44;
dispatch_sync(dispatch_get_main_queue(), ^{
CFRunLoopStop(CFRunLoopGetCurrent());
});
});
CFRunLoopRun();
NSLog(@"Done.");
}
// CHECK: Hello world.
// CHECK: addr=[[ADDR:0x[0-9,a-f]+]]
// CHECK: WARNING: ThreadSanitizer: data race
// CHECK: Location is global 'global' at [[ADDR]] (global_race.cc.exe+0x{{[0-9,a-f]+}})
// CHECK: Done.

View File

@ -0,0 +1,32 @@
// RUN: %clang_tsan %s -o %t -framework Foundation
// RUN: %run %t 2>&1
#import <Foundation/Foundation.h>
long global;
static const long nIter = 1000;
int main() {
NSLog(@"Hello world.");
global = 42;
for (int i = 0; i < nIter; i++) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_sync(dispatch_get_main_queue(), ^{
global = i;
if (i == nIter - 1) {
CFRunLoopStop(CFRunLoopGetCurrent());
}
});
});
}
CFRunLoopRun();
NSLog(@"Done.");
}
// CHECK: Hello world.
// CHECK: Done.
// CHECK-NOT: WARNING: ThreadSanitizer

View File

@ -0,0 +1,44 @@
// RUN: %clang_tsan %s -o %t -framework Foundation
// RUN: %deflake %run %t 2>&1
#import <Foundation/Foundation.h>
#import "../test.h"
long global;
int main() {
NSLog(@"Hello world.");
NSLog(@"addr=%p\n", &global);
barrier_init(&barrier, 2);
dispatch_queue_t q1 = dispatch_queue_create("my.queue1", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t q2 = dispatch_queue_create("my.queue2", DISPATCH_QUEUE_CONCURRENT);
global = 42;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_sync(q1, ^{
global = 43;
barrier_wait(&barrier);
});
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_sync(q2, ^{
barrier_wait(&barrier);
global = 44;
dispatch_sync(dispatch_get_main_queue(), ^{
CFRunLoopStop(CFRunLoopGetCurrent());
});
});
});
CFRunLoopRun();
NSLog(@"Done.");
}
// CHECK: Hello world.
// CHECK: addr=[[ADDR:0x[0-9,a-f]+]]
// CHECK: WARNING: ThreadSanitizer: data race
// CHECK: Location is global 'global' at [[ADDR]] (global_race.cc.exe+0x{{[0-9,a-f]+}})
// CHECK: Done.