Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/rand/rocRAND.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,12 @@ include("error.jl")
include("librocrand.jl")

function version()
s = string(ROCRAND_VERSION)
VersionNumber(join([string(s[1]), s[2:4], s[5:end]], '.'))
# Query the loaded library at runtime rather than reporting the compile-time
# `ROCRAND_VERSION` constant, so `versioninfo` shows the installed version.
v = Ref{Cint}()
rocrand_get_version(v)
ver = Int(v[])
VersionNumber(ver ÷ 100_000, (ver ÷ 100) % 1000, ver % 100)
end

# stdlib Random integration
Expand Down
55 changes: 54 additions & 1 deletion src/utils.jl
Original file line number Diff line number Diff line change
@@ -1,3 +1,39 @@
# Run `code` in a short-lived subprocess and return its stdout, or `nothing` if
# it crashed, timed out, or exited nonzero. Isolates version probes that can
# segfault on broken ROCm installs, where SIGSEGV isn't catchable in-process.
function _version_subprocess(code::String; timeout::Real = 60)
project = Base.active_project()
projarg = project === nothing ? "@." : dirname(project)
cmd = `$(Base.julia_cmd()) --startup-file=no --project=$projarg -e $code`
out = IOBuffer()
try
proc = run(pipeline(ignorestatus(cmd); stdout = out, stderr = devnull); wait = false)
timedout = Ref(false)
timer = Timer(timeout) do _
if process_running(proc)
timedout[] = true
kill(proc)
end
end
wait(proc)
close(timer)
(timedout[] || !success(proc)) && return nothing
v = strip(String(take!(out)))
return isempty(v) ? nothing : v
catch
return nothing
end
end

# rocSPARSE's version query needs a handle, which inits a HIP context and can
# segfault on broken ROCm installs (issue #920). Probe it out-of-process so a
# crash degrades to `"err"` instead of killing the session.
_rocsparse_version_isolated(; timeout::Real = 60) = _version_subprocess("""
using AMDGPU
AMDGPU.functional(:rocsparse) || exit(2)
print(AMDGPU.rocSPARSE.version())
"""; timeout)

"""
versioninfo(io::IO=stdout)

Expand All @@ -10,13 +46,23 @@ function versioninfo(io::IO=stdout)
_status(st::Bool) = st ? "+" : "-"
_libpath(p::String) = isempty(p) ? "-" : p
_ver(lib::Symbol, ver_fn) = functional(lib) ? "$(ver_fn())" : "-"

# `"err"` = present but the out-of-process version probe crashed/timed out.
rocsparse_failed = false
rocsparse_ver = if functional(:rocsparse)
v = _rocsparse_version_isolated()
v === nothing ? (rocsparse_failed = true; "err") : v
else
"-"
end

data = String[
_status(functional(:lld)) "LLD" "-" _libpath(lld_path);
_status(functional(:device_libs)) "Device Libraries" "-" _libpath(libdevice_libs);
_status(functional(:hip)) "HIP" _ver(:hip, HIP.runtime_version) _libpath(libhip);
_status(functional(:rocblas)) "rocBLAS" _ver(:rocblas, rocBLAS.version) _libpath(librocblas);
_status(functional(:rocsolver)) "rocSOLVER" _ver(:rocsolver, rocSOLVER.version) _libpath(librocsolver);
_status(functional(:rocsparse)) "rocSPARSE" _ver(:rocsparse, rocSPARSE.version) _libpath(librocsparse);
_status(functional(:rocsparse)) "rocSPARSE" rocsparse_ver _libpath(librocsparse);
_status(functional(:rocrand)) "rocRAND" _ver(:rocrand, rocRAND.version) _libpath(librocrand);
_status(functional(:rocfft)) "rocFFT" _ver(:rocfft, rocFFT.version) _libpath(librocfft);
_status(functional(:MIOpen)) "MIOpen" _ver(:MIOpen, MIOpen.version) _libpath(libMIOpen_path);
Expand All @@ -26,6 +72,13 @@ function versioninfo(io::IO=stdout)
"Available", "Name", "Version", "Path"],
alignment=[:c, :l, :l, :l])

if rocsparse_failed
@warn """rocSPARSE is installed but its version query failed (it ran in an \
isolated subprocess and crashed or timed out). This usually indicates a \
broken or mismatched ROCm install. See \
https://github.com/JuliaGPU/AMDGPU.jl/issues/920."""
end

if functional(:hip)
println(io)
println(io, "AMDGPU devices")
Expand Down
23 changes: 23 additions & 0 deletions test/core/core_tests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,29 @@ using AMDGPU: HIP, Runtime, Device, Mem
@test AMDGPU.functional() isa Bool
end

@testset "versioninfo probe isolation" begin
probe(code; timeout = 60) = AMDGPU._version_subprocess(code; timeout)

# A clean child returns its stdout.
@test probe("print(\"4.2.0\")") == "4.2.0"

# A failing child degrades to `nothing` without taking down this process —
# the point of the isolation: a SIGSEGV in a vendor library (issue #920)
# must not crash the caller.
@test probe("ccall(:abort, Cvoid, ())") === nothing # SIGABRT
@test probe("unsafe_store!(Ptr{Int}(0), 0)") === nothing # SIGSEGV
@test probe("exit(2)") === nothing # nonzero exit
@test probe("1 + 1") === nothing # no output
@test probe("while true; end"; timeout = 2) === nothing # hang -> timeout

# On a working setup the real rocSPARSE probe returns a parseable version.
if AMDGPU.functional(:rocsparse)
v = AMDGPU._rocsparse_version_isolated()
@test v !== nothing
@test tryparse(VersionNumber, v) !== nothing
end
end

@testset "HIPDevice" begin
@testset "Device props" begin
devices = AMDGPU.devices()
Expand Down