Merge pull request #1794 from etra0/master

Add 64 bit support for OpSwitch
This commit is contained in:
Hans-Kristian Arntzen 2021-11-15 15:05:10 +01:00 committed by GitHub
commit 37dfb3f45f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 95 additions and 12 deletions

View File

@ -135,7 +135,9 @@ bool CFG::post_order_visit(uint32_t block_id)
break;
case SPIRBlock::MultiSelect:
for (auto &target : block.cases)
{
const auto &cases = compiler.get_case_list(block);
for (const auto &target : cases)
{
if (post_order_visit(target.block))
add_branch(block_id, target.block);
@ -143,7 +145,7 @@ bool CFG::post_order_visit(uint32_t block_id)
if (block.default_block && post_order_visit(block.default_block))
add_branch(block_id, block.default_block);
break;
}
default:
break;
}
@ -385,7 +387,9 @@ void DominatorBuilder::lift_continue_block_dominator()
break;
case SPIRBlock::MultiSelect:
for (auto &target : block.cases)
{
auto &cases = cfg.get_compiler().get_case_list(block);
for (auto &target : cases)
{
if (cfg.get_visit_order(target.block) > post_order)
back_edge_dominator = true;
@ -393,6 +397,7 @@ void DominatorBuilder::lift_continue_block_dominator()
if (block.default_block && cfg.get_visit_order(block.default_block) > post_order)
back_edge_dominator = true;
break;
}
default:
break;

View File

@ -854,10 +854,11 @@ struct SPIRBlock : IVariant
struct Case
{
uint32_t value;
uint64_t value;
BlockID block;
};
SmallVector<Case> cases;
SmallVector<Case> cases_32bit;
SmallVector<Case> cases_64bit;
// If we have tried to optimize code for this block but failed,
// keep track of this.

View File

@ -1659,6 +1659,39 @@ SPIRBlock::ContinueBlockType Compiler::continue_block_type(const SPIRBlock &bloc
}
}
const SmallVector<SPIRBlock::Case> &Compiler::get_case_list(const SPIRBlock &block) const
{
uint32_t width = 0;
// First we check if we can get the type directly from the block.condition
// since it can be a SPIRConstant or a SPIRVariable.
if (const auto *constant = maybe_get<SPIRConstant>(block.condition))
{
const auto &type = get<SPIRType>(constant->constant_type);
width = type.width;
}
else if (const auto *var = maybe_get<SPIRVariable>(block.condition))
{
const auto &type = get<SPIRType>(var->basetype);
width = type.width;
}
else
{
auto search = ir.load_type_width.find(block.condition);
if (search == ir.load_type_width.end())
{
SPIRV_CROSS_THROW("Use of undeclared variable on a switch statement.");
}
width = search->second;
}
if (width > 32)
return block.cases_64bit;
return block.cases_32bit;
}
bool Compiler::traverse_all_reachable_opcodes(const SPIRBlock &block, OpcodeHandler &handler) const
{
handler.set_current_block(block);
@ -3057,12 +3090,15 @@ void Compiler::AnalyzeVariableScopeAccessHandler::set_current_block(const SPIRBl
break;
case SPIRBlock::MultiSelect:
{
notify_variable_access(block.condition, block.self);
for (auto &target : block.cases)
auto &cases = compiler.get_case_list(block);
for (auto &target : cases)
test_phi(target.block);
if (block.default_block)
test_phi(block.default_block);
break;
}
default:
break;

View File

@ -1135,6 +1135,11 @@ protected:
bool is_vertex_like_shader() const;
// Get the correct case list for the OpSwitch, since it can be either a
// 32 bit wide condition or a 64 bit, but the type is not embedded in the
// instruction itself.
const SmallVector<SPIRBlock::Case> &get_case_list(const SPIRBlock &block) const;
private:
// Used only to implement the old deprecated get_entry_point() interface.
const SPIREntryPoint &get_first_entry_point(const std::string &name) const;

View File

@ -83,6 +83,7 @@ ParsedIR &ParsedIR::operator=(ParsedIR &&other) SPIRV_CROSS_NOEXCEPT
loop_iteration_depth_soft = other.loop_iteration_depth_soft;
meta_needing_name_fixup = std::move(other.meta_needing_name_fixup);
load_type_width = std::move(other.load_type_width);
}
return *this;
}
@ -115,7 +116,9 @@ ParsedIR &ParsedIR::operator=(const ParsedIR &other)
addressing_model = other.addressing_model;
memory_model = other.memory_model;
meta_needing_name_fixup = other.meta_needing_name_fixup;
load_type_width = other.load_type_width;
// Very deliberate copying of IDs. There is no default copy constructor, nor a simple default constructor.
// Construct object first so we have the correct allocator set-up, then we can copy object into our new pool group.

