RefCounted: use more precise barriers

This improves the DrawCallPerfRun/Vulkan_NoReuseBindGroups benchmark by
2% on an Intel processor but should be a bigger improvement on ARM.

The change was inspired by the Boost documentation at
https://www.boost.org/doc/libs/1_55_0/doc/html/atomic/usage_examples.html
Chromium's base::AtomicRefCount implementation and Rust's core::Arc
implementation.

BUG=dawn:304

Change-Id: I7ca71f34af20fd267cf2efc63871ff330b1dcc7c
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/14482
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Austin Eng <enga@chromium.org>
Reviewed-by: David Turner <digit@google.com>
diff --git a/src/dawn_native/RefCounted.cpp b/src/dawn_native/RefCounted.cpp
index 6782c14..9b4c1c2 100644
--- a/src/dawn_native/RefCounted.cpp
+++ b/src/dawn_native/RefCounted.cpp
@@ -43,14 +43,36 @@
 
     void RefCounted::Reference() {
         ASSERT((mRefCount & ~kPayloadMask) != 0);
-        mRefCount += kRefCountIncrement;
+
+        // The relaxed ordering guarantees only the atomicity of the update, which is enough here
+        // because the reference we are copying from still exists and makes sure other threads
+        // don't delete `this`.
+        // See the explanation in the Boost documentation:
+        //     https://www.boost.org/doc/libs/1_55_0/doc/html/atomic/usage_examples.html
+        mRefCount.fetch_add(kRefCountIncrement, std::memory_order_relaxed);
     }
 
     void RefCounted::Release() {
         ASSERT((mRefCount & ~kPayloadMask) != 0);
 
-        mRefCount -= kRefCountIncrement;
-        if (mRefCount < kRefCountIncrement) {
+        // The release fence here is to make sure all accesses to the object on a thread A
+        // happen-before the object is deleted on a thread B. The release memory order ensures that
+        // all accesses on thread A happen-before the refcount is decreased and the atomic variable
+        // makes sure the refcount decrease in A happens-before the refcount decrease in B. Finally
+        // the acquire fence in the destruction case makes sure the refcount decrease in B
+        // happens-before the `delete this`.
+        //
+        // See the explanation in the Boost documentation:
+        //     https://www.boost.org/doc/libs/1_55_0/doc/html/atomic/usage_examples.html
+        uint64_t previousRefCount =
+            mRefCount.fetch_sub(kRefCountIncrement, std::memory_order_release);
+
+        // Check that the previous reference count was strictly less than 2, ignoring payload bits.
+        if (previousRefCount < 2 * kRefCountIncrement) {
+            // Note that on ARM64 this will generate a `dmb ish` instruction which is a global
+            // memory barrier, when an acquire load on mRefCount (using the `ldar` instruction)
+            // should be enough and could end up being faster.
+            std::atomic_thread_fence(std::memory_order_acquire);
             delete this;
         }
     }