[fuchsia] Implement futex directly on zircon syscalls.

PlatformFutex won't be available in the Fuchsia SDK.

Change-Id: I78343e2f1785569c1fc44aa33d40d3315057ea0e
diff --git a/src/util/BUILD.gn b/src/util/BUILD.gn
index 89b2987..2ab6fb7 100644
--- a/src/util/BUILD.gn
+++ b/src/util/BUILD.gn
@@ -102,18 +102,15 @@
     "vma.c",
   ]
 
-  deps = [
-    "$magma_build_root/src/magma_util",
-  ]
+  deps = []
 
   if (current_os == "fuchsia") {
     sources += [
-      "futex_fuchsia.cpp",
+      "futex_fuchsia.c",
       "os_dirent_fuchsia.cpp",
     ]
     deps += [
       "$mesa_build_root/src/os",
-      "$magma_build_root/src/magma_util/platform:futex",
       "//zircon/public/lib/zx",
       "//zircon/public/lib/zxio",
     ]
diff --git a/src/util/futex_fuchsia.c b/src/util/futex_fuchsia.c
new file mode 100644
index 0000000..8e2c49a
--- /dev/null
+++ b/src/util/futex_fuchsia.c
@@ -0,0 +1,102 @@
+/*
+ * Copyright © 2019 Google, LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include "futex.h"
+#include "os/fuchsia.h"
+#include "util/timespec.h"
+#include <assert.h>
+#include <errno.h>
+#include <zircon/syscalls.h>
+
+static_assert(sizeof(zx_futex_t) == sizeof(uint32_t), "futex type incompatible size");
+
+enum WaitResult { AWOKE, TIMED_OUT, RETRY };
+
+static inline bool Wake(uint32_t* value_ptr, int32_t wake_count)
+{
+   zx_status_t status;
+   if ((status = zx_futex_wake((zx_futex_t*)value_ptr, wake_count)) != ZX_OK) {
+      FUCHSIA_DLOG("zx_futex_wake failed: %d", status);
+      return false;
+   }
+   return true;
+}
+
+static inline bool Wait(uint32_t* value_ptr, int32_t current_value, uint64_t timeout_ns,
+                        enum WaitResult* result_out)
+{
+   const zx_time_t deadline =
+       (timeout_ns == UINT64_MAX) ? ZX_TIME_INFINITE : zx_deadline_after(timeout_ns);
+   zx_status_t status =
+       zx_futex_wait((zx_futex_t*)value_ptr, current_value, ZX_HANDLE_INVALID, deadline);
+   switch (status) {
+   case ZX_OK:
+      *result_out = AWOKE;
+      break;
+   case ZX_ERR_TIMED_OUT:
+      *result_out = TIMED_OUT;
+      break;
+   case ZX_ERR_BAD_STATE:
+      *result_out = RETRY;
+      break;
+   default:
+      FUCHSIA_DLOG("zx_futex_wait returned: %d", status);
+      return false;
+   }
+   return true;
+}
+
+int futex_wake(uint32_t* addr, int count)
+{
+   if (!Wake(addr, count)) {
+      FUCHSIA_DLOG("PlatformFutex::Wake failed");
+      return -1;
+   }
+   return 0;
+}
+
+int futex_wait(uint32_t* addr, int32_t value, const struct timespec* timeout)
+{
+   uint64_t timeout_ns;
+   if (timeout == NULL) {
+      timeout_ns = UINT64_MAX;
+   } else {
+      timeout_ns = timespec_to_nsec(timeout);
+   }
+
+   enum WaitResult result;
+   if (!Wait(addr, value, timeout_ns, &result)) {
+      FUCHSIA_DLOG("PlatformFutex::WaitForever failed");
+      return -EINVAL;
+   }
+   switch (result) {
+   case RETRY:
+      return -EAGAIN;
+   case TIMED_OUT:
+      return -ETIMEDOUT;
+   default:
+      break;
+   }
+   assert(result == AWOKE);
+   return 0;
+}
diff --git a/src/util/futex_fuchsia.cpp b/src/util/futex_fuchsia.cpp
deleted file mode 100644
index ed36e67..0000000
--- a/src/util/futex_fuchsia.cpp
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright © 2019 Google, LLC
- *
- * Permission is hereby granted, free of charge, to any person obtaining a
- * copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation
- * the rights to use, copy, modify, merge, publish, distribute, sublicense,
- * and/or sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice (including the next
- * paragraph) shall be included in all copies or substantial portions of the
- * Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
- * IN THE SOFTWARE.
- */
-
-#include "futex.h"
-#include "platform_futex.h"
-#include <assert.h>
-#include <errno.h>
-#include "os/fuchsia.h"
-
-int futex_wake(uint32_t* addr, int count)
-{
-   if (!magma::PlatformFutex::Wake(addr, count)) {
-      FUCHSIA_DLOG("PlatformFutex::Wake failed");
-      return -1;
-   }
-   return 0;
-}
-
-int futex_wait(uint32_t* addr, int32_t value, const struct timespec* timeout)
-{
-   // Timeouts not implemented.
-   assert(timeout == nullptr);
-   magma::PlatformFutex::WaitResult result;
-   if (!magma::PlatformFutex::WaitForever(addr, value, &result)) {
-      FUCHSIA_DLOG("PlatformFutex::WaitForever failed");
-      return -EINVAL;
-   }
-   if (result == magma::PlatformFutex::WaitResult::RETRY)
-      return -EAGAIN;
-   assert(result == magma::PlatformFutex::WaitResult::AWOKE);
-   return 0;
-}
diff --git a/src/util/tests/futex/BUILD.gn b/src/util/tests/futex/BUILD.gn
new file mode 100644
index 0000000..36825f7
--- /dev/null
+++ b/src/util/tests/futex/BUILD.gn
@@ -0,0 +1,39 @@
+# Copyright 2019 Google, LLC
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice (including the next
+# paragraph) shall be included in all copies or substantial portions of the
+# Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+import("../../../../mesa.gni")
+
+mesa_source_set("futex") {
+  testonly = true
+
+  sources = [
+    "test_futex.cpp",
+  ]
+
+  deps = [ "$mesa_build_root/src/util" ]
+
+  if (is_fuchsia) {
+    deps += [
+      "$mesa_build_root/src/os",
+      "//third_party/googletest:gtest",
+    ]
+  }
+}
diff --git a/src/util/tests/futex/test_futex.cpp b/src/util/tests/futex/test_futex.cpp
new file mode 100644
index 0000000..ba37562
--- /dev/null
+++ b/src/util/tests/futex/test_futex.cpp
@@ -0,0 +1,80 @@
+/*
+ * Copyright © 2019 Google, LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include <atomic>
+#include <chrono>
+#include <thread>
+
+#include "util/futex.h"
+#include "gtest/gtest.h"
+
+TEST(Futex, Retry)
+{
+   uint32_t value = 0;
+   EXPECT_EQ(-EAGAIN, futex_wait(&value, value + 1, NULL));
+}
+
+TEST(Futex, Timeout)
+{
+   uint32_t value = 0;
+
+   auto start = std::chrono::high_resolution_clock::now();
+
+   static constexpr uint32_t kTimeoutMs = 100;
+   struct timespec timeout = {
+       .tv_sec = 0,
+       .tv_nsec = kTimeoutMs * 1000000,
+   };
+   EXPECT_EQ(-ETIMEDOUT, futex_wait(&value, value, &timeout));
+
+   auto end = std::chrono::high_resolution_clock::now();
+   std::chrono::duration<double, std::milli> elapsed = end - start;
+
+   EXPECT_GT(elapsed.count(), kTimeoutMs);
+}
+
+TEST(Futex, WaitAndWake)
+{
+   constexpr int kResultNotReady = 1;
+   static std::atomic<int> result = kResultNotReady;
+   static uint32_t value;
+
+   std::thread thread([]() {
+      int r = futex_wait(&value, value, NULL);
+      result.store(r);
+   });
+
+   int check = kResultNotReady;
+
+   for (int retry = 0; retry < 10; retry++) {
+      std::this_thread::sleep_for(std::chrono::milliseconds(10));
+      futex_wake(&value, 1);
+      check = result.load();
+      if (check != kResultNotReady) {
+         thread.join();
+         break;
+      }
+   }
+
+   EXPECT_EQ(check, 0);
+}
diff --git a/tests/BUILD.gn b/tests/BUILD.gn
index 7261314..69150c7 100644
--- a/tests/BUILD.gn
+++ b/tests/BUILD.gn
@@ -40,6 +40,7 @@
     "unit_tests",
     "$mesa_build_root/src/util/tests/os_dirent",
     "$mesa_build_root/src/util/tests/inflight_list",
+    "$mesa_build_root/src/util/tests/futex",
     "//third_party/googletest:gtest",
   ]
 }