From 840ac6d9818b74fe75c7ef868d25532ac8d4f0ac Mon Sep 17 00:00:00 2001 From: Nathan Froyd Date: Fri, 27 Oct 2017 13:05:51 -0400 Subject: [PATCH] Bug 1385438 - make mozilla::RefCounted use the strongest memory consistency necessary; r=jrmuizel We don't need to use sequential consistency for everything, we can get by with using weaker memory consistency models depending on the operation. --- mfbt/RefCounted.h | 74 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 3 deletions(-) diff --git a/mfbt/RefCounted.h b/mfbt/RefCounted.h index e9acc5f64a92..215bf0729de8 100644 --- a/mfbt/RefCounted.h +++ b/mfbt/RefCounted.h @@ -17,6 +17,8 @@ #include "mozilla/RefCountType.h" #include "mozilla/TypeTraits.h" +#include + #if defined(MOZILLA_INTERNAL_API) #include "nsXPCOM.h" #endif @@ -89,6 +91,74 @@ enum RefCountAtomicity NonAtomicRefCount }; +template +class RC +{ +public: + explicit RC(T aCount) : mValue(aCount) {} + + T operator++() { return ++mValue; } + T operator--() { return --mValue; } + + void operator=(const T& aValue) { mValue = aValue; } + + operator T() const { return mValue; } + +private: + T mValue; +}; + +template +class RC +{ +public: + explicit RC(T aCount) : mValue(aCount) {} + + T operator++() + { + // Memory synchronization is not required when incrementing a + // reference count. The first increment of a reference count on a + // thread is not important, since the first use of the object on a + // thread can happen before it. What is important is the transfer + // of the pointer to that thread, which may happen prior to the + // first increment on that thread. The necessary memory + // synchronization is done by the mechanism that transfers the + // pointer between threads. + return mValue.fetch_add(1, std::memory_order_relaxed) + 1; + } + + T operator--() + { + // Since this may be the last release on this thread, we need + // release semantics so that prior writes on this thread are visible + // to the thread that destroys the object when it reads mValue with + // acquire semantics. + T result = mValue.fetch_sub(1, std::memory_order_release) - 1; + if (result == 0) { + // We're going to destroy the object on this thread, so we need + // acquire semantics to synchronize with the memory released by + // the last release on other threads, that is, to ensure that + // writes prior to that release are now visible on this thread. + std::atomic_thread_fence(std::memory_order_acquire); + } + return result; + } + + // This method is only called in debug builds, so we're not too concerned + // about its performance. + void operator=(const T& aValue) { mValue.store(aValue, std::memory_order_seq_cst); } + + operator T() const + { + // Use acquire semantics since we're not sure what the caller is + // doing. + return mValue.load(std::memory_order_acquire); + } + +private: + std::atomic mValue; +}; + template class RefCounted { @@ -150,9 +220,7 @@ public: } private: - mutable typename Conditional, - MozRefCountType>::Type mRefCnt; + mutable RC mRefCnt; }; #ifdef MOZ_REFCOUNTED_LEAK_CHECKING