Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[OpenMP] Add unit tests for nextgen plugins #74398

Merged
merged 4 commits into from
Dec 20, 2023

Conversation

EthanLuisMcDonough
Copy link
Member

This patch add three GTest unit tests that test plugin read and write operations. Tests can be compiled with ninja -C runtimes/runtimes-bins LibomptUnitTests.

@llvmbot llvmbot added the openmp:libomptarget OpenMP offload runtime label Dec 5, 2023
@llvmbot
Copy link
Collaborator

llvmbot commented Dec 5, 2023

@llvm/pr-subscribers-openmp

Author: Ethan Luis McDonough (EthanLuisMcDonough)

Changes

This patch add three GTest unit tests that test plugin read and write operations. Tests can be compiled with ninja -C runtimes/runtimes-bins LibomptUnitTests.


Full diff: https://github.com/llvm/llvm-project/pull/74398.diff

4 Files Affected:

  • (modified) openmp/libomptarget/CMakeLists.txt (+6)
  • (added) openmp/libomptarget/unittests/CMakeLists.txt (+8)
  • (added) openmp/libomptarget/unittests/Plugins/CMakeLists.txt (+26)
  • (added) openmp/libomptarget/unittests/Plugins/NextgenPluginsTest.cpp (+146)
diff --git a/openmp/libomptarget/CMakeLists.txt b/openmp/libomptarget/CMakeLists.txt
index 5f592f46c8ffa..6a474469ab87a 100644
--- a/openmp/libomptarget/CMakeLists.txt
+++ b/openmp/libomptarget/CMakeLists.txt
@@ -123,3 +123,9 @@ add_subdirectory(tools)
 
 # Add tests.
 add_subdirectory(test)
