This is an archive of the discontinued LLVM Phabricator instance.

[tsan] Handle dispatch_once on OS X
ClosedPublic

Authored by kubamracek on Nov 19 2015, 1:41 AM.

Details

Summary

Reimplement dispatch_once in an interceptor to solve these issues that may produce false positives with TSan on OS X:

  1. there is a racy load inside an inlined part of dispatch_once,
  2. the fast path in dispatch_once doesn't perform an acquire load, so we don't properly synchronize the initialization and subsequent uses of whatever is initialized,
  3. dispatch_once is already used in a lot of already-compiled code, so TSan doesn't see the inlined fast-path.

This patch uses a trick to avoid ever taking the fast path (by never storing ~0 into the predicate), which means the interceptor will always be called even from already-compiled code. Within the interceptor, our own atomic reads and writes are not written into shadow cells, so the race in the inlined part is not reported (because the accesses are only loads).

Diff Detail

Event Timeline

kubamracek updated this revision to Diff 40613.Nov 19 2015, 1:41 AM
kubamracek retitled this revision from to [tsan] Handle dispatch_once on OS X.
kubamracek updated this object.
kubamracek added reviewers: dvyukov, kcc, glider, samsonov.
dvyukov edited edge metadata.Nov 19 2015, 2:05 AM

LGTM with nits

lib/tsan/rtl/tsan_libdispatch_mac.cc
30

I can't imagine how undefined behavior can be intentional. Also, whether it is intentional or not is irrelevant here. So just drop "intentionally".

45

C++ and C cast chained look weird. Do either just C cast, or just reinterpret_cast.

65

For my education: how do these blocks work? Are they allocated with malloc, and then atomically reference-counted?

dvyukov accepted this revision.Nov 19 2015, 2:05 AM
dvyukov edited edge metadata.
This revision is now accepted and ready to land.Nov 19 2015, 2:05 AM
kubamracek added inline comments.Nov 19 2015, 2:32 AM
lib/tsan/rtl/tsan_libdispatch_mac.cc
65

Initially, they are created on the stack (a descriptor containing a function pointer and data). In function calls, we just pass a pointer to this stack variable. Only when we need to store a block reference to somewhere else (global variable, heap location), we transform it into a heap object (malloc'd). In this case (dispatch_once_f), we never do that, because we only pass it around as arguments and invoke it.

Would there be any issues if it was always malloc'd?

This revision was automatically updated to reflect the committed changes.

Would there be any issues if it was always malloc'd?

Would there be any issues if it was always malloc'd?

I don't know.
We would intercept malloc/free from within interceptor scope. And also may or may not intercept some atomic operations depending on implementation.