/* * Copyright (c) Advanced Micro Devices, Inc., or its affiliates. * * SPDX-License-Identifier: MIT */ /* hipArrayCreate API test scenarios 1. Negative Scenarios 2. Allocating Small and big chunk data 3. Multithreaded scenario */ #include #include #include #include #include "hipArrayCommon.hh" #include "DriverContext.hh" static constexpr size_t NUM_W{4}; static constexpr size_t BIGNUM_W{100}; static constexpr size_t NUM_H{4}; static constexpr size_t BIGNUM_H{100}; static constexpr auto ARRAY_LOOP{100}; /* * This API verifies memory allocations for small and * bigger chunks of data. * Two scenarios are verified in this API * 1. SmallArray: Allocates NUM_W*NUM_H in a loop and * releases the memory. * 2. BigArray: Allocates BIGNUM_W*BIGNUM_H in a loop and * releases the memory. * */ static void ArrayCreate_DiffSizes(int gpu) { HIP_CHECK_THREAD(hipSetDevice(gpu)); // Use of GENERATE in thead function causes random failures with multithread condition. std::vector> runs{std::make_pair(NUM_W, NUM_H), std::make_pair(BIGNUM_W, BIGNUM_H)}; for (const auto& size : runs) { std::array array; size_t pavail; HIP_CHECK_THREAD(hipMemGetInfo(&pavail, nullptr)); HIP_ARRAY_DESCRIPTOR desc; desc.NumChannels = 1; desc.Width = std::get<0>(size); desc.Height = std::get<1>(size); desc.Format = HIP_AD_FORMAT_FLOAT; for (int i = 0; i < ARRAY_LOOP; i++) { HIP_CHECK_THREAD(hipArrayCreate(&array[i], &desc)); } for (int i = 0; i < ARRAY_LOOP; i++) { HIP_CHECK_THREAD(hipArrayDestroy(array[i])); } } } /* This testcase verifies hipArrayCreate API for small and big chunks data*/ HIP_TEST_CASE(Unit_hipArrayCreate_DiffSizes) { CHECK_IMAGE_SUPPORT ArrayCreate_DiffSizes(0); HIP_CHECK_THREAD_FINALIZE(); } /* This testcase verifies the hipArrayCreate API in multithreaded scenario by launching threads in parallel on multiple GPUs and verifies the hipArrayCreate API with small and big chunks data */ HIP_TEST_CASE(Unit_hipArrayCreate_MultiThread) { CHECK_IMAGE_SUPPORT std::vector threadlist; int devCnt = 0; devCnt = HipTest::getDeviceCount(); for (int i = 0; i < devCnt; i++) { threadlist.push_back(std::thread(ArrayCreate_DiffSizes, i)); } for (auto& t : threadlist) { t.join(); } HIP_CHECK_THREAD_FINALIZE(); } // Tests ///////////////////////////////////////// #if HT_AMD constexpr auto NORMALIZED_COORDINATES = HIP_TRSF_NORMALIZED_COORDINATES; constexpr auto READ_AS_INTEGER = HIP_TRSF_READ_AS_INTEGER; #else // (EXSWCPHIPT-92) HIP equivalents not defined for CUDA backend. constexpr auto NORMALIZED_COORDINATES = CU_TRSF_NORMALIZED_COORDINATES; constexpr auto READ_AS_INTEGER = CU_TRSF_READ_AS_INTEGER; #endif // Copy data from host to the hipArray_t, accounting 1D or 2D arrays template void copyToArray(hipArray_t dst, const std::vector& src, const size_t height) { const auto sizeInBytes = src.size() * sizeof(T); if (height == 0) { // FIXME(EXSWCPHIPT-64) remove cast when API is fixed (will require major version change) HIP_CHECK(hipMemcpyHtoA(reinterpret_cast(dst), 0, src.data(), sizeInBytes)); } else { const auto pitch = sizeInBytes / height; hip_Memcpy2D copyParams{}; copyParams.srcMemoryType = hipMemoryTypeHost; copyParams.srcXInBytes = 0; // x offset copyParams.srcY = 0; // y offset copyParams.srcHost = src.data(); copyParams.srcPitch = pitch; copyParams.dstMemoryType = hipMemoryTypeArray; copyParams.dstXInBytes = 0; // x offset copyParams.dstY = 0; // y offset copyParams.dstArray = dst; copyParams.WidthInBytes = pitch; copyParams.Height = height; HIP_CHECK(hipMemcpyParam2D(©Params)); } } // Test the allocated array by generating a texture from it then reading from that texture. // Textures are read-only, so write to the array then copy that into normal device memory. template void testArrayAsTexture(hipArray_t array, const size_t width, const size_t height) { using vec_info = vector_info; using scalar_type = typename vec_info::type; const auto h = height ? height : 1; const auto size = sizeof(T) * width * h; // set hip array std::vector hostData(width * h * vec_info::size); // assigned ascending values to the data array to show indexing is working std::iota(std::begin(hostData), std::end(hostData), 0); copyToArray(array, hostData, height); // create texture hipTextureObject_t textObj{}; HIP_RESOURCE_DESC resDesc{}; memset(&resDesc, 0, sizeof(HIP_RESOURCE_DESC)); resDesc.resType = HIP_RESOURCE_TYPE_ARRAY; resDesc.res.array.hArray = array; resDesc.flags = 0; HIP_TEXTURE_DESC texDesc{}; memset(&texDesc, 0, sizeof(HIP_TEXTURE_DESC)); // use the actual values in the texture, not normalized data texDesc.filterMode = HIP_TR_FILTER_MODE_POINT; // Use normalized coordinates and also read the data in the original data type texDesc.flags |= NORMALIZED_COORDINATES | READ_AS_INTEGER; HIP_CHECK(hipTexObjectCreate(&textObj, &resDesc, &texDesc, nullptr)); // run kernel T* device_data{}; HIP_CHECK(hipMalloc(&device_data, size)); readFromTexture<<>>(device_data, textObj, width, height, false); HIP_CHECK(hipGetLastError()); // check for errors when running the kernel // copy data back and then test it std::fill(std::begin(hostData), std::end(hostData), 0); HIP_CHECK(hipMemcpy(hostData.data(), device_data, size, hipMemcpyDeviceToHost)); #if !__HIP_NO_IMAGE_SUPPORT checkDataIsAscending(hostData); #endif // clean up HIP_CHECK(hipTexObjectDestroy(textObj)); HIP_CHECK(hipFree(device_data)); } // Selection of types chosen since trying all types would be slow to compile // Test the happy path of the hipArrayCreate HIP_TEMPLATE_TEST_CASE(Unit_hipArrayCreate_happy, uint, int, int4, ushort, short2, char, uchar2, char4, float, float2, float4) { CHECK_IMAGE_SUPPORT using vec_info = vector_info; DriverContext ctx; HIP_ARRAY_DESCRIPTOR desc; desc.Format = vec_info::format; desc.NumChannels = vec_info::size; desc.Width = 1024; desc.Height = GENERATE(0, 1024); // pointer to the array in device memory hipArray_t array{}; HIP_CHECK(hipArrayCreate(&array, &desc)); testArrayAsTexture(array, desc.Width, desc.Height); HIP_CHECK(hipArrayDestroy(array)); } // Only widths and Heights up to the maxTexture size is supported HIP_TEMPLATE_TEST_CASE(Unit_hipArrayCreate_maxTexture, uint, int, int4, ushort, short2, char, uchar2, char4, float, float2, float4) { CHECK_IMAGE_SUPPORT using vec_info = vector_info; DriverContext ctx; HIP_ARRAY_DESCRIPTOR desc; desc.Format = vec_info::format; desc.NumChannels = vec_info::size; const Sizes sizes(hipArrayDefault); const size_t s = 64; hipArray_t array{}; SECTION("Happy") { SECTION("1D - Max") { desc.Width = sizes.max1D; desc.Height = 0; } SECTION("2D - Max Width") { desc.Width = sizes.max2D[0]; desc.Height = s; } SECTION("2D - Max Height") { desc.Width = s; desc.Height = sizes.max2D[1]; } SECTION("2D - Max Width and Height") { desc.Width = sizes.max2D[0]; desc.Height = sizes.max2D[1]; } auto maxArrayCreateError = hipArrayCreate(&array, &desc); // this can try to alloc many GB of memory, so out of memory is acceptable // return to avoid destroy if (maxArrayCreateError == hipErrorOutOfMemory) return; HIP_CHECK(maxArrayCreateError); HIP_CHECK(hipArrayDestroy(array)); } SECTION("Negative") { SECTION("1D - More Than Max") { desc.Width = sizes.max1D + 1; desc.Height = 0; } SECTION("2D - More Than Max Width") { desc.Width = sizes.max2D[0] + 1; desc.Height = s; } SECTION("2D - More Than Max Height") { desc.Width = s; desc.Height = sizes.max2D[1] + 1; } SECTION("2D - More Than Max Width and Height") { desc.Width = sizes.max2D[0] + 1; desc.Height = sizes.max2D[1] + 1; } HIP_CHECK_ERROR(hipArrayCreate(&array, &desc), hipErrorInvalidValue); } } // zero-width array is not supported HIP_TEST_CASE(Unit_hipArrayCreate_ZeroWidth) { CHECK_IMAGE_SUPPORT DriverContext ctx; HIP_ARRAY_DESCRIPTOR desc; desc.Format = driverFormats[0]; desc.NumChannels = 4; desc.Width = 0; desc.Height = GENERATE(0, 1024); // pointer to the array in device memory hipArray_t array; HIP_CHECK_ERROR(hipArrayCreate(&array, &desc), hipErrorInvalidValue); } // HipArrayCreate will return an error when nullptr is used as the array argument HIP_TEST_CASE(Unit_hipArrayCreate_Nullptr) { CHECK_IMAGE_SUPPORT DriverContext ctx; SECTION("Null array") { HIP_ARRAY_DESCRIPTOR desc; desc.Format = driverFormats[0]; desc.NumChannels = 4; desc.Width = 1024; desc.Height = 1024; HIP_CHECK_ERROR(hipArrayCreate(nullptr, &desc), hipErrorInvalidValue); } SECTION("Null Description") { hipArray_t array; HIP_CHECK_ERROR(hipArrayCreate(&array, nullptr), hipErrorInvalidValue); } } // Only elements with 1,2, or 4 channels is supported HIP_TEST_CASE(Unit_hipArrayCreate_BadNumberChannelElement) { CHECK_IMAGE_SUPPORT DriverContext ctx; HIP_ARRAY_DESCRIPTOR desc; desc.Format = GENERATE(from_range(std::begin(driverFormats), std::end(driverFormats))); desc.NumChannels = GENERATE(-1, 0, 3, 5, 8); desc.Width = 1024; desc.Height = GENERATE(0, 1024); hipArray_t array; INFO("Format: " << formatToString(desc.Format) << " NumChannels: " << desc.NumChannels << " Height: " << desc.Height); HIP_CHECK_ERROR(hipArrayCreate(&array, &desc), hipErrorInvalidValue); } // Only certain channel formats are acceptable. HIP_TEST_CASE(Unit_hipArrayCreate_BadChannelFormat) { CHECK_IMAGE_SUPPORT DriverContext ctx; HIP_ARRAY_DESCRIPTOR desc; // create a bad format desc.Format = std::accumulate(std::begin(driverFormats), std::end(driverFormats), driverFormats[0], [](auto i, auto f) { return static_cast(i + f); }); for (auto&& format : driverFormats) { REQUIRE(desc.Format != format); } desc.NumChannels = 4; desc.Width = 1024; desc.Height = GENERATE(0, 1024); hipArray_t array; INFO("Format: " << formatToString(desc.Format) << " Height: " << desc.Height); HIP_CHECK_ERROR(hipArrayCreate(&array, &desc), hipErrorInvalidValue); }