View File

@ -78,6 +78,13 @@ public:
SmallVector<ID> ids_for_constant_or_type;
SmallVector<ID> ids_for_constant_or_variable;
// We need to keep track of the width the Ops that contains a type for the
// OpSwitch instruction, since this one doesn't contains the type in the
// instruction itself. And in some case we need to cast the condition to
// wider types. We only need the width to do the branch fixup since the
// type check itself can be done at runtime
std::unordered_map<ID, uint32_t> load_type_width;
// Declared capabilities and extensions in the SPIR-V module.
// Not really used except for reflection at the moment.
SmallVector<spv::Capability> declared_capabilities;

View File

@ -14803,19 +14803,24 @@ void CompilerGLSL::emit_block_chain(SPIRBlock &block)
// and let the default: block handle it.
// 2.11 in SPIR-V spec states that for fall-through cases, there is a very strict declaration order which we can take advantage of here.
// We only need to consider possible fallthrough if order[i] branches to order[i + 1].
for (auto &c : block.cases)
auto &cases = get_case_list(block);
for (auto &c : cases)
{
// It's safe to cast to uint32_t since we actually do a check
// previously that we're not using uint64_t as the switch selector.
auto case_value = static_cast<uint32_t>(c.value);
if (c.block != block.next_block && c.block != block.default_block)
{
if (!case_constructs.count(c.block))
block_declaration_order.push_back(c.block);
case_constructs[c.block].push_back(c.value);
case_constructs[c.block].push_back(case_value);
}
else if (c.block == block.next_block && block.default_block != block.next_block)
{
// We might have to flush phi inside specific case labels.
// If we can piggyback on default:, do so instead.
literals_to_merge.push_back(c.value);
literals_to_merge.push_back(case_value);
}
}
@ -14935,7 +14940,7 @@ void CompilerGLSL::emit_block_chain(SPIRBlock &block)
// If there is only one default block, and no cases, this is a case where SPIRV-opt decided to emulate
// non-structured exits with the help of a switch block.
// This is buggy on FXC, so just emit the logical equivalent of a do { } while(false), which is more idiomatic.
bool degenerate_switch = block.default_block != block.merge_block && block.cases.empty();
bool degenerate_switch = block.default_block != block.merge_block && block.cases_32bit.empty();
if (degenerate_switch || is_legacy_es())
{

View File

@ -1018,8 +1018,21 @@ void Parser::parse(const Instruction &instruction)
current_block->condition = ops[0];
current_block->default_block = ops[1];
for (uint32_t i = 2; i + 2 <= length; i += 2)
current_block->cases.push_back({ ops[i], ops[i + 1] });
uint32_t remaining_ops = length - 2;
if ((remaining_ops % 2) == 0)
{
for (uint32_t i = 2; i + 2 <= length; i += 2)
current_block->cases_32bit.push_back({ ops[i], ops[i + 1] });
}
if ((remaining_ops % 3) == 0)
{
for (uint32_t i = 2; i + 3 <= length; i += 3)
{
uint64_t value = (static_cast<uint64_t>(ops[i]) << 32) | ops[i + 1];
current_block->cases_64bit.push_back({ value, ops[i + 2] });
}
}
// If we jump to next block, make it break instead since we're inside a switch case block at that point.
ir.block_meta[current_block->next_block] |= ParsedIR::BLOCK_META_MULTISELECT_MERGE_BIT;
@ -1177,6 +1190,14 @@ void Parser::parse(const Instruction &instruction)
// Actual opcodes.
default:
{
if (length >= 2)
{
const auto *type = maybe_get<SPIRType>(ops[0]);
if (type)
{
ir.load_type_width.insert({ ops[1], type->width });
}
}
if (!current_block)
SPIRV_CROSS_THROW("Currently no block to insert opcode.");