[dawn][common] Add FetchMax helpers for atomic max.

Change-Id: I5ad1a97eb7839d957b0e24497f1c5693e8d2ffe4
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/247998
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
Commit-Queue: Loko Kung <lokokung@google.com>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/dawn/common/Atomic.h b/src/dawn/common/Atomic.h
new file mode 100644
index 0000000..f585509
--- /dev/null
+++ b/src/dawn/common/Atomic.h
@@ -0,0 +1,50 @@
+// Copyright 2025 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef SRC_DAWN_COMMON_ATOMIC_H_
+#define SRC_DAWN_COMMON_ATOMIC_H_
+
+#include <atomic>
+#include <concepts>
+
+namespace dawn {
+
+// Equivalent to C++ stdlib's fetch_max that is only available in C++26 and beyond. This returns the
+// value preceding the effects of this function.
+template <typename T>
+    requires std::totally_ordered<T>
+T FetchMax(std::atomic<T>& t, T arg) {
+    // Atomically set |t| to |arg| if |arg| > |t|.
+    T current = t.load(std::memory_order::acquire);
+    while (arg > current && t.compare_exchange_weak(current, arg, std::memory_order::acq_rel)) {
+    }
+    return current;
+}
+
+}  // namespace dawn
+
+#endif  // SRC_DAWN_COMMON_RESULT_H_
diff --git a/src/dawn/common/BUILD.gn b/src/dawn/common/BUILD.gn
index 461d726..e10e3d2 100644
--- a/src/dawn/common/BUILD.gn
+++ b/src/dawn/common/BUILD.gn
@@ -268,6 +268,7 @@
       "Alloc.h",
       "Assert.cpp",
       "Assert.h",
+      "Atomic.h",
       "BitSetRangeIterator.h",
       "Compiler.h",
       "Constants.h",
diff --git a/src/dawn/common/CMakeLists.txt b/src/dawn/common/CMakeLists.txt
index 0360786..cfa2747 100644
--- a/src/dawn/common/CMakeLists.txt
+++ b/src/dawn/common/CMakeLists.txt
@@ -48,6 +48,7 @@
     "AlignedAlloc.h"
     "Alloc.h"
     "Assert.h"
+    "Atomic.h"
     "BitSetRangeIterator.h"
     "Compiler.h"
     "Constants.h"
diff --git a/src/dawn/native/EventManager.cpp b/src/dawn/native/EventManager.cpp
index 50f85f1..1865258 100644
--- a/src/dawn/native/EventManager.cpp
+++ b/src/dawn/native/EventManager.cpp
@@ -35,6 +35,7 @@
 
 #include "absl/strings/str_format.h"
 #include "dawn/common/Assert.h"
+#include "dawn/common/Atomic.h"
 #include "dawn/common/FutureUtils.h"
 #include "dawn/common/Log.h"
 #include "dawn/native/ChainUtils.h"
@@ -368,11 +369,7 @@
             return;
         }
         if (event->mCallbackMode != wgpu::CallbackMode::WaitAnyOnly) {
-            FutureID lastProcessedEventID = mLastProcessEventID.load(std::memory_order_acquire);
-            while (lastProcessedEventID < futureID &&
-                   !mLastProcessEventID.compare_exchange_weak(lastProcessedEventID, futureID,
-                                                              std::memory_order_acq_rel)) {
-            }
+            FetchMax(mLastProcessEventID, futureID);
         }
         (*events)->emplace(futureID, std::move(event));
     });
diff --git a/src/dawn/native/ExecutionQueue.cpp b/src/dawn/native/ExecutionQueue.cpp
index 3fb78aa..4645106 100644
--- a/src/dawn/native/ExecutionQueue.cpp
+++ b/src/dawn/native/ExecutionQueue.cpp
@@ -31,6 +31,8 @@
 #include <utility>
 #include <vector>
 
+#include "dawn/common/Atomic.h"
+
 namespace dawn::native {
 
 ExecutionSerial ExecutionQueueBase::GetPendingCommandSerial() const {
@@ -53,11 +55,8 @@
                 ExecutionSerial(mLastSubmittedSerial.load(std::memory_order_acquire)));
 
     // Atomically set mCompletedSerial to completedSerial if completedSerial is larger.
-    uint64_t current = mCompletedSerial.load(std::memory_order_acquire);
-    while (uint64_t(completedSerial) > current &&
-           !mCompletedSerial.compare_exchange_weak(current, uint64_t(completedSerial),
-                                                   std::memory_order_acq_rel)) {
-    }
+    FetchMax(mCompletedSerial, uint64_t(completedSerial));
+
     // TODO(crbug.com/421945313): We should call |UpdateCompletedSerial| here also, but since some
     // backends rely on the device lock for safe use of |CheckAndUpdateCompletedSerials|, we
     // separate that call out for now.
@@ -74,11 +73,7 @@
 
 void ExecutionQueueBase::UpdateCompletedSerial(ExecutionSerial completedSerial) {
     // Atomically set mCompletedSerial to completedSerial if completedSerial is larger.
-    uint64_t current = mCompletedSerial.load(std::memory_order_acquire);
-    while (uint64_t(completedSerial) > current &&
-           !mCompletedSerial.compare_exchange_weak(current, uint64_t(completedSerial),
-                                                   std::memory_order_acq_rel)) {
-    }
+    FetchMax(mCompletedSerial, uint64_t(completedSerial));
 
     std::vector<Task> pending;
     mWaitingTasks.Use([&](auto tasks) {