[llvm] [offload] add tests for event synchronization (PR #193927)

Kevin Sala Penades via llvm-commits llvm-commits at lists.llvm.org
Mon May 4 10:02:46 PDT 2026


================
@@ -240,6 +248,115 @@ TEST_P(olLaunchKernelLocalMemStaticTest, Success) {
   ASSERT_SUCCESS(olMemFree(Mem));
 }
 
+// The test intends to verify the correctness of the current implementation of
+// the event synchronisation.
+TEST_P(olLaunchKernelSingleCounterSyncEventTest, SuccessSyncEvent) {
+  void* InitValuePassed;
+  void *ResNum;
+
+  size_t Size = sizeof(uint32_t);
+
+  ASSERT_SUCCESS(olMemAlloc(Device, OL_ALLOC_TYPE_DEVICE, Size, &InitValuePassed));
+  ASSERT_SUCCESS(olMemAlloc(Device, OL_ALLOC_TYPE_DEVICE, Size, &ResNum));
+
+  uint32_t HostInitVal = 0;
+  ASSERT_SUCCESS(olMemcpy(Queue, InitValuePassed, Device, &HostInitVal, Host, Size));
+  ASSERT_SUCCESS(olMemcpy(Queue, ResNum, Device, &HostInitVal, Host, Size));
+  ASSERT_SUCCESS(olSyncQueue(Queue));
+
+  // The execution time of the provided kernel should be high enough to ensure
+  // that the read value of the final result is not correct without explicit
+  // synchronization: explicit waiting for the event following the submitted
+  // operation. LoopRange is arbitrarily set with the goal of prolonging the
+  // kernel execution through a high number of loop iterations. In each
+  // iteration, NumberToAdd is added to the final sum, which is stored in
+  // ResNum.
+  int32_t LoopRange = 1000000;
+  int32_t NumberToAdd = 2;
+
+  ArgsSingleCounter Args{LoopRange, NumberToAdd, (uint32_t *)InitValuePassed,
+                         (uint32_t *)ResNum};
+
+  ASSERT_SUCCESS(
+      olLaunchKernel(Queue, Device, Kernel, &Args, sizeof(Args), &LaunchArgs));
+
+  uint32_t FinalResVal = 0;
+  ASSERT_SUCCESS(olMemcpy(Queue, &FinalResVal, Host, ResNum, Device, Size));
+
+  ol_event_handle_t Event = nullptr;
+  ASSERT_SUCCESS(olCreateEvent(Queue, &Event));
+  ASSERT_SUCCESS(olSyncEvent(Event));
+
+  ASSERT_EQ(FinalResVal, NumberToAdd * LoopRange);
+
+  ASSERT_SUCCESS(olMemFree(InitValuePassed));
+  ASSERT_SUCCESS(olMemFree(ResNum));
+}
+
+// The test checks the correctness of the synchronization between queues using
+// events. Enqueueing the kernel on the queue `Q1` should produce `Result1`,
+// which is used as an initial value for the queue `Q2`. Therefore, before
+// executing any future work, `Q2` should wait for the event `Event1`, which is
+// created after submitting work on `Q1`. The required synchronization is
+// ensured by `olWaitEvents(Q2, &Event1, 1)`. If `Q2` uses the value passed as
+// `Result1` before the work on `Q1` has completed, the result of the kernel
+// enqueued on `Q2` would be incorrect.
+
+TEST_P(olLaunchKernelSingleCounterSyncEventTest, SuccessTwoQueues) {
+  void* InitValuePassed;
+  void *ResNum;
+  void *ResNum2;
+
+  size_t Size = sizeof(uint32_t);
+
+  ASSERT_SUCCESS(olMemAlloc(Device, OL_ALLOC_TYPE_DEVICE, Size, &InitValuePassed));
+  ASSERT_SUCCESS(olMemAlloc(Device, OL_ALLOC_TYPE_DEVICE, Size, &ResNum));
+  ASSERT_SUCCESS(olMemAlloc(Device, OL_ALLOC_TYPE_DEVICE, Size, &ResNum2));
+
+  uint32_t HostInitVal = 0;
+  ASSERT_SUCCESS(olMemcpy(Queue, InitValuePassed, Device, &HostInitVal, Host, Size));
+  ASSERT_SUCCESS(olMemcpy(Queue, ResNum, Device, &HostInitVal, Host, Size));
+  ASSERT_SUCCESS(olMemcpy(Queue, ResNum2, Device, &HostInitVal, Host, Size));
+  ASSERT_SUCCESS(olSyncQueue(Queue));
+
+  ol_queue_handle_t Queue2 = nullptr;
+  ASSERT_SUCCESS(olCreateQueue(Device, &Queue2));
+
+  // For the explanation of the reasoning behind particular values assigned to
+  // parameters, see the comment in the Success test from the same test suite
+  int32_t LoopRange = 1000000;
+  int32_t NumberToAdd = 2;
+
+  ArgsSingleCounter Args{LoopRange, NumberToAdd, (uint32_t *)InitValuePassed,
+                         (uint32_t *)ResNum};
+  ASSERT_SUCCESS(
+      olLaunchKernel(Queue, Device, Kernel, &Args, sizeof(Args), &LaunchArgs));
+
+  ol_event_handle_t Event = nullptr;
+  ASSERT_SUCCESS(olCreateEvent(Queue, &Event));
+  ASSERT_SUCCESS(olWaitEvents(Queue2, &Event, 1));
+  ArgsSingleCounter Args2{LoopRange, NumberToAdd, (uint32_t *)ResNum,
+                          (uint32_t *)ResNum2};
+
+  // At the beginning of the kernel, ResNum from the first queue is saved
+  // locally as the initial value. If operations enqueued before Event have not
+  // been completed by the time the kernel is executed using the second queue,
+  // the FinalResVal would be incorrect.
+  ASSERT_SUCCESS(olLaunchKernel(Queue2, Device, Kernel, &Args2, sizeof(Args2),
+                                &LaunchArgs));
+
+  ASSERT_SUCCESS(olSyncQueue(Queue2));
+  uint32_t FinalResVal = 0;
+  ASSERT_SUCCESS(olMemcpy(Queue, &FinalResVal, Host, ResNum2, Device, Size));
+  ASSERT_SUCCESS(olSyncQueue(Queue2));
----------------
kevinsala wrote:

The queue for the memcpy and the sync are not the same.

https://github.com/llvm/llvm-project/pull/193927


More information about the llvm-commits mailing list