mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-28 16:23:05 +00:00
shader: bound selected-lane record indices by guarded shared append lists
This commit is contained in:
1 parent
ff5d16eefc
commit
ffb6ad6cab
2 files changed
+80
-24
No files matched your search
@@ -1885,6 +1885,10 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool Dominates(const Block* initial, const Block* use, Value witness = {}) const {
|
bool Dominates(const Block* initial, const Block* use, Value witness = {}) const {
|
||||||
|
// The proven local-zero invocation fails every all-lane bypass condition that excludes it,
|
||||||
|
// such as an all-inactive or an all-nonzero bypass.
|
||||||
|
const auto* negated = SimplifyGuard(witness).TryInstruction();
|
||||||
|
const bool nonzero = negated != nullptr && negated->GetOpcode() == ValueOpcode::LogicalNot;
|
||||||
std::vector<const Block*> pending {use};
|
std::vector<const Block*> pending {use};
|
||||||
for (size_t index = 0; index < pending.size(); ++index) {
|
for (size_t index = 0; index < pending.size(); ++index) {
|
||||||
const auto* current = pending[index];
|
const auto* current = pending[index];
|
||||||
@@ -1893,9 +1897,9 @@ private:
|
|||||||
for (const auto* previous: current->ImmPredecessors()) {
|
for (const auto* previous: current->ImmPredecessors()) {
|
||||||
if (!witness.IsEmpty()) {
|
if (!witness.IsEmpty()) {
|
||||||
const auto edge = ConditionalEdge(previous, current);
|
const auto edge = ConditionalEdge(previous, current);
|
||||||
// The proven local-zero invocation makes an all-inactive bypass impossible.
|
if (edge && edge->lanes == LaneQuantifier::All &&
|
||||||
if (edge && edge->lanes == LaneQuantifier::All && !edge->positive &&
|
(edge->positive ? nonzero && Implies(edge->condition, negated->Arg(0))
|
||||||
Implies(witness, edge->condition)) continue;
|
: Implies(witness, edge->condition))) continue;
|
||||||
}
|
}
|
||||||
if (std::ranges::find(pending, previous) == pending.end()) pending.push_back(previous);
|
if (std::ranges::find(pending, previous) == pending.end()) pending.push_back(previous);
|
||||||
}
|
}
|
||||||
@@ -2004,7 +2008,10 @@ private:
|
|||||||
|
|
||||||
bool LocalZeroWitness(Value guard) const {
|
bool LocalZeroWitness(Value guard) const {
|
||||||
const auto* equal = SimplifyGuard(guard).TryInstruction();
|
const auto* equal = SimplifyGuard(guard).TryInstruction();
|
||||||
if (equal == nullptr || equal->GetOpcode() != ValueOpcode::IEqual32) return false;
|
const bool negated = equal != nullptr && equal->GetOpcode() == ValueOpcode::LogicalNot;
|
||||||
|
if (negated) equal = SimplifyGuard(equal->Arg(0)).TryInstruction();
|
||||||
|
const auto expected = negated ? ValueOpcode::INotEqual32 : ValueOpcode::IEqual32;
|
||||||
|
if (equal == nullptr || equal->GetOpcode() != expected) return false;
|
||||||
for (uint32_t arg = 0; arg < 2u; ++arg) {
|
for (uint32_t arg = 0; arg < 2u; ++arg) {
|
||||||
if (equal->Arg(arg).Resolve() != Value(0u)) continue;
|
if (equal->Arg(arg).Resolve() != Value(0u)) continue;
|
||||||
Value induction;
|
Value induction;
|
||||||
@@ -2116,7 +2123,7 @@ private:
|
|||||||
const auto* read = ActiveValue(value, active).TryInstruction();
|
const auto* read = ActiveValue(value, active).TryInstruction();
|
||||||
if (read == nullptr || read->GetOpcode() != ValueOpcode::LoadSharedU32 ||
|
if (read == nullptr || read->GetOpcode() != ValueOpcode::LoadSharedU32 ||
|
||||||
m_program.stage != ShaderType::Compute ||
|
m_program.stage != ShaderType::Compute ||
|
||||||
read->Arg(1).Resolve() != Value(true)) return {};
|
!Implies(active, read->Arg(1))) return {};
|
||||||
uint64_t group_size = 1u;
|
uint64_t group_size = 1u;
|
||||||
for (const auto dimension: m_input.compute->threads_num) {
|
for (const auto dimension: m_input.compute->threads_num) {
|
||||||
if (dimension == 0u || dimension > m_input.compute->wave_size) return {};
|
if (dimension == 0u || dimension > m_input.compute->wave_size) return {};
|
||||||
@@ -2147,10 +2154,16 @@ private:
|
|||||||
const auto* limit = BoundedLoop(read_offset.index, read->Parent(),
|
const auto* limit = BoundedLoop(read_offset.index, read->Parent(),
|
||||||
[](const Inst& compare, uint32_t) { return compare.GetOpcode() == ValueOpcode::ULessThan32; });
|
[](const Inst& compare, uint32_t) { return compare.GetOpcode() == ValueOpcode::ULessThan32; });
|
||||||
if (limit == nullptr) return {};
|
if (limit == nullptr) return {};
|
||||||
|
auto counter_active = active;
|
||||||
const auto* counter_read = ActiveValue(limit->Arg(1), active).TryInstruction();
|
const auto* counter_read = ActiveValue(limit->Arg(1), active).TryInstruction();
|
||||||
|
// Every lane reads the same counter word, so a selected lane carries it.
|
||||||
|
if (counter_read != nullptr && counter_read->GetOpcode() == ValueOpcode::ReadFirstLane) {
|
||||||
|
counter_active = counter_read->Arg(1);
|
||||||
|
counter_read = ActiveValue(counter_read->Arg(0), counter_active).TryInstruction();
|
||||||
|
}
|
||||||
uint32_t consumed_counter = 0;
|
uint32_t consumed_counter = 0;
|
||||||
if (counter_read == nullptr || counter_read->GetOpcode() != ValueOpcode::LoadSharedU32 ||
|
if (counter_read == nullptr || counter_read->GetOpcode() != ValueOpcode::LoadSharedU32 ||
|
||||||
counter_read->Arg(1).Resolve() != Value(true) ||
|
!Implies(counter_active, counter_read->Arg(1)) ||
|
||||||
!constant_address(*counter_read, consumed_counter)) return {};
|
!constant_address(*counter_read, consumed_counter)) return {};
|
||||||
|
|
||||||
struct Initializer { const Inst* inst; uint32_t address; uint32_t words; };
|
struct Initializer { const Inst* inst; uint32_t address; uint32_t words; };
|
||||||
@@ -2373,6 +2386,12 @@ private:
|
|||||||
const auto maximum = SelectorMaximum(index);
|
const auto maximum = SelectorMaximum(index);
|
||||||
if (maximum < MaxIndirectDescriptorProbes) count = Value(maximum + 1u);
|
if (maximum < MaxIndirectDescriptorProbes) count = Value(maximum + 1u);
|
||||||
}
|
}
|
||||||
|
// A record selected from a shared append list is below the list's bound.
|
||||||
|
const auto* lane = index.Resolve().TryInstruction();
|
||||||
|
if (count.IsEmpty() && lane != nullptr &&
|
||||||
|
lane->GetOpcode() == ValueOpcode::ReadFirstLane &&
|
||||||
|
HasActiveLane(lane->Arg(1), lane->Parent()))
|
||||||
|
count = SharedLoadBound(lane->Arg(0), lane->Arg(1));
|
||||||
}
|
}
|
||||||
// Raw pointers have no V# bounds for enumerating an unbounded selector domain.
|
// Raw pointers have no V# bounds for enumerating an unbounded selector domain.
|
||||||
if (memory->kind == ResourceKind::ScalarAddress && count.IsEmpty() &&
|
if (memory->kind == ResourceKind::ScalarAddress && count.IsEmpty() &&
|
||||||
|
|||||||
@@ -4812,7 +4812,8 @@ void TestSharedAppendSelectorBounds() {
|
|||||||
Valid, PayloadOob, MissingInit, InactiveInit, UnknownInit, DuplicateAtomic,
|
Valid, PayloadOob, MissingInit, InactiveInit, UnknownInit, DuplicateAtomic,
|
||||||
WrongAtomicGuard, UnguardedCandidate, WrongStep, WrongLocalIndex, Multiwave,
|
WrongAtomicGuard, UnguardedCandidate, WrongStep, WrongLocalIndex, Multiwave,
|
||||||
OuterReentry, InnerAppendCycle, EarlyCounterRead, WrongConsumerBound, ExtraWriter, CounterAlias,
|
OuterReentry, InnerAppendCycle, EarlyCounterRead, WrongConsumerBound, ExtraWriter, CounterAlias,
|
||||||
PayloadWrap, CounterOob
|
PayloadWrap, CounterOob, Lane, LaneUnimpliedRead, LaneUnimpliedCounter, LaneCounterMask,
|
||||||
|
LaneOtherBypass, LaneInactiveSelect
|
||||||
};
|
};
|
||||||
const auto make_plan = [](Variant variant, uint32_t first_base = 1032u, uint32_t consumed_base = 8u) {
|
const auto make_plan = [](Variant variant, uint32_t first_base = 1032u, uint32_t consumed_base = 8u) {
|
||||||
Fixture fixture;
|
Fixture fixture;
|
||||||
@@ -4845,6 +4846,7 @@ void TestSharedAppendSelectorBounds() {
|
|||||||
auto *entry = fixture.block;
|
auto *entry = fixture.block;
|
||||||
auto *initialize = fixture.AddBlock();
|
auto *initialize = fixture.AddBlock();
|
||||||
auto *initialized = fixture.AddBlock();
|
auto *initialized = fixture.AddBlock();
|
||||||
|
const bool lane = variant >= Variant::Lane;
|
||||||
const auto material = fixture.Buffer({fixture.UserData(0), fixture.UserData(1),
|
const auto material = fixture.Buffer({fixture.UserData(0), fixture.UserData(1),
|
||||||
fixture.UserData(2), fixture.UserData(3)});
|
fixture.UserData(2), fixture.UserData(3)});
|
||||||
const auto table = fixture.Address(fixture.UserData(4), fixture.UserData(5));
|
const auto table = fixture.Address(fixture.UserData(4), fixture.UserData(5));
|
||||||
@@ -4863,12 +4865,18 @@ void TestSharedAppendSelectorBounds() {
|
|||||||
{Value(static_cast<uint32_t>(StageInputKind::LocalInvocationId)), Value(1u)});
|
{Value(static_cast<uint32_t>(StageInputKind::LocalInvocationId)), Value(1u)});
|
||||||
const auto local = fixture.Emit(ValueOpcode::IAdd32,
|
const auto local = fixture.Emit(ValueOpcode::IAdd32,
|
||||||
{x, fixture.Emit(ValueOpcode::ShiftLeftLogical32, {y, Value(3u)})});
|
{x, fixture.Emit(ValueOpcode::ShiftLeftLogical32, {y, Value(3u)})});
|
||||||
auto zero_lane = fixture.Emit(ValueOpcode::IEqual32, {local, Value(0u)});
|
// The negated form skips the initializer only when every lane is nonzero.
|
||||||
|
const auto local_is = [&](uint32_t value) {
|
||||||
|
return fixture.Emit(ValueOpcode::LogicalNot,
|
||||||
|
{fixture.Emit(ValueOpcode::INotEqual32, {local, Value(value)})});
|
||||||
|
};
|
||||||
|
auto zero_lane = lane ? local_is(0u) : fixture.Emit(ValueOpcode::IEqual32, {local, Value(0u)});
|
||||||
if (variant == Variant::InactiveInit) zero_lane = Value(false);
|
if (variant == Variant::InactiveInit) zero_lane = Value(false);
|
||||||
if (variant == Variant::UnknownInit)
|
if (variant == Variant::UnknownInit)
|
||||||
zero_lane = fixture.Emit(ValueOpcode::LogicalAnd,
|
zero_lane = fixture.Emit(ValueOpcode::LogicalAnd,
|
||||||
{zero_lane, fixture.Emit(ValueOpcode::IEqual32, {fixture.UserData(8), Value(0u)})});
|
{zero_lane, fixture.Emit(ValueOpcode::IEqual32, {fixture.UserData(8), Value(0u)})});
|
||||||
choose(entry, initialize, initialized, zero_lane, CFG::BranchCondition::ExecNonZero);
|
choose(entry, initialize, initialized, variant == Variant::LaneOtherBypass ? local_is(1u) : zero_lane,
|
||||||
|
CFG::BranchCondition::ExecNonZero);
|
||||||
if (variant != Variant::MissingInit)
|
if (variant != Variant::MissingInit)
|
||||||
fixture.Emit(ValueOpcode::WriteSharedU32x2,
|
fixture.Emit(ValueOpcode::WriteSharedU32x2,
|
||||||
{Value(0u), Value(0u), Value(0u), zero_lane}, shared(0u), initialize);
|
{Value(0u), Value(0u), Value(0u), zero_lane}, shared(0u), initialize);
|
||||||
@@ -4937,13 +4945,25 @@ void TestSharedAppendSelectorBounds() {
|
|||||||
if (variant == Variant::ExtraWriter)
|
if (variant == Variant::ExtraWriter)
|
||||||
fixture.Emit(ValueOpcode::WriteSharedU32,
|
fixture.Emit(ValueOpcode::WriteSharedU32,
|
||||||
{Value(0u), Value(UINT32_MAX), Value(true)}, shared(12u), previous);
|
{Value(0u), Value(UINT32_MAX), Value(true)}, shared(12u), previous);
|
||||||
if (final_counter.IsEmpty())
|
// Lane variants consume under a lane mask, which their counter and record reads use.
|
||||||
final_counter = fixture.Emit(ValueOpcode::LoadSharedU32,
|
Value lanes(true), other;
|
||||||
{Value(0u), Value(true)}, shared(0u), previous);
|
if (lane) {
|
||||||
|
lanes = fixture.Emit(ValueOpcode::ULessThan32, {y, fixture.UserData(8)}, 0, previous);
|
||||||
|
other = fixture.Emit(ValueOpcode::ULessThan32, {x, fixture.UserData(8)}, 0, previous);
|
||||||
|
}
|
||||||
|
auto *guarded = lane ? fixture.AddBlock() : previous;
|
||||||
auto *header = fixture.AddBlock();
|
auto *header = fixture.AddBlock();
|
||||||
auto *body = fixture.AddBlock();
|
auto *body = fixture.AddBlock();
|
||||||
auto *latch = fixture.AddBlock();
|
auto *latch = fixture.AddBlock();
|
||||||
auto *exit = fixture.AddBlock();
|
auto *exit = fixture.AddBlock();
|
||||||
|
if (lane) choose(previous, guarded, exit, lanes, CFG::BranchCondition::ExecNonZero);
|
||||||
|
previous = guarded;
|
||||||
|
if (final_counter.IsEmpty())
|
||||||
|
final_counter = fixture.Emit(ValueOpcode::LoadSharedU32,
|
||||||
|
{Value(0u), variant == Variant::LaneUnimpliedCounter ? other : lanes}, shared(0u), previous);
|
||||||
|
if (lane)
|
||||||
|
final_counter = fixture.Emit(ValueOpcode::ReadFirstLane,
|
||||||
|
{final_counter, variant == Variant::LaneCounterMask ? other : lanes}, 0, previous);
|
||||||
branch(previous, header);
|
branch(previous, header);
|
||||||
auto &index = header->AppendNewInst(ValueOpcode::Phi);
|
auto &index = header->AppendNewInst(ValueOpcode::Phi);
|
||||||
index.SetFlags(Type::U32);
|
index.SetFlags(Type::U32);
|
||||||
@@ -4952,12 +4972,22 @@ void TestSharedAppendSelectorBounds() {
|
|||||||
choose(header, body, exit, in_list, CFG::BranchCondition::VccNonZero);
|
choose(header, body, exit, in_list, CFG::BranchCondition::VccNonZero);
|
||||||
fixture.block = body;
|
fixture.block = body;
|
||||||
const auto selected = fixture.Emit(ValueOpcode::LoadSharedU32,
|
const auto selected = fixture.Emit(ValueOpcode::LoadSharedU32,
|
||||||
{fixture.Emit(ValueOpcode::ShiftLeftLogical32, {Value(&index), Value(2u)}), Value(true)},
|
{fixture.Emit(ValueOpcode::ShiftLeftLogical32, {Value(&index), Value(2u)}),
|
||||||
shared(consumed_base));
|
variant == Variant::LaneUnimpliedRead ? other : lanes}, shared(consumed_base));
|
||||||
const auto offset32 = fixture.Emit(ValueOpcode::ShiftLeftLogical32, {selected, Value(5u)});
|
Value offset;
|
||||||
const auto offset = fixture.Emit(ValueOpcode::ReadFirstLane,
|
if (lane) {
|
||||||
{fixture.Emit(ValueOpcode::IAdd32,
|
// The record is selected from one lane and scaled on the scalar side.
|
||||||
{fixture.Emit(ValueOpcode::ShiftLeftLogical32, {offset32, Value(2u)}), offset32}), Value(true)});
|
const auto mask = variant == Variant::LaneInactiveSelect
|
||||||
|
? fixture.Emit(ValueOpcode::LogicalAnd, {lanes, other}) : lanes;
|
||||||
|
offset = fixture.Emit(ValueOpcode::IMul32,
|
||||||
|
{fixture.Emit(ValueOpcode::ReadFirstLane, {selected, mask}), Value(160u)});
|
||||||
|
} else {
|
||||||
|
const auto offset32 = fixture.Emit(ValueOpcode::ShiftLeftLogical32, {selected, Value(5u)});
|
||||||
|
offset = fixture.Emit(ValueOpcode::ReadFirstLane,
|
||||||
|
{fixture.Emit(ValueOpcode::IAdd32,
|
||||||
|
{fixture.Emit(ValueOpcode::ShiftLeftLogical32, {offset32, Value(2u)}), offset32}),
|
||||||
|
Value(true)});
|
||||||
|
}
|
||||||
MemoryInfo scalar;
|
MemoryInfo scalar;
|
||||||
scalar.kind = ResourceKind::ScalarBuffer;
|
scalar.kind = ResourceKind::ScalarBuffer;
|
||||||
scalar.offset = 144u;
|
scalar.offset = 144u;
|
||||||
@@ -4986,7 +5016,8 @@ void TestSharedAppendSelectorBounds() {
|
|||||||
fixture.PlanAndTrack({.compute = &compute});
|
fixture.PlanAndTrack({.compute = &compute});
|
||||||
const auto &indirect = fixture.program.descriptor_sources[
|
const auto &indirect = fixture.program.descriptor_sources[
|
||||||
fixture.program.info.images[0].source].indirect_descriptor;
|
fixture.program.info.images[0].source].indirect_descriptor;
|
||||||
const bool proven = variant == Variant::Valid || variant == Variant::PayloadOob;
|
const bool proven = variant == Variant::Valid || variant == Variant::PayloadOob ||
|
||||||
|
variant == Variant::Lane;
|
||||||
Check(indirect && indirect->selector && indirect->selector->offset == 144u &&
|
Check(indirect && indirect->selector && indirect->selector->offset == 144u &&
|
||||||
indirect->selector->stride == (proven ? 160u : 32u) &&
|
indirect->selector->stride == (proven ? 160u : 32u) &&
|
||||||
indirect->key_count.IsEmpty() != proven &&
|
indirect->key_count.IsEmpty() != proven &&
|
||||||
@@ -5003,6 +5034,11 @@ void TestSharedAppendSelectorBounds() {
|
|||||||
Variant::InnerAppendCycle, Variant::EarlyCounterRead, Variant::WrongConsumerBound, Variant::ExtraWriter,
|
Variant::InnerAppendCycle, Variant::EarlyCounterRead, Variant::WrongConsumerBound, Variant::ExtraWriter,
|
||||||
Variant::CounterAlias, Variant::PayloadWrap, Variant::CounterOob})
|
Variant::CounterAlias, Variant::PayloadWrap, Variant::CounterOob})
|
||||||
make_plan(variant);
|
make_plan(variant);
|
||||||
|
auto lane_plan = make_plan(Variant::Lane);
|
||||||
|
for (const auto variant : {Variant::LaneUnimpliedRead, Variant::LaneUnimpliedCounter,
|
||||||
|
Variant::LaneCounterMask, Variant::LaneOtherBypass, Variant::LaneInactiveSelect})
|
||||||
|
CheckFatal([&] { make_plan(variant); }, "not a valid runtime value",
|
||||||
|
"shared append provenance accepted an unimplied lane mask or initializer bypass");
|
||||||
|
|
||||||
LinearTestMemory memory;
|
LinearTestMemory memory;
|
||||||
for (uint32_t index = 0; index < 8u; ++index) memory.words[(144u + 160u * index) / 4u] = index + 1u;
|
for (uint32_t index = 0; index < 8u; ++index) memory.words[(144u + 160u * index) / 4u] = index + 1u;
|
||||||
@@ -5015,7 +5051,7 @@ void TestSharedAppendSelectorBounds() {
|
|||||||
memory.words[word + 3u] = Libs::Graphics::DstSel(4, 5, 6, 7) |
|
memory.words[word + 3u] = Libs::Graphics::DstSel(4, 5, 6, 7) |
|
||||||
(static_cast<uint32_t>(Libs::Graphics::Prospero::ImageType::kColor2D) << 28u);
|
(static_cast<uint32_t>(Libs::Graphics::Prospero::ImageType::kColor2D) << 28u);
|
||||||
}
|
}
|
||||||
std::array<uint32_t, 9> user_data {0x1000u, 160u << 16u, 8u, 0u, 0x2000u, 0u, 0x1800u, 0u, 0u};
|
std::array<uint32_t, 9> user_data {0x1000u, 160u << 16u, 8u, 0u, 0x2000u, 0u, 0x1800u, 0u, 8u};
|
||||||
SrtRuntime runtime {.user_data = user_data, .userdata = &memory,
|
SrtRuntime runtime {.user_data = user_data, .userdata = &memory,
|
||||||
.read_specialization_memory = ReadLinearTestMemory};
|
.read_specialization_memory = ReadLinearTestMemory};
|
||||||
FakeGuestMemory fake(ReadRuntimeMemory, &runtime);
|
FakeGuestMemory fake(ReadRuntimeMemory, &runtime);
|
||||||
@@ -5030,10 +5066,11 @@ void TestSharedAppendSelectorBounds() {
|
|||||||
memory.words[0x800u / 4u] = counts[0];
|
memory.words[0x800u / 4u] = counts[0];
|
||||||
memory.words[0x804u / 4u] = counts[1];
|
memory.words[0x804u / 4u] = counts[1];
|
||||||
const auto expected = counts[1];
|
const auto expected = counts[1];
|
||||||
Check(MaterializeResources(plan, runtime, snapshot, specialization) &&
|
for (const auto *tested : {&plan, &lane_plan})
|
||||||
snapshot.images.size() == std::max(expected, 1u) &&
|
Check(MaterializeResources(*tested, runtime, snapshot, specialization) &&
|
||||||
(expected == 0u || snapshot.images.back().dwords[0] == 0x100u + expected),
|
snapshot.images.size() == std::max(expected, 1u) &&
|
||||||
"disjoint shared append lists or refreshed counts selected unrelated material keys");
|
(expected == 0u || snapshot.images.back().dwords[0] == 0x100u + expected),
|
||||||
|
"disjoint shared append lists or refreshed counts selected unrelated material keys");
|
||||||
}
|
}
|
||||||
memory.words[0x800u / 4u] = 24u;
|
memory.words[0x800u / 4u] = 24u;
|
||||||
memory.words[0x804u / 4u] = 2u;
|
memory.words[0x804u / 4u] = 2u;
|
||||||
|
|||||||
Reference in new issue
Block a user