perf(shader): treat f32 min, max, med3, floor and ceil results as denormal-free
In the float modes that flush f32 denormals, writeOperand flushes the result of v_min/max/min3/max3/med3_f32 and v_floor/ceil_f32. DenormalFlushEliminator drops a flush only when it can show the value isn't a denormal, and it had no rule for these opcodes, so their result flush stayed even when every operand had already been flushed. FPMin32, FPMax32, FPMinTri32, FPMaxTri32 and FPMedTri32 return one of their operands' bits, or the and or or of two zeros, so the result is denormal-free when every operand is. FPFloor32 and FPCeil32 return an integer, a zero, an infinity or a NaN whatever their operand. In IEEE mode the min and max results and the floor and ceil results go through selects with a quieted operand, which the eliminator can't follow, so their flush stays there. agc_denormal_flush_elimination covers the five min and max forms with flushed and with unknown operands, floor and ceil of an unknown value, and a max carried around a loop.
This commit is contained in:
parent
2ccbd376f8
commit
69d0dc2288
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue