Skip to content

Internalize non-kernel deferred codegen entrypoints - #928

Merged
vchuravy merged 1 commit into
mainfrom
vc/internalize_deferred
Sep 10, 2026
Merged

Internalize non-kernel deferred codegen entrypoints#928
vchuravy merged 1 commit into
mainfrom
vc/internalize_deferred

Conversation

@vchuravy

@vchuravy vchuravy commented Sep 9, 2026

Copy link
Copy Markdown
Member

Motivated by JuliaGPU/KernelAbstractions.jl#736.

Problem

Deferred codegen entrypoints (notably the wrappers Enzyme generates) were held externally live across GPUCompiler's InternalizePass so that linking could resolve them. Once linked and alwaysinlined they are dead, but external linkage kept GlobalDCEPass from dropping them, so e.g. the SPIR-V back-end still had to translate functions it cannot express.

Change

Only deferred jobs that are kernels themselves (e.g. child kernels for CUDA dynamic parallelism, which inherit the parent's config) are entrypoints in their own right. The driver now classifies the entrypoints once after deferred codegen, and uses that set both for internalization and for the per-entrypoint finish_ir! loop. Other deferred functions (Enzyme registers its jobs with kernel=false) are internalized like any other function, and dropped by the existing clean-up GlobalDCEPass once inlined. No back-end hooks are touched, so the finish_ir! override in the KernelAbstractions PR should no longer be necessary.

Tests

  • The mock Enzyme helper gains an opt-in always_inline flag on Enzyme.deferred_codegen, marking the generated function alwaysinline like Enzyme's wrappers.
  • Native and PTX "Mock Enzyme" tests now assert the deferred child is define internal, and that it is gone entirely when alwaysinline. The PTX test previously compiled for the native target; it now compiles an actual PTX kernel.
  • New SPIR-V "deferred codegen" testset (both back-ends): the alwaysinline child is absent from the optimized module, and the translated SPIR-V contains exactly one OpFunction. Without the driver change this fails on both back-ends, as the external definition is left behind.

Note that the reflection code_native sets only_entry unless dump_module=true, which silently skips deferred codegen; the SPIR-V test passes dump_module=true for that reason.

Tested locally with Julia 1.10 and 1.12 (native, ptx, spirv, utils, gcn, metal).

🤖 Generated with Claude Code

https://claude.ai/code/session_01Jap1wwekUHPKo3iET2dKhn

Deferred codegen entrypoints were kept externally visible across the
InternalizePass, so that linking could resolve them. Once linked (and,
for Enzyme's wrappers, alwaysinlined) they are dead, but their external
linkage kept GlobalDCEPass from dropping them. Back-ends like SPIR-V
then still had to translate functions they cannot express.

Only deferred jobs that are kernels themselves (e.g. child kernels for
dynamic parallelism) are entrypoints in their own right. Classify the
entrypoints once after deferred codegen, and use that set both for
internalization and for the per-entrypoint `finish_ir!` loop. Other
deferred functions are internalized like any other function, and dropped
by the existing clean-up passes once inlined.

The mock Enzyme test helper gains an opt-in `always_inline` flag that
marks the generated function `alwaysinline`, like Enzyme's wrappers, so
the removal can be tested on the native, PTX and SPIR-V targets.

Assisted-by: Claude Code (Fable 5.1)
@vchuravy
vchuravy requested a review from maleadt September 9, 2026 14:07
@maleadt

maleadt commented Sep 9, 2026

Copy link
Copy Markdown
Member

Ugh, this is becoming one hell of an ugly interface.

Comment thread src/driver.jl
# wrappers Enzyme generates) are only called from within this module, so they should
# be internalized like any other function and dropped once inlined, rather than kept
# around for back-ends that cannot express their signatures (e.g. SPIR-V).
entrypoints = filter(((job′, _),) -> job′ === job || job′.config.kernel, jobs)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jobs is being used for too many things here. It was only intended to be for deduplicating compilation, but now it's also for preserving externally visible symbols, and scheduling finalization... That makes this code really hard to read.

I'm fine with the bugfix, but we should really replace this with separate mechanisms at some point.

@vchuravy

vchuravy commented Sep 9, 2026

Copy link
Copy Markdown
Member Author

Not disagreeing, and I am in a catch-22 of my own making. KA 0.10 should keep Enzyme support...

I would like to re-engineer the GPU integration that Enzyme currently has. Maybe Claude has more follow-through than I do on #582

@codecov

codecov Bot commented Sep 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 81.42%. Comparing base (e9dbe53) to head (d81f2c5).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #928      +/-   ##
==========================================
- Coverage   85.76%   81.42%   -4.34%     
==========================================
  Files          29       29              
  Lines        5598     5895     +297     
==========================================
- Hits         4801     4800       -1     
- Misses        797     1095     +298     

☔ 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 merged commit d5581b1 into main Sep 10, 2026
32 of 33 checks passed
@vchuravy
vchuravy deleted the vc/internalize_deferred branch September 10, 2026 14:13
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.

2 participants