mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-20 19:07:01 +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";
|
||||
dump += CFG::GraphToString(cfg);
|
||||
dump += "\nIR:\n";
|
||||
dump += fmt::format("mode={}\n", ir.dispatcher_fallback ? "dispatcher" : "structured");
|
||||
dump += ir.values != nullptr ? IR::ValueProgramToString(*ir.values) : IR::ProgramToString(ir);
|
||||
return dump;
|
||||
}
|
||||
|
||||
@@ -380,18 +380,18 @@ struct ValueEmitContext {
|
||||
const IR::MemoryInfo& Memory(const IR::Inst& inst) const;
|
||||
const IR::ExportInfo& Export(const IR::Inst& inst) 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);
|
||||
|
||||
EmitterState& state;
|
||||
const IR::ValueProgram& program;
|
||||
std::unordered_map<const IR::Inst*, uint32_t> definitions;
|
||||
std::unordered_map<const IR::Block*, uint32_t> labels;
|
||||
const std::unordered_map<const IR::Inst*, uint32_t>* dispatcher_spills = nullptr;
|
||||
const IR::Block* current_block = nullptr;
|
||||
uint32_t scratch_u32_variable = 0;
|
||||
bool failed = false;
|
||||
std::string error;
|
||||
EmitterState& state;
|
||||
const IR::ValueProgram& program;
|
||||
std::unordered_map<const IR::Inst*, uint32_t> definitions;
|
||||
std::unordered_map<const IR::Block*, uint32_t> labels;
|
||||
const std::unordered_map<const IR::Inst*, uint32_t>* dispatcher_spills = nullptr;
|
||||
std::unordered_map<const IR::Inst*, std::pair<uint32_t, uint32_t>> dispatcher_block_loads;
|
||||
const IR::Block* current_block = nullptr;
|
||||
uint32_t scratch_u32_variable = 0;
|
||||
bool failed = false;
|
||||
std::string error;
|
||||
};
|
||||
|
||||
enum class VertexInputScalarKind { Float, Sint, Uint };
|
||||
|
||||
@@ -36,24 +36,9 @@ void EmitKillIfPixelValidMaskInactive(EmitterState& state) {
|
||||
EmitKillIfBoolFalse(state, active);
|
||||
}
|
||||
|
||||
uint32_t SpillPointerType(EmitterState& state, IR::Type type) {
|
||||
switch (type) {
|
||||
case IR::Type::U1: return TypePointer(state, StorageClassFunction, TypeBool(state));
|
||||
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;
|
||||
}
|
||||
uint32_t SpillPointerType(ValueEmitContext& ctx, IR::Type type) {
|
||||
const auto value_type = ctx.TypeId(type);
|
||||
return value_type == 0 ? 0 : TypePointer(ctx.state, StorageClassFunction, value_type);
|
||||
}
|
||||
|
||||
struct DeferredPhiPatch {
|
||||
@@ -68,10 +53,8 @@ struct StructuredFunctionState {
|
||||
|
||||
struct DispatcherFunctionState {
|
||||
std::unordered_map<const IR::Inst*, uint32_t> spills;
|
||||
uint32_t pc_variable = 0;
|
||||
uint32_t header_label = 0;
|
||||
uint32_t select_label = 0;
|
||||
uint32_t default_label = 0;
|
||||
uint32_t after_switch_label = 0;
|
||||
uint32_t continue_label = 0;
|
||||
uint32_t merge_label = 0;
|
||||
@@ -166,16 +149,13 @@ void EmitDispatcherTarget(ValueEmitContext& ctx, const DispatcherFunctionState&
|
||||
}
|
||||
}
|
||||
|
||||
void EmitDispatcherTerminator(ValueEmitContext& ctx, const DispatcherFunctionState& dispatcher,
|
||||
const IR::Block* block, const IR::ValueBlockInfo& info,
|
||||
uint32_t exit_label) {
|
||||
uint32_t EmitDispatcherNextPc(ValueEmitContext& ctx, const DispatcherFunctionState& dispatcher,
|
||||
const IR::Block* block, const IR::ValueBlockInfo& info) {
|
||||
const auto& term = info.terminator;
|
||||
switch (term.kind) {
|
||||
case CFG::TerminatorKind::Branch:
|
||||
EmitDispatcherTarget(ctx, dispatcher, block, term.true_block);
|
||||
ctx.state.builder.AddFunction(
|
||||
{OpStore, dispatcher.pc_variable, ConstantU32(ctx.state, term.true_block)});
|
||||
break;
|
||||
return ConstantU32(ctx.state, term.true_block);
|
||||
case CFG::TerminatorKind::ConditionalBranch: {
|
||||
EmitDispatcherTarget(ctx, dispatcher, block, term.true_block);
|
||||
EmitDispatcherTarget(ctx, dispatcher, block, term.false_block);
|
||||
@@ -184,16 +164,12 @@ void EmitDispatcherTerminator(ValueEmitContext& ctx, const DispatcherFunctionSta
|
||||
ctx.Def(info.condition),
|
||||
ConstantU32(ctx.state, term.true_block),
|
||||
ConstantU32(ctx.state, term.false_block)});
|
||||
ctx.state.builder.AddFunction({OpStore, dispatcher.pc_variable, selected});
|
||||
break;
|
||||
return selected;
|
||||
}
|
||||
case CFG::TerminatorKind::IndirectBranch: {
|
||||
for (const auto target: term.indirect_targets) {
|
||||
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);
|
||||
if (!info.indirect_target.IsEmpty()) {
|
||||
const auto selector = ctx.Def(info.indirect_target);
|
||||
@@ -214,15 +190,10 @@ void EmitDispatcherTerminator(ValueEmitContext& ctx, const DispatcherFunctionSta
|
||||
selected = next;
|
||||
}
|
||||
}
|
||||
ctx.state.builder.AddFunction({OpStore, dispatcher.pc_variable, selected});
|
||||
break;
|
||||
return selected;
|
||||
}
|
||||
default:
|
||||
ctx.state.builder.AddFunction(
|
||||
{OpStore, dispatcher.pc_variable, ConstantU32(ctx.state, UINT32_MAX)});
|
||||
break;
|
||||
default: return ConstantU32(ctx.state, UINT32_MAX);
|
||||
}
|
||||
ctx.state.builder.AddFunction({OpBranch, exit_label});
|
||||
}
|
||||
|
||||
bool EmitDirectInstruction(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
@@ -346,12 +317,16 @@ void EmitDispatcherFunction(ValueEmitContext& ctx, const DispatcherFunctionState
|
||||
if (ctx.failed) {
|
||||
return;
|
||||
}
|
||||
EmitDispatcherTerminator(ctx, dispatcher, entry, ctx.program.block_info.front(),
|
||||
dispatcher.header_label);
|
||||
const auto initial_pc =
|
||||
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);
|
||||
const auto pc = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpLoad, TypeU32(state), pc, dispatcher.pc_variable});
|
||||
const auto pc = state.builder.AllocateId();
|
||||
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();
|
||||
state.builder.AddFunction(
|
||||
{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);
|
||||
state.builder.AddFunction(
|
||||
{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++) {
|
||||
words.push_back(ctx.program.block_info[index].id);
|
||||
words.push_back(ctx.Label(ctx.program.blocks[index]));
|
||||
}
|
||||
state.builder.AddFunction(words);
|
||||
EmitLabel(state, dispatcher.default_label);
|
||||
state.builder.AddFunction(
|
||||
{OpStore, dispatcher.pc_variable, ConstantU32(ctx.state, UINT32_MAX)});
|
||||
state.builder.AddFunction({OpBranch, dispatcher.after_switch_label});
|
||||
std::vector<uint32_t> next_pc_words {OpPhi, TypeU32(state), next_pc,
|
||||
ConstantU32(state, UINT32_MAX), dispatcher.select_label};
|
||||
|
||||
for (size_t index = 1; index < ctx.program.blocks.size() && !ctx.failed; index++) {
|
||||
EmitBlock(ctx, ctx.program.blocks[index], [&](const IR::Inst& inst) {
|
||||
@@ -381,13 +354,17 @@ void EmitDispatcherFunction(ValueEmitContext& ctx, const DispatcherFunctionState
|
||||
if (ctx.failed) {
|
||||
break;
|
||||
}
|
||||
EmitDispatcherTerminator(ctx, dispatcher, ctx.program.blocks[index],
|
||||
ctx.program.block_info[index], dispatcher.after_switch_label);
|
||||
const auto selected = EmitDispatcherNextPc(ctx, dispatcher, ctx.program.blocks[index],
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
EmitLabel(state, dispatcher.after_switch_label);
|
||||
state.builder.AddFunction(next_pc_words);
|
||||
state.builder.AddFunction({OpBranch, dispatcher.continue_label});
|
||||
EmitLabel(state, dispatcher.continue_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 &&
|
||||
inst->Parent() != current_block) {
|
||||
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();
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -515,10 +498,6 @@ uint32_t ValueEmitContext::Label(const IR::Block* block) const {
|
||||
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) {
|
||||
failed = true;
|
||||
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) {
|
||||
ctx.labels.emplace(block, state.builder.AllocateId());
|
||||
}
|
||||
if (program.dispatcher_fallback) {
|
||||
if (state.program.dispatcher_fallback) {
|
||||
auto& dispatch = dispatcher.emplace();
|
||||
for (const auto* block: program.blocks) {
|
||||
for (const auto& inst: *block) {
|
||||
if (inst.GetOpcode() != IR::ValueOpcode::Phi) {
|
||||
continue;
|
||||
}
|
||||
if (SpillPointerType(state, inst.GetType()) == 0) {
|
||||
if (SpillPointerType(ctx, inst.GetType()) == 0) {
|
||||
ctx.Fail(inst, "cannot be stored by the dispatcher");
|
||||
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) {
|
||||
value = value.Resolve();
|
||||
const auto* definition = value.TryInstruction();
|
||||
if (definition == nullptr || definition->Parent() == consumer) {
|
||||
if (definition == nullptr || definition->Parent() == consumer ||
|
||||
definition->Parent() == program.blocks.front()) {
|
||||
return;
|
||||
}
|
||||
if (SpillPointerType(state, definition->GetType()) == 0) {
|
||||
if (SpillPointerType(ctx, definition->GetType()) == 0) {
|
||||
ctx.Fail(*definition, "cannot be stored by the dispatcher");
|
||||
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].indirect_target, program.blocks[index]);
|
||||
}
|
||||
dispatch.pc_variable = state.builder.AllocateId();
|
||||
dispatch.header_label = state.builder.AllocateId();
|
||||
dispatch.select_label = state.builder.AllocateId();
|
||||
dispatch.default_label = state.builder.AllocateId();
|
||||
dispatch.after_switch_label = state.builder.AllocateId();
|
||||
dispatch.continue_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)),
|
||||
state.pixel_valid_mask_variable, StorageClassFunction});
|
||||
}
|
||||
if (program.dispatcher_fallback) {
|
||||
state.builder.AddFunction({OpVariable,
|
||||
TypePointer(state, StorageClassFunction, TypeU32(state)),
|
||||
dispatcher->pc_variable, StorageClassFunction});
|
||||
if (state.program.dispatcher_fallback) {
|
||||
for (const auto* block: program.blocks) {
|
||||
for (const auto& inst: *block) {
|
||||
if (const auto found = dispatcher->spills.find(&inst);
|
||||
found != dispatcher->spills.end()) {
|
||||
state.builder.AddFunction({OpVariable, SpillPointerType(state, inst.GetType()),
|
||||
state.builder.AddFunction({OpVariable, SpillPointerType(ctx, inst.GetType()),
|
||||
found->second, StorageClassFunction});
|
||||
}
|
||||
}
|
||||
@@ -641,7 +616,7 @@ bool EmitValueProgram(EmitterState& state, const IR::ValueProgram& program, std:
|
||||
EmitStorageBufferOffsets(state);
|
||||
if (program.blocks.empty()) {
|
||||
EmitReturn(ctx);
|
||||
} else if (program.dispatcher_fallback) {
|
||||
} else if (state.program.dispatcher_fallback) {
|
||||
EmitDispatcherFunction(ctx, *dispatcher);
|
||||
} else {
|
||||
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) {
|
||||
const auto count = static_cast<uint32_t>(graph.blocks.size());
|
||||
const auto all = AllBlockIds(count);
|
||||
@@ -1820,7 +1880,6 @@ bool BuildGraph(const Decoder::Program& program, Graph& graph, std::string* erro
|
||||
labels.insert(end_pc);
|
||||
|
||||
std::map<uint32_t, SetpcTargetInfo> setpc_targets;
|
||||
bool indirect_setpc = false;
|
||||
for (uint32_t i = 0; i < program.instructions.size(); i++) {
|
||||
const auto& inst = program.instructions[i];
|
||||
const auto next_pc = InstructionEndPc(inst);
|
||||
@@ -1857,7 +1916,6 @@ bool BuildGraph(const Decoder::Program& program, Graph& graph, std::string* erro
|
||||
}
|
||||
labels.insert(target);
|
||||
}
|
||||
indirect_setpc = indirect_setpc || target_info.indirect;
|
||||
setpc_targets.emplace(inst.pc, std::move(target_info));
|
||||
if (next_pc <= end_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(
|
||||
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 {
|
||||
block.terminator.kind = TerminatorKind::Branch;
|
||||
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) {
|
||||
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);
|
||||
|
||||
ComputeDominators(graph);
|
||||
|
||||
@@ -758,7 +758,6 @@ bool TranslateProgram(const IR::Program& source, IR::ValueProgram& result,
|
||||
const ShaderPixelInputInfo* pixel_input_info,
|
||||
const ShaderComputeInputInfo* compute_input_info, std::string* error) {
|
||||
result = {};
|
||||
result.dispatcher_fallback = source.dispatcher_fallback;
|
||||
const uint32_t wave_size = source.wave_size;
|
||||
const bool per_invocation_masks = source.lane_mask_mode == ShaderLaneMaskMode::PerInvocation;
|
||||
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);
|
||||
}
|
||||
|
||||
program = {};
|
||||
program.stage = stage;
|
||||
program.wave_size = wave_size;
|
||||
program.dispatcher_fallback = cfg.irreducible;
|
||||
program.cfg_failure_kind = cfg.failure_kind;
|
||||
program.fallback_reason = cfg.unsupported_reason;
|
||||
program = {};
|
||||
program.stage = stage;
|
||||
program.wave_size = wave_size;
|
||||
program.cfg_failure_kind = cfg.failure_kind;
|
||||
program.fallback_reason = cfg.unsupported_reason;
|
||||
program.blocks.clear();
|
||||
|
||||
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");
|
||||
}
|
||||
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_set<const Inst*> instructions;
|
||||
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) {
|
||||
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) {
|
||||
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++) {
|
||||
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");
|
||||
}
|
||||
break;
|
||||
case CFG::TerminatorKind::IndirectBranch:
|
||||
case CFG::TerminatorKind::IndirectBranch: {
|
||||
if (!validate_control_value(program.block_info[block_index].indirect_target,
|
||||
Type::U32)) {
|
||||
return Fail(error, "value IR indirect branch selector is invalid");
|
||||
}
|
||||
std::unordered_set<uint32_t> 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)) {
|
||||
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;
|
||||
}
|
||||
case CFG::TerminatorKind::Return:
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -472,8 +579,7 @@ std::string ValueProgramToString(const ValueProgram& program) {
|
||||
}
|
||||
};
|
||||
|
||||
std::string text =
|
||||
fmt::format("mode={}\n", program.dispatcher_fallback ? "dispatcher" : "structured");
|
||||
std::string text;
|
||||
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,
|
||||
program.block_info[block_index].start_pc,
|
||||
|
||||
@@ -65,7 +65,6 @@ struct ValueProgram {
|
||||
std::vector<DescriptorSource> descriptor_sources;
|
||||
std::vector<SrtRead> srt_reads;
|
||||
std::vector<Value> dynamic_reads;
|
||||
bool dispatcher_fallback = false;
|
||||
};
|
||||
|
||||
bool ValidateValueProgram(const ValueProgram& program, bool require_ssa, std::string* error);
|
||||
|
||||
Reference in New Issue
Block a user