Adding HAL semaphore support for statuses-as-failure-payloads. (#18912)

This allows an implementation to have a single atomic value for a
semaphore that encodes the user payload or an error payload that
optionally references an iree_status_t object. Implementations not using
the status feature can ignore it but must perform a
greater-than-or-equal check on `IREE_HAL_SEMAPHORE_FAILURE_VALUE`
instead of equality.
diff --git a/runtime/src/iree/hal/cts/semaphore_submission_test.h b/runtime/src/iree/hal/cts/semaphore_submission_test.h
index a158082..b745761 100644
--- a/runtime/src/iree/hal/cts/semaphore_submission_test.h
+++ b/runtime/src/iree/hal/cts/semaphore_submission_test.h
@@ -882,7 +882,7 @@
   EXPECT_EQ(iree_status_code(wait_status), IREE_STATUS_ABORTED);
   uint64_t value = 1234;
   iree_status_t query_status = iree_hal_semaphore_query(semaphore2, &value);
-  EXPECT_EQ(value, IREE_HAL_SEMAPHORE_FAILURE_VALUE);
+  EXPECT_GE(value, IREE_HAL_SEMAPHORE_FAILURE_VALUE);
   CheckStatusContains(query_status, status);
 
   signal_thread.join();
diff --git a/runtime/src/iree/hal/cts/semaphore_test.h b/runtime/src/iree/hal/cts/semaphore_test.h
index 54e907e..7d0592f 100644
--- a/runtime/src/iree/hal/cts/semaphore_test.h
+++ b/runtime/src/iree/hal/cts/semaphore_test.h
@@ -406,7 +406,7 @@
   EXPECT_EQ(iree_status_code(wait_status), IREE_STATUS_ABORTED);
   uint64_t value = 1234;
   iree_status_t query_status = iree_hal_semaphore_query(semaphore, &value);
-  EXPECT_EQ(value, IREE_HAL_SEMAPHORE_FAILURE_VALUE);
+  EXPECT_GE(value, IREE_HAL_SEMAPHORE_FAILURE_VALUE);
   CheckStatusContains(query_status, status);
 
   iree_hal_semaphore_release(semaphore);
@@ -431,7 +431,7 @@
   EXPECT_EQ(iree_status_code(wait_status), IREE_STATUS_ABORTED);
   uint64_t value = 1234;
   iree_status_t query_status = iree_hal_semaphore_query(semaphore, &value);
-  EXPECT_EQ(value, IREE_HAL_SEMAPHORE_FAILURE_VALUE);
+  EXPECT_GE(value, IREE_HAL_SEMAPHORE_FAILURE_VALUE);
   CheckStatusContains(query_status, status);
 
   signal_thread.join();
@@ -467,7 +467,7 @@
   uint64_t value = 1234;
   iree_status_t semaphore1_query_status =
       iree_hal_semaphore_query(semaphore1, &value);
-  EXPECT_EQ(value, IREE_HAL_SEMAPHORE_FAILURE_VALUE);
+  EXPECT_GE(value, IREE_HAL_SEMAPHORE_FAILURE_VALUE);
   CheckStatusContains(semaphore1_query_status, status);
 
   // semaphore2 must not have changed.
@@ -511,7 +511,7 @@
   uint64_t value = 1234;
   iree_status_t semaphore1_query_status =
       iree_hal_semaphore_query(semaphore1, &value);
-  EXPECT_EQ(value, IREE_HAL_SEMAPHORE_FAILURE_VALUE);
+  EXPECT_GE(value, IREE_HAL_SEMAPHORE_FAILURE_VALUE);
   CheckStatusContains(semaphore1_query_status, status);
 
   // semaphore2 must not have changed.
diff --git a/runtime/src/iree/hal/drivers/cuda/event_semaphore.c b/runtime/src/iree/hal/drivers/cuda/event_semaphore.c
index fb86efe..0c0cf41 100644
--- a/runtime/src/iree/hal/drivers/cuda/event_semaphore.c
+++ b/runtime/src/iree/hal/drivers/cuda/event_semaphore.c
@@ -325,7 +325,7 @@
   }
 
   iree_slim_mutex_lock(&semaphore->mutex);
