From 53328917485fc75f06b941564d25f994824efbbf Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Sat, 30 Dec 2017 17:22:36 +0100 Subject: [PATCH] [dxbc] Implemented switch-case instructions --- src/dxbc/dxbc_chunk_isgn.cpp | 1 - src/dxbc/dxbc_compiler.cpp | 181 ++++++++++++++++++++++++++++---- src/dxbc/dxbc_compiler.h | 38 ++++++- src/dxbc/dxbc_defs.cpp | 2 +- src/spirv/spirv_code_buffer.cpp | 9 +- src/spirv/spirv_code_buffer.h | 37 +++++++ src/spirv/spirv_module.cpp | 16 +++ src/spirv/spirv_module.h | 24 +++++ 8 files changed, 281 insertions(+), 27 deletions(-) diff --git a/src/dxbc/dxbc_chunk_isgn.cpp b/src/dxbc/dxbc_chunk_isgn.cpp index f2d23a06..38083171 100644 --- a/src/dxbc/dxbc_chunk_isgn.cpp +++ b/src/dxbc/dxbc_chunk_isgn.cpp @@ -34,7 +34,6 @@ namespace dxvk { const std::string& semanticName, uint32_t semanticIndex) const { for (auto e = this->begin(); e != this->end(); e++) { - // TODO case-insensitive compare if (e->semanticIndex == semanticIndex && compareSemanticNames(semanticName, e->semanticName)) return &(*e); diff --git a/src/dxbc/dxbc_compiler.cpp b/src/dxbc/dxbc_compiler.cpp index f29adfc8..2e595973 100644 --- a/src/dxbc/dxbc_compiler.cpp +++ b/src/dxbc/dxbc_compiler.cpp @@ -2280,6 +2280,109 @@ namespace dxvk { } + void DxbcCompiler::emitControlFlowSwitch(const DxbcShaderInstruction& ins) { + // Load the selector as a scalar unsigned integer + const DxbcRegisterValue selector = emitRegisterLoad( + ins.src[0], DxbcRegMask(true, false, false, false)); + + // Declare switch block. We cannot insert the switch + // instruction itself yet because the number of case + // statements and blocks is unknown at this point. + DxbcCfgBlock block; + block.type = DxbcCfgBlockType::Switch; + block.b_switch.insertPtr = m_module.getInsertionPtr(); + block.b_switch.selectorId = selector.id; + block.b_switch.labelBreak = m_module.allocateId(); + block.b_switch.labelCase = m_module.allocateId(); + block.b_switch.labelDefault = 0; + block.b_switch.labelCases = nullptr; + m_controlFlowBlocks.push_back(block); + + // Define the first 'case' label + m_module.opLabel(block.b_switch.labelCase); + } + + + void DxbcCompiler::emitControlFlowCase(const DxbcShaderInstruction& ins) { + if (m_controlFlowBlocks.size() == 0 + || m_controlFlowBlocks.back().type != DxbcCfgBlockType::Switch) + throw DxvkError("DxbcCompiler: 'Case' without 'Switch' found"); + + // The source operand must be a 32-bit immediate. + if (ins.src[0].type != DxbcOperandType::Imm32) + throw DxvkError("DxbcCompiler: Invalid operand type for 'Case'"); + + // Use the last label allocated for 'case'. The block starting + // with that label is guaranteed to be empty unless a previous + // 'case' block was not properly closed in the DXBC shader. + DxbcCfgBlockSwitch* block = &m_controlFlowBlocks.back().b_switch; + + DxbcSwitchLabel label; + label.desc.literal = ins.src[0].imm.u32_1; + label.desc.labelId = block->labelCase; + label.next = block->labelCases; + block->labelCases = new DxbcSwitchLabel(label); + } + + + void DxbcCompiler::emitControlFlowDefault(const DxbcShaderInstruction& ins) { + if (m_controlFlowBlocks.size() == 0 + || m_controlFlowBlocks.back().type != DxbcCfgBlockType::Switch) + throw DxvkError("DxbcCompiler: 'Default' without 'Switch' found"); + + // Set the last label allocated for 'case' as the default label. + m_controlFlowBlocks.back().b_switch.labelDefault + = m_controlFlowBlocks.back().b_switch.labelCase; + } + + + void DxbcCompiler::emitControlFlowEndSwitch(const DxbcShaderInstruction& ins) { + if (m_controlFlowBlocks.size() == 0 + || m_controlFlowBlocks.back().type != DxbcCfgBlockType::Switch) + throw DxvkError("DxbcCompiler: 'EndSwitch' without 'Switch' found"); + + // Remove the block from the stack, it's closed + DxbcCfgBlock block = m_controlFlowBlocks.back(); + m_controlFlowBlocks.pop_back(); + + // If no 'default' label was specified, use the last allocated + // 'case' label. This is guaranteed to be an empty block unless + // a previous 'case' block was not closed properly. + if (block.b_switch.labelDefault == 0) + block.b_switch.labelDefault = block.b_switch.labelCase; + + // Close the current 'case' block + m_module.opBranch(block.b_switch.labelBreak); + m_module.opLabel (block.b_switch.labelBreak); + + // Insert the 'switch' statement. For that, we need to + // gather all the literal-label pairs for the construct. + m_module.beginInsertion(block.b_switch.insertPtr); + m_module.opSelectionMerge( + block.b_switch.labelBreak, + spv::SelectionControlMaskNone); + + // We'll restore the original order of the case labels here + std::vector jumpTargets; + for (auto i = block.b_switch.labelCases; i != nullptr; i = i->next) + jumpTargets.insert(jumpTargets.begin(), i->desc); + + m_module.opSwitch( + block.b_switch.selectorId, + block.b_switch.labelDefault, + jumpTargets.size(), + jumpTargets.data()); + m_module.endInsertion(); + + // Destroy the list of case labels + // FIXME we're leaking memory if compilation fails. + DxbcSwitchLabel* caseLabel = block.b_switch.labelCases; + + while (caseLabel != nullptr) + delete std::exchange(caseLabel, caseLabel->next); + } + + void DxbcCompiler::emitControlFlowLoop(const DxbcShaderInstruction& ins) { // Declare the 'loop' block DxbcCfgBlock block; @@ -2325,25 +2428,42 @@ namespace dxvk { void DxbcCompiler::emitControlFlowBreak(const DxbcShaderInstruction& ins) { const bool isBreak = ins.op == DxbcOpcode::Break; - DxbcCfgBlock* loopBlock = cfgFindLoopBlock(); + DxbcCfgBlock* cfgBlock = isBreak + ? cfgFindBlock({ DxbcCfgBlockType::Loop, DxbcCfgBlockType::Switch }) + : cfgFindBlock({ DxbcCfgBlockType::Loop }); - if (loopBlock == nullptr) - throw DxvkError("DxbcCompiler: 'Break' or 'Continue' outside 'Loop' found"); + if (cfgBlock == nullptr) + throw DxvkError("DxbcCompiler: 'Break' or 'Continue' outside 'Loop' or 'Switch' found"); - m_module.opBranch(isBreak - ? loopBlock->b_loop.labelBreak - : loopBlock->b_loop.labelContinue); - m_module.opLabel (m_module.allocateId()); + if (cfgBlock->type == DxbcCfgBlockType::Loop) { + m_module.opBranch(isBreak + ? cfgBlock->b_loop.labelBreak + : cfgBlock->b_loop.labelContinue); + } else /* if (cfgBlock->type == DxbcCfgBlockType::Switch) */ { + m_module.opBranch(cfgBlock->b_switch.labelBreak); + } + + // Subsequent instructions assume that there is an open block + const uint32_t labelId = m_module.allocateId(); + m_module.opLabel(labelId); + + // If this is on the same level as a switch-case construct, + // rather than being nested inside an 'if' statement, close + // the current 'case' block. + if (m_controlFlowBlocks.back().type == DxbcCfgBlockType::Switch) + cfgBlock->b_switch.labelCase = labelId; } - + void DxbcCompiler::emitControlFlowBreakc(const DxbcShaderInstruction& ins) { const bool isBreak = ins.op == DxbcOpcode::Breakc; - DxbcCfgBlock* loopBlock = cfgFindLoopBlock(); + DxbcCfgBlock* cfgBlock = isBreak + ? cfgFindBlock({ DxbcCfgBlockType::Loop, DxbcCfgBlockType::Switch }) + : cfgFindBlock({ DxbcCfgBlockType::Loop }); - if (loopBlock == nullptr) - throw DxvkError("DxbcCompiler: 'Breakc' or 'Continuec' outside 'Loop' found"); + if (cfgBlock == nullptr) + throw DxvkError("DxbcCompiler: 'Breakc' or 'Continuec' outside 'Loop' or 'Switch' found"); // Perform zero test on the first component of the condition const DxbcRegisterValue condition = emitRegisterLoad( @@ -2363,18 +2483,26 @@ namespace dxvk { zeroTest.id, breakBlock, mergeBlock); m_module.opLabel(breakBlock); - m_module.opBranch(isBreak - ? loopBlock->b_loop.labelBreak - : loopBlock->b_loop.labelContinue); + + if (cfgBlock->type == DxbcCfgBlockType::Loop) { + m_module.opBranch(isBreak + ? cfgBlock->b_loop.labelBreak + : cfgBlock->b_loop.labelContinue); + } else /* if (cfgBlock->type == DxbcCfgBlockType::Switch) */ { + m_module.opBranch(cfgBlock->b_switch.labelBreak); + } m_module.opLabel(mergeBlock); } void DxbcCompiler::emitControlFlowRet(const DxbcShaderInstruction& ins) { - // TODO implement properly m_module.opReturn(); - m_module.functionEnd(); + + if (m_controlFlowBlocks.size() == 0) + m_module.functionEnd(); + else + m_module.opLabel(m_module.allocateId()); } @@ -2416,6 +2544,18 @@ namespace dxvk { case DxbcOpcode::EndIf: return this->emitControlFlowEndIf(ins); + case DxbcOpcode::Switch: + return this->emitControlFlowSwitch(ins); + + case DxbcOpcode::Case: + return this->emitControlFlowCase(ins); + + case DxbcOpcode::Default: + return this->emitControlFlowDefault(ins); + + case DxbcOpcode::EndSwitch: + return this->emitControlFlowEndSwitch(ins); + case DxbcOpcode::Loop: return this->emitControlFlowLoop(ins); @@ -3714,11 +3854,14 @@ namespace dxvk { } - DxbcCfgBlock* DxbcCompiler::cfgFindLoopBlock() { + DxbcCfgBlock* DxbcCompiler::cfgFindBlock( + const std::initializer_list& types) { for (auto cur = m_controlFlowBlocks.rbegin(); cur != m_controlFlowBlocks.rend(); cur++) { - if (cur->type == DxbcCfgBlockType::Loop) - return &(*cur); + for (auto type : types) { + if (cur->type == type) + return &(*cur); + } } return nullptr; diff --git a/src/dxbc/dxbc_compiler.h b/src/dxbc/dxbc_compiler.h index 8989eaac..2e2b3d07 100644 --- a/src/dxbc/dxbc_compiler.h +++ b/src/dxbc/dxbc_compiler.h @@ -145,7 +145,7 @@ namespace dxvk { enum class DxbcCfgBlockType : uint32_t { - If, Loop, + If, Loop, Switch, }; @@ -165,12 +165,29 @@ namespace dxvk { }; + struct DxbcSwitchLabel { + SpirvSwitchCaseLabel desc; + DxbcSwitchLabel* next; + }; + + + struct DxbcCfgBlockSwitch { + size_t insertPtr; + uint32_t selectorId; + uint32_t labelBreak; + uint32_t labelCase; + uint32_t labelDefault; + DxbcSwitchLabel* labelCases; + }; + + struct DxbcCfgBlock { DxbcCfgBlockType type; union { - DxbcCfgBlockIf b_if; - DxbcCfgBlockLoop b_loop; + DxbcCfgBlockIf b_if; + DxbcCfgBlockLoop b_loop; + DxbcCfgBlockSwitch b_switch; }; }; @@ -433,6 +450,18 @@ namespace dxvk { void emitControlFlowEndIf( const DxbcShaderInstruction& ins); + void emitControlFlowSwitch( + const DxbcShaderInstruction& ins); + + void emitControlFlowCase( + const DxbcShaderInstruction& ins); + + void emitControlFlowDefault( + const DxbcShaderInstruction& ins); + + void emitControlFlowEndSwitch( + const DxbcShaderInstruction& ins); + void emitControlFlowLoop( const DxbcShaderInstruction& ins); @@ -649,7 +678,8 @@ namespace dxvk { //////////////// // Misc methods - DxbcCfgBlock* cfgFindLoopBlock(); + DxbcCfgBlock* cfgFindBlock( + const std::initializer_list& types); DxbcBufferInfo getBufferInfo( const DxbcRegister& reg); diff --git a/src/dxbc/dxbc_defs.cpp b/src/dxbc/dxbc_defs.cpp index e0474039..da5b8eb3 100644 --- a/src/dxbc/dxbc_defs.cpp +++ b/src/dxbc/dxbc_defs.cpp @@ -42,7 +42,7 @@ namespace dxvk { /* Cut */ { 0, DxbcInstClass::GeometryEmit }, /* Default */ - { }, + { 0, DxbcInstClass::ControlFlow }, /* DerivRtx */ { 2, DxbcInstClass::VectorDeriv, { { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, diff --git a/src/spirv/spirv_code_buffer.cpp b/src/spirv/spirv_code_buffer.cpp index 5d1e4965..bb6e41d3 100644 --- a/src/spirv/spirv_code_buffer.cpp +++ b/src/spirv/spirv_code_buffer.cpp @@ -9,7 +9,8 @@ namespace dxvk { SpirvCodeBuffer::~SpirvCodeBuffer() { } - SpirvCodeBuffer::SpirvCodeBuffer(uint32_t size, const uint32_t* data) { + SpirvCodeBuffer::SpirvCodeBuffer(uint32_t size, const uint32_t* data) + : m_ptr(size) { m_code.resize(size); std::memcpy(m_code.data(), data, size * sizeof(uint32_t)); } @@ -28,6 +29,8 @@ namespace dxvk { m_code.resize(buffer.size() / sizeof(uint32_t)); std::memcpy(reinterpret_cast(m_code.data()), buffer.data(), m_code.size() * sizeof(uint32_t)); + + m_ptr = m_code.size(); } @@ -40,12 +43,14 @@ namespace dxvk { const uint32_t* src = other.m_code.data(); std::memcpy(dst + size, src, other.size()); + m_ptr += other.m_code.size(); } } void SpirvCodeBuffer::putWord(uint32_t word) { - m_code.push_back(word); + m_code.insert(m_code.begin() + m_ptr, word); + m_ptr += 1; } diff --git a/src/spirv/spirv_code_buffer.h b/src/spirv/spirv_code_buffer.h index 53234f67..66ce305f 100644 --- a/src/spirv/spirv_code_buffer.h +++ b/src/spirv/spirv_code_buffer.h @@ -143,9 +143,46 @@ namespace dxvk { */ void store(std::ostream&& stream) const; + /** + * \brief Retrieves current insertion pointer + * + * Sometimes it may be necessay to insert code into the + * middle of the stream rather than appending it. This + * retrieves the current function pointer. Note that the + * pointer will become invalid if any code is inserted + * before the current pointer location. + * \returns Current instruction pointr + */ + size_t getInsertionPtr() const { + return m_ptr; + } + + /** + * \brief Sets insertion pointer to a specific value + * + * Sets the insertion pointer to a value that was + * previously retrieved by \ref getInsertionPtr. + * \returns Current instruction pointr + */ + void beginInsertion(size_t ptr) { + m_ptr = ptr; + } + + /** + * \brief Sets insertion pointer to the end + * + * After this call, new instructions will be + * appended to the stream. In other words, + * this will restore default behaviour. + */ + void endInsertion() { + m_ptr = m_code.size(); + } + private: std::vector m_code; + size_t m_ptr = 0; }; diff --git a/src/spirv/spirv_module.cpp b/src/spirv/spirv_module.cpp index 8b01a22a..f2fbc6f0 100644 --- a/src/spirv/spirv_module.cpp +++ b/src/spirv/spirv_module.cpp @@ -2099,6 +2099,22 @@ namespace dxvk { m_code.putWord(falseLabel); } + + void SpirvModule::opSwitch( + uint32_t selector, + uint32_t jumpDefault, + uint32_t caseCount, + const SpirvSwitchCaseLabel* caseLabels) { + m_code.putIns (spv::OpSwitch, 3 + 2 * caseCount); + m_code.putWord(selector); + m_code.putWord(jumpDefault); + + for (uint32_t i = 0; i < caseCount; i++) { + m_code.putWord(caseLabels[i].literal); + m_code.putWord(caseLabels[i].labelId); + } + } + void SpirvModule::opReturn() { m_code.putIns (spv::OpReturn, 1); diff --git a/src/spirv/spirv_module.h b/src/spirv/spirv_module.h index 835ad844..e094f111 100644 --- a/src/spirv/spirv_module.h +++ b/src/spirv/spirv_module.h @@ -4,6 +4,11 @@ namespace dxvk { + struct SpirvSwitchCaseLabel { + uint32_t literal = 0; + uint32_t labelId = 0; + }; + struct SpirvImageOperands { uint32_t flags = 0; uint32_t sLodBias = 0; @@ -34,6 +39,18 @@ namespace dxvk { SpirvCodeBuffer compile() const; + size_t getInsertionPtr() { + return m_code.getInsertionPtr(); + } + + void beginInsertion(size_t ptr) { + m_code.beginInsertion(ptr); + } + + void endInsertion() { + m_code.endInsertion(); + } + uint32_t allocateId(); void enableCapability( @@ -720,6 +737,13 @@ namespace dxvk { uint32_t trueLabel, uint32_t falseLabel); + void opSwitch( + uint32_t selector, + uint32_t jumpDefault, + uint32_t caseCount, + const SpirvSwitchCaseLabel* caseLabels); + + void opReturn(); void opKill();