Implements a thread-safe WeakRef typing.
Bug: dawn:1769
Change-Id: I54f4600a38def5b398e4309cbbc61711ca3f76e3
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/138460
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Loko Kung <lokokung@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/dawn/common/BUILD.gn b/src/dawn/common/BUILD.gn
index d1f9520..297565f 100644
--- a/src/dawn/common/BUILD.gn
+++ b/src/dawn/common/BUILD.gn
@@ -275,6 +275,9 @@
"TypeTraits.h",
"TypedInteger.h",
"UnderlyingType.h",
+ "WeakRef.h",
+ "WeakRefSupport.cpp",
+ "WeakRefSupport.h",
"ityp_array.h",
"ityp_bitset.h",
"ityp_span.h",
diff --git a/src/dawn/common/CMakeLists.txt b/src/dawn/common/CMakeLists.txt
index 3b1f0ce..cb1b388 100644
--- a/src/dawn/common/CMakeLists.txt
+++ b/src/dawn/common/CMakeLists.txt
@@ -78,6 +78,9 @@
"TypeTraits.h"
"TypedInteger.h"
"UnderlyingType.h"
+ "WeakRef.h"
+ "WeakRefSupport.cpp"
+ "WeakRefSupport.h"
"ityp_array.h"
"ityp_bitset.h"
"ityp_span.h"
diff --git a/src/dawn/common/Ref.h b/src/dawn/common/Ref.h
index 3daa67e..693a8fac 100644
--- a/src/dawn/common/Ref.h
+++ b/src/dawn/common/Ref.h
@@ -15,11 +15,21 @@
#ifndef SRC_DAWN_COMMON_REF_H_
#define SRC_DAWN_COMMON_REF_H_
+#include <mutex>
+#include <type_traits>
+
#include "dawn/common/RefBase.h"
+#include "dawn/common/RefCounted.h"
namespace dawn {
+
+template <typename T>
+class WeakRef;
+
namespace detail {
+class WeakRefSupportBase;
+
template <typename T>
struct RefCountedTraits {
static constexpr T* kNullValue = nullptr;
@@ -33,6 +43,13 @@
class Ref : public RefBase<T*, detail::RefCountedTraits<T>> {
public:
using RefBase<T*, detail::RefCountedTraits<T>>::RefBase;
+
+ template <
+ typename U = T,
+ typename = typename std::enable_if<std::is_base_of_v<detail::WeakRefSupportBase, U>>::type>
+ WeakRef<T> GetWeakRef() {
+ return WeakRef<T>(this->Get());
+ }
};
} // namespace dawn
diff --git a/src/dawn/common/RefCounted.h b/src/dawn/common/RefCounted.h
index fd3dcdd..3424271 100644
--- a/src/dawn/common/RefCounted.h
+++ b/src/dawn/common/RefCounted.h
@@ -21,6 +21,10 @@
namespace dawn {
+namespace detail {
+class WeakRefData;
+} // namespace detail
+
class RefCount {
public:
// Create a refcount with a payload. The refcount starts initially at one.
@@ -87,6 +91,9 @@
void APIRelease() { ReleaseAndLockBeforeDestroy(); }
protected:
+ // Friend class is needed to access the RefCount to TryIncrement.
+ friend class detail::WeakRefData;
+
virtual ~RefCounted();
void ReleaseAndLockBeforeDestroy();
diff --git a/src/dawn/common/WeakRef.h b/src/dawn/common/WeakRef.h
new file mode 100644
index 0000000..4764924
--- /dev/null
+++ b/src/dawn/common/WeakRef.h
@@ -0,0 +1,77 @@
+// Copyright 2023 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_DAWN_COMMON_WEAKREF_H_
+#define SRC_DAWN_COMMON_WEAKREF_H_
+
+#include <utility>
+
+#include "dawn/common/Ref.h"
+#include "dawn/common/WeakRefSupport.h"
+
+namespace dawn {
+
+template <typename T>
+class WeakRef {
+ public:
+ WeakRef() {}
+
+ // Constructors from nullptr.
+ // NOLINTNEXTLINE(runtime/explicit)
+ constexpr WeakRef(std::nullptr_t) : WeakRef() {}
+
+ // Constructors from a WeakRef<U>, where U can also equal T.
+ template <typename U, typename = typename std::enable_if<std::is_base_of_v<T, U>>::type>
+ WeakRef(const WeakRef<U>& other) : mData(other.mData) {}
+ template <typename U, typename = typename std::enable_if<std::is_base_of_v<T, U>>::type>
+ WeakRef<T>& operator=(const WeakRef<U>& other) {
+ mData = other.mData;
+ return *this;
+ }
+ template <typename U, typename = typename std::enable_if<std::is_base_of_v<T, U>>::type>
+ WeakRef(WeakRef<U>&& other) : mData(std::move(other.mData)) {}
+ template <typename U, typename = typename std::enable_if<std::is_base_of_v<T, U>>::type>
+ WeakRef<T>& operator=(WeakRef<U>&& other) {
+ if (&other != this) {
+ mData = std::move(other.mData);
+ }
+ return *this;
+ }
+
+ // Promotes a WeakRef to a Ref. Access to the raw pointer is not allowed because a raw pointer
+ // could become invalid after being retrieved.
+ Ref<T> Promote() {
+ if (mData != nullptr) {
+ return AcquireRef(static_cast<T*>(mData->TryGetRef().Detach()));
+ }
+ return nullptr;
+ }
+
+ private:
+ // Friend is needed so that we can access the data ref in conversions.
+ template <typename U>
+ friend class WeakRef;
+ // Friend is needed to access the private constructor.
+ template <typename U>
+ friend class Ref;
+
+ // Constructor from data should only be allowed via the GetWeakRef function.
+ explicit WeakRef(detail::WeakRefSupportBase* data) : mData(data->mData) {}
+
+ Ref<detail::WeakRefData> mData = nullptr;
+};
+
+} // namespace dawn
+
+#endif // SRC_DAWN_COMMON_WEAKREFCOUNTED_H_
diff --git a/src/dawn/common/WeakRefSupport.cpp b/src/dawn/common/WeakRefSupport.cpp
new file mode 100644
index 0000000..9273ff1
--- /dev/null
+++ b/src/dawn/common/WeakRefSupport.cpp
@@ -0,0 +1,42 @@
+// Copyright 2023 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "dawn/common/WeakRefSupport.h"
+
+#include <utility>
+
+namespace dawn::detail {
+
+WeakRefData::WeakRefData(RefCounted* value) : mValue(value) {}
+
+Ref<RefCounted> WeakRefData::TryGetRef() {
+ std::lock_guard<std::mutex> lock(mMutex);
+ if (!mValue || !mValue->mRefCount.TryIncrement()) {
+ return nullptr;
+ }
+ return AcquireRef(mValue);
+}
+
+void WeakRefData::Invalidate() {
+ std::lock_guard<std::mutex> lock(mMutex);
+ mValue = nullptr;
+}
+
+WeakRefSupportBase::WeakRefSupportBase(Ref<detail::WeakRefData> data) : mData(std::move(data)) {}
+
+WeakRefSupportBase::~WeakRefSupportBase() {
+ mData->Invalidate();
+}
+
+} // namespace dawn::detail
diff --git a/src/dawn/common/WeakRefSupport.h b/src/dawn/common/WeakRefSupport.h
new file mode 100644
index 0000000..9702905
--- /dev/null
+++ b/src/dawn/common/WeakRefSupport.h
@@ -0,0 +1,67 @@
+// Copyright 2023 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_DAWN_COMMON_WEAKREFSUPPORT_H_
+#define SRC_DAWN_COMMON_WEAKREFSUPPORT_H_
+
+#include "dawn/common/Ref.h"
+
+namespace dawn {
+
+template <typename T>
+class WeakRef;
+
+namespace detail {
+
+// Indirection layer to provide external ref-counting for WeakRefs.
+class WeakRefData : public RefCounted {
+ public:
+ explicit WeakRefData(RefCounted* value);
+ void Invalidate();
+
+ // Tries to return a valid Ref to `mValue` if it's internal refcount is not already 0. If the
+ // internal refcount has already reached 0, returns nullptr instead.
+ Ref<RefCounted> TryGetRef();
+
+ private:
+ std::mutex mMutex;
+ RefCounted* mValue = nullptr;
+};
+
+// Interface base class used to enable compile-time verification of WeakRefSupport functions.
+class WeakRefSupportBase {
+ protected:
+ explicit WeakRefSupportBase(Ref<detail::WeakRefData> data);
+ ~WeakRefSupportBase();
+
+ private:
+ template <typename T>
+ friend class ::dawn::WeakRef;
+
+ Ref<detail::WeakRefData> mData;
+};
+
+} // namespace detail
+
+// Class that should be extended to enable WeakRefs for the type.
+template <typename T>
+class WeakRefSupport : public detail::WeakRefSupportBase {
+ public:
+ WeakRefSupport()
+ : WeakRefSupportBase(AcquireRef(new detail::WeakRefData(static_cast<T*>(this)))) {}
+};
+
+} // namespace dawn
+
+#endif // SRC_DAWN_COMMON_WEAKREFSUPPORT_H_
diff --git a/src/dawn/tests/BUILD.gn b/src/dawn/tests/BUILD.gn
index 4c58b9d..4ef3258 100644
--- a/src/dawn/tests/BUILD.gn
+++ b/src/dawn/tests/BUILD.gn
@@ -325,6 +325,7 @@
"unittests/ToggleTests.cpp",
"unittests/TypedIntegerTests.cpp",
"unittests/UnicodeTests.cpp",
+ "unittests/WeakRefTests.cpp",
"unittests/native/AllowedErrorTests.cpp",
"unittests/native/BlobTests.cpp",
"unittests/native/CacheRequestTests.cpp",
diff --git a/src/dawn/tests/unittests/ContentLessObjectCacheTests.cpp b/src/dawn/tests/unittests/ContentLessObjectCacheTests.cpp
index 0b86e15..4ad91c6 100644
--- a/src/dawn/tests/unittests/ContentLessObjectCacheTests.cpp
+++ b/src/dawn/tests/unittests/ContentLessObjectCacheTests.cpp
@@ -20,11 +20,14 @@
#include <vector>
#include "dawn/common/ContentLessObjectCache.h"
+#include "dawn/utils/BinarySemaphore.h"
#include "gtest/gtest.h"
namespace dawn {
namespace {
+using utils::BinarySemaphore;
+
class RefCountedT : public RefCounted {
public:
explicit RefCountedT(size_t value) : mValue(value) {}
@@ -114,26 +117,6 @@
EXPECT_FALSE(cache.Empty());
}
-// Helper struct that basically acts as a semaphore to allow for flow control in multiple threads.
-struct Signal {
- std::mutex mutex;
- std::condition_variable cv;
- bool signaled = false;
-
- void Fire() {
- std::lock_guard<std::mutex> lock(mutex);
- signaled = true;
- cv.notify_one();
- }
- void Wait() {
- std::unique_lock<std::mutex> lock(mutex);
- while (!signaled) {
- cv.wait(lock);
- }
- signaled = false;
- }
-};
-
// Inserting and finding elements should respect the results from the insert call.
TEST(ContentLessObjectCacheTest, InsertingAndFinding) {
constexpr size_t kNumObjects = 100;
@@ -169,12 +152,12 @@
// Finding an element that is in the process of deletion should return nullptr.
TEST(ContentLessObjectCacheTest, FindDeleting) {
- Signal signalA, signalB;
+ BinarySemaphore semA, semB;
ContentLessObjectCache<RefCountedT> cache;
Ref<RefCountedT> object = AcquireRef(new RefCountedT(1, [&](RefCountedT* x) {
- signalA.Fire();
- signalB.Wait();
+ semA.Release();
+ semB.Acquire();
cache.Erase(x);
}));
EXPECT_TRUE(cache.Insert(object.Get()).second);
@@ -183,10 +166,10 @@
auto threadA = [&] { object = nullptr; };
// Thread B will try to Find the entry before it is completely destroyed.
auto threadB = [&] {
- signalA.Wait();
+ semA.Acquire();
RefCountedT blueprint(1);
EXPECT_TRUE(cache.Find(&blueprint) == nullptr);
- signalB.Fire();
+ semB.Release();
};
std::thread tA(threadA);
@@ -198,12 +181,12 @@
// Inserting an element that has an entry which is in process of deletion should insert the new
// object.
TEST(ContentLessObjectCacheTest, InsertDeleting) {
- Signal signalA, signalB;
+ BinarySemaphore semA, semB;
ContentLessObjectCache<RefCountedT> cache;
Ref<RefCountedT> object1 = AcquireRef(new RefCountedT(1, [&](RefCountedT* x) {
- signalA.Fire();
- signalB.Wait();
+ semA.Release();
+ semB.Acquire();
cache.Erase(x);
}));
EXPECT_TRUE(cache.Insert(object1.Get()).second);
@@ -216,9 +199,9 @@
// Thread B will try to Insert a hash equivalent entry before the original is completely
// destroyed.
auto threadB = [&] {
- signalA.Wait();
+ semA.Acquire();
EXPECT_TRUE(cache.Insert(object2.Get()).second);
- signalB.Fire();
+ semB.Release();
};
std::thread tA(threadA);
diff --git a/src/dawn/tests/unittests/WeakRefTests.cpp b/src/dawn/tests/unittests/WeakRefTests.cpp
new file mode 100644
index 0000000..5ae62f3
--- /dev/null
+++ b/src/dawn/tests/unittests/WeakRefTests.cpp
@@ -0,0 +1,170 @@
+// Copyright 2023 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <functional>
+#include <thread>
+
+#include "dawn/common/WeakRef.h"
+#include "dawn/common/WeakRefSupport.h"
+#include "dawn/utils/BinarySemaphore.h"
+#include "gtest/gtest.h"
+
+namespace dawn {
+namespace {
+
+using utils::BinarySemaphore;
+
+class RefCountedT : public RefCounted {};
+
+class WeakRefBaseA : public RefCounted, public WeakRefSupport<WeakRefBaseA> {
+ public:
+ WeakRefBaseA() = default;
+ explicit WeakRefBaseA(std::function<void(WeakRefBaseA*)> deleteFn) : mDeleteFn(deleteFn) {}
+
+ protected:
+ ~WeakRefBaseA() override { mDeleteFn(this); }
+
+ private:
+ std::function<void(WeakRefBaseA*)> mDeleteFn = [](WeakRefBaseA*) -> void {};
+};
+
+class WeakRefDerivedA : public WeakRefBaseA {
+ public:
+ WeakRefDerivedA() = default;
+ explicit WeakRefDerivedA(std::function<void(WeakRefBaseA*)> deleteFn)
+ : WeakRefBaseA(deleteFn) {}
+};
+
+class WeakRefBaseB : public RefCounted, public WeakRefSupport<WeakRefBaseB> {};
+class WeakRefDerivedB : public WeakRefBaseB {};
+
+// When the original refcounted object is destroyed, all WeakRefs are no longer able to Promote.
+TEST(WeakRefTests, BasicPromote) {
+ Ref<WeakRefBaseA> base = AcquireRef(new WeakRefBaseA());
+ WeakRef<WeakRefBaseA> weak = base.GetWeakRef();
+ EXPECT_EQ(weak.Promote().Get(), base.Get());
+
+ base = nullptr;
+ EXPECT_EQ(weak.Promote().Get(), nullptr);
+}
+
+// When the original refcounted object is destroyed, all WeakRefs, including upcasted ones, are no
+// longer able to Promote.
+TEST(WeakRefTests, DerivedPromote) {
+ Ref<WeakRefDerivedA> base = AcquireRef(new WeakRefDerivedA());
+ WeakRef<WeakRefDerivedA> weak1 = base.GetWeakRef();
+ WeakRef<WeakRefBaseA> weak2 = weak1;
+ WeakRef<WeakRefBaseA> weak3 = base.GetWeakRef();
+ EXPECT_EQ(weak1.Promote().Get(), base.Get());
+ EXPECT_EQ(weak2.Promote().Get(), base.Get());
+ EXPECT_EQ(weak3.Promote().Get(), base.Get());
+
+ base = nullptr;
+ EXPECT_EQ(weak1.Promote().Get(), nullptr);
+ EXPECT_EQ(weak2.Promote().Get(), nullptr);
+ EXPECT_EQ(weak3.Promote().Get(), nullptr);
+}
+
+// Trying to promote a WeakRef to a Ref while the original value is being destroyed returns nullptr.
+TEST(WeakRefTests, DeletingAndPromoting) {
+ BinarySemaphore semA, semB;
+ Ref<WeakRefBaseA> base = AcquireRef(new WeakRefBaseA([&](WeakRefBaseA*) {
+ semB.Release();
+ semA.Acquire();
+ }));
+
+ auto f = [&] {
+ WeakRef<WeakRefBaseA> weak = base.GetWeakRef();
+ semA.Release();
+ semB.Acquire();
+ EXPECT_EQ(weak.Promote().Get(), nullptr);
+ semA.Release();
+ };
+ std::thread t(f);
+
+ semA.Acquire();
+ base = nullptr;
+ t.join();
+}
+
+} // anonymous namespace
+} // namespace dawn
+
+// Special compilation tests that are only enabled when experimental headers are available.
+#if __has_include(<experimental/type_traits>)
+#include <experimental/type_traits>
+
+namespace dawn {
+namespace {
+
+// Helper detection utilities for verifying that unintended assignments are not allowed.
+template <typename L, typename R>
+using weakref_copyable_t =
+ decltype(std::declval<WeakRef<L>&>() = std::declval<const WeakRef<R>&>());
+template <typename L, typename R>
+using weakref_movable_t =
+ decltype(std::declval<WeakRef<L>&>() = std::declval<const WeakRef<R>&&>());
+TEST(WeakRefTests, CrossTypesAssignments) {
+ // Same type and upcasting is allowed.
+ static_assert(std::experimental::is_detected_v<weakref_copyable_t, WeakRefBaseA, WeakRefBaseA>,
+ "Same type copy assignment is allowed.");
+ static_assert(std::experimental::is_detected_v<weakref_movable_t, WeakRefBaseA, WeakRefBaseA>,
+ "Same type move assignment is allowed.");
+
+ static_assert(
+ std::experimental::is_detected_v<weakref_copyable_t, WeakRefBaseA, WeakRefDerivedA>,
+ "Upcasting type copy assignment is allowed.");
+ static_assert(
+ std::experimental::is_detected_v<weakref_movable_t, WeakRefBaseA, WeakRefDerivedA>,
+ "Upcasting type move assignment is allowed.");
+
+ // Same type, but down casting is not allowed.
+ static_assert(
+ !std::experimental::is_detected_v<weakref_copyable_t, WeakRefDerivedA, WeakRefBaseA>,
+ "Downcasting type copy assignment is not allowed.");
+ static_assert(
+ !std::experimental::is_detected_v<weakref_movable_t, WeakRefDerivedA, WeakRefBaseA>,
+ "Downcasting type move assignment is not allowed.");
+
+ // Cross types are not allowed.
+ static_assert(!std::experimental::is_detected_v<weakref_copyable_t, WeakRefBaseA, WeakRefBaseB>,
+ "Cross type copy assignment is not allowed.");
+ static_assert(!std::experimental::is_detected_v<weakref_movable_t, WeakRefBaseA, WeakRefBaseB>,
+ "Cross type move assignment is not allowed.");
+ static_assert(
+ !std::experimental::is_detected_v<weakref_copyable_t, WeakRefBaseA, WeakRefDerivedB>,
+ "Cross type upcasting copy assignment is not allowed.");
+ static_assert(
+ !std::experimental::is_detected_v<weakref_movable_t, WeakRefBaseA, WeakRefDerivedB>,
+ "Cross type upcasting move assignment is not allowed.");
+}
+
+// Helper detection utilty to verify whether GetWeakRef is enabled.
+template <typename T>
+using can_get_weakref_t = decltype(std::declval<Ref<T>>().GetWeakRef());
+TEST(WeakRefTests, GetWeakRef) {
+ // The GetWeakRef function is only available on types that extend WeakRefSupport.
+ static_assert(std::experimental::is_detected_v<can_get_weakref_t, WeakRefBaseA>,
+ "GetWeakRef is enabled on classes that directly extend WeakRefSupport.");
+ static_assert(std::experimental::is_detected_v<can_get_weakref_t, WeakRefDerivedA>,
+ "GetWeakRef is enabled on classes that indirectly extend WeakRefSupport.");
+
+ static_assert(!std::experimental::is_detected_v<can_get_weakref_t, RefCountedT>,
+ "GetWeakRef is disabled on classes that do not extend WeakRefSupport.");
+}
+
+} // anonymous namespace
+} // namespace dawn
+
+#endif
diff --git a/src/dawn/utils/BUILD.gn b/src/dawn/utils/BUILD.gn
index 48430d4..f83b679 100644
--- a/src/dawn/utils/BUILD.gn
+++ b/src/dawn/utils/BUILD.gn
@@ -27,6 +27,8 @@
]
sources = [
+ "BinarySemaphore.cpp",
+ "BinarySemaphore.h",
"ComboRenderBundleEncoderDescriptor.cpp",
"ComboRenderBundleEncoderDescriptor.h",
"ComboRenderPipelineDescriptor.cpp",
diff --git a/src/dawn/utils/BinarySemaphore.cpp b/src/dawn/utils/BinarySemaphore.cpp
new file mode 100644
index 0000000..a9bedf7
--- /dev/null
+++ b/src/dawn/utils/BinarySemaphore.cpp
@@ -0,0 +1,33 @@
+// Copyright 2023 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "dawn/utils/BinarySemaphore.h"
+
+namespace dawn::utils {
+
+void BinarySemaphore::Release() {
+ std::lock_guard<std::mutex> lock(mMutex);
+ mSignaled = true;
+ mCv.notify_one();
+}
+
+void BinarySemaphore::Acquire() {
+ std::unique_lock<std::mutex> lock(mMutex);
+ while (!mSignaled) {
+ mCv.wait(lock);
+ }
+ mSignaled = false;
+}
+
+} // namespace dawn::utils
diff --git a/src/dawn/utils/BinarySemaphore.h b/src/dawn/utils/BinarySemaphore.h
new file mode 100644
index 0000000..278ac92
--- /dev/null
+++ b/src/dawn/utils/BinarySemaphore.h
@@ -0,0 +1,38 @@
+// Copyright 2023 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_DAWN_UTILS_BINARYSEMAPHORE_H_
+#define SRC_DAWN_UTILS_BINARYSEMAPHORE_H_
+
+#include <condition_variable>
+#include <mutex>
+
+namespace dawn::utils {
+
+// Binary semaphore implementation. Could eventually be replaced with std::binary_semaphore when
+// C++20 is available everywhere.
+class BinarySemaphore {
+ public:
+ void Release();
+ void Acquire();
+
+ private:
+ std::mutex mMutex;
+ std::condition_variable mCv;
+ bool mSignaled = false;
+};
+
+} // namespace dawn::utils
+
+#endif // SRC_DAWN_UTILS_BINARYSEMAPHORE_H_
diff --git a/src/dawn/utils/CMakeLists.txt b/src/dawn/utils/CMakeLists.txt
index b2595de..d5c2059 100644
--- a/src/dawn/utils/CMakeLists.txt
+++ b/src/dawn/utils/CMakeLists.txt
@@ -15,6 +15,8 @@
add_library(dawn_utils STATIC ${DAWN_PLACEHOLDER_FILE})
common_compile_options(dawn_utils)
target_sources(dawn_utils PRIVATE
+ "BinarySemaphore.cpp"
+ "BinarySemaphore.h"
"ComboRenderBundleEncoderDescriptor.cpp"
"ComboRenderBundleEncoderDescriptor.h"
"ComboRenderPipelineDescriptor.cpp"