Files
OpenCL-CTS/test_conformance/SVM/test_unified_svm_free.cpp
Ben Ashbaugh 089e02cdf7 add system SVM testing via clSVMAllocWithProperties (#2516)
This PR adds system SVM testing both using driver APIs
(`clSVMAllocWithPropertiesKHR`) and the system allocator directly (e.g.
`malloc`). This is done by finding all of the SVM capabilities that are
"system allocated" and duplicating them with a special "use system
allocator" pseudo-capability. When the "use system allocator"
pseudo-capability is not present, the system SVM type is treated the
same as all other unified SVM types and is tested using driver APIs.
When the "use system allocator" pseudo-capability is present, the system
SVM type is allocated using the system allocator directly, though this
also adds some limitations, for example the properties of the allocation
may not be queried using `clGetSVMPointerInfoKHR`.

See discussion in:
https://github.com/KhronosGroup/OpenCL-Docs/issues/1446
2025-10-14 10:07:32 -07:00

261 lines
7.6 KiB
C++

//
// Copyright (c) 2025 The Khronos Group Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#include "unified_svm_fixture.h"
#include "harness/conversions.h"
#include "harness/testHarness.h"
#include "harness/typeWrappers.h"
#include <atomic>
#include <chrono>
#include <vector>
#include <thread>
namespace {
struct CallbackData
{
CallbackData(cl_context ctx, std::vector<cl_svm_capabilities_khr> &caps)
: context{ ctx }, status{ 0 }, svm_pointers{}, svm_caps{ caps }
{}
cl_context context;
std::atomic<cl_uint> status;
std::vector<void *> svm_pointers;
std::vector<cl_svm_capabilities_khr> &svm_caps;
};
// callback which will be passed to clEnqueueSVMFree command
void CL_CALLBACK callback_svm_free(cl_command_queue queue,
cl_uint num_svm_pointers,
void *svm_pointers[], void *user_data)
{
auto data = (CallbackData *)user_data;
data->svm_pointers.resize(num_svm_pointers, 0);
for (size_t i = 0; i < num_svm_pointers; ++i)
{
data->svm_pointers[i] = svm_pointers[i];
if (data->svm_caps[i] & PSEUDO_CAPABILITY_USE_SYSTEM_ALLOCATOR)
{
align_free(data);
}
else
{
clSVMFree(data->context, svm_pointers[i]);
}
}
data->status.store(1, std::memory_order_release);
}
void log_error_usvm_ptrs(const std::vector<void *> &v)
{
for (size_t i = 0; i < v.size(); ++i)
{
log_error("\t%zu: %p\n", i, v[i]);
}
}
}
struct UnifiedSVMFree : UnifiedSVMBase
{
using UnifiedSVMBase::UnifiedSVMBase;
// Test the clEnqueueSVMFree function for a vector of USM pointers
// and validate the callback.
cl_int
test_SVMFreeCallback(std::vector<void *> &buffers,
std::vector<cl_svm_capabilities_khr> &bufferCaps)
{
cl_int err = CL_SUCCESS;
clEventWrapper event;
CallbackData data{ context, bufferCaps };
err = clEnqueueSVMFree(queue, buffers.size(), buffers.data(),
callback_svm_free, &data, 0, 0, &event);
test_error(err, "clEnqueueSVMFree failed");
err = clFinish(queue);
test_error(err, "clFinish failed");
err = check_event_type(event, CL_COMMAND_SVM_FREE);
test_error(err, "Invalid command type returned for clEnqueueSVMFree");
// wait for the callback
while (data.status.load(std::memory_order_acquire) == 0)
{
std::this_thread::sleep_for(std::chrono::microseconds(1));
}
// check if pointers returned in callback are correct
if (data.svm_pointers != buffers)
{
log_error("Invalid SVM pointer returned in the callback \n");
log_error("Expected:\n");
log_error_usvm_ptrs(buffers);
log_error("Got:\n");
log_error_usvm_ptrs(data.svm_pointers);
return TEST_FAIL;
}
return CL_SUCCESS;
}
cl_int test_SVMFree(std::vector<void *> &buffers)
{
cl_int err = CL_SUCCESS;
clEventWrapper event;
err = clEnqueueSVMFree(queue, buffers.size(), buffers.data(), nullptr,
nullptr, 0, 0, &event);
test_error(err, "clEnqueueSVMFree failed");
err = clFinish(queue);
test_error(err, "clFinish failed");
err = check_event_type(event, CL_COMMAND_SVM_FREE);
test_error(err, "Invalid command type returned for clEnqueueSVMFree");
return CL_SUCCESS;
}
cl_int run() override
{
cl_int err;
// Test clEnqueueSVMFree function with a callback
for (int it = 0; it < test_iterations; it++)
{
std::vector<void *> buffers;
std::vector<cl_svm_capabilities_khr> bufferCaps;
size_t numSVMBuffers = get_random_size_t(1, 20, d);
for (int i = 0; i < numSVMBuffers; i++)
{
size_t typeIndex =
get_random_size_t(0, deviceUSVMCaps.size() - 1, d);
auto mem = get_usvm_wrapper<cl_uchar>(typeIndex);
err = mem->allocate(alloc_count);
test_error(err, "SVM allocation failed");
buffers.push_back(mem->get_ptr());
bufferCaps.push_back(deviceUSVMCaps[typeIndex]);
mem->reset();
}
err = test_SVMFreeCallback(buffers, bufferCaps);
test_error(err, "test_SVMFree");
}
// We need to filter out the SVM types that are system allocated
// as we cannot test clEnqueueSVMFree without a callback for them
std::vector<size_t> test_indexes;
for (size_t i = 0; i < deviceUSVMCaps.size(); i++)
{
auto caps = deviceUSVMCaps[i];
if (0 == (caps & PSEUDO_CAPABILITY_USE_SYSTEM_ALLOCATOR))
{
test_indexes.push_back(i);
}
}
if (!test_indexes.empty())
{
// Test clEnqueueSVMFree function with no callback
for (int it = 0; it < test_iterations; it++)
{
std::vector<void *> buffers;
size_t numSVMBuffers = get_random_size_t(1, 20, d);
while (buffers.size() != numSVMBuffers)
{
size_t test_index =
get_random_size_t(0, test_indexes.size() - 1, d);
size_t typeIndex = test_indexes[test_index];
auto mem = get_usvm_wrapper<cl_uchar>(typeIndex);
err = mem->allocate(alloc_count);
test_error(err, "SVM allocation failed");
buffers.push_back(mem->get_ptr());
mem->reset();
}
err = test_SVMFree(buffers);
test_error(err, "test_SVMFree");
}
}
return CL_SUCCESS;
}
static constexpr size_t alloc_count = 1024;
static constexpr size_t test_iterations = 100;
};
REGISTER_TEST(unified_svm_free)
{
if (!is_extension_available(device, "cl_khr_unified_svm"))
{
log_info("cl_khr_unified_svm is not supported, skipping test.\n");
return TEST_SKIPPED_ITSELF;
}
cl_int err;
clContextWrapper contextWrapper;
clCommandQueueWrapper queueWrapper;
// For now: create a new context and queue.
// If we switch to a new test executable and run the tests without
// forceNoContextCreation then this can be removed, and we can just use the
// context and the queue from the harness.
if (context == nullptr)
{
contextWrapper =
clCreateContext(nullptr, 1, &device, nullptr, nullptr, &err);
test_error(err, "clCreateContext failed");
context = contextWrapper;
}
if (queue == nullptr)
{
queueWrapper = clCreateCommandQueue(context, device, 0, &err);
test_error(err, "clCreateCommandQueue failed");
queue = queueWrapper;
}
UnifiedSVMFree Test(context, device, queue, num_elements);
err = Test.setup();
test_error(err, "test setup failed");
err = Test.run();
test_error(err, "test failed");
return TEST_PASS;
}