Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 132 additions & 0 deletions core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -997,6 +997,107 @@ void verifySignedMinimum() {
}
}

void verifyWaterfallRuntimeValues() {
using namespace ShaderRecompiler;
IrResourcePlan plan;
plan.userDataCount = 1u;
std::vector<std::unique_ptr<IrValue>> values;
const auto make = [&](IrOpcode opcode, IrType type, std::initializer_list<IrValue*> arguments = {}) -> IrValue& {
values.push_back(std::make_unique<IrValue>(opcode, type, static_cast<std::uint32_t>(values.size())));
for (auto* argument : arguments) values.back()->AddArgument(argument);
return *values.back();
};
const auto constant = [&](std::uint32_t bits) -> IrValue& {
auto& value = make(IrOpcode::Void, IrType::U32);
value.SetImmediateU32(bits);
return value;
};
auto& scalar = make(IrOpcode::Void, IrType::ScalarReg);
scalar.SetRegister({RegisterBank::Scalar, 0});
auto& vector = make(IrOpcode::Void, IrType::VectorReg);
vector.SetRegister({RegisterBank::Vector, 0});
auto& input = make(IrOpcode::GetUserData, IrType::U32, {&scalar});
auto& lane = make(IrOpcode::GetRegister, IrType::U32, {&vector});
auto& varying = make(IrOpcode::IEqual32, IrType::Bool, {&input, &lane});
auto& uniform = make(IrOpcode::IEqual32, IrType::Bool, {&input, &constant(4u)});
auto& other = make(IrOpcode::IEqual32, IrType::Bool, {&lane, &constant(5u)});
auto& selected = make(IrOpcode::SelectU32, IrType::U32, {&varying, &input, &lane});
const std::array<std::uint32_t, 1> data{4u};
SrtRuntime runtime{data};
for (const bool reverse : {false, true}) {
auto& mask = reverse ? make(IrOpcode::LogicalAnd, IrType::Bool, {&uniform, &varying}) : make(IrOpcode::LogicalAnd, IrType::Bool, {&varying, &uniform});
auto& first = make(IrOpcode::ReadFirstLane, IrType::U32, {&selected, &mask});
require(SrtWalker().ValidateRuntimeValue(plan, &first), "waterfall: an active-mask conjunct was rejected");
Detail::Evaluator evaluator(plan, runtime);
std::uint32_t result = 0;
require(evaluator.Evaluate(Detail::IrNode(&first), result) && result == data[0], "waterfall: the active select arm was not evaluated");
auto& unrelated = make(IrOpcode::SelectU32, IrType::U32, {&other, &input, &lane});
auto& invalid = make(IrOpcode::ReadFirstLane, IrType::U32, {&unrelated, &mask});
require(!SrtWalker().ValidateRuntimeValue(plan, &invalid), "waterfall: an unrelated condition was accepted");
}
auto& either = make(IrOpcode::LogicalOr, IrType::Bool, {&varying, &uniform});
auto& invalid = make(IrOpcode::ReadFirstLane, IrType::U32, {&selected, &either});
require(!SrtWalker().ValidateRuntimeValue(plan, &invalid), "waterfall: a disjunction was treated as an implication");
auto& nestedCondition = make(IrOpcode::LogicalAnd, IrType::Bool, {&varying, &uniform});
auto& next = make(IrOpcode::IAdd32, IrType::U32, {&input, &constant(1u)});
auto& nested = make(IrOpcode::SelectU32, IrType::U32, {&nestedCondition, &input, &next});
auto& nestedFirst = make(IrOpcode::ReadFirstLane, IrType::U32, {&nested, &varying});
require(SrtWalker().ValidateRuntimeValue(plan, &nestedFirst), "waterfall: the active condition was not propagated into a nested select");
require(Detail::Evaluator::MayEvaluate(plan, &nestedFirst), "waterfall: a nested masked condition was not evaluable");
for (const auto word : {4u, 7u}) {
const std::array<std::uint32_t, 1> inputData{word};
SrtRuntime nestedRuntime{inputData};
Detail::Evaluator evaluator(plan, nestedRuntime);
std::uint32_t result = 0;
require(evaluator.Evaluate(Detail::IrNode(&nestedFirst), result) && result == (word == 4u ? word : word + 1u), "waterfall: a nested select ignored its uniform condition");
}
}

