Skip to content

Fix line number handling in @kernel - #734

Merged
vchuravy merged 3 commits into
mainfrom
vc/kernel-linenumbers
Sep 13, 2026
Merged

vchuravy merged 3 commits into
mainfrom
vc/kernel-linenumbers

Conversation

@vchuravy

@vchuravy vchuravy commented Jul 27, 2026

Copy link
Copy Markdown
Member

Fixes #732.

@kernel spliced LineNumberNodes 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_effect statements at compile time (record_coverage in jlgen.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 under julia --code-coverage, against CPU() (POCL):

@kernel function mul2!(a)
    i = @index(Global)
    x = a[i]
    a[i] = 2 * x
end

@kernel function sync2!(a)
    i = @index(Local)
    lm = @localmem Float64 (8,)
    lm[i] = a[i]
    @synchronize
    a[i] = lm[i] + 1
end

Before — five of the kernels' lines are reported as untracked (-) even though both kernels were compiled and run:

        - @kernel function mul2!(a)
        1     i = @index(Global)
        1     x = a[i]
        1     a[i] = 2 * x
        - end
        -
        - @kernel function sync2!(a)
        1     i = @index(Local)
        -     lm = @localmem Float64 (8,)
        1     lm[i] = a[i]
        -     @synchronize
        -     a[i] = lm[i] + 1
        - end

After — every line is accounted for:

        2 @kernel function mul2!(a)
        1     i = @index(Global)
        1     x = a[i]
        1     a[i] = 2 * x
        - end
        -
        2 @kernel function sync2!(a)
        1     i = @index(Local)
        1     lm = @localmem Float64 (8,)
        1     lm[i] = a[i]
        1     @synchronize
        1     a[i] = lm[i] + 1
        - end

The lost coverage did not vanish, it was charged to src/macros.jl. From the same pre-fix run:

        3 function emit(loop)
        3     stmts = Any[]
        -
        3     body = Expr(:block, loop.stmts...)
        3     loopexpr = quote
        -         $(loop.allocations...)
        1         if __active_lane__
        2             $(unblock(body))

Causes

  • emit built the workitem loop with a quote block, so every emitted block started with a macros.jl line node. It now builds the expression directly.
  • MacroTools.unblock strips 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 why a[i] = lm[i] + 1 above reads as untracked. Replaced with a variant that only unwraps blocks carrying no line info.
  • split hoists @uniform / @localmem / @private out 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:

  • The generated constructor functions are relocated to the @kernel call site, so the @kernel function … line is attributed to the user's file instead of macros.jl:39-46. That is the -2 change on the @kernel lines above.
  • @groupsize, @ndrange, @localmem, @private, @synchronize and @print return bare expressions instead of quote blocks, so they no longer inject KernelAbstractions.jl line nodes into kernel bodies.

Testing

Two new test files, both run from runtests.jl only (they are backend-independent, so there is no reason to make every backend package pay for them):

  • test/linenumbers.jl asserts that a @kernel expansion only ever refers to the file it was written in, and that every source line of the kernel is represented. Covers the plain case, @synchronize plus hoisted allocations, @uniform/@private, the inbounds=/unsafe_indices= configurations, and the kernel-language macros.
  • test/coverage.jl is the end-to-end check: it runs the script above in a subprocess under --code-coverage and 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 .cov file next to every tracked source in both the checkout and the depot, and would perturb the outer report when the suite itself runs under Pkg.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=user and --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 standard julia-actions/julia-runtest CI 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

@github-actions

github-actions Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Benchmark Results

Show table
main 3084d86... main / 3084d86...
launch/3D static workgroup, dynamic ndrange 19.6 ± 10 μs 21.9 ± 21 μs 0.894 ± 0.96
launch/3D static workgroup, static ndrange 19.6 ± 13 μs 21.8 ± 21 μs 0.902 ± 1.1
launch/dynamic workgroup, dynamic ndrange 20.5 ± 6.3 μs 21.4 ± 18 μs 0.96 ± 0.85
launch/dynamic workgroup, dynamic ndrange, workgroupsize given 19.3 ± 8.8 μs 20 ± 18 μs 0.969 ± 0.97
launch/static workgroup, dynamic ndrange 19.8 ± 14 μs 21 ± 20 μs 0.942 ± 1.1
launch/static workgroup, static ndrange 19.6 ± 11 μs 21.2 ± 19 μs 0.926 ± 1
partition/dynamic workgroup, dynamic ndrange 0.0441 ± 0.0071 μs 0.0455 ± 0.0093 μs 0.969 ± 0.25
partition/static workgroup, dynamic ndrange 0.0518 ± 0.0097 μs 0.0519 ± 0.0097 μs 0.998 ± 0.26
partition/static workgroup, static ndrange 1.12 ± 0.008 ns 1.13 ± 0.032 ns 0.992 ± 0.029
saxpy/default/Float16/1024 24.8 ± 21 μs 28.6 ± 22 μs 0.868 ± 1
saxpy/default/Float16/1048576 0.313 ± 0.024 ms 0.315 ± 0.024 ms 0.993 ± 0.11
saxpy/default/Float16/16384 0.0396 ± 0.02 ms 0.0511 ± 0.025 ms 0.774 ± 0.54
saxpy/default/Float16/2048 29.2 ± 20 μs 0.0333 ± 0.02 ms 0.878 ± 0.8
saxpy/default/Float16/256 21.6 ± 12 μs 22.6 ± 19 μs 0.956 ± 0.99
saxpy/default/Float16/262144 0.108 ± 0.021 ms 0.111 ± 0.021 ms 0.976 ± 0.26
saxpy/default/Float16/32768 0.0521 ± 0.025 ms 0.0548 ± 0.026 ms 0.951 ± 0.64
saxpy/default/Float16/4096 0.0321 ± 0.02 ms 0.0398 ± 0.022 ms 0.806 ± 0.67
saxpy/default/Float16/512 22.5 ± 15 μs 23.3 ± 21 μs 0.967 ± 1.1
saxpy/default/Float16/64 21.4 ± 11 μs 21.9 ± 16 μs 0.98 ± 0.89
saxpy/default/Float16/65536 0.0503 ± 0.02 ms 0.0523 ± 0.021 ms 0.962 ± 0.54
saxpy/default/Float32/1024 24.3 ± 22 μs 0.0321 ± 0.024 ms 0.758 ± 0.89
saxpy/default/Float32/1048576 0.338 ± 0.07 ms 0.355 ± 0.052 ms 0.951 ± 0.24
saxpy/default/Float32/16384 0.0455 ± 0.023 ms 0.0475 ± 0.024 ms 0.957 ± 0.69
saxpy/default/Float32/2048 0.0433 ± 0.02 ms 0.0434 ± 0.019 ms 0.997 ± 0.63
saxpy/default/Float32/256 21.3 ± 10 μs 22 ± 18 μs 0.967 ± 0.93
saxpy/default/Float32/262144 0.101 ± 0.02 ms 0.104 ± 0.019 ms 0.971 ± 0.26
saxpy/default/Float32/32768 0.0501 ± 0.025 ms 0.0445 ± 0.022 ms 1.13 ± 0.79
saxpy/default/Float32/4096 0.0332 ± 0.022 ms 0.0338 ± 0.023 ms 0.984 ± 0.95
saxpy/default/Float32/512 22 ± 20 μs 27.5 ± 23 μs 0.801 ± 1
saxpy/default/Float32/64 21.6 ± 13 μs 22.2 ± 19 μs 0.972 ± 1
saxpy/default/Float32/65536 0.0569 ± 0.026 ms 0.0574 ± 0.024 ms 0.99 ± 0.61
saxpy/default/Float64/1024 0.032 ± 0.024 ms 0.0378 ± 0.022 ms 0.847 ± 0.82
saxpy/default/Float64/1048576 0.608 ± 0.08 ms 0.65 ± 0.087 ms 0.935 ± 0.18
saxpy/default/Float64/16384 0.0469 ± 0.024 ms 0.0536 ± 0.028 ms 0.875 ± 0.64
saxpy/default/Float64/2048 0.0378 ± 0.023 ms 0.0341 ± 0.023 ms 1.11 ± 1
saxpy/default/Float64/256 21.4 ± 12 μs 23.4 ± 22 μs 0.916 ± 0.99
saxpy/default/Float64/262144 0.17 ± 0.033 ms 0.174 ± 0.031 ms 0.977 ± 0.26
saxpy/default/Float64/32768 0.0528 ± 0.025 ms 0.0548 ± 0.027 ms 0.963 ± 0.66
saxpy/default/Float64/4096 0.0354 ± 0.025 ms 0.0359 ± 0.026 ms 0.986 ± 0.99
saxpy/default/Float64/512 21.5 ± 13 μs 23.5 ± 22 μs 0.913 ± 1
saxpy/default/Float64/64 21.3 ± 13 μs 21.5 ± 14 μs 0.99 ± 0.9
saxpy/default/Float64/65536 0.0629 ± 0.024 ms 0.0656 ± 0.025 ms 0.958 ± 0.51
saxpy/static workgroup=(1024,)/Float16/1024 26.6 ± 22 μs 27.5 ± 21 μs 0.967 ± 1.1
saxpy/static workgroup=(1024,)/Float16/1048576 0.314 ± 0.025 ms 0.313 ± 0.024 ms 1 ± 0.11
saxpy/static workgroup=(1024,)/Float16/16384 0.0471 ± 0.023 ms 0.0481 ± 0.023 ms 0.979 ± 0.68
saxpy/static workgroup=(1024,)/Float16/2048 28.9 ± 19 μs 31.4 ± 20 μs 0.92 ± 0.84
saxpy/static workgroup=(1024,)/Float16/256 23.7 ± 18 μs 24.8 ± 21 μs 0.956 ± 1.1
saxpy/static workgroup=(1024,)/Float16/262144 0.107 ± 0.021 ms 0.108 ± 0.02 ms 0.988 ± 0.27
saxpy/static workgroup=(1024,)/Float16/32768 0.0502 ± 0.023 ms 0.0425 ± 0.019 ms 1.18 ± 0.77
saxpy/static workgroup=(1024,)/Float16/4096 0.0378 ± 0.02 ms 0.0416 ± 0.019 ms 0.909 ± 0.63
saxpy/static workgroup=(1024,)/Float16/512 24.3 ± 20 μs 25.1 ± 21 μs 0.969 ± 1.1
saxpy/static workgroup=(1024,)/Float16/64 23.8 ± 18 μs 25.2 ± 21 μs 0.943 ± 1
saxpy/static workgroup=(1024,)/Float16/65536 0.0584 ± 0.023 ms 0.0521 ± 0.02 ms 1.12 ± 0.62
saxpy/static workgroup=(1024,)/Float32/1024 24.4 ± 22 μs 27.5 ± 23 μs 0.888 ± 1.1
saxpy/static workgroup=(1024,)/Float32/1048576 0.334 ± 0.066 ms 0.34 ± 0.056 ms 0.982 ± 0.25
saxpy/static workgroup=(1024,)/Float32/16384 0.0455 ± 0.024 ms 0.0467 ± 0.024 ms 0.973 ± 0.71
saxpy/static workgroup=(1024,)/Float32/2048 0.0392 ± 0.02 ms 0.0375 ± 0.021 ms 1.05 ± 0.79
saxpy/static workgroup=(1024,)/Float32/256 22.8 ± 14 μs 23.5 ± 21 μs 0.971 ± 1
saxpy/static workgroup=(1024,)/Float32/262144 0.101 ± 0.018 ms 0.101 ± 0.019 ms 1 ± 0.26
saxpy/static workgroup=(1024,)/Float32/32768 0.0504 ± 0.025 ms 0.0499 ± 0.023 ms 1.01 ± 0.69
saxpy/static workgroup=(1024,)/Float32/4096 0.0322 ± 0.021 ms 0.0434 ± 0.019 ms 0.74 ± 0.58
saxpy/static workgroup=(1024,)/Float32/512 23.7 ± 21 μs 25.8 ± 23 μs 0.918 ± 1.2
saxpy/static workgroup=(1024,)/Float32/64 22 ± 13 μs 22.7 ± 18 μs 0.968 ± 0.95
saxpy/static workgroup=(1024,)/Float32/65536 0.0497 ± 0.022 ms 0.0547 ± 0.023 ms 0.909 ± 0.55
saxpy/static workgroup=(1024,)/Float64/1024 29.3 ± 24 μs 0.0361 ± 0.023 ms 0.814 ± 0.84
saxpy/static workgroup=(1024,)/Float64/1048576 0.584 ± 0.074 ms 0.624 ± 0.077 ms 0.936 ± 0.17
saxpy/static workgroup=(1024,)/Float64/16384 0.0446 ± 0.023 ms 0.0461 ± 0.024 ms 0.966 ± 0.7
saxpy/static workgroup=(1024,)/Float64/2048 0.0387 ± 0.023 ms 0.0384 ± 0.022 ms 1.01 ± 0.84
saxpy/static workgroup=(1024,)/Float64/256 26.4 ± 21 μs 25.8 ± 20 μs 1.02 ± 1.1
saxpy/static workgroup=(1024,)/Float64/262144 0.164 ± 0.034 ms 0.169 ± 0.031 ms 0.97 ± 0.27
saxpy/static workgroup=(1024,)/Float64/32768 0.0511 ± 0.022 ms 0.0513 ± 0.024 ms 0.996 ± 0.64
saxpy/static workgroup=(1024,)/Float64/4096 0.0345 ± 0.023 ms 0.0437 ± 0.021 ms 0.789 ± 0.65
saxpy/static workgroup=(1024,)/Float64/512 24.7 ± 21 μs 26.2 ± 22 μs 0.942 ± 1.1
saxpy/static workgroup=(1024,)/Float64/64 21.8 ± 12 μs 24.8 ± 21 μs 0.878 ± 0.88
saxpy/static workgroup=(1024,)/Float64/65536 0.0592 ± 0.021 ms 0.0635 ± 0.022 ms 0.932 ± 0.46
time_to_load 0.654 ± 0.002 s 0.687 ± 0.0051 s 0.953 ± 0.0077
main 3084d86... main / 3084d86...
launch/3D static workgroup, dynamic ndrange 7 allocs: 0.297 kB 7 allocs: 0.297 kB 1
launch/3D static workgroup, static ndrange 8 allocs: 0.328 kB 8 allocs: 0.328 kB 1
launch/dynamic workgroup, dynamic ndrange 17 allocs: 0.469 kB 17 allocs: 0.469 kB 1
launch/dynamic workgroup, dynamic ndrange, workgroupsize given 3 allocs: 0.172 kB 3 allocs: 0.172 kB 1
launch/static workgroup, dynamic ndrange 3 allocs: 0.172 kB 3 allocs: 0.172 kB 1
launch/static workgroup, static ndrange 4 allocs: 0.203 kB 4 allocs: 0.203 kB 1
partition/dynamic workgroup, dynamic ndrange 2 allocs: 0.0625 kB 2 allocs: 0.0625 kB 1
partition/static workgroup, dynamic ndrange 2 allocs: 32 B 2 allocs: 32 B 1
partition/static workgroup, static ndrange 0 allocs: 0 B 0 allocs: 0 B
saxpy/default/Float16/1024 26 allocs: 0.609 kB 26 allocs: 0.609 kB 1
saxpy/default/Float16/1048576 26 allocs: 0.609 kB 26 allocs: 0.609 kB 1
saxpy/default/Float16/16384 26 allocs: 0.609 kB 26 allocs: 0.609 kB 1
saxpy/default/Float16/2048 26 allocs: 0.609 kB 26 allocs: 0.609 kB 1
saxpy/default/Float16/256 18 allocs: 0.484 kB 18 allocs: 0.484 kB 1
saxpy/default/Float16/262144 26 allocs: 0.609 kB 26 allocs: 0.609 kB 1
saxpy/default/Float16/32768 26 allocs: 0.609 kB 26 allocs: 0.609 kB 1
saxpy/default/Float16/4096 26 allocs: 0.609 kB 26 allocs: 0.609 kB 1
saxpy/default/Float16/512 26 allocs: 0.609 kB 26 allocs: 0.609 kB 1
saxpy/default/Float16/64 18 allocs: 0.484 kB 18 allocs: 0.484 kB 1
saxpy/default/Float16/65536 26 allocs: 0.609 kB 26 allocs: 0.609 kB 1
saxpy/default/Float32/1024 26 allocs: 0.609 kB 26 allocs: 0.609 kB 1
saxpy/default/Float32/1048576 26 allocs: 0.609 kB 26 allocs: 0.609 kB 1
saxpy/default/Float32/16384 26 allocs: 0.609 kB 26 allocs: 0.609 kB 1
saxpy/default/Float32/2048 26 allocs: 0.609 kB 26 allocs: 0.609 kB 1
saxpy/default/Float32/256 18 allocs: 0.484 kB 18 allocs: 0.484 kB 1
saxpy/default/Float32/262144 26 allocs: 0.609 kB 26 allocs: 0.609 kB 1
saxpy/default/Float32/32768 26 allocs: 0.609 kB 26 allocs: 0.609 kB 1
saxpy/default/Float32/4096 26 allocs: 0.609 kB 26 allocs: 0.609 kB 1
saxpy/default/Float32/512 26 allocs: 0.609 kB 26 allocs: 0.609 kB 1
saxpy/default/Float32/64 18 allocs: 0.484 kB 18 allocs: 0.484 kB 1
saxpy/default/Float32/65536 26 allocs: 0.609 kB 26 allocs: 0.609 kB 1
saxpy/default/Float64/1024 26 allocs: 0.609 kB 26 allocs: 0.609 kB 1
saxpy/default/Float64/1048576 26 allocs: 0.609 kB 26 allocs: 0.609 kB 1
saxpy/default/Float64/16384 26 allocs: 0.609 kB 26 allocs: 0.609 kB 1
saxpy/default/Float64/2048 26 allocs: 0.609 kB 26 allocs: 0.609 kB 1
saxpy/default/Float64/256 18 allocs: 0.484 kB 18 allocs: 0.484 kB 1
saxpy/default/Float64/262144 26 allocs: 0.609 kB 26 allocs: 0.609 kB 1
saxpy/default/Float64/32768 26 allocs: 0.609 kB 26 allocs: 0.609 kB 1
saxpy/default/Float64/4096 26 allocs: 0.609 kB 26 allocs: 0.609 kB 1
saxpy/default/Float64/512 26 allocs: 0.609 kB 26 allocs: 0.609 kB 1
saxpy/default/Float64/64 18 allocs: 0.484 kB 18 allocs: 0.484 kB 1
saxpy/default/Float64/65536 26 allocs: 0.609 kB 26 allocs: 0.609 kB 1
saxpy/static workgroup=(1024,)/Float16/1024 6 allocs: 0.219 kB 6 allocs: 0.219 kB 1
saxpy/static workgroup=(1024,)/Float16/1048576 6 allocs: 0.219 kB 6 allocs: 0.219 kB 1
saxpy/static workgroup=(1024,)/Float16/16384 6 allocs: 0.219 kB 6 allocs: 0.219 kB 1
saxpy/static workgroup=(1024,)/Float16/2048 6 allocs: 0.219 kB 6 allocs: 0.219 kB 1
saxpy/static workgroup=(1024,)/Float16/256 3 allocs: 0.172 kB 3 allocs: 0.172 kB 1
saxpy/static workgroup=(1024,)/Float16/262144 6 allocs: 0.219 kB 6 allocs: 0.219 kB 1
saxpy/static workgroup=(1024,)/Float16/32768 6 allocs: 0.219 kB 6 allocs: 0.219 kB 1
saxpy/static workgroup=(1024,)/Float16/4096 6 allocs: 0.219 kB 6 allocs: 0.219 kB 1
saxpy/static workgroup=(1024,)/Float16/512 6 allocs: 0.219 kB 6 allocs: 0.219 kB 1
saxpy/static workgroup=(1024,)/Float16/64 3 allocs: 0.172 kB 3 allocs: 0.172 kB 1
saxpy/static workgroup=(1024,)/Float16/65536 6 allocs: 0.219 kB 6 allocs: 0.219 kB 1
saxpy/static workgroup=(1024,)/Float32/1024 6 allocs: 0.219 kB 6 allocs: 0.219 kB 1
saxpy/static workgroup=(1024,)/Float32/1048576 6 allocs: 0.219 kB 6 allocs: 0.219 kB 1
saxpy/static workgroup=(1024,)/Float32/16384 6 allocs: 0.219 kB 6 allocs: 0.219 kB 1
saxpy/static workgroup=(1024,)/Float32/2048 6 allocs: 0.219 kB 6 allocs: 0.219 kB 1
saxpy/static workgroup=(1024,)/Float32/256 3 allocs: 0.172 kB 3 allocs: 0.172 kB 1
saxpy/static workgroup=(1024,)/Float32/262144 6 allocs: 0.219 kB 6 allocs: 0.219 kB 1
saxpy/static workgroup=(1024,)/Float32/32768 6 allocs: 0.219 kB 6 allocs: 0.219 kB 1
saxpy/static workgroup=(1024,)/Float32/4096 6 allocs: 0.219 kB 6 allocs: 0.219 kB 1
saxpy/static workgroup=(1024,)/Float32/512 6 allocs: 0.219 kB 6 allocs: 0.219 kB 1
saxpy/static workgroup=(1024,)/Float32/64 3 allocs: 0.172 kB 3 allocs: 0.172 kB 1
saxpy/static workgroup=(1024,)/Float32/65536 6 allocs: 0.219 kB 6 allocs: 0.219 kB 1
saxpy/static workgroup=(1024,)/Float64/1024 6 allocs: 0.219 kB 6 allocs: 0.219 kB 1
saxpy/static workgroup=(1024,)/Float64/1048576 6 allocs: 0.219 kB 6 allocs: 0.219 kB 1
saxpy/static workgroup=(1024,)/Float64/16384 6 allocs: 0.219 kB 6 allocs: 0.219 kB 1
saxpy/static workgroup=(1024,)/Float64/2048 6 allocs: 0.219 kB 6 allocs: 0.219 kB 1
saxpy/static workgroup=(1024,)/Float64/256 3 allocs: 0.172 kB 3 allocs: 0.172 kB 1
saxpy/static workgroup=(1024,)/Float64/262144 6 allocs: 0.219 kB 6 allocs: 0.219 kB 1
saxpy/static workgroup=(1024,)/Float64/32768 6 allocs: 0.219 kB 6 allocs: 0.219 kB 1
saxpy/static workgroup=(1024,)/Float64/4096 6 allocs: 0.219 kB 6 allocs: 0.219 kB 1
saxpy/static workgroup=(1024,)/Float64/512 6 allocs: 0.219 kB 6 allocs: 0.219 kB 1
saxpy/static workgroup=(1024,)/Float64/64 3 allocs: 0.172 kB 3 allocs: 0.172 kB 1
saxpy/static workgroup=(1024,)/Float64/65536 6 allocs: 0.219 kB 6 allocs: 0.219 kB 1
time_to_load 0.201 k allocs: 11.8 kB 0.201 k allocs: 11.8 kB 1

Benchmark Plots

A plot of the benchmark results have been uploaded as an artifact to the workflow run for this PR.
Go to "Actions"->"Benchmark a pull request"->[the most recent run]->"Artifacts" (at the bottom).

@codecov

codecov Bot commented Jul 27, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.34884% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 63.97%. Comparing base (4135768) to head (3084d86).

Files with missing lines Patch % Lines
src/KernelAbstractions.jl 87.50% 1 Missing ⚠️
src/macros.jl 97.14% 1 Missing ⚠️
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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@vchuravy
vchuravy marked this pull request as ready for review July 27, 2026 12:23
@vchuravy vchuravy added this to the 0.10.0 milestone Sep 7, 2026
@vchuravy
vchuravy force-pushed the vc/kernel-linenumbers branch from 222425e to c251578 Compare September 9, 2026 17:25
@vchuravy

vchuravy commented Sep 9, 2026

Copy link
Copy Markdown
Member Author

Rebased onto main and fixed the CI failure.

The coverage test passed locally and on the Windows job, which runs Pkg.test() without coverage, but failed everywhere julia-runtest runs Pkg.test(coverage=true). The cause is that Base.julia_cmd() forwards the parent's --code-coverage flag, so under CI the subprocess ran with --code-coverage=@<pkgroot> inherited from the parent. That is exactly the path-tracking mode in which GPUCompiler records no device coverage (see the "known limitation" above), and the script itself lives outside the package root, so nothing was tracked at all.

The subprocess command now strips the inherited flags and requests user mode explicitly. Verified with a --code-coverage=@<pkgroot> parent on Julia 1.10 and 1.12, and with a full Pkg.test(coverage=true) run on 1.12.

@vchuravy
vchuravy force-pushed the vc/kernel-linenumbers branch from c251578 to ec188ef Compare September 11, 2026 06:31
vchuravy and others added 3 commits September 13, 2026 14:22
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)
@vchuravy
vchuravy force-pushed the vc/kernel-linenumbers branch from ec188ef to 3084d86 Compare September 13, 2026 12:22
@vchuravy
vchuravy merged commit e5f5f61 into main Sep 13, 2026
84 checks passed
@vchuravy
vchuravy deleted the vc/kernel-linenumbers branch September 13, 2026 16:03
@giordano

