feat(finalizer): fail a link that drops an expected kernel - #1367
Open
lucifer1004 wants to merge 1 commit into
Open
lucifer1004 wants to merge 1 commit into
lucifer1004 wants to merge 1 commit into
Conversation
nvJitLink can report success while dropping a module's kernels: an unresolved device-runtime symbol makes it discard the module, and the link-time optimizer strips a kernel it finds unreachable. The result is a well-formed but empty image, which is_valid_cubin accepts. Every finalizer link now takes the kernels its output must define and checks the output's entry points after the link: cubin entries from the ELF symbol table (FUNC symbols with STO_CUDA_ENTRY), PTX entries from ptx-parse. A missing kernel is FinalizerError::MissingKernels, and an image whose entries cannot be read is UnreadableEntryInventory. Rooting stays with the @llvm.used the exporter already emits; nvJitLink's -kernels-used is not passed, since it also lets the link drop kernels it does not list. The expected kernels reach every finalization route: - build-time materialization: the collector's kernel export names; - embedded bundles finalized at load: the bundle's Kernel entries; - sidecar files (cargo oxide interop, cuda-host's file loader): a new <module>.kernels sidecar the compiler writes next to the .ll, before the completing .target. An artifact the compiler emitted must have it; a manual .ll is checked against one only when present. Cached cubins are checked on a hit too, since a stored image may predate the check. The cuda-host build/link functions and the finalizer entry points take the expected kernels as a new argument. Tests: synthetic empty and partial cubins, PTX with only device functions, unreadable inventories, the sidecar format, and a live link whose unrooted kernel nvJitLink drops (cubin and PTX); the live file-cache test now roots its kernel, which it previously linked away. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: Zihua Wu <13583761+lucifer1004@users.noreply.github.com>
lucifer1004
force-pushed
the
feat/final-link-kernel-check
branch
from
September 30, 2026 00:58
66f9ad8 to
770c4e8
Compare
This was referenced Oct 1, 2026
This branch has not been deployed
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.
First PR of the three-PR stack discussed in #1071 (final-link safety → dormant device-runtime link contract → first public Graph API).
Problem
nvJitLink can report success while dropping a module's kernels. An unresolved device-runtime symbol makes it discard the module, and the link-time optimizer strips a kernel it finds unreachable. The result is a well-formed but empty image that
is_valid_cubinaccepts, so the failure only surfaces at launch time, if at all.Change
Every finalizer link now takes the kernels its output must define. After the link it lists the output's entry points and fails if any expected kernel is missing:
FUNCsymbols withSTO_CUDA_ENTRY(st_other & 0x10) from the ELF symbol table;.entrycallables throughptx-parse, ascuda-host's entry registry already does.New errors:
FinalizerError::MissingKernels { output, missing, present }andUnreadableEntryInventory.Kernels stay rooted through the
@llvm.usedthe NVVM exporter already emits. nvJitLink's-kernels-usedis deliberately not passed, because it also lets the link drop kernels that are not listed. The link inputs and options are unchanged, so artifact digests and the recipe stay the same.Where the expected kernels come from
rustc-codegen-cuda)cuda-host)Kernelentriescargo oxideinterop,cuda-hostfile loader)<module>.kernelssidecarThe compiler writes
<module>.kernelsnext to the.ll, before the completing.target. Its first line iscuda-oxide expected-kernels v1, followed by one export name per line..targetcarries the compile-options marker) must have the sidecar. A missing or malformed one fails closed with a rebuild hint..llis checked only against a sidecar that is present.The format lives in
cuda-artifact-finalizerrather thanoxide-artifacts, so this PR needs nooxide-artifactsrelease.Cached cubins are re-checked on a hit, because a stored image may predate the check.
API changes
Finalizer::{materialize_nvvm_ir, materialize_nvvm_ir_with_report, link_ltoir, link_ltoir_with_report},LtoLinker::{link_ltoir, link_ltoir_with_report, link_ptx_to_cubin}, and the publiccuda_host::ltoir::{build_*, link_*}functions takeexpected_kernels: &[&str]. An empty list means the module has no launchable entries.cargo oxide cleanalso removes.kernels;.gitignoreand the book mention the new sidecar.ptx-parse, which adds that edge to every example lockfile.scripts/sync-example-locks.sh --checkpasses.Tests
cuda-hostnow roots its kernel. Before, it linked an empty module.cargo test --workspace, therustc-codegen-cudatests, and clippy are clean.cargo oxide run vecadd --materialize-cubin --arch sm_120andcargo oxide run libdevice_mathpass.🤖 Generated with Claude Code