mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-20 10:56:58 +02:00
refactor(shader): reduce dispatcher state overhead
This commit is contained in:
@@ -46,6 +46,7 @@ std::string MakeIrDump(const CFG::Graph& cfg, const IR::Program& ir) {
|
|||||||
std::string dump = "CFG:\n";
|
std::string dump = "CFG:\n";
|
||||||
dump += CFG::GraphToString(cfg);
|
dump += CFG::GraphToString(cfg);
|
||||||
dump += "\nIR:\n";
|
dump += "\nIR:\n";
|
||||||
|
dump += fmt::format("mode={}\n", ir.dispatcher_fallback ? "dispatcher" : "structured");
|
||||||
dump += ir.values != nullptr ? IR::ValueProgramToString(*ir.values) : IR::ProgramToString(ir);
|
dump += ir.values != nullptr ? IR::ValueProgramToString(*ir.values) : IR::ProgramToString(ir);
|
||||||
return dump;
|
return dump;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -380,18 +380,18 @@ struct ValueEmitContext {
|
|||||||
const IR::MemoryInfo& Memory(const IR::Inst& inst) const;
|
const IR::MemoryInfo& Memory(const IR::Inst& inst) const;
|
||||||
const IR::ExportInfo& Export(const IR::Inst& inst) const;
|
const IR::ExportInfo& Export(const IR::Inst& inst) const;
|
||||||
uint32_t Label(const IR::Block* block) const;
|
uint32_t Label(const IR::Block* block) const;
|
||||||
size_t BlockIndex(const IR::Block* block) const;
|
|
||||||
void Fail(const IR::Inst& inst, const char* reason);
|
void Fail(const IR::Inst& inst, const char* reason);
|
||||||
|
|
||||||
EmitterState& state;
|
EmitterState& state;
|
||||||
const IR::ValueProgram& program;
|
const IR::ValueProgram& program;
|
||||||
std::unordered_map<const IR::Inst*, uint32_t> definitions;
|
std::unordered_map<const IR::Inst*, uint32_t> definitions;
|
||||||
std::unordered_map<const IR::Block*, uint32_t> labels;
|
std::unordered_map<const IR::Block*, uint32_t> labels;
|
||||||
const std::unordered_map<const IR::Inst*, uint32_t>* dispatcher_spills = nullptr;
|
const std::unordered_map<const IR::Inst*, uint32_t>* dispatcher_spills = nullptr;
|
||||||
const IR::Block* current_block = nullptr;
|
std::unordered_map<const IR::Inst*, std::pair<uint32_t, uint32_t>> dispatcher_block_loads;
|
||||||
uint32_t scratch_u32_variable = 0;
|
const IR::Block* current_block = nullptr;
|
||||||
bool failed = false;
|
uint32_t scratch_u32_variable = 0;
|
||||||
std::string error;
|
bool failed = false;
|
||||||
|
std::string error;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class VertexInputScalarKind { Float, Sint, Uint };
|
enum class VertexInputScalarKind { Float, Sint, Uint };
|
||||||
|
|||||||
@@ -36,24 +36,9 @@ void EmitKillIfPixelValidMaskInactive(EmitterState& state) {
|
|||||||
EmitKillIfBoolFalse(state, active);
|
EmitKillIfBoolFalse(state, active);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t SpillPointerType(EmitterState& state, IR::Type type) {
|
uint32_t SpillPointerType(ValueEmitContext& ctx, IR::Type type) {
|
||||||
switch (type) {
|
const auto value_type = ctx.TypeId(type);
|
||||||
case IR::Type::U1: return TypePointer(state, StorageClassFunction, TypeBool(state));
|
return value_type == 0 ? 0 : TypePointer(ctx.state, StorageClassFunction, value_type);
|
||||||
case IR::Type::U8:
|
|
||||||
case IR::Type::U16:
|
|
||||||
case IR::Type::U32:
|
|
||||||
case IR::Type::F16: return TypePointer(state, StorageClassFunction, TypeU32(state));
|
|
||||||
case IR::Type::F32: return TypePointer(state, StorageClassFunction, TypeF32(state));
|
|
||||||
case IR::Type::U64: return TypePointer(state, StorageClassFunction, TypeU64(state));
|
|
||||||
case IR::Type::U32x2: return TypePointer(state, StorageClassFunction, TypeU32Pair(state));
|
|
||||||
case IR::Type::U32x3:
|
|
||||||
return TypePointer(state, StorageClassFunction, TypeU32Vector(state, 3));
|
|
||||||
case IR::Type::U32x4:
|
|
||||||
return TypePointer(state, StorageClassFunction, TypeU32Vector(state, 4));
|
|
||||||
case IR::Type::F32x2:
|
|
||||||
return TypePointer(state, StorageClassFunction, TypeF32Vector(state, 2));
|
|
||||||
default: return 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DeferredPhiPatch {
|
struct DeferredPhiPatch {
|
||||||
@@ -68,10 +53,8 @@ struct StructuredFunctionState {
|
|||||||
|
|
||||||
struct DispatcherFunctionState {
|
struct DispatcherFunctionState {
|
||||||
std::unordered_map<const IR::Inst*, uint32_t> spills;
|
std::unordered_map<const IR::Inst*, uint32_t> spills;
|
||||||
uint32_t pc_variable = 0;
|
|
||||||
uint32_t header_label = 0;
|
uint32_t header_label = 0;
|
||||||
uint32_t select_label = 0;
|
uint32_t select_label = 0;
|
||||||
uint32_t default_label = 0;
|
|
||||||
uint32_t after_switch_label = 0;
|
uint32_t after_switch_label = 0;
|
||||||
uint32_t continue_label = 0;
|
uint32_t continue_label = 0;
|
||||||
uint32_t merge_label = 0;
|
uint32_t merge_label = 0;
|
||||||
@@ -166,16 +149,13 @@ void EmitDispatcherTarget(ValueEmitContext& ctx, const DispatcherFunctionState&
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmitDispatcherTerminator(ValueEmitContext& ctx, const DispatcherFunctionState& dispatcher,
|
uint32_t EmitDispatcherNextPc(ValueEmitContext& ctx, const DispatcherFunctionState& dispatcher,
|
||||||
const IR::Block* block, const IR::ValueBlockInfo& info,
|
const IR::Block* block, const IR::ValueBlockInfo& info) {
|
||||||
uint32_t exit_label) {
|
|
||||||
const auto& term = info.terminator;
|
const auto& term = info.terminator;
|
||||||
switch (term.kind) {
|
switch (term.kind) {
|
||||||
case CFG::TerminatorKind::Branch:
|
case CFG::TerminatorKind::Branch:
|
||||||
EmitDispatcherTarget(ctx, dispatcher, block, term.true_block);
|
EmitDispatcherTarget(ctx, dispatcher, block, term.true_block);
|
||||||
ctx.state.builder.AddFunction(
|
return ConstantU32(ctx.state, term.true_block);
|
||||||
{OpStore, dispatcher.pc_variable, ConstantU32(ctx.state, term.true_block)});
|
|
||||||
break;
|
|
||||||
case CFG::TerminatorKind::ConditionalBranch: {
|
case CFG::TerminatorKind::ConditionalBranch: {
|
||||||
EmitDispatcherTarget(ctx, dispatcher, block, term.true_block);
|
EmitDispatcherTarget(ctx, dispatcher, block, term.true_block);
|
||||||
EmitDispatcherTarget(ctx, dispatcher, block, term.false_block);
|
EmitDispatcherTarget(ctx, dispatcher, block, term.false_block);
|
||||||
@@ -184,16 +164,12 @@ void EmitDispatcherTerminator(ValueEmitContext& ctx, const DispatcherFunctionSta
|
|||||||
ctx.Def(info.condition),
|
ctx.Def(info.condition),
|
||||||
ConstantU32(ctx.state, term.true_block),
|
ConstantU32(ctx.state, term.true_block),
|
||||||
ConstantU32(ctx.state, term.false_block)});
|
ConstantU32(ctx.state, term.false_block)});
|
||||||
ctx.state.builder.AddFunction({OpStore, dispatcher.pc_variable, selected});
|
return selected;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
case CFG::TerminatorKind::IndirectBranch: {
|
case CFG::TerminatorKind::IndirectBranch: {
|
||||||
for (const auto target: term.indirect_targets) {
|
for (const auto target: term.indirect_targets) {
|
||||||
EmitDispatcherTarget(ctx, dispatcher, block, target);
|
EmitDispatcherTarget(ctx, dispatcher, block, target);
|
||||||
}
|
}
|
||||||
for (const auto target: term.indirect_selector_targets) {
|
|
||||||
EmitDispatcherTarget(ctx, dispatcher, block, target);
|
|
||||||
}
|
|
||||||
uint32_t selected = ConstantU32(ctx.state, UINT32_MAX);
|
uint32_t selected = ConstantU32(ctx.state, UINT32_MAX);
|
||||||
if (!info.indirect_target.IsEmpty()) {
|
if (!info.indirect_target.IsEmpty()) {
|
||||||
const auto selector = ctx.Def(info.indirect_target);
|
const auto selector = ctx.Def(info.indirect_target);
|
||||||
@@ -214,15 +190,10 @@ void EmitDispatcherTerminator(ValueEmitContext& ctx, const DispatcherFunctionSta
|
|||||||
selected = next;
|
selected = next;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctx.state.builder.AddFunction({OpStore, dispatcher.pc_variable, selected});
|
return selected;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
default:
|
default: return ConstantU32(ctx.state, UINT32_MAX);
|
||||||
ctx.state.builder.AddFunction(
|
|
||||||
{OpStore, dispatcher.pc_variable, ConstantU32(ctx.state, UINT32_MAX)});
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
ctx.state.builder.AddFunction({OpBranch, exit_label});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EmitDirectInstruction(ValueEmitContext& ctx, const IR::Inst& inst) {
|
bool EmitDirectInstruction(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||||
@@ -346,12 +317,16 @@ void EmitDispatcherFunction(ValueEmitContext& ctx, const DispatcherFunctionState
|
|||||||
if (ctx.failed) {
|
if (ctx.failed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
EmitDispatcherTerminator(ctx, dispatcher, entry, ctx.program.block_info.front(),
|
const auto initial_pc =
|
||||||
dispatcher.header_label);
|
EmitDispatcherNextPc(ctx, dispatcher, entry, ctx.program.block_info.front());
|
||||||
|
const auto initial_parent = state.current_label;
|
||||||
|
state.builder.AddFunction({OpBranch, dispatcher.header_label});
|
||||||
|
|
||||||
EmitLabel(state, dispatcher.header_label);
|
EmitLabel(state, dispatcher.header_label);
|
||||||
const auto pc = state.builder.AllocateId();
|
const auto pc = state.builder.AllocateId();
|
||||||
state.builder.AddFunction({OpLoad, TypeU32(state), pc, dispatcher.pc_variable});
|
const auto next_pc = state.builder.AllocateId();
|
||||||
|
state.builder.AddFunction({OpPhi, TypeU32(state), pc, initial_pc, initial_parent, next_pc,
|
||||||
|
dispatcher.continue_label});
|
||||||
const auto done = state.builder.AllocateId();
|
const auto done = state.builder.AllocateId();
|
||||||
state.builder.AddFunction(
|
state.builder.AddFunction(
|
||||||
{OpIEqual, TypeBool(state), done, pc, ConstantU32(ctx.state, UINT32_MAX)});
|
{OpIEqual, TypeBool(state), done, pc, ConstantU32(ctx.state, UINT32_MAX)});
|
||||||
@@ -363,16 +338,14 @@ void EmitDispatcherFunction(ValueEmitContext& ctx, const DispatcherFunctionState
|
|||||||
EmitLabel(state, dispatcher.select_label);
|
EmitLabel(state, dispatcher.select_label);
|
||||||
state.builder.AddFunction(
|
state.builder.AddFunction(
|
||||||
{OpSelectionMerge, dispatcher.after_switch_label, SelectionControlNone});
|
{OpSelectionMerge, dispatcher.after_switch_label, SelectionControlNone});
|
||||||
std::vector<uint32_t> words {OpSwitch, pc, dispatcher.default_label};
|
std::vector<uint32_t> words {OpSwitch, pc, dispatcher.after_switch_label};
|
||||||
for (size_t index = 1; index < ctx.program.blocks.size(); index++) {
|
for (size_t index = 1; index < ctx.program.blocks.size(); index++) {
|
||||||
words.push_back(ctx.program.block_info[index].id);
|
words.push_back(ctx.program.block_info[index].id);
|
||||||
words.push_back(ctx.Label(ctx.program.blocks[index]));
|
words.push_back(ctx.Label(ctx.program.blocks[index]));
|
||||||
}
|
}
|
||||||
state.builder.AddFunction(words);
|
state.builder.AddFunction(words);
|
||||||
EmitLabel(state, dispatcher.default_label);
|
std::vector<uint32_t> next_pc_words {OpPhi, TypeU32(state), next_pc,
|
||||||
state.builder.AddFunction(
|
ConstantU32(state, UINT32_MAX), dispatcher.select_label};
|
||||||
{OpStore, dispatcher.pc_variable, ConstantU32(ctx.state, UINT32_MAX)});
|
|
||||||
state.builder.AddFunction({OpBranch, dispatcher.after_switch_label});
|
|
||||||
|
|
||||||
for (size_t index = 1; index < ctx.program.blocks.size() && !ctx.failed; index++) {
|
for (size_t index = 1; index < ctx.program.blocks.size() && !ctx.failed; index++) {
|
||||||
EmitBlock(ctx, ctx.program.blocks[index], [&](const IR::Inst& inst) {
|
EmitBlock(ctx, ctx.program.blocks[index], [&](const IR::Inst& inst) {
|
||||||
@@ -381,13 +354,17 @@ void EmitDispatcherFunction(ValueEmitContext& ctx, const DispatcherFunctionState
|
|||||||
if (ctx.failed) {
|
if (ctx.failed) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
EmitDispatcherTerminator(ctx, dispatcher, ctx.program.blocks[index],
|
const auto selected = EmitDispatcherNextPc(ctx, dispatcher, ctx.program.blocks[index],
|
||||||
ctx.program.block_info[index], dispatcher.after_switch_label);
|
ctx.program.block_info[index]);
|
||||||
|
next_pc_words.push_back(selected);
|
||||||
|
next_pc_words.push_back(state.current_label);
|
||||||
|
state.builder.AddFunction({OpBranch, dispatcher.after_switch_label});
|
||||||
}
|
}
|
||||||
if (ctx.failed) {
|
if (ctx.failed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
EmitLabel(state, dispatcher.after_switch_label);
|
EmitLabel(state, dispatcher.after_switch_label);
|
||||||
|
state.builder.AddFunction(next_pc_words);
|
||||||
state.builder.AddFunction({OpBranch, dispatcher.continue_label});
|
state.builder.AddFunction({OpBranch, dispatcher.continue_label});
|
||||||
EmitLabel(state, dispatcher.continue_label);
|
EmitLabel(state, dispatcher.continue_label);
|
||||||
state.builder.AddFunction({OpBranch, dispatcher.header_label});
|
state.builder.AddFunction({OpBranch, dispatcher.header_label});
|
||||||
@@ -438,8 +415,14 @@ uint32_t ValueEmitContext::Def(IR::Value value) {
|
|||||||
if (dispatcher_spills != nullptr && current_block != nullptr &&
|
if (dispatcher_spills != nullptr && current_block != nullptr &&
|
||||||
inst->Parent() != current_block) {
|
inst->Parent() != current_block) {
|
||||||
if (const auto found = dispatcher_spills->find(inst); found != dispatcher_spills->end()) {
|
if (const auto found = dispatcher_spills->find(inst); found != dispatcher_spills->end()) {
|
||||||
|
if (const auto loaded = dispatcher_block_loads.find(inst);
|
||||||
|
loaded != dispatcher_block_loads.end() &&
|
||||||
|
loaded->second.first == state.current_label) {
|
||||||
|
return loaded->second.second;
|
||||||
|
}
|
||||||
const auto id = state.builder.AllocateId();
|
const auto id = state.builder.AllocateId();
|
||||||
state.builder.AddFunction({OpLoad, TypeId(inst->GetType()), id, found->second});
|
state.builder.AddFunction({OpLoad, TypeId(inst->GetType()), id, found->second});
|
||||||
|
dispatcher_block_loads.insert_or_assign(inst, std::pair {state.current_label, id});
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -515,10 +498,6 @@ uint32_t ValueEmitContext::Label(const IR::Block* block) const {
|
|||||||
return labels.at(block);
|
return labels.at(block);
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t ValueEmitContext::BlockIndex(const IR::Block* block) const {
|
|
||||||
return static_cast<size_t>(std::ranges::find(program.blocks, block) - program.blocks.begin());
|
|
||||||
}
|
|
||||||
|
|
||||||
void ValueEmitContext::Fail(const IR::Inst& inst, const char* reason) {
|
void ValueEmitContext::Fail(const IR::Inst& inst, const char* reason) {
|
||||||
failed = true;
|
failed = true;
|
||||||
error = fmt::format("typed opcode {} {}", IR::ValueOpcodeName(inst.GetOpcode()), reason);
|
error = fmt::format("typed opcode {} {}", IR::ValueOpcodeName(inst.GetOpcode()), reason);
|
||||||
@@ -534,14 +513,14 @@ bool EmitValueProgram(EmitterState& state, const IR::ValueProgram& program, std:
|
|||||||
for (const auto* block: program.blocks) {
|
for (const auto* block: program.blocks) {
|
||||||
ctx.labels.emplace(block, state.builder.AllocateId());
|
ctx.labels.emplace(block, state.builder.AllocateId());
|
||||||
}
|
}
|
||||||
if (program.dispatcher_fallback) {
|
if (state.program.dispatcher_fallback) {
|
||||||
auto& dispatch = dispatcher.emplace();
|
auto& dispatch = dispatcher.emplace();
|
||||||
for (const auto* block: program.blocks) {
|
for (const auto* block: program.blocks) {
|
||||||
for (const auto& inst: *block) {
|
for (const auto& inst: *block) {
|
||||||
if (inst.GetOpcode() != IR::ValueOpcode::Phi) {
|
if (inst.GetOpcode() != IR::ValueOpcode::Phi) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (SpillPointerType(state, inst.GetType()) == 0) {
|
if (SpillPointerType(ctx, inst.GetType()) == 0) {
|
||||||
ctx.Fail(inst, "cannot be stored by the dispatcher");
|
ctx.Fail(inst, "cannot be stored by the dispatcher");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -551,10 +530,11 @@ bool EmitValueProgram(EmitterState& state, const IR::ValueProgram& program, std:
|
|||||||
const auto mark_cross_block = [&](IR::Value value, const IR::Block* consumer) {
|
const auto mark_cross_block = [&](IR::Value value, const IR::Block* consumer) {
|
||||||
value = value.Resolve();
|
value = value.Resolve();
|
||||||
const auto* definition = value.TryInstruction();
|
const auto* definition = value.TryInstruction();
|
||||||
if (definition == nullptr || definition->Parent() == consumer) {
|
if (definition == nullptr || definition->Parent() == consumer ||
|
||||||
|
definition->Parent() == program.blocks.front()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (SpillPointerType(state, definition->GetType()) == 0) {
|
if (SpillPointerType(ctx, definition->GetType()) == 0) {
|
||||||
ctx.Fail(*definition, "cannot be stored by the dispatcher");
|
ctx.Fail(*definition, "cannot be stored by the dispatcher");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -575,10 +555,8 @@ bool EmitValueProgram(EmitterState& state, const IR::ValueProgram& program, std:
|
|||||||
mark_cross_block(program.block_info[index].condition, program.blocks[index]);
|
mark_cross_block(program.block_info[index].condition, program.blocks[index]);
|
||||||
mark_cross_block(program.block_info[index].indirect_target, program.blocks[index]);
|
mark_cross_block(program.block_info[index].indirect_target, program.blocks[index]);
|
||||||
}
|
}
|
||||||
dispatch.pc_variable = state.builder.AllocateId();
|
|
||||||
dispatch.header_label = state.builder.AllocateId();
|
dispatch.header_label = state.builder.AllocateId();
|
||||||
dispatch.select_label = state.builder.AllocateId();
|
dispatch.select_label = state.builder.AllocateId();
|
||||||
dispatch.default_label = state.builder.AllocateId();
|
|
||||||
dispatch.after_switch_label = state.builder.AllocateId();
|
dispatch.after_switch_label = state.builder.AllocateId();
|
||||||
dispatch.continue_label = state.builder.AllocateId();
|
dispatch.continue_label = state.builder.AllocateId();
|
||||||
dispatch.merge_label = state.builder.AllocateId();
|
dispatch.merge_label = state.builder.AllocateId();
|
||||||
@@ -610,15 +588,12 @@ bool EmitValueProgram(EmitterState& state, const IR::ValueProgram& program, std:
|
|||||||
TypePointer(state, StorageClassFunction, TypeU32(state)),
|
TypePointer(state, StorageClassFunction, TypeU32(state)),
|
||||||
state.pixel_valid_mask_variable, StorageClassFunction});
|
state.pixel_valid_mask_variable, StorageClassFunction});
|
||||||
}
|
}
|
||||||
if (program.dispatcher_fallback) {
|
if (state.program.dispatcher_fallback) {
|
||||||
state.builder.AddFunction({OpVariable,
|
|
||||||
TypePointer(state, StorageClassFunction, TypeU32(state)),
|
|
||||||
dispatcher->pc_variable, StorageClassFunction});
|
|
||||||
for (const auto* block: program.blocks) {
|
for (const auto* block: program.blocks) {
|
||||||
for (const auto& inst: *block) {
|
for (const auto& inst: *block) {
|
||||||
if (const auto found = dispatcher->spills.find(&inst);
|
if (const auto found = dispatcher->spills.find(&inst);
|
||||||
found != dispatcher->spills.end()) {
|
found != dispatcher->spills.end()) {
|
||||||
state.builder.AddFunction({OpVariable, SpillPointerType(state, inst.GetType()),
|
state.builder.AddFunction({OpVariable, SpillPointerType(ctx, inst.GetType()),
|
||||||
found->second, StorageClassFunction});
|
found->second, StorageClassFunction});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -641,7 +616,7 @@ bool EmitValueProgram(EmitterState& state, const IR::ValueProgram& program, std:
|
|||||||
EmitStorageBufferOffsets(state);
|
EmitStorageBufferOffsets(state);
|
||||||
if (program.blocks.empty()) {
|
if (program.blocks.empty()) {
|
||||||
EmitReturn(ctx);
|
EmitReturn(ctx);
|
||||||
} else if (program.dispatcher_fallback) {
|
} else if (state.program.dispatcher_fallback) {
|
||||||
EmitDispatcherFunction(ctx, *dispatcher);
|
EmitDispatcherFunction(ctx, *dispatcher);
|
||||||
} else {
|
} else {
|
||||||
EmitStructuredFunction(ctx);
|
EmitStructuredFunction(ctx);
|
||||||
|
|||||||
@@ -682,6 +682,66 @@ void RebuildPredecessors(Graph& graph) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PruneUnreachableBlocks(Graph& graph) {
|
||||||
|
if (graph.entry_block >= graph.blocks.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<bool> reachable(graph.blocks.size(), false);
|
||||||
|
std::vector<uint32_t> pending {graph.entry_block};
|
||||||
|
while (!pending.empty()) {
|
||||||
|
const auto block_id = pending.back();
|
||||||
|
pending.pop_back();
|
||||||
|
if (block_id >= graph.blocks.size() || reachable[block_id]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
reachable[block_id] = true;
|
||||||
|
for (const auto successor: graph.blocks[block_id].successors) {
|
||||||
|
pending.push_back(successor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (std::ranges::all_of(reachable, [](bool value) { return value; })) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint32_t> id_map(graph.blocks.size(), UINT32_MAX);
|
||||||
|
std::vector<BasicBlock> blocks;
|
||||||
|
blocks.reserve(std::ranges::count(reachable, true));
|
||||||
|
for (uint32_t old_id = 0; old_id < graph.blocks.size(); old_id++) {
|
||||||
|
if (!reachable[old_id]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
id_map[old_id] = static_cast<uint32_t>(blocks.size());
|
||||||
|
blocks.push_back(std::move(graph.blocks[old_id]));
|
||||||
|
}
|
||||||
|
|
||||||
|
graph.entry_block = RemapId(graph.entry_block, id_map);
|
||||||
|
graph.failure_block = RemapId(graph.failure_block, id_map);
|
||||||
|
graph.blocks = std::move(blocks);
|
||||||
|
for (auto& block: graph.blocks) {
|
||||||
|
block.id = RemapId(block.id, id_map);
|
||||||
|
RemapIds(block.successors, id_map);
|
||||||
|
block.predecessors.clear();
|
||||||
|
block.dominators.clear();
|
||||||
|
block.post_dominators.clear();
|
||||||
|
auto& terminator = block.terminator;
|
||||||
|
terminator.true_block = RemapId(terminator.true_block, id_map);
|
||||||
|
terminator.false_block = RemapId(terminator.false_block, id_map);
|
||||||
|
terminator.merge_block = RemapId(terminator.merge_block, id_map);
|
||||||
|
terminator.continue_block = RemapId(terminator.continue_block, id_map);
|
||||||
|
for (auto& target: terminator.indirect_targets) {
|
||||||
|
target = RemapId(target, id_map);
|
||||||
|
}
|
||||||
|
for (auto& target: terminator.indirect_selector_targets) {
|
||||||
|
target = RemapId(target, id_map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
graph.back_edges.clear();
|
||||||
|
graph.natural_loops.clear();
|
||||||
|
graph.components.clear();
|
||||||
|
RebuildPredecessors(graph);
|
||||||
|
}
|
||||||
|
|
||||||
void ComputeDominators(Graph& graph) {
|
void ComputeDominators(Graph& graph) {
|
||||||
const auto count = static_cast<uint32_t>(graph.blocks.size());
|
const auto count = static_cast<uint32_t>(graph.blocks.size());
|
||||||
const auto all = AllBlockIds(count);
|
const auto all = AllBlockIds(count);
|
||||||
@@ -1820,7 +1880,6 @@ bool BuildGraph(const Decoder::Program& program, Graph& graph, std::string* erro
|
|||||||
labels.insert(end_pc);
|
labels.insert(end_pc);
|
||||||
|
|
||||||
std::map<uint32_t, SetpcTargetInfo> setpc_targets;
|
std::map<uint32_t, SetpcTargetInfo> setpc_targets;
|
||||||
bool indirect_setpc = false;
|
|
||||||
for (uint32_t i = 0; i < program.instructions.size(); i++) {
|
for (uint32_t i = 0; i < program.instructions.size(); i++) {
|
||||||
const auto& inst = program.instructions[i];
|
const auto& inst = program.instructions[i];
|
||||||
const auto next_pc = InstructionEndPc(inst);
|
const auto next_pc = InstructionEndPc(inst);
|
||||||
@@ -1857,7 +1916,6 @@ bool BuildGraph(const Decoder::Program& program, Graph& graph, std::string* erro
|
|||||||
}
|
}
|
||||||
labels.insert(target);
|
labels.insert(target);
|
||||||
}
|
}
|
||||||
indirect_setpc = indirect_setpc || target_info.indirect;
|
|
||||||
setpc_targets.emplace(inst.pc, std::move(target_info));
|
setpc_targets.emplace(inst.pc, std::move(target_info));
|
||||||
if (next_pc <= end_pc) {
|
if (next_pc <= end_pc) {
|
||||||
labels.insert(next_pc);
|
labels.insert(next_pc);
|
||||||
@@ -1935,9 +1993,6 @@ bool BuildGraph(const Decoder::Program& program, Graph& graph, std::string* erro
|
|||||||
block.terminator.indirect_selector_targets.push_back(
|
block.terminator.indirect_selector_targets.push_back(
|
||||||
pc_to_block.at(target_info.selector_target_pcs[i]));
|
pc_to_block.at(target_info.selector_target_pcs[i]));
|
||||||
}
|
}
|
||||||
if (target_info.table_load_pc != UINT32_MAX) {
|
|
||||||
AddUnique(graph.code_table_load_pcs, target_info.table_load_pc);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
block.terminator.kind = TerminatorKind::Branch;
|
block.terminator.kind = TerminatorKind::Branch;
|
||||||
block.terminator.condition = BranchCondition::Always;
|
block.terminator.condition = BranchCondition::Always;
|
||||||
@@ -1998,6 +2053,22 @@ bool BuildGraph(const Decoder::Program& program, Graph& graph, std::string* erro
|
|||||||
for (auto& block: graph.blocks) {
|
for (auto& block: graph.blocks) {
|
||||||
SortUnique(block.predecessors);
|
SortUnique(block.predecessors);
|
||||||
}
|
}
|
||||||
|
PruneUnreachableBlocks(graph);
|
||||||
|
graph.code_table_load_pcs.clear();
|
||||||
|
bool indirect_setpc = false;
|
||||||
|
for (const auto& block: graph.blocks) {
|
||||||
|
if (block.terminator.kind != TerminatorKind::IndirectBranch) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
indirect_setpc = true;
|
||||||
|
if (block.inst_begin >= block.inst_end || block.inst_end > program.instructions.size()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto found = setpc_targets.find(program.instructions[block.inst_end - 1u].pc);
|
||||||
|
if (found != setpc_targets.end() && found->second.table_load_pc != UINT32_MAX) {
|
||||||
|
AddUnique(graph.code_table_load_pcs, found->second.table_load_pc);
|
||||||
|
}
|
||||||
|
}
|
||||||
SortUnique(graph.code_table_load_pcs);
|
SortUnique(graph.code_table_load_pcs);
|
||||||
|
|
||||||
ComputeDominators(graph);
|
ComputeDominators(graph);
|
||||||
|
|||||||
@@ -758,7 +758,6 @@ bool TranslateProgram(const IR::Program& source, IR::ValueProgram& result,
|
|||||||
const ShaderPixelInputInfo* pixel_input_info,
|
const ShaderPixelInputInfo* pixel_input_info,
|
||||||
const ShaderComputeInputInfo* compute_input_info, std::string* error) {
|
const ShaderComputeInputInfo* compute_input_info, std::string* error) {
|
||||||
result = {};
|
result = {};
|
||||||
result.dispatcher_fallback = source.dispatcher_fallback;
|
|
||||||
const uint32_t wave_size = source.wave_size;
|
const uint32_t wave_size = source.wave_size;
|
||||||
const bool per_invocation_masks = source.lane_mask_mode == ShaderLaneMaskMode::PerInvocation;
|
const bool per_invocation_masks = source.lane_mask_mode == ShaderLaneMaskMode::PerInvocation;
|
||||||
uint32_t vector_limit = 1u;
|
uint32_t vector_limit = 1u;
|
||||||
|
|||||||
@@ -1181,12 +1181,11 @@ bool LowerProgram(const Decoder::Program& decoded, const CFG::Graph& cfg, Shader
|
|||||||
EXIT("invalid shader wave size: %u\n", wave_size);
|
EXIT("invalid shader wave size: %u\n", wave_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
program = {};
|
program = {};
|
||||||
program.stage = stage;
|
program.stage = stage;
|
||||||
program.wave_size = wave_size;
|
program.wave_size = wave_size;
|
||||||
program.dispatcher_fallback = cfg.irreducible;
|
program.cfg_failure_kind = cfg.failure_kind;
|
||||||
program.cfg_failure_kind = cfg.failure_kind;
|
program.fallback_reason = cfg.unsupported_reason;
|
||||||
program.fallback_reason = cfg.unsupported_reason;
|
|
||||||
program.blocks.clear();
|
program.blocks.clear();
|
||||||
|
|
||||||
if (cfg.blocks.empty()) {
|
if (cfg.blocks.empty()) {
|
||||||
|
|||||||
@@ -142,6 +142,7 @@ bool ValidateValueProgram(const ValueProgram& program, bool require_ssa, std::st
|
|||||||
return Fail(error, "value IR block storage is inconsistent");
|
return Fail(error, "value IR block storage is inconsistent");
|
||||||
}
|
}
|
||||||
std::unordered_set<const Block*> blocks;
|
std::unordered_set<const Block*> blocks;
|
||||||
|
std::unordered_map<const Block*, size_t> block_indices;
|
||||||
std::unordered_map<uint32_t, const Block*> blocks_by_id;
|
std::unordered_map<uint32_t, const Block*> blocks_by_id;
|
||||||
std::unordered_set<const Inst*> instructions;
|
std::unordered_set<const Inst*> instructions;
|
||||||
for (size_t block_index = 0; block_index < program.blocks.size(); block_index++) {
|
for (size_t block_index = 0; block_index < program.blocks.size(); block_index++) {
|
||||||
@@ -153,6 +154,10 @@ bool ValidateValueProgram(const ValueProgram& program, bool require_ssa, std::st
|
|||||||
if (!blocks.insert(block).second) {
|
if (!blocks.insert(block).second) {
|
||||||
return Fail(error, "value IR block pointer is duplicated");
|
return Fail(error, "value IR block pointer is duplicated");
|
||||||
}
|
}
|
||||||
|
block_indices.emplace(block, block_index);
|
||||||
|
if (program.block_info[block_index].id == UINT32_MAX) {
|
||||||
|
return Fail(error, "value IR block uses the reserved exit id");
|
||||||
|
}
|
||||||
if (!blocks_by_id.emplace(program.block_info[block_index].id, block).second) {
|
if (!blocks_by_id.emplace(program.block_info[block_index].id, block).second) {
|
||||||
return Fail(error, "value IR block id is duplicated");
|
return Fail(error, "value IR block id is duplicated");
|
||||||
}
|
}
|
||||||
@@ -162,6 +167,9 @@ bool ValidateValueProgram(const ValueProgram& program, bool require_ssa, std::st
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!program.blocks.empty() && !program.blocks.front()->ImmPredecessors().empty()) {
|
||||||
|
return Fail(error, "value IR entry block has a predecessor");
|
||||||
|
}
|
||||||
|
|
||||||
for (size_t block_index = 0; block_index < program.blocks.size(); block_index++) {
|
for (size_t block_index = 0; block_index < program.blocks.size(); block_index++) {
|
||||||
const auto* block = program.blocks[block_index];
|
const auto* block = program.blocks[block_index];
|
||||||
@@ -224,12 +232,16 @@ bool ValidateValueProgram(const ValueProgram& program, bool require_ssa, std::st
|
|||||||
return Fail(error, "value IR conditional branch condition is invalid");
|
return Fail(error, "value IR conditional branch condition is invalid");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case CFG::TerminatorKind::IndirectBranch:
|
case CFG::TerminatorKind::IndirectBranch: {
|
||||||
if (!validate_control_value(program.block_info[block_index].indirect_target,
|
if (!validate_control_value(program.block_info[block_index].indirect_target,
|
||||||
Type::U32)) {
|
Type::U32)) {
|
||||||
return Fail(error, "value IR indirect branch selector is invalid");
|
return Fail(error, "value IR indirect branch selector is invalid");
|
||||||
}
|
}
|
||||||
|
std::unordered_set<uint32_t> indirect_targets;
|
||||||
for (const auto target: terminator.indirect_targets) {
|
for (const auto target: terminator.indirect_targets) {
|
||||||
|
if (!indirect_targets.insert(target).second) {
|
||||||
|
return Fail(error, "value IR indirect branch target is duplicated");
|
||||||
|
}
|
||||||
if (!add_target(target)) {
|
if (!add_target(target)) {
|
||||||
return Fail(error, "value IR indirect branch target is missing");
|
return Fail(error, "value IR indirect branch target is missing");
|
||||||
}
|
}
|
||||||
@@ -247,6 +259,7 @@ bool ValidateValueProgram(const ValueProgram& program, bool require_ssa, std::st
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case CFG::TerminatorKind::Return:
|
case CFG::TerminatorKind::Return:
|
||||||
case CFG::TerminatorKind::Unsupported: break;
|
case CFG::TerminatorKind::Unsupported: break;
|
||||||
}
|
}
|
||||||
@@ -440,6 +453,100 @@ bool ValidateValueProgram(const ValueProgram& program, bool require_ssa, std::st
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (program.blocks.empty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<bool> reachable(program.blocks.size(), false);
|
||||||
|
std::vector<size_t> pending {0u};
|
||||||
|
while (!pending.empty()) {
|
||||||
|
const auto block_index = pending.back();
|
||||||
|
pending.pop_back();
|
||||||
|
if (reachable[block_index]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
reachable[block_index] = true;
|
||||||
|
for (const auto* successor: program.blocks[block_index]->ImmSuccessors()) {
|
||||||
|
pending.push_back(block_indices.at(successor));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!std::ranges::all_of(reachable, [](bool value) { return value; })) {
|
||||||
|
return Fail(error, "value IR contains an unreachable block");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::vector<bool>> dominators(program.blocks.size(),
|
||||||
|
std::vector<bool>(program.blocks.size(), true));
|
||||||
|
dominators.front().assign(program.blocks.size(), false);
|
||||||
|
dominators.front().front() = true;
|
||||||
|
bool changed = true;
|
||||||
|
while (changed) {
|
||||||
|
changed = false;
|
||||||
|
for (size_t block_index = 1; block_index < program.blocks.size(); block_index++) {
|
||||||
|
std::vector<bool> next(program.blocks.size(), true);
|
||||||
|
for (const auto* predecessor: program.blocks[block_index]->ImmPredecessors()) {
|
||||||
|
const auto predecessor_index = block_indices.at(predecessor);
|
||||||
|
for (size_t candidate = 0; candidate < next.size(); candidate++) {
|
||||||
|
next[candidate] = next[candidate] && dominators[predecessor_index][candidate];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next[block_index] = true;
|
||||||
|
if (next != dominators[block_index]) {
|
||||||
|
dominators[block_index] = std::move(next);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unordered_map<const Inst*, size_t> instruction_positions;
|
||||||
|
for (const auto* block: program.blocks) {
|
||||||
|
size_t position = 0;
|
||||||
|
for (const auto& inst: *block) {
|
||||||
|
instruction_positions.emplace(&inst, position++);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const auto dominates = [&](const Block* definition, const Block* use) {
|
||||||
|
return dominators[block_indices.at(use)][block_indices.at(definition)];
|
||||||
|
};
|
||||||
|
const auto control_dominates = [&](Value value, const Block* use) {
|
||||||
|
const auto* definition = value.TryInstruction();
|
||||||
|
return definition == nullptr || definition->Parent() == use ||
|
||||||
|
dominates(definition->Parent(), use);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (size_t block_index = 0; block_index < program.blocks.size(); block_index++) {
|
||||||
|
const auto* block = program.blocks[block_index];
|
||||||
|
for (const auto& inst: *block) {
|
||||||
|
for (size_t arg_index = 0; arg_index < inst.NumArgs(); arg_index++) {
|
||||||
|
const auto* definition = inst.Arg(arg_index).TryInstruction();
|
||||||
|
if (definition == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (inst.GetOpcode() == ValueOpcode::Phi) {
|
||||||
|
const auto* predecessor = inst.PhiBlock(arg_index);
|
||||||
|
if (definition->Parent() != predecessor &&
|
||||||
|
!dominates(definition->Parent(), predecessor)) {
|
||||||
|
return Fail(error,
|
||||||
|
"value IR Phi incoming definition does not dominate its edge");
|
||||||
|
}
|
||||||
|
} else if (definition->Parent() == block) {
|
||||||
|
if (instruction_positions.at(definition) >= instruction_positions.at(&inst)) {
|
||||||
|
return Fail(error,
|
||||||
|
"value IR instruction uses a same-block definition before it");
|
||||||
|
}
|
||||||
|
} else if (!dominates(definition->Parent(), block)) {
|
||||||
|
return Fail(error, "value IR instruction definition does not dominate its use");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const auto& info = program.block_info[block_index];
|
||||||
|
if (!info.condition.IsEmpty() && !control_dominates(info.condition, block)) {
|
||||||
|
return Fail(error, "value IR branch condition definition does not dominate its use");
|
||||||
|
}
|
||||||
|
if (!info.indirect_target.IsEmpty() && !control_dominates(info.indirect_target, block)) {
|
||||||
|
return Fail(error, "value IR indirect selector definition does not dominate its use");
|
||||||
|
}
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -472,8 +579,7 @@ std::string ValueProgramToString(const ValueProgram& program) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
std::string text =
|
std::string text;
|
||||||
fmt::format("mode={}\n", program.dispatcher_fallback ? "dispatcher" : "structured");
|
|
||||||
for (size_t block_index = 0; block_index < program.blocks.size(); block_index++) {
|
for (size_t block_index = 0; block_index < program.blocks.size(); block_index++) {
|
||||||
text += fmt::format("Block ${} pc=0x{:08x}..0x{:08x}\n", block_index,
|
text += fmt::format("Block ${} pc=0x{:08x}..0x{:08x}\n", block_index,
|
||||||
program.block_info[block_index].start_pc,
|
program.block_info[block_index].start_pc,
|
||||||
|
|||||||
@@ -65,7 +65,6 @@ struct ValueProgram {
|
|||||||
std::vector<DescriptorSource> descriptor_sources;
|
std::vector<DescriptorSource> descriptor_sources;
|
||||||
std::vector<SrtRead> srt_reads;
|
std::vector<SrtRead> srt_reads;
|
||||||
std::vector<Value> dynamic_reads;
|
std::vector<Value> dynamic_reads;
|
||||||
bool dispatcher_fallback = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
bool ValidateValueProgram(const ValueProgram& program, bool require_ssa, std::string* error);
|
bool ValidateValueProgram(const ValueProgram& program, bool require_ssa, std::string* error);
|
||||||
|
|||||||
+316
-33
@@ -7854,7 +7854,7 @@ void TestNewShaderRecompilerCfgNestedEarlyExitSharedTerminal() {
|
|||||||
CheckSpirvBinaryValidates(result.spirv);
|
CheckSpirvBinaryValidates(result.spirv);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestNewShaderRecompilerCfgExternallyEnteredSelectionDispatcher() {
|
void TestNewShaderRecompilerCfgPrunesUnreachableSelectionEntry() {
|
||||||
const uint32_t shader[] = {
|
const uint32_t shader[] = {
|
||||||
EncodeSopp(0x02, 1), // entry -> header, skipping unreachable entry
|
EncodeSopp(0x02, 1), // entry -> header, skipping unreachable entry
|
||||||
EncodeSopp(0x02, 2), // unreachable entry -> shared selection arm
|
EncodeSopp(0x02, 2), // unreachable entry -> shared selection arm
|
||||||
@@ -7874,24 +7874,22 @@ void TestNewShaderRecompilerCfgExternallyEnteredSelectionDispatcher() {
|
|||||||
error.c_str());
|
error.c_str());
|
||||||
const auto original_coverage =
|
const auto original_coverage =
|
||||||
CfgInstructionCoverage(graph, decoded.instructions.size());
|
CfgInstructionCoverage(graph, decoded.instructions.size());
|
||||||
Check(!ShaderRecompiler::CFG::Structurize(graph, &error) &&
|
Check(original_coverage[1] == 0u,
|
||||||
Common::ContainsStr(error, "externally entered region block"),
|
"CFG retained an unreachable external selection entry");
|
||||||
"improper externally-entered selection was accepted as structured");
|
Check(ShaderRecompiler::CFG::Structurize(graph, &error), error.c_str());
|
||||||
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) ==
|
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) ==
|
||||||
original_coverage,
|
original_coverage,
|
||||||
"externally-entered selection handling duplicated semantic code");
|
"selection structurization changed reachable semantic code");
|
||||||
|
|
||||||
auto options = MakeCompileOptions(ShaderType::Compute);
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
||||||
options.dump_ir = true;
|
options.dump_ir = true;
|
||||||
ShaderRecompiler::CompileResult result;
|
ShaderRecompiler::CompileResult result;
|
||||||
Check(ShaderRecompiler::TryRecompile(shader, options, result, &error),
|
Check(ShaderRecompiler::TryRecompile(shader, options, result, &error),
|
||||||
error.c_str());
|
error.c_str());
|
||||||
Check(result.program.dispatcher_fallback &&
|
Check(!result.program.dispatcher_fallback &&
|
||||||
Common::ContainsStr(result.ir_dump,
|
Common::ContainsStr(result.ir_dump, "mode=structured") &&
|
||||||
"externally entered region block"),
|
SpirvInstructionOpcodeCount(result.spirv, 251) == 0u,
|
||||||
"improper externally-entered selection lost its dispatcher reason");
|
"unreachable selection entry still forced dispatcher mode");
|
||||||
Check(SpirvInstructionOpcodeCount(result.spirv, 251) != 0u,
|
|
||||||
"externally-entered selection dispatcher lacks OpSwitch");
|
|
||||||
CheckSpirvBinaryValidates(result.spirv);
|
CheckSpirvBinaryValidates(result.spirv);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7937,16 +7935,38 @@ void TestNewShaderRecompilerDispatcherSpillsU32x3() {
|
|||||||
std::string error;
|
std::string error;
|
||||||
Check(TryRecompile(shader, options, result, &error), error.c_str());
|
Check(TryRecompile(shader, options, result, &error), error.c_str());
|
||||||
Check(result.program.dispatcher_fallback &&
|
Check(result.program.dispatcher_fallback &&
|
||||||
result.program.values->blocks.size() >= 2u,
|
result.program.values->blocks.size() >= 3u,
|
||||||
"U32x3 spill fixture did not select dispatcher mode");
|
"U32x3 spill fixture did not select dispatcher mode");
|
||||||
|
const auto before = MeasureSpirv(result.spirv);
|
||||||
|
{
|
||||||
|
auto prologue_program = result.program;
|
||||||
|
IR::IREmitter definition(prologue_program.values->blocks[0]);
|
||||||
|
const auto vector =
|
||||||
|
definition.Emit(IR::ValueOpcode::CompositeConstructU32x3,
|
||||||
|
{IR::Value(1u), IR::Value(2u), IR::Value(3u)});
|
||||||
|
IR::IREmitter use(prologue_program.values->blocks[1]);
|
||||||
|
use.Emit(IR::ValueOpcode::CompositeExtractU32x3, {vector, IR::Value(2u)});
|
||||||
|
Check(Spirv::AnalyzeProgramRequirements(prologue_program, &error),
|
||||||
|
error.c_str());
|
||||||
|
std::vector<uint32_t> spirv;
|
||||||
|
Check(Spirv::EmitProgram(prologue_program, result.resources,
|
||||||
|
options.input_info, spirv, &error),
|
||||||
|
error.c_str());
|
||||||
|
CheckSpirvBinaryValidates(spirv);
|
||||||
|
const auto after = MeasureSpirv(spirv);
|
||||||
|
Check(after.function_variables == before.function_variables &&
|
||||||
|
after.loads == before.loads && after.stores == before.stores,
|
||||||
|
"dispatcher spilled a value defined by its dominating prologue");
|
||||||
|
}
|
||||||
|
|
||||||
auto program = result.program;
|
auto program = result.program;
|
||||||
IR::IREmitter definition(program.values->blocks[0]);
|
IR::IREmitter definition(program.values->blocks[1]);
|
||||||
const auto vector =
|
const auto vector =
|
||||||
definition.Emit(IR::ValueOpcode::CompositeConstructU32x3,
|
definition.Emit(IR::ValueOpcode::CompositeConstructU32x3,
|
||||||
{IR::Value(1u), IR::Value(2u), IR::Value(3u)});
|
{IR::Value(1u), IR::Value(2u), IR::Value(3u)});
|
||||||
IR::IREmitter use(program.values->blocks[1]);
|
IR::IREmitter use(program.values->blocks[2]);
|
||||||
use.Emit(IR::ValueOpcode::CompositeExtractU32x3, {vector, IR::Value(2u)});
|
use.Emit(IR::ValueOpcode::CompositeExtractU32x3, {vector, IR::Value(2u)});
|
||||||
|
use.Emit(IR::ValueOpcode::CompositeExtractU32x3, {vector, IR::Value(1u)});
|
||||||
Check(Spirv::AnalyzeProgramRequirements(program, &error), error.c_str());
|
Check(Spirv::AnalyzeProgramRequirements(program, &error), error.c_str());
|
||||||
|
|
||||||
std::vector<uint32_t> spirv;
|
std::vector<uint32_t> spirv;
|
||||||
@@ -7954,7 +7974,6 @@ void TestNewShaderRecompilerDispatcherSpillsU32x3() {
|
|||||||
&error),
|
&error),
|
||||||
error.c_str());
|
error.c_str());
|
||||||
CheckSpirvBinaryValidates(spirv);
|
CheckSpirvBinaryValidates(spirv);
|
||||||
const auto before = MeasureSpirv(result.spirv);
|
|
||||||
const auto after = MeasureSpirv(spirv);
|
const auto after = MeasureSpirv(spirv);
|
||||||
Check(after.function_variables == before.function_variables + 1u &&
|
Check(after.function_variables == before.function_variables + 1u &&
|
||||||
after.loads == before.loads + 1u &&
|
after.loads == before.loads + 1u &&
|
||||||
@@ -8025,13 +8044,13 @@ void TestNewShaderRecompilerU64PairLowering() {
|
|||||||
Check(TryRecompile(dispatcher_shader, options, result, &error),
|
Check(TryRecompile(dispatcher_shader, options, result, &error),
|
||||||
error.c_str());
|
error.c_str());
|
||||||
Check(result.program.dispatcher_fallback &&
|
Check(result.program.dispatcher_fallback &&
|
||||||
result.program.values->blocks.size() >= 2u,
|
result.program.values->blocks.size() >= 3u,
|
||||||
"U64 spill fixture did not select dispatcher mode");
|
"U64 spill fixture did not select dispatcher mode");
|
||||||
program = result.program;
|
program = result.program;
|
||||||
IR::IREmitter definition(program.values->blocks[0]);
|
IR::IREmitter definition(program.values->blocks[1]);
|
||||||
const auto vector = definition.Emit(IR::ValueOpcode::CompositeConstructU64,
|
const auto vector = definition.Emit(IR::ValueOpcode::CompositeConstructU64,
|
||||||
{IR::Value(1u), IR::Value(2u)});
|
{IR::Value(1u), IR::Value(2u)});
|
||||||
IR::IREmitter use(program.values->blocks[1]);
|
IR::IREmitter use(program.values->blocks[2]);
|
||||||
use.Emit(IR::ValueOpcode::CompositeExtractU64, {vector, IR::Value(1u)});
|
use.Emit(IR::ValueOpcode::CompositeExtractU64, {vector, IR::Value(1u)});
|
||||||
Check(Spirv::AnalyzeProgramRequirements(program, &error), error.c_str());
|
Check(Spirv::AnalyzeProgramRequirements(program, &error), error.c_str());
|
||||||
spirv.clear();
|
spirv.clear();
|
||||||
@@ -8262,8 +8281,62 @@ void TestNewShaderRecompilerSetpcJumpTable() {
|
|||||||
CheckSpirvBinaryValidates(result.spirv);
|
CheckSpirvBinaryValidates(result.spirv);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TestNewShaderRecompilerPrunesUnreachableSetpcMetadata() {
|
||||||
|
const uint32_t shader[] = {
|
||||||
|
EncodeSop2(0x07, 0, 0, 129), // s_min_u32 s0, s0, 1
|
||||||
|
EncodeSop2(0x1e, 0, 0, 131), // s_lshl_b32 s0, s0, 3
|
||||||
|
EncodeSop1(0x1f, 8, 0), // s_getpc_b64 s[8:9]
|
||||||
|
EncodeSop2(0x00, 8, 8, 255), // s_add_u32 s8, s8, literal
|
||||||
|
0x0000003cu,
|
||||||
|
EncodeSop2(0x04, 9, 9, 128), // s_addc_u32 s9, s9, 0
|
||||||
|
EncodeSmem0(0x01, 4, 4),
|
||||||
|
0u, // reachable s_load_dwordx2 s[4:5]
|
||||||
|
EncodeSopp(0x02, 5), // skip the otherwise valid jump-table dispatch
|
||||||
|
EncodeSopp(0x0c, 0), // unreachable s_waitcnt 0
|
||||||
|
EncodeSop1(0x1f, 10, 0),
|
||||||
|
EncodeSop2(0x00, 10, 10, 4),
|
||||||
|
EncodeSop2(0x04, 11, 11, 5),
|
||||||
|
EncodeSop1(0x20, 0, 10), // unreachable s_setpc_b64 s[10:11]
|
||||||
|
EncodeSMovB32(1, 4), // reachable use of the loaded s4
|
||||||
|
EncodeSopp(0x02, 1),
|
||||||
|
EncodeSMovB32(2, 129),
|
||||||
|
EncodeSopp(0x01, 0),
|
||||||
|
0x0000000cu,
|
||||||
|
0x00000000u,
|
||||||
|
0x00000014u,
|
||||||
|
0x00000000u,
|
||||||
|
};
|
||||||
|
|
||||||
|
ShaderRecompiler::Decoder::Program decoded;
|
||||||
|
std::string error;
|
||||||
|
Check(ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded,
|
||||||
|
&error),
|
||||||
|
error.c_str());
|
||||||
|
ShaderRecompiler::CFG::Graph graph;
|
||||||
|
Check(ShaderRecompiler::CFG::BuildGraph(decoded, graph, &error),
|
||||||
|
error.c_str());
|
||||||
|
Check(!graph.irreducible && graph.code_table_load_pcs.empty(),
|
||||||
|
"unreachable S_SETPC retained jump-table metadata or dispatcher mode");
|
||||||
|
|
||||||
|
ShaderRecompiler::IR::Program ir;
|
||||||
|
Check(ShaderRecompiler::IR::LowerProgram(decoded, graph, ShaderType::Compute,
|
||||||
|
64u, ir, &error),
|
||||||
|
error.c_str());
|
||||||
|
const auto scalar_loads =
|
||||||
|
std::ranges::count_if(ir.blocks, [](const auto &block) {
|
||||||
|
return std::ranges::any_of(block.instructions, [](const auto &inst) {
|
||||||
|
return inst.op == ShaderRecompiler::IR::Opcode::SLoadDword &&
|
||||||
|
inst.pc == 0x18u;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
Check(scalar_loads == 1u,
|
||||||
|
"unreachable S_SETPC metadata suppressed a reachable scalar load");
|
||||||
|
}
|
||||||
|
|
||||||
void TestNewShaderRecompilerSetpcDwordJumpTable() {
|
void TestNewShaderRecompilerSetpcDwordJumpTable() {
|
||||||
const uint32_t shader[] = {
|
const uint32_t shader[] = {
|
||||||
|
EncodeSopp(0x02, 1), // skip one unreachable block
|
||||||
|
EncodeSMovB32(100, 129), // unreachable
|
||||||
EncodeSop2(0x07, 106, 0, 130), // s_min_u32 vcc_lo, s0, 2
|
EncodeSop2(0x07, 106, 0, 130), // s_min_u32 vcc_lo, s0, 2
|
||||||
EncodeSop2(0x1e, 106, 106, 130), // s_lshl_b32 vcc_lo, vcc_lo, 2
|
EncodeSop2(0x1e, 106, 106, 130), // s_lshl_b32 vcc_lo, vcc_lo, 2
|
||||||
EncodeSop1(0x1f, 4, 0), // s_getpc_b64 s[4:5]
|
EncodeSop1(0x1f, 4, 0), // s_getpc_b64 s[4:5]
|
||||||
@@ -8311,13 +8384,16 @@ void TestNewShaderRecompilerSetpcDwordJumpTable() {
|
|||||||
Check(jump != result.program.values->block_info.end() &&
|
Check(jump != result.program.values->block_info.end() &&
|
||||||
jump->terminator.indirect_targets.size() == 2 &&
|
jump->terminator.indirect_targets.size() == 2 &&
|
||||||
jump->terminator.indirect_target_pcs ==
|
jump->terminator.indirect_target_pcs ==
|
||||||
std::vector<uint32_t>({0x30u, 0x38u}) &&
|
std::vector<uint32_t>({0x38u, 0x40u}) &&
|
||||||
jump->terminator.indirect_selector_values ==
|
jump->terminator.indirect_selector_values ==
|
||||||
std::vector<uint32_t>({0u, 4u, 8u}) &&
|
std::vector<uint32_t>({0u, 4u, 8u}) &&
|
||||||
jump->terminator.indirect_selector_targets.size() == 3 &&
|
jump->terminator.indirect_selector_targets.size() == 3 &&
|
||||||
block_pc(jump->terminator.indirect_selector_targets[0]) == 0x30u &&
|
block_pc(jump->terminator.indirect_selector_targets[0]) == 0x38u &&
|
||||||
block_pc(jump->terminator.indirect_selector_targets[1]) == 0x38u &&
|
block_pc(jump->terminator.indirect_selector_targets[1]) == 0x40u &&
|
||||||
block_pc(jump->terminator.indirect_selector_targets[2]) == 0x30u,
|
block_pc(jump->terminator.indirect_selector_targets[2]) == 0x38u &&
|
||||||
|
std::ranges::none_of(
|
||||||
|
result.program.values->block_info,
|
||||||
|
[](const auto &info) { return info.start_pc == 0x04u; }),
|
||||||
"subtractive S_SETPC_B64 table targets or selector mapping changed");
|
"subtractive S_SETPC_B64 table targets or selector mapping changed");
|
||||||
Check(!Common::ContainsStr(result.ir_dump, "SLoadDword"),
|
Check(!Common::ContainsStr(result.ir_dump, "SLoadDword"),
|
||||||
"subtractive S_SETPC_B64 table load reached normal IR");
|
"subtractive S_SETPC_B64 table load reached normal IR");
|
||||||
@@ -8689,6 +8765,210 @@ void TestValuePhiValidation() {
|
|||||||
};
|
};
|
||||||
validate_cfg(true);
|
validate_cfg(true);
|
||||||
validate_cfg(false);
|
validate_cfg(false);
|
||||||
|
{
|
||||||
|
ValueProgram program;
|
||||||
|
for (uint32_t id = 0; id < 2; id++) {
|
||||||
|
program.block_storage.push_back(std::make_unique<Block>());
|
||||||
|
program.blocks.push_back(program.block_storage.back().get());
|
||||||
|
program.block_info.push_back({.id = id});
|
||||||
|
}
|
||||||
|
program.block_info[0].terminator.kind =
|
||||||
|
ShaderRecompiler::CFG::TerminatorKind::Branch;
|
||||||
|
program.block_info[0].terminator.true_block = 1;
|
||||||
|
program.block_info[1].terminator.kind =
|
||||||
|
ShaderRecompiler::CFG::TerminatorKind::Branch;
|
||||||
|
program.block_info[1].terminator.true_block = 0;
|
||||||
|
program.blocks[0]->AddBranch(program.blocks[1]);
|
||||||
|
program.blocks[1]->AddBranch(program.blocks[0]);
|
||||||
|
std::string error;
|
||||||
|
Check(!ValidateValueProgram(program, true, &error) &&
|
||||||
|
Common::ContainsStr(error, "entry block has a predecessor"),
|
||||||
|
"value IR accepted a backedge to its synthetic entry block");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
ValueProgram program;
|
||||||
|
for (uint32_t id = 0; id < 3; id++) {
|
||||||
|
program.block_storage.push_back(std::make_unique<Block>());
|
||||||
|
program.blocks.push_back(program.block_storage.back().get());
|
||||||
|
program.block_info.push_back({.id = id});
|
||||||
|
}
|
||||||
|
program.block_info[0].terminator.kind =
|
||||||
|
ShaderRecompiler::CFG::TerminatorKind::Branch;
|
||||||
|
program.block_info[0].terminator.true_block = 1;
|
||||||
|
auto &indirect = program.block_info[1];
|
||||||
|
indirect.terminator.kind =
|
||||||
|
ShaderRecompiler::CFG::TerminatorKind::IndirectBranch;
|
||||||
|
indirect.terminator.indirect_targets = {1};
|
||||||
|
indirect.terminator.indirect_selector_values = {0};
|
||||||
|
indirect.terminator.indirect_selector_targets = {2};
|
||||||
|
indirect.indirect_target = Value(0u);
|
||||||
|
program.blocks[0]->AddBranch(program.blocks[1]);
|
||||||
|
program.blocks[1]->AddBranch(program.blocks[1]);
|
||||||
|
std::string error;
|
||||||
|
Check(!ValidateValueProgram(program, true, &error) &&
|
||||||
|
Common::ContainsStr(error,
|
||||||
|
"selector target is not a CFG successor"),
|
||||||
|
"value IR accepted an indirect selector target outside its target "
|
||||||
|
"set");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
ValueProgram program;
|
||||||
|
program.block_storage.push_back(std::make_unique<Block>());
|
||||||
|
program.blocks.push_back(program.block_storage.back().get());
|
||||||
|
program.block_info.push_back({.id = UINT32_MAX});
|
||||||
|
std::string error;
|
||||||
|
Check(!ValidateValueProgram(program, true, &error) &&
|
||||||
|
Common::ContainsStr(error, "reserved exit id"),
|
||||||
|
"value IR accepted the dispatcher exit sentinel as a block id");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
ValueProgram program;
|
||||||
|
for (uint32_t id = 0; id < 2; id++) {
|
||||||
|
program.block_storage.push_back(std::make_unique<Block>());
|
||||||
|
program.blocks.push_back(program.block_storage.back().get());
|
||||||
|
program.block_info.push_back({.id = id});
|
||||||
|
}
|
||||||
|
program.block_info[0].terminator.kind =
|
||||||
|
ShaderRecompiler::CFG::TerminatorKind::Branch;
|
||||||
|
program.block_info[0].terminator.true_block = 1;
|
||||||
|
auto &indirect = program.block_info[1];
|
||||||
|
indirect.terminator.kind =
|
||||||
|
ShaderRecompiler::CFG::TerminatorKind::IndirectBranch;
|
||||||
|
indirect.terminator.indirect_targets = {1, 1};
|
||||||
|
indirect.indirect_target = Value(0u);
|
||||||
|
program.blocks[0]->AddBranch(program.blocks[1]);
|
||||||
|
program.blocks[1]->AddBranch(program.blocks[1]);
|
||||||
|
std::string error;
|
||||||
|
Check(!ValidateValueProgram(program, true, &error) &&
|
||||||
|
Common::ContainsStr(error, "target is duplicated"),
|
||||||
|
"value IR accepted duplicate indirect branch targets");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
ValueProgram program;
|
||||||
|
for (uint32_t id = 0; id < 2; id++) {
|
||||||
|
program.block_storage.push_back(std::make_unique<Block>());
|
||||||
|
program.blocks.push_back(program.block_storage.back().get());
|
||||||
|
program.block_info.push_back({.id = id});
|
||||||
|
}
|
||||||
|
std::string error;
|
||||||
|
Check(!ValidateValueProgram(program, true, &error) &&
|
||||||
|
Common::ContainsStr(error, "unreachable block"),
|
||||||
|
"value IR accepted an unreachable block");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
ValueProgram program;
|
||||||
|
program.block_storage.push_back(std::make_unique<Block>());
|
||||||
|
auto *block = program.block_storage.back().get();
|
||||||
|
program.blocks.push_back(block);
|
||||||
|
program.block_info.push_back({.id = 0});
|
||||||
|
auto &use =
|
||||||
|
block->AppendNewInst(ValueOpcode::IAdd32, {Value(1u), Value(2u)});
|
||||||
|
auto &definition =
|
||||||
|
block->AppendNewInst(ValueOpcode::IAdd32, {Value(3u), Value(4u)});
|
||||||
|
use.SetArg(0, Value(&definition));
|
||||||
|
std::string error;
|
||||||
|
Check(!ValidateValueProgram(program, true, &error) &&
|
||||||
|
Common::ContainsStr(error, "same-block definition"),
|
||||||
|
"value IR accepted a same-block forward use");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
ValueProgram program;
|
||||||
|
for (uint32_t id = 0; id < 4; id++) {
|
||||||
|
program.block_storage.push_back(std::make_unique<Block>());
|
||||||
|
program.blocks.push_back(program.block_storage.back().get());
|
||||||
|
program.block_info.push_back({.id = id});
|
||||||
|
}
|
||||||
|
program.block_info[0].terminator.kind =
|
||||||
|
ShaderRecompiler::CFG::TerminatorKind::ConditionalBranch;
|
||||||
|
program.block_info[0].terminator.true_block = 1;
|
||||||
|
program.block_info[0].terminator.false_block = 2;
|
||||||
|
program.block_info[0].condition = Value(true);
|
||||||
|
for (uint32_t id : {1u, 2u}) {
|
||||||
|
program.block_info[id].terminator.kind =
|
||||||
|
ShaderRecompiler::CFG::TerminatorKind::Branch;
|
||||||
|
program.block_info[id].terminator.true_block = 3;
|
||||||
|
}
|
||||||
|
program.blocks[0]->AddBranch(program.blocks[1]);
|
||||||
|
program.blocks[0]->AddBranch(program.blocks[2]);
|
||||||
|
program.blocks[1]->AddBranch(program.blocks[3]);
|
||||||
|
program.blocks[2]->AddBranch(program.blocks[3]);
|
||||||
|
auto &definition = program.blocks[1]->AppendNewInst(ValueOpcode::IAdd32,
|
||||||
|
{Value(1u), Value(2u)});
|
||||||
|
program.blocks[2]->AppendNewInst(ValueOpcode::IAdd32,
|
||||||
|
{Value(&definition), Value(3u)});
|
||||||
|
std::string error;
|
||||||
|
Check(!ValidateValueProgram(program, true, &error) &&
|
||||||
|
Common::ContainsStr(error, "does not dominate its use"),
|
||||||
|
"value IR accepted a non-dominating ordinary definition");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
ValueProgram program;
|
||||||
|
for (uint32_t id = 0; id < 4; id++) {
|
||||||
|
program.block_storage.push_back(std::make_unique<Block>());
|
||||||
|
program.blocks.push_back(program.block_storage.back().get());
|
||||||
|
program.block_info.push_back({.id = id});
|
||||||
|
}
|
||||||
|
program.block_info[0].terminator.kind =
|
||||||
|
ShaderRecompiler::CFG::TerminatorKind::ConditionalBranch;
|
||||||
|
program.block_info[0].terminator.true_block = 1;
|
||||||
|
program.block_info[0].terminator.false_block = 2;
|
||||||
|
program.block_info[0].condition = Value(true);
|
||||||
|
for (uint32_t id : {1u, 2u}) {
|
||||||
|
program.block_info[id].terminator.kind =
|
||||||
|
ShaderRecompiler::CFG::TerminatorKind::Branch;
|
||||||
|
program.block_info[id].terminator.true_block = 3;
|
||||||
|
}
|
||||||
|
program.blocks[0]->AddBranch(program.blocks[1]);
|
||||||
|
program.blocks[0]->AddBranch(program.blocks[2]);
|
||||||
|
program.blocks[1]->AddBranch(program.blocks[3]);
|
||||||
|
program.blocks[2]->AddBranch(program.blocks[3]);
|
||||||
|
auto &definition = program.blocks[1]->AppendNewInst(ValueOpcode::IAdd32,
|
||||||
|
{Value(1u), Value(2u)});
|
||||||
|
auto &phi = program.blocks[3]->AppendNewInst(ValueOpcode::Phi);
|
||||||
|
phi.SetFlags(Type::U32);
|
||||||
|
phi.AddPhiOperand(program.blocks[1], Value(3u));
|
||||||
|
phi.AddPhiOperand(program.blocks[2], Value(&definition));
|
||||||
|
std::string error;
|
||||||
|
Check(!ValidateValueProgram(program, true, &error) &&
|
||||||
|
Common::ContainsStr(error, "does not dominate its edge"),
|
||||||
|
"value IR accepted an unavailable Phi-edge definition");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
ValueProgram program;
|
||||||
|
for (uint32_t id = 0; id < 5; id++) {
|
||||||
|
program.block_storage.push_back(std::make_unique<Block>());
|
||||||
|
program.blocks.push_back(program.block_storage.back().get());
|
||||||
|
program.block_info.push_back({.id = id});
|
||||||
|
}
|
||||||
|
program.block_info[0].terminator.kind =
|
||||||
|
ShaderRecompiler::CFG::TerminatorKind::ConditionalBranch;
|
||||||
|
program.block_info[0].terminator.true_block = 1;
|
||||||
|
program.block_info[0].terminator.false_block = 2;
|
||||||
|
program.block_info[0].condition = Value(true);
|
||||||
|
program.block_info[1].terminator.kind =
|
||||||
|
ShaderRecompiler::CFG::TerminatorKind::Branch;
|
||||||
|
program.block_info[1].terminator.true_block = 3;
|
||||||
|
program.block_info[2].terminator.kind =
|
||||||
|
ShaderRecompiler::CFG::TerminatorKind::ConditionalBranch;
|
||||||
|
program.block_info[2].terminator.true_block = 3;
|
||||||
|
program.block_info[2].terminator.false_block = 4;
|
||||||
|
program.block_info[3].terminator.kind =
|
||||||
|
ShaderRecompiler::CFG::TerminatorKind::Branch;
|
||||||
|
program.block_info[3].terminator.true_block = 4;
|
||||||
|
program.blocks[0]->AddBranch(program.blocks[1]);
|
||||||
|
program.blocks[0]->AddBranch(program.blocks[2]);
|
||||||
|
program.blocks[1]->AddBranch(program.blocks[3]);
|
||||||
|
program.blocks[2]->AddBranch(program.blocks[3]);
|
||||||
|
program.blocks[2]->AddBranch(program.blocks[4]);
|
||||||
|
program.blocks[3]->AddBranch(program.blocks[4]);
|
||||||
|
auto &condition = program.blocks[1]->AppendNewInst(ValueOpcode::IEqual32,
|
||||||
|
{Value(1u), Value(2u)});
|
||||||
|
program.block_info[2].condition = Value(&condition);
|
||||||
|
std::string error;
|
||||||
|
Check(!ValidateValueProgram(program, true, &error) &&
|
||||||
|
Common::ContainsStr(error, "branch condition definition"),
|
||||||
|
"value IR accepted a non-dominating branch condition");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestWqmConstantPropagation() {
|
void TestWqmConstantPropagation() {
|
||||||
@@ -10996,23 +11276,25 @@ void TestNewShaderRecompilerSpirvSizeBaselines() {
|
|||||||
EncodeSopp(0x01),
|
EncodeSopp(0x01),
|
||||||
};
|
};
|
||||||
const auto dispatcher_result = compile("dispatcher", dispatcher,
|
const auto dispatcher_result = compile("dispatcher", dispatcher,
|
||||||
{.words = 258,
|
{.words = 242,
|
||||||
.instructions = 78,
|
.instructions = 67,
|
||||||
.variables = 4,
|
.variables = 3,
|
||||||
.function_variables = 4,
|
.function_variables = 3,
|
||||||
.loads = 4,
|
.loads = 3,
|
||||||
.stores = 14,
|
.stores = 6,
|
||||||
.labels = 14,
|
.phis = 2,
|
||||||
|
.labels = 13,
|
||||||
.loop_merges = 1,
|
.loop_merges = 1,
|
||||||
.selection_merges = 1,
|
.selection_merges = 1,
|
||||||
.branches = 11,
|
.branches = 10,
|
||||||
.conditional_branches = 1,
|
.conditional_branches = 1,
|
||||||
.switches = 1});
|
.switches = 1});
|
||||||
Check(dispatcher_result.program.dispatcher_fallback &&
|
Check(dispatcher_result.program.dispatcher_fallback &&
|
||||||
Common::ContainsStr(dispatcher_result.ir_dump, "Phi") &&
|
Common::ContainsStr(dispatcher_result.ir_dump, "Phi") &&
|
||||||
SpirvInstructionOpcodeCount(dispatcher_result.spirv, 245u) == 0u &&
|
SpirvInstructionOpcodeCount(dispatcher_result.spirv, 245u) == 2u &&
|
||||||
SpirvInstructionOpcodeCount(dispatcher_result.spirv, 251u) == 1u,
|
SpirvInstructionOpcodeCount(dispatcher_result.spirv, 251u) == 1u,
|
||||||
"dispatcher size fixture no longer exercises whole-function dispatch");
|
"dispatcher size fixture lost its two control Phis or switch");
|
||||||
|
CheckSpirvPhiParents(dispatcher_result.spirv);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
@@ -11078,7 +11360,7 @@ int main() {
|
|||||||
TestNewShaderRecompilerCfgLoopSharedRegion();
|
TestNewShaderRecompilerCfgLoopSharedRegion();
|
||||||
TestNewShaderRecompilerCfgOverlappingEarlyExitLadder();
|
TestNewShaderRecompilerCfgOverlappingEarlyExitLadder();
|
||||||
TestNewShaderRecompilerCfgNestedEarlyExitSharedTerminal();
|
TestNewShaderRecompilerCfgNestedEarlyExitSharedTerminal();
|
||||||
TestNewShaderRecompilerCfgExternallyEnteredSelectionDispatcher();
|
TestNewShaderRecompilerCfgPrunesUnreachableSelectionEntry();
|
||||||
TestNewShaderRecompilerCfgIrreducibleDispatcher();
|
TestNewShaderRecompilerCfgIrreducibleDispatcher();
|
||||||
TestNewShaderRecompilerDispatcherSpillsU32x3();
|
TestNewShaderRecompilerDispatcherSpillsU32x3();
|
||||||
TestNewShaderRecompilerU64PairLowering();
|
TestNewShaderRecompilerU64PairLowering();
|
||||||
@@ -11089,6 +11371,7 @@ int main() {
|
|||||||
TestNewShaderRecompilerBranchConditionForms();
|
TestNewShaderRecompilerBranchConditionForms();
|
||||||
TestNewShaderRecompilerSetpcBranch();
|
TestNewShaderRecompilerSetpcBranch();
|
||||||
TestNewShaderRecompilerSetpcJumpTable();
|
TestNewShaderRecompilerSetpcJumpTable();
|
||||||
|
TestNewShaderRecompilerPrunesUnreachableSetpcMetadata();
|
||||||
TestNewShaderRecompilerSetpcDwordJumpTable();
|
TestNewShaderRecompilerSetpcDwordJumpTable();
|
||||||
TestTypedEntryStateIsMinimal();
|
TestTypedEntryStateIsMinimal();
|
||||||
TestWqmConstantPropagation();
|
TestWqmConstantPropagation();
|
||||||
|
|||||||
Reference in New Issue
Block a user