[llvm] [offload] add tests for event synchronization (PR #193927)
via llvm-commits
llvm-commits at lists.llvm.org
Tue May 5 01:44:41 PDT 2026
https://github.com/EuphoricThinking updated https://github.com/llvm/llvm-project/pull/193927
>From fe4fd0e2503552302a4380ae82623ba4a77910b9 Mon Sep 17 00:00:00 2001
From: Agata Momot <agata.momot at intel.com>
Date: Tue, 31 Mar 2026 08:24:25 +0000
Subject: [PATCH] [offload] add tests for the events synchronization
This patch adds:
- a new kernel `void single_counter(int32_t init_loop, int32_t addend,
uint32_t *init_val, uint32_t *out)`
- a new test suite `olLaunchKernelSingleCounterSyncEventTest`, with
two tests
The test `SuccessSyncEvent` verifies whether `olSyncEvent(Event)`
awaits the completion of `Event`.
The test `SuccessTwoQueues` 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.
---
.../OffloadAPI/device_code/CMakeLists.txt | 2 +
.../OffloadAPI/device_code/single_counter.cpp | 23 ++++
.../OffloadAPI/kernel/olLaunchKernel.cpp | 120 ++++++++++++++++++
3 files changed, 145 insertions(+)
create mode 100644 offload/unittests/OffloadAPI/device_code/single_counter.cpp
diff --git a/offload/unittests/OffloadAPI/device_code/CMakeLists.txt b/offload/unittests/OffloadAPI/device_code/CMakeLists.txt
index 22ebacf62e83e..57f529e1a4ae6 100644
--- a/offload/unittests/OffloadAPI/device_code/CMakeLists.txt
+++ b/offload/unittests/OffloadAPI/device_code/CMakeLists.txt
@@ -7,6 +7,7 @@ add_offload_test_device_code(byte.cpp byte)
add_offload_test_device_code(localmem.cpp localmem)
add_offload_test_device_code(localmem_reduction.cpp localmem_reduction)
add_offload_test_device_code(localmem_static.cpp localmem_static)
+add_offload_test_device_code(single_counter.cpp single_counter)
add_offload_test_device_code(global.cpp global)
add_offload_test_device_code(global_ctor.cpp global_ctor)
add_offload_test_device_code(global_dtor.cpp global_dtor)
@@ -21,6 +22,7 @@ add_custom_target(offload_device_binaries DEPENDS
localmem.bin
localmem_reduction.bin
localmem_static.bin
+ single_counter.bin
global.bin
global_ctor.bin
global_dtor.bin
diff --git a/offload/unittests/OffloadAPI/device_code/single_counter.cpp b/offload/unittests/OffloadAPI/device_code/single_counter.cpp
new file mode 100644
index 0000000000000..54a5590f49dcf
--- /dev/null
+++ b/offload/unittests/OffloadAPI/device_code/single_counter.cpp
@@ -0,0 +1,23 @@
+#include <gpuintrin.h>
+#include <stdint.h>
+
+extern "C" __gpu_kernel void single_counter(int32_t init_loop, int32_t addend,
+ uint32_t *init_val, uint32_t *out) {
+
+ if (__gpu_thread_id(0) != 0) {
+ return;
+ }
+
+ volatile int32_t counter = init_loop;
+
+ // We save the value at the beginning of the function in order to verify later
+ // whether the previously submitted kernel has finished before the new
+ // enqueue.
+ uint32_t local_sum = *init_val;
+
+ while (counter--) {
+ local_sum += addend;
+ }
+
+ *out = local_sum;
+}
diff --git a/offload/unittests/OffloadAPI/kernel/olLaunchKernel.cpp b/offload/unittests/OffloadAPI/kernel/olLaunchKernel.cpp
index 166b8dabff0d8..1f4b0d1faaf61 100644
--- a/offload/unittests/OffloadAPI/kernel/olLaunchKernel.cpp
+++ b/offload/unittests/OffloadAPI/kernel/olLaunchKernel.cpp
@@ -60,6 +60,7 @@ KERNEL_TEST(Byte, byte)
KERNEL_TEST(LocalMem, localmem)
KERNEL_TEST(LocalMemReduction, localmem_reduction)
KERNEL_TEST(LocalMemStatic, localmem_static)
+KERNEL_TEST(SingleCounterSyncEvent, single_counter)
KERNEL_TEST(GlobalCtor, global_ctor)
KERNEL_TEST(GlobalDtor, global_dtor)
@@ -77,6 +78,13 @@ struct LaunchMultipleKernelTestBase : LaunchKernelTestBase {
std::vector<ol_symbol_handle_t> Kernels;
};
+struct ArgsSingleCounter {
+ int32_t InitLoop;
+ int32_t Addend;
+ uint32_t *InitVal;
+ uint32_t *Out;
+};
+
#define KERNEL_MULTI_TEST(NAME, PROGRAM, ...) \
struct olLaunchKernel##NAME##Test : LaunchMultipleKernelTestBase { \
void SetUp() override { SetUpKernels(#PROGRAM, {__VA_ARGS__}); } \
@@ -240,6 +248,118 @@ 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 *ResNum1;
+ 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, &ResNum1));
+ 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, ResNum1, 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 *)ResNum1};
+ 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 *)ResNum1,
+ (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(Queue2, &FinalResVal, Host, ResNum2, Device, Size));
+ ASSERT_SUCCESS(olSyncQueue(Queue2));
+
+ ASSERT_EQ(FinalResVal, 2 * NumberToAdd * LoopRange);
+
+ ASSERT_SUCCESS(olMemFree(InitValuePassed));
+ ASSERT_SUCCESS(olMemFree(ResNum1));
+ ASSERT_SUCCESS(olMemFree(ResNum2));
+}
+
TEST_P(olLaunchKernelGlobalTest, Success) {
void *Mem;
ASSERT_SUCCESS(olMemAlloc(Device, OL_ALLOC_TYPE_MANAGED,
More information about the llvm-commits
mailing list