Fix line number handling in @kernel - #734
Conversation
Benchmark ResultsShow table
Benchmark PlotsA plot of the benchmark results have been uploaded as an artifact to the workflow run for this PR. |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #734 +/- ##
==========================================
+ Coverage 63.73% 63.97% +0.24%
==========================================
Files 23 23
Lines 1941 1960 +19
==========================================
+ Hits 1237 1254 +17
- Misses 704 706 +2 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
222425e to
c251578
Compare
|
Rebased onto The coverage test passed locally and on the Windows job, which runs The subprocess command now strips the inherited flags and requests |
c251578 to
ec188ef
Compare
The `@kernel` expansion spliced `LineNumberNode`s pointing into KernelAbstractions' own sources into the generated code, and dropped some of the user's line information entirely. Coverage and profiling tools then attributed kernel bodies to `src/macros.jl` instead of the user's file. Three separate causes: - `emit` built the workitem loop with a `quote` block, so every emitted block started with a `macros.jl` line node. Build the expression directly instead. - `MacroTools.unblock` strips line nodes when collapsing a single-statement block, which silently discarded the line of the first statement after a `@synchronize`. Use a variant that only unwraps blocks without line information. - `split` hoists `@uniform`/`@localmem`/`@private` out of the workitem loop, leaving their line nodes behind in the loop body. Track the pending line node and move it along with the statement. The generated constructor functions are now attributed to the `@kernel` call site rather than to `macros.jl`, and the kernel-language macros (`@groupsize`, `@ndrange`, `@localmem`, `@private`, `@synchronize`, `@print`) return bare expressions so they no longer inject `KernelAbstractions.jl` line nodes into kernel bodies. Fixes #732 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Runs a script defining two kernels in a subprocess under `--code-coverage`, then asserts that every line of both kernel bodies is reported as tracked in the resulting LCOV tracefile. GPUCompiler records device coverage by visiting the source location of each `:code_coverage_effect` while compiling (`record_coverage` in jlgen.jl), so this exercises the actual consumer of the line information that `@kernel` emits. Against the previous macro expansion it fails on five lines: both `@kernel function` signatures, the hoisted `@localmem`, the `@synchronize` and the statement following it. Coverage is written to an LCOV tracefile inside a temporary directory rather than using `--code-coverage=user`, which would drop a `.cov` file next to every tracked source file in both the checkout and the depot, and would perturb the outer report when the suite runs under `Pkg.test(coverage=true)`. Note the `=@path` mode that `Pkg.test(coverage=true)` selects does not record device coverage at all, independently of this change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`Base.julia_cmd()` forwards the parent's `--code-coverage` flags. Under `Pkg.test(coverage=true)`, as run by julia-runtest on CI, that is `--code-coverage=@<pkgroot>`, which left the subprocess in path-tracking mode: the script lives outside the package root, and GPUCompiler only records device coverage in `user` and `all` mode. Every kernel line was therefore reported as untracked. Strip the inherited flags and request `user` mode explicitly, so the subprocess behaves the same regardless of how the suite itself is run. Assisted-by: Claude Code (Fable 5.1)
ec188ef to
3084d86
Compare
|
This may have broken Oceananigans on GPU, now most kernels fail to compile with errors like https://buildkite.com/clima/oceananigans/builds/33494#01a09aec-7e20-4cfa-acd5-ba89acad5023/L9 Edit: uhm, maybe not this PR because CliMA/Oceananigans.jl#5799 is currently checking out 4135768, but it's something recent, it was working on 41a7b23: 41a7b23...4135768 (can't really see anything related to debug info in this range though). Edit 2: maybe the issue is from JuliaGPU/CUDA.jl#2944, I'm checking out the branch not a specific revision (that branch is being rebased, so having fixed revision wouldn't work very well....) |
|
Do you have the ptx file? |
|
I tried to define simple kernels with greek letters sprinkled everywhere (kernel name, variable names), but couldn't reproduce the error locally. I'll try to reduce the error we're seeing in Oceananigans tests and open an issue, in any case it's most definitely not related to this PR as the failure predates this PR being merged (but it should somehow have to do with debuginfo because |
|
Turned out to be a bug in CUDA.jl 6.4: JuliaGPU/CUDA.jl#3274 |
Fixes #732.
@kernelsplicedLineNumberNodes pointing into KernelAbstractions' own sources into the generated code, and dropped some of the user's line information entirely. GPUCompiler emits device coverage by walking the debug info of:code_coverage_effectstatements at compile time (record_coverageinjlgen.jl), so bad line info directly becomes bad coverage: kernel lines get reported as untracked, and KernelAbstractions' own sources get credited instead.Reproduction
kernels.jl, run underjulia --code-coverage, againstCPU()(POCL):Before — five of the kernels' lines are reported as untracked (
-) even though both kernels were compiled and run:After — every line is accounted for:
The lost coverage did not vanish, it was charged to
src/macros.jl. From the same pre-fix run:Causes
emitbuilt the workitem loop with aquoteblock, so every emitted block started with amacros.jlline node. It now builds the expression directly.MacroTools.unblockstrips line nodes when it collapses a single-statement block. This silently discarded the line of the first statement after a@synchronize— that statement ended up with no line information at all, which is whya[i] = lm[i] + 1above reads as untracked. Replaced with a variant that only unwraps blocks carrying no line info.splithoists@uniform/@localmem/@privateout of the workitem loop but left their line nodes behind in the loop body. The pending line node is now tracked and moved along with the statement.Additionally:
@kernelcall site, so the@kernel function …line is attributed to the user's file instead ofmacros.jl:39-46. That is the-→2change on the@kernellines above.@groupsize,@ndrange,@localmem,@private,@synchronizeand@printreturn bare expressions instead ofquoteblocks, so they no longer injectKernelAbstractions.jlline nodes into kernel bodies.Testing
Two new test files, both run from
runtests.jlonly (they are backend-independent, so there is no reason to make every backend package pay for them):test/linenumbers.jlasserts that a@kernelexpansion only ever refers to the file it was written in, and that every source line of the kernel is represented. Covers the plain case,@synchronizeplus hoisted allocations,@uniform/@private, theinbounds=/unsafe_indices=configurations, and the kernel-language macros.test/coverage.jlis the end-to-end check: it runs the script above in a subprocess under--code-coverageand asserts every kernel line is tracked in the resulting LCOV tracefile. Against the pre-fix expansion it fails on exactly the five lines shown above. Takes ~60-75s, since coverage forces the subprocess to recompile rather than use cached native code.Coverage is written to an LCOV tracefile in a temp dir rather than via
--code-coverage=user, which would drop a.covfile next to every tracked source in both the checkout and the depot, and would perturb the outer report when the suite itself runs underPkg.test(coverage=true).Full suite passes (3125 pass, 4 pre-existing broken), Runic clean.
Known limitation, not addressed here
Device coverage is only recorded for
--code-coverage=userand--code-coverage=all. The--code-coverage=@<path>mode records nothing for device code — verified empirically, and it is the mode itself rather than which files are tracked (pointing@<path>at a tree containing both the script and KernelAbstractions' sources still records nothing).That matters because
Pkg.test(coverage=true)passes--code-coverage=@$(pkgroot), which is what the standardjulia-actions/julia-runtestCI setup uses. So under a typical Codecov setup this PR upgrades the@kernel function …signature line from untracked to tracked, but kernel body lines will still be missing. That gap is in GPUCompiler/Julia, not here.Device coverage also means "this code was compiled", with counts reflecting compilations rather than executions — inherent to GPUCompiler's approach, since device code cannot call into the Julia runtime.
🤖 Generated with Claude Code