-  if (semaphore->current_value == IREE_HAL_SEMAPHORE_FAILURE_VALUE) {
+  if (semaphore->current_value >= IREE_HAL_SEMAPHORE_FAILURE_VALUE) {
     iree_slim_mutex_unlock(&semaphore->mutex);
     IREE_TRACE_ZONE_END(z0);
     return iree_make_status(IREE_STATUS_ABORTED);
@@ -350,7 +350,7 @@
   }
 
   iree_slim_mutex_lock(&semaphore->mutex);
-  if (semaphore->current_value == IREE_HAL_SEMAPHORE_FAILURE_VALUE) {
+  if (semaphore->current_value >= IREE_HAL_SEMAPHORE_FAILURE_VALUE) {
     status = iree_make_status(IREE_STATUS_ABORTED);
   }
   iree_slim_mutex_unlock(&semaphore->mutex);
@@ -444,7 +444,7 @@
     iree_hal_cuda_semaphore_t* semaphore =
         iree_hal_cuda_semaphore_cast(semaphore_list.semaphores[i]);
     iree_slim_mutex_lock(&semaphore->mutex);
-    if (semaphore->current_value == IREE_HAL_SEMAPHORE_FAILURE_VALUE) {
+    if (semaphore->current_value >= IREE_HAL_SEMAPHORE_FAILURE_VALUE) {
       iree_slim_mutex_unlock(&semaphore->mutex);
       status = iree_make_status(IREE_STATUS_ABORTED);
       break;
diff --git a/runtime/src/iree/hal/drivers/hip/event_semaphore.c b/runtime/src/iree/hal/drivers/hip/event_semaphore.c
index 926eb54..de10b09 100644
--- a/runtime/src/iree/hal/drivers/hip/event_semaphore.c
+++ b/runtime/src/iree/hal/drivers/hip/event_semaphore.c
@@ -323,7 +323,7 @@
   }
 
   iree_slim_mutex_lock(&semaphore->mutex);
-  if (semaphore->current_value == IREE_HAL_SEMAPHORE_FAILURE_VALUE) {
+  if (semaphore->current_value >= IREE_HAL_SEMAPHORE_FAILURE_VALUE) {
     iree_slim_mutex_unlock(&semaphore->mutex);
     IREE_TRACE_ZONE_END(z0);
     return iree_make_status(IREE_STATUS_ABORTED);
@@ -346,7 +346,7 @@
   }
 
   iree_slim_mutex_lock(&semaphore->mutex);
-  if (semaphore->current_value == IREE_HAL_SEMAPHORE_FAILURE_VALUE) {
+  if (semaphore->current_value >= IREE_HAL_SEMAPHORE_FAILURE_VALUE) {
     status = iree_make_status(IREE_STATUS_ABORTED);
   }
   iree_slim_mutex_unlock(&semaphore->mutex);
@@ -440,7 +440,7 @@
     iree_hal_hip_semaphore_t* semaphore =
         iree_hal_hip_semaphore_cast(semaphore_list.semaphores[i]);
     iree_slim_mutex_lock(&semaphore->mutex);
-    if (semaphore->current_value == IREE_HAL_SEMAPHORE_FAILURE_VALUE) {
+    if (semaphore->current_value >= IREE_HAL_SEMAPHORE_FAILURE_VALUE) {
       iree_slim_mutex_unlock(&semaphore->mutex);
       status = iree_make_status(IREE_STATUS_ABORTED);
       break;
diff --git a/runtime/src/iree/hal/semaphore.h b/runtime/src/iree/hal/semaphore.h
index 8cc073b..52571ed 100644
--- a/runtime/src/iree/hal/semaphore.h
+++ b/runtime/src/iree/hal/semaphore.h
@@ -30,10 +30,6 @@
 };
 typedef uint32_t iree_hal_semaphore_flags_t;
 
-//===----------------------------------------------------------------------===//
-// iree_hal_semaphore_t
-//===----------------------------------------------------------------------===//
-
 // The maximum valid payload value of an iree_hal_semaphore_t.
 // Payload values larger than this indicate that the semaphore has failed.
 //
@@ -56,8 +52,66 @@
 //   https://vulkan.gpuinfo.org/displayextensionproperty.php?name=maxTimelineSemaphoreValueDifference
 #define IREE_HAL_SEMAPHORE_MAX_VALUE (2147483647ull - 1)
 
+// The minimum value for a semaphore that indicates failure. Any value
+// greater-than-or-equal-to (>=) this indicates the semaphore has failed.
+//
+// If the upper bit 63 is set then the value represents an iree_status_t.
+// Use iree_hal_semaphore_failure_as_status to convert a payload value to a
+// status. Not all implementations do (or can) support encoding statuses and may
+// only ever be able to set a failing semaphore to this value.
 #define IREE_HAL_SEMAPHORE_FAILURE_VALUE (IREE_HAL_SEMAPHORE_MAX_VALUE + 1)
 
+// Bit indicating that a failing semaphore value can be interpreted as an
+// iree_status_t.
+#define IREE_HAL_SEMAPHORE_FAILURE_VALUE_STATUS_BIT 0x8000000000000000ull
+
+// Returns a semaphore payload value that encodes the given |status|.
+// Ownership of the status is transferred to the semaphore and it must be
+// freed by a consumer. Not all implementations can support failure status
+// payloads and this should only be used by those implementations that can.
+static inline uint64_t iree_hal_status_as_semaphore_failure(
+    iree_status_t status) {
+  return IREE_HAL_SEMAPHORE_FAILURE_VALUE_STATUS_BIT |
+         (((uint64_t)status) >> 1);
+}
+
+// Returns OK if the |value| does not indicate an error.
+// Returns an error status if the semaphore payload value represents a failure.
+// If the payload contains an encoded iree_status_t it will be cloned and the
+// new copy will be returned to the caller.
+static inline iree_status_t iree_hal_semaphore_failure_as_status(
+    uint64_t value) {
+  if (value >= IREE_HAL_SEMAPHORE_FAILURE_VALUE) {
+    if (value & IREE_HAL_SEMAPHORE_FAILURE_VALUE_STATUS_BIT) {
+      // The top bits of a pointer are sign-extended from bit 47 so we can
+      // restore the top bit by left-shifting the upper bits and then
+      // right-shifting with sign extension. We only use a single bit today and
+      // so bit 62 should still be the original value of the pointer.
+      // Note that if the status is just a code (no allocated pointer) this
+      // clone is a no-op and the code will be returned without an allocation.
+      //
+      // See:
+      // https://en.wikipedia.org/wiki/X86-64#Canonical_form_addresses
+      return iree_status_clone((iree_status_t)(((int64_t)value << 1) >> 1));
+    } else {
+      return iree_status_from_code(IREE_STATUS_INTERNAL);
+    }
+  } else {
+    return iree_ok_status();
+  }
+}
+
+// Frees an iree_status_t encoded in a semaphore |value|, if any.
+static inline void iree_hal_semaphore_failure_free(uint64_t value) {
+  if (value & IREE_HAL_SEMAPHORE_FAILURE_VALUE_STATUS_BIT) {
+    iree_status_free((iree_status_t)(((int64_t)value << 1) >> 1));
+  }
+}
+
+//===----------------------------------------------------------------------===//
+// iree_hal_semaphore_t
+//===----------------------------------------------------------------------===//
+
 // Synchronization mechanism for host->device, device->host, host->host,
 // and device->device notification. Semaphores behave like Vulkan timeline
 // semaphores (or D3D12 fences) and contain a monotonically increasing