void verifyUniformVectorComponents() {
using namespace ShaderRecompiler;
IrResourcePlan plan;
plan.userDataCount = 1u;
std::vector<std::unique_ptr<IrValue>> values;
const auto make = [&](IrOpcode opcode, IrType type, std::initializer_list<IrValue*> arguments = {}) -> IrValue& {
values.push_back(std::make_unique<IrValue>(opcode, type, static_cast<std::uint32_t>(values.size())));
for (auto* argument : arguments) values.back()->AddArgument(argument);
return *values.back();
};
const auto constant = [&](std::uint32_t bits) -> IrValue& {
auto& value = make(IrOpcode::Void, IrType::U32);
value.SetImmediateU32(bits);
return value;
};
auto& scalar = make(IrOpcode::Void, IrType::ScalarReg);
scalar.SetRegister({RegisterBank::Scalar, 0});
auto& vector = make(IrOpcode::Void, IrType::VectorReg);
vector.SetRegister({RegisterBank::Vector, 0});
auto& input = make(IrOpcode::GetUserData, IrType::U32, {&scalar});
auto& lane = make(IrOpcode::GetRegister, IrType::U32, {&vector});
auto& tuple = make(IrOpcode::CompositeConstructU32x4, IrType::U32x4, {&constant(0u), &input, &lane, &constant(0xffffffffu)});
const std::array<std::uint32_t, 1> data{4u};
SrtRuntime runtime{data};
for (std::uint32_t component = 0; component <= 4u; ++component) {
auto& value = make(IrOpcode::CompositeExtractU32x4, IrType::U32, {&tuple, &constant(component)});
const bool expected = component != 2u && component < 4u;
require(SrtWalker().ValidateRuntimeValue(plan, &value) == expected, "uniform vector: component validation was wrong");
require(Detail::Evaluator::MayEvaluate(plan, &value) == expected, "uniform vector: component evaluability was wrong");
Detail::Evaluator evaluator(plan, runtime);
std::uint32_t result = 0;
require(evaluator.Evaluate(Detail::IrNode(&value), result) == expected, "uniform vector: component evaluation was wrong");
if (expected) require(result == (component == 0u ? 0u : component == 1u ? data[0] : 0xffffffffu), "uniform vector: the wrong component was read");
}
for (const auto a : {0u, 1u, 0x7fffffffu, 0x80000000u, 0xffffffffu}) {
for (const auto b : {0u, 1u, 0x7fffffffu, 0x80000000u, 0xffffffffu}) {
auto& value = make(IrOpcode::UGreaterThanEqual32, IrType::Bool, {&constant(a), &constant(b)});
require(SrtWalker().ValidateRuntimeValue(plan, &value) && Detail::Evaluator::MayEvaluate(plan, &value), "uniform comparison: unsigned greater-than-or-equal was rejected");
Detail::Evaluator evaluator(plan, runtime);
std::uint32_t result = 0;
require(evaluator.Evaluate(Detail::IrNode(&value), result) && result == static_cast<std::uint32_t>(a >= b), "uniform comparison: unsigned greater-than-or-equal was wrong");
}
}
}

void verifyMayEvaluate() {
using namespace ShaderRecompiler;
IrResourcePlan plan;
Expand Down Expand Up @@ -4799,6 +4900,35 @@ void verifyWaterfallImageTable() {
auto& selectOther = make(IrOpcode::SelectU32, IrType::U32, {&otherCond, &input, &lane});
auto& firstOther = make(IrOpcode::ReadFirstLane, IrType::U32, {&selectOther, &conjMask});
require(!SrtWalker().ValidateRuntimeValue(planA, &firstOther), "waterfall image table: non-implied condition was accepted by validator");
auto scaledCode = code;
scaledCode[4] = 0x34020085u;
scaledCode[5] = 0x7e240501u;
auto scaledRequest = TableRequest(scaledCode, userData);
const auto scaledPlan = GetResourcePlan(scaledRequest);
require(scaledPlan->info.images.size() == 1u && scaledPlan->info.images[0].table == 0u, "waterfall image table: a scale before readfirstlane was not planned as a table");
const auto& scaledColumn = *scaledPlan->descriptorSources.at(scaledPlan->info.images[0].source).tableColumn;
require(scaledColumn.stride == 32u && scaledColumn.offset == 0x40u, "waterfall image table: scalarizing the offset changed its column");
const auto scaledCompiled = CompileTable(scaledRequest);
const auto& scaledSnapshot = scaledCompiled.capture->snapshot.tables.tables.at(0);
require(scaledSnapshot.keys == Abi::MaxKeys && (scaledSnapshot.codes[2] & Abi::PoisonFlag) == 0u && (scaledSnapshot.codes[4] & Abi::PoisonFlag) == 0u, "waterfall image table: scalarized offsets lost valid records");
scaledCode.insert(scaledCode.begin() + 4, {0x7d8a0080u, 0x7e020284u, 0x02000282u});
const auto boundedScaledPlan = GetResourcePlan(TableRequest(scaledCode, userData));
const auto& boundedScaledColumn = *boundedScaledPlan->descriptorSources.at(boundedScaledPlan->info.images[0].source).tableColumn;
require(boundedScaledColumn.maxKey == 6u && boundedScaledColumn.stride == 32u, "waterfall image table: scalarized offsets lost their key bound");
auto maskedRequest = TableRequest(scaledCode, userData);
maskedRequest.context.compute->numThreads = {1u, 1u, 1u};
const auto maskedPlan = GetResourcePlan(maskedRequest);
require(maskedPlan->info.images.size() == 1u && maskedPlan->info.images[0].table == 0u && maskedPlan->descriptorSources.at(maskedPlan->info.images[0].source).tableColumn->stride == 32u, "waterfall image table: an entry-lane mask hid the scalarized scale");
auto irregularCode = code;
irregularCode[4] = 0x34020100u;
irregularCode[5] = 0x7e240501u;
bool refused = false;
try {
static_cast<void>(GetResourcePlan(TableRequest(irregularCode, userData)));
} catch (const std::runtime_error& error) {
refused = std::string_view(error.what()).find("not a valid runtime value") != std::string_view::npos;
}
require(refused, "waterfall image table: a variable shift was accepted as a constant-stride column");
}

void verifySplitWaterfallImageTable() {
Expand Down Expand Up @@ -9671,6 +9801,8 @@ int main(int argc, char** argv) {
return 0;
}
if (argc == 2 && std::string_view(argv[1]) == "--srt-read-patching") {
verifyUniformVectorComponents();
verifyWaterfallRuntimeValues();
verifySrtReadPatching();
std::cout << "SRT read patching replaces repeated and multiple uses\n";
return 0;
Expand Down
7 changes: 7 additions & 0 deletions core/shader/recompiler/Optimization/src/ResourceTracker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,13 @@ class Tracker {

static bool MatchAffineOffset(IrValue* value, IrValue*& key, std::uint32_t& stride, std::uint32_t& addend) {
value = value->Resolve();
if (value->Opcode() == IrOpcode::ReadFirstLane && value->ArgumentCount() == 2u) {
const auto* activeMask = value->Argument(1)->Resolve();
value = value->Argument(0)->Resolve();
while (value->Opcode() == IrOpcode::SelectU32 && value->ArgumentCount() == 3u && Detail::ActiveMaskImplies(activeMask, value->Argument(0))) {
value = value->Argument(1)->Resolve();
}
}
addend = 0;
bool matched = MatchScale(value, key, stride);
if (!matched && value->Opcode() == IrOpcode::IAdd32 && value->ArgumentCount() == 2u) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ bool BasicEvaluator<TNode>::EvaluateWide(TNode value, std::uint64_t& result) {
return false;
}
const TNode& inst = value;
if (_masked && inst.Type() == IrType::Bool && ActiveMaskImplies(_activeMask, inst)) {
result = 1u;
return true;
}
if (_masked && IsRuntimeSelect(inst.Opcode()) && inst.ArgumentCount() == 3 && ActiveMaskImplies(_activeMask, inst.Argument(0))) {
return EvaluateWide(inst.Argument(1), result);
}
Expand Down Expand Up @@ -150,6 +154,10 @@ bool BasicEvaluator<TNode>::EvaluateExtract(const TNode& inst, std::uint64_t& re
return false;
}
const auto component = static_cast<std::uint32_t>(index.ImmediateBits());
if (inst.Opcode() == IrOpcode::CompositeExtractU32x4) {
const TNode source = inst.Argument(0);
return component < 4u && source.Opcode() == IrOpcode::CompositeConstructU32x4 && source.ArgumentCount() == 4u && EvaluateWide(source.Argument(component), result);
}
if (component >= 2u) {
return false;
}
Expand Down Expand Up @@ -319,6 +327,7 @@ std::optional<std::size_t> evaluatedArguments(IrOpcode opcode) {
case IrOpcode::SLessThan32:
case IrOpcode::ULessThan32:
case IrOpcode::UGreaterThan32:
case IrOpcode::UGreaterThanEqual32:
case IrOpcode::LogicalAnd:
case IrOpcode::LogicalOr:
case IrOpcode::LogicalXor: return 2u;
Expand Down Expand Up @@ -356,6 +365,12 @@ std::optional<bool> evaluableAlone(const IrResourcePlan& program, const IrValue&
case IrOpcode::LoadAddressU32:
case IrOpcode::ReadConstBuffer: return IsRawRead(program, value);
case IrOpcode::Phi: return std::nullopt;
case IrOpcode::CompositeExtractU32x4: {
const auto* source = value.ArgumentCount() == 2u && value.Argument(0) != nullptr ? value.Argument(0)->Resolve() : nullptr;
const auto* index = value.ArgumentCount() == 2u && value.Argument(1) != nullptr ? value.Argument(1)->Resolve() : nullptr;
if (source == nullptr || source->Opcode() != IrOpcode::CompositeConstructU32x4 || source->ArgumentCount() != 4u || index == nullptr || !index->HasImmediate() || index->Type() != IrType::U32 || index->ImmediateU32() >= 4u) return false;
return std::nullopt;
}
case IrOpcode::CompositeExtractU64:
case IrOpcode::CompositeExtractU32x2: {
if (value.ArgumentCount() < 2) return true;
Expand Down Expand Up @@ -404,6 +419,10 @@ bool BasicEvaluator<TNode>::MayEvaluate(const IrResourcePlan& program, IrValue*
return;
}
const auto resolved = [](IrValue* value) { return value != nullptr ? value->Resolve() : nullptr; };
if (current->Opcode() == IrOpcode::CompositeExtractU32x4) {
required.push_back(resolved(current->Argument(0)->Resolve()->Argument(current->Argument(1)->Resolve()->ImmediateU32())));
return;
}
if (current->Opcode() == IrOpcode::CompositeExtractU64) {
required.push_back(resolved(current->Argument(0)));
return;
Expand All @@ -430,6 +449,10 @@ bool BasicEvaluator<TNode>::MayEvaluate(const IrResourcePlan& program, IrValue*
const auto [current, expanded] = pending.back();
pending.pop_back();
if (known.contains(current)) continue;
if (activeMask != nullptr && current->Type() == IrType::Bool && ActiveMaskImplies(activeMask, current)) {
known.emplace(current, true);
continue;
}
if (current->Opcode() == IrOpcode::ReadFirstLane && current->ArgumentCount() >= 2) {
auto* source = current->Argument(0) != nullptr ? current->Argument(0)->Resolve() : nullptr;
const auto* mask = current->Argument(1) != nullptr ? current->Argument(1)->Resolve() : nullptr;
Expand Down Expand Up @@ -489,6 +512,7 @@ EvaluatorReads BasicEvaluator<TNode>::Reads(const IrResourcePlan& program, const
switch (value.Opcode()) {
case IrOpcode::LoadAddressU32:
case IrOpcode::ReadConstBuffer: return IsRawRead(program, value) ? EvaluatorReads::Closure : EvaluatorReads::Nothing;
case IrOpcode::CompositeConstructU32x4:
case IrOpcode::CompositeConstructU32x2:
case IrOpcode::IAddCarry32:
case IrOpcode::GetUserData:
Expand All @@ -498,6 +522,7 @@ EvaluatorReads BasicEvaluator<TNode>::Reads(const IrResourcePlan& program, const
case IrOpcode::BitCastU32F32:
case IrOpcode::BitCastF32U32:
case IrOpcode::CompositeExtractU64:
case IrOpcode::CompositeExtractU32x4:
case IrOpcode::CompositeExtractU32x2:
case IrOpcode::ReadConst: return EvaluatorReads::Arguments;
default: return evaluatedArguments(value.Opcode()) ? EvaluatorReads::Arguments : EvaluatorReads::Nothing;
Expand Down Expand Up @@ -543,7 +568,8 @@ bool BasicEvaluator<TNode>::EvaluateInst(const TNode& inst, std::uint64_t& resul
case IrOpcode::BitCastU32F32:
case IrOpcode::BitCastF32U32: return Arg(inst, 0, result);
case IrOpcode::CompositeExtractU64:
case IrOpcode::CompositeExtractU32x2: return EvaluateExtract(inst, result);
case IrOpcode::CompositeExtractU32x2:
case IrOpcode::CompositeExtractU32x4: return EvaluateExtract(inst, result);
case IrOpcode::CompositeConstructU64:
if (!binary()) {
return false;
Expand Down Expand Up @@ -828,6 +854,12 @@ bool BasicEvaluator<TNode>::EvaluateInst(const TNode& inst, std::uint64_t& resul
}
return false;
}
case IrOpcode::UGreaterThanEqual32:
if (binary()) {
result = static_cast<std::uint32_t>(a) >= static_cast<std::uint32_t>(b) ? 1u : 0u;
return true;
}
return false;
case IrOpcode::IEqual32:
if (binary()) {
result = static_cast<std::uint32_t>(a) == static_cast<std::uint32_t>(b) ? 1u : 0u;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ bool IsRuntimeUniformOp(IrOpcode opcode) {
case IrOpcode::ULessThan32:
case IrOpcode::IEqual32:
case IrOpcode::UGreaterThan32:
case IrOpcode::UGreaterThanEqual32:
case IrOpcode::INotEqual32:
case IrOpcode::LogicalOr:
case IrOpcode::LogicalAnd:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ bool RuntimeValidator::Validate(IrValue* raw, bool requireUniform) {
return valid;
};
const auto op = inst.Opcode();
if (_activeMask != nullptr && inst.Type() == IrType::Bool && ActiveMaskImplies(_activeMask, &inst)) {
return finish(true);
}
if (op == IrOpcode::ReadConst) {
IrValue* slot = inst.ArgumentCount() == 2 ? inst.Argument(1)->Resolve() : nullptr;
if (inst.ArgumentCount() != 2 || inst.Argument(0)->Resolve()->Opcode() == IrOpcode::Void || inst.Argument(0)->Resolve()->Opcode() != IrOpcode::GetSrtResource || slot == nullptr || !slot->HasImmediate() || slot->Type() != IrType::U32 || slot->ImmediateU32() >= _program.srtReads.size()) {
Expand Down Expand Up @@ -114,6 +117,13 @@ bool RuntimeValidator::Validate(IrValue* raw, bool requireUniform) {
if (!IsRawRead(_program, inst) || handle == nullptr || handle->Opcode() != expected) {
return finish(false);
}
} else if (op == IrOpcode::CompositeExtractU32x4) {
IrValue* source = inst.ArgumentCount() == 2u ? inst.Argument(0)->Resolve() : nullptr;
IrValue* index = inst.ArgumentCount() == 2u ? inst.Argument(1)->Resolve() : nullptr;
if (source == nullptr || source->Opcode() != IrOpcode::CompositeConstructU32x4 || source->ArgumentCount() != 4u || index == nullptr || !index->HasImmediate() || index->Type() != IrType::U32 || index->ImmediateU32() >= 4u) {
return finish(false);
}
return finish(Validate(source->Argument(index->ImmediateU32())));
} else if (op == IrOpcode::CompositeExtractU64) {
IrValue* index = inst.ArgumentCount() == 2 ? inst.Argument(1)->Resolve() : nullptr;
if (index == nullptr || !index->HasImmediate() || index->Type() != IrType::U32 || index->ImmediateU32() >= 2u) {
Expand Down
Loading
Loading