From 3cac3747f3b0ec9c0e8b19575db3d3fe65d7512c Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Thu, 5 Nov 2026 11:05:10 +0100 Subject: [PATCH] Make failure dumps downloadable from CI When a back-end or SPIR-V validation/optimization fails, the module is written to a temporary file to attach to a bug report. On CI that file disappears with the runner, so a failure that doesn't reproduce locally can't be debugged. Write these dumps through one helper that, on GitHub Actions, puts them in `$RUNNER_TEMP/gpucompiler-dumps` (with a notice) for an `actions/upload-artifact` step to collect, and on Buildkite uploads them as artifacts. The input and output of a failed SPIR-V step now share a base name. Unlike the dump removed in #861, this only runs once a compilation has failed, so it costs nothing otherwise. --- src/spirv.jl | 13 +++---------- src/utils.jl | 37 +++++++++++++++++++++++++++++++++++-- test/utils.jl | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 12 deletions(-) diff --git a/src/spirv.jl b/src/spirv.jl index 06331866..b653d313 100644 --- a/src/spirv.jl +++ b/src/spirv.jl @@ -323,10 +323,6 @@ end # translate to SPIR-V input = bitcode(mod) - dump_input() = let path = tempname(cleanup=false) * ".bc" - write(path, input) - path - end spirv = if target.backend === :llvm # compile in-process through libspirv. the back-end derives the triple from the # options; unlike `llc`, it rejects unknown extensions instead of ignoring them. @@ -355,16 +351,13 @@ end error("Unsupported SPIR-V back-end $(repr(target.backend)); expected :llvm or :khronos.") end - dump_output(output) = let path = tempname(cleanup=false) * ".spv" - write(path, output) - path - end + dump_files(output) = join(dump_failure(".bc" => input, ".spv" => output), " and ") if target.validate message = spirv_validate(spirv) if message !== nothing error("""Failed to validate generated SPIR-V: $message - If you think this is a bug, please file an issue and attach $(dump_input()) and $(dump_output(spirv)).""") + If you think this is a bug, please file an issue and attach $(dump_files(spirv)).""") end end @@ -372,7 +365,7 @@ end optimized = spirv_optimize(spirv) if optimized === nothing error("""Failed to optimize generated SPIR-V. - If you think this is a bug, please file an issue and attach $(dump_input()) and $(dump_output(spirv)).""") + If you think this is a bug, please file an issue and attach $(dump_files(spirv)).""") end spirv = optimized end diff --git a/src/utils.jl b/src/utils.jl index 02758d79..a6d91681 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -114,8 +114,7 @@ function external_result(backend::ExternalBackend, status, what::String, message diagnostics::Vector{String}, input::Vector{UInt8}; warn=(msg)->@warn(msg)) if status != 0 - path = tempname(cleanup=false) * ".bc" - write(path, input) + path, = dump_failure(".bc" => input) msg = what message == C_NULL || (msg *= ":\n" * take_message(backend, message)) isempty(diagnostics) || (msg *= "\n" * join(diagnostics, "\n")) @@ -214,6 +213,40 @@ macro safe_show(exs...) end +## failure dumps + +# Write the inputs and outputs of a failed compilation step (`extension => data` pairs, which +# share a random base name) to files that can be attached to a bug report, and return their +# paths. Such failures often only happen on CI, so make the files downloadable there: on +# GitHub Actions they are written to `$RUNNER_TEMP/gpucompiler-dumps` for an +# `actions/upload-artifact` step to pick up, and on Buildkite they are uploaded as artifacts. +function dump_failure(artifacts::Pair{String}...) + on_github = get(ENV, "GITHUB_ACTIONS", "false") == "true" && haskey(ENV, "RUNNER_TEMP") + dir = on_github ? mkpath(joinpath(ENV["RUNNER_TEMP"], "gpucompiler-dumps")) : tempdir() + stem = tempname(dir; cleanup=false) + paths = String[] + for (ext, data) in artifacts + path = stem * ext + write(path, data) + push!(paths, path) + end + + if on_github + println("::notice title=GPUCompiler failure dump::wrote $(join(basename.(paths), ", ")) to $dir") + elseif get(ENV, "BUILDKITE", "false") == "true" + for path in paths + try + run(`buildkite-agent artifact upload $path`) + catch err + @safe_warn "Failed to upload $path as a Buildkite artifact" exception=err + end + end + end + + return paths +end + + ## safe deprecation warnings const depwarn_lock = Threads.SpinLock() diff --git a/test/utils.jl b/test/utils.jl index c36f2c40..95b07a81 100644 --- a/test/utils.jl +++ b/test/utils.jl @@ -55,6 +55,56 @@ end end end +@testset "failure dumps" begin + # files of one dump share a base name, wherever they are written + function check_dump(paths, dir) + @test length(paths) == 2 + @test all(path -> dirname(path) == dir, paths) + @test first.(splitext.(paths))[1] == first.(splitext.(paths))[2] + @test read(paths[1]) == UInt8[1, 2, 3] + @test read(paths[2], String) == "output" + rm.(paths) + end + + withenv("GITHUB_ACTIONS" => nothing, "BUILDKITE" => nothing) do + paths = GPUCompiler.dump_failure(".bc" => UInt8[1, 2, 3], ".spv" => "output") + check_dump(paths, tempdir()) + end + + # on GitHub Actions, next to the runner's other temporary files, with a notice + mktempdir() do runner_temp + withenv("GITHUB_ACTIONS" => "true", "RUNNER_TEMP" => runner_temp, + "BUILDKITE" => nothing) do + paths, output = mktemp() do log, io + paths = redirect_stdout(io) do + GPUCompiler.dump_failure(".bc" => UInt8[1, 2, 3], ".spv" => "output") + end + flush(io) + paths, read(log, String) + end + dir = joinpath(runner_temp, "gpucompiler-dumps") + @test startswith(output, "::notice") + @test all(path -> occursin(basename(path), output), paths) + check_dump(paths, dir) + + # failures of the external back-ends point at their dump + backend = GPUCompiler.ExternalBackend("libdoesnotexist", "Test") + err = try + redirect_stdout(devnull) do + GPUCompiler.external_result(backend, 1, "Failed to compile", Cstring(C_NULL), + String[], UInt8[1, 2, 3]) + end + nothing + catch err + err + end + @test err isa ErrorException + m = match(r"attach (\S+\.bc)\.", err.msg) + @test m !== nothing && dirname(m[1]) == dir && read(m[1]) == UInt8[1, 2, 3] + end + end +end + using Logging safe_log() = GPUCompiler.@safe_debug "safe_log"