#include #include #include #include #include #include using CUresult = int; using CUdevice = int; using CUcontext = void*; using CUarray = void*; using CUmodule = void*; using CUfunction = void*; using CUstream = void*; using CUsurfObject = uint64_t; struct CUDA_ARRAY3D_DESCRIPTOR { size_t Width; size_t Height; size_t Depth; uint32_t Format; uint32_t NumChannels; uint32_t Flags; }; static_assert(sizeof(CUDA_ARRAY3D_DESCRIPTOR) == 40); struct CUDA_RESOURCE_DESC { uint32_t resType; uint32_t alignment; union { struct { CUarray hArray; } array; int reserved[32]; } res; uint32_t flags; uint32_t reserved; }; static_assert(sizeof(CUDA_RESOURCE_DESC) == 144); struct CUDA_MEMCPY2D { size_t srcXInBytes; size_t srcY; uint32_t srcMemoryType; uint32_t srcAlignment; const void* srcHost; uint64_t srcDevice; CUarray srcArray; size_t srcPitch; size_t dstXInBytes; size_t dstY; uint32_t dstMemoryType; uint32_t dstAlignment; void* dstHost; uint64_t dstDevice; CUarray dstArray; size_t dstPitch; size_t WidthInBytes; size_t Height; }; static_assert(sizeof(CUDA_MEMCPY2D) == 128); template static Function load_function(void* library, const char* name) { void* raw = dlsym(library, name); Function function{}; static_assert(sizeof(function) == sizeof(raw)); std::memcpy(&function, &raw, sizeof(function)); return function; } static CUresult report(const char* stage, CUresult result, CUresult (*get_error_string)(CUresult, const char**) = nullptr) { const char* message = nullptr; if (get_error_string != nullptr) get_error_string(result, &message); std::printf("%s: CUDA result=%d", stage, result); if (message != nullptr) std::printf(" (%s)", message); std::putchar('\n'); return result; } // One unformatted 4-byte store (sust.b.2d.v2.b16.zero, and the same bytes via // sust.b.2d.b32.zero) into a surface of the given CUDA array format. X is a byte offset (unformatted surface addressing). The expected // result is the fill pattern with the four stored bytes replaced at X, or the // untouched fill pattern when X is out of bounds. struct StoreCase { const char* name; uint32_t format; // CUarray_format uint32_t channels; uint32_t channelBytes; uint32_t x; // byte offset uint32_t y; uint16_t value0; uint16_t value1; uint8_t fill[16]; // one pixel of initial contents }; int main() { constexpr unsigned int width = 16; constexpr unsigned int height = 16; const char* ptx = R"PTX( .version 8.9 .target sm_89 .address_size 64 .visible .entry d4r_surface_half_store_probe( .param .b64 surface, .param .b32 x, .param .b32 y, .param .b32 packed ) { .reg .b64 %rd<2>; .reg .b16 %rs<2>; .reg .b32 %r<4>; ld.param.b64 %rd1, [surface]; ld.param.b32 %r0, [x]; ld.param.b32 %r1, [y]; ld.param.b32 %r2, [packed]; mov.b32 {%rs0, %rs1}, %r2; sust.b.2d.v2.b16.zero [%rd1, {%r0,%r1}], {%rs0,%rs1}; ret; } .visible .entry d4r_surface_b32_store_probe( .param .b64 surface, .param .b32 x, .param .b32 y, .param .b32 packed ) { .reg .b64 %rd<2>; .reg .b32 %r<4>; ld.param.b64 %rd1, [surface]; ld.param.b32 %r0, [x]; ld.param.b32 %r1, [y]; ld.param.b32 %r2, [packed]; sust.b.2d.b32.zero [%rd1, {%r0,%r1}], %r2; ret; } )PTX"; // Fill values: 0x3400 is half 0.25, 0x3e800000 is float 0.25. const StoreCase cases[] = { {"R16F store spans two pixels", 0x10, 1, 2, 4 * 2, 6, 0x3555, 0xc248, {0x00, 0x34}}, {"R16F row start pair", 0x10, 1, 2, 0, 0, 0x8001, 0x7bff, {0x00, 0x34}}, {"R16F row end pair", 0x10, 1, 2, width * 2 - 4, 15, 0xfc00, 0x0400, {0x00, 0x34}}, {"R16 uint store spans two pixels", 0x02, 1, 2, 10 * 2, 3, 0xfffe, 0x1234, {0x11, 0x22}}, {"R8 uint store spans four pixels", 0x01, 1, 1, 8, 5, 0x80ff, 0x017f, {0x5a}}, {"RG16F in-bounds", 0x10, 2, 2, 5 * 4, 7, 0x3555, 0xc248, {0x00, 0x34, 0x00, 0x34}}, {"RG16F subnormal/max", 0x10, 2, 2, 9 * 4, 3, 0x0001, 0x7bff, {0x00, 0x34, 0x00, 0x34}}, {"RG16F negative zero/min normal", 0x10, 2, 2, 0, 0, 0x8000, 0x0400, {0x00, 0x34, 0x00, 0x34}}, {"RG16F .zero out-of-bounds", 0x10, 2, 2, width * 4, 7, 0x3555, 0xc248, {0x00, 0x34, 0x00, 0x34}}, {"RGBA16F upper half-pixel", 0x10, 4, 2, 3 * 8 + 4, 2, 0x3555, 0xc248, {0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34}}, {"RGBA16F lower half-pixel", 0x10, 4, 2, 11 * 8, 12, 0xbc00, 0x4900, {0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34}}, {"R32F packed halves", 0x20, 1, 4, 6 * 4, 1, 0x3555, 0xc248, {0x00, 0x00, 0x80, 0x3e}}, {"RG16 uint", 0x02, 2, 2, 4 * 4, 9, 0xfffe, 0x1234, {0x11, 0x11, 0x22, 0x22}}, {"R32F denormal bits", 0x20, 1, 4, 2 * 4, 4, 0x0001, 0x0000, {0x00, 0x00, 0x80, 0x3e}}, {"RG16F infinity/quiet NaN", 0x10, 2, 2, 13 * 4, 13, 0xfc00, 0x7e00, {0x00, 0x34, 0x00, 0x34}}, }; void* library = dlopen("libcuda.so", RTLD_NOW | RTLD_LOCAL); if (library == nullptr) { std::fprintf(stderr, "dlopen(libcuda.so) failed: %s\n", dlerror()); return 1; } using InitFn = CUresult (*)(unsigned int); using DeviceGetFn = CUresult (*)(CUdevice*, int); using ContextCreateFn = CUresult (*)(CUcontext*, unsigned int, CUdevice); using ContextDestroyFn = CUresult (*)(CUcontext); using Array3DCreateFn = CUresult (*)(CUarray*, const CUDA_ARRAY3D_DESCRIPTOR*); using ArrayDestroyFn = CUresult (*)(CUarray); using SurfaceCreateFn = CUresult (*)(CUsurfObject*, const CUDA_RESOURCE_DESC*); using SurfaceDestroyFn = CUresult (*)(CUsurfObject); using Copy2DFn = CUresult (*)(const CUDA_MEMCPY2D*); using ModuleLoadFn = CUresult (*)(CUmodule*, const void*); using ModuleGetFunctionFn = CUresult (*)(CUfunction*, CUmodule, const char*); using LaunchKernelFn = CUresult (*)(CUfunction, unsigned int, unsigned int, unsigned int, unsigned int, unsigned int, unsigned int, unsigned int, CUstream, void**, void**); using ContextSynchronizeFn = CUresult (*)(void); using ModuleUnloadFn = CUresult (*)(CUmodule); using GetErrorStringFn = CUresult (*)(CUresult, const char**); const auto init = load_function(library, "cuInit"); const auto deviceGet = load_function(library, "cuDeviceGet"); const auto contextCreate = load_function(library, "cuCtxCreate_v2"); const auto contextDestroy = load_function(library, "cuCtxDestroy_v2"); const auto array3DCreate = load_function(library, "cuArray3DCreate_v2"); const auto arrayDestroy = load_function(library, "cuArrayDestroy"); const auto surfaceCreate = load_function(library, "cuSurfObjectCreate"); const auto surfaceDestroy = load_function(library, "cuSurfObjectDestroy"); const auto copy2D = load_function(library, "cuMemcpy2D_v2"); const auto moduleLoad = load_function(library, "cuModuleLoadData"); const auto moduleGetFunction = load_function(library, "cuModuleGetFunction"); const auto launchKernel = load_function(library, "cuLaunchKernel"); const auto synchronize = load_function(library, "cuCtxSynchronize"); const auto moduleUnload = load_function(library, "cuModuleUnload"); const auto getErrorString = load_function(library, "cuGetErrorString"); if (init == nullptr || deviceGet == nullptr || contextCreate == nullptr || contextDestroy == nullptr || array3DCreate == nullptr || arrayDestroy == nullptr || surfaceCreate == nullptr || surfaceDestroy == nullptr || copy2D == nullptr || moduleLoad == nullptr || moduleGetFunction == nullptr || launchKernel == nullptr || synchronize == nullptr || moduleUnload == nullptr) { std::fprintf(stderr, "ZLUDA libcuda.so is missing one or more half-surface probe exports\n"); dlclose(library); return 1; } CUcontext context = nullptr; CUmodule module = nullptr; CUfunction functions[2] = {}; const char* functionNames[2] = {"sust.b.2d.v2.b16", "sust.b.2d.b32"}; CUresult result = report("cuInit", init(0), getErrorString); CUdevice device = 0; if (result == 0) result = report("cuDeviceGet", deviceGet(&device, 0), getErrorString); if (result == 0) result = report("cuCtxCreate_v2", contextCreate(&context, 0, device), getErrorString); if (result == 0) result = report("cuModuleLoadData(half surface store PTX)", moduleLoad(&module, ptx), getErrorString); if (result == 0) result = report("cuModuleGetFunction(d4r_surface_half_store_probe)", moduleGetFunction(&functions[0], module, "d4r_surface_half_store_probe"), getErrorString); if (result == 0) result = report("cuModuleGetFunction(d4r_surface_b32_store_probe)", moduleGetFunction(&functions[1], module, "d4r_surface_b32_store_probe"), getErrorString); size_t passed = 0; size_t total = 2 * sizeof(cases) / sizeof(cases[0]); for (size_t functionIndex = 0; functionIndex < 2; ++functionIndex) for (const StoreCase& storeCase : cases) { CUfunction function = functions[functionIndex]; if (result != 0) break; const size_t pixelBytes = static_cast(storeCase.channels) * storeCase.channelBytes; const size_t rowBytes = width * pixelBytes; std::vector expected(rowBytes * height); for (size_t offset = 0; offset < expected.size(); ++offset) expected[offset] = storeCase.fill[offset % pixelBytes]; std::vector surfaceBytes = expected; const uint8_t stored[4] = { static_cast(storeCase.value0), static_cast(storeCase.value0 >> 8), static_cast(storeCase.value1), static_cast(storeCase.value1 >> 8)}; if (storeCase.x + 4 <= rowBytes && storeCase.y < height) std::memcpy(expected.data() + storeCase.y * rowBytes + storeCase.x, stored, 4); CUarray array = nullptr; CUsurfObject surface = 0; const CUDA_ARRAY3D_DESCRIPTOR arrayDescriptor{width, height, 0, storeCase.format, storeCase.channels, 2 /* SURFACE_LDST */}; CUresult caseResult = array3DCreate(&array, &arrayDescriptor); if (caseResult == 0) { CUDA_RESOURCE_DESC resource{}; resource.resType = 0; // CUDA_RESOURCE_TYPE_ARRAY resource.res.array.hArray = array; caseResult = surfaceCreate(&surface, &resource); } if (caseResult == 0) { CUDA_MEMCPY2D upload{}; upload.srcMemoryType = 1; // CUDA_MEMORYTYPE_HOST upload.srcHost = surfaceBytes.data(); upload.srcPitch = rowBytes; upload.dstMemoryType = 3; // CUDA_MEMORYTYPE_ARRAY upload.dstArray = array; upload.WidthInBytes = rowBytes; upload.Height = height; caseResult = copy2D(&upload); } if (caseResult == 0) { uint64_t surfaceArgument = surface; uint32_t x = storeCase.x; uint32_t y = storeCase.y; uint32_t packed = storeCase.value0 | (static_cast(storeCase.value1) << 16); void* arguments[] = {&surfaceArgument, &x, &y, &packed}; caseResult = launchKernel(function, 1, 1, 1, 1, 1, 1, 0, nullptr, arguments, nullptr); if (caseResult == 0) caseResult = synchronize(); } if (caseResult == 0) { CUDA_MEMCPY2D readback{}; readback.srcMemoryType = 3; // CUDA_MEMORYTYPE_ARRAY readback.srcArray = array; readback.dstMemoryType = 1; // CUDA_MEMORYTYPE_HOST readback.dstHost = surfaceBytes.data(); readback.dstPitch = rowBytes; readback.WidthInBytes = rowBytes; readback.Height = height; caseResult = copy2D(&readback); } size_t mismatches = 0; for (size_t offset = 0; caseResult == 0 && offset < expected.size(); offset += pixelBytes) { if (std::memcmp(surfaceBytes.data() + offset, expected.data() + offset, pixelBytes) == 0) continue; ++mismatches; std::printf(" pixel (%zu,%zu) got", (offset % rowBytes) / pixelBytes, offset / rowBytes); for (size_t b = 0; b < pixelBytes; ++b) std::printf(" %02x", surfaceBytes[offset + b]); std::printf(" expected"); for (size_t b = 0; b < pixelBytes; ++b) std::printf(" %02x", expected[offset + b]); std::putchar('\n'); } const bool casePassed = caseResult == 0 && mismatches == 0; passed += casePassed ? 1 : 0; std::printf("%s %s: %s (CUDA result=%d, mismatched pixels=%zu)\n", functionNames[functionIndex], storeCase.name, casePassed ? "PASS" : "FAIL", caseResult, mismatches); if (surface != 0) surfaceDestroy(surface); if (array != nullptr) arrayDestroy(array); } if (result == 0) std::printf("sust.b.2d unformatted 4-byte surface store tests: %zu/%zu passed\n", passed, total); const int exitCode = result == 0 && passed == total ? 0 : 1; if (module != nullptr) moduleUnload(module); if (context != nullptr) contextDestroy(context); dlclose(library); return exitCode; }