fix(recompiler): accept optional-body merges implied by the skip predicate in stencil replay

An EXEC- or VCC-skipped optional body is accepted when every merge phi's
value from the body equals the skipped value on lanes inactive in the
body. A body value that implies the skip predicate is false on every
inactive lane, so it equals the skipped value when that value is the
predicate itself. inactiveEquals now accepts that case, which covers a
lane's own bit of a ballot of the predicate merged with the predicate.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
battleblob1andClaude Opus 5.5 committed 2026-10-26 18:24:34 +02:00
1 parent 570089a806
commit bf0589e282
2 files changed
+33

No files matched your search

@@ -1236,6 +1236,38 @@ int main() {
return StencilReplaySafe(b.program);
};
const auto optionalPredicateBody = [&](bool negated) {
Builder b;
b.program.Resources().stage = IrShaderStage::Pixel;
auto& entry = *b.block;
auto& predicate = pixelPredicate(b);
auto& condition = b.emit(IrOpcode::LogicalNot, IrType::Bool, {&predicate});
auto& body = b.newBlock();
auto& merge = b.newBlock();
entry.AddBranch(&body);
entry.AddBranch(&merge);
body.AddBranch(&merge);
auto& entryInfo = b.program.Metadata().blockInfo[0u];
entryInfo.terminator.kind = TerminatorKind::ConditionalBranch;
entryInfo.terminator.condition = BranchCondition::ExecZero;
entryInfo.terminator.trueBlock = merge.Id();
entryInfo.terminator.falseBlock = body.Id();
entryInfo.condition = &condition;
auto& bodyInfo = b.program.Metadata().blockInfo[1u];
bodyInfo.terminator.kind = TerminatorKind::Branch;
bodyInfo.terminator.trueBlock = merge.Id();
b.block = &body;
auto& written = negated ? b.emit(IrOpcode::LogicalNot, IrType::Bool, {&predicate}) : b.ballotBit(predicate);
b.block = &merge;
auto& merged = b.phi(IrType::Bool, entry, predicate);
merged.AddPhiOperand(&body, &written);
b.keep(b.select(merged, b.constant(1u), b.constant(0u)));
exportUnder(b, b.truth(), true, true);
return StencilReplaySafe(b.program);
};
passed &= run("an optional wave body may merge a lane's own bit of the predicate's ballot with the skipped predicate", [&] { return optionalPredicateBody(false); });
passed &= run("an optional wave body cannot merge a value that differs from the skipped predicate on inactive lanes", [&] { return !optionalPredicateBody(true); });
passed &= run("an optional wave body preserves inactive values consumed only within a lane", [&] { return optionalPixelBody(false); });
passed &= run("an optional wave body cannot change helper coordinates for later implicit samples", [&] { return !optionalPixelBody(true); });
passed &= run("an optional wave body cannot change helper coordinates for LOD queries", [&] { return !optionalPixelBody(true, IrOpcode::ImageQueryLod); });
+1
View File
@@ -497,6 +497,7 @@ private:
if (depth > 32u) return false;
value = value->Resolve();
if (sameValue(value, other)) return true;
if (value->Type() == IrType::Bool && other->Resolve() == predicate->Resolve() && GuardImplies(program, value, predicate)) return true;
if ((value->Opcode() == IrOpcode::SelectU32 || value->Opcode() == IrOpcode::SelectF32 || value->Opcode() == IrOpcode::SelectU1) && value->ArgumentCount() == 3u && (value->Argument(0)->Resolve() == predicate->Resolve() || GuardImplies(program, value->Argument(0), predicate))) return inactiveEquals(value->Argument(2), other, predicate, region, depth + 1u);
if (value->IsPhi() && region.contains(value->Parent()) && value->ArgumentCount() != 0u && value->ArgumentCount() == value->PhiBlockCount()) {
return std::all_of(value->Arguments().begin(), value->Arguments().end(), [&](const IrValue* argument) { return inactiveEquals(argument, other, predicate, region, depth + 1u); });