diff --git a/src/dxbc/dxbc_compiler.cpp b/src/dxbc/dxbc_compiler.cpp index e7ec1627d..94110cd7a 100644 --- a/src/dxbc/dxbc_compiler.cpp +++ b/src/dxbc/dxbc_compiler.cpp @@ -48,84 +48,37 @@ namespace dxvk { void DxbcCompiler::processInstruction(const DxbcShaderInstruction& ins) { - switch (ins.op) { - case DxbcOpcode::DclGlobalFlags: - return this->emitDclGlobalFlags(ins); + switch (ins.opClass) { + case DxbcInstClass::Declaration: + return this->emitDcl(ins); - case DxbcOpcode::DclTemps: - return this->emitDclTemps(ins); + case DxbcInstClass::ControlFlow: + return this->emitControlFlow(ins); - case DxbcOpcode::DclInput: - case DxbcOpcode::DclInputSgv: - case DxbcOpcode::DclInputSiv: - case DxbcOpcode::DclInputPs: - case DxbcOpcode::DclInputPsSgv: - case DxbcOpcode::DclInputPsSiv: - case DxbcOpcode::DclOutput: - case DxbcOpcode::DclOutputSgv: - case DxbcOpcode::DclOutputSiv: - return this->emitDclInterfaceReg(ins); - - case DxbcOpcode::DclConstantBuffer: - return this->emitDclConstantBuffer(ins); - - case DxbcOpcode::DclSampler: - return this->emitDclSampler(ins); - - case DxbcOpcode::DclResource: - return this->emitDclResource(ins); - - case DxbcOpcode::Add: - case DxbcOpcode::Div: - case DxbcOpcode::Exp: - case DxbcOpcode::Log: - case DxbcOpcode::Mad: - case DxbcOpcode::Max: - case DxbcOpcode::Min: - case DxbcOpcode::Mul: - case DxbcOpcode::Mov: - case DxbcOpcode::Rsq: - case DxbcOpcode::Sqrt: - case DxbcOpcode::IAdd: - case DxbcOpcode::IMad: - case DxbcOpcode::IMax: - case DxbcOpcode::IMin: - case DxbcOpcode::INeg: - return this->emitVectorAlu(ins); - - case DxbcOpcode::Movc: - return this->emitVectorCmov(ins); - - case DxbcOpcode::Eq: - case DxbcOpcode::Ge: - case DxbcOpcode::Lt: - case DxbcOpcode::Ne: - case DxbcOpcode::IEq: - case DxbcOpcode::IGe: - case DxbcOpcode::ILt: - case DxbcOpcode::INe: - return this->emitVectorCmp(ins); - - case DxbcOpcode::Dp2: - case DxbcOpcode::Dp3: - case DxbcOpcode::Dp4: - return this->emitVectorDot(ins); - - case DxbcOpcode::IMul: - return this->emitVectorImul(ins); - - case DxbcOpcode::SinCos: - return this->emitVectorSinCos(ins); - - case DxbcOpcode::Sample: + case DxbcInstClass::TextureSample: return this->emitSample(ins); - case DxbcOpcode::Ret: - return this->emitRet(ins); + case DxbcInstClass::VectorAlu: + return this->emitVectorAlu(ins); + + case DxbcInstClass::VectorCmov: + return this->emitVectorCmov(ins); + + case DxbcInstClass::VectorCmp: + return this->emitVectorCmp(ins); + + case DxbcInstClass::VectorDot: + return this->emitVectorDot(ins); + + case DxbcInstClass::VectorImul: + return this->emitVectorImul(ins); + + case DxbcInstClass::VectorSinCos: + return this->emitVectorSinCos(ins); default: Logger::warn( - str::format("DxbcCompiler: Unhandled opcode: ", + str::format("DxbcCompiler: Unhandled opcode class: ", ins.op)); } } @@ -171,6 +124,42 @@ namespace dxvk { } + void DxbcCompiler::emitDcl(const DxbcShaderInstruction& ins) { + switch (ins.op) { + case DxbcOpcode::DclGlobalFlags: + return this->emitDclGlobalFlags(ins); + + case DxbcOpcode::DclTemps: + return this->emitDclTemps(ins); + + case DxbcOpcode::DclInput: + case DxbcOpcode::DclInputSgv: + case DxbcOpcode::DclInputSiv: + case DxbcOpcode::DclInputPs: + case DxbcOpcode::DclInputPsSgv: + case DxbcOpcode::DclInputPsSiv: + case DxbcOpcode::DclOutput: + case DxbcOpcode::DclOutputSgv: + case DxbcOpcode::DclOutputSiv: + return this->emitDclInterfaceReg(ins); + + case DxbcOpcode::DclConstantBuffer: + return this->emitDclConstantBuffer(ins); + + case DxbcOpcode::DclSampler: + return this->emitDclSampler(ins); + + case DxbcOpcode::DclResource: + return this->emitDclResource(ins); + + default: + Logger::warn( + str::format("DxbcCompiler: Unhandled opcode: ", + ins.op)); + } + } + + void DxbcCompiler::emitDclGlobalFlags(const DxbcShaderInstruction& ins) { // TODO implement properly } @@ -962,13 +951,191 @@ namespace dxvk { emitRegisterStore(ins.dst[0], result); } + + void DxbcCompiler::emitControlFlowIf(const DxbcShaderInstruction& ins) { + // Load the first component of the condition + // operand and perform a zero test on it. + const DxbcRegisterValue condition = emitRegisterLoad( + ins.src[0], DxbcRegMask(true, false, false, false)); - void DxbcCompiler::emitRet(const DxbcShaderInstruction& ins) { + const DxbcRegisterValue zeroTest = emitRegisterZeroTest( + condition, ins.controls.zeroTest); + + // Declare the 'if' block. We do not know if there + // will be an 'else' block or not, so we'll assume + // that there is one and leave it empty otherwise. + DxbcCfgBlock block; + block.type = DxbcCfgBlockType::If; + block.b_if.labelIf = m_module.allocateId(); + block.b_if.labelElse = m_module.allocateId(); + block.b_if.labelEnd = m_module.allocateId(); + block.b_if.hadElse = false; + m_controlFlowBlocks.push_back(block); + + m_module.opSelectionMerge( + block.b_if.labelEnd, + spv::SelectionControlMaskNone); + + m_module.opBranchConditional( + zeroTest.id, + block.b_if.labelIf, + block.b_if.labelElse); + + m_module.opLabel(block.b_if.labelIf); + } + + + void DxbcCompiler::emitControlFlowElse(const DxbcShaderInstruction& ins) { + if (m_controlFlowBlocks.size() == 0 + || m_controlFlowBlocks.back().type != DxbcCfgBlockType::If + || m_controlFlowBlocks.back().b_if.hadElse) + throw DxvkError("DxbcCompiler: 'Else' without 'If' found"); + + // Set the 'Else' flag so that we do + // not insert a dummy block on 'EndIf' + DxbcCfgBlock& block = m_controlFlowBlocks.back(); + block.b_if.hadElse = true; + + // Close the 'If' block by branching to + // the merge block we declared earlier + m_module.opBranch(block.b_if.labelEnd); + m_module.opLabel (block.b_if.labelElse); + } + + + void DxbcCompiler::emitControlFlowEndIf(const DxbcShaderInstruction& ins) { + if (m_controlFlowBlocks.size() == 0 + || m_controlFlowBlocks.back().type != DxbcCfgBlockType::If) + throw DxvkError("DxbcCompiler: 'EndIf' without 'If' found"); + + // Remove the block from the stack, it's closed + const DxbcCfgBlock block = m_controlFlowBlocks.back(); + m_controlFlowBlocks.pop_back(); + + // End the active 'if' or 'else' block + m_module.opBranch(block.b_if.labelEnd); + + // If there was no 'else' block in this construct, we still + // have to declare it because we used it as a branch target. + if (!block.b_if.hadElse) { + m_module.opLabel (block.b_if.labelElse); + m_module.opBranch(block.b_if.labelEnd); + } + + // Declare the merge block + m_module.opLabel(block.b_if.labelEnd); + } + + + void DxbcCompiler::emitControlFlowLoop(const DxbcShaderInstruction& ins) { + // Declare the 'loop' block + DxbcCfgBlock block; + block.type = DxbcCfgBlockType::Loop; + block.b_loop.labelHeader = m_module.allocateId(); + block.b_loop.labelBegin = m_module.allocateId(); + block.b_loop.labelContinue = m_module.allocateId(); + block.b_loop.labelBreak = m_module.allocateId(); + m_controlFlowBlocks.push_back(block); + + m_module.opBranch(block.b_loop.labelHeader); + m_module.opLabel (block.b_loop.labelHeader); + + m_module.opLoopMerge( + block.b_loop.labelBreak, + block.b_loop.labelContinue, + spv::LoopControlMaskNone); + + m_module.opBranch(block.b_loop.labelBegin); + m_module.opLabel (block.b_loop.labelBegin); + } + + + void DxbcCompiler::emitControlFlowEndLoop(const DxbcShaderInstruction& ins) { + if (m_controlFlowBlocks.size() == 0 + || m_controlFlowBlocks.back().type != DxbcCfgBlockType::Loop) + throw DxvkError("DxbcCompiler: 'EndLoop' without 'Loop' found"); + + // Remove the block from the stack, it's closed + const DxbcCfgBlock block = m_controlFlowBlocks.back(); + m_controlFlowBlocks.pop_back(); + + // Declare the continue block + m_module.opBranch(block.b_loop.labelContinue); + m_module.opLabel (block.b_loop.labelContinue); + + // Declare the merge block + m_module.opBranch(block.b_loop.labelHeader); + m_module.opLabel (block.b_loop.labelBreak); + } + + + void DxbcCompiler::emitControlFlowBreakc(const DxbcShaderInstruction& ins) { + DxbcCfgBlock* loopBlock = cfgFindLoopBlock(); + + if (loopBlock == nullptr) + throw DxvkError("DxbcCompiler: 'Breakc' outside 'Loop' found"); + + // Perform zero test on the first component of the condition + const DxbcRegisterValue condition = emitRegisterLoad( + ins.src[0], DxbcRegMask(true, false, false, false)); + + const DxbcRegisterValue zeroTest = emitRegisterZeroTest( + condition, ins.controls.zeroTest); + + // We basically have to wrap this into an 'if' block + const uint32_t breakBlock = m_module.allocateId(); + const uint32_t mergeBlock = m_module.allocateId(); + + m_module.opSelectionMerge(mergeBlock, + spv::SelectionControlMaskNone); + + m_module.opBranchConditional( + zeroTest.id, breakBlock, mergeBlock); + + m_module.opLabel(breakBlock); + m_module.opBranch(loopBlock->b_loop.labelBreak); + + m_module.opLabel(mergeBlock); + } + + + void DxbcCompiler::emitControlFlowRet(const DxbcShaderInstruction& ins) { // TODO implement properly m_module.opReturn(); m_module.functionEnd(); } + + void DxbcCompiler::emitControlFlow(const DxbcShaderInstruction& ins) { + switch (ins.op) { + case DxbcOpcode::If: + return this->emitControlFlowIf(ins); + + case DxbcOpcode::Else: + return this->emitControlFlowElse(ins); + + case DxbcOpcode::EndIf: + return this->emitControlFlowEndIf(ins); + + case DxbcOpcode::Loop: + return this->emitControlFlowLoop(ins); + + case DxbcOpcode::EndLoop: + return this->emitControlFlowEndLoop(ins); + + case DxbcOpcode::Breakc: + return this->emitControlFlowBreakc(ins); + + case DxbcOpcode::Ret: + return this->emitControlFlowRet(ins); + + default: + Logger::warn(str::format( + "DxbcCompiler: Unhandled instruction: ", + ins.op)); + } + } + DxbcRegisterValue DxbcCompiler::emitRegisterBitcast( DxbcRegisterValue srcValue, @@ -1131,6 +1298,23 @@ namespace dxvk { } + DxbcRegisterValue DxbcCompiler::emitRegisterZeroTest( + DxbcRegisterValue value, + DxbcZeroTest test) { + DxbcRegisterValue result; + result.type.ctype = DxbcScalarType::Bool; + result.type.ccount = 1; + + const uint32_t zeroId = m_module.constu32(0u); + const uint32_t typeId = getVectorTypeId(result.type); + + result.id = test == DxbcZeroTest::TestZ + ? m_module.opIEqual (typeId, value.id, zeroId) + : m_module.opINotEqual(typeId, value.id, zeroId); + return result; + } + + DxbcRegisterValue DxbcCompiler::emitSrcOperandModifiers( DxbcRegisterValue value, DxbcRegModifiers modifiers) { @@ -1433,7 +1617,7 @@ namespace dxvk { default: Logger::warn(str::format( - "dxbc: Unhandled vertex sv output: ", + "DxbcCompiler: Unhandled vertex sv output: ", svMapping.sv)); } } @@ -1538,14 +1722,26 @@ namespace dxvk { } + DxbcCfgBlock* DxbcCompiler::cfgFindLoopBlock() { + for (auto cur = m_controlFlowBlocks.rbegin(); + cur != m_controlFlowBlocks.rend(); cur++) { + if (cur->type == DxbcCfgBlockType::Loop) + return &(*cur); + } + + return nullptr; + } + + uint32_t DxbcCompiler::getScalarTypeId(DxbcScalarType type) { switch (type) { - case DxbcScalarType::Uint32: return m_module.defIntType(32, 0); - case DxbcScalarType::Uint64: return m_module.defIntType(64, 0); - case DxbcScalarType::Sint32: return m_module.defIntType(32, 1); - case DxbcScalarType::Sint64: return m_module.defIntType(64, 1); + case DxbcScalarType::Uint32: return m_module.defIntType(32, 0); + case DxbcScalarType::Uint64: return m_module.defIntType(64, 0); + case DxbcScalarType::Sint32: return m_module.defIntType(32, 1); + case DxbcScalarType::Sint64: return m_module.defIntType(64, 1); case DxbcScalarType::Float32: return m_module.defFloatType(32); case DxbcScalarType::Float64: return m_module.defFloatType(64); + case DxbcScalarType::Bool: return m_module.defBoolType(); } throw DxvkError("DxbcCompiler: Invalid scalar type"); diff --git a/src/dxbc/dxbc_compiler.h b/src/dxbc/dxbc_compiler.h index a10332b8d..58fae510b 100644 --- a/src/dxbc/dxbc_compiler.h +++ b/src/dxbc/dxbc_compiler.h @@ -82,6 +82,37 @@ namespace dxvk { }; + enum class DxbcCfgBlockType : uint32_t { + If, Loop, + }; + + + struct DxbcCfgBlockIf { + uint32_t labelIf; + uint32_t labelElse; + uint32_t labelEnd; + bool hadElse; + }; + + + struct DxbcCfgBlockLoop { + uint32_t labelHeader; + uint32_t labelBegin; + uint32_t labelContinue; + uint32_t labelBreak; + }; + + + struct DxbcCfgBlock { + DxbcCfgBlockType type; + + union { + DxbcCfgBlockIf b_if; + DxbcCfgBlockLoop b_loop; + }; + }; + + /** * \brief DXBC to SPIR-V shader compiler * @@ -149,6 +180,11 @@ namespace dxvk { std::array m_samplers; std::array m_textures; + /////////////////////////////////////////////// + // Control flow information. Stores labels for + // currently active if-else blocks and loops. + std::vector m_controlFlowBlocks; + /////////////////////////////////////////////////////////// // Array of input values. Since v# registers are indexable // in DXBC, we need to copy them into an array first. @@ -173,14 +209,17 @@ namespace dxvk { ///////////////////////////////////////////////////// // Shader interface and metadata declaration methods + void emitDcl( + const DxbcShaderInstruction& ins); + void emitDclGlobalFlags( - const DxbcShaderInstruction& ins); + const DxbcShaderInstruction& ins); void emitDclTemps( - const DxbcShaderInstruction& ins); + const DxbcShaderInstruction& ins); void emitDclInterfaceReg( - const DxbcShaderInstruction& ins); + const DxbcShaderInstruction& ins); void emitDclInput( uint32_t regIdx, @@ -228,9 +267,31 @@ namespace dxvk { void emitSample( const DxbcShaderInstruction& ins); - void emitRet( + ///////////////////////////////////// + // Control flow instruction handlers + void emitControlFlowIf( const DxbcShaderInstruction& ins); + void emitControlFlowElse( + const DxbcShaderInstruction& ins); + + void emitControlFlowEndIf( + const DxbcShaderInstruction& ins); + + void emitControlFlowLoop( + const DxbcShaderInstruction& ins); + + void emitControlFlowEndLoop( + const DxbcShaderInstruction& ins); + + void emitControlFlowBreakc( + const DxbcShaderInstruction& ins); + + void emitControlFlowRet( + const DxbcShaderInstruction& ins); + + void emitControlFlow( + const DxbcShaderInstruction& ins); ///////////////////////////////////////// // Generic register manipulation methods @@ -262,6 +323,10 @@ namespace dxvk { DxbcRegisterValue emitRegisterNegate( DxbcRegisterValue value); + DxbcRegisterValue emitRegisterZeroTest( + DxbcRegisterValue value, + DxbcZeroTest test); + DxbcRegisterValue emitSrcOperandModifiers( DxbcRegisterValue value, DxbcRegModifiers modifiers); @@ -333,6 +398,10 @@ namespace dxvk { uint32_t emitNewVariable( const DxbcRegisterInfo& info); + ///////////////////////////////////// + // Control flow block search methods + DxbcCfgBlock* cfgFindLoopBlock(); + /////////////////////////// // Type definition methods uint32_t getScalarTypeId( diff --git a/src/dxbc/dxbc_decoder.cpp b/src/dxbc/dxbc_decoder.cpp index 42d1d986a..5b33a064f 100644 --- a/src/dxbc/dxbc_decoder.cpp +++ b/src/dxbc/dxbc_decoder.cpp @@ -122,6 +122,7 @@ namespace dxvk { // operands. Doing this mostly automatically means that // the compiler can rely on the operands being valid. const DxbcInstFormat format = dxbcInstructionFormat(m_instruction.op); + m_instruction.opClass = format.instructionClass; for (uint32_t i = 0; i < format.operandCount; i++) this->decodeOperand(code, format.operands[i]); diff --git a/src/dxbc/dxbc_decoder.h b/src/dxbc/dxbc_decoder.h index c27bfe059..ad5b871eb 100644 --- a/src/dxbc/dxbc_decoder.h +++ b/src/dxbc/dxbc_decoder.h @@ -154,7 +154,7 @@ namespace dxvk { struct DxbcRegister { DxbcOperandType type; DxbcScalarType dataType; - DxbcComponentCount componentCount; + DxbcComponentCount componentCount; uint32_t idxDim; DxbcRegIndex idx[DxbcMaxRegIndexDim]; @@ -225,6 +225,7 @@ namespace dxvk { */ struct DxbcShaderInstruction { DxbcOpcode op; + DxbcInstClass opClass; DxbcOpModifiers modifiers; DxbcShaderOpcodeControls controls; DxbcShaderSampleControls sampleControls; diff --git a/src/dxbc/dxbc_defs.cpp b/src/dxbc/dxbc_defs.cpp index 854924f29..219209178 100644 --- a/src/dxbc/dxbc_defs.cpp +++ b/src/dxbc/dxbc_defs.cpp @@ -12,19 +12,27 @@ namespace dxvk { /* And */ { }, /* Break */ - { }, + { 0, DxbcInstClass::ControlFlow }, /* Breakc */ - { }, + { 1, DxbcInstClass::ControlFlow, { + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, /* Call */ - { }, + { 1, DxbcInstClass::ControlFlow, { + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, /* Callc */ - { }, + { 2, DxbcInstClass::ControlFlow, { + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, /* Case */ { }, /* Continue */ - { }, + { 0, DxbcInstClass::ControlFlow }, /* Continuec */ - { }, + { 1, DxbcInstClass::ControlFlow, { + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, /* Cut */ { }, /* Default */ @@ -60,15 +68,15 @@ namespace dxvk { { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, } }, /* Else */ - { }, + { 0, DxbcInstClass::ControlFlow }, /* Emit */ { }, /* EmitThenCut */ { }, /* EndIf */ - { }, + { 0, DxbcInstClass::ControlFlow }, /* EndLoop */ - { }, + { 0, DxbcInstClass::ControlFlow }, /* EndSwitch */ { }, /* Eq */ @@ -101,7 +109,9 @@ namespace dxvk { { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, } }, /* If */ - { }, + { 1, DxbcInstClass::ControlFlow, { + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, /* IEq */ { 3, DxbcInstClass::VectorCmp, { { DxbcOperandKind::DstReg, DxbcScalarType::Sint32 }, @@ -175,7 +185,7 @@ namespace dxvk { { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, } }, /* Loop */ - { }, + { 0, DxbcInstClass::ControlFlow }, /* Lt */ { 3, DxbcInstClass::VectorCmp, { { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, @@ -238,7 +248,9 @@ namespace dxvk { /* Ret */ { 0, DxbcInstClass::ControlFlow }, /* Retc */ - { }, + { 1, DxbcInstClass::ControlFlow, { + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, /* RoundNe */ { }, /* RoundNi */ diff --git a/src/dxbc/dxbc_defs.h b/src/dxbc/dxbc_defs.h index b39043f36..61205477b 100644 --- a/src/dxbc/dxbc_defs.h +++ b/src/dxbc/dxbc_defs.h @@ -29,6 +29,7 @@ namespace dxvk { */ enum class DxbcInstClass { Declaration, ///< Interface or resource declaration + ControlFlow, ///< Control flow instructions TextureSample, ///< Texture sampling instruction VectorAlu, ///< Component-wise vector instructions VectorCmov, ///< Component-wise conditional move @@ -36,7 +37,6 @@ namespace dxvk { VectorDot, ///< Dot product instruction VectorImul, ///< Component-wise integer multiplication VectorSinCos, ///< Sine and Cosine instruction - ControlFlow, ///< Control flow instructions Undefined, ///< Instruction code not defined }; diff --git a/src/dxbc/dxbc_enums.h b/src/dxbc/dxbc_enums.h index 883db2eb4..86cddcb31 100644 --- a/src/dxbc/dxbc_enums.h +++ b/src/dxbc/dxbc_enums.h @@ -470,6 +470,7 @@ namespace dxvk { Sint64 = 3, Float32 = 4, Float64 = 5, + Bool = 6, }; } \ No newline at end of file diff --git a/src/spirv/spirv_module.cpp b/src/spirv/spirv_module.cpp index a362aa487..ff33582f6 100644 --- a/src/spirv/spirv_module.cpp +++ b/src/spirv/spirv_module.cpp @@ -1267,6 +1267,44 @@ namespace dxvk { } + void SpirvModule::opLoopMerge( + uint32_t mergeBlock, + uint32_t continueTarget, + uint32_t loopControl) { + m_code.putIns (spv::OpLoopMerge, 4); + m_code.putWord(mergeBlock); + m_code.putWord(continueTarget); + m_code.putWord(loopControl); + } + + + void SpirvModule::opSelectionMerge( + uint32_t mergeBlock, + uint32_t selectionControl) { + m_code.putIns (spv::OpSelectionMerge, 3); + m_code.putWord(mergeBlock); + m_code.putWord(selectionControl); + } + + + void SpirvModule::opBranch( + uint32_t label) { + m_code.putIns (spv::OpBranch, 2); + m_code.putWord(label); + } + + + void SpirvModule::opBranchConditional( + uint32_t condition, + uint32_t trueLabel, + uint32_t falseLabel) { + m_code.putIns (spv::OpBranchConditional, 4); + m_code.putWord(condition); + m_code.putWord(trueLabel); + m_code.putWord(falseLabel); + } + + void SpirvModule::opReturn() { m_code.putIns (spv::OpReturn, 1); } diff --git a/src/spirv/spirv_module.h b/src/spirv/spirv_module.h index 6a9cc6211..f99fb6ef8 100644 --- a/src/spirv/spirv_module.h +++ b/src/spirv/spirv_module.h @@ -434,6 +434,23 @@ namespace dxvk { uint32_t sampledImage, uint32_t coordinates); + void opLoopMerge( + uint32_t mergeBlock, + uint32_t continueTarget, + uint32_t loopControl); + + void opSelectionMerge( + uint32_t mergeBlock, + uint32_t selectionControl); + + void opBranch( + uint32_t label); + + void opBranchConditional( + uint32_t condition, + uint32_t trueLabel, + uint32_t falseLabel); + void opReturn(); private: