Ross Brunton 2c11a83691
[Offload] Add olCalculateOptimalOccupancy (#142950)
This is equivalent to `cuOccupancyMaxPotentialBlockSize`. It is
currently
only implemented on Cuda; AMDGPU and Host return unsupported.

---------

Co-authored-by: Callum Fare <callum@codeplay.com>
2025-08-19 15:16:47 +01:00

225 lines
7.8 KiB
C++

//===------- Offload API tests - gtest fixtures --==-----------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include <OffloadAPI.h>
#include <OffloadPrint.hpp>
#include <gtest/gtest.h>
#include <thread>
#include "Environment.hpp"
#pragma once
#ifndef ASSERT_SUCCESS
#define ASSERT_SUCCESS(ACTUAL) \
do { \
ol_result_t Res = ACTUAL; \
if (Res && Res->Code != OL_ERRC_SUCCESS) { \
GTEST_FAIL() << #ACTUAL " returned " << Res->Code << ": " \
<< Res->Details; \
} \
} while (0)
#endif
#ifndef ASSERT_SUCCESS_OR_UNSUPPORTED
#define ASSERT_SUCCESS_OR_UNSUPPORTED(ACTUAL) \
do { \
ol_result_t Res = ACTUAL; \
if (Res && Res->Code == OL_ERRC_UNSUPPORTED) { \
GTEST_SKIP() << #ACTUAL " returned unsupported; skipping test"; \
return; \
} else if (Res && Res->Code != OL_ERRC_SUCCESS) { \
GTEST_FAIL() << #ACTUAL " returned " << Res->Code << ": " \
<< Res->Details; \
} \
} while (0)
#endif
// TODO: rework this so the EXPECTED/ACTUAL results are readable
#ifndef ASSERT_ERROR
#define ASSERT_ERROR(EXPECTED, ACTUAL) \
do { \
ol_result_t Res = ACTUAL; \
ASSERT_TRUE(Res && (Res->Code == EXPECTED)); \
} while (0)
#endif
#ifndef ASSERT_ANY_ERROR
#define ASSERT_ANY_ERROR(ACTUAL) \
do { \
ol_result_t Res = ACTUAL; \
ASSERT_TRUE(Res); \
} while (0)
#endif
#define RETURN_ON_FATAL_FAILURE(...) \
__VA_ARGS__; \
if (this->HasFatalFailure() || this->IsSkipped()) { \
return; \
} \
(void)0
inline std::string SanitizeString(const std::string &Str) {
auto NewStr = Str;
std::replace_if(
NewStr.begin(), NewStr.end(), [](char C) { return !std::isalnum(C); },
'_');
return NewStr;
}
template <typename Fn> inline void threadify(Fn body) {
std::vector<std::thread> Threads;
for (size_t I = 0; I < 20; I++) {
Threads.emplace_back(
[&body](size_t I) {
std::string ScopeMsg{"Thread #"};
ScopeMsg.append(std::to_string(I));
SCOPED_TRACE(ScopeMsg);
body(I);
},
I);
}
for (auto &T : Threads) {
T.join();
}
}
struct OffloadTest : ::testing::Test {
ol_device_handle_t Host = TestEnvironment::getHostDevice();
};
struct OffloadDeviceTest
: OffloadTest,
::testing::WithParamInterface<TestEnvironment::Device> {
void SetUp() override {
RETURN_ON_FATAL_FAILURE(OffloadTest::SetUp());
auto DeviceParam = GetParam();
Device = DeviceParam.Handle;
if (Device == nullptr)
GTEST_SKIP() << "No available devices.";
}
ol_platform_backend_t getPlatformBackend() const {
ol_platform_handle_t Platform = nullptr;
if (olGetDeviceInfo(Device, OL_DEVICE_INFO_PLATFORM,
sizeof(ol_platform_handle_t), &Platform))
return OL_PLATFORM_BACKEND_UNKNOWN;
ol_platform_backend_t Backend;
if (olGetPlatformInfo(Platform, OL_PLATFORM_INFO_BACKEND,
sizeof(ol_platform_backend_t), &Backend))
return OL_PLATFORM_BACKEND_UNKNOWN;
return Backend;
}
ol_device_handle_t Device = nullptr;
};
struct OffloadPlatformTest : OffloadDeviceTest {
void SetUp() override {
RETURN_ON_FATAL_FAILURE(OffloadDeviceTest::SetUp());
ASSERT_SUCCESS(olGetDeviceInfo(Device, OL_DEVICE_INFO_PLATFORM,
sizeof(Platform), &Platform));
ASSERT_NE(Platform, nullptr);
}
ol_platform_handle_t Platform = nullptr;
};
// Fixture for a generic program test. If you want a different program, use
// offloadQueueTest and create your own program handle with the binary you want.
struct OffloadProgramTest : OffloadDeviceTest {
void SetUp() override { SetUpWith("foo"); }
void SetUpWith(const char *ProgramName) {
RETURN_ON_FATAL_FAILURE(OffloadDeviceTest::SetUp());
ASSERT_TRUE(
TestEnvironment::loadDeviceBinary(ProgramName, Device, DeviceBin));
ASSERT_GE(DeviceBin->getBufferSize(), 0lu);
ASSERT_SUCCESS(olCreateProgram(Device, DeviceBin->getBufferStart(),
DeviceBin->getBufferSize(), &Program));
}
void TearDown() override {
if (Program) {
olDestroyProgram(Program);
}
RETURN_ON_FATAL_FAILURE(OffloadDeviceTest::TearDown());
}
ol_program_handle_t Program = nullptr;
std::unique_ptr<llvm::MemoryBuffer> DeviceBin;
};
struct OffloadKernelTest : OffloadProgramTest {
void SetUp() override {
RETURN_ON_FATAL_FAILURE(OffloadProgramTest::SetUp());
ASSERT_SUCCESS(olGetSymbol(Program, "foo", OL_SYMBOL_KIND_KERNEL, &Kernel));
}
void TearDown() override {
RETURN_ON_FATAL_FAILURE(OffloadProgramTest::TearDown());
}
ol_symbol_handle_t Kernel = nullptr;
};
struct OffloadGlobalTest : OffloadProgramTest {
void SetUp() override {
RETURN_ON_FATAL_FAILURE(OffloadProgramTest::SetUpWith("global"));
ASSERT_SUCCESS(olGetSymbol(Program, "global",
OL_SYMBOL_KIND_GLOBAL_VARIABLE, &Global));
}
void TearDown() override {
RETURN_ON_FATAL_FAILURE(OffloadProgramTest::TearDown());
}
ol_symbol_handle_t Global = nullptr;
};
struct OffloadQueueTest : OffloadDeviceTest {
void SetUp() override {
RETURN_ON_FATAL_FAILURE(OffloadDeviceTest::SetUp());
ASSERT_SUCCESS(olCreateQueue(Device, &Queue));
}
void TearDown() override {
if (Queue) {
olDestroyQueue(Queue);
}
RETURN_ON_FATAL_FAILURE(OffloadDeviceTest::TearDown());
}
ol_queue_handle_t Queue = nullptr;
};
struct OffloadEventTest : OffloadQueueTest {
void SetUp() override {
RETURN_ON_FATAL_FAILURE(OffloadQueueTest::SetUp());
ASSERT_SUCCESS(olCreateEvent(Queue, &Event));
ASSERT_SUCCESS(olSyncQueue(Queue));
}
void TearDown() override {
if (Event)
olDestroyEvent(Event);
RETURN_ON_FATAL_FAILURE(OffloadQueueTest::TearDown());
}
ol_event_handle_t Event = nullptr;
};
#define OFFLOAD_TESTS_INSTANTIATE_DEVICE_FIXTURE(FIXTURE) \
INSTANTIATE_TEST_SUITE_P( \
, FIXTURE, ::testing::ValuesIn(TestEnvironment::getDevices()), \
[](const ::testing::TestParamInfo<TestEnvironment::Device> &info) { \
return SanitizeString(info.param.Name); \
})