mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-24 21:06:55 +02:00
shader: simplify shared-exit control flow
This commit is contained in:
@@ -636,6 +636,29 @@ uint32_t EmitDppUpdateU32(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
ctx.Arg(inst, 1));
|
||||
}
|
||||
|
||||
uint32_t EmitConditionRef(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
if (ctx.other_half == nullptr) return ctx.Arg(inst, 0);
|
||||
// A native scalar branch makes one decision for both emulated wave halves.
|
||||
if (ctx.half != 0) return ctx.other_half->Def(IR::Value(&inst));
|
||||
const auto kind = inst.Flags<CFG::BranchCondition>();
|
||||
if (kind == CFG::BranchCondition::ScalarInstruction) return ctx.Arg(inst, 0);
|
||||
const auto ballot = ctx.Ballot(inst.Arg(0));
|
||||
const auto low = ctx.state.builder.AllocateId();
|
||||
const auto high = ctx.state.builder.AllocateId();
|
||||
const auto combined = ctx.state.builder.AllocateId();
|
||||
const auto result = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(ctx.state), low, ballot, 0);
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(ctx.state), high, ballot, 1);
|
||||
const bool zero = kind == CFG::BranchCondition::ExecZero ||
|
||||
kind == CFG::BranchCondition::VccZero || kind == CFG::BranchCondition::SccZero;
|
||||
ctx.state.builder.AddFunction(zero ? spv::OpBitwiseAnd : spv::OpBitwiseOr,
|
||||
TypeU32(ctx.state), combined, low, high);
|
||||
ctx.state.builder.AddFunction(zero ? spv::OpIEqual : spv::OpINotEqual,
|
||||
TypeBool(ctx.state), result, combined,
|
||||
ConstantU32(ctx.state, zero ? ~0u : 0u));
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t EmitBallot(ValueEmitContext& ctx, IR::Value predicate) {
|
||||
return ctx.Ballot(predicate);
|
||||
}
|
||||
|
||||
@@ -182,6 +182,7 @@ uint32_t EmitDppUpdateU32(ValueEmitContext& ctx, const IR::Inst& in
|
||||
uint32_t EmitWqmU64(EmitterState& state, uint32_t value);
|
||||
uint32_t EmitLaneId(EmitterState& state);
|
||||
uint32_t EmitBallot(ValueEmitContext& ctx, IR::Value predicate);
|
||||
uint32_t EmitConditionRef(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
uint32_t EmitReadFirstLane(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
uint32_t EmitReadLane(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
uint32_t EmitWriteLane(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
|
||||
@@ -97,30 +97,6 @@ void EmitReturn(ValueEmitContext& ctx) {
|
||||
ctx.state.builder.AddFunction(spv::OpReturn);
|
||||
}
|
||||
|
||||
uint32_t BranchCondition(ValueEmitContext& ctx, const IR::BlockInfo& info) {
|
||||
// Scalar-instruction conditions already test the full wave's raw register values.
|
||||
if (ctx.other_half == nullptr ||
|
||||
info.terminator.condition == CFG::BranchCondition::ScalarInstruction ||
|
||||
info.terminator.condition == CFG::BranchCondition::GotoVariable) {
|
||||
return ctx.Def(info.condition);
|
||||
}
|
||||
const auto ballot = ctx.Ballot(info.condition);
|
||||
const auto low = ctx.state.builder.AllocateId();
|
||||
const auto high = ctx.state.builder.AllocateId();
|
||||
const auto result = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(ctx.state), low, ballot, 0);
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(ctx.state), high, ballot, 1);
|
||||
const auto kind = info.terminator.condition;
|
||||
const bool zero = kind == CFG::BranchCondition::ExecZero ||
|
||||
kind == CFG::BranchCondition::VccZero ||
|
||||
kind == CFG::BranchCondition::SccZero;
|
||||
const auto combined =
|
||||
EmitBinaryU32(ctx.state, zero ? spv::OpBitwiseAnd : spv::OpBitwiseOr, low, high);
|
||||
ctx.state.builder.AddFunction(zero ? spv::OpIEqual : spv::OpINotEqual, TypeBool(ctx.state),
|
||||
result, combined, ConstantU32(ctx.state, zero ? ~0u : 0u));
|
||||
return result;
|
||||
}
|
||||
|
||||
void EmitStructuredTerminator(ValueEmitContext& ctx, const IR::Block* block,
|
||||
const IR::BlockInfo& info) {
|
||||
const auto& program = ctx.state.program;
|
||||
@@ -160,7 +136,7 @@ void EmitStructuredTerminator(ValueEmitContext& ctx, const IR::Block* block,
|
||||
EmitReturn(ctx);
|
||||
return;
|
||||
}
|
||||
const auto condition = BranchCondition(ctx, info);
|
||||
const auto condition = ctx.Def(info.condition);
|
||||
emit_merge();
|
||||
ctx.state.builder.AddFunction(spv::OpBranchConditional, condition,
|
||||
ctx.Label(true_block), ctx.Label(false_block));
|
||||
@@ -193,7 +169,7 @@ uint32_t EmitDispatcherNextPc(ValueEmitContext& ctx, const DispatcherFunctionSta
|
||||
EmitDispatcherTarget(ctx, dispatcher, block, term.false_block);
|
||||
const auto selected = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(
|
||||
spv::OpSelect, TypeU32(ctx.state), selected, BranchCondition(ctx, info),
|
||||
spv::OpSelect, TypeU32(ctx.state), selected, ctx.Def(info.condition),
|
||||
ConstantU32(ctx.state, term.true_block), ConstantU32(ctx.state, term.false_block));
|
||||
return selected;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -18,10 +18,17 @@ enum class BranchCondition {
|
||||
ExecZero,
|
||||
ExecNonZero,
|
||||
ScalarInstruction,
|
||||
GotoVariable,
|
||||
Expression,
|
||||
Unknown
|
||||
};
|
||||
|
||||
struct ConditionExpression {
|
||||
enum class Op { Constant, Variable, Native, Not, Or };
|
||||
Op op;
|
||||
uint32_t lhs;
|
||||
uint32_t rhs = UINT32_MAX;
|
||||
};
|
||||
|
||||
enum class TerminatorKind { Branch, ConditionalBranch, IndirectBranch, Return, Unsupported };
|
||||
|
||||
enum class FailureKind {
|
||||
@@ -48,8 +55,7 @@ struct Terminator {
|
||||
std::vector<uint32_t> indirect_targets;
|
||||
std::vector<uint32_t> indirect_selector_values;
|
||||
std::vector<uint32_t> indirect_selector_targets;
|
||||
uint32_t goto_variable = UINT32_MAX;
|
||||
int32_t goto_value = -1;
|
||||
uint32_t expression = UINT32_MAX;
|
||||
bool loop_header = false;
|
||||
};
|
||||
|
||||
@@ -59,6 +65,8 @@ struct BasicBlock {
|
||||
uint32_t end_pc = 0;
|
||||
uint32_t inst_begin = 0;
|
||||
uint32_t inst_end = 0;
|
||||
struct Assignment { uint32_t variable; uint32_t expression; };
|
||||
std::vector<Assignment> assignments;
|
||||
std::vector<uint32_t> predecessors;
|
||||
std::vector<uint32_t> successors;
|
||||
std::vector<uint32_t> dominators;
|
||||
@@ -89,6 +97,7 @@ struct StronglyConnectedComponent {
|
||||
|
||||
struct Graph {
|
||||
std::vector<BasicBlock> blocks;
|
||||
std::vector<ConditionExpression> expressions;
|
||||
std::vector<BackEdge> back_edges;
|
||||
std::vector<NaturalLoop> natural_loops;
|
||||
std::vector<StronglyConnectedComponent> components;
|
||||
|
||||
@@ -746,55 +746,61 @@ void Translator::WriteCompareResult(const Decoder::Operand& operand, IR::U1 valu
|
||||
WriteMask(operand, ir.LogicalAnd(ir.GetExec(), value));
|
||||
}
|
||||
|
||||
void Translator::AddBranchCondition(const CFG::BasicBlock& source, IR::BlockInfo& info) {
|
||||
if (source.terminator.goto_value >= 0) {
|
||||
if (source.terminator.goto_variable == UINT32_MAX) {
|
||||
EXIT("block %u sets an invalid goto variable", source.id);
|
||||
void Translator::AddBranchCondition(const CFG::Graph& graph, const CFG::BasicBlock& source,
|
||||
IR::BlockInfo& info) {
|
||||
const auto native_condition = [&](CFG::BranchCondition kind) -> IR::U1 {
|
||||
IR::U1 condition;
|
||||
switch (kind) {
|
||||
case CFG::BranchCondition::Always: condition = IR::U1(IR::Value(true)); break;
|
||||
case CFG::BranchCondition::SccZero: condition = ir.LogicalNot(ir.GetScc()); break;
|
||||
case CFG::BranchCondition::SccNonZero: condition = ir.GetScc(); break;
|
||||
case CFG::BranchCondition::VccZero: condition = ir.LogicalNot(ir.GetVcc()); break;
|
||||
case CFG::BranchCondition::VccNonZero: condition = ir.GetVcc(); break;
|
||||
case CFG::BranchCondition::ExecZero: condition = ir.LogicalNot(ir.GetExec()); break;
|
||||
case CFG::BranchCondition::ExecNonZero: condition = ir.GetExec(); break;
|
||||
case CFG::BranchCondition::ScalarInstruction:
|
||||
EXIT_IF(instruction_branch_condition.IsEmpty());
|
||||
condition = instruction_branch_condition;
|
||||
break;
|
||||
default: EXIT("block %u has an invalid native branch condition", source.id);
|
||||
}
|
||||
ir.SetGotoVariable(source.terminator.goto_variable,
|
||||
IR::U1(IR::Value(source.terminator.goto_value != 0)));
|
||||
return IR::U1(ir.Emit(IR::ValueOpcode::ConditionRef, {condition}, kind));
|
||||
};
|
||||
const auto expression = [&](auto&& self, uint32_t index) -> IR::U1 {
|
||||
const auto& value = graph.expressions.at(index);
|
||||
switch (value.op) {
|
||||
case CFG::ConditionExpression::Op::Constant: return IR::U1(IR::Value(value.lhs != 0));
|
||||
case CFG::ConditionExpression::Op::Variable: return ir.GetGotoVariable(value.lhs);
|
||||
case CFG::ConditionExpression::Op::Native:
|
||||
return native_condition(static_cast<CFG::BranchCondition>(value.lhs));
|
||||
case CFG::ConditionExpression::Op::Not: return ir.LogicalNot(self(self, value.lhs));
|
||||
case CFG::ConditionExpression::Op::Or:
|
||||
return ir.LogicalOr(self(self, value.lhs), self(self, value.rhs));
|
||||
}
|
||||
EXIT("invalid CFG condition expression");
|
||||
};
|
||||
for (const auto& assignment: source.assignments) {
|
||||
ir.SetGotoVariable(assignment.variable, expression(expression, assignment.expression));
|
||||
}
|
||||
if (source.terminator.kind == CFG::TerminatorKind::IndirectBranch) {
|
||||
if (source.terminator.indirect_selector_code != UINT32_MAX) {
|
||||
info.indirect_target = ReadScalarCode(source.terminator.indirect_selector_code);
|
||||
} else if (source.terminator.indirect_pc_sgpr != UINT32_MAX) {
|
||||
info.indirect_target =
|
||||
ir.GetScalarReg(static_cast<IR::ScalarReg>(source.terminator.indirect_pc_sgpr));
|
||||
const auto& term = source.terminator;
|
||||
if (term.kind == CFG::TerminatorKind::IndirectBranch) {
|
||||
if (term.indirect_selector_code != UINT32_MAX) {
|
||||
info.indirect_target = ReadScalarCode(term.indirect_selector_code);
|
||||
} else if (term.indirect_pc_sgpr != UINT32_MAX) {
|
||||
info.indirect_target = ir.GetScalarReg(static_cast<IR::ScalarReg>(term.indirect_pc_sgpr));
|
||||
} else {
|
||||
EXIT("block %u has no indirect branch selector", source.id);
|
||||
}
|
||||
ir.Emit(IR::ValueOpcode::ReferenceU32, {info.indirect_target});
|
||||
return;
|
||||
}
|
||||
if (source.terminator.kind != CFG::TerminatorKind::ConditionalBranch) {
|
||||
return;
|
||||
if (term.kind == CFG::TerminatorKind::ConditionalBranch) {
|
||||
const auto condition = term.expression != UINT32_MAX
|
||||
? expression(expression, term.expression)
|
||||
: native_condition(term.condition);
|
||||
info.condition = condition;
|
||||
ir.Emit(IR::ValueOpcode::Reference, {condition});
|
||||
}
|
||||
// EXEC and VCC are invocation-local Boolean masks. Branching on that Boolean lets inactive
|
||||
// invocations leave the region without reconstructing a host-subgroup mask.
|
||||
IR::U1 condition;
|
||||
switch (source.terminator.condition) {
|
||||
case CFG::BranchCondition::Always: condition = IR::U1(IR::Value(true)); break;
|
||||
case CFG::BranchCondition::SccZero: condition = ir.LogicalNot(ir.GetScc()); break;
|
||||
case CFG::BranchCondition::SccNonZero: condition = ir.GetScc(); break;
|
||||
case CFG::BranchCondition::VccZero: condition = ir.LogicalNot(ir.GetVcc()); break;
|
||||
case CFG::BranchCondition::VccNonZero: condition = ir.GetVcc(); break;
|
||||
case CFG::BranchCondition::ExecZero: condition = ir.LogicalNot(ir.GetExec()); break;
|
||||
case CFG::BranchCondition::ExecNonZero: condition = ir.GetExec(); break;
|
||||
case CFG::BranchCondition::ScalarInstruction:
|
||||
EXIT_IF(instruction_branch_condition.IsEmpty());
|
||||
condition = instruction_branch_condition;
|
||||
break;
|
||||
case CFG::BranchCondition::GotoVariable:
|
||||
if (source.terminator.goto_variable == UINT32_MAX) {
|
||||
EXIT("block %u reads an invalid goto variable", source.id);
|
||||
}
|
||||
condition = ir.GetGotoVariable(source.terminator.goto_variable);
|
||||
break;
|
||||
case CFG::BranchCondition::Unknown:
|
||||
EXIT("block %u has an unknown branch condition", source.id);
|
||||
}
|
||||
info.condition = condition;
|
||||
ir.Emit(IR::ValueOpcode::Reference, {condition});
|
||||
}
|
||||
|
||||
namespace {
|
||||
@@ -1297,7 +1303,7 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
|
||||
}
|
||||
translator.TranslateInstruction(instruction);
|
||||
}
|
||||
translator.AddBranchCondition(cfg_block, result.block_info[typed_index]);
|
||||
translator.AddBranchCondition(cfg, cfg_block, result.block_info[typed_index]);
|
||||
}
|
||||
IR::ValidateProgram(result, false);
|
||||
return result;
|
||||
|
||||
@@ -15,7 +15,7 @@ public:
|
||||
void TranslateInstruction(const Decoder::Instruction& inst);
|
||||
void TranslateEmbeddedFetch(const Decoder::Instruction& inst, uint32_t attribute,
|
||||
uint32_t component_count, const ShaderBufferResource& resource);
|
||||
void AddBranchCondition(const CFG::BasicBlock& source, IR::BlockInfo& info);
|
||||
void AddBranchCondition(const CFG::Graph& graph, const CFG::BasicBlock& source, IR::BlockInfo& info);
|
||||
|
||||
private:
|
||||
const Decoder::Operand& SourceAt(const Decoder::Instruction& inst, uint32_t index);
|
||||
|
||||
@@ -4,6 +4,7 @@ VALUE_OPCODE(Identity, Opaque, Opaque)
|
||||
VALUE_OPCODE(Void, Void)
|
||||
VALUE_OPCODE(Reference, Void, U1)
|
||||
VALUE_OPCODE(ReferenceU32, Void, U32)
|
||||
VALUE_OPCODE(ConditionRef, U1, U1)
|
||||
|
||||
// Translation and register-state pseudo operations.
|
||||
VALUE_OPCODE(GetUserData, U32, ScalarReg)
|
||||
|
||||
@@ -630,6 +630,11 @@ void FoldInstruction(Block& block, Block::iterator instruction,
|
||||
}
|
||||
}
|
||||
return;
|
||||
case ValueOpcode::ConditionRef: {
|
||||
const auto value = Arg(inst, 0);
|
||||
if (IsImmediate(value, Type::U1)) Replace(inst, value);
|
||||
return;
|
||||
}
|
||||
case ValueOpcode::LogicalNot: {
|
||||
const auto value = Arg(inst, 0);
|
||||
if (IsImmediate(value, Type::U1)) {
|
||||
|
||||
@@ -89,6 +89,7 @@ bool IsRuntimeSelect(ValueOpcode op) {
|
||||
|
||||
bool IsRuntimeUniformOp(ValueOpcode op) {
|
||||
switch (op) {
|
||||
case ValueOpcode::ConditionRef:
|
||||
case ValueOpcode::BitCastU32F32:
|
||||
case ValueOpcode::BitCastF32U32:
|
||||
case ValueOpcode::ConvertU32F32:
|
||||
@@ -970,6 +971,7 @@ bool SrtWalker::EvaluateInst(const Inst& inst, uint64_t& result) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case ValueOpcode::ConditionRef: return Arg(inst, 0, result);
|
||||
case ValueOpcode::LogicalNot:
|
||||
if (Arg(inst, 0, a)) {
|
||||
result = a == 0u;
|
||||
|
||||
@@ -23390,6 +23390,73 @@ TestCase SharedReturnKeepsSelectedValues() {
|
||||
return test;
|
||||
}
|
||||
|
||||
TestCase SiblingSharedExitKeepsCapturedConditions() {
|
||||
using O = ShaderOpcode;
|
||||
std::vector<u32> code = {
|
||||
EncodeVop1(0x01, 5, 4),
|
||||
EncodeVop2(0x1a, 5, InlineU32(6), 5),
|
||||
EncodeVop2(0x25, 5, Vgpr(0), 5), // group * 64 + lane
|
||||
EncodeSop2(0x0e, 6, 4, InlineU32(1)),
|
||||
0, // choose left or right
|
||||
EncodeSMovB32(8, InlineU32(20)),
|
||||
EncodeSop2(0x0e, 6, 4, InlineU32(2)),
|
||||
0, // left: skip nested exit
|
||||
EncodeSop2(0x0e, 6, 4, InlineU32(4)),
|
||||
0, // nested left: shared exit
|
||||
0, // left -> normal return
|
||||
EncodeSMovB32(8, InlineU32(30)),
|
||||
EncodeSop2(0x0e, 6, 4, InlineU32(8)),
|
||||
0, // first right shared exit
|
||||
EncodeSMovB32(8, InlineU32(40)),
|
||||
EncodeSop2(0x0e, 6, 4, InlineU32(16)),
|
||||
0, // second right shared exit
|
||||
EncodeSMovB32(8, InlineU32(50)),
|
||||
EncodeSopc(0x06, InlineU32(0), InlineU32(1)), // overwrite SCC
|
||||
};
|
||||
AppendStoreSgprAtLaneDwordOffset(&code, 8, 5, 0);
|
||||
AppendEnd(&code);
|
||||
const auto shared_exit = static_cast<u32>(code.size());
|
||||
code.push_back(EncodeSop2(0x00, 8, 8, InlineU32(1))); // overwrite SCC again
|
||||
AppendStoreSgprAtLaneDwordOffset(&code, 8, 5, 0);
|
||||
AppendEnd(&code);
|
||||
code[4] = EncodeSopp(0x04, 11u - 5u);
|
||||
code[7] = EncodeSopp(0x04, 10u - 8u);
|
||||
code[9] = EncodeSopp(0x04, shared_exit - 10u);
|
||||
code[10] = EncodeSopp(0x02, 17u - 11u);
|
||||
code[13] = EncodeSopp(0x04, shared_exit - 14u);
|
||||
code[16] = EncodeSopp(0x04, shared_exit - 17u);
|
||||
|
||||
TestCase test;
|
||||
test.name = "SiblingSharedExitKeepsCapturedConditions";
|
||||
test.code = std::move(code);
|
||||
for (u32 group = 0; group < 32; ++group) {
|
||||
u32 result = 50;
|
||||
if (group & 1u) {
|
||||
if ((group & 2u) && !(group & 4u)) result = 21;
|
||||
} else if (!(group & 8u)) {
|
||||
result = 31;
|
||||
} else if (!(group & 16u)) {
|
||||
result = 41;
|
||||
}
|
||||
test.expected.insert(test.expected.end(), 64, result);
|
||||
}
|
||||
test.opcodes = {O::V_MOV_B32, O::V_LSHLREV_B32, O::V_ADD_NC_U32,
|
||||
O::S_AND_B32, O::S_MOV_B32, O::S_CMP_EQ_U32, O::S_ADD_U32,
|
||||
O::S_CBRANCH_SCC0, O::S_BRANCH, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
||||
test.required_spirv = {"OpSelectionMerge"};
|
||||
test.forbidden_spirv = {"OpSwitch"};
|
||||
test.compute_info.wave_size = 64;
|
||||
test.compute_info.threads_num[0] = 64;
|
||||
test.compute_info.threads_num[1] = 1;
|
||||
test.compute_info.threads_num[2] = 1;
|
||||
test.compute_info.group_id[0] = true;
|
||||
test.compute_info.thread_ids_num = 1;
|
||||
test.compute_info.workgroup_register = 4;
|
||||
test.has_compute_info = true;
|
||||
test.dispatch_x = 32;
|
||||
return test;
|
||||
}
|
||||
|
||||
TestCase BranchVccnzUsesWaveMask() {
|
||||
using O = ShaderOpcode;
|
||||
|
||||
@@ -29477,6 +29544,7 @@ std::vector<TestCase> MakeCases() {
|
||||
AddCase(BranchSelect);
|
||||
AddCase(SimpleLoop);
|
||||
AddCase(SharedReturnKeepsSelectedValues);
|
||||
AddCase(SiblingSharedExitKeepsCapturedConditions);
|
||||
AddCase(BranchVccnzUsesWaveMask);
|
||||
AddCase(BranchVccnzUsesCarryProducedWaveMask);
|
||||
AddCase(ScalarMemRealtimeCapturedPlaceholder);
|
||||
@@ -34450,6 +34518,7 @@ int main(int argc, char **argv) {
|
||||
RunCase(&vulkan, ImageSampleAndGather());
|
||||
RunCase(&vulkan, ImageGatherLodApproximatesLevelZero());
|
||||
RunCase(&vulkan, SharedReturnKeepsSelectedValues());
|
||||
RunCase(&vulkan, SiblingSharedExitKeepsCapturedConditions());
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--position-w-only") == 0) {
|
||||
|
||||
+131
-158
@@ -1818,12 +1818,12 @@ void TestSopkCompareImmediateExtension() {
|
||||
TranslateProgram(shader, MakeCompileOptions(ShaderType::Vertex));
|
||||
const auto branch = std::ranges::find_if(
|
||||
translated.program.block_info, [](const auto &block) {
|
||||
return block.terminator.condition == CFG::BranchCondition::SccZero;
|
||||
return block.terminator.kind == CFG::TerminatorKind::ConditionalBranch;
|
||||
});
|
||||
Check(branch != translated.program.block_info.end(),
|
||||
"captured LUT parameter export branch was lost");
|
||||
const auto condition = branch->condition.Resolve();
|
||||
Check(condition.IsImmediate() && condition.U1() == (counts == 6u),
|
||||
Check(condition.IsImmediate() && condition.U1() == (counts != 6u),
|
||||
"captured LUT unsigned compare suppressed a live parameter export");
|
||||
}
|
||||
}
|
||||
@@ -7882,17 +7882,13 @@ void TestNewShaderRecompilerCfgLoopExitSharedWithSelection() {
|
||||
const auto coverage = CfgInstructionCoverage(graph, decoded.instructions.size());
|
||||
Check(block_count == 8u && graph.natural_loops.size() == 1u,
|
||||
"shared loop-exit fixture has the wrong native CFG");
|
||||
Check(!ShaderRecompiler::CFG::Structurize(graph) &&
|
||||
graph.unsupported_reason.find("duplicate structured merge block") !=
|
||||
std::string::npos,
|
||||
"shared loop exit did not terminate at its structured merge conflict");
|
||||
Check(graph.blocks.size() == block_count &&
|
||||
CfgInstructionCoverage(graph, decoded.instructions.size()) == coverage,
|
||||
"shared loop-exit fallback changed semantic instruction coverage");
|
||||
Check(ShaderRecompiler::CFG::Structurize(graph), graph.unsupported_reason.c_str());
|
||||
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) == coverage,
|
||||
"shared loop-exit routing changed semantic instruction coverage");
|
||||
|
||||
auto result = RecompileForTest(shader, MakeCompileOptions(ShaderType::Compute));
|
||||
Check(result.program.dispatcher_fallback && SpirvContainsOpcode(result.spirv, 251),
|
||||
"shared loop exit did not emit its dispatcher fallback");
|
||||
Check(!result.program.dispatcher_fallback && !SpirvContainsOpcode(result.spirv, 251),
|
||||
"shared loop exit did not retain structured control flow");
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
}
|
||||
|
||||
@@ -7922,7 +7918,7 @@ void TestNewShaderRecompilerCfgLoopEarlyBreakNoSelection() {
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
}
|
||||
|
||||
void TestNewShaderRecompilerCfgNestedLoopNonlocalExitDispatcher() {
|
||||
void TestNewShaderRecompilerCfgNestedLoopNonlocalExitStructured() {
|
||||
const uint32_t shader[] = {
|
||||
EncodeSopc(0x0a, 0, 129), // outer loop: s_cmp_lt_u32 s0, 1
|
||||
EncodeSopp(0x04, 9), // outer exit -> end
|
||||
@@ -7942,10 +7938,10 @@ void TestNewShaderRecompilerCfgNestedLoopNonlocalExitDispatcher() {
|
||||
options.dump_ir = true;
|
||||
|
||||
auto result = RecompileForTest(shader, options);
|
||||
Check((result.ir_dump.find("mode=dispatcher") != std::string::npos),
|
||||
"nested-loop nonlocal exit did not select dispatcher fallback");
|
||||
Check(SpirvInstructionOpcodeCount(result.spirv, 251) != 0,
|
||||
"nested-loop nonlocal exit dispatcher SPIR-V lacks OpSwitch");
|
||||
Check((result.ir_dump.find("mode=structured") != std::string::npos),
|
||||
"nested-loop nonlocal exit did not retain structured control flow");
|
||||
Check(SpirvInstructionOpcodeCount(result.spirv, 251) == 0,
|
||||
"nested-loop nonlocal exit unexpectedly selected dispatcher OpSwitch");
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
}
|
||||
|
||||
@@ -8006,27 +8002,23 @@ void TestNewShaderRecompilerCfgNestedLoopExitTailMergeSplit() {
|
||||
Check(graph.blocks.size() > original_block_count,
|
||||
"nested loop exit tails did not create a private inner merge");
|
||||
|
||||
const auto *outer_header = graph.FindBlockByPc(0);
|
||||
const auto *inner_header = graph.FindBlockByPc(8);
|
||||
Check(outer_header != nullptr && inner_header != nullptr &&
|
||||
outer_header->terminator.loop_header &&
|
||||
inner_header->terminator.loop_header,
|
||||
"nested loop exit-tail fixture did not retain both loop headers");
|
||||
Check(inner_header->terminator.merge_block !=
|
||||
outer_header->terminator.continue_block,
|
||||
"inner loop merge still aliases the outer continue target");
|
||||
const auto *inner_merge =
|
||||
graph.FindBlock(inner_header->terminator.merge_block);
|
||||
Check(inner_merge != nullptr &&
|
||||
inner_merge->inst_begin == inner_merge->inst_end &&
|
||||
inner_merge->terminator.kind ==
|
||||
ShaderRecompiler::CFG::TerminatorKind::Branch &&
|
||||
inner_merge->terminator.true_block ==
|
||||
outer_header->terminator.continue_block,
|
||||
"private inner merge does not forward to the outer continue target");
|
||||
std::vector<const ShaderRecompiler::CFG::BasicBlock *> headers;
|
||||
for (const auto &block : graph.blocks)
|
||||
if (block.terminator.loop_header) headers.push_back(&block);
|
||||
Check(headers.size() == 2u, "nested loop exit-tail fixture lost a loop header");
|
||||
if (!graph.Dominates(headers[0]->id, headers[1]->id)) std::swap(headers[0], headers[1]);
|
||||
const auto *outer_header = headers[0];
|
||||
const auto *inner_header = headers[1];
|
||||
Check(inner_header->terminator.merge_block != outer_header->terminator.continue_block &&
|
||||
graph.Dominates(outer_header->id, inner_header->terminator.merge_block),
|
||||
"inner loop merge escaped the enclosing loop body");
|
||||
auto result = RecompileForTest(shader, MakeCompileOptions(ShaderType::Compute));
|
||||
Check(!result.program.dispatcher_fallback,
|
||||
"nested loop exit tails selected the dispatcher");
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
}
|
||||
|
||||
void TestNewShaderRecompilerCfgMixedContinueNonmergeExitDispatcher() {
|
||||
void TestNewShaderRecompilerCfgMixedContinueNonmergeExitStructured() {
|
||||
const uint32_t shader[] = {
|
||||
EncodeSopc(0x06, 7, 7), // entry branch bypasses loop -> exit X
|
||||
EncodeSopp(0x05, 5), // entry -> X
|
||||
@@ -8046,10 +8038,10 @@ void TestNewShaderRecompilerCfgMixedContinueNonmergeExitDispatcher() {
|
||||
options.dump_ir = true;
|
||||
|
||||
auto result = RecompileForTest(shader, options);
|
||||
Check((result.ir_dump.find("mode=dispatcher") != std::string::npos),
|
||||
"mixed continue/nonmerge exit did not select dispatcher fallback");
|
||||
Check(SpirvInstructionOpcodeCount(result.spirv, 251) != 0,
|
||||
"mixed continue/nonmerge exit dispatcher SPIR-V lacks OpSwitch");
|
||||
Check((result.ir_dump.find("mode=structured") != std::string::npos),
|
||||
"mixed continue/nonmerge exit did not retain structured control flow");
|
||||
Check(SpirvInstructionOpcodeCount(result.spirv, 251) == 0,
|
||||
"mixed continue/nonmerge exit unexpectedly selected dispatcher OpSwitch");
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
}
|
||||
|
||||
@@ -8100,7 +8092,7 @@ void TestNewShaderRecompilerCfgDirectConditionalLatchNoSelection() {
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
}
|
||||
|
||||
void TestNewShaderRecompilerCfgLoopEarlyContinuesNoSelection() {
|
||||
void TestNewShaderRecompilerCfgLoopEarlyContinuesStructured() {
|
||||
const uint32_t shader[] = {
|
||||
EncodeSMovB32(0, 128), // s0 = 0
|
||||
EncodeSopc(0x0a, 0, 130), // loop: s_cmp_lt_u32 s0, 2
|
||||
@@ -8125,8 +8117,8 @@ void TestNewShaderRecompilerCfgLoopEarlyContinuesNoSelection() {
|
||||
"loop early continues should stay on structured path");
|
||||
Check(SpirvInstructionOpcodeCount(result.spirv, 246) != 0,
|
||||
"loop early continues SPIR-V lacks OpLoopMerge");
|
||||
Check(SpirvInstructionOpcodeCount(result.spirv, 247) == 0,
|
||||
"loop early continues SPIR-V unexpectedly used OpSelectionMerge");
|
||||
Check(SpirvInstructionOpcodeCount(result.spirv, 250) <= 4u,
|
||||
"loop early continues introduced duplicate conditional branches");
|
||||
Check(SpirvInstructionOpcodeCount(result.spirv, 251) == 0,
|
||||
"loop early continues unexpectedly used dispatcher OpSwitch");
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
@@ -8242,29 +8234,28 @@ void TestNewShaderRecompilerCfgMultipleLoopLatches() {
|
||||
ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded);
|
||||
ShaderRecompiler::CFG::Graph graph;
|
||||
graph = ShaderRecompiler::CFG::BuildGraph(decoded);
|
||||
const auto original_block_count = graph.blocks.size();
|
||||
const auto coverage = CfgInstructionCoverage(graph, decoded.instructions.size());
|
||||
Check(graph.back_edges.size() == 2u,
|
||||
"multiple-latch fixture lacks two native backedges");
|
||||
Check(ShaderRecompiler::CFG::Structurize(graph),
|
||||
graph.unsupported_reason.c_str());
|
||||
Check(graph.blocks.size() == original_block_count + 2u,
|
||||
"multiple native latches did not create one synthetic continue and one "
|
||||
"empty header");
|
||||
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) == coverage,
|
||||
"shared backedge construction duplicated guest instructions");
|
||||
Check(graph.back_edges.size() == 1u && graph.natural_loops.size() == 1u,
|
||||
"multiple native latches were not coalesced to one SPIR-V backedge");
|
||||
const auto &loop = graph.natural_loops.front();
|
||||
const auto *continue_block = graph.FindBlock(loop.continue_block);
|
||||
Check(continue_block != nullptr &&
|
||||
continue_block->inst_begin == continue_block->inst_end &&
|
||||
continue_block->predecessors.size() == 2u,
|
||||
"canonical continue does not join both native latches");
|
||||
continue_block->terminator.true_block == loop.header &&
|
||||
graph.Dominates(loop.header, continue_block->id),
|
||||
"canonical continue does not return to its natural loop header");
|
||||
|
||||
auto options = MakeCompileOptions(ShaderType::Compute);
|
||||
auto result = RecompileForTest(shader, options);
|
||||
Check(SpirvInstructionOpcodeCount(result.spirv, 246) == 1u,
|
||||
"multiple-latch SPIR-V has the wrong loop-merge count");
|
||||
Check(SpirvInstructionOpcodeCount(result.spirv, 247) == 0u,
|
||||
"multiple-latch SPIR-V unexpectedly used a selection merge");
|
||||
Check(SpirvInstructionOpcodeCount(result.spirv, 250) <= 2u,
|
||||
"multiple-latch routing duplicated a native conditional branch");
|
||||
Check(SpirvInstructionOpcodeCount(result.spirv, 251) == 0u,
|
||||
"multiple-latch SPIR-V unexpectedly used dispatcher OpSwitch");
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
@@ -8319,36 +8310,20 @@ void TestNewShaderRecompilerCfgNestedEarlyExitLoopForwarders() {
|
||||
const auto original_block_count = graph.blocks.size();
|
||||
const auto original_coverage =
|
||||
CfgInstructionCoverage(graph, decoded.instructions.size());
|
||||
const auto original_empty_blocks =
|
||||
std::ranges::count_if(graph.blocks, [](const auto &block) {
|
||||
return block.inst_begin == block.inst_end;
|
||||
});
|
||||
Check(original_block_count == 7u && graph.natural_loops.size() == 1u,
|
||||
"nested early-exit fixture has the wrong native CFG");
|
||||
const bool structured = ShaderRecompiler::CFG::Structurize(graph);
|
||||
Check(structured, graph.unsupported_reason.c_str());
|
||||
Check(graph.blocks.size() == original_block_count + 4u &&
|
||||
CfgInstructionCoverage(graph, decoded.instructions.size()) ==
|
||||
original_coverage &&
|
||||
std::ranges::count_if(graph.blocks,
|
||||
[](const auto &block) {
|
||||
return block.inst_begin == block.inst_end;
|
||||
}) == original_empty_blocks + 4,
|
||||
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) == original_coverage,
|
||||
"nested early-exit structurization changed semantic coverage");
|
||||
const auto *preheader = graph.FindBlockByPc(0x00u);
|
||||
const auto *outer = graph.FindBlockByPc(0x08u);
|
||||
const auto *inner = graph.FindBlockByPc(0x10u);
|
||||
const auto *loop = graph.FindBlockByPc(0x20u);
|
||||
Check(
|
||||
preheader != nullptr && outer != nullptr && inner != nullptr &&
|
||||
loop != nullptr && loop->terminator.loop_header &&
|
||||
preheader->terminator.merge_block != UINT32_MAX &&
|
||||
outer->terminator.merge_block != UINT32_MAX &&
|
||||
inner->terminator.merge_block != UINT32_MAX &&
|
||||
preheader->terminator.merge_block != outer->terminator.merge_block &&
|
||||
preheader->terminator.merge_block != inner->terminator.merge_block &&
|
||||
outer->terminator.merge_block != inner->terminator.merge_block,
|
||||
"nested early-exit constructs do not have distinct structured merges");
|
||||
std::unordered_set<uint32_t> merges;
|
||||
for (const auto &block : graph.blocks) {
|
||||
if (block.terminator.merge_block != UINT32_MAX)
|
||||
Check(merges.insert(block.terminator.merge_block).second,
|
||||
"nested early-exit constructs share a structured merge");
|
||||
}
|
||||
Check(graph.natural_loops.size() == 1u,
|
||||
"nested early-exit routing changed the natural loop count");
|
||||
|
||||
auto options = MakeCompileOptions(ShaderType::Compute);
|
||||
options.dump_ir = true;
|
||||
@@ -8365,7 +8340,7 @@ void TestNewShaderRecompilerCfgNestedEarlyExitLoopForwarders() {
|
||||
|
||||
void TestNewShaderRecompilerCfgExecSccSharedArm() {
|
||||
const uint32_t shader[] = {
|
||||
EncodeSop2(0x15, 126, 4, 126), // s_andn2_b64 exec, s4, exec
|
||||
EncodeSop2(0x0f, 126, 4, 126), // s_and_b64 exec, s4, exec (runtime mask)
|
||||
EncodeSopp(0x08, 2), // execz -> shared arm
|
||||
EncodeSop2(0x15, 30, 30, 126), // s_andn2_b64 s30, s30, exec
|
||||
EncodeSopp(0x04, 2), // scc0 -> other arm, else shared arm
|
||||
@@ -8386,15 +8361,6 @@ void TestNewShaderRecompilerCfgExecSccSharedArm() {
|
||||
"shared-arm fixture already duplicated a semantic instruction");
|
||||
Check(ShaderRecompiler::CFG::Structurize(graph),
|
||||
graph.unsupported_reason.c_str());
|
||||
uint32_t route_selects = 0;
|
||||
uint32_t route_sets = 0;
|
||||
for (const auto &block : graph.blocks) {
|
||||
route_selects += block.terminator.condition ==
|
||||
ShaderRecompiler::CFG::BranchCondition::GotoVariable;
|
||||
route_sets += block.terminator.goto_value >= 0;
|
||||
}
|
||||
Check(graph.blocks.size() == 5u && route_selects == 0u && route_sets == 0u,
|
||||
"shared return arm introduced synthetic routing state");
|
||||
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) ==
|
||||
original_coverage,
|
||||
"EXEC/SCC shared-arm structurization changed semantic coverage");
|
||||
@@ -8437,7 +8403,7 @@ void TestSharedReturnPreservesDescriptorDominance() {
|
||||
graph.unsupported_reason.c_str());
|
||||
const auto *overwrite = graph.FindBlockByPc(0x10u);
|
||||
const auto *body = graph.FindBlockByPc(0x20u);
|
||||
Check(graph.blocks.size() == 5u && overwrite != nullptr && body != nullptr &&
|
||||
Check(overwrite != nullptr && body != nullptr &&
|
||||
graph.Dominates(overwrite->id, body->id) &&
|
||||
std::ranges::count_if(graph.blocks, [](const auto &block) {
|
||||
return block.terminator.kind ==
|
||||
@@ -8466,6 +8432,78 @@ void TestSharedReturnPreservesDescriptorDominance() {
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
}
|
||||
|
||||
void TestCfgSiblingSharedExit() {
|
||||
// f7030726b9470dd8: a nested exit on one side and two exits on the
|
||||
// other side share an epilogue, separate from the normal return.
|
||||
const uint32_t shader[] = {
|
||||
EncodeSopc(0x06, 0, 128), EncodeSopp(0x04, 5), // right or left
|
||||
EncodeSopc(0x06, 1, 128), EncodeSopp(0x04, 2), // join or nested exit
|
||||
EncodeSopc(0x06, 2, 128), EncodeSopp(0x04, 7), // shared exit
|
||||
EncodeSopp(0x02, 4), // left -> main
|
||||
EncodeSopc(0x06, 3, 128), EncodeSopp(0x04, 4), // right -> shared exit
|
||||
EncodeSopc(0x06, 4, 128), EncodeSopp(0x04, 2), // second shared exit
|
||||
EncodeSMovB32(5, 129), EncodeSopp(0x01), // main and normal return
|
||||
EncodeSMovB32(5, 130), EncodeSopp(0x01), // shared exit epilogue
|
||||
};
|
||||
using namespace ShaderRecompiler;
|
||||
Decoder::Program decoded;
|
||||
Decoder::DecodeProgram(std::span{shader}, decoded);
|
||||
const auto original = CFG::BuildGraph(decoded);
|
||||
auto structured = original;
|
||||
Check(CFG::Structurize(structured), structured.unsupported_reason.c_str());
|
||||
Check(CfgInstructionCoverage(structured, decoded.instructions.size()) ==
|
||||
CfgInstructionCoverage(original, decoded.instructions.size()),
|
||||
"shared sibling exit duplicated or dropped guest instructions");
|
||||
const auto trace = [&](const CFG::Graph &graph, uint32_t decisions) {
|
||||
std::vector<uint32_t> executed;
|
||||
std::vector<bool> variables(original.blocks.size() * 2);
|
||||
const auto expression = [&](auto &&self, uint32_t index) -> bool {
|
||||
const auto &value = graph.expressions.at(index);
|
||||
switch (value.op) {
|
||||
case CFG::ConditionExpression::Op::Constant: return value.lhs != 0;
|
||||
case CFG::ConditionExpression::Op::Variable: return variables.at(value.lhs);
|
||||
case CFG::ConditionExpression::Op::Native: std::abort();
|
||||
case CFG::ConditionExpression::Op::Not: return !self(self, value.lhs);
|
||||
case CFG::ConditionExpression::Op::Or:
|
||||
return self(self, value.lhs) || self(self, value.rhs);
|
||||
}
|
||||
std::abort();
|
||||
};
|
||||
auto current = graph.entry_block;
|
||||
for (unsigned steps = 0; steps < 100; ++steps) {
|
||||
const auto &block = graph.blocks.at(current);
|
||||
for (auto inst = block.inst_begin; inst < block.inst_end; ++inst)
|
||||
executed.push_back(inst);
|
||||
for (const auto &assignment : block.assignments) {
|
||||
variables.at(assignment.variable) =
|
||||
graph.expressions[assignment.expression].op == CFG::ConditionExpression::Op::Native
|
||||
? (decisions & (1u << (assignment.variable - original.blocks.size()))) != 0
|
||||
: expression(expression, assignment.expression);
|
||||
}
|
||||
const auto &term = block.terminator;
|
||||
if (term.kind == CFG::TerminatorKind::Return) return executed;
|
||||
Check(term.kind == CFG::TerminatorKind::Branch ||
|
||||
term.kind == CFG::TerminatorKind::ConditionalBranch,
|
||||
"shared exit trace has an unsupported terminator");
|
||||
const bool condition = term.expression != UINT32_MAX
|
||||
? expression(expression, term.expression)
|
||||
: (decisions & (1u << block.id)) != 0;
|
||||
current = term.kind == CFG::TerminatorKind::Branch || condition
|
||||
? term.true_block : term.false_block;
|
||||
}
|
||||
Check(false, "shared exit trace did not terminate");
|
||||
return executed;
|
||||
};
|
||||
for (uint32_t decisions = 0; decisions < (1u << original.blocks.size()); ++decisions)
|
||||
Check(trace(original, decisions) == trace(structured, decisions),
|
||||
"goto elimination changed the executed shared-exit path");
|
||||
auto result = RecompileForTest(shader, MakeCompileOptions(ShaderType::Compute));
|
||||
Check(!result.program.dispatcher_fallback &&
|
||||
SpirvInstructionOpcodeCount(result.spirv, 251) == 0,
|
||||
"shared sibling exit selected dispatcher lowering");
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
}
|
||||
|
||||
void TestNewShaderRecompilerCfgNestedTailEarlyExit() {
|
||||
const uint32_t shader[] = {
|
||||
EncodeSopc(0x06, 0, 0), // preceding outer condition
|
||||
@@ -8499,17 +8537,6 @@ void TestNewShaderRecompilerCfgNestedTailEarlyExit() {
|
||||
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) ==
|
||||
original_coverage,
|
||||
"nested-tail routing changed semantic instruction coverage");
|
||||
Check(std::ranges::count_if(
|
||||
graph.blocks,
|
||||
[](const auto &block) {
|
||||
return block.terminator.condition ==
|
||||
ShaderRecompiler::CFG::BranchCondition::GotoVariable;
|
||||
}) == 1u &&
|
||||
std::ranges::count_if(graph.blocks,
|
||||
[](const auto &block) {
|
||||
return block.terminator.goto_value >= 0;
|
||||
}) == 3u,
|
||||
"nested-tail early exit did not use typed route state");
|
||||
|
||||
auto options = MakeCompileOptions(ShaderType::Pixel);
|
||||
options.dump_ir = true;
|
||||
@@ -8549,17 +8576,6 @@ void TestNewShaderRecompilerCfgSharedReturnAfterNestedSelections() {
|
||||
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) ==
|
||||
original_coverage,
|
||||
"shared-exit route ordering changed semantic instruction coverage");
|
||||
const auto route_selects =
|
||||
std::ranges::count_if(graph.blocks, [](const auto &block) {
|
||||
return block.terminator.condition ==
|
||||
ShaderRecompiler::CFG::BranchCondition::GotoVariable;
|
||||
});
|
||||
const auto route_sets =
|
||||
std::ranges::count_if(graph.blocks, [](const auto &block) {
|
||||
return block.terminator.goto_value >= 0;
|
||||
});
|
||||
Check(route_selects == 0u && route_sets == 0u,
|
||||
"nested selections introduced routing state for a shared return");
|
||||
|
||||
auto options = MakeCompileOptions(ShaderType::Compute);
|
||||
options.dump_ir = true;
|
||||
@@ -8628,7 +8644,6 @@ void TestNewShaderRecompilerCfgLoopSharedRegion() {
|
||||
ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded);
|
||||
ShaderRecompiler::CFG::Graph graph;
|
||||
graph = ShaderRecompiler::CFG::BuildGraph(decoded);
|
||||
const auto original_block_count = graph.blocks.size();
|
||||
const auto original_coverage =
|
||||
CfgInstructionCoverage(graph, decoded.instructions.size());
|
||||
Check(graph.natural_loops.size() == 1u,
|
||||
@@ -8641,18 +8656,8 @@ void TestNewShaderRecompilerCfgLoopSharedRegion() {
|
||||
loop_header->terminator.continue_block != UINT32_MAX,
|
||||
"loop shared-region routing did not preserve the natural loop");
|
||||
|
||||
uint32_t route_selects = 0;
|
||||
uint32_t route_sets = 0;
|
||||
for (const auto &block : graph.blocks) {
|
||||
route_selects += block.terminator.condition ==
|
||||
ShaderRecompiler::CFG::BranchCondition::GotoVariable;
|
||||
route_sets += block.terminator.goto_value >= 0;
|
||||
}
|
||||
Check(route_selects == 1u && route_sets == 3u &&
|
||||
graph.blocks.size() >= original_block_count + 5u &&
|
||||
CfgInstructionCoverage(graph, decoded.instructions.size()) ==
|
||||
original_coverage,
|
||||
"loop shared region was not routed without semantic duplication");
|
||||
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) == original_coverage,
|
||||
"loop shared region changed guest instruction coverage");
|
||||
|
||||
auto options = MakeCompileOptions(ShaderType::Compute);
|
||||
options.dump_ir = true;
|
||||
@@ -8701,11 +8706,6 @@ void TestNewShaderRecompilerCfgSharedRegionBeforeEarlyBreakLoop() {
|
||||
graph.unsupported_reason.c_str());
|
||||
Check(graph.natural_loops.size() == 1u && graph.back_edges.size() == 1u,
|
||||
"selection routing introduced a cycle around a structured loop exit");
|
||||
Check(std::ranges::count_if(graph.blocks, [](const auto &block) {
|
||||
return block.terminator.condition ==
|
||||
ShaderRecompiler::CFG::BranchCondition::GotoVariable;
|
||||
}) == 1u,
|
||||
"shared-region routing rewrote unrelated loop-control branches");
|
||||
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) ==
|
||||
original_coverage,
|
||||
"shared-region/early-break routing changed semantic coverage");
|
||||
@@ -8770,17 +8770,6 @@ void TestNewShaderRecompilerCfgOverlappingEarlyExitLadder() {
|
||||
"early-exit ladder routing changed semantic instruction coverage");
|
||||
Check(graph.blocks.size() > original_block_count,
|
||||
"early-exit ladder routing did not add forwarding blocks");
|
||||
const auto route_selects =
|
||||
std::ranges::count_if(graph.blocks, [](const auto &block) {
|
||||
return block.terminator.condition ==
|
||||
ShaderRecompiler::CFG::BranchCondition::GotoVariable;
|
||||
});
|
||||
const auto route_sets =
|
||||
std::ranges::count_if(graph.blocks, [](const auto &block) {
|
||||
return block.terminator.goto_value >= 0;
|
||||
});
|
||||
Check(route_selects != 0u && route_sets >= 3u,
|
||||
"early-exit ladder lacks explicit typed route state");
|
||||
|
||||
auto options = MakeCompileOptions(ShaderType::Pixel);
|
||||
options.dump_ir = true;
|
||||
@@ -8818,17 +8807,6 @@ void TestNewShaderRecompilerCfgNestedEarlyExitSharedTerminal() {
|
||||
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) ==
|
||||
original_coverage,
|
||||
"nested early-exit structurization changed semantic coverage");
|
||||
const auto route_selects =
|
||||
std::ranges::count_if(graph.blocks, [](const auto &block) {
|
||||
return block.terminator.condition ==
|
||||
ShaderRecompiler::CFG::BranchCondition::GotoVariable;
|
||||
});
|
||||
const auto route_sets =
|
||||
std::ranges::count_if(graph.blocks, [](const auto &block) {
|
||||
return block.terminator.goto_value >= 0;
|
||||
});
|
||||
Check(route_selects == 1u && route_sets == 3u,
|
||||
"nested early exit lacks complete typed route state");
|
||||
|
||||
auto options = MakeCompileOptions(ShaderType::Compute);
|
||||
options.dump_ir = true;
|
||||
@@ -8873,12 +8851,6 @@ void TestNewShaderRecompilerCfgEarlyReturnSharedLoopContinuation() {
|
||||
original_coverage &&
|
||||
graph.natural_loops.size() == 2u,
|
||||
"shared-continuation gateway changed semantic blocks or the loops");
|
||||
Check(std::ranges::none_of(graph.blocks,
|
||||
[](const auto &block) {
|
||||
return block.terminator.goto_variable !=
|
||||
UINT32_MAX;
|
||||
}),
|
||||
"private early return introduced unnecessary routing state");
|
||||
|
||||
auto options = MakeCompileOptions(ShaderType::Compute);
|
||||
auto result = RecompileForTest(shader, options);
|
||||
@@ -13629,13 +13601,13 @@ int main() {
|
||||
TestNewShaderRecompilerCfgSharedOuterAndLoopMerge();
|
||||
TestNewShaderRecompilerCfgLoopExitSharedWithSelection();
|
||||
TestNewShaderRecompilerCfgLoopEarlyBreakNoSelection();
|
||||
TestNewShaderRecompilerCfgNestedLoopNonlocalExitDispatcher();
|
||||
TestNewShaderRecompilerCfgNestedLoopNonlocalExitStructured();
|
||||
TestNewShaderRecompilerCfgNestedLoopLocalExitNoSelection();
|
||||
TestNewShaderRecompilerCfgNestedLoopExitTailMergeSplit();
|
||||
TestNewShaderRecompilerCfgMixedContinueNonmergeExitDispatcher();
|
||||
TestNewShaderRecompilerCfgMixedContinueNonmergeExitStructured();
|
||||
TestNewShaderRecompilerCfgConditionalLatchNoSelection();
|
||||
TestNewShaderRecompilerCfgDirectConditionalLatchNoSelection();
|
||||
TestNewShaderRecompilerCfgLoopEarlyContinuesNoSelection();
|
||||
TestNewShaderRecompilerCfgLoopEarlyContinuesStructured();
|
||||
TestNewShaderRecompilerCfgLoopGatewaySelection();
|
||||
TestNewShaderRecompilerCfgConditionalLoopHeaderSelection();
|
||||
TestNewShaderRecompilerCfgMultipleLoopLatches();
|
||||
@@ -13643,6 +13615,7 @@ int main() {
|
||||
TestNewShaderRecompilerCfgNestedEarlyExitLoopForwarders();
|
||||
TestNewShaderRecompilerCfgExecSccSharedArm();
|
||||
TestSharedReturnPreservesDescriptorDominance();
|
||||
TestCfgSiblingSharedExit();
|
||||
TestNewShaderRecompilerCfgNestedTailEarlyExit();
|
||||
TestNewShaderRecompilerCfgSharedReturnAfterNestedSelections();
|
||||
TestNewShaderRecompilerCfgAlternatingSharedReturns();
|
||||
|
||||
Reference in New Issue
Block a user