944 lines
42 KiB
C++
944 lines
42 KiB
C++
#include "IntermediateRepresentation/IrProgram.hpp"
|
|
#include "Optimization/MaskedSelectEliminator.hpp"
|
|
#include "RdnaDecoder/RdnaInstruction.hpp"
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <exception>
|
|
#include <functional>
|
|
#include <initializer_list>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
using namespace ShaderRecompiler;
|
|
|
|
namespace {
|
|
|
|
struct Builder {
|
|
IrProgram program;
|
|
IrBlock* block = nullptr;
|
|
|
|
Builder() {
|
|
block = &newBlock();
|
|
program.SetEntryBlock(*block);
|
|
}
|
|
|
|
IrBlock& newBlock() {
|
|
auto& created = program.CreateBlock();
|
|
program.BlockOrder().push_back(&created);
|
|
return created;
|
|
}
|
|
|
|
IrValue& constant(std::uint32_t value) {
|
|
auto& created = program.CreateValue(IrOpcode::Void, IrType::U32);
|
|
created.SetImmediateU32(value);
|
|
return created;
|
|
}
|
|
|
|
IrValue& truth() {
|
|
auto& created = program.CreateValue(IrOpcode::Void, IrType::Bool);
|
|
created.SetImmediateBool(true);
|
|
return created;
|
|
}
|
|
|
|
IrValue& emit(IrOpcode opcode, IrType type, std::initializer_list<IrValue*> arguments, std::uint64_t flags = 0) {
|
|
auto& created = program.CreateValue(opcode, type, flags);
|
|
for (auto* argument : arguments) created.AddArgument(argument);
|
|
block->AppendInstruction(&created);
|
|
return created;
|
|
}
|
|
|
|
IrValue& lane() {
|
|
return emit(IrOpcode::LaneId, IrType::U32, {});
|
|
}
|
|
|
|
IrValue& mask(std::uint32_t lanes) {
|
|
return emit(IrOpcode::ULessThan32, IrType::Bool, {&lane(), &constant(lanes)});
|
|
}
|
|
|
|
IrValue& select(IrValue& condition, IrValue& active, IrValue& inactive) {
|
|
return emit(IrOpcode::SelectU32, IrType::U32, {&condition, &active, &inactive});
|
|
}
|
|
|
|
IrValue& add(IrValue& value, std::uint32_t immediate) {
|
|
return emit(IrOpcode::IAdd32, IrType::U32, {&value, &constant(immediate)});
|
|
}
|
|
|
|
IrValue& logicalAnd(IrValue& left, IrValue& right) {
|
|
return emit(IrOpcode::LogicalAnd, IrType::Bool, {&left, &right});
|
|
}
|
|
|
|
IrValue& threadBit(IrValue& low, IrValue& high) {
|
|
auto& laneId = lane();
|
|
auto& word = select(emit(IrOpcode::ULessThan32, IrType::Bool, {&laneId, &constant(32u)}), low, high);
|
|
auto& shift = emit(IrOpcode::BitwiseAnd32, IrType::U32, {&laneId, &constant(31u)});
|
|
auto& shifted = emit(IrOpcode::ShiftRightLogical32, IrType::U32, {&word, &shift});
|
|
auto& bit = emit(IrOpcode::BitwiseAnd32, IrType::U32, {&shifted, &constant(1u)});
|
|
return emit(IrOpcode::INotEqual32, IrType::Bool, {&bit, &constant(0u)});
|
|
}
|
|
|
|
IrValue& ballotBit(IrValue& predicate) {
|
|
auto& ballot = emit(IrOpcode::Ballot, IrType::U32x4, {&predicate});
|
|
auto& low = emit(IrOpcode::CompositeExtractU32x4, IrType::U32, {&ballot, &constant(0u)});
|
|
auto& high = emit(IrOpcode::CompositeExtractU32x4, IrType::U32, {&ballot, &constant(1u)});
|
|
return threadBit(low, high);
|
|
}
|
|
|
|
std::pair<IrValue*, IrValue*> ballotWords(IrValue& predicate) {
|
|
auto& ballot = emit(IrOpcode::Ballot, IrType::U32x4, {&predicate});
|
|
return {&emit(IrOpcode::CompositeExtractU32x4, IrType::U32, {&ballot, &constant(0u)}), &emit(IrOpcode::CompositeExtractU32x4, IrType::U32, {&ballot, &constant(1u)})};
|
|
}
|
|
|
|
IrValue& wordAnd(IrValue& left, IrValue& right) {
|
|
return emit(IrOpcode::BitwiseAnd32, IrType::U32, {&left, &right});
|
|
}
|
|
|
|
IrValue& phi(IrType type, IrBlock& predecessor, IrValue& incoming) {
|
|
auto& created = program.CreateValue(IrOpcode::Phi, type);
|
|
block->AppendInstruction(&created);
|
|
created.AddPhiOperand(&predecessor, &incoming);
|
|
return created;
|
|
}
|
|
|
|
void keep(IrValue& value) {
|
|
(void)emit(IrOpcode::ReferenceU32, IrType::Void, {&value});
|
|
}
|
|
|
|
std::uint32_t eliminate() {
|
|
return MaskedSelectEliminator{}.Eliminate(program).removedSelects;
|
|
}
|
|
};
|
|
|
|
bool removed(const IrValue& value) {
|
|
return value.Parent() == nullptr;
|
|
}
|
|
|
|
bool expect(const char* name, bool condition) {
|
|
if (!condition) std::fprintf(stderr, "%s\n", name);
|
|
return condition;
|
|
}
|
|
|
|
bool run(const char* name, const std::function<bool()>& test) {
|
|
try {
|
|
return expect(name, test());
|
|
} catch (const std::exception& error) {
|
|
std::fprintf(stderr, "%s: %s\n", name, error.what());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
int main() {
|
|
bool passed = true;
|
|
|
|
passed &= run("a write read only under its own exec must lose its select", [] {
|
|
Builder b;
|
|
auto& exec = b.mask(16u);
|
|
auto& old = b.lane();
|
|
auto& written = b.select(exec, b.add(old, 1u), old);
|
|
auto& next = b.select(exec, b.add(written, 2u), old);
|
|
b.keep(next);
|
|
return b.eliminate() == 1u && removed(written) && !removed(next);
|
|
});
|
|
|
|
passed &= run("a write read under a wider exec must keep its select", [] {
|
|
Builder b;
|
|
auto& wide = b.mask(32u);
|
|
auto& exec = b.logicalAnd(wide, b.mask(16u));
|
|
auto& old = b.lane();
|
|
auto& written = b.select(exec, b.add(old, 1u), old);
|
|
auto& next = b.select(wide, b.add(written, 2u), old);
|
|
b.keep(next);
|
|
return b.eliminate() == 0u && !removed(written);
|
|
});
|
|
|
|
passed &= run("a write read under a narrower exec must lose its select", [] {
|
|
Builder b;
|
|
auto& exec = b.mask(32u);
|
|
auto& narrow = b.logicalAnd(exec, b.mask(16u));
|
|
auto& old = b.lane();
|
|
auto& written = b.select(exec, b.add(old, 1u), old);
|
|
auto& next = b.select(narrow, b.add(written, 2u), old);
|
|
b.keep(next);
|
|
return b.eliminate() == 1u && removed(written);
|
|
});
|
|
|
|
passed &= run("a write whose old lanes flow into the next write must keep its select", [] {
|
|
Builder b;
|
|
auto& exec = b.mask(16u);
|
|
auto& old = b.lane();
|
|
auto& written = b.select(exec, b.add(old, 1u), old);
|
|
auto& next = b.select(exec, b.add(old, 2u), written);
|
|
b.keep(next);
|
|
return b.eliminate() == 0u && !removed(written);
|
|
});
|
|
|
|
passed &= run("a chain of writes under one exec must collapse once the last one goes", [] {
|
|
Builder b;
|
|
auto& exec = b.mask(16u);
|
|
auto& old = b.lane();
|
|
auto& first = b.select(exec, b.add(old, 1u), old);
|
|
auto& second = b.select(exec, b.add(first, 2u), first);
|
|
auto& sink = b.select(exec, b.add(second, 3u), old);
|
|
b.keep(sink);
|
|
return b.eliminate() == 2u && removed(first) && removed(second) && !removed(sink);
|
|
});
|
|
|
|
passed &= run("a write read through thousands of lane-local values still loses its select", [] {
|
|
Builder b;
|
|
auto& exec = b.mask(16u);
|
|
auto& old = b.lane();
|
|
auto& written = b.select(exec, b.add(old, 1u), old);
|
|
IrValue* value = &written;
|
|
for (std::uint32_t step = 0; step < 6000u; ++step) value = &b.add(*value, step);
|
|
b.keep(b.select(exec, *value, old));
|
|
return b.eliminate() == 1u && removed(written);
|
|
});
|
|
|
|
passed &= run("thousands of lane-local values ending in a wider read keep the select", [] {
|
|
Builder b;
|
|
auto& wide = b.mask(32u);
|
|
auto& exec = b.logicalAnd(wide, b.mask(16u));
|
|
auto& old = b.lane();
|
|
auto& written = b.select(exec, b.add(old, 1u), old);
|
|
IrValue* value = &written;
|
|
for (std::uint32_t step = 0; step < 6000u; ++step) value = &b.add(*value, step);
|
|
b.keep(b.select(wide, *value, old));
|
|
return b.eliminate() == 0u && !removed(written);
|
|
});
|
|
|
|
passed &= run("cross-lane reads must keep the select", [] {
|
|
bool ok = true;
|
|
for (const IrOpcode opcode : {IrOpcode::DppMoveU32, IrOpcode::BpermuteU32, IrOpcode::Permlane16U32, IrOpcode::SwizzleU32}) {
|
|
Builder b;
|
|
auto& exec = b.mask(16u);
|
|
auto& old = b.lane();
|
|
auto& written = b.select(exec, b.add(old, 1u), old);
|
|
auto& read = b.emit(opcode, IrType::U32, {&written, &exec});
|
|
b.keep(b.select(exec, read, old));
|
|
ok &= b.eliminate() == 0u && !removed(written);
|
|
}
|
|
Builder b;
|
|
auto& exec = b.mask(16u);
|
|
auto& old = b.lane();
|
|
auto& written = b.select(exec, b.add(old, 1u), old);
|
|
auto& ballot = b.emit(IrOpcode::Ballot, IrType::U32x4, {&b.emit(IrOpcode::INotEqual32, IrType::Bool, {&written, &b.constant(0u)})});
|
|
b.keep(b.emit(IrOpcode::CompositeExtractU32x4, IrType::U32, {&ballot, &b.constant(0u)}));
|
|
return ok && b.eliminate() == 0u && !removed(written);
|
|
});
|
|
|
|
passed &= run("a first-lane read guarded at both ends loses the source select", [] {
|
|
Builder b;
|
|
auto& exec = b.mask(16u);
|
|
auto& narrow = b.logicalAnd(exec, b.mask(8u));
|
|
auto& old = b.lane();
|
|
auto& written = b.select(exec, b.add(old, 1u), old);
|
|
auto& read = b.emit(IrOpcode::ReadFirstLane, IrType::U32, {&written, &narrow});
|
|
b.keep(b.select(narrow, read, old));
|
|
return b.eliminate() == 1u && removed(written);
|
|
});
|
|
|
|
passed &= run("an unguarded first-lane result keeps the source select for empty exec", [] {
|
|
Builder b;
|
|
auto& exec = b.emit(IrOpcode::INotEqual32, IrType::Bool, {&b.lane(), &b.constant(0u)});
|
|
auto& old = b.lane();
|
|
auto& written = b.select(exec, b.add(old, 1u), old);
|
|
auto& empty = b.logicalAnd(exec, b.mask(0u));
|
|
b.keep(b.emit(IrOpcode::ReadFirstLane, IrType::U32, {&written, &empty}));
|
|
return b.eliminate() == 0u && !removed(written);
|
|
});
|
|
|
|
passed &= run("a first-lane result read under the write mask keeps the select when read exec is narrower", [] {
|
|
Builder b;
|
|
auto& exec = b.emit(IrOpcode::INotEqual32, IrType::Bool, {&b.lane(), &b.constant(0u)});
|
|
auto& empty = b.logicalAnd(exec, b.mask(0u));
|
|
auto& old = b.lane();
|
|
auto& written = b.select(exec, b.add(old, 1u), old);
|
|
auto& read = b.emit(IrOpcode::ReadFirstLane, IrType::U32, {&written, &empty});
|
|
b.keep(b.select(exec, read, old));
|
|
return b.eliminate() == 0u && !removed(written);
|
|
});
|
|
|
|
passed &= run("a first-lane read under a wider exec keeps the select", [] {
|
|
Builder b;
|
|
auto& wide = b.mask(32u);
|
|
auto& exec = b.logicalAnd(wide, b.mask(16u));
|
|
auto& old = b.lane();
|
|
auto& written = b.select(exec, b.add(old, 1u), old);
|
|
auto& read = b.emit(IrOpcode::ReadFirstLane, IrType::U32, {&written, &wide});
|
|
b.keep(b.select(wide, read, old));
|
|
return b.eliminate() == 0u && !removed(written);
|
|
});
|
|
|
|
const auto selectedExec = [](bool negate, bool widerArm) {
|
|
Builder b;
|
|
auto& wide = b.mask(32u);
|
|
auto& exec = b.logicalAnd(wide, b.mask(16u));
|
|
auto& empty = b.logicalAnd(exec, b.mask(0u));
|
|
auto& selector = negate ? b.emit(IrOpcode::LogicalNot, IrType::Bool, {&exec}) : exec;
|
|
auto& later = b.emit(IrOpcode::SelectU1, IrType::Bool, {&selector, negate ? (widerArm ? &wide : &empty) : &wide, negate ? &wide : (widerArm ? &wide : &empty)});
|
|
auto& old = b.lane();
|
|
auto& written = b.select(exec, b.add(old, 1u), old);
|
|
b.keep(b.select(later, b.add(written, 2u), old));
|
|
return std::pair{b.eliminate(), removed(written)};
|
|
};
|
|
|
|
passed &= run("selected boolean exec implies the write mask through its selector", [&] {
|
|
for (bool negate : {false, true}) {
|
|
const auto [count, gone] = selectedExec(negate, false);
|
|
if (count != 1u || !gone) return false;
|
|
}
|
|
return true;
|
|
});
|
|
|
|
passed &= run("selected boolean exec with a wider unguarded arm keeps the select", [&] {
|
|
for (bool negate : {false, true}) {
|
|
const auto [count, gone] = selectedExec(negate, true);
|
|
if (count != 0u || gone) return false;
|
|
}
|
|
return true;
|
|
});
|
|
|
|
const auto rebuiltExec = [](bool mismatchedHigh) {
|
|
Builder b;
|
|
const auto [low, high] = b.ballotWords(b.mask(16u));
|
|
const auto [wideLow, wideHigh] = b.ballotWords(b.mask(48u));
|
|
auto& exec = b.threadBit(*low, *high);
|
|
auto& later = b.threadBit(*low, mismatchedHigh ? *wideHigh : *high);
|
|
auto& old = b.lane();
|
|
auto& written = b.select(exec, b.add(old, 1u), old);
|
|
b.keep(b.select(later, b.add(written, 2u), old));
|
|
return std::pair{b.eliminate(), removed(written)};
|
|
};
|
|
|
|
passed &= run("an exec rebuilt from the same words implies the write mask", [&] {
|
|
const auto [count, gone] = rebuiltExec(false);
|
|
return count >= 1u && gone;
|
|
});
|
|
|
|
passed &= run("matching low mask words alone do not imply a wave64 write mask", [&] {
|
|
const auto [count, gone] = rebuiltExec(true);
|
|
return count == 0u && !gone;
|
|
});
|
|
|
|
const auto firstLaneInLoop = [](bool carriedResult) {
|
|
Builder b;
|
|
auto& exec = b.mask(16u);
|
|
auto& old = b.lane();
|
|
auto& written = b.select(exec, b.add(old, 1u), old);
|
|
IrBlock& entry = *b.block;
|
|
IrBlock& loop = b.newBlock();
|
|
entry.AddBranch(&loop);
|
|
loop.AddBranch(&loop);
|
|
b.block = &loop;
|
|
auto& loopExec = b.logicalAnd(exec, b.mask(8u));
|
|
auto& read = b.emit(IrOpcode::ReadFirstLane, IrType::U32, {&written, &loopExec});
|
|
if (carriedResult) {
|
|
auto& carried = b.phi(IrType::U32, entry, old);
|
|
carried.AddPhiOperand(&loop, &read);
|
|
b.keep(b.select(loopExec, carried, old));
|
|
} else {
|
|
b.keep(b.select(loopExec, read, old));
|
|
}
|
|
return std::pair{b.eliminate(), removed(written)};
|
|
};
|
|
|
|
passed &= run("a loop-local first-lane result guarded in its own iteration loses the source select", [&] {
|
|
const auto [count, gone] = firstLaneInLoop(false);
|
|
return count == 1u && gone;
|
|
});
|
|
|
|
passed &= run("a first-lane result carried to another loop iteration keeps the source select", [&] {
|
|
const auto [count, gone] = firstLaneInLoop(true);
|
|
return count == 0u && !gone;
|
|
});
|
|
|
|
passed &= run("a branch condition derived from the write keeps the select", [] {
|
|
Builder b;
|
|
auto& exec = b.mask(16u);
|
|
auto& old = b.lane();
|
|
auto& written = b.select(exec, b.add(old, 1u), old);
|
|
auto& taken = b.emit(IrOpcode::INotEqual32, IrType::Bool, {&written, &b.constant(0u)});
|
|
(void)b.emit(IrOpcode::Reference, IrType::Void, {&taken});
|
|
return b.eliminate() == 0u && !removed(written);
|
|
});
|
|
|
|
passed &= run("a compare masked by the same exec must not keep the select", [] {
|
|
Builder b;
|
|
auto& exec = b.mask(16u);
|
|
auto& old = b.lane();
|
|
auto& written = b.select(exec, b.add(old, 1u), old);
|
|
auto& compare = b.logicalAnd(exec, b.emit(IrOpcode::INotEqual32, IrType::Bool, {&written, &b.constant(0u)}));
|
|
auto& ballot = b.emit(IrOpcode::Ballot, IrType::U32x4, {&compare});
|
|
b.keep(b.emit(IrOpcode::CompositeExtractU32x4, IrType::U32, {&ballot, &b.constant(0u)}));
|
|
return b.eliminate() == 1u && removed(written);
|
|
});
|
|
|
|
passed &= run("an exec rebuilt from a ballot of a narrower mask implies the write's exec", [] {
|
|
Builder b;
|
|
auto& exec = b.mask(48u);
|
|
auto& old = b.lane();
|
|
auto& written = b.select(exec, b.add(old, 1u), old);
|
|
auto& later = b.ballotBit(b.logicalAnd(exec, b.mask(40u)));
|
|
auto& next = b.select(later, b.add(written, 2u), old);
|
|
b.keep(next);
|
|
return b.eliminate() == 1u && removed(written);
|
|
});
|
|
|
|
passed &= run("an exec rebuilt from a ballot of a wider mask keeps the select", [] {
|
|
Builder b;
|
|
auto& wide = b.mask(48u);
|
|
auto& exec = b.logicalAnd(wide, b.mask(40u));
|
|
auto& old = b.lane();
|
|
auto& written = b.select(exec, b.add(old, 1u), old);
|
|
auto& later = b.ballotBit(wide);
|
|
b.keep(b.select(later, b.add(written, 2u), old));
|
|
return b.eliminate() == 0u && !removed(written);
|
|
});
|
|
|
|
passed &= run("a select under the full wave's exec must go whatever reads it", [] {
|
|
Builder b;
|
|
auto& full = b.threadBit(b.constant(0xffffffffu), b.constant(0xffffffffu));
|
|
auto& old = b.lane();
|
|
auto& written = b.select(full, b.add(old, 1u), old);
|
|
b.keep(written);
|
|
return b.eliminate() != 0u && removed(written);
|
|
});
|
|
|
|
passed &= run("a partial wave's exec is not the full wave", [] {
|
|
Builder b;
|
|
auto& partial = b.threadBit(b.constant(0xffffffffu), b.constant(0x0000ffffu));
|
|
auto& old = b.lane();
|
|
auto& written = b.select(partial, b.add(old, 1u), old);
|
|
b.keep(written);
|
|
return b.eliminate() == 0u && !removed(written);
|
|
});
|
|
|
|
const auto diamond = [](bool wideRead) {
|
|
Builder b;
|
|
auto& wide = b.mask(48u);
|
|
auto& exec = b.logicalAnd(wide, b.mask(16u));
|
|
auto& old = b.lane();
|
|
IrBlock& head = *b.block;
|
|
IrBlock& body = b.newBlock();
|
|
IrBlock& merge = b.newBlock();
|
|
head.AddBranch(&body);
|
|
head.AddBranch(&merge);
|
|
body.AddBranch(&merge);
|
|
b.block = &body;
|
|
auto& written = b.select(exec, b.add(old, 1u), old);
|
|
b.block = &merge;
|
|
auto& phi = b.program.CreateValue(IrOpcode::Phi, IrType::U32);
|
|
merge.AppendInstruction(&phi);
|
|
phi.AddPhiOperand(&head, &old);
|
|
phi.AddPhiOperand(&body, &written);
|
|
b.keep(b.select(wideRead ? wide : exec, b.add(phi, 2u), old));
|
|
return std::pair{b.eliminate(), removed(written)};
|
|
};
|
|
|
|
passed &= run("a write merged by a phi and read under its own exec must lose its select", [&] {
|
|
const auto [count, gone] = diamond(false);
|
|
return count == 1u && gone;
|
|
});
|
|
|
|
passed &= run("a write merged by a phi and read under the wider exec must keep its select", [&] {
|
|
const auto [count, gone] = diamond(true);
|
|
return count == 0u && !gone;
|
|
});
|
|
|
|
const auto mergedExec = [](bool wideBranch, bool loopMerge) {
|
|
Builder b;
|
|
auto& wide = b.mask(48u);
|
|
auto& exec = b.logicalAnd(wide, b.mask(32u));
|
|
auto& old = b.lane();
|
|
auto& written = b.select(exec, b.add(old, 1u), old);
|
|
IrBlock& head = *b.block;
|
|
IrBlock& body = b.newBlock();
|
|
IrBlock& merge = b.newBlock();
|
|
head.AddBranch(&body);
|
|
head.AddBranch(&merge);
|
|
body.AddBranch(&merge);
|
|
b.block = &body;
|
|
auto& killed = b.logicalAnd(wideBranch ? wide : exec, b.mask(16u));
|
|
auto& phi = b.program.CreateValue(IrOpcode::Phi, IrType::Bool);
|
|
merge.AppendInstruction(&phi);
|
|
phi.AddPhiOperand(&head, &exec);
|
|
phi.AddPhiOperand(&body, &killed);
|
|
if (loopMerge) {
|
|
IrBlock& exit = b.newBlock();
|
|
merge.AddBranch(&merge);
|
|
merge.AddBranch(&exit);
|
|
phi.AddPhiOperand(&merge, &phi);
|
|
b.block = &exit;
|
|
} else {
|
|
b.block = &merge;
|
|
}
|
|
b.keep(b.select(phi, b.add(written, 2u), old));
|
|
return std::pair{b.eliminate(), removed(written)};
|
|
};
|
|
|
|
passed &= run("an exec merged by a phi whose every incoming exec implies the write's exec drops the select", [&] {
|
|
const auto [count, gone] = mergedExec(false, false);
|
|
return count == 1u && gone;
|
|
});
|
|
|
|
passed &= run("an exec merged by a phi with a wider incoming exec keeps the select", [&] {
|
|
const auto [count, gone] = mergedExec(true, false);
|
|
return count == 0u && !gone;
|
|
});
|
|
|
|
passed &= run("an exec carried around a loop whose every incoming exec implies the write's exec drops the select", [&] {
|
|
const auto [count, gone] = mergedExec(false, true);
|
|
return count == 1u && gone;
|
|
});
|
|
|
|
passed &= run("an exec carried around a loop with a wider incoming exec keeps the select", [&] {
|
|
const auto [count, gone] = mergedExec(true, true);
|
|
return count == 0u && !gone;
|
|
});
|
|
|
|
const auto mergedWords = [](bool wideBranch) {
|
|
Builder b;
|
|
auto& wide = b.mask(48u);
|
|
auto& exec = b.logicalAnd(wide, b.mask(40u));
|
|
auto& old = b.lane();
|
|
auto& written = b.select(exec, b.add(old, 1u), old);
|
|
auto& saved = b.emit(IrOpcode::Ballot, IrType::U32x4, {&exec});
|
|
auto& savedLow = b.emit(IrOpcode::CompositeExtractU32x4, IrType::U32, {&saved, &b.constant(0u)});
|
|
auto& savedHigh = b.emit(IrOpcode::CompositeExtractU32x4, IrType::U32, {&saved, &b.constant(1u)});
|
|
IrBlock& head = *b.block;
|
|
IrBlock& body = b.newBlock();
|
|
IrBlock& merge = b.newBlock();
|
|
head.AddBranch(&body);
|
|
head.AddBranch(&merge);
|
|
body.AddBranch(&merge);
|
|
b.block = &body;
|
|
auto& survived = b.emit(IrOpcode::Ballot, IrType::U32x4, {&b.logicalAnd(wideBranch ? wide : exec, b.mask(24u))});
|
|
auto& survivedLow = b.emit(IrOpcode::CompositeExtractU32x4, IrType::U32, {&survived, &b.constant(0u)});
|
|
auto& survivedHigh = b.emit(IrOpcode::CompositeExtractU32x4, IrType::U32, {&survived, &b.constant(1u)});
|
|
auto& low = b.program.CreateValue(IrOpcode::Phi, IrType::U32);
|
|
auto& high = b.program.CreateValue(IrOpcode::Phi, IrType::U32);
|
|
merge.AppendInstruction(&low);
|
|
merge.AppendInstruction(&high);
|
|
low.AddPhiOperand(&head, &savedLow);
|
|
low.AddPhiOperand(&body, &survivedLow);
|
|
high.AddPhiOperand(&head, &savedHigh);
|
|
high.AddPhiOperand(&body, &survivedHigh);
|
|
b.block = &merge;
|
|
b.keep(b.select(b.threadBit(low, high), b.add(written, 2u), old));
|
|
return std::pair{b.eliminate(), removed(written)};
|
|
};
|
|
|
|
passed &= run("an exec rebuilt from mask words merged by phis implies the write's exec", [&] {
|
|
const auto [count, gone] = mergedWords(false);
|
|
return count == 1u && gone;
|
|
});
|
|
|
|
passed &= run("an exec rebuilt from merged mask words with a wider incoming ballot keeps the select", [&] {
|
|
const auto [count, gone] = mergedWords(true);
|
|
return count == 0u && !gone;
|
|
});
|
|
|
|
const auto chained = [](bool wideRoot, std::uint32_t kills) {
|
|
Builder b;
|
|
auto& wide = b.mask(48u);
|
|
auto& exec = b.logicalAnd(wide, b.mask(32u));
|
|
auto& old = b.lane();
|
|
auto& written = b.select(exec, b.add(old, 1u), old);
|
|
IrValue* later = wideRoot ? &wide : &exec;
|
|
for (std::uint32_t kill = 0; kill < kills; ++kill) later = &b.logicalAnd(*later, b.mask(31u - kill % 24u));
|
|
b.keep(b.select(*later, b.add(written, 2u), old));
|
|
return std::pair{b.eliminate(), removed(written)};
|
|
};
|
|
|
|
passed &= run("an exec narrowed by a long chain of kills still implies the write's exec", [&] {
|
|
const auto [count, gone] = chained(false, 24u);
|
|
return count == 1u && gone;
|
|
});
|
|
|
|
passed &= run("a long chain of kills rooted at a wider exec keeps the select", [&] {
|
|
const auto [count, gone] = chained(true, 24u);
|
|
return count == 0u && !gone;
|
|
});
|
|
|
|
passed &= run("an exec narrowed by hundreds of kills still implies the write's exec", [&] {
|
|
const auto [count, gone] = chained(false, 300u);
|
|
return count == 1u && gone;
|
|
});
|
|
|
|
passed &= run("hundreds of kills rooted at a wider exec keep the select", [&] {
|
|
const auto [count, gone] = chained(true, 300u);
|
|
return count == 0u && !gone;
|
|
});
|
|
|
|
passed &= run("a write in a loop must keep its select", [] {
|
|
Builder b;
|
|
auto& exec = b.mask(16u);
|
|
auto& old = b.lane();
|
|
IrBlock& head = *b.block;
|
|
IrBlock& loop = b.newBlock();
|
|
IrBlock& exit = b.newBlock();
|
|
head.AddBranch(&loop);
|
|
loop.AddBranch(&loop);
|
|
loop.AddBranch(&exit);
|
|
b.block = &loop;
|
|
auto& written = b.select(exec, b.add(old, 1u), old);
|
|
auto& next = b.select(exec, b.add(written, 2u), old);
|
|
b.keep(next);
|
|
return b.eliminate() == 0u && !removed(written);
|
|
});
|
|
|
|
const auto laterLoop = [](bool wideRead) {
|
|
Builder b;
|
|
auto& wide = b.mask(48u);
|
|
auto& exec = b.logicalAnd(wide, b.mask(16u));
|
|
auto& old = b.lane();
|
|
auto& written = b.select(exec, b.add(old, 1u), old);
|
|
IrBlock& head = *b.block;
|
|
IrBlock& loop = b.newBlock();
|
|
head.AddBranch(&loop);
|
|
loop.AddBranch(&loop);
|
|
b.block = &loop;
|
|
b.keep(b.select(wideRead ? wide : exec, b.add(written, 2u), old));
|
|
return std::pair{b.eliminate(), removed(written)};
|
|
};
|
|
|
|
passed &= run("a write read inside a later loop under its own exec loses its select", [&] {
|
|
const auto [count, gone] = laterLoop(false);
|
|
return count == 1u && gone;
|
|
});
|
|
|
|
passed &= run("a write read inside a later loop under a wider exec keeps its select", [&] {
|
|
const auto [count, gone] = laterLoop(true);
|
|
return count == 0u && !gone;
|
|
});
|
|
|
|
enum class Latch { Narrowed, Revived };
|
|
const auto carried = [](Latch latch, bool readInside) {
|
|
Builder b;
|
|
auto& wide = b.mask(48u);
|
|
auto& exec = b.logicalAnd(wide, b.mask(40u));
|
|
auto& old = b.lane();
|
|
auto& written = b.select(exec, b.add(old, 1u), old);
|
|
const auto [savedLow, savedHigh] = b.ballotWords(exec);
|
|
IrBlock& entry = *b.block;
|
|
IrBlock& header = b.newBlock();
|
|
IrBlock& exit = b.newBlock();
|
|
entry.AddBranch(&header);
|
|
header.AddBranch(&header);
|
|
header.AddBranch(&exit);
|
|
b.block = &header;
|
|
auto& low = b.phi(IrType::U32, entry, *savedLow);
|
|
auto& high = b.phi(IrType::U32, entry, *savedHigh);
|
|
auto& value = b.phi(IrType::U32, entry, written);
|
|
auto& loopExec = b.threadBit(low, high);
|
|
auto& next = b.select(loopExec, b.add(value, 2u), value);
|
|
if (readInside) b.keep(b.select(loopExec, b.add(value, 3u), old));
|
|
if (latch == Latch::Narrowed) {
|
|
const auto [stayLow, stayHigh] = b.ballotWords(b.mask(24u));
|
|
low.AddPhiOperand(&header, &b.wordAnd(low, *stayLow));
|
|
high.AddPhiOperand(&header, &b.wordAnd(high, *stayHigh));
|
|
} else {
|
|
const auto [revivedLow, revivedHigh] = b.ballotWords(wide);
|
|
low.AddPhiOperand(&header, revivedLow);
|
|
high.AddPhiOperand(&header, revivedHigh);
|
|
}
|
|
value.AddPhiOperand(&header, &next);
|
|
b.block = &exit;
|
|
b.keep(b.select(b.threadBit(*savedLow, *savedHigh), b.add(value, 4u), old));
|
|
return std::pair{b.eliminate(), removed(written)};
|
|
};
|
|
|
|
passed &= run("a write carried through a loop whose exec is saved, narrowed and restored loses its select", [&] {
|
|
const auto [count, gone] = carried(Latch::Narrowed, false);
|
|
return count == 1u && gone;
|
|
});
|
|
|
|
passed &= run("a write read inside a loop under the loop's narrowed exec loses its select", [&] {
|
|
const auto [count, gone] = carried(Latch::Narrowed, true);
|
|
return count == 1u && gone;
|
|
});
|
|
|
|
passed &= run("a lane that becomes active again inside a loop and reads the write keeps the select", [&] {
|
|
const auto [count, gone] = carried(Latch::Revived, true);
|
|
return count == 0u && !gone;
|
|
});
|
|
|
|
passed &= run("a lane that becomes active again inside a loop but never reads the write lets the select go", [&] {
|
|
const auto [count, gone] = carried(Latch::Revived, false);
|
|
return count == 1u && gone;
|
|
});
|
|
|
|
passed &= run("an exec proven only by assuming a loop phi is forgotten once that phi turns out wider", [] {
|
|
Builder b;
|
|
auto& wide = b.mask(48u);
|
|
auto& exec = b.logicalAnd(wide, b.mask(40u));
|
|
auto& old = b.lane();
|
|
auto& written = b.select(exec, b.add(old, 1u), old);
|
|
const auto [savedLow, savedHigh] = b.ballotWords(exec);
|
|
IrBlock& entry = *b.block;
|
|
IrBlock& header = b.newBlock();
|
|
IrBlock& kill = b.newBlock();
|
|
IrBlock& revive = b.newBlock();
|
|
IrBlock& latch = b.newBlock();
|
|
IrBlock& exit = b.newBlock();
|
|
entry.AddBranch(&header);
|
|
header.AddBranch(&kill);
|
|
header.AddBranch(&revive);
|
|
kill.AddBranch(&latch);
|
|
revive.AddBranch(&latch);
|
|
latch.AddBranch(&header);
|
|
latch.AddBranch(&exit);
|
|
b.block = &header;
|
|
auto& low = b.phi(IrType::U32, entry, *savedLow);
|
|
auto& high = b.phi(IrType::U32, entry, *savedHigh);
|
|
(void)b.select(b.threadBit(low, high), written, old);
|
|
b.block = &kill;
|
|
const auto [stayLow, stayHigh] = b.ballotWords(b.mask(24u));
|
|
auto& killedLow = b.wordAnd(low, *stayLow);
|
|
auto& killedHigh = b.wordAnd(high, *stayHigh);
|
|
b.keep(b.select(b.threadBit(killedLow, killedHigh), written, old));
|
|
b.block = &revive;
|
|
const auto [revivedLow, revivedHigh] = b.ballotWords(wide);
|
|
b.block = &latch;
|
|
auto& mergedLow = b.phi(IrType::U32, kill, killedLow);
|
|
auto& mergedHigh = b.phi(IrType::U32, kill, killedHigh);
|
|
mergedLow.AddPhiOperand(&revive, revivedLow);
|
|
mergedHigh.AddPhiOperand(&revive, revivedHigh);
|
|
low.AddPhiOperand(&latch, &mergedLow);
|
|
high.AddPhiOperand(&latch, &mergedHigh);
|
|
return b.eliminate() == 0u && !removed(written);
|
|
});
|
|
|
|
passed &= run("a write mask computed inside an earlier loop does not let that loop's phis stand for it", [] {
|
|
Builder b;
|
|
auto& old = b.lane();
|
|
IrBlock& entry = *b.block;
|
|
IrBlock& header = b.newBlock();
|
|
IrBlock& exit = b.newBlock();
|
|
entry.AddBranch(&header);
|
|
header.AddBranch(&header);
|
|
header.AddBranch(&exit);
|
|
b.block = &header;
|
|
auto& low = b.phi(IrType::U32, entry, b.constant(0u));
|
|
auto& high = b.phi(IrType::U32, entry, b.constant(0u));
|
|
auto& bound = b.phi(IrType::U32, entry, b.constant(64u));
|
|
auto& exec = b.emit(IrOpcode::ULessThan32, IrType::Bool, {&b.lane(), &bound});
|
|
const auto [stayLow, stayHigh] = b.ballotWords(exec);
|
|
low.AddPhiOperand(&header, stayLow);
|
|
high.AddPhiOperand(&header, stayHigh);
|
|
bound.AddPhiOperand(&header, &b.emit(IrOpcode::ISub32, IrType::U32, {&bound, &b.constant(8u)}));
|
|
b.block = &exit;
|
|
auto& written = b.select(exec, b.add(old, 1u), old);
|
|
b.keep(b.select(b.threadBit(low, high), b.add(written, 2u), old));
|
|
return b.eliminate() == 0u && !removed(written);
|
|
});
|
|
|
|
const auto wholeQuads = [](Builder& b, IrValue& live) -> IrValue& {
|
|
auto& ballot = b.emit(IrOpcode::Ballot, IrType::U32x4, {&live});
|
|
auto& allLow = b.emit(IrOpcode::Ballot, IrType::U32x4, {&b.truth()});
|
|
auto& allHigh = b.emit(IrOpcode::Ballot, IrType::U32x4, {&b.truth()});
|
|
auto& low = b.emit(IrOpcode::BitwiseAnd32, IrType::U32, {&b.emit(IrOpcode::CompositeExtractU32x4, IrType::U32, {&ballot, &b.constant(0u)}), &b.emit(IrOpcode::CompositeExtractU32x4, IrType::U32, {&allLow, &b.constant(0u)})});
|
|
auto& high = b.emit(IrOpcode::BitwiseAnd32, IrType::U32, {&b.emit(IrOpcode::CompositeExtractU32x4, IrType::U32, {&ballot, &b.constant(1u)}), &b.emit(IrOpcode::CompositeExtractU32x4, IrType::U32, {&allHigh, &b.constant(1u)})});
|
|
auto& wqm = b.emit(IrOpcode::WqmU64, IrType::U64, {&b.emit(IrOpcode::CompositeConstructU64, IrType::U64, {&low, &high})});
|
|
return b.threadBit(b.emit(IrOpcode::CompositeExtractU64, IrType::U32, {&wqm, &b.constant(0u)}), b.emit(IrOpcode::CompositeExtractU64, IrType::U32, {&wqm, &b.constant(1u)}));
|
|
};
|
|
|
|
const auto sample = [&](IrShaderStage stage, std::uint32_t sampleFlags, IrOpcode opcode = IrOpcode::ImageSampleRaw, bool wholeQuadExec = false) {
|
|
Builder b;
|
|
b.program.Resources().stage = stage;
|
|
MemoryInfo memory;
|
|
memory.kind = ResourceKind::Image;
|
|
memory.imageSampleFlags = sampleFlags;
|
|
b.program.Resources().memoryInfo.push_back(memory);
|
|
auto& exec = wholeQuadExec ? wholeQuads(b, b.mask(16u)) : b.mask(16u);
|
|
auto& old = b.lane();
|
|
auto& written = b.select(exec, b.add(old, 1u), old);
|
|
auto& address = b.emit(IrOpcode::MakeImageAddress, IrType::ImageAddress, {&written, &old});
|
|
auto& image = b.emit(IrOpcode::GetImageResource, IrType::ImageResource, {});
|
|
auto& sampler = b.emit(IrOpcode::GetSamplerResource, IrType::SamplerResource, {});
|
|
MemoryFlags flags{0u, 0u};
|
|
std::uint64_t bits = 0;
|
|
std::memcpy(&bits, &flags, sizeof(flags));
|
|
auto& sampled = b.emit(opcode, IrType::U32x4, {&image, &sampler, &address}, bits);
|
|
b.keep(b.select(exec, b.emit(IrOpcode::CompositeExtractU32x4, IrType::U32, {&sampled, &b.constant(0u)}), old));
|
|
b.eliminate();
|
|
return removed(written);
|
|
};
|
|
|
|
passed &= run("an implicit-LOD pixel sample reads its quad's coordinates and keeps the select", [&] {
|
|
return !sample(IrShaderStage::Pixel, 0u);
|
|
});
|
|
|
|
passed &= run("an implicit-LOD pixel sample under whole quad mode reads no masked quad and drops the select", [&] {
|
|
return sample(IrShaderStage::Pixel, 0u, IrOpcode::ImageSampleRaw, true);
|
|
});
|
|
passed &= run("an explicit-LOD sample reads only its lane and drops the select", [&] {
|
|
return sample(IrShaderStage::Pixel, RdnaImageSampleFlagLevelZero) && sample(IrShaderStage::Mesh, 0u);
|
|
});
|
|
|
|
passed &= run("a pixel gather without an LOD of its own takes it from the quad and keeps the select", [&] {
|
|
return !sample(IrShaderStage::Pixel, 0u, IrOpcode::ImageGatherRaw) && !sample(IrShaderStage::Pixel, RdnaImageSampleFlagBias, IrOpcode::ImageGatherRaw);
|
|
});
|
|
|
|
passed &= run("a gather with an explicit LOD reads only its lane and drops the select", [&] {
|
|
return sample(IrShaderStage::Pixel, RdnaImageSampleFlagLevelZero, IrOpcode::ImageGatherRaw) && sample(IrShaderStage::Pixel, RdnaImageSampleFlagLod, IrOpcode::ImageGatherRaw) &&
|
|
sample(IrShaderStage::Mesh, 0u, IrOpcode::ImageGatherRaw);
|
|
});
|
|
|
|
passed &= run("an image size query reads only its lane's level and drops the select", [] {
|
|
Builder b;
|
|
auto& exec = b.mask(16u);
|
|
auto& old = b.lane();
|
|
auto& written = b.select(exec, b.add(old, 1u), old);
|
|
auto& address = b.emit(IrOpcode::MakeImageAddress, IrType::ImageAddress, {&written});
|
|
auto& size = b.emit(IrOpcode::ImageQueryDimensions, IrType::U32x4, {&b.emit(IrOpcode::GetImageResource, IrType::ImageResource, {}), &address});
|
|
b.keep(b.select(exec, b.emit(IrOpcode::CompositeExtractU32x4, IrType::U32, {&size, &b.constant(0u)}), old));
|
|
return b.eliminate() == 1u && removed(written);
|
|
});
|
|
|
|
const auto guarded = [](IrOpcode opcode, bool sameExec, bool asActive) {
|
|
Builder b;
|
|
auto& wide = b.mask(48u);
|
|
auto& exec = b.logicalAnd(wide, b.mask(16u));
|
|
auto& old = b.lane();
|
|
auto& written = b.select(exec, b.add(old, 1u), old);
|
|
IrValue& active = asActive ? b.emit(IrOpcode::INotEqual32, IrType::Bool, {&written, &b.constant(0u)}) : sameExec ? exec : wide;
|
|
switch (opcode) {
|
|
case IrOpcode::WriteSharedU32:
|
|
b.emit(opcode, IrType::Void, {&old, &written, &active});
|
|
break;
|
|
case IrOpcode::LoadBufferU32:
|
|
b.keep(b.select(exec, b.emit(opcode, IrType::U32, {&b.emit(IrOpcode::GetBufferResource, IrType::BufferResource, {}), &written, &b.constant(0u), &b.constant(0u), &active}), old));
|
|
break;
|
|
case IrOpcode::ImageRead: {
|
|
auto& address = b.emit(IrOpcode::MakeImageAddress, IrType::ImageAddress, {&written, &old});
|
|
auto& image = b.emit(IrOpcode::GetImageResource, IrType::ImageResource, {});
|
|
auto& read = b.emit(opcode, IrType::U32x4, {&image, &address, &active});
|
|
b.keep(b.select(exec, b.emit(IrOpcode::CompositeExtractU32x4, IrType::U32, {&read, &b.constant(0u)}), old));
|
|
break;
|
|
}
|
|
case IrOpcode::ImageWrite: {
|
|
auto& address = b.emit(IrOpcode::MakeImageAddress, IrType::ImageAddress, {&old, &old});
|
|
auto& texel = b.emit(IrOpcode::CompositeConstructU32x4, IrType::U32x4, {&written, &old, &old, &old});
|
|
b.emit(opcode, IrType::Void, {&b.emit(IrOpcode::GetImageResource, IrType::ImageResource, {}), &address, &texel, &active});
|
|
break;
|
|
}
|
|
default:
|
|
b.emit(opcode, IrType::Void, {&b.emit(IrOpcode::CompositeConstructU32x4, IrType::U32x4, {&written, &old, &old, &old}), &active});
|
|
break;
|
|
}
|
|
b.eliminate();
|
|
return removed(written);
|
|
};
|
|
|
|
passed &= run("a store, load or export whose exec implies the write's exec drops the select", [&] {
|
|
return guarded(IrOpcode::WriteSharedU32, true, false) && guarded(IrOpcode::LoadBufferU32, true, false) && guarded(IrOpcode::ImageRead, true, false) && guarded(IrOpcode::SetAttribute, true, false);
|
|
});
|
|
|
|
passed &= run("a store, load or export under a wider exec keeps the select", [&] {
|
|
return !guarded(IrOpcode::WriteSharedU32, false, false) && !guarded(IrOpcode::LoadBufferU32, false, false) && !guarded(IrOpcode::ImageRead, false, false) && !guarded(IrOpcode::SetAttribute, false, false);
|
|
});
|
|
|
|
passed &= run("an image write under the write's exec drops the select, under a wider exec or as its exec keeps it", [&] {
|
|
return guarded(IrOpcode::ImageWrite, true, false) && !guarded(IrOpcode::ImageWrite, false, false) && !guarded(IrOpcode::ImageWrite, true, true);
|
|
});
|
|
|
|
passed &= run("a value used as a store's exec keeps the select", [&] {
|
|
return !guarded(IrOpcode::WriteSharedU32, true, true);
|
|
});
|
|
|
|
passed &= run("an atomic keeps the select", [&] {
|
|
return !guarded(IrOpcode::SharedAtomicIAdd32, true, false);
|
|
});
|
|
|
|
passed &= run("a write under whole quad mode read only by the lanes it was widened from drops the select in every stage", [&] {
|
|
const auto widened = [&](IrShaderStage stage) {
|
|
Builder b;
|
|
b.program.Resources().stage = stage;
|
|
auto& live = b.mask(16u);
|
|
auto& quads = wholeQuads(b, live);
|
|
auto& old = b.lane();
|
|
auto& written = b.select(quads, b.add(old, 1u), old);
|
|
auto& next = b.select(b.logicalAnd(live, b.mask(8u)), b.add(written, 2u), old);
|
|
b.keep(next);
|
|
return b.eliminate() != 0u && removed(written) && !removed(next);
|
|
};
|
|
return widened(IrShaderStage::Pixel) && widened(IrShaderStage::Compute);
|
|
});
|
|
|
|
passed &= run("a write under whole quad mode read by a lane outside the widened mask keeps the select in compute", [&] {
|
|
Builder b;
|
|
b.program.Resources().stage = IrShaderStage::Compute;
|
|
auto& live = b.mask(16u);
|
|
auto& quads = wholeQuads(b, live);
|
|
auto& old = b.lane();
|
|
auto& written = b.select(quads, b.add(old, 1u), old);
|
|
auto& next = b.select(b.mask(32u), b.add(written, 2u), old);
|
|
b.keep(next);
|
|
return b.eliminate() == 0u && !removed(written);
|
|
});
|
|
|
|
passed &= run("a write under whole quad mode read only by the lanes it was widened from drops the select", [&] {
|
|
Builder b;
|
|
auto& live = b.mask(16u);
|
|
auto& quads = wholeQuads(b, live);
|
|
auto& old = b.lane();
|
|
auto& written = b.select(quads, b.add(old, 1u), old);
|
|
auto& next = b.select(b.logicalAnd(live, b.mask(8u)), b.add(written, 2u), old);
|
|
b.keep(next);
|
|
return b.eliminate() != 0u && removed(written) && !removed(next);
|
|
});
|
|
|
|
passed &= run("a write under whole quad mode read by other lanes keeps the select", [&] {
|
|
Builder b;
|
|
auto& live = b.mask(16u);
|
|
auto& quads = wholeQuads(b, live);
|
|
auto& old = b.lane();
|
|
auto& written = b.select(quads, b.add(old, 1u), old);
|
|
auto& next = b.select(b.mask(32u), b.add(written, 2u), old);
|
|
b.keep(next);
|
|
return b.eliminate() == 0u && !removed(written);
|
|
});
|
|
|
|
const auto shaded = [](std::uint32_t units) {
|
|
Builder b;
|
|
b.program.Resources().stage = IrShaderStage::Pixel;
|
|
MemoryInfo memory;
|
|
memory.kind = ResourceKind::Image;
|
|
memory.imageSampleFlags = RdnaImageSampleFlagLevelZero;
|
|
b.program.Resources().memoryInfo.push_back(memory);
|
|
MemoryFlags flags{0u, 0u};
|
|
std::uint64_t bits = 0;
|
|
std::memcpy(&bits, &flags, sizeof(flags));
|
|
auto& exec = b.mask(48u);
|
|
auto& image = b.emit(IrOpcode::GetImageResource, IrType::ImageResource, {});
|
|
auto& sampler = b.emit(IrOpcode::GetSamplerResource, IrType::SamplerResource, {});
|
|
IrValue* color = &b.lane();
|
|
IrValue* address = nullptr;
|
|
std::uint32_t kept = 0;
|
|
std::vector<IrValue*> compared;
|
|
for (std::uint32_t unit = 0; unit < units; ++unit) {
|
|
auto& sum = b.add(*color, unit);
|
|
auto& low = b.emit(IrOpcode::ULessThan32, IrType::Bool, {&sum, &b.constant(100u + unit)});
|
|
compared.push_back(&b.select(low, sum, b.add(*color, 1u)));
|
|
address = &b.emit(IrOpcode::MakeImageAddress, IrType::ImageAddress, {compared.back(), color});
|
|
auto& gathered = b.emit(IrOpcode::ImageGatherRaw, IrType::U32x4, {&image, &sampler, address}, bits);
|
|
color = &b.select(exec, b.emit(IrOpcode::CompositeExtractU32x4, IrType::U32, {&gathered, &b.constant(0u)}), *color);
|
|
}
|
|
b.emit(IrOpcode::ImageWrite, IrType::Void, {&image, address, &b.emit(IrOpcode::CompositeConstructU32x4, IrType::U32x4, {color, color, color, color}), &exec});
|
|
const auto stats = MaskedSelectEliminator{}.Eliminate(b.program);
|
|
for (const IrValue* select : compared) kept += removed(*select) ? 0u : 1u;
|
|
return std::pair{stats, kept};
|
|
};
|
|
|
|
passed &= run("selects that each reach the rest of the program are settled in steps that grow with the program, not with its square", [&] {
|
|
const auto [small, smallKept] = shaded(500u);
|
|
const auto [large, largeKept] = shaded(4000u);
|
|
return small.removedSelects == 500u && smallKept == 500u && large.removedSelects == 4000u && largeKept == 4000u && small.steps != 0u && large.steps <= 9u * small.steps;
|
|
});
|
|
|
|
return passed ? 0 : 1;
|
|
}
|