Fix line number handling in @kernel - #734
Open
vchuravy wants to merge 2 commits into
Open
Conversation
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>
Contributor
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 64.77% 0.75% -64.02%
==========================================
Files 24 23 -1
Lines 2024 1853 -171
==========================================
- Hits 1311 14 -1297
- Misses 713 1839 +1126 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
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>
vchuravy
marked this pull request as ready for review
July 27, 2026 12:23
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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