Use single 64bit atomic refcount for Dawn objects

Bug: dawn:105
Change-Id: I7741d5f01579f269db272200d39088c07e2acd92
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/7440
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
Commit-Queue: Austin Eng <enga@chromium.org>
diff --git a/src/dawn_native/RefCounted.cpp b/src/dawn_native/RefCounted.cpp
index 64a4817..5ec8050 100644
--- a/src/dawn_native/RefCounted.cpp
+++ b/src/dawn_native/RefCounted.cpp
@@ -24,53 +24,21 @@
     RefCounted::~RefCounted() {
     }
 
-    void RefCounted::ReferenceInternal() {
-        ASSERT(mInternalRefs != 0);
-
-        // TODO(cwallez@chromium.org): what to do on overflow?
-        mInternalRefs++;
-    }
-
-    void RefCounted::ReleaseInternal() {
-        ASSERT(mInternalRefs != 0);
-
-        mInternalRefs--;
-
-        if (mInternalRefs == 0) {
-            ASSERT(mExternalRefs == 0);
-            // TODO(cwallez@chromium.org): would this work with custom allocators?
-            delete this;
-        }
-    }
-
-    uint32_t RefCounted::GetExternalRefs() const {
-        return mExternalRefs;
-    }
-
-    uint32_t RefCounted::GetInternalRefs() const {
-        return mInternalRefs;
+    uint64_t RefCounted::GetRefCount() const {
+        return mRefCount;
     }
 
     void RefCounted::Reference() {
-        ASSERT(mInternalRefs != 0);
-
-        // mExternalRefs != 0 counts as one internal ref.
-        if (mExternalRefs == 0) {
-            ReferenceInternal();
-        }
-
-        // TODO(cwallez@chromium.org): what to do on overflow?
-        mExternalRefs++;
+        ASSERT(mRefCount != 0);
+        mRefCount++;
     }
 
     void RefCounted::Release() {
-        ASSERT(mInternalRefs != 0);
-        ASSERT(mExternalRefs != 0);
+        ASSERT(mRefCount != 0);
 
-        mExternalRefs--;
-        // mExternalRefs != 0 counts as one internal ref.
-        if (mExternalRefs == 0) {
-            ReleaseInternal();
+        mRefCount--;
+        if (mRefCount == 0) {
+            delete this;
         }
     }
 
diff --git a/src/dawn_native/RefCounted.h b/src/dawn_native/RefCounted.h
index ccc63f9..6eb4ab0 100644
--- a/src/dawn_native/RefCounted.h
+++ b/src/dawn_native/RefCounted.h
@@ -15,6 +15,7 @@
 #ifndef DAWNNATIVE_REFCOUNTED_H_
 #define DAWNNATIVE_REFCOUNTED_H_
 
+#include <atomic>
 #include <cstdint>
 
 namespace dawn_native {
@@ -24,19 +25,14 @@
         RefCounted();
         virtual ~RefCounted();
 
-        void ReferenceInternal();
-        void ReleaseInternal();
-
-        uint32_t GetExternalRefs() const;
-        uint32_t GetInternalRefs() const;
+        uint64_t GetRefCount() const;
 
         // Dawn API
         void Reference();
         void Release();
 
       protected:
-        uint32_t mExternalRefs = 1;
-        uint32_t mInternalRefs = 1;
+        std::atomic_uint64_t mRefCount = {1};
     };
 
     template <typename T>
@@ -111,12 +107,12 @@
       private:
         void Reference() const {
             if (mPointee != nullptr) {
-                mPointee->ReferenceInternal();
+                mPointee->Reference();
             }
         }
         void Release() const {
             if (mPointee != nullptr) {
-                mPointee->ReleaseInternal();
+                mPointee->Release();
             }
         }
 
diff --git a/src/tests/unittests/RefCountedTests.cpp b/src/tests/unittests/RefCountedTests.cpp
index 38dfef9..e8a7f6f 100644
--- a/src/tests/unittests/RefCountedTests.cpp
+++ b/src/tests/unittests/RefCountedTests.cpp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 #include <gtest/gtest.h>
+#include <thread>
 
 #include "dawn_native/RefCounted.h"
 
@@ -38,8 +39,8 @@
     bool* deleted = nullptr;
 };
 
-// Test that RCs start with one external ref, and removing it destroys the object.
-TEST(RefCounted, StartsWithOneExternalRef) {
+// Test that RCs start with one ref, and removing it destroys the object.
+TEST(RefCounted, StartsWithOneRef) {
     bool deleted = false;
     auto test = new RCTest(&deleted);
 
@@ -47,39 +48,51 @@
     ASSERT_TRUE(deleted);
 }
 
-// Test internal refs keep the RC alive.
-TEST(RefCounted, InternalRefKeepsAlive) {
+// Test adding refs keep the RC alive.
+TEST(RefCounted, AddingRefKeepsAlive) {
     bool deleted = false;
     auto test = new RCTest(&deleted);
 
-    test->ReferenceInternal();
-    test->Release();
-    ASSERT_FALSE(deleted);
-
-    test->ReleaseInternal();
-    ASSERT_TRUE(deleted);
-}
-
-// Test that when adding an external ref from 0, an internal ref is added
-TEST(RefCounted, AddExternalRefFromZero) {
-    bool deleted = false;
-    auto test = new RCTest(&deleted);
-
-    test->ReferenceInternal();
-    test->Release();
-    ASSERT_FALSE(deleted);
-
-    // Reference adds an internal ref and release removes one
     test->Reference();
     test->Release();
     ASSERT_FALSE(deleted);
 
-    test->ReleaseInternal();
+    test->Release();
     ASSERT_TRUE(deleted);
 }
 
-// Test Ref remove internal reference when going out of scope
-TEST(Ref, EndOfScopeRemovesInternalRef) {
+// Test that Reference and Release atomically change the refcount.
+TEST(RefCounted, RaceOnReferenceRelease) {
+    bool deleted = false;
+    auto* test = new RCTest(&deleted);
+
+    auto referenceManyTimes = [test]() {
+        for (uint32_t i = 0; i < 100000; ++i) {
+            test->Reference();
+        }
+    };
+    std::thread t1(referenceManyTimes);
+    std::thread t2(referenceManyTimes);
+
+    t1.join();
+    t2.join();
+    ASSERT_EQ(test->GetRefCount(), 200001u);
+
+    auto releaseManyTimes = [test]() {
+        for (uint32_t i = 0; i < 100000; ++i) {
+            test->Release();
+        }
+    };
+
+    std::thread t3(releaseManyTimes);
+    std::thread t4(releaseManyTimes);
+    t3.join();
+    t4.join();
+    ASSERT_EQ(test->GetRefCount(), 1u);
+}
+
+// Test Ref remove reference when going out of scope
+TEST(Ref, EndOfScopeRemovesRef) {
     bool deleted = false;
     {
         Ref<RCTest> test(new RCTest(&deleted));