mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-20 10:56:58 +02:00
shader: own control flow on IR blocks with direct targets
This commit is contained in:
@@ -83,44 +83,31 @@ void StoreDispatcherPhiEdge(ValueEmitContext& ctx, const DispatcherFunctionState
|
||||
}
|
||||
}
|
||||
|
||||
const IR::Block* TargetBlock(const IR::Program& program, uint32_t id) {
|
||||
const auto found = std::ranges::find_if(
|
||||
program.block_info, [&](const IR::BlockInfo& info) { return info.id == id; });
|
||||
if (found == program.block_info.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
return program.blocks[static_cast<size_t>(found - program.block_info.begin())];
|
||||
}
|
||||
|
||||
void EmitReturn(ValueEmitContext& ctx) {
|
||||
EmitKillIfPixelValidMaskInactive(ctx.state);
|
||||
ctx.state.builder.AddFunction(spv::OpReturn);
|
||||
}
|
||||
|
||||
void EmitStructuredTerminator(ValueEmitContext& ctx, const IR::Block* block,
|
||||
const IR::BlockInfo& info) {
|
||||
const auto& program = ctx.state.program;
|
||||
const auto& term = info.terminator;
|
||||
void EmitStructuredTerminator(ValueEmitContext& ctx, const IR::Block* block) {
|
||||
const auto& term = block->terminator;
|
||||
const auto emit_merge = [&]() {
|
||||
if (term.loop_header) {
|
||||
const auto* merge = TargetBlock(program, term.merge_block);
|
||||
const auto* cont = TargetBlock(program, term.continue_block);
|
||||
const auto* merge = term.merge_block;
|
||||
const auto* cont = term.continue_block;
|
||||
if (merge != nullptr && cont != nullptr) {
|
||||
ctx.state.builder.AddFunction(spv::OpLoopMerge, merge->Definition(),
|
||||
cont->Definition(), spv::LoopControlMaskNone);
|
||||
}
|
||||
} else if (term.kind == CFG::TerminatorKind::ConditionalBranch &&
|
||||
term.merge_block != UINT32_MAX) {
|
||||
if (const auto* merge = TargetBlock(program, term.merge_block); merge != nullptr) {
|
||||
ctx.state.builder.AddFunction(spv::OpSelectionMerge, merge->Definition(),
|
||||
spv::SelectionControlMaskNone);
|
||||
}
|
||||
term.merge_block != nullptr) {
|
||||
ctx.state.builder.AddFunction(spv::OpSelectionMerge, term.merge_block->Definition(),
|
||||
spv::SelectionControlMaskNone);
|
||||
}
|
||||
};
|
||||
|
||||
switch (term.kind) {
|
||||
case CFG::TerminatorKind::Branch: {
|
||||
const auto* target = TargetBlock(program, term.true_block);
|
||||
const auto* target = term.true_block;
|
||||
if (target == nullptr) {
|
||||
EmitReturn(ctx);
|
||||
return;
|
||||
@@ -130,13 +117,13 @@ void EmitStructuredTerminator(ValueEmitContext& ctx, const IR::Block* block,
|
||||
return;
|
||||
}
|
||||
case CFG::TerminatorKind::ConditionalBranch: {
|
||||
const auto* true_block = TargetBlock(program, term.true_block);
|
||||
const auto* false_block = TargetBlock(program, term.false_block);
|
||||
if (true_block == nullptr || false_block == nullptr || info.condition.IsEmpty()) {
|
||||
const auto* true_block = term.true_block;
|
||||
const auto* false_block = term.false_block;
|
||||
if (true_block == nullptr || false_block == nullptr || block->condition.IsEmpty()) {
|
||||
EmitReturn(ctx);
|
||||
return;
|
||||
}
|
||||
const auto condition = ctx.Def(info.condition);
|
||||
const auto condition = ctx.Def(block->condition);
|
||||
emit_merge();
|
||||
ctx.state.builder.AddFunction(spv::OpBranchConditional, condition,
|
||||
true_block->Definition(), false_block->Definition());
|
||||
@@ -147,8 +134,7 @@ void EmitStructuredTerminator(ValueEmitContext& ctx, const IR::Block* block,
|
||||
}
|
||||
|
||||
void EmitDispatcherTarget(ValueEmitContext& ctx, const DispatcherFunctionState& dispatcher,
|
||||
const IR::Block* from, uint32_t target) {
|
||||
const auto* block = TargetBlock(ctx.state.program, target);
|
||||
const IR::Block* from, const IR::Block* block) {
|
||||
if (block != nullptr) {
|
||||
StoreDispatcherPhiEdge(ctx, dispatcher, from, block);
|
||||
if (ctx.other_half != nullptr) {
|
||||
@@ -158,41 +144,37 @@ void EmitDispatcherTarget(ValueEmitContext& ctx, const DispatcherFunctionState&
|
||||
}
|
||||
|
||||
uint32_t EmitDispatcherNextPc(ValueEmitContext& ctx, const DispatcherFunctionState& dispatcher,
|
||||
const IR::Block* block, const IR::BlockInfo& info) {
|
||||
const auto& term = info.terminator;
|
||||
const IR::Block* block) {
|
||||
const auto& term = block->terminator;
|
||||
switch (term.kind) {
|
||||
case CFG::TerminatorKind::Branch:
|
||||
EmitDispatcherTarget(ctx, dispatcher, block, term.true_block);
|
||||
return ConstantU32(ctx.state, term.true_block);
|
||||
return ConstantU32(ctx.state, term.true_block->id);
|
||||
case CFG::TerminatorKind::ConditionalBranch: {
|
||||
EmitDispatcherTarget(ctx, dispatcher, block, term.true_block);
|
||||
EmitDispatcherTarget(ctx, dispatcher, block, term.false_block);
|
||||
const auto selected = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(
|
||||
spv::OpSelect, TypeU32(ctx.state), selected, ctx.Def(info.condition),
|
||||
ConstantU32(ctx.state, term.true_block), ConstantU32(ctx.state, term.false_block));
|
||||
ctx.state.builder.AddFunction(spv::OpSelect, TypeU32(ctx.state), selected,
|
||||
ctx.Def(block->condition),
|
||||
ConstantU32(ctx.state, term.true_block->id),
|
||||
ConstantU32(ctx.state, term.false_block->id));
|
||||
return selected;
|
||||
}
|
||||
case CFG::TerminatorKind::IndirectBranch: {
|
||||
for (const auto target: term.indirect_targets) {
|
||||
for (const auto* target: block->ImmSuccessors()) {
|
||||
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);
|
||||
const auto& values = term.indirect_selector_code != UINT32_MAX
|
||||
? term.indirect_selector_values
|
||||
: term.indirect_target_pcs;
|
||||
const auto& targets = term.indirect_selector_code != UINT32_MAX
|
||||
? term.indirect_selector_targets
|
||||
: term.indirect_targets;
|
||||
for (size_t index = 0; index < std::min(values.size(), targets.size()); index++) {
|
||||
if (!block->indirect_target.IsEmpty()) {
|
||||
const auto selector = ctx.Def(block->indirect_target);
|
||||
for (const auto& branch: term.cases) {
|
||||
const auto match = ctx.state.builder.AllocateId();
|
||||
const auto next = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(spv::OpIEqual, TypeBool(ctx.state), match,
|
||||
selector, ConstantU32(ctx.state, values[index]));
|
||||
selector, ConstantU32(ctx.state, branch.value));
|
||||
ctx.state.builder.AddFunction(spv::OpSelect, TypeU32(ctx.state), next, match,
|
||||
ConstantU32(ctx.state, targets[index]), selected);
|
||||
ConstantU32(ctx.state, branch.target->id),
|
||||
selected);
|
||||
selected = next;
|
||||
}
|
||||
}
|
||||
@@ -349,7 +331,7 @@ void EmitStructuredFunction(ValueEmitContext& ctx) {
|
||||
EmitStructuredInstruction(lane, structured, inst);
|
||||
});
|
||||
structured.block_exit_labels.emplace(block, ctx.state.current_label);
|
||||
EmitStructuredTerminator(ctx, block, program.block_info[index]);
|
||||
EmitStructuredTerminator(ctx, block);
|
||||
}
|
||||
PatchStructuredPhis(ctx, structured);
|
||||
}
|
||||
@@ -361,8 +343,7 @@ void EmitDispatcherFunction(ValueEmitContext& ctx, const DispatcherFunctionState
|
||||
EmitBlock(ctx, entry, [&](ValueEmitContext& lane, const IR::Inst& inst) {
|
||||
EmitDispatcherInstruction(lane, dispatcher, inst);
|
||||
});
|
||||
const auto initial_pc =
|
||||
EmitDispatcherNextPc(ctx, dispatcher, entry, state.program.block_info.front());
|
||||
const auto initial_pc = EmitDispatcherNextPc(ctx, dispatcher, entry);
|
||||
const auto initial_parent = state.current_label;
|
||||
state.builder.AddFunction(spv::OpBranch, dispatcher.header_label);
|
||||
|
||||
@@ -384,7 +365,7 @@ void EmitDispatcherFunction(ValueEmitContext& ctx, const DispatcherFunctionState
|
||||
spv::SelectionControlMaskNone);
|
||||
std::vector<uint32_t> words {spv::OpSwitch, pc, dispatcher.after_switch_label};
|
||||
for (size_t index = 1; index < state.program.blocks.size(); index++) {
|
||||
words.push_back(state.program.block_info[index].id);
|
||||
words.push_back(state.program.blocks[index]->id);
|
||||
words.push_back(state.program.blocks[index]->Definition());
|
||||
}
|
||||
state.builder.AddFunction(words);
|
||||
@@ -396,8 +377,7 @@ void EmitDispatcherFunction(ValueEmitContext& ctx, const DispatcherFunctionState
|
||||
[&](ValueEmitContext& lane, const IR::Inst& inst) {
|
||||
EmitDispatcherInstruction(lane, dispatcher, inst);
|
||||
});
|
||||
const auto selected = EmitDispatcherNextPc(ctx, dispatcher, state.program.blocks[index],
|
||||
state.program.block_info[index]);
|
||||
const auto selected = EmitDispatcherNextPc(ctx, dispatcher, state.program.blocks[index]);
|
||||
next_pc_words.push_back(selected);
|
||||
next_pc_words.push_back(state.current_label);
|
||||
state.builder.AddFunction(spv::OpBranch, dispatcher.after_switch_label);
|
||||
@@ -668,8 +648,8 @@ void EmitProgram(EmitterState& state) {
|
||||
}
|
||||
}
|
||||
for (size_t index = 0; index < program.blocks.size(); 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.blocks[index]->condition, program.blocks[index]);
|
||||
mark_cross_block(program.blocks[index]->indirect_target, program.blocks[index]);
|
||||
}
|
||||
dispatch.header_label = state.builder.AllocateId();
|
||||
dispatch.select_label = state.builder.AllocateId();
|
||||
|
||||
@@ -735,7 +735,7 @@ void Translator::WriteCompareResult(const Decoder::Operand& operand, IR::U1 valu
|
||||
}
|
||||
|
||||
void Translator::AddBranchCondition(const CFG::Graph& graph, const CFG::BasicBlock& source,
|
||||
IR::BlockInfo& info) {
|
||||
IR::Block& info) {
|
||||
const auto native_condition = [&](CFG::BranchCondition kind) -> IR::U1 {
|
||||
IR::U1 condition;
|
||||
switch (kind) {
|
||||
@@ -1023,45 +1023,67 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
|
||||
|
||||
result.block_storage.reserve(cfg.blocks.size() + 1u);
|
||||
result.blocks.reserve(cfg.blocks.size() + 1u);
|
||||
result.block_info.reserve(cfg.blocks.size() + 1u);
|
||||
const auto max_id = std::ranges::max_element(cfg.blocks, {}, [](const CFG::BasicBlock& block) {
|
||||
return block.id;
|
||||
})->id;
|
||||
if (max_id == UINT32_MAX) {
|
||||
EXIT("cannot allocate a typed entry block id");
|
||||
}
|
||||
CFG::Terminator terminator;
|
||||
terminator.kind = CFG::TerminatorKind::Branch;
|
||||
terminator.true_block = cfg.blocks.front().id;
|
||||
result.block_storage.push_back(std::make_unique<IR::Block>());
|
||||
result.blocks.push_back(result.block_storage.back().get());
|
||||
result.block_info.push_back({max_id + 1u, cfg.blocks.front().start_pc,
|
||||
cfg.blocks.front().start_pc, std::move(terminator)});
|
||||
auto& entry = *result.blocks.front();
|
||||
entry.id = max_id + 1u;
|
||||
entry.start_pc = entry.end_pc = cfg.blocks.front().start_pc;
|
||||
entry.terminator.kind = CFG::TerminatorKind::Branch;
|
||||
|
||||
std::unordered_map<uint32_t, size_t> block_indices;
|
||||
block_indices.reserve(cfg.blocks.size());
|
||||
for (const auto& source_block: cfg.blocks) {
|
||||
if (!block_indices.emplace(source_block.id, result.blocks.size()).second) {
|
||||
EXIT("CFG contains duplicate block id %u", source_block.id);
|
||||
std::unordered_map<uint32_t, IR::Block*> blocks_by_id;
|
||||
blocks_by_id.reserve(cfg.blocks.size());
|
||||
for (const auto& source: cfg.blocks) {
|
||||
auto& block = *result.block_storage.emplace_back(std::make_unique<IR::Block>());
|
||||
if (!blocks_by_id.emplace(source.id, &block).second) {
|
||||
EXIT("CFG contains duplicate block id %u", source.id);
|
||||
}
|
||||
result.block_storage.push_back(std::make_unique<IR::Block>());
|
||||
result.blocks.push_back(result.block_storage.back().get());
|
||||
result.block_info.push_back(
|
||||
{source_block.id, source_block.start_pc, source_block.end_pc, source_block.terminator});
|
||||
result.blocks.push_back(&block);
|
||||
block.id = source.id;
|
||||
block.start_pc = source.start_pc;
|
||||
block.end_pc = source.end_pc;
|
||||
}
|
||||
for (const auto& source_block: cfg.blocks) {
|
||||
const auto source_index = block_indices.at(source_block.id);
|
||||
for (const auto successor: source_block.successors) {
|
||||
const auto target = block_indices.find(successor);
|
||||
if (target == block_indices.end()) {
|
||||
EXIT("CFG block %u has unknown successor %u", source_block.id, successor);
|
||||
const auto target_block = [&](uint32_t id) -> IR::Block* {
|
||||
return id == UINT32_MAX ? nullptr : blocks_by_id.at(id);
|
||||
};
|
||||
entry.terminator.true_block = target_block(cfg.blocks.front().id);
|
||||
for (const auto& source: cfg.blocks) {
|
||||
auto& block = *blocks_by_id.at(source.id);
|
||||
const auto& term = source.terminator;
|
||||
block.terminator.kind = term.kind;
|
||||
block.terminator.true_block = target_block(term.true_block);
|
||||
block.terminator.false_block = target_block(term.false_block);
|
||||
block.terminator.merge_block = target_block(term.merge_block);
|
||||
block.terminator.continue_block = target_block(term.continue_block);
|
||||
block.terminator.loop_header = term.loop_header;
|
||||
block.terminator.indexed = term.indirect_selector_code != UINT32_MAX;
|
||||
const auto& values =
|
||||
block.terminator.indexed ? term.indirect_selector_values : term.indirect_target_pcs;
|
||||
const auto& targets =
|
||||
block.terminator.indexed ? term.indirect_selector_targets : term.indirect_targets;
|
||||
if (values.size() != targets.size()) {
|
||||
EXIT("CFG block %u has inconsistent indirect targets", source.id);
|
||||
}
|
||||
block.terminator.cases.reserve(values.size());
|
||||
for (size_t index = 0; index < values.size(); ++index) {
|
||||
block.terminator.cases.push_back({values[index], target_block(targets[index])});
|
||||
}
|
||||
for (const auto successor: source.successors) {
|
||||
const auto target = blocks_by_id.find(successor);
|
||||
if (target == blocks_by_id.end()) {
|
||||
EXIT("CFG block %u has unknown successor %u", source.id, successor);
|
||||
}
|
||||
result.blocks[source_index]->AddBranch(result.blocks[target->second]);
|
||||
block.AddBranch(target->second);
|
||||
}
|
||||
}
|
||||
{
|
||||
result.blocks.front()->AddBranch(result.blocks.at(block_indices.at(cfg.blocks.front().id)));
|
||||
IR::IREmitter entry_ir(result.blocks.front());
|
||||
entry.AddBranch(entry.terminator.true_block);
|
||||
IR::IREmitter entry_ir(&entry);
|
||||
const auto builtin = [&](IR::StageInputKind kind, uint32_t component = 0u) {
|
||||
return IR::U32(
|
||||
entry_ir.Emit(IR::ValueOpcode::GetBuiltin,
|
||||
@@ -1331,10 +1353,10 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
|
||||
}
|
||||
}
|
||||
const bool flush_f32_inputs = options.stage == ShaderType::Compute &&
|
||||
(options.input_info.compute->float_mode & 0x10u) == 0;
|
||||
(options.input_info.compute->float_mode & 0x10u) == 0;
|
||||
for (const auto& cfg_block: cfg.blocks) {
|
||||
const auto typed_index = block_indices.at(cfg_block.id);
|
||||
Translator translator(result, result.blocks[typed_index], vector_limit, flush_f32_inputs);
|
||||
auto* block = blocks_by_id.at(cfg_block.id);
|
||||
Translator translator(result, block, vector_limit, flush_f32_inputs);
|
||||
for (uint32_t index = cfg_block.inst_begin; index < cfg_block.inst_end; index++) {
|
||||
const auto& instruction = decoded.instructions[index];
|
||||
if (IsCodeTableLoad(cfg, instruction.pc)) {
|
||||
@@ -1357,7 +1379,7 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
|
||||
}
|
||||
translator.TranslateInstruction(instruction);
|
||||
}
|
||||
translator.AddBranchCondition(cfg, cfg_block, result.block_info[typed_index]);
|
||||
translator.AddBranchCondition(cfg, cfg_block, *block);
|
||||
}
|
||||
IR::ValidateProgram(result, false);
|
||||
return result;
|
||||
|
||||
@@ -17,7 +17,8 @@ public:
|
||||
void TranslateInstruction(const Decoder::Instruction& inst);
|
||||
void TranslateEmbeddedFetch(const Decoder::Instruction& inst, uint32_t attribute,
|
||||
uint32_t component_count, const ShaderBufferResource& resource);
|
||||
void AddBranchCondition(const CFG::Graph& graph, const CFG::BasicBlock& source, IR::BlockInfo& info);
|
||||
void AddBranchCondition(const CFG::Graph& graph, const CFG::BasicBlock& source,
|
||||
IR::Block& info);
|
||||
|
||||
private:
|
||||
const Decoder::Operand& SourceAt(const Decoder::Instruction& inst, uint32_t index);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "graphics/shader/recompiler/frontend/cfg/ShaderCFG.h"
|
||||
#include "graphics/shader/recompiler/ir/Reg.h"
|
||||
#include "graphics/shader/recompiler/ir/Value.h"
|
||||
|
||||
@@ -49,6 +50,27 @@ public:
|
||||
const_iterator end() const;
|
||||
bool empty() const;
|
||||
|
||||
struct SwitchCase {
|
||||
uint32_t value;
|
||||
Block* target;
|
||||
};
|
||||
struct Terminator {
|
||||
CFG::TerminatorKind kind = CFG::TerminatorKind::Return;
|
||||
Block* true_block = nullptr;
|
||||
Block* false_block = nullptr;
|
||||
Block* merge_block = nullptr;
|
||||
Block* continue_block = nullptr;
|
||||
std::vector<SwitchCase> cases;
|
||||
bool indexed = false;
|
||||
bool loop_header = false;
|
||||
};
|
||||
uint32_t id = 0;
|
||||
uint32_t start_pc = 0;
|
||||
uint32_t end_pc = 0;
|
||||
Terminator terminator;
|
||||
Value condition;
|
||||
Value indirect_target;
|
||||
|
||||
std::array<Value, NumScalarRegs> ssa_sreg_values {};
|
||||
std::array<Value, NumScalarRegs> ssa_thread_bit_sreg_values {};
|
||||
std::array<Value, NumScalarRegs> ssa_sreg_mask_tags {};
|
||||
|
||||
@@ -230,12 +230,11 @@ bool HasShaderMemoryWrites(const Program& program) {
|
||||
}
|
||||
|
||||
void ValidateProgram(const Program& program, bool require_ssa) {
|
||||
if (program.blocks.size() != program.block_info.size() ||
|
||||
program.blocks.size() != program.block_storage.size()) {
|
||||
if (program.blocks.size() != program.block_storage.size()) {
|
||||
return Fail("value IR block storage is inconsistent");
|
||||
}
|
||||
std::unordered_map<const Block*, size_t> block_indices;
|
||||
std::unordered_map<uint32_t, const Block*> blocks_by_id;
|
||||
std::unordered_set<uint32_t> block_ids;
|
||||
std::unordered_map<const Inst*, size_t> instruction_positions;
|
||||
for (size_t block_index = 0; block_index < program.blocks.size(); block_index++) {
|
||||
const auto* block = program.blocks[block_index];
|
||||
@@ -246,10 +245,10 @@ void ValidateProgram(const Program& program, bool require_ssa) {
|
||||
if (!block_indices.emplace(block, block_index).second) {
|
||||
return Fail("value IR block pointer is duplicated");
|
||||
}
|
||||
if (program.block_info[block_index].id == UINT32_MAX) {
|
||||
if (block->id == UINT32_MAX) {
|
||||
return Fail("value IR block uses the reserved exit id");
|
||||
}
|
||||
if (!blocks_by_id.emplace(program.block_info[block_index].id, block).second) {
|
||||
if (!block_ids.insert(block->id).second) {
|
||||
return Fail("value IR block id is duplicated");
|
||||
}
|
||||
size_t position = 0;
|
||||
@@ -294,15 +293,14 @@ void ValidateProgram(const Program& program, bool require_ssa) {
|
||||
}
|
||||
|
||||
std::unordered_set<const Block*> expected_successors;
|
||||
const auto add_target = [&](uint32_t id) {
|
||||
const auto found = blocks_by_id.find(id);
|
||||
if (found == blocks_by_id.end()) {
|
||||
const auto add_target = [&](const Block* target) {
|
||||
if (!block_indices.contains(target)) {
|
||||
return false;
|
||||
}
|
||||
expected_successors.insert(found->second);
|
||||
expected_successors.insert(target);
|
||||
return true;
|
||||
};
|
||||
const auto& terminator = program.block_info[block_index].terminator;
|
||||
const auto& terminator = block->terminator;
|
||||
const auto validate_control_value = [&](Value value, Type type) {
|
||||
if (value.IsEmpty() || value.GetType() != type) {
|
||||
return false;
|
||||
@@ -320,44 +318,32 @@ void ValidateProgram(const Program& program, bool require_ssa) {
|
||||
if (!add_target(terminator.true_block) || !add_target(terminator.false_block)) {
|
||||
return Fail("value IR conditional branch target is missing");
|
||||
}
|
||||
if (!validate_control_value(program.block_info[block_index].condition, Type::U1)) {
|
||||
if (!validate_control_value(block->condition, Type::U1)) {
|
||||
return Fail("value IR conditional branch condition is invalid");
|
||||
}
|
||||
break;
|
||||
case CFG::TerminatorKind::IndirectBranch: {
|
||||
if (!validate_control_value(program.block_info[block_index].indirect_target,
|
||||
Type::U32)) {
|
||||
if (!validate_control_value(block->indirect_target, Type::U32)) {
|
||||
return Fail("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("value IR indirect branch target is duplicated");
|
||||
std::unordered_set<uint32_t> values;
|
||||
for (const auto& branch: terminator.cases) {
|
||||
if (!values.insert(branch.value).second) {
|
||||
return Fail("value IR indirect selector value is duplicated");
|
||||
}
|
||||
if (!add_target(target)) {
|
||||
if (!add_target(branch.target)) {
|
||||
return Fail("value IR indirect branch target is missing");
|
||||
}
|
||||
}
|
||||
if (terminator.indirect_selector_values.size() !=
|
||||
terminator.indirect_selector_targets.size()) {
|
||||
return Fail("value IR indirect selector table is inconsistent");
|
||||
}
|
||||
for (const auto target: terminator.indirect_selector_targets) {
|
||||
const auto found = blocks_by_id.find(target);
|
||||
if (found == blocks_by_id.end() ||
|
||||
!expected_successors.contains(found->second)) {
|
||||
return Fail("value IR indirect selector target is not a CFG successor");
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CFG::TerminatorKind::Return:
|
||||
case CFG::TerminatorKind::Unsupported: break;
|
||||
}
|
||||
if ((terminator.merge_block != UINT32_MAX &&
|
||||
!blocks_by_id.contains(terminator.merge_block)) ||
|
||||
(terminator.continue_block != UINT32_MAX &&
|
||||
!blocks_by_id.contains(terminator.continue_block))) {
|
||||
if ((terminator.merge_block != nullptr &&
|
||||
!block_indices.contains(terminator.merge_block)) ||
|
||||
(terminator.continue_block != nullptr &&
|
||||
!block_indices.contains(terminator.continue_block))) {
|
||||
return Fail("value IR structured control target is missing");
|
||||
}
|
||||
if (successors != expected_successors) {
|
||||
@@ -664,7 +650,7 @@ void ValidateProgram(const Program& program, bool require_ssa) {
|
||||
}
|
||||
}
|
||||
}
|
||||
const auto& info = program.block_info[block_index];
|
||||
const auto& info = *program.blocks[block_index];
|
||||
if (!info.condition.IsEmpty() && !control_dominates(info.condition, block)) {
|
||||
return Fail("value IR branch condition definition does not dominate its use");
|
||||
}
|
||||
@@ -675,9 +661,9 @@ void ValidateProgram(const Program& program, bool require_ssa) {
|
||||
}
|
||||
|
||||
void ResolveControlFlowIdentities(Program& program) {
|
||||
for (auto& info: program.block_info) {
|
||||
info.condition = info.condition.Resolve();
|
||||
info.indirect_target = info.indirect_target.Resolve();
|
||||
for (auto* block: program.blocks) {
|
||||
block->condition = block->condition.Resolve();
|
||||
block->indirect_target = block->indirect_target.Resolve();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -712,9 +698,9 @@ std::string ProgramToString(const Program& program) {
|
||||
|
||||
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,
|
||||
program.block_info[block_index].end_pc);
|
||||
text +=
|
||||
fmt::format("Block ${} pc=0x{:08x}..0x{:08x}\n", block_index,
|
||||
program.blocks[block_index]->start_pc, program.blocks[block_index]->end_pc);
|
||||
for (const auto& inst: *program.blocks[block_index]) {
|
||||
const auto type = inst.GetType();
|
||||
if (type != Type::Void) {
|
||||
|
||||
@@ -493,15 +493,6 @@ struct ShaderInfo {
|
||||
bool operator==(const ShaderInfo& other) const = default;
|
||||
};
|
||||
|
||||
struct BlockInfo {
|
||||
uint32_t id = 0;
|
||||
uint32_t start_pc = 0;
|
||||
uint32_t end_pc = 0;
|
||||
CFG::Terminator terminator;
|
||||
Value condition;
|
||||
Value indirect_target;
|
||||
};
|
||||
|
||||
struct DescriptorSource {
|
||||
struct IndirectDescriptor {
|
||||
struct SelectorRead {
|
||||
@@ -631,7 +622,6 @@ struct Program: ResourcePlan {
|
||||
bool dispatcher_fallback = false;
|
||||
CFG::FailureKind cfg_failure_kind = CFG::FailureKind::None;
|
||||
std::string fallback_reason;
|
||||
std::vector<BlockInfo> block_info;
|
||||
struct ScalarWrite { uint32_t pc; ScalarReg reg; };
|
||||
std::vector<ScalarWrite> scalar_writes;
|
||||
// Typed memory and export instructions reference shader-local metadata by dense index.
|
||||
|
||||
@@ -680,30 +680,29 @@ bool BuildSamplerPlan(const ShaderInfo& base, SamplerPlan& plan) {
|
||||
template <typename Predicate>
|
||||
static std::vector<ResourceBlock> ResourceControlFlow(const Program& program, const Predicate& predicate) {
|
||||
// The renderer checks captured scalar reads against final buffer and image write ranges.
|
||||
if (program.blocks.size() != program.block_info.size() || program.has_address_writes) {
|
||||
if (program.has_address_writes) {
|
||||
return {};
|
||||
}
|
||||
std::unordered_map<uint32_t, uint32_t> indices;
|
||||
for (uint32_t i = 0; i < program.block_info.size(); i++) {
|
||||
if (!indices.emplace(program.block_info[i].id, i).second) {
|
||||
std::unordered_map<const Block*, uint32_t> indices;
|
||||
for (uint32_t i = 0; i < program.blocks.size(); i++) {
|
||||
if (!indices.emplace(program.blocks[i], i).second) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
std::vector<ResourceBlock> blocks(program.blocks.size());
|
||||
for (uint32_t i = 0; i < blocks.size(); i++) {
|
||||
auto& block = blocks[i];
|
||||
const auto& info = program.block_info[i];
|
||||
const auto& info = *program.blocks[i];
|
||||
const auto& terminator = info.terminator;
|
||||
std::vector<uint32_t> successors;
|
||||
const std::array targets {terminator.true_block, terminator.false_block};
|
||||
std::span<Block* const> successors;
|
||||
switch (terminator.kind) {
|
||||
case CFG::TerminatorKind::Branch: successors.push_back(terminator.true_block); break;
|
||||
case CFG::TerminatorKind::Branch: successors = std::span(targets).first(1); break;
|
||||
case CFG::TerminatorKind::ConditionalBranch:
|
||||
successors = {terminator.true_block, terminator.false_block};
|
||||
successors = targets;
|
||||
block.condition = info.condition;
|
||||
break;
|
||||
case CFG::TerminatorKind::IndirectBranch:
|
||||
successors = terminator.indirect_targets;
|
||||
break;
|
||||
case CFG::TerminatorKind::IndirectBranch: successors = info.ImmSuccessors(); break;
|
||||
case CFG::TerminatorKind::Return: break;
|
||||
default: return {};
|
||||
}
|
||||
@@ -824,17 +823,16 @@ static std::optional<std::array<uint64_t, 3>> FillIndex(Value value, uint32_t ax
|
||||
}
|
||||
|
||||
static UniformFillPlan AnalyzeUniformFill(const Program& program) {
|
||||
if (program.stage != ShaderType::Compute || program.blocks.empty() ||
|
||||
program.blocks.size() != program.block_info.size() || program.info.uses_dma ||
|
||||
if (program.stage != ShaderType::Compute || program.blocks.empty() || program.info.uses_dma ||
|
||||
!program.info.samplers.empty()) {
|
||||
return {};
|
||||
}
|
||||
std::unordered_set<uint32_t> visited;
|
||||
uint32_t index = 0;
|
||||
const Inst* store = nullptr;
|
||||
std::unordered_set<const Block*> visited;
|
||||
const auto* block = program.blocks.front();
|
||||
const Inst* store = nullptr;
|
||||
for (;;) {
|
||||
if (!visited.insert(index).second) return {};
|
||||
for (const auto& inst: *program.blocks[index]) {
|
||||
if (block == nullptr || !visited.insert(block).second) return {};
|
||||
for (const auto& inst: *block) {
|
||||
if (AddressOpcodeInfoOf(inst.GetOpcode()).access != AddressAccess::None) return {};
|
||||
if (!inst.MayHaveSideEffects()) continue;
|
||||
if (store != nullptr || (BufferAccessOf(inst.GetOpcode()) != BufferAccess::Write &&
|
||||
@@ -842,12 +840,10 @@ static UniformFillPlan AnalyzeUniformFill(const Program& program) {
|
||||
return {};
|
||||
store = &inst;
|
||||
}
|
||||
const auto& term = program.block_info[index].terminator;
|
||||
const auto& term = block->terminator;
|
||||
if (term.kind == CFG::TerminatorKind::Return) break;
|
||||
if (term.kind != CFG::TerminatorKind::Branch) return {};
|
||||
const auto next = std::ranges::find(program.block_info, term.true_block, &BlockInfo::id);
|
||||
if (next == program.block_info.end()) return {};
|
||||
index = static_cast<uint32_t>(next - program.block_info.begin());
|
||||
block = term.true_block;
|
||||
}
|
||||
if (store == nullptr || visited.size() != program.blocks.size()) return {};
|
||||
for (const auto& buffer: program.info.buffers) {
|
||||
|
||||
@@ -613,8 +613,7 @@ private:
|
||||
value = value.Resolve();
|
||||
const auto* phi = value.TryInstruction();
|
||||
if (m_shader_writes || phi == nullptr || phi->GetOpcode() != ValueOpcode::Phi ||
|
||||
phi->NumArgs() != 2u || phi->NumPhiBlocks() != 2u || phi->GetType() != Type::U32 ||
|
||||
m_program.blocks.size() != m_program.block_info.size()) {
|
||||
phi->NumArgs() != 2u || phi->NumPhiBlocks() != 2u || phi->GetType() != Type::U32) {
|
||||
return value;
|
||||
}
|
||||
const auto* merge = phi->Parent();
|
||||
@@ -637,7 +636,7 @@ private:
|
||||
if (branch == merge || branch->ImmSuccessors().size() != 2u) {
|
||||
return value;
|
||||
}
|
||||
std::array<uint32_t, 2> target_ids;
|
||||
std::array<const Block*, 2> targets;
|
||||
for (uint32_t arm = 0; arm < 2; arm++) {
|
||||
const auto* incoming = phi->PhiBlock(arm);
|
||||
if (incoming == merge ||
|
||||
@@ -648,29 +647,20 @@ private:
|
||||
incoming->ImmSuccessors()[0] != merge))) {
|
||||
return value;
|
||||
}
|
||||
const auto* target = incoming == branch ? merge : incoming;
|
||||
const auto it = std::ranges::find(m_program.blocks, target);
|
||||
if (it == m_program.blocks.end()) {
|
||||
return value;
|
||||
}
|
||||
target_ids[arm] = m_program.block_info[it - m_program.blocks.begin()].id;
|
||||
targets[arm] = incoming == branch ? merge : incoming;
|
||||
}
|
||||
const auto branch_it = std::ranges::find(m_program.blocks, branch);
|
||||
if (branch_it == m_program.blocks.end()) {
|
||||
return value;
|
||||
}
|
||||
const auto& info = m_program.block_info[branch_it - m_program.blocks.begin()];
|
||||
const auto& info = *branch;
|
||||
const auto& term = info.terminator;
|
||||
if (term.kind != CFG::TerminatorKind::ConditionalBranch ||
|
||||
!((term.true_block == target_ids[0] && term.false_block == target_ids[1]) ||
|
||||
(term.false_block == target_ids[0] && term.true_block == target_ids[1])) ||
|
||||
!((term.true_block == targets[0] && term.false_block == targets[1]) ||
|
||||
(term.false_block == targets[0] && term.true_block == targets[1])) ||
|
||||
!ValidateRuntimeValue(m_program, info.condition, RuntimeValueType::Integer) ||
|
||||
!ValidateRuntimeValue(m_program, phi->Arg(0)) ||
|
||||
!ValidateRuntimeValue(m_program, phi->Arg(1))) {
|
||||
return value;
|
||||
}
|
||||
// Retain a host expression; replacing the GPU Phi would break SSA dominance.
|
||||
const auto true_arg = term.true_block == target_ids[0] ? 0u : 1u;
|
||||
const auto true_arg = term.true_block == targets[0] ? 0u : 1u;
|
||||
auto& selected = m_program.value_storage.emplace_back(ValueOpcode::SelectU32);
|
||||
selected.SetArg(0, info.condition);
|
||||
selected.SetArg(1, phi->Arg(true_arg));
|
||||
@@ -852,9 +842,9 @@ private:
|
||||
if (entry.source.dwords[word].Resolve() == Value(read))
|
||||
entry.source.dwords[word] = flat;
|
||||
}
|
||||
for (auto& info: m_program.block_info) {
|
||||
if (info.condition.Resolve() == Value(read)) info.condition = flat;
|
||||
if (info.indirect_target.Resolve() == Value(read)) info.indirect_target = flat;
|
||||
for (auto* block: m_program.blocks) {
|
||||
if (block->condition.Resolve() == Value(read)) block->condition = flat;
|
||||
if (block->indirect_target.Resolve() == Value(read)) block->indirect_target = flat;
|
||||
}
|
||||
if (keep) {
|
||||
if (resolved->planning_handle.IsEmpty()) {
|
||||
@@ -1022,9 +1012,6 @@ private:
|
||||
};
|
||||
|
||||
bool NonzeroOnEntry(Value value, const Block* block) const {
|
||||
if (m_program.blocks.size() != m_program.block_info.size()) {
|
||||
return false;
|
||||
}
|
||||
// Each unique predecessor must execute before this use. Stop at joins: an
|
||||
// unrelated comparison is not a bound on FindILsb's zero-input sentinel.
|
||||
for (size_t depth = 0; block != nullptr && depth < m_program.blocks.size(); ++depth) {
|
||||
@@ -1098,15 +1085,11 @@ private:
|
||||
}
|
||||
|
||||
std::optional<EdgePredicate> ConditionalEdge(const Block* from, const Block* to) const {
|
||||
const auto position = std::ranges::find(m_program.blocks, from);
|
||||
const auto target = std::ranges::find(m_program.blocks, to);
|
||||
if (position == m_program.blocks.end() || target == m_program.blocks.end()) return {};
|
||||
const auto& info = m_program.block_info[position - m_program.blocks.begin()];
|
||||
const auto& term = info.terminator;
|
||||
const auto id = m_program.block_info[target - m_program.blocks.begin()].id;
|
||||
const auto& term = from->terminator;
|
||||
if (term.kind != CFG::TerminatorKind::ConditionalBranch ||
|
||||
(term.true_block == id) == (term.false_block == id)) return {};
|
||||
EdgePredicate edge {info.condition, term.true_block == id};
|
||||
(term.true_block == to) == (term.false_block == to))
|
||||
return {};
|
||||
EdgePredicate edge {from->condition, term.true_block == to};
|
||||
while (const auto* inst = edge.condition.Resolve().TryInstruction()) {
|
||||
if (inst->GetOpcode() == ValueOpcode::LogicalNot) {
|
||||
edge.positive = !edge.positive;
|
||||
@@ -1470,9 +1453,9 @@ private:
|
||||
|
||||
const Inst* BoundedLoop(Value key, const Block* use, const auto& accepts_bound) const {
|
||||
const auto* phi = key.Resolve().TryInstruction();
|
||||
if (phi == nullptr || phi->GetOpcode() != ValueOpcode::Phi ||
|
||||
phi->GetType() != Type::U32 || phi->NumArgs() != 2u || phi->NumPhiBlocks() != 2u ||
|
||||
m_program.blocks.size() != m_program.block_info.size()) return {};
|
||||
if (phi == nullptr || phi->GetOpcode() != ValueOpcode::Phi || phi->GetType() != Type::U32 ||
|
||||
phi->NumArgs() != 2u || phi->NumPhiBlocks() != 2u)
|
||||
return {};
|
||||
const Block* increment_block = nullptr;
|
||||
uint32_t initial_arm = 0;
|
||||
for (uint32_t initial = 0; initial < 2u; ++initial) {
|
||||
@@ -1904,22 +1887,13 @@ private:
|
||||
}
|
||||
|
||||
bool ImpossibleSwitchEdge(const Block* from, const Block* to) const {
|
||||
const auto from_it = std::ranges::find(m_program.blocks, from);
|
||||
const auto to_it = std::ranges::find(m_program.blocks, to);
|
||||
if (from_it == m_program.blocks.end() || to_it == m_program.blocks.end() ||
|
||||
m_program.block_info.size() != m_program.blocks.size()) return false;
|
||||
const auto& info = m_program.block_info[from_it - m_program.blocks.begin()];
|
||||
const auto& term = info.terminator;
|
||||
if (term.kind != CFG::TerminatorKind::IndirectBranch ||
|
||||
term.indirect_selector_code == UINT32_MAX ||
|
||||
term.indirect_selector_values.empty() ||
|
||||
term.indirect_selector_values.size() != term.indirect_selector_targets.size()) return false;
|
||||
const auto target = m_program.block_info[to_it - m_program.blocks.begin()].id;
|
||||
const auto maximum = SelectorMaximum(info.indirect_target);
|
||||
const auto& term = from->terminator;
|
||||
if (term.kind != CFG::TerminatorKind::IndirectBranch || !term.indexed) return false;
|
||||
const auto maximum = SelectorMaximum(from->indirect_target);
|
||||
bool found = false;
|
||||
for (size_t i = 0; i < term.indirect_selector_values.size(); ++i) {
|
||||
if (term.indirect_selector_targets[i] != target) continue;
|
||||
if (term.indirect_selector_values[i] <= maximum) return false;
|
||||
for (const auto& branch: term.cases) {
|
||||
if (branch.target != to) continue;
|
||||
if (branch.value <= maximum) return false;
|
||||
found = true;
|
||||
}
|
||||
return found;
|
||||
|
||||
Reference in New Issue
Block a user