+
+# Add unit tests if GMock/GTest is present
+if (EXISTS ${LLVM_THIRD_PARTY_DIR}/unittest)
+  add_subdirectory(${LLVM_THIRD_PARTY_DIR}/unittest ${CMAKE_CURRENT_BINARY_DIR}/third-party/unittest)
+  add_subdirectory(unittests)
+endif()
diff --git a/openmp/libomptarget/unittests/CMakeLists.txt b/openmp/libomptarget/unittests/CMakeLists.txt
new file mode 100644
index 0000000000000..0a29afd68569a
--- /dev/null
+++ b/openmp/libomptarget/unittests/CMakeLists.txt
@@ -0,0 +1,8 @@
+add_custom_target(LibomptUnitTests)
+set_target_properties(LibomptUnitTests PROPERTIES FOLDER "Libomptarget tests")
+
+function(add_libompt_unittest test_dirname)
+  add_unittest(LibomptUnitTests ${test_dirname} ${ARGN})
+endfunction()
+
+add_subdirectory(Plugins)
diff --git a/openmp/libomptarget/unittests/Plugins/CMakeLists.txt b/openmp/libomptarget/unittests/Plugins/CMakeLists.txt
new file mode 100644
index 0000000000000..234ba699c4834
--- /dev/null
+++ b/openmp/libomptarget/unittests/Plugins/CMakeLists.txt
@@ -0,0 +1,26 @@
+set(LIBOMPTARGET_BUILD_AMDGPU_PLUGIN TRUE CACHE BOOL
+  "Whether to build AMDGPU plugin")
+set(LIBOMPTARGET_BUILD_CUDA_PLUGIN TRUE CACHE BOOL
+  "Whether to build CUDA plugin")
+
+set(PLUGINS_TEST_COMMON omptarget OMPT omptarget.devicertl)
+set(PLUGINS_TEST_SOURCES NextgenPluginsTest.cpp)
+set(PLUGINS_TEST_INCLUDE ${LIBOMPTARGET_INCLUDE_DIR})
+
+if (LIBOMPTARGET_BUILD_AMDGPU_PLUGIN)
+  libomptarget_say("Building plugin unit tests for AMDGPU")
+  add_libompt_unittest(PluginsTestAmd ${PLUGINS_TEST_SOURCES})
+  add_dependencies(PluginsTestAmd ${PLUGINS_TEST_COMMON} omptarget.rtl.amdgpu)
+  target_link_libraries(PluginsTestAmd PRIVATE ${PLUGINS_TEST_COMMON} omptarget.rtl.amdgpu)
+  target_include_directories(PluginsTestAmd PRIVATE ${PLUGINS_TEST_INCLUDE})
+else()
+  libomptarget_say("Skipping AMDGPU plugin unit tests")
+endif()
+
+if (LIBOMPTARGET_BUILD_CUDA_PLUGIN)
+  libomptarget_say("Building plugin unit tests for Cuda")
+  add_libompt_unittest(PluginsTestCu ${PLUGINS_TEST_SOURCES})
+  add_dependencies(PluginsTestCu ${PLUGINS_TEST_COMMON} omptarget.rtl.cuda)
+  target_link_libraries(PluginsTestCu PRIVATE ${PLUGINS_TEST_COMMON} omptarget.rtl.cuda)
+  target_include_directories(PluginsTestCu PRIVATE ${PLUGINS_TEST_INCLUDE})
+endif()
diff --git a/openmp/libomptarget/unittests/Plugins/NextgenPluginsTest.cpp b/openmp/libomptarget/unittests/Plugins/NextgenPluginsTest.cpp
new file mode 100644
index 0000000000000..24311076e8acd
--- /dev/null
+++ b/openmp/libomptarget/unittests/Plugins/NextgenPluginsTest.cpp
@@ -0,0 +1,146 @@
+#include "Shared/PluginAPI.h"
+#include "omptarget.h"
+#include "gtest/gtest.h"
+
+const int DEVICE_ID = 0, DEVICE_TWO = 1;
+bool setup_map[DEVICE_TWO + 1];
+
+int init_test_device(int ID) {
+  if (setup_map[ID]) {
+    return OFFLOAD_SUCCESS;
+  }
+  if (__tgt_rtl_init_plugin() == OFFLOAD_FAIL ||
+      __tgt_rtl_init_device(ID) == OFFLOAD_FAIL) {
+    return OFFLOAD_FAIL;
+  }
+  setup_map[ID] = true;
+  return OFFLOAD_SUCCESS;
+}
+
+// Test plugin initialization
+TEST(NextgenPluginsTest, PluginInit) {
+  EXPECT_EQ(OFFLOAD_SUCCESS, init_test_device(DEVICE_ID));
+}
+
+// Test GPU allocation and R/W
+TEST(NextgenPluginsTest, PluginAlloc) {
+  int32_t test_value = 23;
+  int32_t host_value = -1;
+  int64_t var_size = sizeof(int32_t);
+
+  // Init plugin and device
+  EXPECT_EQ(OFFLOAD_SUCCESS, init_test_device(DEVICE_ID));
+
+  // Allocate memory
+  void *device_ptr =
+      __tgt_rtl_data_alloc(DEVICE_ID, var_size, nullptr, TARGET_ALLOC_DEFAULT);
+
+  // Check that the result is not null
+  EXPECT_NE(device_ptr, nullptr);
+
+  // Submit data to device
+  EXPECT_EQ(OFFLOAD_SUCCESS, __tgt_rtl_data_submit(DEVICE_ID, device_ptr,
+                                                   &test_value, var_size));
+
+  // Read data from device
+  EXPECT_EQ(OFFLOAD_SUCCESS, __tgt_rtl_data_retrieve(DEVICE_ID, &host_value,
+                                                     device_ptr, var_size));
+
+  // Compare values
+  EXPECT_EQ(host_value, test_value);
+
+  // Cleanup data
+  EXPECT_EQ(OFFLOAD_SUCCESS,
+            __tgt_rtl_data_delete(DEVICE_ID, device_ptr, TARGET_ALLOC_DEFAULT));
+}
+
+// Test async GPU allocation and R/W
+TEST(NextgenPluginsTest, PluginAsyncAlloc) {
+  int32_t test_value = 47;
+  int32_t host_value = -1;
+  int64_t var_size = sizeof(int32_t);
+  __tgt_async_info info;
+
+  // Init plugin and device
+  EXPECT_EQ(OFFLOAD_SUCCESS, init_test_device(DEVICE_ID));
+
+  // Allocate memory
+  void *device_ptr =
+      __tgt_rtl_data_alloc(DEVICE_ID, var_size, nullptr, TARGET_ALLOC_DEFAULT);
+
+  // Check that the result is not null
+  EXPECT_NE(device_ptr, nullptr);
+
+  // Submit data to device asynchronously
+  EXPECT_EQ(OFFLOAD_SUCCESS,
+            __tgt_rtl_data_submit_async(DEVICE_ID, device_ptr, &test_value,
+                                        var_size, &info));
+
+  // Wait for async request to process
+  EXPECT_EQ(OFFLOAD_SUCCESS, __tgt_rtl_synchronize(DEVICE_ID, &info));
+
+  // Read data from device
+  EXPECT_EQ(OFFLOAD_SUCCESS,
+            __tgt_rtl_data_retrieve_async(DEVICE_ID, &host_value, device_ptr,
+                                          var_size, &info));
+
+  // Wait for async request to process
+  EXPECT_EQ(OFFLOAD_SUCCESS, __tgt_rtl_synchronize(DEVICE_ID, &info));
+
+  // Compare values
+  EXPECT_EQ(host_value, test_value);
+
+  // Cleanup data
+  EXPECT_EQ(OFFLOAD_SUCCESS,
+            __tgt_rtl_data_delete(DEVICE_ID, device_ptr, TARGET_ALLOC_DEFAULT));
+}
+
+// Test GPU data exchange
+TEST(NextgenPluginsTest, PluginDataSwap) {
+  int32_t test_value = 23;
+  int32_t host_value = -1;
+  int64_t var_size = sizeof(int32_t);
+
+  // Only run test if we have multiple GPUs to test
+  // GPUs must be compatible for test to work
+  if (__tgt_rtl_number_of_devices() > 1 &&
+      __tgt_rtl_is_data_exchangable(DEVICE_ID, DEVICE_TWO)) {
+    // Init both GPUs
+    EXPECT_EQ(OFFLOAD_SUCCESS, init_test_device(DEVICE_ID));
+    EXPECT_EQ(OFFLOAD_SUCCESS, init_test_device(DEVICE_TWO));
+
+    // Allocate memory on both GPUs
+    // DEVICE_ID will be the source
+    // DEVICE_TWO will be the destination
+    void *source_ptr = __tgt_rtl_data_alloc(DEVICE_ID, var_size, nullptr,
+                                            TARGET_ALLOC_DEFAULT);
+    void *dest_ptr = __tgt_rtl_data_alloc(DEVICE_TWO, var_size, nullptr,
+                                          TARGET_ALLOC_DEFAULT);
+
+    // Check for success in allocation
+    EXPECT_NE(source_ptr, nullptr);
+    EXPECT_NE(dest_ptr, nullptr);
+
+    // Write data to source
+    EXPECT_EQ(OFFLOAD_SUCCESS, __tgt_rtl_data_submit(DEVICE_ID, source_ptr,
+                                                     &test_value, var_size));
+
+    // Transfer data between devices
+    EXPECT_EQ(OFFLOAD_SUCCESS,
+              __tgt_rtl_data_exchange(DEVICE_ID, source_ptr, DEVICE_TWO,
+                                      dest_ptr, var_size));
+
+    // Read from destination device (DEVICE_TWO) memory
+    EXPECT_EQ(OFFLOAD_SUCCESS, __tgt_rtl_data_retrieve(DEVICE_TWO, &host_value,
+                                                       dest_ptr, var_size));
+
+    // Ensure match
+    EXPECT_EQ(host_value, test_value);
+
+    // Cleanup
+    EXPECT_EQ(OFFLOAD_SUCCESS, __tgt_rtl_data_delete(DEVICE_ID, source_ptr,
+                                                     TARGET_ALLOC_DEFAULT));
+    EXPECT_EQ(OFFLOAD_SUCCESS, __tgt_rtl_data_delete(DEVICE_TWO, dest_ptr,
+                                                     TARGET_ALLOC_DEFAULT));
+  }
+}

Copy link
Contributor

@jhuber6 jhuber6 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I've been wanting the ability to test stuff without relying on OpenMP codegen for awhile.

@@ -0,0 +1,8 @@
add_custom_target(LibomptUnitTests)
set_target_properties(LibomptUnitTests PROPERTIES FOLDER "Libomptarget tests")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
set_target_properties(LibomptUnitTests PROPERTIES FOLDER "Libomptarget tests")
set_target_properties(LibomptUnitTests PROPERTIES FOLDER "Tests/UnitTests")

This is a weird folder name.

set(PLUGINS_TEST_SOURCES NextgenPluginsTest.cpp)
set(PLUGINS_TEST_INCLUDE ${LIBOMPTARGET_INCLUDE_DIR})

if (LIBOMPTARGET_BUILD_AMDGPU_PLUGIN)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (LIBOMPTARGET_BUILD_AMDGPU_PLUGIN)
if(TARGET omptarget.rtl.amdgpu)

We should be able to just check the existence of the target so long as we add the unit tests after the main source.

Comment on lines 1 to 5
set(LIBOMPTARGET_BUILD_AMDGPU_PLUGIN TRUE CACHE BOOL
"Whether to build AMDGPU plugin")
set(LIBOMPTARGET_BUILD_CUDA_PLUGIN TRUE CACHE BOOL
"Whether to build CUDA plugin")

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
set(LIBOMPTARGET_BUILD_AMDGPU_PLUGIN TRUE CACHE BOOL
"Whether to build AMDGPU plugin")
set(LIBOMPTARGET_BUILD_CUDA_PLUGIN TRUE CACHE BOOL
"Whether to build CUDA plugin")

