| //===--- Exclusivity.cpp - Exclusivity tracking ---------------------------===// |
| // |
| // This source file is part of the Swift.org open source project |
| // |
| // Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors |
| // Licensed under Apache License v2.0 with Runtime Library Exception |
| // |
| // See https://swift.org/LICENSE.txt for license information |
| // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // This implements the runtime support for dynamically tracking exclusivity. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "swift/Basic/Lazy.h" |
| #include "swift/Runtime/Config.h" |
| #include "swift/Runtime/Debug.h" |
| #include "swift/Runtime/Exclusivity.h" |
| #include "swift/Runtime/Metadata.h" |
| #include "ThreadLocalStorage.h" |
| #include <memory> |
| #include <stdio.h> |
| |
| // Pick a return-address strategy |
| #if __GNUC__ |
| #define get_return_address() __builtin_return_address(0) |
| #elif _MSC_VER |
| #include <intrin.h> |
| #define get_return_address() _ReturnAddress() |
| #else |
| #error missing implementation for get_return_address |
| #define get_return_address() ((void*) 0) |
| #endif |
| |
| using namespace swift; |
| |
| bool swift::_swift_disableExclusivityChecking = false; |
| |
| static const char *getAccessName(ExclusivityFlags flags) { |
| switch (flags) { |
| case ExclusivityFlags::Read: return "read"; |
| case ExclusivityFlags::Modify: return "modification"; |
| default: return "unknown"; |
| } |
| } |
| |
| LLVM_ATTRIBUTE_ALWAYS_INLINE |
| static void reportExclusivityConflict(ExclusivityFlags oldAction, void *oldPC, |
| ExclusivityFlags newFlags, void *newPC, |
| void *pointer) { |
| constexpr unsigned maxMessageLength = 100; |
| constexpr unsigned maxAccessDescriptionLength = 50; |
| char message[maxMessageLength]; |
| snprintf(message, sizeof(message), |
| "Simultaneous accesses to 0x%" PRIxPTR ", but modification requires " |
| "exclusive access", |
| reinterpret_cast<uintptr_t>(pointer)); |
| fprintf(stderr, "%s.\n", message); |
| |
| char oldAccess[maxAccessDescriptionLength]; |
| snprintf(oldAccess, sizeof(oldAccess), |
| "Previous access (a %s) started at", getAccessName(oldAction)); |
| fprintf(stderr, "%s ", oldAccess); |
| if (oldPC) { |
| dumpStackTraceEntry(0, oldPC, /*shortOutput=*/true); |
| fprintf(stderr, " (0x%" PRIxPTR ").\n", reinterpret_cast<uintptr_t>(oldPC)); |
| } else { |
| fprintf(stderr, "<unknown>.\n"); |
| } |
| |
| char newAccess[maxAccessDescriptionLength]; |
| snprintf(newAccess, sizeof(newAccess), "Current access (a %s) started at", |
| getAccessName(getAccessAction(newFlags))); |
| fprintf(stderr, "%s:\n", newAccess); |
| // The top frame is in swift_beginAccess, don't print it. |
| constexpr unsigned framesToSkip = 1; |
| printCurrentBacktrace(framesToSkip); |
| |
| RuntimeErrorDetails::Thread secondaryThread = { |
| .description = oldAccess, |
| .numFrames = 1, |
| .frames = &oldPC |
| }; |
| RuntimeErrorDetails details = { |
| .version = RuntimeErrorDetails::currentVersion, |
| .errorType = "exclusivity-violation", |
| .currentStackDescription = newAccess, |
| .framesToSkip = framesToSkip, |
| .memoryAddress = pointer, |
| .numExtraThreads = 1, |
| .threads = &secondaryThread |
| }; |
| _swift_reportToDebugger(RuntimeErrorFlagFatal, message, &details); |
| } |
| |
| namespace { |
| |
| /// A single access that we're tracking. |
| /// |
| /// The following inputs are accepted by the begin_access runtime entry |
| /// point. This table show the action performed by the current runtime to |
| /// convert those inputs into stored fields in the Access scratch buffer. |
| /// |
| /// Pointer | Runtime | Access | PC | Reported| Access |
| /// Argument| Behavior | Pointer| Arg | PC | PC |
| /// -------- ------------- -------- ------- --------- ---------- |
| /// null | [trap or missing enforcement] |
| /// nonnull | [nontracked]| null | null | caller | [discard] |
| /// nonnull | [nontracked]| null | valid | <same> | [discard] |
| /// nonnull | [tracked] | <same> | null | caller | caller |
| /// nonnull | [tracked] | <same> | valid | <same> | <same> |
| /// |
| /// [nontracked] means that the Access scratch buffer will not be added to the |
| /// runtime's list of tracked accesses. However, it may be passed to a |
| /// subsequent call to end_unpaired_access. The null Pointer field then |
| /// identifies the Access record as nontracked. |
| /// |
| /// The runtime owns the contents of the scratch buffer, which is allocated by |
| /// the compiler but otherwise opaque. The runtime may later reuse the Pointer |
| /// or PC fields or any spare bits for additional flags, and/or a pointer to |
| /// out-of-line storage. |
| struct Access { |
| void *Pointer; |
| void *PC; |
| uintptr_t NextAndAction; |
| |
| enum : uintptr_t { |
| ActionMask = (uintptr_t)ExclusivityFlags::ActionMask, |
| NextMask = ~ActionMask |
| }; |
| |
| Access *getNext() const { |
| return reinterpret_cast<Access*>(NextAndAction & NextMask); |
| } |
| |
| void setNext(Access *next) { |
| NextAndAction = |
| reinterpret_cast<uintptr_t>(next) | (NextAndAction & ActionMask); |
| } |
| |
| ExclusivityFlags getAccessAction() const { |
| return ExclusivityFlags(NextAndAction & ActionMask); |
| } |
| |
| void initialize(void *pc, void *pointer, Access *next, |
| ExclusivityFlags action) { |
| Pointer = pointer; |
| PC = pc; |
| NextAndAction = reinterpret_cast<uintptr_t>(next) | uintptr_t(action); |
| } |
| }; |
| |
| static_assert(sizeof(Access) <= sizeof(ValueBuffer) && |
| alignof(Access) <= alignof(ValueBuffer), |
| "Access doesn't fit in a value buffer!"); |
| |
| /// A set of accesses that we're tracking. Just a singly-linked list. |
| class AccessSet { |
| Access *Head = nullptr; |
| public: |
| constexpr AccessSet() {} |
| |
| bool insert(Access *access, void *pc, void *pointer, ExclusivityFlags flags) { |
| auto action = getAccessAction(flags); |
| |
| for (Access *cur = Head; cur != nullptr; cur = cur->getNext()) { |
| // Ignore accesses to different values. |
| if (cur->Pointer != pointer) |
| continue; |
| |
| // If both accesses are reads, it's not a conflict. |
| if (action == ExclusivityFlags::Read && |
| action == cur->getAccessAction()) |
| continue; |
| |
| // Otherwise, it's a conflict. |
| reportExclusivityConflict(cur->getAccessAction(), cur->PC, |
| flags, pc, pointer); |
| |
| // 0 means no backtrace will be printed. |
| fatalError(0, "Fatal access conflict detected.\n"); |
| } |
| if (!isTracking(flags)) |
| return false; |
| |
| // Insert to the front of the array so that remove tends to find it faster. |
| access->initialize(pc, pointer, Head, action); |
| Head = access; |
| return true; |
| } |
| |
| void remove(Access *access) { |
| auto cur = Head; |
| // Fast path: stack discipline. |
| if (cur == access) { |
| Head = cur->getNext(); |
| return; |
| } |
| |
| Access *last = cur; |
| for (cur = cur->getNext(); cur != nullptr; |
| last = cur, cur = cur->getNext()) { |
| assert(last->getNext() == cur); |
| if (cur == access) { |
| last->setNext(cur->getNext()); |
| return; |
| } |
| } |
| |
| swift_runtime_unreachable("access not found in set"); |
| } |
| |
| #ifndef NDEBUG |
| /// Only available with asserts. Intended to be used with |
| /// swift_dumpTrackedAccess(). |
| void forEach(std::function<void (Access *)> action) { |
| for (auto *iter = Head; iter != nullptr; iter = iter->getNext()) { |
| action(iter); |
| } |
| } |
| #endif |
| }; |
| |
| } // end anonymous namespace |
| |
| // Each of these cases should define a function with this prototype: |
| // AccessSets &getAllSets(); |
| |
| #if SWIFT_TLS_HAS_RESERVED_PTHREAD_SPECIFIC |
| // Use the reserved TSD key if possible. |
| |
| static AccessSet &getAccessSet() { |
| AccessSet *set = static_cast<AccessSet*>( |
| SWIFT_THREAD_GETSPECIFIC(SWIFT_EXCLUSIVITY_TLS_KEY)); |
| if (set) |
| return *set; |
| |
| static OnceToken_t setupToken; |
| SWIFT_ONCE_F(setupToken, [](void *) { |
| pthread_key_init_np(SWIFT_EXCLUSIVITY_TLS_KEY, [](void *pointer) { |
| delete static_cast<AccessSet*>(pointer); |
| }); |
| }, nullptr); |
| |
| set = new AccessSet(); |
| SWIFT_THREAD_SETSPECIFIC(SWIFT_EXCLUSIVITY_TLS_KEY, set); |
| return *set; |
| } |
| |
| #elif SWIFT_TLS_HAS_THREADLOCAL |
| // Second choice is direct language support for thread-locals. |
| |
| static LLVM_THREAD_LOCAL AccessSet ExclusivityAccessSet; |
| |
| static AccessSet &getAccessSet() { |
| return ExclusivityAccessSet; |
| } |
| |
| #else |
| // Use the platform thread-local data API. |
| |
| static __swift_thread_key_t createAccessSetThreadKey() { |
| __swift_thread_key_t key; |
| int result = SWIFT_THREAD_KEY_CREATE(&key, [](void *pointer) { |
| delete static_cast<AccessSet*>(pointer); |
| }); |
| |
| if (result != 0) { |
| fatalError(0, "couldn't create thread key for exclusivity: %s\n", |
| strerror(result)); |
| } |
| return key; |
| } |
| |
| static AccessSet &getAccessSet() { |
| static __swift_thread_key_t key = createAccessSetThreadKey(); |
| |
| AccessSet *set = static_cast<AccessSet*>(SWIFT_THREAD_GETSPECIFIC(key)); |
| if (!set) { |
| set = new AccessSet(); |
| SWIFT_THREAD_SETSPECIFIC(key, set); |
| } |
| return *set; |
| } |
| |
| #endif |
| |
| /// Begin tracking a dynamic access. |
| /// |
| /// This may cause a runtime failure if an incompatible access is |
| /// already underway. |
| void swift::swift_beginAccess(void *pointer, ValueBuffer *buffer, |
| ExclusivityFlags flags, void *pc) { |
| assert(pointer && "beginning an access on a null pointer?"); |
| |
| Access *access = reinterpret_cast<Access*>(buffer); |
| |
| // If exclusivity checking is disabled, record in the access buffer that we |
| // didn't track anything. pc is currently undefined in this case. |
| if (_swift_disableExclusivityChecking) { |
| access->Pointer = nullptr; |
| return; |
| } |
| |
| // If the provided `pc` is null, then the runtime may override it for |
| // diagnostics. |
| if (!pc) |
| pc = get_return_address(); |
| |
| if (!getAccessSet().insert(access, pc, pointer, flags)) |
| access->Pointer = nullptr; |
| } |
| |
| /// End tracking a dynamic access. |
| void swift::swift_endAccess(ValueBuffer *buffer) { |
| Access *access = reinterpret_cast<Access*>(buffer); |
| auto pointer = access->Pointer; |
| |
| // If the pointer in the access is null, we must've declined |
| // to track it because exclusivity tracking was disabled. |
| if (!pointer) { |
| return; |
| } |
| |
| getAccessSet().remove(access); |
| } |
| |
| #ifndef NDEBUG |
| |
| // Dump the accesses that are currently being tracked by the runtime. |
| // |
| // This is only intended to be used in the debugger. |
| void swift::swift_dumpTrackedAccesses() { |
| getAccessSet().forEach([](Access *a) { |
| fprintf(stderr, "Access. Pointer: %p. PC: %p. AccessAction: %s\n", |
| a->Pointer, a->PC, getAccessName(a->getAccessAction())); |
| }); |
| } |
| |
| #endif |