giordano commented Sep 13, 2026

Copy link
Copy Markdown
Collaborator

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

Error During Test at /var/lib/buildkite-agent/builds/nautilus-19/clima/oceananigans/test/test_poisson_solvers_stretched_grids.jl:43
  Test threw exception
  Expression: stretched_poisson_solver_correct_answer(Float64, arch, topo, 8, 11, faces; stretched_axis)
  Failed to compile PTX code (ptxas exited with code 255)
  Invocation arguments: --generate-line-info --verbose --gpu-name sm_70 --output-file /tmp/jl_JeJa92Xgpy.cubin /tmp/jl_vABwFELCV8.ptx
  ptxas fatal   : Unexpected non-ASCII character encountered on line 6313
  ptxas fatal   : Ptx assembly aborted due to errors
  If you think this is a bug, please file an issue and attach /tmp/jl_vABwFELCV8.ptx

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....)

@vchuravy

Copy link
Copy Markdown
Member Author

Do you have the ptx file?

@giordano

Copy link
Copy Markdown
Collaborator

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 julia -g0 seems to be a workaround)

@giordano

Copy link
Copy Markdown
Collaborator

Turned out to be a bug in CUDA.jl 6.4: JuliaGPU/CUDA.jl#3274

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Code coverage tracking of kernels does not work correctly

2 participants