Aren't these already declared somewhere else?

add_dependencies(PluginsTestCu ${PLUGINS_TEST_COMMON} omptarget.rtl.cuda)
target_link_libraries(PluginsTestCu PRIVATE ${PLUGINS_TEST_COMMON} omptarget.rtl.cuda)
target_include_directories(PluginsTestCu PRIVATE ${PLUGINS_TEST_INCLUDE})
endif()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Realistically this should be a loop over all the targets, just have the plugin append some CMake list of enabled targets, then we can just iterate it here.

Copy link
Member

@jdoerfert jdoerfert left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I think after Joseph's comments are addressed and the licence is in, we can merge this.

#include "omptarget.h"
#include "gtest/gtest.h"

const int DEVICE_ID = 0, DEVICE_TWO = 1;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a follow up we should iterate over all devices, not hardcode 2.

}

// Test plugin initialization
TEST(NextgenPluginsTest, PluginInit) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason for not using a test fixture for this instead of the global variables?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The global variables serve to keep track of which devices have been initialized. Devices can't be re-initialized, so I think the initialized devices would have to either be tracked through static class members or exist outside the test fixture to persist across tests. I don't think there's a way to check if a device has been initialized through the functions defined in PluginAPI.h, but I definitely could be overlooking something.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense, thanks for explaining.

Copy link

github-actions bot commented Dec 15, 2023

:white_check_mark: With the latest revision this PR passed the C/C++ code formatter.

@EthanLuisMcDonough EthanLuisMcDonough changed the title [WIP] [OpenMP] Add unit tests for nextgen plugins [OpenMP] Add unit tests for nextgen plugins Dec 15, 2023
Copy link
Member

@jdoerfert jdoerfert left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with getting this in and adapting it in-tree.

@jplehr concerns?

if (EXISTS ${LLVM_THIRD_PARTY_DIR}/unittest)
add_subdirectory(${LLVM_THIRD_PARTY_DIR}/unittest ${CMAKE_CURRENT_BINARY_DIR}/third-party/unittest)
add_subdirectory(unittests)
endif()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lld just does this:
llvm_add_unittests(LLD_UNITTESTS_ADDED)
See

llvm_add_unittests(LLD_UNITTESTS_ADDED)

and

https://github.com/llvm/llvm-project/blob/394274965a119973612c25e0eaf299c8954cce94/llvm/cmake/modules/AddLLVM.cmake#L2497C2-L2497C2

We should check if we can do the same, reducing the logic we carry.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty sure that's a name defined in the same way, we could use the same name if needed.

We also need these to run w/ check-openmp or similar. Long term I'd like language tests to be all all in a different directory so we can test things like CUDA, HIP, SYCL, OpenACC, C, C++, Fortran, etc.

@jplehr
Copy link
Contributor

jplehr commented Dec 20, 2023

I'm fine with getting this in and adapting it in-tree.

@jplehr concerns?

No, I think that makes sense.

@jdoerfert jdoerfert merged commit 3c10e5b into llvm:main Dec 20, 2023
4 checks passed
@vzakhari
Copy link
Contributor

Hi @EthanLuisMcDonough, @jdoerfert, do you know what might be causing this buildbot failure https://lab.llvm.org/buildbot/#/builders/270/builds/5927/steps/6/logs/stdio?

@vvereschaka
Copy link
Contributor

vvereschaka commented Dec 21, 2023

looks like these changes break the llvm configuration when openmp is a part of LLVM_ENABLE_PROJECTS, but not in LLVM_ENABLE_RUNTIMES. It tries to create new build targets with already existing names.

  add_library cannot create target "llvm_gtest" because another target with
  the same name already exists.  The existing target is a static library
  created in source directory

and

 add_library cannot create target "llvm_gtest_main" because another target
 with the same name already exists.  The existing target is a static library
 created in source directory

https://lab.llvm.org/buildbot/#/builders/270/builds/5927/steps/6/logs/stdio

EthanLuisMcDonough added a commit that referenced this pull request Dec 21, 2023
This patch addresses an issue introduced in pull request #74398. CMake
will attempt to re-build gtest if openmp is enabled as a project (as
opposed to being enabled as a runtime). This patch adds a check that
prevents this from happening.
@EthanLuisMcDonough
Copy link
Member Author

I think #76141 should resolve this. I've my patch out locally, and it seems to fix the issue. I hope you all don't mind me immediately merging it; I didn't want to keep the buildbot failing.

@vvereschaka
Copy link
Contributor

Thank you @EthanLuisMcDonough

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
openmp:libomp OpenMP host runtime openmp:libomptarget OpenMP offload runtime openmp
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants