Skip to content

Inline the generated functions that return their body explicitly - #951

Merged
maleadt merged 2 commits into
mainfrom
tb/generated-inline
Nov 6, 2026
Merged

maleadt merged 2 commits into
mainfrom
tb/generated-inline

Conversation

@maleadt

@maleadt maleadt commented Nov 5, 2026 •

Copy link
Copy Markdown
Member

@inline had no effect on most of our @inline @generated functions. Julia marks a generated body for inlining by wrapping what the generator returns, and an explicit return in the generator skips that wrapper. Runic adds that return to every generator:

julia> @inline @generated f(x) = (ex = :(x); return quote $ex end);

julia> code_lowered(f, (Int,))[1].inlining
0

Only KernelInterface's shuffles were inlined, because they already wrap their bodies with inline_body. Everything else that was meant to be inlined wasn't:

  • On the POCL back-end, arrayref_union and arrayset_union: every load or store of an element of an isbits-union array was a call, returning the element through a stack slot. OpenCL.jl's copy of the same function has no return and is inlined.
  • On the host, the argument helpers in backend_launch.jl and POCL's argument_types, launch_converted, set_args! and convert_arguments, which run on every kernel launch.

This PR moves inline_body to KernelInterface's utilities and uses it for all of them, plus KI.argument_types and POCL's exception_field. A new codegen check verifies that copying an array of isbits unions in a kernel no longer calls either accessor.

This was found through JuliaGPU/AcceleratedKernels.jl#209: the non-inlined accessors exposed an LLVM 23 SPIR-V back-end bug, which generated an OpPhi that failed validation. That bug is fixed separately (llvm/llvm-project#239343, shipped in SPIRV_LLVM_Backend_jll 23.1.4+1), so this PR is no longer needed for that.

`@inline` on a generated function only marks the body for inlining when
the generator falls through to it, not when it returns it explicitly, as
Runic formats it. Move the `inline_body` helper that the shuffles use to
the utilities, and use it for `argument_types` too.
`@inline @generated` functions whose generator returns the body with an
explicit `return`, which Runic inserts, were not marked for inlining.
On the POCL back-end that includes `arrayref_union` and
`arrayset_union`, so that loads from arrays of isbits unions became
calls returning the element through a stack slot plus a pointer to it.
This is slower, and in some kernels the LLVM SPIR-V back-end then emits
invalid SPIR-V for phis of these pointers (e.g. AcceleratedKernels'
reductions with a `Union{Missing, Float32}` accumulator).

Mark the bodies for inlining with `KernelInterface.inline_body`, here
and in the host-side generated functions with the same problem.
@github-actions

github-actions Bot commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

Benchmark results

Compared with a13691a5b, the merge base.
Times are the minimum time of every benchmark. A change is unusual when it exceeds both three times the benchmark's typical change between consecutive runs on the default branch and a minimum per category (performance 3%, latency 5%, applications 3%, other 3%). A benchmark that starts or stops allocating is listed however few the allocations. A single run can still be a fluke, so rerun before drawing conclusions.

Geometric mean Unusual: slower Unusual: faster Compared
Performance +0.1% 1 1 32
Latency +1.0% 0 0 13
Applications −12.7% 0 2 5
Benchmark Baseline This PR Change
Performance
pocl/serial/math/exp-f64 140 µs 151 µs +8.1% 🔴 slower
pocl/parallel/scheduling/explicit 181 µs 171 µs −5.6% 🟢 faster
Applications
pocl/parallel/dgsem/fluxdiff-node 445 µs 294 µs −33.9% 🟢 faster
pocl/serial/stencil/timestep 12.2 ms 11.6 ms −4.9% 🟢 faster
All benchmarks
Benchmark Baseline This PR Change
Performance
julia/serial/arithmetic/muladd-reference 655 µs 655 µs −0.0%
julia/serial/math/exp-f64-reference 284 µs 298 µs +5.1%
julia/serial/narrow/masked-reference 707 µs 697 µs −1.5%
pocl/parallel/atomics/histogram 5.14 ms 5.15 ms +0.2%
pocl/parallel/groupreduce/add-f64 1.17 ms 1.17 ms +0.0%
pocl/parallel/groupreduce/fallback 4.42 ms 4.42 ms +0.0%
pocl/parallel/groupscan/add-i32 299 µs 301 µs +0.6%
pocl/parallel/indexing/cartesian3d 80.4 µs 79.9 µs −0.6%
pocl/parallel/indexing/linear3d 79.7 µs 78.8 µs −1.0%
pocl/parallel/indexing/ntuple3d 79.8 µs 77.4 µs −3.1%
pocl/parallel/indexing/ntuple4d 163 µs 163 µs −0.0%
pocl/parallel/localmem/transpose-naive 458 µs 457 µs −0.2%
pocl/parallel/localmem/transpose-tiled 110 µs 122 µs +11.0%
pocl/parallel/private/across-barrier 84.0 µs 83.9 µs −0.1%
pocl/parallel/scheduling/default 181 µs 182 µs +0.2%
pocl/parallel/scheduling/explicit 181 µs 171 µs −5.6% 🟢 faster
pocl/parallel/stream/copy/cache 662 ns 691 ns +4.4%
pocl/parallel/stream/copy/dram 6.34 ms 6.40 ms +1.0%
pocl/parallel/stream/triad/cache 7.62 µs 7.24 µs −5.0%
pocl/parallel/stream/triad/dram 9.44 ms 9.02 ms −4.4%
pocl/parallel/util/copyto-small 26.0 ns 24.0 ns −7.7%
pocl/parallel/util/zeros-large 1.01 ms 992 µs −1.7%
pocl/parallel/util/zeros-small 653 ns 647 ns −0.9%
pocl/serial/arithmetic/contract 1.91 ms 1.91 ms +0.1%
pocl/serial/arithmetic/muladd 752 µs 760 µs +1.1%
pocl/serial/const/marked 730 µs 727 µs −0.4%
pocl/serial/const/unmarked 918 µs 914 µs −0.4%
pocl/serial/math/exp-f64 140 µs 151 µs +8.1% 🔴 slower
pocl/serial/math/sin-f64 196 µs 196 µs +0.4%
pocl/serial/narrow/masked 778 µs 802 µs +3.1%
pocl/serial/narrow/unmasked 783 µs 808 µs +3.2%
pocl/serial/stream/triad/dram 8.74 ms 8.66 ms −0.9%
Latency
host/import/ka 503 ms 502 ms −0.0%
host/precompile/ka 39.8 s 39.5 s −0.7%
host/startup 132 ms 131 ms −0.5%
pocl/parallel/launch/dynamic-default 4.26 µs 4.33 µs +1.7%
pocl/parallel/launch/groupreduce-f64 5.60 µs 6.42 µs +14.6%
pocl/parallel/launch/many-args 12.3 µs 12.0 µs −2.5%
pocl/parallel/launch/partial-workgroup 4.05 µs 4.15 µs +2.5%
pocl/serial/compile/groupreduce 1.46 s 1.47 s +0.3%
pocl/serial/compile/localmem-barrier 692 ms 690 ms −0.2%
pocl/serial/compile/new-kernel 122 ms 122 ms −0.6%
pocl/serial/compile/new-workgroupsize 8.86 ms 8.88 ms +0.2%
pocl/serial/first-kernel/cold-cache 357 ms 358 ms +0.2%
pocl/serial/first-kernel/warm-cache 278 ms 277 ms −0.6%
Applications
pocl/parallel/dgsem/fluxdiff-node 445 µs 294 µs −33.9% 🟢 faster
pocl/parallel/dgsem/weak-element 212 µs 212 µs +0.4%
pocl/parallel/dgsem/weak-node-localmem 146 µs 118 µs −19.5%
pocl/parallel/stencil/timestep 7.92 ms 7.90 ms −0.2%
pocl/serial/stencil/timestep 12.2 ms 11.6 ms −4.9% 🟢 faster

History: https://juliagpu.org/perf/?pkg=kernelabstractions

@maleadt
maleadt merged commit 678212f into main Nov 6, 2026
29 of 30 checks passed
@maleadt
maleadt deleted the tb/generated-inline branch November 6, 2026 07:34
@codecov

codecov Bot commented Nov 6, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.85714% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.11%. Comparing base (a13691a) to head (3d35ba2).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
src/pocl/device/array.jl 84.61% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #951      +/-   ##
==========================================
- Coverage   84.91%   84.11%   -0.81%     
==========================================
  Files          31       31              
  Lines        2612     2562      -50     
==========================================
- Hits         2218     2155      -63     
- Misses        394      407      +13     

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

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.

1 participant