vISA: report GRF RA failure when the iteration budget leaves operands unallocated - #427
Open
pvelesko wants to merge 3 commits into
Open
vISA: report GRF RA failure when the iteration budget leaves operands unallocated#427pvelesko wants to merge 3 commits into
pvelesko wants to merge 3 commits into
Conversation
… out
GlobalRA::setupFailSafeIfNeeded() enables fail-safe RA once
getIterNo() == maxRAIterations - 1. With -maxRAIterations 1 that is iteration 0,
so fail-safe RA is on for the very first iteration. If that iteration still has
to insert spill code it creates new spill/fill temporaries and increments
iterationNo to maxRAIterations, leaving no iteration in which to allocate them,
and the post-loop failure report in coloringRegAlloc() is skipped because it is
guarded by !reserveSpillReg. coloringRegAlloc() then returns VISA_SUCCESS with
G4_RegVars whose physical register is still null, and the post-RA passes
dereference it.
ocloc compile -file %s -device dg2 \
-options "-cl-opt-disable -igc_opts 'VISAOptions=-maxRAIterations 1'"
The kernel holds 8 simultaneously live SIMD32 predicates, which is the point at
which the allocator first has to spill on dg2; 7 does not trigger it.
-cl-opt-disable keeps the predicates from being folded away and
intel_reqd_sub_group_size(32) stops a narrower dispatch from relieving the
pressure.
Without the following commit this invocation dies with SIGSEGV in the post-RA
scoreboard pass, so the expected "Build succeeded" never appears.
Signed-off-by: Paulius Velesko <pvelesko@pglc.io>
Companion to the -maxRAIterations 1 reproducer added earlier. Both invocations leave the GRF RA loop in the same state: GlobalRA::setupFailSafeIfNeeded() enables fail-safe RA for iteration maxRAIterations - 1, that iteration still has to insert spill code, and iterationNo reaches maxRAIterations with no iteration left to run. What the two cases leave behind is not the same. Instrumenting coloringRegAlloc() with the same check the post-RA passes perform on every operand phyReg = base->isRegVar() ? base->asRegVar()->getPhyReg() : base; gives, on this kernel: device -maxRAIterations iterationNo reserveSpillReg operands null phyReg dg2 1 1 1 3254 6 dg2 2 2 1 3246 0 dg2 3 1 0 3122 0 rpl-s 1 1 1 8640 0 rpl-s 2 2 1 8642 0 At -maxRAIterations 1 on dg2 six operands are left unallocated and the compiler segfaults on them. At -maxRAIterations 2 nothing is left unallocated. That is what fail-safe RA is supposed to achieve: it assigns the spill/fill temporaries it creates out of the reserved GRFs on the spot, which is why it needs no follow-up iteration. Exhausting the iteration counter is therefore not by itself evidence that the result is unusable, and the result was verified to be correct: on rpl-s, which diverges the same way and does not crash, the kernel produced at -maxRAIterations 1 and 2 computes the same values as the host reference over 4096 work items. Reporting the exhaustion regardless of what the fail-safe iteration achieved throws that result away. coloringRegAlloc() returns VISA_SPILL, Optimizer::optimization() bails out, and CEncoder::Compile() (IGC/Compiler/CISACodeGen/CISABuilder.cpp) records SIMD_SKIP_SPILL and produces no kernel. The kernel is intel_reqd_sub_group_size(32), so SIMD16 and SIMD8 are not candidates, every attempt fails identically, and IGC emits a program containing no kernel at all while ocloc still prints "Build succeeded": build -maxRAIterations bytes sections reporting 2 9160 .spv .misc.buildOptions .note .strtab keeping 2 35136 .text.k .symtab .spv ... .ze_info -asmToConsole makes the difference checkable from ocloc's output alone: a compile that emits the kernel dumps its assembly, a compile that discarded it dumps nothing. Signed-off-by: Paulius Velesko <pvelesko@pglc.io>
… unallocated
coloringRegAlloc() could return VISA_SUCCESS while leaving G4_RegVars with a
null physical register, which made the compiler segfault in a later pass.
GlobalRA::setupFailSafeIfNeeded() enables fail-safe RA when
getIterNo() == maxRAIterations - 1, unconditionally, so fail-safe RA is always
on for the last permitted iteration. With -maxRAIterations 1 that is iteration
0, so it is on for the very first one. If that iteration still has to insert
spill code it creates new spill/fill temporaries and increments iterationNo to
maxRAIterations, so the loop ends with no iteration left in which to allocate
them. The post-loop failure report was guarded by !reserveSpillReg:
if (!reserveSpillReg &&
(failedToSpill || (!hasStackCall && iterationNo == maxRAIterations)))
on the assumption that fail-safe RA needs no follow-up iteration: it assigns
the spill/fill temporaries it creates out of the reserved GRFs as it inserts
the spill code. That holds except when fail-safe RA is entered in the very
first iteration, before the state a later iteration would have set up exists.
Then some temporaries are left with a null physical register, the report is
skipped, coloringRegAlloc() falls through to VISA_SUCCESS, and the post-RA
passes dereference the null:
vISA::G4_BB_SB::getFootprintForOperand visa/LocalScheduler/SWSB_G4IR.cpp
vISA::DDD::getBucketsForOperand visa/LocalScheduler/LocalScheduler_G4IR.cpp
both of which do
phyReg = base->isRegVar() ? base->asRegVar()->getPhyReg() : base;
switch (phyReg->getKind()) {
with no null check. (Which of the two faults first depends on configuration;
guarding either one would only move the crash.)
So test the failure condition directly: when the iteration budget is exhausted
under fail-safe RA, report failure iff an operand was really left without a
physical register. hasUnallocatedOperand() checks exactly what the post-RA
passes dereference. It runs at most once per kernel, only on the
exhausted-budget-under-fail-safe path, which is never reached at default
settings. A complete fail-safe result is still confirmed as before: reporting
failure for it would make Optimizer::optimization() bail out and IGC discard a
usable kernel (with intel_reqd_sub_group_size there is no narrower SIMD mode to
retry, and IGC emits a program with no kernel at all while ocloc prints "Build
succeeded").
RA then returns VISA_SPILL for the truly incomplete case and IGC's existing
retry logic recompiles the kernel successfully. The reproducers added in the
preceding commits cover both sides: ra-iteration-limit-graceful-failure.cl
segfaulted before this change and builds after it;
ra-iteration-limit-complete-failsafe-result.cl keeps its kernel.
Effect on generated code, dg2/rpl-s/bmg-g21, ocloc AOT: every output is
byte-for-byte identical to the unpatched tree across -maxRAIterations
{unset,1,2,3,4} except dg2 -maxRAIterations 1, where the unpatched compiler
segfaults and this change produces a successful build.
Signed-off-by: Paulius Velesko <pvelesko@pglc.io>
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.
coloringRegAlloc()skipped the out-of-iterations failure report whenever fail-safe RA was enabled:if (!reserveSpillReg && (failedToSpill || (!hasStackCall && iterationNo == maxRAIterations))) {Fail-safe RA reserves GRFs so spill code can always be placed, and
GraphColor.cpp:12434commits its result withif (isColoringGood == true || reserveSpillReg), i.e. without checking whether colouring actually succeeded. That is fine when fail-safe runs as a later iteration, but entered on iteration 0 (-maxRAIterations 1) it can commit a result in which some operands still have no physical register.coloringRegAlloc()then returnsVISA_SUCCESS,Optimizer::optimization()proceeds, and the first post-RA pass to read a physical register dereferences null:at
Report the exhaustion under fail-safe RA when, and only when, the allocation really is incomplete. Running the iteration counter out is not by itself evidence of failure: a complete fail-safe result is a usable kernel and must not be discarded. Reporting it unconditionally regresses
-maxRAIterations 2on the same kernel, where fail-safe allocates everything:VISA_SPILLmakesOptimizer::optimization()bail,CEncoder::Compile()recordsSIMD_SKIP_SPILL, and sinceintel_reqd_sub_group_size(32)rules out the SIMD16 and SIMD8 retries, IGC emits a zebin with no kernel while ocloc still prints "Build succeeded".The predicate scan is short-circuited behind
iterationNo == maxRAIterations && reserveSpillReg, so it runs only on that path.Two tests, gated against three builds:
Note that
IGC/ocloc_testsonly builds with-DIGC_OPTION__ENABLE_OCLOC_LIT_TESTS=ON.Fixes #421