Files
TheRock/rocm-systems/projects/hip-tests/catch/unit/library/library_get_global.cc
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

474 lines
17 KiB
C++

/*
* Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
*
* SPDX-License-Identifier: MIT
*/
#include <hip_test_common.hh>
#include <hip/hiprtc.h>
#include <string>
#include <vector>
namespace {
constexpr size_t kArrLen = 32;
std::vector<char> CompileSource(const std::string& code, const std::string& gpu_arch) {
hiprtcProgram prog;
HIPRTC_CHECK(hiprtcCreateProgram(&prog, code.c_str(), "code.cu", 0, nullptr, nullptr));
#ifdef __HIP_PLATFORM_AMD__
std::string offload_arch = "--offload-arch=" + gpu_arch;
#else
std::string offload_arch = "--fmad=false";
#endif
const char* opts[] = {offload_arch.c_str()};
HIPRTC_CHECK(hiprtcCompileProgram(prog, 1, opts));
size_t size;
HIPRTC_CHECK(hiprtcGetCodeSize(prog, &size));
std::vector<char> res(size, 0);
HIPRTC_CHECK(hiprtcGetCode(prog, res.data()));
HIPRTC_CHECK(hiprtcDestroyProgram(&prog));
return res;
}
// Note: variables are *defined* (with `= {0}`) rather than just declared
// `extern`. HIPRTC produces a fully-linked code object via hiprtcGetCode, and
// ld.lld inside HIPRTC rejects unresolved externals at device link time. The
// CUDA equivalent for "extern __device__ resolved at load" requires
// -fgpu-rdc + nvrtcGetBitcode + nvrtcLink*, which is a separate flow and not
// what hipLibraryGetGlobal needs to demonstrate.
const std::string kGlobalSrc =
"extern \"C\" __device__ float d_var[32] = {0};\n"
"extern \"C\" __global__ void write_d_var(float v) {\n"
" d_var[threadIdx.x] = v + threadIdx.x;\n"
"}\n"
"extern \"C\" __global__ void read_d_var(float* out) {\n"
" out[threadIdx.x] = d_var[threadIdx.x];\n"
"}\n";
// HIPRTC's source-compile path does not auto-include
// __clang_hip_runtime_wrapper.h (where the public HIP toolchain defines
// `#define __managed__ __attribute__((managed))`). Including
// <hip/hip_runtime.h> doesn't help either — the public header never defines
// the macro. So in HIPRTC kernels we must spell the underlying attribute
// directly. The offline-compiled .code path (see loadlib_co.cc /
// library_code_load.cc) does have the wrapper and can use natural
// __managed__ syntax.
const std::string kManagedSrc =
"extern \"C\" __attribute__((managed)) float m_var[32] = {0};\n"
"extern \"C\" __global__ void write_m_var(float v) {\n"
" m_var[threadIdx.x] = v + threadIdx.x;\n"
"}\n"
"extern \"C\" __global__ void read_m_var(float* out) {\n"
" out[threadIdx.x] = m_var[threadIdx.x];\n"
"}\n";
std::string GpuArch() {
hipDeviceProp_t prop;
HIP_CHECK(hipGetDeviceProperties(&prop, 0));
return prop.gcnArchName;
}
} // namespace
HIP_TEST_CASE(Unit_hipLibraryGetGlobal_Negative) {
auto code = CompileSource(kGlobalSrc, GpuArch());
hipLibrary_t lib = nullptr;
HIP_CHECK(hipLibraryLoadData(&lib, code.data(), nullptr, nullptr, 0, nullptr, nullptr, 0));
void* dptr = nullptr;
size_t bytes = 0;
SECTION("null name") {
HIP_CHECK_ERROR(hipLibraryGetGlobal(&dptr, &bytes, lib, nullptr), hipErrorInvalidValue);
}
SECTION("null library") {
HIP_CHECK_ERROR(hipLibraryGetGlobal(&dptr, &bytes, nullptr, "d_var"),
hipErrorInvalidResourceHandle);
}
SECTION("both dptr and bytes null") {
HIP_CHECK_ERROR(hipLibraryGetGlobal(nullptr, nullptr, lib, "d_var"), hipErrorInvalidValue);
}
SECTION("missing symbol") {
HIP_CHECK_ERROR(hipLibraryGetGlobal(&dptr, &bytes, lib, "no_such_symbol"), hipErrorNotFound);
}
HIP_CHECK(hipLibraryUnload(lib));
}
HIP_TEST_CASE(Unit_hipLibraryGetGlobal_Positive) {
auto code = CompileSource(kGlobalSrc, GpuArch());
hipLibrary_t lib = nullptr;
HIP_CHECK(hipLibraryLoadData(&lib, code.data(), nullptr, nullptr, 0, nullptr, nullptr, 0));
void* dptr = nullptr;
size_t bytes = 0;
HIP_CHECK(hipLibraryGetGlobal(&dptr, &bytes, lib, "d_var"));
REQUIRE(dptr != nullptr);
REQUIRE(bytes == sizeof(float) * kArrLen);
hipKernel_t writer = nullptr;
hipKernel_t reader = nullptr;
HIP_CHECK(hipLibraryGetKernel(&writer, lib, "write_d_var"));
HIP_CHECK(hipLibraryGetKernel(&reader, lib, "read_d_var"));
hipStream_t stream;
HIP_CHECK(hipStreamCreate(&stream));
float seed = 100.0f;
void* writer_args[] = {&seed};
HIP_CHECK(hipLaunchKernel(writer, 1, kArrLen, writer_args, 0, stream));
float* d_out = nullptr;
HIP_CHECK(hipMalloc(&d_out, sizeof(float) * kArrLen));
void* reader_args[] = {&d_out};
HIP_CHECK(hipLaunchKernel(reader, 1, kArrLen, reader_args, 0, stream));
HIP_CHECK(hipStreamSynchronize(stream));
std::vector<float> host_out(kArrLen, 0.0f);
HIP_CHECK(hipMemcpy(host_out.data(), d_out, sizeof(float) * kArrLen, hipMemcpyDeviceToHost));
for (size_t i = 0; i < kArrLen; ++i) {
INFO("Index: " << i);
REQUIRE(host_out[i] == seed + static_cast<float>(i));
}
HIP_CHECK(hipFree(d_out));
HIP_CHECK(hipStreamDestroy(stream));
HIP_CHECK(hipLibraryUnload(lib));
}
// Verifies the device pointer returned by hipLibraryGetGlobal works with the
// regular memory APIs (hipMemcpy / hipMemset), which is the primary
// CUDA-documented use case. Exercises both H->D and D->H directions and
// confirms a kernel sees the data written via hipMemcpy.
HIP_TEST_CASE(Unit_hipLibraryGetGlobal_HipMemcpyInterop) {
auto code = CompileSource(kGlobalSrc, GpuArch());
hipLibrary_t lib = nullptr;
HIP_CHECK(hipLibraryLoadData(&lib, code.data(), nullptr, nullptr, 0, nullptr, nullptr, 0));
void* dptr = nullptr;
size_t bytes = 0;
HIP_CHECK(hipLibraryGetGlobal(&dptr, &bytes, lib, "d_var"));
REQUIRE(dptr != nullptr);
REQUIRE(bytes == sizeof(float) * kArrLen);
hipStream_t stream;
HIP_CHECK(hipStreamCreate(&stream));
SECTION("hipMemcpy H->D writes are visible to a kernel reading the global") {
std::vector<float> host_in(kArrLen);
for (size_t i = 0; i < kArrLen; ++i) host_in[i] = static_cast<float>(i) * 7.0f + 1.0f;
HIP_CHECK(hipMemcpy(dptr, host_in.data(), bytes, hipMemcpyHostToDevice));
hipKernel_t reader = nullptr;
HIP_CHECK(hipLibraryGetKernel(&reader, lib, "read_d_var"));
float* d_out = nullptr;
HIP_CHECK(hipMalloc(&d_out, bytes));
void* args[] = {&d_out};
HIP_CHECK(hipLaunchKernel(reader, 1, kArrLen, args, 0, stream));
HIP_CHECK(hipStreamSynchronize(stream));
std::vector<float> host_out(kArrLen, 0.0f);
HIP_CHECK(hipMemcpy(host_out.data(), d_out, bytes, hipMemcpyDeviceToHost));
for (size_t i = 0; i < kArrLen; ++i) {
INFO("Index: " << i);
REQUIRE(host_out[i] == host_in[i]);
}
HIP_CHECK(hipFree(d_out));
}
SECTION("hipMemcpy D->H reads what a kernel wrote into the global") {
hipKernel_t writer = nullptr;
HIP_CHECK(hipLibraryGetKernel(&writer, lib, "write_d_var"));
float seed = 25.0f;
void* args[] = {&seed};
HIP_CHECK(hipLaunchKernel(writer, 1, kArrLen, args, 0, stream));
HIP_CHECK(hipStreamSynchronize(stream));
std::vector<float> host_out(kArrLen, 0.0f);
HIP_CHECK(hipMemcpy(host_out.data(), dptr, bytes, hipMemcpyDeviceToHost));
for (size_t i = 0; i < kArrLen; ++i) {
INFO("Index: " << i);
REQUIRE(host_out[i] == seed + static_cast<float>(i));
}
}
SECTION("hipMemset on the device pointer zeroes the global") {
HIP_CHECK(hipMemset(dptr, 0, bytes));
hipKernel_t reader = nullptr;
HIP_CHECK(hipLibraryGetKernel(&reader, lib, "read_d_var"));
float* d_out = nullptr;
HIP_CHECK(hipMalloc(&d_out, bytes));
void* args[] = {&d_out};
HIP_CHECK(hipLaunchKernel(reader, 1, kArrLen, args, 0, stream));
HIP_CHECK(hipStreamSynchronize(stream));
std::vector<float> host_out(kArrLen, 1.0f);
HIP_CHECK(hipMemcpy(host_out.data(), d_out, bytes, hipMemcpyDeviceToHost));
for (size_t i = 0; i < kArrLen; ++i) {
INFO("Index: " << i);
REQUIRE(host_out[i] == 0.0f);
}
HIP_CHECK(hipFree(d_out));
}
HIP_CHECK(hipStreamDestroy(stream));
HIP_CHECK(hipLibraryUnload(lib));
}
// Same hipMemcpy/hipMemset interop coverage for managed-var pointers.
// The returned host pointer is a managed allocation, so direct host
// dereference works and hipMemcpy with hipMemcpyDefault works too.
HIP_TEST_CASE(Unit_hipLibraryGetManaged_HipMemcpyInterop) {
CHECK_MANAGED_MEMORY_SUPPORT
auto code = CompileSource(kManagedSrc, GpuArch());
hipLibrary_t lib = nullptr;
HIP_CHECK(hipLibraryLoadData(&lib, code.data(), nullptr, nullptr, 0, nullptr, nullptr, 0));
void* host_ptr = nullptr;
size_t bytes = 0;
HIP_CHECK(hipLibraryGetManaged(&host_ptr, &bytes, lib, "m_var"));
REQUIRE(host_ptr != nullptr);
hipStream_t stream;
HIP_CHECK(hipStreamCreate(&stream));
// Stage: write via hipMemcpy(Default) into the managed buffer, then verify
// a device kernel sees those values.
std::vector<float> host_in(kArrLen);
for (size_t i = 0; i < kArrLen; ++i) host_in[i] = static_cast<float>(i) * 11.0f - 3.0f;
HIP_CHECK(hipMemcpy(host_ptr, host_in.data(), bytes, hipMemcpyDefault));
hipKernel_t reader = nullptr;
HIP_CHECK(hipLibraryGetKernel(&reader, lib, "read_m_var"));
float* d_out = nullptr;
HIP_CHECK(hipMalloc(&d_out, bytes));
void* args[] = {&d_out};
HIP_CHECK(hipLaunchKernel(reader, 1, kArrLen, args, 0, stream));
HIP_CHECK(hipStreamSynchronize(stream));
std::vector<float> dev_out(kArrLen, 0.0f);
HIP_CHECK(hipMemcpy(dev_out.data(), d_out, bytes, hipMemcpyDeviceToHost));
for (size_t i = 0; i < kArrLen; ++i) {
INFO("Index: " << i);
REQUIRE(dev_out[i] == host_in[i]);
}
HIP_CHECK(hipFree(d_out));
HIP_CHECK(hipStreamDestroy(stream));
HIP_CHECK(hipLibraryUnload(lib));
}
HIP_TEST_CASE(Unit_hipLibraryGetGlobal_OneNullParam) {
auto code = CompileSource(kGlobalSrc, GpuArch());
hipLibrary_t lib = nullptr;
HIP_CHECK(hipLibraryLoadData(&lib, code.data(), nullptr, nullptr, 0, nullptr, nullptr, 0));
SECTION("dptr null, bytes set") {
size_t bytes = 0;
HIP_CHECK(hipLibraryGetGlobal(nullptr, &bytes, lib, "d_var"));
REQUIRE(bytes == sizeof(float) * kArrLen);
}
SECTION("dptr set, bytes null") {
void* dptr = nullptr;
HIP_CHECK(hipLibraryGetGlobal(&dptr, nullptr, lib, "d_var"));
REQUIRE(dptr != nullptr);
}
HIP_CHECK(hipLibraryUnload(lib));
}
HIP_TEST_CASE(Unit_hipLibraryGetManaged_Negative) {
CHECK_MANAGED_MEMORY_SUPPORT
auto code = CompileSource(kManagedSrc, GpuArch());
hipLibrary_t lib = nullptr;
HIP_CHECK(hipLibraryLoadData(&lib, code.data(), nullptr, nullptr, 0, nullptr, nullptr, 0));
void* dptr = nullptr;
size_t bytes = 0;
SECTION("null name") {
HIP_CHECK_ERROR(hipLibraryGetManaged(&dptr, &bytes, lib, nullptr), hipErrorInvalidValue);
}
SECTION("null library") {
HIP_CHECK_ERROR(hipLibraryGetManaged(&dptr, &bytes, nullptr, "m_var"),
hipErrorInvalidResourceHandle);
}
SECTION("both dptr and bytes null") {
HIP_CHECK_ERROR(hipLibraryGetManaged(nullptr, nullptr, lib, "m_var"), hipErrorInvalidValue);
}
SECTION("missing symbol") {
HIP_CHECK_ERROR(hipLibraryGetManaged(&dptr, &bytes, lib, "no_such_symbol"), hipErrorNotFound);
}
HIP_CHECK(hipLibraryUnload(lib));
}
HIP_TEST_CASE(Unit_hipLibraryGetManaged_Positive) {
CHECK_MANAGED_MEMORY_SUPPORT
auto code = CompileSource(kManagedSrc, GpuArch());
hipLibrary_t lib = nullptr;
HIP_CHECK(hipLibraryLoadData(&lib, code.data(), nullptr, nullptr, 0, nullptr, nullptr, 0));
void* host_ptr = nullptr;
size_t bytes = 0;
HIP_CHECK(hipLibraryGetManaged(&host_ptr, &bytes, lib, "m_var"));
REQUIRE(host_ptr != nullptr);
REQUIRE(bytes == sizeof(float) * kArrLen);
hipKernel_t writer = nullptr;
hipKernel_t reader = nullptr;
HIP_CHECK(hipLibraryGetKernel(&writer, lib, "write_m_var"));
HIP_CHECK(hipLibraryGetKernel(&reader, lib, "read_m_var"));
hipStream_t stream;
HIP_CHECK(hipStreamCreate(&stream));
// Regression test for PR #1517 lazy-allocation bug:
// immediately after hipLibraryGetManaged, launch a kernel that reads m_var.
// The device-side managed-pointer slot must already be patched at library
// build time (DynCO::initDynManagedVars). If it is null the kernel will fault.
float seed = 50.0f;
void* writer_args[] = {&seed};
HIP_CHECK(hipLaunchKernel(writer, 1, kArrLen, writer_args, 0, stream));
HIP_CHECK(hipStreamSynchronize(stream));
// Read managed memory directly from host after kernel finished.
auto* host_view = static_cast<float*>(host_ptr);
for (size_t i = 0; i < kArrLen; ++i) {
INFO("Host read after device write, index: " << i);
REQUIRE(host_view[i] == seed + static_cast<float>(i));
}
// Also exercise host-write → device-read direction.
for (size_t i = 0; i < kArrLen; ++i) {
host_view[i] = static_cast<float>(i) * 3.0f;
}
float* d_out = nullptr;
HIP_CHECK(hipMalloc(&d_out, sizeof(float) * kArrLen));
void* reader_args[] = {&d_out};
HIP_CHECK(hipLaunchKernel(reader, 1, kArrLen, reader_args, 0, stream));
HIP_CHECK(hipStreamSynchronize(stream));
std::vector<float> dev_out(kArrLen, 0.0f);
HIP_CHECK(hipMemcpy(dev_out.data(), d_out, sizeof(float) * kArrLen, hipMemcpyDeviceToHost));
for (size_t i = 0; i < kArrLen; ++i) {
INFO("Device read after host write, index: " << i);
REQUIRE(dev_out[i] == static_cast<float>(i) * 3.0f);
}
HIP_CHECK(hipFree(d_out));
HIP_CHECK(hipStreamDestroy(stream));
HIP_CHECK(hipLibraryUnload(lib));
}
// Coverage for hipLibraryGetKernel / hipLibraryGetKernelCount /
// hipLibraryEnumerateKernels exercised against a code object that also
// contains __device__ globals — guards the LibraryContainer refactor that
// now sources kernels from the underlying DynCO instead of an internal
// FatBinaryInfo + Function map.
HIP_TEST_CASE(Unit_hipLibraryGetKernel_CountAndEnumerate) {
auto code = CompileSource(kGlobalSrc, GpuArch());
hipLibrary_t lib = nullptr;
HIP_CHECK(hipLibraryLoadData(&lib, code.data(), nullptr, nullptr, 0, nullptr, nullptr, 0));
// kGlobalSrc defines two kernels (write_d_var, read_d_var).
unsigned int count = 0;
HIP_CHECK(hipLibraryGetKernelCount(&count, lib));
REQUIRE(count == 2);
hipKernel_t writer = nullptr;
hipKernel_t reader = nullptr;
HIP_CHECK(hipLibraryGetKernel(&writer, lib, "write_d_var"));
HIP_CHECK(hipLibraryGetKernel(&reader, lib, "read_d_var"));
REQUIRE(writer != nullptr);
REQUIRE(reader != nullptr);
REQUIRE(writer != reader);
// Cached lookup: asking for the same name again must return the same handle.
hipKernel_t writer_again = nullptr;
HIP_CHECK(hipLibraryGetKernel(&writer_again, lib, "write_d_var"));
REQUIRE(writer_again == writer);
// Missing name must report not found.
hipKernel_t missing = nullptr;
HIP_CHECK_ERROR(hipLibraryGetKernel(&missing, lib, "nonexistent_kernel"), hipErrorNotFound);
// EnumerateKernels must return both handles (order is implementation-defined).
hipKernel_t enumerated[2] = {nullptr, nullptr};
HIP_CHECK(hipLibraryEnumerateKernels(enumerated, 2, lib));
REQUIRE(enumerated[0] != nullptr);
REQUIRE(enumerated[1] != nullptr);
REQUIRE(enumerated[0] != enumerated[1]);
const bool covers_writer = (enumerated[0] == writer) || (enumerated[1] == writer);
const bool covers_reader = (enumerated[0] == reader) || (enumerated[1] == reader);
REQUIRE(covers_writer);
REQUIRE(covers_reader);
HIP_CHECK(hipLibraryUnload(lib));
}
// Confirms hipLibraryGetGlobal and hipLibraryGetKernel are interchangeable in
// either order — both trigger the lazy BuildIt, and DynCO populates kernels
// and globals together in loadCodeObject. Either path must leave the other
// operational.
HIP_TEST_CASE(Unit_hipLibraryGetKernel_OrderingWithGetGlobal) {
SECTION("GetGlobal first, then GetKernel") {
auto code = CompileSource(kGlobalSrc, GpuArch());
hipLibrary_t lib = nullptr;
HIP_CHECK(hipLibraryLoadData(&lib, code.data(), nullptr, nullptr, 0, nullptr, nullptr, 0));
void* dptr = nullptr;
size_t bytes = 0;
HIP_CHECK(hipLibraryGetGlobal(&dptr, &bytes, lib, "d_var"));
hipKernel_t reader = nullptr;
HIP_CHECK(hipLibraryGetKernel(&reader, lib, "read_d_var"));
REQUIRE(reader != nullptr);
HIP_CHECK(hipLibraryUnload(lib));
}
SECTION("GetKernel first, then GetGlobal") {
auto code = CompileSource(kGlobalSrc, GpuArch());
hipLibrary_t lib = nullptr;
HIP_CHECK(hipLibraryLoadData(&lib, code.data(), nullptr, nullptr, 0, nullptr, nullptr, 0));
hipKernel_t writer = nullptr;
HIP_CHECK(hipLibraryGetKernel(&writer, lib, "write_d_var"));
REQUIRE(writer != nullptr);
void* dptr = nullptr;
size_t bytes = 0;
HIP_CHECK(hipLibraryGetGlobal(&dptr, &bytes, lib, "d_var"));
REQUIRE(dptr != nullptr);
REQUIRE(bytes == sizeof(float) * kArrLen);
HIP_CHECK(hipLibraryUnload(lib));
}
}
HIP_TEST_CASE(Unit_hipLibraryGet_CrossKindRejection) {
CHECK_MANAGED_MEMORY_SUPPORT
// Source has both a __device__ var (d_var) and a __managed__ var (m_var).
const std::string src = kGlobalSrc + kManagedSrc;
auto code = CompileSource(src, GpuArch());
hipLibrary_t lib = nullptr;
HIP_CHECK(hipLibraryLoadData(&lib, code.data(), nullptr, nullptr, 0, nullptr, nullptr, 0));
void* dptr = nullptr;
size_t bytes = 0;
// GetManaged on a __device__ variable must fail.
HIP_CHECK_ERROR(hipLibraryGetManaged(&dptr, &bytes, lib, "d_var"), hipErrorNotFound);
// GetGlobal on a __managed__ variable returns the managed host pointer
// (matching CUDA's cuLibraryGetGlobal behavior on managed symbols).
HIP_CHECK(hipLibraryGetGlobal(&dptr, &bytes, lib, "m_var"));
REQUIRE(dptr != nullptr);
REQUIRE(bytes == sizeof(float) * kArrLen);
HIP_CHECK(hipLibraryUnload(lib));
}