Skip to content

[comgr][hotswap] Reuse locally dead SGPR pairs for far returns - #3678

Closed
nirmie wants to merge 2 commits into
amd-stagingfrom
users/nirmie/hotswap-pr1-scratch-search
Closed

[comgr][hotswap] Reuse locally dead SGPR pairs for far returns#3678
nirmie wants to merge 2 commits into
amd-stagingfrom
users/nirmie/hotswap-pr1-scratch-search

Conversation

@nirmie

@nirmie nirmie commented Jul 29, 2026

Copy link
Copy Markdown

A far trampoline needs an aligned scratch SGPR pair for its set-PC return.
When a kernel declares the full numbered SGPR file (up to s105 on gfx1250) no
aligned pair fits above the high-water mark, so the watermark search fails, the
far site is declined, and the whole rewrite fails closed. On the offline
gfx1250 corpus this affects a real class of objects: 23,579 objects declare a
kernel at sgpr_count 107, and the far+full-SGPR ones (e.g. a 16.8 MB hipBLASLt
object with 45 such kernels) cannot be rewritten at all today.

The first commit adds findLocallyDeadSgprPair as a fallback: when the
high-water block is exhausted, scan every aligned numbered pair from the top
down and reuse one whose incoming value is provably dead at the site, neither
read by the replacement nor live across the continuation, using the current-text
liveness primitives landed in #3618 (getReplacementIncomingSgprs,
getLiveSgprsAtContinuation). The watermark search gains a ReportNoSpace
flag so it stays quiet when a fallback follows. This is the corrected,
current-layout re-extraction of the exhausted-SGPR search policy that used to
live in the older #3604 draft, rebuilt on #3618's partial-VCC-correct liveness
rather than that draft's own removed walker.

The fallback alone was not enough: the continuation-liveness query fails closed
whenever any deferred trampoline is pending for the queried function, so a
function with more far sites than its SGPR headroom rescued only its first site
and declined the rest. The second commit narrows that guard. An in-function
pending trampoline keeps its source's original bytes in .text until final fixup
(emitToTrampoline only queues it), and at finalization only redirects the
source through an appended pool that returns to the same continuation, so the
incoming-live set at every continuation is unchanged and the proof stays valid.
Only a trampoline with no resolved source function can add an edge into an
arbitrary function, so that case stays fail-closed; the now-unused per-function
pending set is removed.

Together this converts 42 corpus objects (21 unique kernels, duplicated across
kpack/ and objects/ paths) from fail to pass with zero regressions and zero
pass->pass output-hash changes -- the change only ever touches objects that
previously declined a far site. Peak RSS rises (~1.0 GB -> ~1.3 GB) because the
large functions now build liveness instead of failing early.

Verified with two LIT fixtures: hotswap-trampoline-locally-dead-sgpr.s covers
a full-SGPR kernel with two far sites in one function -- the first queues a
trampoline, the second reuses a dead pair while it is pending -- and asserts a
valid SGPR-backed set-PC with no s_add_pc_i64, idempotent; the full-SGPR
fail-closed assertion in hotswap-trampoline-long-branch.s is updated to match
the new behavior. Full HotSwap LIT suite 127/127; offline corpus 42 newly
passing, 0 regressions; clang-format clean.

A far trampoline needs an aligned scratch SGPR pair for its set-PC
return. When a kernel declares the full numbered SGPR file, no aligned
pair fits above the high-water mark, so findSafeSgprScratchBlock fails
and the far site is declined, failing the whole rewrite.

Add findLocallyDeadSgprPair as a fallback: when the watermark block is
exhausted, scan every aligned numbered pair from the top down and reuse
one whose incoming value is provably dead at the site (neither read by
the replacement nor live across the continuation), using the current-text
liveness primitives from #3618. The watermark search now suppresses its
no-space error when a fallback follows.

Add a LIT regression exercising the full-SGPR far-return case and drop
the now-obsolete fail-closed assertion for it from the long-branch test.

Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added comgr Related to Code Object Manager hotswap Related to the Comgr Hotswap feature labels Jul 29, 2026
The current-text SGPR liveness query fails closed whenever any deferred
trampoline is pending for the queried function. A function with more far
sites than its high-water SGPR headroom therefore rescued only its first
far site with a locally dead pair; every later site saw the first site's
pending trampoline and declined, failing the rewrite.

An in-function pending trampoline does not actually invalidate the proof
the query needs. Its source keeps its original bytes in .text until final
fixup (emitToTrampoline only queues it), so the decode is faithful, and at
finalization it only redirects the source through an appended pool that
returns to the same continuation. The incoming-live set at every
continuation in the function is unchanged, so the scratch-liveness proof
stays valid. Only a trampoline with no resolved source function can add an
edge into an arbitrary function, so keep failing closed for that case and
drop the now-unused per-function pending set.

Extend the locally-dead-pair test to two far sites in one function so the
second site exercises reuse while the first trampoline is pending.

Co-Authored-By: Claude <noreply@anthropic.com>
@nirmie

nirmie commented Jul 29, 2026

Copy link
Copy Markdown
Author

Do not land yet. On-device smoke test on gfx1250 (b02-3, GPU 0) found a runtime regression: the hiptensor trinary/scale contraction tests (#92-95) hang (300s timeout) under this comgr, where stock passes them in 10-31s each. Isolated to a single binary: trinary_bilinear_contraction_test RunKernel/0 passes, RunKernel/1 hangs (GPU at 100%, never returns).

Bisected to the first commit (the findLocallyDeadSgprPair fallback) -- it reproduces with only that commit applied, before the pending-trampoline guard change. So the dead-pair liveness proof is unsound on at least one contraction kernel: it reuses a numbered SGPR pair that is actually live at runtime, corrupting the kernel. The offline corpus missed this because it only validates translation (valid ELF emitted), not execution.

Investigating the liveness gap. Marking blocked until fixed.

@nirmie

nirmie commented Jul 29, 2026

Copy link
Copy Markdown
Author

Root-caused. Captured the runtime inputs (dump hook on hotswapRewrite) and replayed offline: the failing kernels are Composable Kernel kernel_contraction_multiple_d_xdl_cshuffle, declaring sgpr_count 104-107 -- right at the s105 max, which is exactly the case the fallback was built for. On those objects the fallback reuses low numbered pairs (s[6:7], s[10:11], s[22:23]) that in a near-max-SGPR kernel hold live, long-lived values (s6 is read pervasively as a loop-invariant operand). getLiveSgprsAtContinuation proves the pair dead at the far site continuation, but it is live-through the function; the get-pc/set-pc return clobbers it and the kernel loops forever.

Stock declines these same far sites ("no aligned block below s106"); the fallback's reuse is the behavioral delta, and it is unsound for these kernels. The offline corpus passed because it only checks translation, not execution -- the CK contraction kernels are not in the corpus, and the 42 newly-passing objects there are rocsolver.

The fix needs the scratch-liveness proof to not treat a function-live-in (kernel-entry-preloaded or loop-invariant) SGPR as dead -- either mark registers with no in-range def as live-in, or restrict the search to pairs genuinely above the kernel's live allocation. Keeping this blocked until that lands.

@nirmie nirmie closed this Aug 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comgr Related to Code Object Manager hotswap Related to the Comgr Hotswap feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant