Merge pull request #4490 from karanvora2599/perf/shader-minmax-floor-denormal-free

perf(shader): treat f32 min, max, med3, floor and ceil results as denormal-free
This commit is contained in:
Dean 2026-10-14 13:31:23 +13:00 committed by GitHub
commit 520ee1a2e8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 67 additions and 0 deletions

View File

@ -150,6 +150,66 @@ int main() {
return b.eliminate() == 1u && !clamped.HasUses() && product.HasUses();
});
passed &= run("a min, max, min3, max3 or med3 of flushed values needs no flush, one with an unknown operand does", [] {
Builder b;
const auto value = [&](IrValue& bits) -> IrValue& { return b.emit(IrOpcode::BitCastF32U32, IrType::F32, {&bits}); };
const auto flushedResult = [&](IrOpcode opcode, std::initializer_list<IrValue*> arguments) -> IrValue& {
return b.flush(b.emit(IrOpcode::BitCastU32F32, IrType::U32, {&b.emit(opcode, IrType::F32, arguments)}));
};
auto& x = value(b.flush(b.unknown()));
auto& y = value(b.flush(b.unknown()));
auto& z = value(b.flush(b.unknown()));
auto& raw = value(b.unknown());
IrValue* removed[] = {
&flushedResult(IrOpcode::FPMin32, {&x, &y}),
&flushedResult(IrOpcode::FPMax32, {&y, &x}),
&flushedResult(IrOpcode::FPMinTri32, {&x, &y, &z}),
&flushedResult(IrOpcode::FPMaxTri32, {&z, &y, &x}),
&flushedResult(IrOpcode::FPMedTri32, {&x, &z, &y}),
};
auto& mixedPair = flushedResult(IrOpcode::FPMax32, {&x, &raw});
auto& mixedMedian = flushedResult(IrOpcode::FPMedTri32, {&x, &y, &raw});
for (auto* flushed : removed) b.keep(*flushed);
b.keep(mixedPair);
b.keep(mixedMedian);
const bool counted = b.eliminate() == 5u;
bool kept = false;
for (auto* flushed : removed) kept |= flushed->HasUses();
return counted && !kept && mixedPair.HasUses() && mixedMedian.HasUses();
});
passed &= run("a floor or ceil needs no flush, even of an unknown value", [] {
Builder b;
auto& raw = b.emit(IrOpcode::BitCastF32U32, IrType::F32, {&b.unknown()});
auto& floored = b.flush(b.emit(IrOpcode::BitCastU32F32, IrType::U32, {&b.emit(IrOpcode::FPFloor32, IrType::F32, {&raw})}));
auto& ceiled = b.flush(b.emit(IrOpcode::BitCastU32F32, IrType::U32, {&b.emit(IrOpcode::FPCeil32, IrType::F32, {&raw})}));
b.keep(floored);
b.keep(ceiled);
return b.eliminate() == 2u && !floored.HasUses() && !ceiled.HasUses();
});
passed &= run("a max carried around a loop without a flush keeps its flush and the search ends", [] {
Builder b;
IrBlock& head = *b.block;
IrBlock& body = b.newBlock();
IrBlock& exit = b.newBlock();
head.AddBranch(&body);
body.AddBranch(&body);
body.AddBranch(&exit);
auto& initial = b.flush(b.unknown());
b.block = &body;
auto& carried = b.program.CreateValue(IrOpcode::Phi, IrType::U32);
body.AppendInstruction(&carried);
auto& step = b.emit(IrOpcode::BitCastF32U32, IrType::F32, {&b.flush(b.unknown())});
auto& maximum = b.emit(IrOpcode::BitCastU32F32, IrType::U32, {&b.emit(IrOpcode::FPMax32, IrType::F32, {&b.emit(IrOpcode::BitCastF32U32, IrType::F32, {&carried}), &step})});
carried.AddPhiOperand(&head, &initial);
carried.AddPhiOperand(&body, &maximum);
auto& flushed = b.flush(maximum);
b.block = &exit;
b.keep(flushed);
return b.eliminate() == 0u && flushed.HasUses();
});
passed &= run("a phi of flushed values needs no flush", [] {
Builder b;
IrBlock& head = *b.block;

View File

@ -51,6 +51,8 @@ private:
case IrOpcode::ConvertF32S32:
case IrOpcode::ConvertF32U32:
case IrOpcode::ConvertF32F16:
case IrOpcode::FPFloor32:
case IrOpcode::FPCeil32:
return true;
case IrOpcode::BitCastU32F32:
case IrOpcode::BitCastF32U32:
@ -67,6 +69,11 @@ private:
case IrOpcode::SelectU32:
case IrOpcode::SelectF32:
return Contains(value.Argument(1)) && Contains(value.Argument(2));
case IrOpcode::FPMin32:
case IrOpcode::FPMax32:
case IrOpcode::FPMinTri32:
case IrOpcode::FPMaxTri32:
case IrOpcode::FPMedTri32:
case IrOpcode::Phi:
for (std::size_t index = 0; index < value.ArgumentCount(); ++index) {
if (!Contains(value.Argument(index))) return false;