Merge remote-tracking branch 'origin/swift-3.0-branch' into stable

* origin/swift-3.0-branch:
  Disable the "gcd-io-race.mm" test to investigate bot hangs due to the test being deadlocked.
  [tsan] Add support for GCD IO channels on Darwin
diff --git a/lib/tsan/rtl/tsan_libdispatch_mac.cc b/lib/tsan/rtl/tsan_libdispatch_mac.cc
index 15805b9..529cedb 100644
--- a/lib/tsan/rtl/tsan_libdispatch_mac.cc
+++ b/lib/tsan/rtl/tsan_libdispatch_mac.cc
@@ -36,6 +36,7 @@
   bool free_context_in_callback;
   bool submitted_synchronously;
   bool is_barrier_block;
+  uptr non_queue_sync_object;
 } tsan_block_context_t;
 
 // The offsets of different fields of the dispatch_queue_t structure, exported
@@ -94,12 +95,13 @@
 static void dispatch_callback_wrap(void *param) {
   SCOPED_INTERCEPTOR_RAW(dispatch_callback_wrap);
   tsan_block_context_t *context = (tsan_block_context_t *)param;
-  dispatch_queue_t q = context->queue;
+  bool is_queue_serial = context->queue && IsQueueSerial(context->queue);
+  uptr sync_ptr = (uptr)context->queue ?: context->non_queue_sync_object;
 
-  uptr serial_sync = (uptr)q;
-  uptr concurrent_sync = ((uptr)q) + sizeof(uptr);
+  uptr serial_sync = (uptr)sync_ptr;
+  uptr concurrent_sync = ((uptr)sync_ptr) + sizeof(uptr);
   uptr submit_sync = (uptr)context;
-  bool serial_task = IsQueueSerial(q) || context->is_barrier_block;
+  bool serial_task = context->is_barrier_block || is_queue_serial;
 
   Acquire(thr, pc, submit_sync);
   Acquire(thr, pc, serial_sync);
@@ -148,7 +150,7 @@
     dispatch_block_t heap_block = Block_copy(block);                         \
     SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_END();                             \
     tsan_block_context_t new_context = {                                     \
-        q, heap_block, &invoke_and_release_block, false, true, barrier};     \
+        q, heap_block, &invoke_and_release_block, false, true, barrier, 0};  \
     Release(thr, pc, (uptr)&new_context);                                    \
     SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_START();                           \
     REAL(name##_f)(q, &new_context, dispatch_callback_wrap);                 \
@@ -174,7 +176,7 @@
                    dispatch_function_t work) {                                \
     SCOPED_TSAN_INTERCEPTOR(name, q, context, work);                          \
     tsan_block_context_t new_context = {                                      \
-        q, context, work, false, true, barrier};                              \
+        q, context, work, false, true, barrier, 0};                           \
     Release(thr, pc, (uptr)&new_context);                                     \
     SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_START();                            \
     REAL(name)(q, &new_context, dispatch_callback_wrap);                      \
@@ -358,7 +360,7 @@
     return REAL(dispatch_source_set_event_handler)(source, nullptr);
   dispatch_queue_t q = GetTargetQueueFromSource(source);
   __block tsan_block_context_t new_context = {
-      q, handler, &invoke_block, false, false, false };
+      q, handler, &invoke_block, false, false, false, 0 };
   dispatch_block_t new_handler = Block_copy(^(void) {
     new_context.orig_context = handler;  // To explicitly capture "handler".
     dispatch_callback_wrap(&new_context);
@@ -387,7 +389,7 @@
     return REAL(dispatch_source_set_cancel_handler)(source, nullptr);
   dispatch_queue_t q = GetTargetQueueFromSource(source);
   __block tsan_block_context_t new_context = {
-      q, handler, &invoke_block, false, false, false };
+      q, handler, &invoke_block, false, false, false, 0};
   dispatch_block_t new_handler = Block_copy(^(void) {
     new_context.orig_context = handler;  // To explicitly capture "handler".
     dispatch_callback_wrap(&new_context);
@@ -418,7 +420,7 @@
     return REAL(dispatch_source_set_registration_handler)(source, nullptr);
   dispatch_queue_t q = GetTargetQueueFromSource(source);
   __block tsan_block_context_t new_context = {
-      q, handler, &invoke_block, false, false, false };
+      q, handler, &invoke_block, false, false, false, 0};
   dispatch_block_t new_handler = Block_copy(^(void) {
     new_context.orig_context = handler;  // To explicitly capture "handler".
     dispatch_callback_wrap(&new_context);
@@ -501,6 +503,179 @@
   });
 }
 
+typedef void (^fd_handler_t)(dispatch_data_t data, int error);
+typedef void (^cleanup_handler_t)(int error);
+
+TSAN_INTERCEPTOR(void, dispatch_read, dispatch_fd_t fd, size_t length,
+                 dispatch_queue_t q, fd_handler_t h) {
+  SCOPED_TSAN_INTERCEPTOR(dispatch_read, fd, length, q, h);
+  __block tsan_block_context_t new_context = {
+      q, nullptr, &invoke_block, false, false, false, 0};
+  fd_handler_t new_h = Block_copy(^(dispatch_data_t data, int error) {
+    new_context.orig_context = ^(void) {
+      h(data, error);
+    };
+    dispatch_callback_wrap(&new_context);
+  });
+  uptr submit_sync = (uptr)&new_context;
+  Release(thr, pc, submit_sync);
+  REAL(dispatch_read)(fd, length, q, new_h);
+  Block_release(new_h);
+}
+
+TSAN_INTERCEPTOR(void, dispatch_write, dispatch_fd_t fd, dispatch_data_t data,
+                 dispatch_queue_t q, fd_handler_t h) {
+  SCOPED_TSAN_INTERCEPTOR(dispatch_write, fd, data, q, h);
+  __block tsan_block_context_t new_context = {
+      q, nullptr, &invoke_block, false, false, false, 0};
+  fd_handler_t new_h = Block_copy(^(dispatch_data_t data, int error) {
+    new_context.orig_context = ^(void) {
+      h(data, error);
+    };
+    dispatch_callback_wrap(&new_context);
+  });
+  uptr submit_sync = (uptr)&new_context;
+  Release(thr, pc, submit_sync);
+  REAL(dispatch_write)(fd, data, q, new_h);
+  Block_release(new_h);
+}
+
+TSAN_INTERCEPTOR(void, dispatch_io_read, dispatch_io_t channel, off_t offset,
+                 size_t length, dispatch_queue_t q, dispatch_io_handler_t h) {
+  SCOPED_TSAN_INTERCEPTOR(dispatch_io_read, channel, offset, length, q, h);
+  __block tsan_block_context_t new_context = {
+      q, nullptr, &invoke_block, false, false, false, 0};
+  dispatch_io_handler_t new_h =
+      Block_copy(^(bool done, dispatch_data_t data, int error) {
+        new_context.orig_context = ^(void) {
+          h(done, data, error);
+        };
+        dispatch_callback_wrap(&new_context);
+      });
+  uptr submit_sync = (uptr)&new_context;
+  Release(thr, pc, submit_sync);
+  REAL(dispatch_io_read)(channel, offset, length, q, new_h);
+  Block_release(new_h);
+}
+
+TSAN_INTERCEPTOR(void, dispatch_io_write, dispatch_io_t channel, off_t offset,
+                 dispatch_data_t data, dispatch_queue_t q,
+                 dispatch_io_handler_t h) {
+  SCOPED_TSAN_INTERCEPTOR(dispatch_io_write, channel, offset, data, q, h);
+  __block tsan_block_context_t new_context = {
+      q, nullptr, &invoke_block, false, false, false, 0};
+  dispatch_io_handler_t new_h =
+      Block_copy(^(bool done, dispatch_data_t data, int error) {
+        new_context.orig_context = ^(void) {
+          h(done, data, error);
+        };
+        dispatch_callback_wrap(&new_context);
+      });
+  uptr submit_sync = (uptr)&new_context;
+  Release(thr, pc, submit_sync);
+  REAL(dispatch_io_write)(channel, offset, data, q, new_h);
+  Block_release(new_h);
+}
+
+TSAN_INTERCEPTOR(void, dispatch_io_barrier, dispatch_io_t channel,
+                 dispatch_block_t barrier) {
+  SCOPED_TSAN_INTERCEPTOR(dispatch_io_barrier, channel, barrier);
+  __block tsan_block_context_t new_context = {
+      nullptr, nullptr, &invoke_block, false, false, false, 0};
+  new_context.non_queue_sync_object = (uptr)channel;
+  new_context.is_barrier_block = true;
+  dispatch_block_t new_block = Block_copy(^(void) {
+    new_context.orig_context = ^(void) {
+      barrier();
+    };
+    dispatch_callback_wrap(&new_context);
+  });
+  uptr submit_sync = (uptr)&new_context;
+  Release(thr, pc, submit_sync);
+  REAL(dispatch_io_barrier)(channel, new_block);
+  Block_release(new_block);
+}
+
+TSAN_INTERCEPTOR(dispatch_io_t, dispatch_io_create, dispatch_io_type_t type,
+                 dispatch_fd_t fd, dispatch_queue_t q, cleanup_handler_t h) {
+  SCOPED_TSAN_INTERCEPTOR(dispatch_io_create, type, fd, q, h);
+  __block dispatch_io_t new_channel = nullptr;
+  __block tsan_block_context_t new_context = {
+      q, nullptr, &invoke_block, false, false, false, 0};
+  cleanup_handler_t new_h = Block_copy(^(int error) {
+    {
+      SCOPED_INTERCEPTOR_RAW(dispatch_io_create_callback);
+      Acquire(thr, pc, (uptr)new_channel);  // Release() in dispatch_io_close.
+    }
+    new_context.orig_context = ^(void) {
+      h(error);
+    };
+    dispatch_callback_wrap(&new_context);
+  });
+  uptr submit_sync = (uptr)&new_context;
+  Release(thr, pc, submit_sync);
+  new_channel = REAL(dispatch_io_create)(type, fd, q, new_h);
+  Block_release(new_h);
+  return new_channel;
+}
+
+TSAN_INTERCEPTOR(dispatch_io_t, dispatch_io_create_with_path,
+                 dispatch_io_type_t type, const char *path, int oflag,
+                 mode_t mode, dispatch_queue_t q, cleanup_handler_t h) {
+  SCOPED_TSAN_INTERCEPTOR(dispatch_io_create_with_path, type, path, oflag, mode,
+                          q, h);
+  __block dispatch_io_t new_channel = nullptr;
+  __block tsan_block_context_t new_context = {
+      q, nullptr, &invoke_block, false, false, false, 0};
+  cleanup_handler_t new_h = Block_copy(^(int error) {
+    {
+      SCOPED_INTERCEPTOR_RAW(dispatch_io_create_callback);
+      Acquire(thr, pc, (uptr)new_channel);  // Release() in dispatch_io_close.
+    }
+    new_context.orig_context = ^(void) {
+      h(error);
+    };
+    dispatch_callback_wrap(&new_context);
+  });
+  uptr submit_sync = (uptr)&new_context;
+  Release(thr, pc, submit_sync);
+  new_channel =
+      REAL(dispatch_io_create_with_path)(type, path, oflag, mode, q, new_h);
+  Block_release(new_h);
+  return new_channel;
+}
+
+TSAN_INTERCEPTOR(dispatch_io_t, dispatch_io_create_with_io,
+                 dispatch_io_type_t type, dispatch_io_t io, dispatch_queue_t q,
+                 cleanup_handler_t h) {
+  SCOPED_TSAN_INTERCEPTOR(dispatch_io_create_with_io, type, io, q, h);
+  __block dispatch_io_t new_channel = nullptr;
+  __block tsan_block_context_t new_context = {
+      q, nullptr, &invoke_block, false, false, false, 0};
+  cleanup_handler_t new_h = Block_copy(^(int error) {
+    {
+      SCOPED_INTERCEPTOR_RAW(dispatch_io_create_callback);
+      Acquire(thr, pc, (uptr)new_channel);  // Release() in dispatch_io_close.
+    }
+    new_context.orig_context = ^(void) {
+      h(error);
+    };
+    dispatch_callback_wrap(&new_context);
+  });
+  uptr submit_sync = (uptr)&new_context;
+  Release(thr, pc, submit_sync);
+  new_channel = REAL(dispatch_io_create_with_io)(type, io, q, new_h);
+  Block_release(new_h);
+  return new_channel;
+}
+
+TSAN_INTERCEPTOR(void, dispatch_io_close, dispatch_io_t channel,
+                 dispatch_io_close_flags_t flags) {
+  SCOPED_TSAN_INTERCEPTOR(dispatch_io_close, channel, flags);
+  Release(thr, pc, (uptr)channel);  // Acquire() in dispatch_io_create[_*].
+  return REAL(dispatch_io_close)(channel, flags);
+}
+
 }  // namespace __tsan
 
 #endif  // SANITIZER_MAC
diff --git a/test/tsan/Darwin/gcd-fd.mm b/test/tsan/Darwin/gcd-fd.mm
new file mode 100644
index 0000000..75da9cd
--- /dev/null
+++ b/test/tsan/Darwin/gcd-fd.mm
@@ -0,0 +1,60 @@
+// RUN: %clang_tsan %s -o %t -framework Foundation
+// RUN: %env_tsan_opts=ignore_interceptors_accesses=1 %run %t 2>&1 | FileCheck %s
+
+#import <Foundation/Foundation.h>
+
+long my_global = 0;
+
+int main(int argc, const char *argv[]) {
+  fprintf(stderr, "Hello world.\n");
+
+  dispatch_queue_t queue = dispatch_queue_create("my.queue", DISPATCH_QUEUE_SERIAL);
+  dispatch_semaphore_t sem = dispatch_semaphore_create(0);
+
+  NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"temp-gcd-io.%d", getpid()]];
+
+  dispatch_io_t channel = dispatch_io_create_with_path(DISPATCH_IO_STREAM, path.fileSystemRepresentation, O_CREAT | O_WRONLY,
+      0666, queue, ^(int error) { });
+  dispatch_io_set_high_water(channel, 1);
+
+  NSData *ns_data = [NSMutableData dataWithLength:1000];
+  dispatch_data_t data = dispatch_data_create(ns_data.bytes, ns_data.length, NULL, DISPATCH_DATA_DESTRUCTOR_DEFAULT);
+
+  my_global++;
+  dispatch_io_write(channel, 0, data, queue, ^(bool done, dispatch_data_t remainingData, int error) {
+    my_global++;
+    dispatch_async(queue, ^{
+      my_global++;
+      if (done) {
+        dispatch_semaphore_signal(sem);
+      }
+    });
+  });
+
+  dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
+  my_global++;
+  dispatch_io_close(channel, 0);
+  channel = dispatch_io_create_with_path(DISPATCH_IO_STREAM, path.fileSystemRepresentation, O_RDONLY,
+      0, queue, ^(int error) { });
+  dispatch_io_set_high_water(channel, 1);
+
+  my_global++;
+  dispatch_io_read(channel, 0, SIZE_MAX, queue, ^(bool done, dispatch_data_t remainingData, int error) {
+    my_global++;
+    dispatch_async(queue, ^{
+      my_global++;
+      if (done) {
+        dispatch_semaphore_signal(sem);
+      }
+    });
+  });
+
+  dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
+  my_global++;
+  fprintf(stderr, "Done.\n");
+  return 0;
+}
+
+// CHECK: Hello world.
+// CHECK-NOT: WARNING: ThreadSanitizer
+// CHECK: Done.
diff --git a/test/tsan/Darwin/gcd-io-barrier-race.mm b/test/tsan/Darwin/gcd-io-barrier-race.mm
new file mode 100644
index 0000000..fffc19b
--- /dev/null
+++ b/test/tsan/Darwin/gcd-io-barrier-race.mm
@@ -0,0 +1,55 @@
+// RUN: %clang_tsan %s -o %t -framework Foundation
+// RUN: %env_tsan_opts=ignore_interceptors_accesses=1 %deflake %run %t 2>&1 | FileCheck %s
+
+#import <Foundation/Foundation.h>
+
+#import "../test.h"
+
+dispatch_queue_t queue;
+dispatch_data_t data;
+dispatch_semaphore_t sem;
+const char *path;
+
+long my_global = 0;
+
+int main(int argc, const char *argv[]) {
+  fprintf(stderr, "Hello world.\n");
+  print_address("addr=", 1, &my_global);
+  barrier_init(&barrier, 2);
+
+  queue = dispatch_queue_create("my.queue", DISPATCH_QUEUE_CONCURRENT);
+  sem = dispatch_semaphore_create(0);
+  NSString *ns_path = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"temp-gcd-io.%d", getpid()]];
+  path = ns_path.fileSystemRepresentation;
+  NSData *ns_data = [NSMutableData dataWithLength:1000];
+  data = dispatch_data_create(ns_data.bytes, ns_data.length, NULL, DISPATCH_DATA_DESTRUCTOR_DEFAULT);
+  
+  dispatch_io_t channel = dispatch_io_create_with_path(DISPATCH_IO_STREAM, path, O_CREAT | O_WRONLY, 0666, queue, ^(int error) { });
+  if (! channel) abort();
+  dispatch_io_set_high_water(channel, 1);
+
+  dispatch_io_write(channel, 0, data, queue, ^(bool done, dispatch_data_t remainingData, int error) {
+    if (error) abort();
+    my_global = 42;
+    barrier_wait(&barrier);
+  });
+
+  dispatch_io_barrier(channel, ^{
+    barrier_wait(&barrier);
+    my_global = 43;
+
+    dispatch_semaphore_signal(sem);
+  });
+
+  dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
+  dispatch_io_close(channel, 0);
+  
+  fprintf(stderr, "Done.\n");
+  return 0;
+}
+
+// CHECK: Hello world.
+// CHECK: addr=[[ADDR:0x[0-9,a-f]+]]
+// CHECK: WARNING: ThreadSanitizer: data race
+// CHECK: Location is global 'my_global' {{(of size 8 )?}}at [[ADDR]] (gcd-io-barrier-race.mm.tmp+0x{{[0-9,a-f]+}})
+// CHECK: Done.
diff --git a/test/tsan/Darwin/gcd-io-barrier.mm b/test/tsan/Darwin/gcd-io-barrier.mm
new file mode 100644
index 0000000..fe30138
--- /dev/null
+++ b/test/tsan/Darwin/gcd-io-barrier.mm
@@ -0,0 +1,48 @@
+// RUN: %clang_tsan %s -o %t -framework Foundation
+// RUN: %env_tsan_opts=ignore_interceptors_accesses=1 %run %t 2>&1 | FileCheck %s
+
+#import <Foundation/Foundation.h>
+
+dispatch_queue_t queue;
+dispatch_data_t data;
+dispatch_semaphore_t sem;
+const char *path;
+
+long my_global = 0;
+
+int main(int argc, const char *argv[]) {
+  fprintf(stderr, "Hello world.\n");
+  
+  queue = dispatch_queue_create("my.queue", DISPATCH_QUEUE_CONCURRENT);
+  sem = dispatch_semaphore_create(0);
+  NSString *ns_path = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"temp-gcd-io.%d", getpid()]];
+  path = ns_path.fileSystemRepresentation;
+  NSData *ns_data = [NSMutableData dataWithLength:1000];
+  data = dispatch_data_create(ns_data.bytes, ns_data.length, NULL, DISPATCH_DATA_DESTRUCTOR_DEFAULT);
+  
+  dispatch_io_t channel = dispatch_io_create_with_path(DISPATCH_IO_STREAM, path, O_CREAT | O_WRONLY, 0666, queue, ^(int error) { });
+  if (! channel) abort();
+  dispatch_io_set_high_water(channel, 1);
+
+  for (int i = 0; i < 1000; i++) {
+    dispatch_io_barrier(channel, ^{
+      my_global = 42;
+    });
+  }
+
+  dispatch_io_barrier(channel, ^{
+    my_global = 43;
+
+    dispatch_semaphore_signal(sem);
+  });
+
+  dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
+  dispatch_io_close(channel, 0);
+  
+  fprintf(stderr, "Done.\n");
+  return 0;
+}
+
+// CHECK: Hello world.
+// CHECK-NOT: WARNING: ThreadSanitizer
+// CHECK: Done.
diff --git a/test/tsan/Darwin/gcd-io-cleanup.mm b/test/tsan/Darwin/gcd-io-cleanup.mm
new file mode 100644
index 0000000..b15fa0d
--- /dev/null
+++ b/test/tsan/Darwin/gcd-io-cleanup.mm
@@ -0,0 +1,56 @@
+// RUN: %clang_tsan %s -o %t -framework Foundation
+// RUN: %env_tsan_opts=ignore_interceptors_accesses=1 %run %t 2>&1 | FileCheck %s
+
+#import <Foundation/Foundation.h>
+
+long my_global = 0;
+
+int main(int argc, const char *argv[]) {
+  fprintf(stderr, "Hello world.\n");
+  
+  dispatch_queue_t queue = dispatch_queue_create("my.queue", DISPATCH_QUEUE_CONCURRENT);
+  dispatch_semaphore_t sem = dispatch_semaphore_create(0);
+  NSString *ns_path = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"temp-gcd-io.%d", getpid()]];
+  const char *path = ns_path.fileSystemRepresentation;
+  dispatch_io_t channel;
+  
+  dispatch_fd_t fd = open(path, O_CREAT | O_WRONLY, 0666);
+  my_global++;
+  channel = dispatch_io_create(DISPATCH_IO_STREAM, fd, queue, ^(int error) {
+    my_global++;
+    dispatch_semaphore_signal(sem);
+  });
+  if (! channel) abort();
+  my_global++;
+  dispatch_io_close(channel, 0);
+  dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
+  
+  my_global++;
+  channel = dispatch_io_create_with_path(DISPATCH_IO_STREAM, path, O_CREAT | O_WRONLY, 0666, queue, ^(int error) {
+    my_global++;
+    dispatch_semaphore_signal(sem);
+  });
+  if (! channel) abort();
+  my_global++;
+  dispatch_io_close(channel, 0);
+  dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
+  
+  my_global++;
+  dispatch_io_t other_channel = dispatch_io_create_with_path(DISPATCH_IO_STREAM, path, O_CREAT | O_WRONLY, 0666, queue, ^(int error) { });
+  channel = dispatch_io_create_with_io(DISPATCH_IO_STREAM, other_channel, queue, ^(int error) {
+    my_global++;
+    dispatch_semaphore_signal(sem);
+  });
+  if (! channel) abort();
+  my_global++;
+  dispatch_io_close(channel, 0);
+  dispatch_io_close(other_channel, 0);
+  dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
+  
+  fprintf(stderr, "Done.\n");
+  return 0;
+}
+
+// CHECK: Hello world.
+// CHECK-NOT: WARNING: ThreadSanitizer
+// CHECK: Done.
diff --git a/test/tsan/Darwin/gcd-io-race.mm b/test/tsan/Darwin/gcd-io-race.mm
new file mode 100644
index 0000000..0bec28f
--- /dev/null
+++ b/test/tsan/Darwin/gcd-io-race.mm
@@ -0,0 +1,56 @@
+// RUN: %clang_tsan %s -o %t -framework Foundation
+// RUN: %env_tsan_opts=ignore_interceptors_accesses=1 %deflake %run %t 2>&1 | FileCheck %s
+
+// REQUIRES: disabled
+
+#import <Foundation/Foundation.h>
+
+#import "../test.h"
+
+dispatch_queue_t queue;
+dispatch_data_t data;
+dispatch_semaphore_t sem;
+const char *path;
+
+long my_global = 0;
+
+int main(int argc, const char *argv[]) {
+  fprintf(stderr, "Hello world.\n");
+  print_address("addr=", 1, &my_global);
+  barrier_init(&barrier, 2);
+  
+  queue = dispatch_queue_create("my.queue", DISPATCH_QUEUE_CONCURRENT);
+  sem = dispatch_semaphore_create(0);
+  NSString *ns_path = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"temp-gcd-io.%d", getpid()]];
+  path = ns_path.fileSystemRepresentation;
+  NSData *ns_data = [NSMutableData dataWithLength:1000];
+  data = dispatch_data_create(ns_data.bytes, ns_data.length, NULL, DISPATCH_DATA_DESTRUCTOR_DEFAULT);
+  
+  dispatch_io_t channel = dispatch_io_create_with_path(DISPATCH_IO_STREAM, path, O_CREAT | O_WRONLY, 0666, queue, ^(int error) { });
+  if (! channel) abort();
+  dispatch_io_set_high_water(channel, 1);
+  
+  dispatch_io_write(channel, 0, data, queue, ^(bool done, dispatch_data_t remainingData, int error) {
+    my_global = 42;
+    barrier_wait(&barrier);
+  });
+
+  dispatch_io_write(channel, 0, data, queue, ^(bool done, dispatch_data_t remainingData, int error) {
+    barrier_wait(&barrier);
+    my_global = 42;
+
+    dispatch_semaphore_signal(sem);
+  });
+  
+  dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
+  dispatch_io_close(channel, 0);
+  
+  fprintf(stderr, "Done.\n");
+  return 0;
+}
+
+// CHECK: Hello world.
+// CHECK: addr=[[ADDR:0x[0-9,a-f]+]]
+// CHECK: WARNING: ThreadSanitizer: data race
+// CHECK: Location is global 'my_global' {{(of size 8 )?}}at [[ADDR]] (gcd-io-race.mm.tmp+0x{{[0-9,a-f]+}})
+// CHECK: Done.
diff --git a/test/tsan/Darwin/gcd-io.mm b/test/tsan/Darwin/gcd-io.mm
new file mode 100644
index 0000000..4a1726d
--- /dev/null
+++ b/test/tsan/Darwin/gcd-io.mm
@@ -0,0 +1,117 @@
+// RUN: %clang_tsan %s -o %t -framework Foundation
+// RUN: %env_tsan_opts=ignore_interceptors_accesses=1 %run %t 2>&1 | FileCheck %s
+
+#import <Foundation/Foundation.h>
+
+dispatch_queue_t queue;
+dispatch_data_t data;
+dispatch_semaphore_t sem;
+const char *path;
+
+long my_global = 0;
+
+void test_dispatch_io_write() {
+  dispatch_io_t channel = dispatch_io_create_with_path(DISPATCH_IO_STREAM, path, O_CREAT | O_WRONLY, 0666, queue, ^(int error) { });
+  if (! channel) abort();
+  dispatch_io_set_high_water(channel, 1);
+  
+  my_global++;
+  dispatch_io_write(channel, 0, data, queue, ^(bool done, dispatch_data_t remainingData, int error) {
+    if (error) abort();
+    my_global++;
+    dispatch_async(queue, ^{
+      my_global++;
+      if (done) {
+        dispatch_semaphore_signal(sem);
+      }
+    });
+  });
+  
+  dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
+  my_global++;
+  dispatch_io_close(channel, 0);
+}
+
+void test_dispatch_write() {
+  dispatch_fd_t fd = open(path, O_CREAT | O_WRONLY, 0666);
+  if (fd == -1) abort();
+  
+  my_global++;
+  dispatch_write(fd, data, queue, ^(dispatch_data_t data, int error) {
+    if (error) abort();
+    my_global++;
+    dispatch_async(queue, ^{
+      my_global++;
+      
+      dispatch_semaphore_signal(sem);
+    });
+  });
+  
+  dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
+  my_global++;
+  close(fd);
+}
+
+void test_dispatch_io_read() {
+  dispatch_io_t channel = dispatch_io_create_with_path(DISPATCH_IO_STREAM, path, O_RDONLY,
+                       0, queue, ^(int error) { });
+  dispatch_io_set_high_water(channel, 1);
+  
+  my_global++;
+  dispatch_io_read(channel, 0, SIZE_MAX, queue, ^(bool done, dispatch_data_t remainingData, int error) {
+    if (error) abort();
+    my_global++;
+    dispatch_async(queue, ^{
+      my_global++;
+      if (done) {
+        dispatch_semaphore_signal(sem);
+      }
+    });
+  });
+  
+  dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
+  my_global++;
+  dispatch_io_close(channel, 0);
+}
+
+void test_dispatch_read() {
+  dispatch_fd_t fd = open(path, O_RDONLY, 0);
+  if (fd == -1) abort();
+  
+  my_global++;
+  dispatch_read(fd, SIZE_MAX, queue, ^(dispatch_data_t data, int error) {
+    if (error) abort();
+    my_global++;
+    dispatch_async(queue, ^{
+      my_global++;
+      dispatch_semaphore_signal(sem);
+    });
+  });
+  
+  dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
+  my_global++;
+  close(fd);
+}
+
+int main(int argc, const char *argv[]) {
+  fprintf(stderr, "Hello world.\n");
+  
+  queue = dispatch_queue_create("my.queue", DISPATCH_QUEUE_SERIAL);
+  sem = dispatch_semaphore_create(0);
+  NSString *ns_path = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"temp-gcd-io.%d", getpid()]];
+  path = ns_path.fileSystemRepresentation;
+  NSData *ns_data = [NSMutableData dataWithLength:1000];
+  data = dispatch_data_create(ns_data.bytes, ns_data.length, NULL, DISPATCH_DATA_DESTRUCTOR_DEFAULT);
+  
+  test_dispatch_io_write();
+  test_dispatch_write();
+  test_dispatch_io_read();
+  test_dispatch_read();
+  
+  fprintf(stderr, "Done.\n");
+  return 0;
+}
+
+// CHECK: Hello world.
+// CHECK-NOT: WARNING: ThreadSanitizer
+// CHECK: Done.