Validate native D3D12 HIP interop and isolate Windows mapped-view leak
This commit is contained in:
1 parent
e95e061e81
commit
239a644270
11 files changed
+561
-18
No files matched your search
+9
-2
@@ -12,7 +12,7 @@ endif()
|
|||||||
if(NOT EXISTS "${D4R_HIP_ROOT}/include/hip/hip_runtime_api.h")
|
if(NOT EXISTS "${D4R_HIP_ROOT}/include/hip/hip_runtime_api.h")
|
||||||
message(FATAL_ERROR "Set D4R_HIP_ROOT to a Windows HIP SDK (headers and bin/clang++.exe required)")
|
message(FATAL_ERROR "Set D4R_HIP_ROOT to a Windows HIP SDK (headers and bin/clang++.exe required)")
|
||||||
endif()
|
endif()
|
||||||
find_program(D4R_HIP_CLANG NAMES clang++.exe PATHS "${D4R_HIP_ROOT}/bin" NO_DEFAULT_PATH REQUIRED)
|
find_program(D4R_HIP_CLANG NAMES clang++.exe PATHS "${D4R_HIP_ROOT}/bin" "${D4R_HIP_ROOT}/lib/llvm/bin" NO_DEFAULT_PATH REQUIRED)
|
||||||
set(probe_object "${CMAKE_CURRENT_BINARY_DIR}/probe_gfx1201.hsaco")
|
set(probe_object "${CMAKE_CURRENT_BINARY_DIR}/probe_gfx1201.hsaco")
|
||||||
add_custom_command(OUTPUT "${probe_object}"
|
add_custom_command(OUTPUT "${probe_object}"
|
||||||
COMMAND "${D4R_HIP_CLANG}" -x hip --offload-arch=gfx1201
|
COMMAND "${D4R_HIP_CLANG}" -x hip --offload-arch=gfx1201
|
||||||
@@ -21,7 +21,7 @@ add_custom_command(OUTPUT "${probe_object}"
|
|||||||
-o "${probe_object}"
|
-o "${probe_object}"
|
||||||
DEPENDS tools/windows/probe_kernel.hip VERBATIM)
|
DEPENDS tools/windows/probe_kernel.hip VERBATIM)
|
||||||
add_custom_target(d4r_probe_code_object ALL DEPENDS "${probe_object}")
|
add_custom_target(d4r_probe_code_object ALL DEPENDS "${probe_object}")
|
||||||
foreach(probe hip_gfx1201_probe cuda_driver_probe)
|
foreach(probe hip_gfx1201_probe cuda_driver_probe d3d12_hip_interop_probe hip_stream_lifecycle_probe)
|
||||||
add_executable(d4r_${probe} tools/windows/${probe}.cpp)
|
add_executable(d4r_${probe} tools/windows/${probe}.cpp)
|
||||||
target_compile_definitions(d4r_${probe} PRIVATE WIN32_LEAN_AND_MEAN NOMINMAX __HIP_PLATFORM_AMD__)
|
target_compile_definitions(d4r_${probe} PRIVATE WIN32_LEAN_AND_MEAN NOMINMAX __HIP_PLATFORM_AMD__)
|
||||||
target_include_directories(d4r_${probe} PRIVATE "${D4R_HIP_ROOT}/include")
|
target_include_directories(d4r_${probe} PRIVATE "${D4R_HIP_ROOT}/include")
|
||||||
@@ -37,6 +37,7 @@ foreach(probe hip_gfx1201_probe cuda_driver_probe)
|
|||||||
endif()
|
endif()
|
||||||
install(TARGETS d4r_${probe} RUNTIME DESTINATION bin)
|
install(TARGETS d4r_${probe} RUNTIME DESTINATION bin)
|
||||||
endforeach()
|
endforeach()
|
||||||
|
target_link_libraries(d4r_d3d12_hip_interop_probe PRIVATE d3d12 dxgi dxguid)
|
||||||
add_dependencies(d4r_hip_gfx1201_probe d4r_probe_code_object)
|
add_dependencies(d4r_hip_gfx1201_probe d4r_probe_code_object)
|
||||||
install(FILES "${probe_object}" DESTINATION bin)
|
install(FILES "${probe_object}" DESTINATION bin)
|
||||||
install(FILES scripts/windows/test-windows-rdna4.ps1 DESTINATION .)
|
install(FILES scripts/windows/test-windows-rdna4.ps1 DESTINATION .)
|
||||||
@@ -45,6 +46,12 @@ include(CTest)
|
|||||||
add_test(NAME hip_gfx1201 COMMAND d4r_hip_gfx1201_probe
|
add_test(NAME hip_gfx1201 COMMAND d4r_hip_gfx1201_probe
|
||||||
--hip-root "${D4R_HIP_ROOT}" --module "${probe_object}")
|
--hip-root "${D4R_HIP_ROOT}" --module "${probe_object}")
|
||||||
set_tests_properties(hip_gfx1201 PROPERTIES TIMEOUT 90 LABELS "hardware;gfx1201")
|
set_tests_properties(hip_gfx1201 PROPERTIES TIMEOUT 90 LABELS "hardware;gfx1201")
|
||||||
|
add_test(NAME d3d12_hip_interop COMMAND d4r_d3d12_hip_interop_probe
|
||||||
|
--hip-root "${D4R_HIP_ROOT}" --module "${probe_object}" --iterations 32)
|
||||||
|
set_tests_properties(d3d12_hip_interop PROPERTIES TIMEOUT 120 LABELS "hardware;interop")
|
||||||
|
add_test(NAME d3d12_hip_map_lifetime COMMAND d4r_d3d12_hip_interop_probe
|
||||||
|
--hip-root "${D4R_HIP_ROOT}" --interop-mode map --iterations 64)
|
||||||
|
set_tests_properties(d3d12_hip_map_lifetime PROPERTIES TIMEOUT 120 LABELS "hardware;interop;lifetime")
|
||||||
set(D4R_ZLUDA_ROOT "" CACHE PATH "Local Windows ZLUDA directory (nvcuda.dll)")
|
set(D4R_ZLUDA_ROOT "" CACHE PATH "Local Windows ZLUDA directory (nvcuda.dll)")
|
||||||
if(EXISTS "${D4R_ZLUDA_ROOT}/nvcuda.dll")
|
if(EXISTS "${D4R_ZLUDA_ROOT}/nvcuda.dll")
|
||||||
foreach(context primary created)
|
foreach(context primary created)
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ This is a work log for an implementation in progress, not a claim of DLSS suppor
|
|||||||
|
|
||||||
## Milestone and gates
|
## Milestone and gates
|
||||||
|
|
||||||
Current milestone: M1/M2 passed on the physical RX 9070 XT; next: independent
|
Current milestone: M1/M2 and M4 passed on the physical RX 9070 XT with the
|
||||||
Windows D3D12/HIP interop and validated gfx12 WMMA, before NGX integration.
|
isolated TheRock profile. Next: NGX initialization and validated gfx12 WMMA.
|
||||||
Do not integrate NGX until the integer PTX workload is stable on the real GPU.
|
Do not integrate NGX until the integer PTX workload is stable on the real GPU.
|
||||||
|
|
||||||
| Gate | Status |
|
| Gate | Status |
|
||||||
@@ -16,7 +16,7 @@ Do not integrate NGX until the integer PTX workload is stable on the real GPU.
|
|||||||
| M1: native HIP gfx1201 allocation, kernel, CPU verification | PASS, 32 iterations + guard verification |
|
| M1: native HIP gfx1201 allocation, kernel, CPU verification | PASS, 32 iterations + guard verification |
|
||||||
| M2: Windows ZLUDA integer PTX, primary and created contexts | PASS, 32 iterations each + guard verification |
|
| M2: Windows ZLUDA integer PTX, primary and created contexts | PASS, 32 iterations each + guard verification |
|
||||||
| M3: Windows NGX initialization and simple DLSS path | Pending |
|
| M3: Windows NGX initialization and simple DLSS path | Pending |
|
||||||
| M4: D3D12 / HIP external memory and fence round trip | Pending |
|
| M4: D3D12 / HIP external memory and fence round trip | PASS with TheRock; stable 7.2 has mapped-view leak |
|
||||||
| M5: independently validated gfx12 WMMA backend | Pending |
|
| M5: independently validated gfx12 WMMA backend | Pending |
|
||||||
| M6: K layers, full transformer and image validation | Pending |
|
| M6: K layers, full transformer and image validation | Pending |
|
||||||
| M7: M FP16-equivalent baseline and full transformer | Pending |
|
| M7: M FP16-equivalent baseline and full transformer | Pending |
|
||||||
@@ -36,6 +36,10 @@ Do not integrate NGX until the integer PTX workload is stable on the real GPU.
|
|||||||
LLVM commit `590b9320a5be90e40268759c6203c01fde121e68`.
|
LLVM commit `590b9320a5be90e40268759c6203c01fde121e68`.
|
||||||
* Keep stable SDK and TheRock installations separate. Select one directory per
|
* Keep stable SDK and TheRock installations separate. Select one directory per
|
||||||
build and test run; never mix headers, import libraries, runtime or COMGR.
|
build and test run; never mix headers, import libraries, runtime or COMGR.
|
||||||
|
* Tested isolated TheRock core: `10.2.0a20260929`, HIP `7.17.26386`, commit
|
||||||
|
`0ad5f73253`; wheel SHA256
|
||||||
|
`9623b97ca511eaa176905075a504393eca6fde594222af10b55e4fb1edfe5a25`.
|
||||||
|
No machine-wide runtime replacement is performed.
|
||||||
* CMake 3.31.6, Ninja 1.11.1 (Python package 1.11.1.4).
|
* CMake 3.31.6, Ninja 1.11.1 (Python package 1.11.1.4).
|
||||||
* Windows host toolchain: LLVM-MinGW `20260922`, UCRT x64, SHA256
|
* Windows host toolchain: LLVM-MinGW `20260922`, UCRT x64, SHA256
|
||||||
`e3ad77d117a4bea19a7a3b333341824d79a5a371004a10e25b8504e7b3047666`.
|
`e3ad77d117a4bea19a7a3b333341824d79a5a371004a10e25b8504e7b3047666`.
|
||||||
@@ -125,6 +129,25 @@ NT HANDLE ownership must be explicit; Win32 import is not fd ownership transfer.
|
|||||||
Test repeated create/destroy cycles as upstream noted a mapped-buffer leak on
|
Test repeated create/destroy cycles as upstream noted a mapped-buffer leak on
|
||||||
Linux ROCm. CPU verification copies are allowed in tests, never in the fast path.
|
Linux ROCm. CPU verification copies are allowed in tests, never in the fast path.
|
||||||
|
|
||||||
|
Implemented independent test: DXGI LUID matches HIP, an R32_UINT texture is
|
||||||
|
copied to a shared DEFAULT-heap linear buffer in VRAM, HIP maps that D3D12
|
||||||
|
resource, checks every input element and XORs it, D3D12 copies it back to the
|
||||||
|
texture and finally to a readback buffer solely for test verification. Shared
|
||||||
|
D3D12 fence values sequence both queues within the same iteration. There is no
|
||||||
|
CPU data copy between the APIs; native tiled texture import is not yet tested.
|
||||||
|
The future shim needs GPU texture/linear conversion or validated direct arrays.
|
||||||
|
The caller closes its original NT HANDLEs after HIP releases imports.
|
||||||
|
|
||||||
|
Stable HIP SDK `7.2.60201` fails mapped-buffer lifetime testing. `resource` and
|
||||||
|
`import` modes are stable, but `map` leaks one HANDLE and 262144 bytes per cycle.
|
||||||
|
The runtime's extra `view->retain()` is the cause; AMD removed it in
|
||||||
|
[CLR commit 529f6b1](https://github.com/ROCm/clr/commit/529f6b1641de436dafbb095d5438b0fbf773765d).
|
||||||
|
The source backport is retained at `patches/rocm/0001-external-memory-view-ownership.patch`.
|
||||||
|
No pointer/refcount or DLL binary hacks are used. Current TheRock passes both
|
||||||
|
the 64-cycle map lifetime test and the 32-cycle synchronized texture round trip.
|
||||||
|
The runner automatically isolates resource/import/stream lifetimes on failure.
|
||||||
|
Do not declare stable SDK 7.2 acceptable for the leak-free fast path.
|
||||||
|
|
||||||
## Authoritative references
|
## Authoritative references
|
||||||
|
|
||||||
* [AMD HIP SDK Windows support matrix](https://rocm.docs.amd.com/projects/install-on-windows/en/latest/reference/system-requirements.html)
|
* [AMD HIP SDK Windows support matrix](https://rocm.docs.amd.com/projects/install-on-windows/en/latest/reference/system-requirements.html)
|
||||||
@@ -151,6 +174,18 @@ powershell -NoProfile -File scripts/windows/build-windows-rdna4.ps1
|
|||||||
powershell -NoProfile -File scripts/windows/test-windows-rdna4.ps1
|
powershell -NoProfile -File scripts/windows/test-windows-rdna4.ps1
|
||||||
```
|
```
|
||||||
|
|
||||||
|
For the currently validated external-memory runtime, use the separate profile:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
powershell -NoProfile -File scripts/windows/setup-windows-tools.ps1 -RuntimeProfile therock
|
||||||
|
powershell -NoProfile -File scripts/windows/build-windows-rdna4.ps1 -RuntimeProfile therock
|
||||||
|
powershell -NoProfile -File scripts/windows/test-windows-rdna4.ps1 -RuntimeProfile therock
|
||||||
|
```
|
||||||
|
|
||||||
|
This builds into `build/windows-rdna4-therock` and installs into
|
||||||
|
`dist/windows-rdna4-therock`. The stable profile stays available for reproducing
|
||||||
|
the runtime bug. It is expected to return failure at the lifetime gate.
|
||||||
|
|
||||||
The build accepts `-HipRoot`, `-ZludaRoot`, `-ToolchainRoot`, `-BuildDirectory`
|
The build accepts `-HipRoot`, `-ZludaRoot`, `-ToolchainRoot`, `-BuildDirectory`
|
||||||
and `-InstallDirectory`. MSVC users can invoke CMake from a Developer Shell
|
and `-InstallDirectory`. MSVC users can invoke CMake from a Developer Shell
|
||||||
without the portable MinGW compiler. Equivalent direct build:
|
without the portable MinGW compiler. Equivalent direct build:
|
||||||
@@ -165,11 +200,13 @@ cmake --install build/windows-rdna4 --prefix dist/windows-rdna4-diagnostics
|
|||||||
The one-command diagnostic runner is also installed at
|
The one-command diagnostic runner is also installed at
|
||||||
`dist/windows-rdna4-diagnostics/test-windows-rdna4.ps1`. It requires the selected
|
`dist/windows-rdna4-diagnostics/test-windows-rdna4.ps1`. It requires the selected
|
||||||
HIP SDK and ZLUDA directory; the developer setup auto-discovers local defaults.
|
HIP SDK and ZLUDA directory; the developer setup auto-discovers local defaults.
|
||||||
Success: exit 0, all three tests passed in `summary.json`, `PASS HIP` and
|
Success: exit 0, all five tests passed in `summary.json`, `PASS HIP`, `PASS CUDA`,
|
||||||
`PASS CUDA` in stdout. Failure: send the **single printed ZIP path**; it contains
|
`PASS INTEROP_LIFETIME` and `PASS INTEROP` in stdout. Failure: send the **single printed ZIP path**; it contains
|
||||||
stdout/stderr, timeout/exception exit code, DLL paths/versions/hashes, driver/OS
|
stdout/stderr, timeout/exception exit code, DLL paths/versions/hashes, driver/OS
|
||||||
inventory and a minidump for an unhandled exception. Upstream trace is attempted
|
inventory and a minidump for an unhandled exception. Upstream trace is attempted
|
||||||
automatically on CUDA failure when `zluda.exe` is available.
|
automatically on CUDA failure when `trace/nvcuda.dll` is available. The trace
|
||||||
|
DLL forwards to the real DLL through `ZLUDA_CUDA_LIB`; explicit loading no
|
||||||
|
longer bypasses the trace by accidentally passing the real DLL to the launcher.
|
||||||
|
|
||||||
The HIP module compiler uses the selected SDK, fixed `--offload-arch=gfx1201`,
|
The HIP module compiler uses the selected SDK, fixed `--offload-arch=gfx1201`,
|
||||||
wave32 and no host/device library dependency. Host code uses the installed HIP
|
wave32 and no host/device library dependency. Host code uses the installed HIP
|
||||||
@@ -194,6 +231,14 @@ all context/module/memory teardown calls successful. ZLUDA binary SHA256
|
|||||||
`51dd32dc116a6c14c7a4bf5acf620bba0546ca7ec5f8a6e2148dd68f1714b165`.
|
`51dd32dc116a6c14c7a4bf5acf620bba0546ca7ec5f8a6e2148dd68f1714b165`.
|
||||||
Logs: local `test-results/m1-m2.zip`; ignored by git.
|
Logs: local `test-results/m1-m2.zip`; ignored by git.
|
||||||
|
|
||||||
|
2026-09-29 M4: TheRock profile passed all HIP/PTX checks plus D3D12/HIP round
|
||||||
|
trip (32 iterations, 16384 elements each), exact integer output and GPU fence
|
||||||
|
synchronization. Map lifetime: 64 import/map/free/destroy cycles, HANDLE count
|
||||||
|
267 after both warmup and the last cycle, free VRAM 16926011392 bytes throughout
|
||||||
|
the measured steady state. Full interop cycle HANDLE count also stays constant
|
||||||
|
after warmup; stream teardown lowers it. Recreating streams did not resolve the
|
||||||
|
stable runtime mapped-view bug; the fix is the runtime ownership correction.
|
||||||
|
|
||||||
Local proprietary inputs supplied by the user: `nvngx_dlss.dll` 310.9.1.0,
|
Local proprietary inputs supplied by the user: `nvngx_dlss.dll` 310.9.1.0,
|
||||||
`_nvngx.dll` 32.0.16.1714. Neither is packaged or tracked.
|
`_nvngx.dll` 32.0.16.1714. Neither is packaged or tracked.
|
||||||
|
|
||||||
@@ -206,4 +251,4 @@ launching the MinGW child compiler, so branch/commit/build require scoped tool
|
|||||||
approval in this managed environment. HIP_PATH ends in a backslash; build script
|
approval in this managed environment. HIP_PATH ends in a backslash; build script
|
||||||
normalizes it before PowerShell 5 argument quoting. Test runner uses .NET Process
|
normalizes it before PowerShell 5 argument quoting. Test runner uses .NET Process
|
||||||
to retain reliable exit codes and concurrently drain both diagnostic streams.
|
to retain reliable exit codes and concurrently drain both diagnostic streams.
|
||||||
No K/M/DLSS/OptiScaler support is claimed by this first milestone.
|
No K/M/DLSS/OptiScaler support is claimed by these diagnostic milestones.
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
From: d4r Windows port
|
||||||
|
Subject: [PATCH] Backport AMD external-memory view ownership fix to ROCm 7.2
|
||||||
|
|
||||||
|
Upstream fix: ROCm/clr 529f6b1641de436dafbb095d5438b0fbf773765d
|
||||||
|
(ROCm/rocm-systems #3528). Already present in current upstream.
|
||||||
|
The constructor owns reference 1. MemObjMap does not retain the view;
|
||||||
|
hipFree removes the entry and releases reference 1. The extra retain
|
||||||
|
leaks the view and its parent imported allocation.
|
||||||
|
|
||||||
|
Hardware regression: d4r_d3d12_hip_interop_probe --interop-mode map
|
||||||
|
--iterations 64. Windows SDK 7.2.60201 leaks one HANDLE / 256 KiB each
|
||||||
|
cycle; tested TheRock 10.2.0a20260929 (HIP 7.17.26386) stays bounded.
|
||||||
|
This patch is for rebuilding old runtime sources, never binary patching.
|
||||||
|
|
||||||
|
diff --git a/hipamd/src/hip_memory.cpp b/hipamd/src/hip_memory.cpp
|
||||||
|
--- a/hipamd/src/hip_memory.cpp
|
||||||
|
+++ b/hipamd/src/hip_memory.cpp
|
||||||
|
@@ -179,7 +179,6 @@ hipError_t hipExternalMemoryGetMappedBuffer(void** devPtr, hipExternalMemory_t extMem,
|
||||||
|
// Map the device memory to the user pointer
|
||||||
|
*devPtr = reinterpret_cast<void*>(devMem->virtualAddress());
|
||||||
|
amd::MemObjMap::AddMemObj(*devPtr, view);
|
||||||
|
- view->retain();
|
||||||
|
|
||||||
|
HIP_RETURN(hipSuccess);
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
[CmdletBinding()]
|
[CmdletBinding()]
|
||||||
param(
|
param(
|
||||||
[string]$HipRoot = $env:HIP_PATH,
|
[string]$HipRoot = $env:HIP_PATH,
|
||||||
|
[ValidateSet('stable', 'therock')][string]$RuntimeProfile = 'stable',
|
||||||
[string]$ZludaRoot,
|
[string]$ZludaRoot,
|
||||||
[string]$ToolchainRoot,
|
[string]$ToolchainRoot,
|
||||||
[string]$BuildDirectory,
|
[string]$BuildDirectory,
|
||||||
@@ -8,6 +9,11 @@ param(
|
|||||||
)
|
)
|
||||||
$ErrorActionPreference = 'Stop'
|
$ErrorActionPreference = 'Stop'
|
||||||
$repo = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
|
$repo = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
|
||||||
|
if ($RuntimeProfile -eq 'therock') {
|
||||||
|
if (!$PSBoundParameters.ContainsKey('HipRoot')) { $HipRoot = Join-Path $repo '.tools/therock-10.2.0a20260929/_rocm_sdk_core' }
|
||||||
|
if (!$BuildDirectory) { $BuildDirectory = Join-Path $repo 'build/windows-rdna4-therock' }
|
||||||
|
if (!$InstallDirectory) { $InstallDirectory = Join-Path $repo 'dist/windows-rdna4-therock' }
|
||||||
|
}
|
||||||
if (!$HipRoot) { $HipRoot = 'C:\Program Files\AMD\ROCm\7.2' }
|
if (!$HipRoot) { $HipRoot = 'C:\Program Files\AMD\ROCm\7.2' }
|
||||||
if (!$BuildDirectory) { $BuildDirectory = Join-Path $repo 'build/windows-rdna4' }
|
if (!$BuildDirectory) { $BuildDirectory = Join-Path $repo 'build/windows-rdna4' }
|
||||||
if (!$InstallDirectory) { $InstallDirectory = Join-Path $repo 'dist/windows-rdna4-diagnostics' }
|
if (!$InstallDirectory) { $InstallDirectory = Join-Path $repo 'dist/windows-rdna4-diagnostics' }
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
[CmdletBinding()]
|
[CmdletBinding()]
|
||||||
param()
|
param([ValidateSet('stable', 'therock')][string]$RuntimeProfile = 'stable')
|
||||||
$ErrorActionPreference = 'Stop'
|
$ErrorActionPreference = 'Stop'
|
||||||
$repo = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
|
$repo = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
|
||||||
$localTools = Join-Path $repo '.tools'
|
$localTools = Join-Path $repo '.tools'
|
||||||
@@ -14,7 +14,10 @@ function Get-VerifiedArchive([string]$Url, [string]$Name, [string]$Hash, [string
|
|||||||
if ((Get-FileHash -LiteralPath $archive -Algorithm SHA256).Hash -ne $Hash) {
|
if ((Get-FileHash -LiteralPath $archive -Algorithm SHA256).Hash -ne $Hash) {
|
||||||
throw "SHA256 mismatch: $archive (remove this download and retry)"
|
throw "SHA256 mismatch: $archive (remove this download and retry)"
|
||||||
}
|
}
|
||||||
Expand-Archive -LiteralPath $archive -DestinationPath $Destination -Force
|
if ([IO.Path]::GetExtension($archive) -eq '.whl') {
|
||||||
|
python -c 'import sys,zipfile; zipfile.ZipFile(sys.argv[1]).extractall(sys.argv[2])' $archive $Destination
|
||||||
|
if ($LASTEXITCODE) { throw "Wheel extraction exit=$LASTEXITCODE" }
|
||||||
|
} else { Expand-Archive -LiteralPath $archive -DestinationPath $Destination -Force }
|
||||||
if (!(Test-Path -LiteralPath $Marker)) { throw "Archive layout changed: $Marker" }
|
if (!(Test-Path -LiteralPath $Marker)) { throw "Archive layout changed: $Marker" }
|
||||||
}
|
}
|
||||||
Get-VerifiedArchive 'https://github.com/mstorsjo/llvm-mingw/releases/download/20260922/llvm-mingw-20260922-ucrt-x86_64.zip' `
|
Get-VerifiedArchive 'https://github.com/mstorsjo/llvm-mingw/releases/download/20260922/llvm-mingw-20260922-ucrt-x86_64.zip' `
|
||||||
@@ -23,6 +26,13 @@ Get-VerifiedArchive 'https://github.com/mstorsjo/llvm-mingw/releases/download/20
|
|||||||
Get-VerifiedArchive 'https://github.com/vosen/ZLUDA/releases/download/v7-preview.11/zluda-windows-ee2f25a.zip' `
|
Get-VerifiedArchive 'https://github.com/vosen/ZLUDA/releases/download/v7-preview.11/zluda-windows-ee2f25a.zip' `
|
||||||
'zluda.zip' '7788c1ed43385e62f0cc5f4d1aead24c92b671c02f1d685f63182540dea16d74' (Join-Path $localTools 'zluda') `
|
'zluda.zip' '7788c1ed43385e62f0cc5f4d1aead24c92b671c02f1d685f63182540dea16d74' (Join-Path $localTools 'zluda') `
|
||||||
(Join-Path $localTools 'zluda/zluda/nvcuda.dll')
|
(Join-Path $localTools 'zluda/zluda/nvcuda.dll')
|
||||||
|
if ($RuntimeProfile -eq 'therock') {
|
||||||
|
Get-VerifiedArchive 'https://nightly.repo.amd.com/rocm/core/whl-next/rocm-sdk-core/rocm_sdk_core-10.2.0a20260929-py3-none-win_amd64.whl' `
|
||||||
|
'rocm_sdk_core-10.2.0a20260929-py3-none-win_amd64.whl' `
|
||||||
|
'9623b97ca511eaa176905075a504393eca6fde594222af10b55e4fb1edfe5a25' `
|
||||||
|
(Join-Path $localTools 'therock-10.2.0a20260929') `
|
||||||
|
(Join-Path $localTools 'therock-10.2.0a20260929/_rocm_sdk_core/bin/amdhip64_7.dll')
|
||||||
|
}
|
||||||
if (!(Test-Path (Join-Path $localTools 'python/cmake/data/bin/cmake.exe')) -or
|
if (!(Test-Path (Join-Path $localTools 'python/cmake/data/bin/cmake.exe')) -or
|
||||||
!(Test-Path (Join-Path $localTools 'python/bin/ninja.exe'))) {
|
!(Test-Path (Join-Path $localTools 'python/bin/ninja.exe'))) {
|
||||||
python -m pip install --target (Join-Path $localTools 'python') cmake==3.31.6 ninja==1.11.1.4
|
python -m pip install --target (Join-Path $localTools 'python') cmake==3.31.6 ninja==1.11.1.4
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
[CmdletBinding()]
|
[CmdletBinding()]
|
||||||
param(
|
param(
|
||||||
[string]$HipRoot = $env:HIP_PATH,
|
[string]$HipRoot = $env:HIP_PATH,
|
||||||
|
[ValidateSet('stable', 'therock')][string]$RuntimeProfile = 'stable',
|
||||||
[string]$ZludaRoot,
|
[string]$ZludaRoot,
|
||||||
[string]$PackageRoot,
|
[string]$PackageRoot,
|
||||||
[string]$OutputDirectory,
|
[string]$OutputDirectory,
|
||||||
@@ -8,6 +9,15 @@ param(
|
|||||||
[int]$TimeoutSeconds = 180
|
[int]$TimeoutSeconds = 180
|
||||||
)
|
)
|
||||||
$ErrorActionPreference = 'Stop'
|
$ErrorActionPreference = 'Stop'
|
||||||
|
if ($RuntimeProfile -eq 'therock') {
|
||||||
|
$repoRoot = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
|
||||||
|
# Works both from scripts/windows and the installed dist/<profile> directory.
|
||||||
|
if (!$PSBoundParameters.ContainsKey('HipRoot')) { $HipRoot = Join-Path $repoRoot '.tools/therock-10.2.0a20260929/_rocm_sdk_core' }
|
||||||
|
if (!$PackageRoot) {
|
||||||
|
if (Test-Path (Join-Path $PSScriptRoot 'bin/d4r_hip_gfx1201_probe.exe')) { $PackageRoot = $PSScriptRoot }
|
||||||
|
else { $PackageRoot = Join-Path $repoRoot 'dist/windows-rdna4-therock' }
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!$PackageRoot) {
|
if (!$PackageRoot) {
|
||||||
if (Test-Path (Join-Path $PSScriptRoot 'bin/d4r_hip_gfx1201_probe.exe')) { $PackageRoot = $PSScriptRoot }
|
if (Test-Path (Join-Path $PSScriptRoot 'bin/d4r_hip_gfx1201_probe.exe')) { $PackageRoot = $PSScriptRoot }
|
||||||
else { $PackageRoot = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../../dist/windows-rdna4-diagnostics')) }
|
else { $PackageRoot = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../../dist/windows-rdna4-diagnostics')) }
|
||||||
@@ -30,7 +40,7 @@ $originalDiag = $env:D4R_DIAG_DIR
|
|||||||
$originalLog = $env:ZLUDA_LOG_DIR
|
$originalLog = $env:ZLUDA_LOG_DIR
|
||||||
$originalCuda = $env:ZLUDA_CUDA_LIB
|
$originalCuda = $env:ZLUDA_CUDA_LIB
|
||||||
$exitStatus = 1
|
$exitStatus = 1
|
||||||
$summary = [ordered]@{utc=[DateTime]::UtcNow.ToString('o'); hipRoot=$HipRoot; zludaRoot=$ZludaRoot; tests=@()}
|
$summary = [ordered]@{utc=[DateTime]::UtcNow.ToString('o'); profile=$RuntimeProfile; hipRoot=$HipRoot; zludaRoot=$ZludaRoot; tests=@()}
|
||||||
function Quote-Argument([string]$Value) {
|
function Quote-Argument([string]$Value) {
|
||||||
# All generated paths are absolute file/directory paths, with no trailing backslash.
|
# All generated paths are absolute file/directory paths, with no trailing backslash.
|
||||||
if ($Value.Contains('"')) { throw 'Double quotes are not allowed in arguments' }
|
if ($Value.Contains('"')) { throw 'Double quotes are not allowed in arguments' }
|
||||||
@@ -76,7 +86,9 @@ try {
|
|||||||
$env:D4R_DIAG_DIR = $OutputDirectory
|
$env:D4R_DIAG_DIR = $OutputDirectory
|
||||||
$env:ZLUDA_LOG_DIR = Join-Path $OutputDirectory 'zluda-trace'
|
$env:ZLUDA_LOG_DIR = Join-Path $OutputDirectory 'zluda-trace'
|
||||||
$env:ZLUDA_CUDA_LIB = Join-Path $ZludaRoot 'nvcuda.dll'
|
$env:ZLUDA_CUDA_LIB = Join-Path $ZludaRoot 'nvcuda.dll'
|
||||||
$files = @((Join-Path $HipRoot 'bin/amdhip64_7.dll'), (Join-Path $HipRoot 'bin/amd_comgr_3.dll'),
|
$comgr = Join-Path $HipRoot 'bin/amd_comgr_3.dll'
|
||||||
|
if (!(Test-Path -LiteralPath $comgr)) { $comgr = Join-Path $HipRoot 'bin/amd_comgr.dll' }
|
||||||
|
$files = @((Join-Path $HipRoot 'bin/amdhip64_7.dll'), $comgr,
|
||||||
(Join-Path $ZludaRoot 'nvcuda.dll'), (Join-Path $PackageRoot 'bin/probe_gfx1201.hsaco'))
|
(Join-Path $ZludaRoot 'nvcuda.dll'), (Join-Path $PackageRoot 'bin/probe_gfx1201.hsaco'))
|
||||||
$summary.files = @($files | ForEach-Object {
|
$summary.files = @($files | ForEach-Object {
|
||||||
if (!(Test-Path -LiteralPath $_)) { throw "Required file missing: $_" }
|
if (!(Test-Path -LiteralPath $_)) { throw "Required file missing: $_" }
|
||||||
@@ -100,14 +112,27 @@ try {
|
|||||||
'--context', $context, '--iterations', "$Iterations")
|
'--context', $context, '--iterations', "$Iterations")
|
||||||
$cudaOk = $cudaOk -and $ok
|
$cudaOk = $cudaOk -and $ok
|
||||||
# On failure collect the upstream trace automatically if available.
|
# On failure collect the upstream trace automatically if available.
|
||||||
if (!$ok -and (Test-Path (Join-Path $ZludaRoot 'zluda.exe'))) {
|
if (!$ok -and (Test-Path (Join-Path $ZludaRoot 'trace/nvcuda.dll'))) {
|
||||||
Invoke-Probe "cuda-$context-trace" (Join-Path $ZludaRoot 'zluda.exe') @(
|
Invoke-Probe "cuda-$context-trace" (Join-Path $bin 'd4r_cuda_driver_probe.exe') @(
|
||||||
'--zluda-trace', '--', (Join-Path $bin 'd4r_cuda_driver_probe.exe'),
|
'--hip-root', $HipRoot, '--cuda-dll', (Join-Path $ZludaRoot 'trace/nvcuda.dll'),
|
||||||
'--hip-root', $HipRoot, '--cuda-dll', (Join-Path $ZludaRoot 'nvcuda.dll'),
|
|
||||||
'--context', $context, '--iterations', '1') | Out-Null
|
'--context', $context, '--iterations', '1') | Out-Null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($cudaOk) { $exitStatus = 0 }
|
if ($cudaOk) {
|
||||||
|
$mapOk = Invoke-Probe 'interop-map-lifetime' (Join-Path $bin 'd4r_d3d12_hip_interop_probe.exe') @(
|
||||||
|
'--hip-root', $HipRoot, '--interop-mode', 'map', '--iterations', '64')
|
||||||
|
$interopOk = Invoke-Probe 'interop-roundtrip' (Join-Path $bin 'd4r_d3d12_hip_interop_probe.exe') @(
|
||||||
|
'--hip-root', $HipRoot, '--module', (Join-Path $bin 'probe_gfx1201.hsaco'), '--iterations', "$Iterations")
|
||||||
|
if (!$mapOk -or !$interopOk) {
|
||||||
|
foreach ($mode in @('resource', 'import')) {
|
||||||
|
Invoke-Probe "interop-$mode-lifetime" (Join-Path $bin 'd4r_d3d12_hip_interop_probe.exe') @(
|
||||||
|
'--hip-root', $HipRoot, '--interop-mode', $mode, '--iterations', '32') | Out-Null
|
||||||
|
}
|
||||||
|
Invoke-Probe 'hip-stream-lifetime' (Join-Path $bin 'd4r_hip_stream_lifecycle_probe.exe') @(
|
||||||
|
'--hip-root', $HipRoot, '--module', (Join-Path $bin 'probe_gfx1201.hsaco'), '--iterations', '32') | Out-Null
|
||||||
|
}
|
||||||
|
if ($mapOk -and $interopOk) { $exitStatus = 0 }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
$summary.error = $_.Exception.Message
|
$summary.error = $_.Exception.Message
|
||||||
|
|||||||
@@ -0,0 +1,346 @@
|
|||||||
|
#if defined(__MINGW32__)
|
||||||
|
#define WIDL_EXPLICIT_AGGREGATE_RETURNS
|
||||||
|
#endif
|
||||||
|
#include "hip_api.h"
|
||||||
|
#include <d3d12.h>
|
||||||
|
#include <dxgi1_6.h>
|
||||||
|
#include <wrl/client.h>
|
||||||
|
|
||||||
|
using Microsoft::WRL::ComPtr;
|
||||||
|
using namespace d4r::diag;
|
||||||
|
|
||||||
|
static void handle_checkpoint(const char* stage)
|
||||||
|
{
|
||||||
|
DWORD count = 0;
|
||||||
|
if (!GetProcessHandleCount(GetCurrentProcess(), &count))
|
||||||
|
throw std::runtime_error("GetProcessHandleCount failed");
|
||||||
|
std::printf("HANDLE_STAGE %s count=%lu\n", stage, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dx(HRESULT hr, const char* call)
|
||||||
|
{
|
||||||
|
std::printf("D3D12 %s -> 0x%08lx\n", call, static_cast<unsigned long>(hr));
|
||||||
|
if (FAILED(hr)) throw std::runtime_error(std::string(call) + " HRESULT=" + std::to_string(hr));
|
||||||
|
}
|
||||||
|
struct NativeHandle {
|
||||||
|
HANDLE handle = nullptr;
|
||||||
|
~NativeHandle() { if (handle) CloseHandle(handle); }
|
||||||
|
};
|
||||||
|
struct ExternalApi {
|
||||||
|
HipApi& hip;
|
||||||
|
#define EXT(name) decltype(&::name) name = hip.library.symbol<decltype(&::name)>(#name)
|
||||||
|
EXT(hipImportExternalMemory);
|
||||||
|
EXT(hipExternalMemoryGetMappedBuffer);
|
||||||
|
EXT(hipDestroyExternalMemory);
|
||||||
|
EXT(hipImportExternalSemaphore);
|
||||||
|
EXT(hipDestroyExternalSemaphore);
|
||||||
|
EXT(hipWaitExternalSemaphoresAsync);
|
||||||
|
EXT(hipSignalExternalSemaphoresAsync);
|
||||||
|
#undef EXT
|
||||||
|
};
|
||||||
|
struct Imported {
|
||||||
|
ExternalApi& api;
|
||||||
|
hipExternalMemory_t memory = nullptr;
|
||||||
|
hipExternalSemaphore_t semaphore = nullptr;
|
||||||
|
hipStream_t stream = nullptr;
|
||||||
|
bool owns_stream = true;
|
||||||
|
void* mapped = nullptr;
|
||||||
|
void* errors = nullptr;
|
||||||
|
~Imported() {
|
||||||
|
// No resource may be released while the HIP stream is still using it.
|
||||||
|
if (stream) (void)api.hip.hipStreamSynchronize(stream);
|
||||||
|
if (stream && owns_stream) (void)api.hip.hipStreamDestroy(stream);
|
||||||
|
if (errors) (void)api.hip.hipFree(errors);
|
||||||
|
if (mapped) (void)api.hip.hipFree(mapped);
|
||||||
|
if (memory) (void)api.hipDestroyExternalMemory(memory);
|
||||||
|
if (semaphore) (void)api.hipDestroyExternalSemaphore(semaphore);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
static D3D12_RESOURCE_DESC buffer_desc(uint64_t bytes)
|
||||||
|
{
|
||||||
|
D3D12_RESOURCE_DESC desc{};
|
||||||
|
desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
|
||||||
|
desc.Width = bytes; desc.Height = 1; desc.DepthOrArraySize = 1;
|
||||||
|
desc.MipLevels = 1; desc.SampleDesc.Count = 1;
|
||||||
|
desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
|
||||||
|
return desc;
|
||||||
|
}
|
||||||
|
static ComPtr<ID3D12Resource> make_buffer(ID3D12Device* device, uint64_t bytes,
|
||||||
|
D3D12_HEAP_TYPE type, D3D12_HEAP_FLAGS flags, D3D12_RESOURCE_STATES state)
|
||||||
|
{
|
||||||
|
D3D12_HEAP_PROPERTIES properties{};
|
||||||
|
properties.Type = type;
|
||||||
|
properties.CreationNodeMask = properties.VisibleNodeMask = 1;
|
||||||
|
auto desc = buffer_desc(bytes);
|
||||||
|
ComPtr<ID3D12Resource> resource;
|
||||||
|
dx(device->CreateCommittedResource(&properties, flags, &desc, state, nullptr,
|
||||||
|
IID_PPV_ARGS(resource.GetAddressOf())), "CreateCommittedResource(buffer)");
|
||||||
|
return resource;
|
||||||
|
}
|
||||||
|
static void transition(ID3D12GraphicsCommandList* list, ID3D12Resource* resource,
|
||||||
|
D3D12_RESOURCE_STATES before, D3D12_RESOURCE_STATES after)
|
||||||
|
{
|
||||||
|
D3D12_RESOURCE_BARRIER barrier{};
|
||||||
|
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
|
||||||
|
barrier.Transition.pResource = resource;
|
||||||
|
barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
|
||||||
|
barrier.Transition.StateBefore = before; barrier.Transition.StateAfter = after;
|
||||||
|
list->ResourceBarrier(1, &barrier);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void import_lifetime(ID3D12Device* device, ExternalApi& api, const Args& args)
|
||||||
|
{
|
||||||
|
if (args.interop_mode != "resource" && args.interop_mode != "import" && args.interop_mode != "map")
|
||||||
|
throw std::runtime_error("--interop-mode must be roundtrip, resource, import or map");
|
||||||
|
DWORD baseline = 0, handles = 0;
|
||||||
|
for (unsigned i = 0; i < args.iterations; ++i) {
|
||||||
|
{
|
||||||
|
auto resource = make_buffer(device, 65536, D3D12_HEAP_TYPE_DEFAULT,
|
||||||
|
D3D12_HEAP_FLAG_SHARED, D3D12_RESOURCE_STATE_COMMON);
|
||||||
|
NativeHandle handle;
|
||||||
|
dx(device->CreateSharedHandle(resource.Get(), nullptr, GENERIC_ALL, nullptr,
|
||||||
|
&handle.handle), "CreateSharedHandle(lifecycle)");
|
||||||
|
Imported imported{api};
|
||||||
|
if (args.interop_mode != "resource") {
|
||||||
|
hipExternalMemoryHandleDesc desc{};
|
||||||
|
desc.type = hipExternalMemoryHandleTypeD3D12Resource;
|
||||||
|
desc.handle.win32.handle = handle.handle;
|
||||||
|
desc.size = 65536;
|
||||||
|
desc.flags = hipExternalMemoryDedicated;
|
||||||
|
api.hip.check(api.hipImportExternalMemory(&imported.memory, &desc), "hipImportExternalMemory(lifecycle)");
|
||||||
|
if (args.interop_mode == "map") {
|
||||||
|
hipExternalMemoryBufferDesc map{}; map.size = 65536;
|
||||||
|
api.hip.check(api.hipExternalMemoryGetMappedBuffer(&imported.mapped, imported.memory, &map), "hipExternalMemoryGetMappedBuffer(lifecycle)");
|
||||||
|
api.hip.check(api.hip.hipFree(imported.mapped), "hipFree(lifecycle)"); imported.mapped = nullptr;
|
||||||
|
}
|
||||||
|
api.hip.check(api.hipDestroyExternalMemory(imported.memory), "hipDestroyExternalMemory(lifecycle)"); imported.memory = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!GetProcessHandleCount(GetCurrentProcess(), &handles)) throw std::runtime_error("GetProcessHandleCount failed");
|
||||||
|
size_t free_bytes = 0, total_bytes = 0;
|
||||||
|
api.hip.check(api.hip.hipMemGetInfo(&free_bytes, &total_bytes), "hipMemGetInfo");
|
||||||
|
if (i == 1 || (args.iterations == 1 && i == 0)) baseline = handles;
|
||||||
|
std::printf("IMPORT_CLEANUP mode=%s iteration=%u handles=%lu baseline=%lu free_vram=%zu\n",
|
||||||
|
args.interop_mode.c_str(), i, handles, baseline, free_bytes);
|
||||||
|
}
|
||||||
|
if (handles > baseline + 4) throw std::runtime_error("External memory lifecycle handle growth");
|
||||||
|
std::printf("PASS INTEROP_LIFETIME mode=%s iterations=%u\n", args.interop_mode.c_str(), args.iterations);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
start();
|
||||||
|
try {
|
||||||
|
Args args(argc, argv);
|
||||||
|
if (args.module.empty() && args.interop_mode == "roundtrip") throw std::runtime_error("--module required");
|
||||||
|
if (args.stream_lifetime != "persistent" && args.stream_lifetime != "cycle")
|
||||||
|
throw std::runtime_error("--stream-lifetime must be persistent or cycle");
|
||||||
|
HipApi hip(args.hip_root);
|
||||||
|
hipDeviceProp_t props{};
|
||||||
|
hip.select_gfx1201(args.device, props);
|
||||||
|
ExternalApi external{hip};
|
||||||
|
ComPtr<IDXGIFactory4> factory;
|
||||||
|
dx(CreateDXGIFactory1(IID_PPV_ARGS(factory.GetAddressOf())), "CreateDXGIFactory1");
|
||||||
|
ComPtr<IDXGIAdapter1> adapter;
|
||||||
|
bool matched = false;
|
||||||
|
for (UINT i = 0; ; ++i) {
|
||||||
|
const HRESULT enumerated = factory->EnumAdapters1(i, adapter.ReleaseAndGetAddressOf());
|
||||||
|
if (enumerated == DXGI_ERROR_NOT_FOUND) break;
|
||||||
|
dx(enumerated, "EnumAdapters1");
|
||||||
|
DXGI_ADAPTER_DESC1 desc{};
|
||||||
|
dx(adapter->GetDesc1(&desc), "GetDesc1");
|
||||||
|
std::printf("DXGI adapter=%s LUID=%08lx:%08lx\n", utf8(desc.Description).c_str(),
|
||||||
|
static_cast<unsigned long>(desc.AdapterLuid.HighPart), desc.AdapterLuid.LowPart);
|
||||||
|
if (std::memcmp(&desc.AdapterLuid, props.luid, sizeof(LUID)) == 0) { matched = true; break; }
|
||||||
|
}
|
||||||
|
if (!matched) throw std::runtime_error("HIP/DXGI LUID mismatch: cross-adapter import prohibited");
|
||||||
|
ComPtr<ID3D12Device> device;
|
||||||
|
dx(D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_12_0, IID_PPV_ARGS(device.GetAddressOf())), "D3D12CreateDevice");
|
||||||
|
if (args.interop_mode != "roundtrip") { import_lifetime(device.Get(), external, args); return 0; }
|
||||||
|
ComPtr<ID3D12CommandQueue> queue;
|
||||||
|
D3D12_COMMAND_QUEUE_DESC qd{};
|
||||||
|
qd.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
|
||||||
|
dx(device->CreateCommandQueue(&qd, IID_PPV_ARGS(queue.GetAddressOf())), "CreateCommandQueue");
|
||||||
|
ComPtr<ID3D12Fence> shared_fence, complete;
|
||||||
|
dx(device->CreateFence(0, D3D12_FENCE_FLAG_SHARED, IID_PPV_ARGS(shared_fence.GetAddressOf())), "CreateFence(shared)");
|
||||||
|
dx(device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(complete.GetAddressOf())), "CreateFence(completion)");
|
||||||
|
NativeHandle fence_handle, event;
|
||||||
|
event.handle = CreateEventW(nullptr, FALSE, FALSE, nullptr);
|
||||||
|
if (!event.handle) throw std::runtime_error("CreateEvent failed");
|
||||||
|
dx(device->CreateSharedHandle(shared_fence.Get(), nullptr, GENERIC_ALL, nullptr,
|
||||||
|
&fence_handle.handle), "CreateSharedHandle(fence)");
|
||||||
|
|
||||||
|
// R32_UINT texture -> linear shared buffer -> HIP -> texture -> verification readback.
|
||||||
|
constexpr UINT width = 256, height = 64, count = width * height;
|
||||||
|
const uint64_t bytes = static_cast<uint64_t>(count) * sizeof(uint32_t);
|
||||||
|
auto upload = make_buffer(device.Get(), bytes, D3D12_HEAP_TYPE_UPLOAD, D3D12_HEAP_FLAG_NONE, D3D12_RESOURCE_STATE_GENERIC_READ);
|
||||||
|
auto readback = make_buffer(device.Get(), bytes, D3D12_HEAP_TYPE_READBACK, D3D12_HEAP_FLAG_NONE, D3D12_RESOURCE_STATE_COPY_DEST);
|
||||||
|
D3D12_RESOURCE_DESC td{};
|
||||||
|
td.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
|
||||||
|
td.Width = width; td.Height = height; td.DepthOrArraySize = 1; td.MipLevels = 1;
|
||||||
|
td.SampleDesc.Count = 1; td.Format = DXGI_FORMAT_R32_UINT;
|
||||||
|
D3D12_HEAP_PROPERTIES hp{}; hp.Type = D3D12_HEAP_TYPE_DEFAULT;
|
||||||
|
hp.CreationNodeMask = hp.VisibleNodeMask = 1;
|
||||||
|
ComPtr<ID3D12Resource> texture;
|
||||||
|
dx(device->CreateCommittedResource(&hp, D3D12_HEAP_FLAG_NONE, &td, D3D12_RESOURCE_STATE_COPY_DEST,
|
||||||
|
nullptr, IID_PPV_ARGS(texture.GetAddressOf())), "CreateCommittedResource(texture)");
|
||||||
|
D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint{};
|
||||||
|
UINT rows; UINT64 row_bytes, total_bytes;
|
||||||
|
device->GetCopyableFootprints(&td, 0, 1, 0, &footprint, &rows, &row_bytes, &total_bytes);
|
||||||
|
if (total_bytes != bytes || footprint.Footprint.RowPitch != width * 4)
|
||||||
|
throw std::runtime_error("Unexpected footprint; test requires no padding");
|
||||||
|
auto texture_copy = D3D12_TEXTURE_COPY_LOCATION{};
|
||||||
|
texture_copy.pResource = texture.Get();
|
||||||
|
texture_copy.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
|
||||||
|
auto linear_copy = D3D12_TEXTURE_COPY_LOCATION{};
|
||||||
|
linear_copy.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
|
||||||
|
linear_copy.PlacedFootprint = footprint;
|
||||||
|
hipModule_t module = nullptr;
|
||||||
|
hip.check(hip.hipModuleLoad(&module, args.module.c_str()), "hipModuleLoad");
|
||||||
|
struct ModuleCleanup { HipApi& hip; hipModule_t m; ~ModuleCleanup() { (void)hip.hipModuleUnload(m); } } mc{hip, module};
|
||||||
|
hipFunction_t kernel = nullptr;
|
||||||
|
hip.check(hip.hipModuleGetFunction(&kernel, module, "d4r_probe_interop"), "hipModuleGetFunction");
|
||||||
|
struct StreamCleanup {
|
||||||
|
HipApi& hip;
|
||||||
|
hipStream_t stream = nullptr;
|
||||||
|
~StreamCleanup() { if (stream) {
|
||||||
|
(void)hip.hipStreamSynchronize(stream);
|
||||||
|
(void)hip.hipStreamDestroy(stream);
|
||||||
|
} }
|
||||||
|
} persistent{hip};
|
||||||
|
if (args.stream_lifetime == "persistent")
|
||||||
|
hip.check(hip.hipStreamCreateWithFlags(&persistent.stream, hipStreamNonBlocking), "hipStreamCreateWithFlags(persistent)");
|
||||||
|
loaded_modules();
|
||||||
|
DWORD handles_before = 0; GetProcessHandleCount(GetCurrentProcess(), &handles_before);
|
||||||
|
DWORD warmed_handles = 0;
|
||||||
|
DWORD final_cycle_handles = 0;
|
||||||
|
for (unsigned iteration = 0; iteration < args.iterations; ++iteration) {
|
||||||
|
const uint32_t seed = 0x12345678u + iteration * 31u;
|
||||||
|
auto shared = make_buffer(device.Get(), bytes, D3D12_HEAP_TYPE_DEFAULT, D3D12_HEAP_FLAG_SHARED,
|
||||||
|
D3D12_RESOURCE_STATE_COPY_DEST);
|
||||||
|
const auto shared_desc = buffer_desc(bytes);
|
||||||
|
D3D12_RESOURCE_ALLOCATION_INFO allocation_info{};
|
||||||
|
#if defined(__MINGW32__)
|
||||||
|
device->GetResourceAllocationInfo(&allocation_info, 0, 1, &shared_desc);
|
||||||
|
#else
|
||||||
|
allocation_info = device->GetResourceAllocationInfo(0, 1, &shared_desc);
|
||||||
|
#endif
|
||||||
|
NativeHandle memory_handle;
|
||||||
|
dx(device->CreateSharedHandle(shared.Get(), nullptr, GENERIC_ALL, nullptr,
|
||||||
|
&memory_handle.handle), "CreateSharedHandle(resource)");
|
||||||
|
Imported imported{external};
|
||||||
|
handle_checkpoint("created_resource");
|
||||||
|
hipExternalMemoryHandleDesc memory_desc{};
|
||||||
|
memory_desc.type = hipExternalMemoryHandleTypeD3D12Resource;
|
||||||
|
memory_desc.handle.win32.handle = memory_handle.handle;
|
||||||
|
memory_desc.size = allocation_info.SizeInBytes;
|
||||||
|
memory_desc.flags = hipExternalMemoryDedicated;
|
||||||
|
std::printf("STAGE hipImportExternalMemory(D3D12Resource) size=%llu\n", memory_desc.size);
|
||||||
|
hip.check(external.hipImportExternalMemory(&imported.memory, &memory_desc), "hipImportExternalMemory(D3D12Resource)");
|
||||||
|
handle_checkpoint("imported_memory");
|
||||||
|
hipExternalMemoryBufferDesc map_desc{}; map_desc.size = bytes;
|
||||||
|
hip.check(external.hipExternalMemoryGetMappedBuffer(&imported.mapped, imported.memory, &map_desc), "hipExternalMemoryGetMappedBuffer");
|
||||||
|
handle_checkpoint("mapped_memory");
|
||||||
|
hipExternalSemaphoreHandleDesc sem_desc{};
|
||||||
|
sem_desc.type = hipExternalSemaphoreHandleTypeD3D12Fence;
|
||||||
|
sem_desc.handle.win32.handle = fence_handle.handle;
|
||||||
|
std::printf("STAGE hipImportExternalSemaphore(D3D12Fence)\n");
|
||||||
|
hip.check(external.hipImportExternalSemaphore(&imported.semaphore, &sem_desc), "hipImportExternalSemaphore(D3D12Fence)");
|
||||||
|
handle_checkpoint("imported_semaphore");
|
||||||
|
if (persistent.stream) { imported.stream = persistent.stream; imported.owns_stream = false; }
|
||||||
|
else hip.check(hip.hipStreamCreateWithFlags(&imported.stream, hipStreamNonBlocking), "hipStreamCreateWithFlags");
|
||||||
|
handle_checkpoint("created_stream");
|
||||||
|
hip.check(hip.hipMalloc(&imported.errors, sizeof(uint32_t)), "hipMalloc(errors)");
|
||||||
|
const uint32_t zero = 0;
|
||||||
|
hip.check(hip.hipMemcpy(imported.errors, &zero, sizeof(zero), hipMemcpyHostToDevice), "hipMemcpy(error initialization)");
|
||||||
|
uint32_t* host_upload = nullptr;
|
||||||
|
D3D12_RANGE no_read{0, 0};
|
||||||
|
dx(upload->Map(0, &no_read, reinterpret_cast<void**>(&host_upload)), "Map(upload initialization)");
|
||||||
|
for (uint32_t i = 0; i < count; ++i) host_upload[i] = pattern(i, seed);
|
||||||
|
D3D12_RANGE written{0, static_cast<SIZE_T>(bytes)}; upload->Unmap(0, &written);
|
||||||
|
ComPtr<ID3D12CommandAllocator> allocator;
|
||||||
|
ComPtr<ID3D12GraphicsCommandList> first, second;
|
||||||
|
dx(device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(allocator.GetAddressOf())), "CreateCommandAllocator");
|
||||||
|
dx(device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, allocator.Get(), nullptr,
|
||||||
|
IID_PPV_ARGS(first.GetAddressOf())), "CreateCommandList(input)");
|
||||||
|
if (iteration) transition(first.Get(), texture.Get(), D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_COPY_DEST);
|
||||||
|
linear_copy.pResource = upload.Get();
|
||||||
|
first->CopyTextureRegion(&texture_copy, 0, 0, 0, &linear_copy, nullptr);
|
||||||
|
transition(first.Get(), texture.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_COPY_SOURCE);
|
||||||
|
linear_copy.pResource = shared.Get();
|
||||||
|
first->CopyTextureRegion(&linear_copy, 0, 0, 0, &texture_copy, nullptr);
|
||||||
|
transition(first.Get(), shared.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_COMMON);
|
||||||
|
dx(first->Close(), "Close(input)");
|
||||||
|
ID3D12CommandList* submit[] = {first.Get()}; queue->ExecuteCommandLists(1, submit);
|
||||||
|
const UINT64 ready = 2ull * iteration + 1, done = ready + 1;
|
||||||
|
dx(queue->Signal(shared_fence.Get(), ready), "Signal(D3D12 input ready)");
|
||||||
|
hipExternalSemaphoreWaitParams wait{}; wait.params.fence.value = ready;
|
||||||
|
hip.check(external.hipWaitExternalSemaphoresAsync(&imported.semaphore, &wait, 1, imported.stream), "hipWaitExternalSemaphoresAsync");
|
||||||
|
uint32_t n = count, mutable_seed = seed;
|
||||||
|
void* parameters[] = {&imported.mapped, &n, &mutable_seed, &imported.errors};
|
||||||
|
hip.check(hip.hipModuleLaunchKernel(kernel, (n + 127) / 128, 1, 1, 128, 1, 1, 0,
|
||||||
|
imported.stream, parameters, nullptr), "hipModuleLaunchKernel(shared VRAM)");
|
||||||
|
hipExternalSemaphoreSignalParams signal{}; signal.params.fence.value = done;
|
||||||
|
hip.check(external.hipSignalExternalSemaphoresAsync(&imported.semaphore, &signal, 1, imported.stream), "hipSignalExternalSemaphoresAsync");
|
||||||
|
dx(device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, allocator.Get(), nullptr,
|
||||||
|
IID_PPV_ARGS(second.GetAddressOf())), "CreateCommandList(output)");
|
||||||
|
transition(second.Get(), shared.Get(), D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_COPY_SOURCE);
|
||||||
|
transition(second.Get(), texture.Get(), D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_COPY_DEST);
|
||||||
|
second->CopyTextureRegion(&texture_copy, 0, 0, 0, &linear_copy, nullptr);
|
||||||
|
transition(second.Get(), texture.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_COPY_SOURCE);
|
||||||
|
linear_copy.pResource = readback.Get();
|
||||||
|
second->CopyTextureRegion(&linear_copy, 0, 0, 0, &texture_copy, nullptr);
|
||||||
|
dx(second->Close(), "Close(output)");
|
||||||
|
dx(queue->Wait(shared_fence.Get(), done), "Wait(HIP output ready)");
|
||||||
|
submit[0] = second.Get(); queue->ExecuteCommandLists(1, submit);
|
||||||
|
dx(queue->Signal(complete.Get(), iteration + 1), "Signal(completion)");
|
||||||
|
dx(complete->SetEventOnCompletion(iteration + 1, event.handle), "SetEventOnCompletion");
|
||||||
|
if (WaitForSingleObject(event.handle, 30000) != WAIT_OBJECT_0) throw std::runtime_error("D3D12 fence timeout");
|
||||||
|
hip.check(hip.hipStreamSynchronize(imported.stream), "hipStreamSynchronize");
|
||||||
|
uint32_t errors = ~0u;
|
||||||
|
hip.check(hip.hipMemcpy(&errors, imported.errors, sizeof(errors), hipMemcpyDeviceToHost), "hipMemcpy(error verification)");
|
||||||
|
if (errors) throw std::runtime_error("HIP input mismatch count=" + std::to_string(errors));
|
||||||
|
uint32_t* host_result = nullptr;
|
||||||
|
D3D12_RANGE read{0, static_cast<SIZE_T>(bytes)};
|
||||||
|
dx(readback->Map(0, &read, reinterpret_cast<void**>(&host_result)), "Map(final verification)");
|
||||||
|
bool good = true;
|
||||||
|
for (uint32_t i = 0; i < count; ++i) if (host_result[i] != (pattern(i, seed) ^ 0x3579bdf1u)) { good = false; break; }
|
||||||
|
readback->Unmap(0, &no_read);
|
||||||
|
if (!good) throw std::runtime_error("D3D12 output mismatch");
|
||||||
|
// Check each release in the successful path; RAII handles all failures.
|
||||||
|
if (!persistent.stream) hip.check(hip.hipStreamDestroy(imported.stream), "hipStreamDestroy");
|
||||||
|
imported.stream = nullptr;
|
||||||
|
handle_checkpoint("released_stream_reference");
|
||||||
|
hip.check(hip.hipFree(imported.errors), "hipFree(errors)"); imported.errors = nullptr;
|
||||||
|
handle_checkpoint("freed_errors");
|
||||||
|
hip.check(hip.hipFree(imported.mapped), "hipFree(external mapping)"); imported.mapped = nullptr;
|
||||||
|
handle_checkpoint("freed_mapping");
|
||||||
|
hip.check(external.hipDestroyExternalMemory(imported.memory), "hipDestroyExternalMemory"); imported.memory = nullptr;
|
||||||
|
handle_checkpoint("destroyed_memory");
|
||||||
|
hip.check(external.hipDestroyExternalSemaphore(imported.semaphore), "hipDestroyExternalSemaphore"); imported.semaphore = nullptr;
|
||||||
|
handle_checkpoint("destroyed_semaphore");
|
||||||
|
shared.Reset();
|
||||||
|
if (!CloseHandle(memory_handle.handle)) throw std::runtime_error("CloseHandle(shared memory) failed");
|
||||||
|
memory_handle.handle = nullptr;
|
||||||
|
first.Reset(); second.Reset(); allocator.Reset();
|
||||||
|
DWORD cycle_handles = 0; GetProcessHandleCount(GetCurrentProcess(), &cycle_handles);
|
||||||
|
if (iteration == 0) warmed_handles = cycle_handles;
|
||||||
|
final_cycle_handles = cycle_handles;
|
||||||
|
std::printf("CLEANUP iteration=%u handles=%lu warmed_baseline=%lu\n", iteration, cycle_handles, warmed_handles);
|
||||||
|
std::printf("ROUNDTRIP iteration=%u verified=%u vram_transfer=1 cpu_staging_between_apis=0\n", iteration, count);
|
||||||
|
}
|
||||||
|
if (persistent.stream) {
|
||||||
|
hip.check(hip.hipStreamDestroy(persistent.stream), "hipStreamDestroy(persistent)");
|
||||||
|
persistent.stream = nullptr;
|
||||||
|
}
|
||||||
|
DWORD handles_after = 0; GetProcessHandleCount(GetCurrentProcess(), &handles_after);
|
||||||
|
std::printf("HANDLES before=%lu after=%lu\n", handles_before, handles_after);
|
||||||
|
if (final_cycle_handles > warmed_handles + 4) throw std::runtime_error("Handle count grew after first-cycle warmup");
|
||||||
|
std::printf("PASS INTEROP architecture=gfx1201 iterations=%u texture=R32_UINT external_fence=1\n", args.iterations);
|
||||||
|
return 0;
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
std::fprintf(stderr, "FAIL INTEROP %s\n", e.what());
|
||||||
|
loaded_modules();
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -84,6 +84,8 @@ inline void start()
|
|||||||
}
|
}
|
||||||
struct Args {
|
struct Args {
|
||||||
std::string hip_root, cuda_dll, module, context = "primary";
|
std::string hip_root, cuda_dll, module, context = "primary";
|
||||||
|
std::string stream_lifetime = "persistent";
|
||||||
|
std::string interop_mode = "roundtrip";
|
||||||
unsigned iterations = 32;
|
unsigned iterations = 32;
|
||||||
int device = -1;
|
int device = -1;
|
||||||
Args(int argc, char** argv) {
|
Args(int argc, char** argv) {
|
||||||
@@ -95,6 +97,8 @@ struct Args {
|
|||||||
else if (key == "--cuda-dll") cuda_dll = value;
|
else if (key == "--cuda-dll") cuda_dll = value;
|
||||||
else if (key == "--module") module = value;
|
else if (key == "--module") module = value;
|
||||||
else if (key == "--context") context = value;
|
else if (key == "--context") context = value;
|
||||||
|
else if (key == "--stream-lifetime") stream_lifetime = value;
|
||||||
|
else if (key == "--interop-mode") interop_mode = value;
|
||||||
else if (key == "--iterations") {
|
else if (key == "--iterations") {
|
||||||
size_t end = 0;
|
size_t end = 0;
|
||||||
iterations = static_cast<unsigned>(std::stoul(value, &end));
|
iterations = static_cast<unsigned>(std::stoul(value, &end));
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ struct HipApi {
|
|||||||
D4R_HIP_FUNCTION(hipModuleGetFunction);
|
D4R_HIP_FUNCTION(hipModuleGetFunction);
|
||||||
D4R_HIP_FUNCTION(hipModuleLaunchKernel);
|
D4R_HIP_FUNCTION(hipModuleLaunchKernel);
|
||||||
D4R_HIP_FUNCTION(hipDeviceSynchronize);
|
D4R_HIP_FUNCTION(hipDeviceSynchronize);
|
||||||
|
D4R_HIP_FUNCTION(hipStreamCreateWithFlags);
|
||||||
|
D4R_HIP_FUNCTION(hipStreamSynchronize);
|
||||||
|
D4R_HIP_FUNCTION(hipStreamDestroy);
|
||||||
#undef D4R_HIP_FUNCTION
|
#undef D4R_HIP_FUNCTION
|
||||||
explicit HipApi(const std::string& root) :
|
explicit HipApi(const std::string& root) :
|
||||||
search(std::filesystem::path(wide(root)) / L"bin"),
|
search(std::filesystem::path(wide(root)) / L"bin"),
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
#include "hip_api.h"
|
||||||
|
|
||||||
|
// Isolate Windows HIP stream lifetime from D3D12 and external-resource imports.
|
||||||
|
int main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
using namespace d4r::diag;
|
||||||
|
start();
|
||||||
|
try {
|
||||||
|
Args args(argc, argv);
|
||||||
|
HipApi hip(args.hip_root);
|
||||||
|
hipDeviceProp_t props{};
|
||||||
|
hip.select_gfx1201(args.device, props);
|
||||||
|
hipModule_t module = nullptr;
|
||||||
|
hipFunction_t kernel = nullptr;
|
||||||
|
void* output = nullptr;
|
||||||
|
struct ResourceCleanup {
|
||||||
|
HipApi& hip; hipModule_t& module; void*& output;
|
||||||
|
~ResourceCleanup() {
|
||||||
|
if (output) (void)hip.hipFree(output);
|
||||||
|
if (module) (void)hip.hipModuleUnload(module);
|
||||||
|
}
|
||||||
|
} resources{hip, module, output};
|
||||||
|
if (!args.module.empty()) {
|
||||||
|
hip.check(hip.hipModuleLoad(&module, args.module.c_str()), "hipModuleLoad");
|
||||||
|
hip.check(hip.hipModuleGetFunction(&kernel, module, "d4r_probe_pattern"), "hipModuleGetFunction");
|
||||||
|
hip.check(hip.hipMalloc(&output, 128 * sizeof(uint32_t)), "hipMalloc");
|
||||||
|
}
|
||||||
|
DWORD baseline = 0, count = 0;
|
||||||
|
for (unsigned i = 0; i < args.iterations; ++i) {
|
||||||
|
hipStream_t stream = nullptr;
|
||||||
|
hip.check(hip.hipStreamCreateWithFlags(&stream, hipStreamNonBlocking), "hipStreamCreateWithFlags");
|
||||||
|
struct StreamCleanup {
|
||||||
|
HipApi& hip; hipStream_t& stream;
|
||||||
|
~StreamCleanup() { if (stream) { (void)hip.hipStreamSynchronize(stream); (void)hip.hipStreamDestroy(stream); } }
|
||||||
|
} cleanup{hip, stream};
|
||||||
|
if (kernel) {
|
||||||
|
uint32_t n = 128, seed = i;
|
||||||
|
void* parameters[] = {&output, &n, &seed};
|
||||||
|
hip.check(hip.hipModuleLaunchKernel(kernel, 1, 1, 1, 128, 1, 1, 0,
|
||||||
|
stream, parameters, nullptr), "hipModuleLaunchKernel");
|
||||||
|
}
|
||||||
|
hip.check(hip.hipStreamSynchronize(stream), "hipStreamSynchronize");
|
||||||
|
hip.check(hip.hipStreamDestroy(stream), "hipStreamDestroy");
|
||||||
|
stream = nullptr;
|
||||||
|
if (!GetProcessHandleCount(GetCurrentProcess(), &count))
|
||||||
|
throw std::runtime_error("GetProcessHandleCount failed");
|
||||||
|
if (i == 1 || (args.iterations == 1 && i == 0)) baseline = count;
|
||||||
|
std::printf("STREAM_CLEANUP iteration=%u handles=%lu baseline=%lu\n", i, count, baseline);
|
||||||
|
}
|
||||||
|
if (output) { hip.check(hip.hipFree(output), "hipFree"); output = nullptr; }
|
||||||
|
if (module) { hip.check(hip.hipModuleUnload(module), "hipModuleUnload"); module = nullptr; }
|
||||||
|
if (count > baseline + 4) throw std::runtime_error("HIP stream lifecycle handle growth");
|
||||||
|
std::printf("PASS HIP_STREAM iterations=%u handles=%lu\n", args.iterations, count);
|
||||||
|
return 0;
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
std::fprintf(stderr, "FAIL HIP_STREAM %s\n", e.what());
|
||||||
|
loaded_modules();
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,3 +7,15 @@ extern "C" __attribute__((global)) void d4r_probe_pattern(
|
|||||||
if (i < count)
|
if (i < count)
|
||||||
output[i] = (i * 1664525u + seed) ^ 0xa5a55a5au;
|
output[i] = (i * 1664525u + seed) ^ 0xa5a55a5au;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern "C" __attribute__((global)) void d4r_probe_interop(
|
||||||
|
unsigned int* shared, unsigned int count, unsigned int seed, unsigned int* errors)
|
||||||
|
{
|
||||||
|
const unsigned int i = __builtin_amdgcn_workgroup_id_x() * 128u +
|
||||||
|
__builtin_amdgcn_workitem_id_x();
|
||||||
|
if (i < count) {
|
||||||
|
const unsigned int expected = (i * 1664525u + seed) ^ 0xa5a55a5au;
|
||||||
|
if (shared[i] != expected) __atomic_fetch_add(errors, 1u, __ATOMIC_RELAXED);
|
||||||
|
shared[i] ^= 0x3579bdf1u;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user