/** ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** * Copyright 2015 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ // Contents originally forked from: // https://github.com/KhronosGroup/glslang/ // // Copyright (C) 2014 LunarG, Inc. // // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions // are met: // // Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following // disclaimer in the documentation and/or other materials provided // with the distribution. // // Neither the name of 3Dlabs Inc. Ltd. nor the names of its // contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE // COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "xenia/gpu/spirv/spv_emitter.h" #include #include "xenia/base/assert.h" #include "xenia/base/logging.h" namespace xe { namespace gpu { namespace spirv { SpvEmitter::SpvEmitter() { ClearAccessChain(); } SpvEmitter::~SpvEmitter() = default; Id SpvEmitter::ImportExtendedInstructions(const char* name) { auto import = new Instruction(AllocateUniqueId(), NoType, Op::OpExtInstImport); import->AddStringOperand(name); imports_.push_back(import); return import->result_id(); } // For creating new grouped_types_ (will return old type if the requested one // was already made). Id SpvEmitter::MakeVoidType() { Instruction* type; auto& grouped_type = grouped_types_[static_cast(Op::OpTypeVoid)]; if (grouped_type.empty()) { type = new Instruction(AllocateUniqueId(), NoType, Op::OpTypeVoid); grouped_type.push_back(type); constants_types_globals_.push_back(type); module_.MapInstruction(type); } else { type = grouped_type.back(); } return type->result_id(); } Id SpvEmitter::MakeBoolType() { Instruction* type; auto& grouped_type = grouped_types_[static_cast(Op::OpTypeBool)]; if (grouped_type.empty()) { type = new Instruction(AllocateUniqueId(), NoType, Op::OpTypeBool); grouped_type.push_back(type); constants_types_globals_.push_back(type); module_.MapInstruction(type); } else { type = grouped_type.back(); } return type->result_id(); } Id SpvEmitter::MakeSamplerType() { Instruction* type; auto& grouped_type = grouped_types_[static_cast(Op::OpTypeSampler)]; if (grouped_type.empty()) { type = new Instruction(AllocateUniqueId(), NoType, Op::OpTypeSampler); grouped_type.push_back(type); constants_types_globals_.push_back(type); module_.MapInstruction(type); } else { type = grouped_type.back(); } return type->result_id(); } Id SpvEmitter::MakePointer(spv::StorageClass storage_class, Id pointee) { // try to find it auto& grouped_type = grouped_types_[static_cast(Op::OpTypePointer)]; for (auto& type : grouped_type) { if (type->immediate_operand(0) == (unsigned)storage_class && type->id_operand(1) == pointee) { return type->result_id(); } } // not found, make it auto type = new Instruction(AllocateUniqueId(), NoType, Op::OpTypePointer); type->AddImmediateOperand(storage_class); type->AddIdOperand(pointee); grouped_type.push_back(type); constants_types_globals_.push_back(type); module_.MapInstruction(type); return type->result_id(); } Id SpvEmitter::MakeIntegerType(int bit_width, bool is_signed) { // try to find it auto& grouped_type = grouped_types_[static_cast(Op::OpTypeInt)]; for (auto& type : grouped_type) { if (type->immediate_operand(0) == (unsigned)bit_width && type->immediate_operand(1) == (is_signed ? 1u : 0u)) { return type->result_id(); } } // not found, make it auto type = new Instruction(AllocateUniqueId(), NoType, Op::OpTypeInt); type->AddImmediateOperand(bit_width); type->AddImmediateOperand(is_signed ? 1 : 0); grouped_type.push_back(type); constants_types_globals_.push_back(type); module_.MapInstruction(type); return type->result_id(); } Id SpvEmitter::MakeFloatType(int bit_width) { // try to find it auto& grouped_type = grouped_types_[static_cast(Op::OpTypeFloat)]; for (auto& type : grouped_type) { if (type->immediate_operand(0) == (unsigned)bit_width) { return type->result_id(); } } // not found, make it auto type = new Instruction(AllocateUniqueId(), NoType, Op::OpTypeFloat); type->AddImmediateOperand(bit_width); grouped_type.push_back(type); constants_types_globals_.push_back(type); module_.MapInstruction(type); return type->result_id(); } // Make a struct without checking for duplication. // See makeStructResultType() for non-decorated structs // needed as the result of some instructions, which does // check for duplicates. Id SpvEmitter::MakeStructType(std::initializer_list members, const char* name) { // Don't look for previous one, because in the general case, // structs can be duplicated except for decorations. // not found, make it Instruction* type = new Instruction(AllocateUniqueId(), NoType, Op::OpTypeStruct); type->AddIdOperands(members); auto& grouped_type = grouped_types_[static_cast(Op::OpTypeStruct)]; grouped_type.push_back(type); constants_types_globals_.push_back(type); module_.MapInstruction(type); AddName(type->result_id(), name); return type->result_id(); } // Make a struct for the simple results of several instructions, // checking for duplication. Id SpvEmitter::MakePairStructType(Id type0, Id type1) { // try to find it auto& grouped_type = grouped_types_[static_cast(Op::OpTypeStruct)]; for (auto& type : grouped_type) { if (type->operand_count() != 2) { continue; } if (type->id_operand(0) != type0 || type->id_operand(1) != type1) { continue; } return type->result_id(); } // not found, make it return MakeStructType({type0, type1}, "ResType"); } Id SpvEmitter::MakeVectorType(Id component_type, int component_count) { // try to find it auto& grouped_type = grouped_types_[static_cast(Op::OpTypeVector)]; for (auto& type : grouped_type) { if (type->id_operand(0) == component_type && type->immediate_operand(1) == (unsigned)component_count) { return type->result_id(); } } // not found, make it auto type = new Instruction(AllocateUniqueId(), NoType, Op::OpTypeVector); type->AddIdOperand(component_type); type->AddImmediateOperand(component_count); grouped_type.push_back(type); constants_types_globals_.push_back(type); module_.MapInstruction(type); return type->result_id(); } Id SpvEmitter::MakeMatrix2DType(Id component_type, int cols, int rows) { assert(cols <= kMaxMatrixSize && rows <= kMaxMatrixSize); Id column = MakeVectorType(component_type, rows); // try to find it auto& grouped_type = grouped_types_[static_cast(Op::OpTypeMatrix)]; for (auto& type : grouped_type) { if (type->id_operand(0) == column && type->immediate_operand(1) == (unsigned)cols) { return type->result_id(); } } // not found, make it auto type = new Instruction(AllocateUniqueId(), NoType, Op::OpTypeMatrix); type->AddIdOperand(column); type->AddImmediateOperand(cols); grouped_type.push_back(type); constants_types_globals_.push_back(type); module_.MapInstruction(type); return type->result_id(); } Id SpvEmitter::MakeArrayType(Id element_type, int length) { // First, we need a constant instruction for the size Id length_id = MakeUintConstant(length); // try to find existing type auto& grouped_type = grouped_types_[static_cast(Op::OpTypeArray)]; for (auto& type : grouped_type) { if (type->id_operand(0) == element_type && type->id_operand(1) == length_id) { return type->result_id(); } } // not found, make it auto type = new Instruction(AllocateUniqueId(), NoType, Op::OpTypeArray); type->AddIdOperand(element_type); type->AddIdOperand(length_id); grouped_type.push_back(type); constants_types_globals_.push_back(type); module_.MapInstruction(type); return type->result_id(); } Id SpvEmitter::MakeRuntimeArray(Id element_type) { auto type = new Instruction(AllocateUniqueId(), NoType, Op::OpTypeRuntimeArray); type->AddIdOperand(element_type); constants_types_globals_.push_back(type); module_.MapInstruction(type); return type->result_id(); } Id SpvEmitter::MakeFunctionType(Id return_type, std::initializer_list param_types) { // try to find it auto& grouped_type = grouped_types_[static_cast(Op::OpTypeFunction)]; for (auto& type : grouped_type) { if (type->id_operand(0) == return_type && param_types.size() == type->operand_count() - 1) { bool mismatch = false; for (int i = 0; i < param_types.size(); ++i) { if (type->id_operand(i + 1) != *(param_types.begin() + i)) { mismatch = true; break; } } if (!mismatch) { return type->result_id(); } } } // not found, make it auto type = new Instruction(AllocateUniqueId(), NoType, Op::OpTypeFunction); type->AddIdOperand(return_type); type->AddIdOperands(param_types); grouped_type.push_back(type); constants_types_globals_.push_back(type); module_.MapInstruction(type); return type->result_id(); } Id SpvEmitter::MakeImageType(Id sampled_type, spv::Dim dim, bool has_depth, bool is_arrayed, bool is_multisampled, int sampled, spv::ImageFormat format) { // try to find it auto& grouped_type = grouped_types_[static_cast(Op::OpTypeImage)]; for (auto& type : grouped_type) { if (type->id_operand(0) == sampled_type && type->immediate_operand(1) == (unsigned int)dim && type->immediate_operand(2) == (has_depth ? 1u : 0u) && type->immediate_operand(3) == (is_arrayed ? 1u : 0u) && type->immediate_operand(4) == (is_multisampled ? 1u : 0u) && type->immediate_operand(5) == sampled && type->immediate_operand(6) == static_cast(format)) { return type->result_id(); } } // not found, make it auto type = new Instruction(AllocateUniqueId(), NoType, Op::OpTypeImage); type->AddIdOperand(sampled_type); type->AddImmediateOperand(dim); type->AddImmediateOperand(has_depth ? 1 : 0); type->AddImmediateOperand(is_arrayed ? 1 : 0); type->AddImmediateOperand(is_multisampled ? 1 : 0); type->AddImmediateOperand(sampled); type->AddImmediateOperand(format); grouped_type.push_back(type); constants_types_globals_.push_back(type); module_.MapInstruction(type); return type->result_id(); } Id SpvEmitter::MakeSampledImageType(Id image_type) { // try to find it auto& grouped_type = grouped_types_[static_cast(Op::OpTypeSampledImage)]; for (auto& type : grouped_type) { if (type->id_operand(0) == image_type) { return type->result_id(); } } // not found, make it auto type = new Instruction(AllocateUniqueId(), NoType, Op::OpTypeSampledImage); type->AddIdOperand(image_type); grouped_type.push_back(type); constants_types_globals_.push_back(type); module_.MapInstruction(type); return type->result_id(); } Id SpvEmitter::GetDerefTypeId(Id result_id) const { Id type_id = GetTypeId(result_id); assert(IsPointerType(type_id)); return module_.instruction(type_id)->immediate_operand(1); } Op SpvEmitter::GetMostBasicTypeClass(Id type_id) const { auto instr = module_.instruction(type_id); Op type_class = instr->opcode(); switch (type_class) { case Op::OpTypeVoid: case Op::OpTypeBool: case Op::OpTypeInt: case Op::OpTypeFloat: case Op::OpTypeStruct: return type_class; case Op::OpTypeVector: case Op::OpTypeMatrix: case Op::OpTypeArray: case Op::OpTypeRuntimeArray: return GetMostBasicTypeClass(instr->id_operand(0)); case Op::OpTypePointer: return GetMostBasicTypeClass(instr->id_operand(1)); default: assert(0); return Op::OpTypeFloat; } } int SpvEmitter::GetTypeComponentCount(Id type_id) const { auto instr = module_.instruction(type_id); switch (instr->opcode()) { case Op::OpTypeBool: case Op::OpTypeInt: case Op::OpTypeFloat: return 1; case Op::OpTypeVector: case Op::OpTypeMatrix: return instr->immediate_operand(1); default: assert(0); return 1; } } // Return the lowest-level type of scalar that an homogeneous composite is made // out of. // Typically, this is just to find out if something is made out of ints or // floats. // However, it includes returning a structure, if say, it is an array of // structure. Id SpvEmitter::GetScalarTypeId(Id type_id) const { auto instr = module_.instruction(type_id); Op type_class = instr->opcode(); switch (type_class) { case Op::OpTypeVoid: case Op::OpTypeBool: case Op::OpTypeInt: case Op::OpTypeFloat: case Op::OpTypeStruct: return instr->result_id(); case Op::OpTypeVector: case Op::OpTypeMatrix: case Op::OpTypeArray: case Op::OpTypeRuntimeArray: case Op::OpTypePointer: return GetScalarTypeId(GetContainedTypeId(type_id)); default: assert(0); return NoResult; } } // Return the type of 'member' of a composite. Id SpvEmitter::GetContainedTypeId(Id type_id, int member) const { auto instr = module_.instruction(type_id); Op type_class = instr->opcode(); switch (type_class) { case Op::OpTypeVector: case Op::OpTypeMatrix: case Op::OpTypeArray: case Op::OpTypeRuntimeArray: return instr->id_operand(0); case Op::OpTypePointer: return instr->id_operand(1); case Op::OpTypeStruct: return instr->id_operand(member); default: assert(0); return NoResult; } } // Return the immediately contained type of a given composite type. Id SpvEmitter::GetContainedTypeId(Id type_id) const { return GetContainedTypeId(type_id, 0); } // See if a scalar constant of this type has already been created, so it // can be reused rather than duplicated. (Required by the specification). Id SpvEmitter::FindScalarConstant(Op type_class, Op opcode, Id type_id, uint32_t value) const { auto& grouped_constant = grouped_constants_[static_cast(type_class)]; for (auto constant : grouped_constant) { if (constant->opcode() == opcode && constant->type_id() == type_id && constant->immediate_operand(0) == value) { return constant->result_id(); } } return 0; } // Version of findScalarConstant (see above) for scalars that take two operands // (e.g. a 'double'). Id SpvEmitter::FindScalarConstant(Op type_class, Op opcode, Id type_id, uint32_t v1, uint32_t v2) const { auto& grouped_constant = grouped_constants_[static_cast(type_class)]; for (auto constant : grouped_constant) { if (constant->opcode() == opcode && constant->type_id() == type_id && constant->immediate_operand(0) == v1 && constant->immediate_operand(1) == v2) { return constant->result_id(); } } return 0; } // Return true if consuming 'opcode' means consuming a constant. // "constant" here means after final transform to executable code, // the value consumed will be a constant, so includes specialization. bool SpvEmitter::IsConstantOpCode(Op opcode) const { switch (opcode) { case Op::OpUndef: case Op::OpConstantTrue: case Op::OpConstantFalse: case Op::OpConstant: case Op::OpConstantComposite: case Op::OpConstantSampler: case Op::OpConstantNull: case Op::OpSpecConstantTrue: case Op::OpSpecConstantFalse: case Op::OpSpecConstant: case Op::OpSpecConstantComposite: case Op::OpSpecConstantOp: return true; default: return false; } } Id SpvEmitter::MakeBoolConstant(bool value, bool is_spec_constant) { Id type_id = MakeBoolType(); Op opcode = is_spec_constant ? (value ? Op::OpSpecConstantTrue : Op::OpSpecConstantFalse) : (value ? Op::OpConstantTrue : Op::OpConstantFalse); // See if we already made it Id existing = 0; auto& grouped_constant = grouped_constants_[static_cast(Op::OpTypeBool)]; for (auto& constant : grouped_constant) { if (constant->type_id() == type_id && constant->opcode() == opcode) { return constant->result_id(); } } // Make it auto c = new Instruction(AllocateUniqueId(), type_id, opcode); constants_types_globals_.push_back(c); grouped_constants_[static_cast(Op::OpTypeBool)].push_back(c); module_.MapInstruction(c); return c->result_id(); } Id SpvEmitter::MakeIntegerConstant(Id type_id, uint32_t value, bool is_spec_constant) { Op opcode = is_spec_constant ? Op::OpSpecConstant : Op::OpConstant; Id existing = FindScalarConstant(Op::OpTypeInt, opcode, type_id, value); if (existing) { return existing; } auto c = new Instruction(AllocateUniqueId(), type_id, opcode); c->AddImmediateOperand(value); constants_types_globals_.push_back(c); grouped_constants_[static_cast(Op::OpTypeInt)].push_back(c); module_.MapInstruction(c); return c->result_id(); } Id SpvEmitter::MakeFloatConstant(float value, bool is_spec_constant) { Op opcode = is_spec_constant ? Op::OpSpecConstant : Op::OpConstant; Id type_id = MakeFloatType(32); uint32_t uint32_value = *reinterpret_cast(&value); Id existing = FindScalarConstant(Op::OpTypeFloat, opcode, type_id, uint32_value); if (existing) { return existing; } auto c = new Instruction(AllocateUniqueId(), type_id, opcode); c->AddImmediateOperand(uint32_value); constants_types_globals_.push_back(c); grouped_constants_[static_cast(Op::OpTypeFloat)].push_back(c); module_.MapInstruction(c); return c->result_id(); } Id SpvEmitter::MakeDoubleConstant(double value, bool is_spec_constant) { Op opcode = is_spec_constant ? Op::OpSpecConstant : Op::OpConstant; Id type_id = MakeFloatType(64); uint64_t uint64_value = *reinterpret_cast(&value); uint32_t op1 = static_cast(uint64_value & 0xFFFFFFFF); uint32_t op2 = static_cast(uint64_value >> 32); Id existing = FindScalarConstant(Op::OpTypeFloat, opcode, type_id, op1, op2); if (existing) { return existing; } auto c = new Instruction(AllocateUniqueId(), type_id, opcode); c->AddImmediateOperand(op1); c->AddImmediateOperand(op2); constants_types_globals_.push_back(c); grouped_constants_[static_cast(Op::OpTypeFloat)].push_back(c); module_.MapInstruction(c); return c->result_id(); } Id SpvEmitter::FindCompositeConstant( Op type_class, std::initializer_list components) const { auto& grouped_constant = grouped_constants_[static_cast(type_class)]; for (auto& constant : grouped_constant) { // same shape? if (constant->operand_count() != components.size()) { continue; } // same contents? bool mismatch = false; for (int op = 0; op < constant->operand_count(); ++op) { if (constant->id_operand(op) != *(components.begin() + op)) { mismatch = true; break; } } if (!mismatch) { return constant->result_id(); } } return NoResult; } Id SpvEmitter::MakeCompositeConstant(Id type_id, std::initializer_list components) { assert(type_id); Op type_class = GetTypeClass(type_id); switch (type_class) { case Op::OpTypeVector: case Op::OpTypeArray: case Op::OpTypeStruct: case Op::OpTypeMatrix: break; default: assert(0); return MakeFloatConstant(0.0); } Id existing = FindCompositeConstant(type_class, components); if (existing) { return existing; } auto c = new Instruction(AllocateUniqueId(), type_id, Op::OpConstantComposite); c->AddIdOperands(components); constants_types_globals_.push_back(c); grouped_constants_[static_cast(type_class)].push_back(c); module_.MapInstruction(c); return c->result_id(); } Instruction* SpvEmitter::AddEntryPoint(spv::ExecutionModel execution_model, Function* entry_point, const char* name) { auto instr = new Instruction(Op::OpEntryPoint); instr->AddImmediateOperand(execution_model); instr->AddIdOperand(entry_point->id()); instr->AddStringOperand(name); entry_points_.push_back(instr); return instr; } // Currently relying on the fact that all 'value' of interest are small // non-negative values. void SpvEmitter::AddExecutionMode(Function* entry_point, spv::ExecutionMode execution_mode, int value1, int value2, int value3) { auto instr = new Instruction(Op::OpExecutionMode); instr->AddIdOperand(entry_point->id()); instr->AddImmediateOperand(execution_mode); if (value1 >= 0) { instr->AddImmediateOperand(value1); } if (value2 >= 0) { instr->AddImmediateOperand(value2); } if (value3 >= 0) { instr->AddImmediateOperand(value3); } execution_modes_.push_back(instr); } void SpvEmitter::AddName(Id target_id, const char* value) { if (!value) { return; } auto instr = new Instruction(Op::OpName); instr->AddIdOperand(target_id); instr->AddStringOperand(value); names_.push_back(instr); } void SpvEmitter::AddMemberName(Id target_id, int member, const char* value) { if (!value) { return; } auto instr = new Instruction(Op::OpMemberName); instr->AddIdOperand(target_id); instr->AddImmediateOperand(member); instr->AddStringOperand(value); names_.push_back(instr); } void SpvEmitter::AddLine(Id target_id, Id file_name, int line_number, int column_number) { auto instr = new Instruction(Op::OpLine); instr->AddIdOperand(target_id); instr->AddIdOperand(file_name); instr->AddImmediateOperand(line_number); instr->AddImmediateOperand(column_number); lines_.push_back(instr); } void SpvEmitter::AddDecoration(Id target_id, spv::Decoration decoration, int num) { if (decoration == static_cast(BadValue)) { return; } auto instr = new Instruction(Op::OpDecorate); instr->AddIdOperand(target_id); instr->AddImmediateOperand(decoration); if (num >= 0) { instr->AddImmediateOperand(num); } decorations_.push_back(instr); } void SpvEmitter::AddMemberDecoration(Id target_id, int member, spv::Decoration decoration, int num) { auto instr = new Instruction(Op::OpMemberDecorate); instr->AddIdOperand(target_id); instr->AddImmediateOperand(member); instr->AddImmediateOperand(decoration); if (num >= 0) { instr->AddImmediateOperand(num); } decorations_.push_back(instr); } Function* SpvEmitter::MakeMainEntry() { assert(!main_function_); Block* entry = nullptr; main_function_ = MakeFunctionEntry(MakeVoidType(), "main", {}, &entry); return main_function_; } Function* SpvEmitter::MakeFunctionEntry(Id return_type, const char* name, std::initializer_list param_types, Block** entry) { Id type_id = MakeFunctionType(return_type, param_types); Id first_param_id = param_types.size() ? AllocateUniqueIds((int)param_types.size()) : 0; auto function = new Function(AllocateUniqueId(), return_type, type_id, first_param_id, module_); if (entry) { *entry = new Block(AllocateUniqueId(), *function); function->push_block(*entry); set_build_point(*entry); } AddName(function->id(), name); return function; } void SpvEmitter::MakeReturn(bool implicit, Id return_value) { if (return_value) { auto inst = new Instruction(NoResult, NoType, Op::OpReturnValue); inst->AddIdOperand(return_value); build_point_->AddInstruction(inst); } else { build_point_->AddInstruction( new Instruction(NoResult, NoType, Op::OpReturn)); } if (!implicit) { CreateAndSetNoPredecessorBlock("post-return"); } } void SpvEmitter::LeaveFunction() { Block* block = build_point_; Function& function = build_point_->parent(); assert(block); // If our function did not contain a return, add a return void now. if (!block->is_terminated()) { // Whether we're in an unreachable (non-entry) block. bool unreachable = function.entry_block() != block && !block->predecessor_count(); if (unreachable) { // Given that this block is at the end of a function, it must be right // after an explicit return, just remove it. function.pop_block(block); } else { // We'll add a return instruction at the end of the current block, // which for a non-void function is really error recovery (?), as the // source being translated should have had an explicit return, which would // have been followed by an unreachable block, which was handled above. if (function.return_type() == MakeVoidType()) { MakeReturn(true); } else { MakeReturn(true, CreateUndefined(function.return_type())); } } } } void SpvEmitter::MakeDiscard() { build_point_->AddInstruction(new Instruction(Op::OpKill)); CreateAndSetNoPredecessorBlock("post-discard"); } Id SpvEmitter::CreateVariable(spv::StorageClass storage_class, Id type, const char* name) { Id pointer_type = MakePointer(storage_class, type); auto instr = new Instruction(AllocateUniqueId(), pointer_type, Op::OpVariable); instr->AddImmediateOperand(storage_class); switch (storage_class) { case spv::StorageClass::Function: // Validation rules require the declaration in the entry block. build_point_->parent().AddLocalVariable(instr); break; default: constants_types_globals_.push_back(instr); module_.MapInstruction(instr); break; } AddName(instr->result_id(), name); return instr->result_id(); } Id SpvEmitter::CreateUndefined(Id type) { auto instr = new Instruction(AllocateUniqueId(), type, Op::OpUndef); build_point_->AddInstruction(instr); return instr->result_id(); } void SpvEmitter::CreateStore(Id pointer_id, Id value_id) { auto instr = new Instruction(Op::OpStore); instr->AddIdOperand(pointer_id); instr->AddIdOperand(value_id); build_point_->AddInstruction(instr); } Id SpvEmitter::CreateLoad(Id pointer_id) { auto instr = new Instruction(AllocateUniqueId(), GetDerefTypeId(pointer_id), Op::OpLoad); instr->AddIdOperand(pointer_id); build_point_->AddInstruction(instr); return instr->result_id(); } Id SpvEmitter::CreateAccessChain(spv::StorageClass storage_class, Id base_id, std::vector index_ids) { // Figure out the final resulting type. auto base_type_id = GetTypeId(base_id); assert(IsPointerType(base_type_id) && index_ids.size()); auto type_id = GetContainedTypeId(base_type_id); for (auto index_id : index_ids) { if (IsStructType(type_id)) { assert(IsConstantScalar(index_id)); type_id = GetContainedTypeId(type_id, GetConstantScalar(index_id)); } else { type_id = GetContainedTypeId(type_id, index_id); } } auto chain_type_id = MakePointer(storage_class, type_id); // Make the instruction auto instr = new Instruction(AllocateUniqueId(), chain_type_id, Op::OpAccessChain); instr->AddIdOperand(base_id); instr->AddIdOperands(index_ids); build_point_->AddInstruction(instr); return instr->result_id(); } Id SpvEmitter::CreateArrayLength(Id struct_id, int array_member) { auto instr = new Instruction(AllocateUniqueId(), MakeIntType(32), Op::OpArrayLength); instr->AddIdOperand(struct_id); instr->AddImmediateOperand(array_member); build_point_->AddInstruction(instr); return instr->result_id(); } Id SpvEmitter::CreateCompositeExtract(Id composite, Id type_id, uint32_t index) { auto instr = new Instruction(AllocateUniqueId(), type_id, Op::OpCompositeExtract); instr->AddIdOperand(composite); instr->AddImmediateOperand(index); build_point_->AddInstruction(instr); return instr->result_id(); } Id SpvEmitter::CreateCompositeExtract(Id composite, Id type_id, std::vector indices) { auto instr = new Instruction(AllocateUniqueId(), type_id, Op::OpCompositeExtract); instr->AddIdOperand(composite); instr->AddImmediateOperands(indices); build_point_->AddInstruction(instr); return instr->result_id(); } Id SpvEmitter::CreateCompositeInsert(Id object, Id composite, Id type_id, uint32_t index) { auto instr = new Instruction(AllocateUniqueId(), type_id, Op::OpCompositeInsert); instr->AddIdOperand(object); instr->AddIdOperand(composite); instr->AddImmediateOperand(index); build_point_->AddInstruction(instr); return instr->result_id(); } Id SpvEmitter::CreateCompositeInsert(Id object, Id composite, Id type_id, std::vector indices) { auto instr = new Instruction(AllocateUniqueId(), type_id, Op::OpCompositeInsert); instr->AddIdOperand(object); instr->AddIdOperand(composite); instr->AddImmediateOperands(indices); build_point_->AddInstruction(instr); return instr->result_id(); } Id SpvEmitter::CreateVectorExtractDynamic(Id vector, Id type_id, Id component_index) { auto instr = new Instruction(AllocateUniqueId(), type_id, Op::OpVectorExtractDynamic); instr->AddIdOperand(vector); instr->AddIdOperand(component_index); build_point_->AddInstruction(instr); return instr->result_id(); } Id SpvEmitter::CreateVectorInsertDynamic(Id vector, Id type_id, Id component, Id component_index) { auto instr = new Instruction(AllocateUniqueId(), type_id, Op::OpVectorInsertDynamic); instr->AddIdOperand(vector); instr->AddIdOperand(component); instr->AddIdOperand(component_index); build_point_->AddInstruction(instr); return instr->result_id(); } void SpvEmitter::CreateNop() { auto instr = new Instruction(spv::Op::OpNop); build_point_->AddInstruction(instr); } void SpvEmitter::CreateControlBarrier( spv::Scope execution_scope, spv::Scope memory_scope, spv::MemorySemanticsMask memory_semantics) { auto instr = new Instruction(Op::OpControlBarrier); instr->AddImmediateOperand(MakeUintConstant(execution_scope)); instr->AddImmediateOperand(MakeUintConstant(memory_scope)); instr->AddImmediateOperand(MakeUintConstant(memory_semantics)); build_point_->AddInstruction(instr); } void SpvEmitter::CreateMemoryBarrier( spv::Scope execution_scope, spv::MemorySemanticsMask memory_semantics) { auto instr = new Instruction(Op::OpMemoryBarrier); instr->AddImmediateOperand(MakeUintConstant(execution_scope)); instr->AddImmediateOperand(MakeUintConstant(memory_semantics)); build_point_->AddInstruction(instr); } Id SpvEmitter::CreateUnaryOp(Op opcode, Id type_id, Id operand) { auto instr = new Instruction(AllocateUniqueId(), type_id, opcode); instr->AddIdOperand(operand); build_point_->AddInstruction(instr); return instr->result_id(); } Id SpvEmitter::CreateBinOp(Op opcode, Id type_id, Id left, Id right) { auto instr = new Instruction(AllocateUniqueId(), type_id, opcode); instr->AddIdOperand(left); instr->AddIdOperand(right); build_point_->AddInstruction(instr); return instr->result_id(); } Id SpvEmitter::CreateTriOp(Op opcode, Id type_id, Id op1, Id op2, Id op3) { auto instr = new Instruction(AllocateUniqueId(), type_id, opcode); instr->AddIdOperand(op1); instr->AddIdOperand(op2); instr->AddIdOperand(op3); build_point_->AddInstruction(instr); return instr->result_id(); } Id SpvEmitter::CreateOp(Op opcode, Id type_id, const std::vector& operands) { auto instr = new Instruction(AllocateUniqueId(), type_id, opcode); instr->AddIdOperands(operands); build_point_->AddInstruction(instr); return instr->result_id(); } Id SpvEmitter::CreateFunctionCall(Function* function, std::vector args) { auto instr = new Instruction(AllocateUniqueId(), function->return_type(), Op::OpFunctionCall); instr->AddIdOperand(function->id()); instr->AddIdOperands(args); build_point_->AddInstruction(instr); return instr->result_id(); } Id SpvEmitter::CreateSwizzle(Id type_id, Id source, std::vector channels) { if (channels.size() == 1) { return CreateCompositeExtract(source, type_id, channels.front()); } auto instr = new Instruction(AllocateUniqueId(), type_id, Op::OpVectorShuffle); assert(IsVector(source)); instr->AddIdOperand(source); instr->AddIdOperand(source); instr->AddImmediateOperands(channels); build_point_->AddInstruction(instr); return instr->result_id(); } Id SpvEmitter::CreateLvalueSwizzle(Id type_id, Id target, Id source, std::vector channels) { assert(GetComponentCount(source) == channels.size()); if (channels.size() == 1 && GetComponentCount(source) == 1) { return CreateCompositeInsert(source, target, type_id, channels.front()); } auto instr = new Instruction(AllocateUniqueId(), type_id, Op::OpVectorShuffle); assert(IsVector(source)); assert(IsVector(target)); instr->AddIdOperand(target); instr->AddIdOperand(source); // Set up an identity shuffle from the base value to the result value. uint32_t components[4] = {0, 1, 2, 3}; // Punch in the l-value swizzle. int component_count = GetComponentCount(target); for (int i = 0; i < (int)channels.size(); ++i) { components[channels[i]] = component_count + i; } // finish the instruction with these components selectors. for (int i = 0; i < component_count; ++i) { instr->AddImmediateOperand(components[i]); } build_point_->AddInstruction(instr); return instr->result_id(); } void SpvEmitter::PromoteScalar(spv::Decoration precision, Id& left, Id& right) { int direction = GetComponentCount(right) - GetComponentCount(left); if (direction > 0) { left = SmearScalar(precision, left, GetTypeId(right)); } else if (direction < 0) { right = SmearScalar(precision, right, GetTypeId(left)); } } Id SpvEmitter::SmearScalar(spv::Decoration precision, Id scalar_value, Id vector_type_id) { assert(GetComponentCount(scalar_value) == 1); int component_count = GetTypeComponentCount(vector_type_id); if (component_count == 1) { return scalar_value; } auto instr = new Instruction(AllocateUniqueId(), vector_type_id, Op::OpCompositeConstruct); for (int i = 0; i < component_count; ++i) { instr->AddIdOperand(scalar_value); } build_point_->AddInstruction(instr); return instr->result_id(); } Id SpvEmitter::CreateExtendedInstructionCall(spv::Decoration precision, Id result_type, Id instruction_set, int instruction_ordinal, std::initializer_list args) { auto instr = new Instruction(AllocateUniqueId(), result_type, Op::OpExtInst); instr->AddIdOperand(instruction_set); instr->AddImmediateOperand(instruction_ordinal); instr->AddIdOperands(args); build_point_->AddInstruction(instr); return instr->result_id(); } // Accept all parameters needed to create a texture instruction. // Create the correct instruction based on the inputs, and make the call. Id SpvEmitter::CreateTextureCall(spv::Decoration precision, Id result_type, bool fetch, bool proj, bool gather, const TextureParameters& parameters) { static const int kMaxTextureArgs = 10; Id tex_args[kMaxTextureArgs] = {}; // Set up the fixed arguments. int arg_count = 0; bool is_explicit = false; tex_args[arg_count++] = parameters.sampler; tex_args[arg_count++] = parameters.coords; if (parameters.depth_ref) { tex_args[arg_count++] = parameters.depth_ref; } if (parameters.comp) { tex_args[arg_count++] = parameters.comp; } // Set up the optional arguments. int opt_arg_index = arg_count; // track which operand, if it exists, is the // mask of optional arguments speculatively // make room for the mask operand. ++arg_count; auto mask = spv::ImageOperandsMask::MaskNone; // the mask operand if (parameters.bias) { mask = mask | spv::ImageOperandsMask::Bias; tex_args[arg_count++] = parameters.bias; } if (parameters.lod) { mask = mask | spv::ImageOperandsMask::Lod; tex_args[arg_count++] = parameters.lod; is_explicit = true; } if (parameters.grad_x) { mask = mask | spv::ImageOperandsMask::Grad; tex_args[arg_count++] = parameters.grad_x; tex_args[arg_count++] = parameters.grad_y; is_explicit = true; } if (parameters.offset) { if (IsConstant(parameters.offset)) { mask = mask | spv::ImageOperandsMask::ConstOffset; } else { mask = mask | spv::ImageOperandsMask::Offset; } tex_args[arg_count++] = parameters.offset; } if (parameters.offsets) { mask = mask | spv::ImageOperandsMask::ConstOffsets; tex_args[arg_count++] = parameters.offsets; } if (parameters.sample) { mask = mask | spv::ImageOperandsMask::Sample; tex_args[arg_count++] = parameters.sample; } if (mask == spv::ImageOperandsMask::MaskNone) { --arg_count; // undo speculative reservation for the mask argument } else { tex_args[opt_arg_index] = static_cast(mask); } // Set up the instruction. Op opcode; opcode = Op::OpImageSampleImplicitLod; if (fetch) { opcode = Op::OpImageFetch; } else if (gather) { if (parameters.depth_ref) { opcode = Op::OpImageDrefGather; } else { opcode = Op::OpImageGather; } } else if (is_explicit) { if (parameters.depth_ref) { if (proj) { opcode = Op::OpImageSampleProjDrefExplicitLod; } else { opcode = Op::OpImageSampleDrefExplicitLod; } } else { if (proj) { opcode = Op::OpImageSampleProjExplicitLod; } else { opcode = Op::OpImageSampleExplicitLod; } } } else { if (parameters.depth_ref) { if (proj) { opcode = Op::OpImageSampleProjDrefImplicitLod; } else { opcode = Op::OpImageSampleDrefImplicitLod; } } else { if (proj) { opcode = Op::OpImageSampleProjImplicitLod; } else { opcode = Op::OpImageSampleImplicitLod; } } } // See if the result type is expecting a smeared result. // This happens when a legacy shadow*() call is made, which gets a vec4 back // instead of a float. Id smeared_type = result_type; if (!IsScalarType(result_type)) { switch (opcode) { case Op::OpImageSampleDrefImplicitLod: case Op::OpImageSampleDrefExplicitLod: case Op::OpImageSampleProjDrefImplicitLod: case Op::OpImageSampleProjDrefExplicitLod: result_type = GetScalarTypeId(result_type); break; default: break; } } // Build the SPIR-V instruction auto instr = new Instruction(AllocateUniqueId(), result_type, opcode); for (int op = 0; op < opt_arg_index; ++op) { instr->AddIdOperand(tex_args[op]); } if (opt_arg_index < arg_count) { instr->AddImmediateOperand(tex_args[opt_arg_index]); } for (int op = opt_arg_index + 1; op < arg_count; ++op) { instr->AddIdOperand(tex_args[op]); } SetPrecision(instr->result_id(), precision); build_point_->AddInstruction(instr); Id result_id = instr->result_id(); // When a smear is needed, do it, as per what was computed above when // result_type was changed to a scalar type. if (result_type != smeared_type) { result_id = SmearScalar(precision, result_id, smeared_type); } return result_id; } Id SpvEmitter::CreateTextureQueryCall(Op opcode, const TextureParameters& parameters) { // Figure out the result type. Id result_type = 0; switch (opcode) { case Op::OpImageQuerySize: case Op::OpImageQuerySizeLod: { int component_count; switch (GetTypeDimensionality(GetImageType(parameters.sampler))) { case spv::Dim::Dim1D: case spv::Dim::Buffer: component_count = 1; break; case spv::Dim::Dim2D: case spv::Dim::Cube: case spv::Dim::Rect: component_count = 2; break; case spv::Dim::Dim3D: component_count = 3; break; case spv::Dim::SubpassData: CheckNotImplemented("input-attachment dim"); break; default: assert(0); break; } if (IsArrayedImageType(GetImageType(parameters.sampler))) { ++component_count; } if (component_count == 1) { result_type = MakeIntType(32); } else { result_type = MakeVectorType(MakeIntType(32), component_count); } break; } case Op::OpImageQueryLod: result_type = MakeVectorType(MakeFloatType(32), 2); break; case Op::OpImageQueryLevels: case Op::OpImageQuerySamples: result_type = MakeIntType(32); break; default: assert(0); break; } auto instr = new Instruction(AllocateUniqueId(), result_type, opcode); instr->AddIdOperand(parameters.sampler); if (parameters.coords) { instr->AddIdOperand(parameters.coords); } if (parameters.lod) { instr->AddIdOperand(parameters.lod); } build_point_->AddInstruction(instr); return instr->result_id(); } Id SpvEmitter::CreateCompare(spv::Decoration precision, Id value1, Id value2, bool is_equal) { Id bool_type_id = MakeBoolType(); Id value_type_id = GetTypeId(value1); assert(value_type_id == GetTypeId(value2)); assert(!IsScalar(value1)); // Vectors. if (IsVectorType(value_type_id)) { Op op; if (GetMostBasicTypeClass(value_type_id) == Op::OpTypeFloat) { op = is_equal ? Op::OpFOrdEqual : Op::OpFOrdNotEqual; } else { op = is_equal ? Op::OpIEqual : Op::OpINotEqual; } Id bool_vector_type_id = MakeVectorType(bool_type_id, GetTypeComponentCount(value_type_id)); Id bool_vector = CreateBinOp(op, bool_vector_type_id, value1, value2); SetPrecision(bool_vector, precision); // Reduce vector compares with any() and all(). op = is_equal ? Op::OpAll : Op::OpAny; return CreateUnaryOp(op, bool_type_id, bool_vector); } CheckNotImplemented("Composite comparison of non-vectors"); return NoResult; // Recursively handle aggregates, which include matrices, arrays, and // structures // and accumulate the results. // Matrices // Arrays // int numElements; // const llvm::ArrayType* arrayType = // llvm::dyn_cast(value1->getType()); // if (arrayType) // numElements = (int)arrayType->getNumElements(); // else { // // better be structure // const llvm::StructType* structType = // llvm::dyn_cast(value1->getType()); // assert(structType); // numElements = structType->getNumElements(); //} // assert(numElements > 0); // for (int element = 0; element < numElements; ++element) { // // Get intermediate comparison values // llvm::Value* element1 = builder.CreateExtractValue(value1, element, // "element1"); // setInstructionPrecision(element1, precision); // llvm::Value* element2 = builder.CreateExtractValue(value2, element, // "element2"); // setInstructionPrecision(element2, precision); // llvm::Value* subResult = createCompare(precision, element1, element2, // equal, "comp"); // // Accumulate intermediate comparison // if (element == 0) // result = subResult; // else { // if (equal) // result = builder.CreateAnd(result, subResult); // else // result = builder.CreateOr(result, subResult); // setInstructionPrecision(result, precision); // } //} // return result; } // OpCompositeConstruct Id SpvEmitter::CreateCompositeConstruct(Id type_id, std::vector constituent_ids) { assert(IsAggregateType(type_id) || (GetTypeComponentCount(type_id) > 1 && GetTypeComponentCount(type_id) == constituent_ids.size())); auto instr = new Instruction(AllocateUniqueId(), type_id, Op::OpCompositeConstruct); instr->AddIdOperands(constituent_ids); build_point_->AddInstruction(instr); return instr->result_id(); } Id SpvEmitter::CreateConstructor(spv::Decoration precision, std::vector source_ids, Id result_type_id) { Id result = 0; int target_component_count = GetTypeComponentCount(result_type_id); int target_component = 0; // Special case: when calling a vector constructor with a single scalar // argument, smear the scalar if (source_ids.size() == 1 && IsScalar(source_ids[0]) && target_component_count > 1) { return SmearScalar(precision, source_ids[0], result_type_id); } // Accumulate the arguments for OpCompositeConstruct. Id scalar_type_id = GetScalarTypeId(result_type_id); std::vector constituent_ids; for (auto source_id : source_ids) { assert(!IsAggregate(source_id)); int source_component_count = GetComponentCount(source_id); int sources_to_use = source_component_count; if (sources_to_use + target_component > target_component_count) { sources_to_use = target_component_count - target_component; } for (int s = 0; s < sources_to_use; ++s) { Id arg = source_id; if (source_component_count > 1) { arg = CreateSwizzle(scalar_type_id, arg, {static_cast(s)}); } if (target_component_count > 1) { constituent_ids.push_back(arg); } else { result = arg; } ++target_component; } if (target_component >= target_component_count) { break; } } if (!constituent_ids.empty()) { result = CreateCompositeConstruct(result_type_id, constituent_ids); } SetPrecision(result, precision); return result; } Id SpvEmitter::CreateMatrixConstructor(spv::Decoration precision, std::vector sources, Id result_type_id) { Id component_type_id = GetScalarTypeId(result_type_id); int column_count = GetTypeColumnCount(result_type_id); int row_count = GetTypeRowCount(result_type_id); // Will use a two step process: // 1. make a compile-time 2D array of values // 2. construct a matrix from that array // Step 1. // Initialize the array to the identity matrix. Id ids[kMaxMatrixSize][kMaxMatrixSize]; Id one = MakeFloatConstant(1.0); Id zero = MakeFloatConstant(0.0); for (int col = 0; col < kMaxMatrixSize; ++col) { for (int row = 0; row < kMaxMatrixSize; ++row) { if (col == row) { ids[col][row] = one; } else { ids[col][row] = zero; } } } // Modify components as dictated by the arguments. if (sources.size() == 1 && IsScalar(sources[0])) { // A single scalar; resets the diagonals. for (int col = 0; col < kMaxMatrixSize; ++col) { ids[col][col] = sources[0]; } } else if (IsMatrix(sources[0])) { // Constructing from another matrix; copy over the parts that exist in both // the argument and constructee. Id matrix = sources[0]; int min_column_count = std::min(column_count, GetColumnCount(matrix)); int min_row_count = std::min(row_count, GetRowCount(matrix)); for (int col = 0; col < min_column_count; ++col) { std::vector indexes; indexes.push_back(col); for (int row = 0; row < min_row_count; ++row) { indexes.push_back(row); ids[col][row] = CreateCompositeExtract(matrix, component_type_id, indexes); indexes.pop_back(); SetPrecision(ids[col][row], precision); } } } else { // Fill in the matrix in column-major order with whatever argument // components are available. int row = 0; int col = 0; for (auto source : sources) { Id arg_component = source; for (int comp = 0; comp < GetComponentCount(source); ++comp) { if (GetComponentCount(source) > 1) { arg_component = CreateCompositeExtract(source, component_type_id, comp); SetPrecision(arg_component, precision); } ids[col][row++] = arg_component; if (row == row_count) { row = 0; ++col; } } } } // Step 2: construct a matrix from that array. // Make the column vectors. Id column_type_id = GetContainedTypeId(result_type_id); std::vector matrix_columns; for (int col = 0; col < column_count; ++col) { std::vector vector_components; for (int row = 0; row < row_count; ++row) { vector_components.push_back(ids[col][row]); } matrix_columns.push_back( CreateCompositeConstruct(column_type_id, vector_components)); } // Make the matrix. return CreateCompositeConstruct(result_type_id, matrix_columns); } SpvEmitter::If::If(SpvEmitter& emitter, Id condition) : emitter_(emitter), condition_(condition) { function_ = &emitter_.build_point()->parent(); // make the blocks, but only put the then-block into the function, // the else-block and merge-block will be added later, in order, after // earlier code is emitted then_block_ = new Block(emitter_.AllocateUniqueId(), *function_); merge_block_ = new Block(emitter_.AllocateUniqueId(), *function_); // Save the current block, so that we can add in the flow control split when // makeEndIf is called. header_block_ = emitter_.build_point(); function_->push_block(then_block_); emitter_.set_build_point(then_block_); } void SpvEmitter::If::MakeBeginElse() { // Close out the "then" by having it jump to the merge_block emitter_.CreateBranch(merge_block_); // Make the first else block and add it to the function else_block_ = new Block(emitter_.AllocateUniqueId(), *function_); function_->push_block(else_block_); // Start building the else block emitter_.set_build_point(else_block_); } void SpvEmitter::If::MakeEndIf() { // jump to the merge block emitter_.CreateBranch(merge_block_); // Go back to the header_block and make the flow control split emitter_.set_build_point(header_block_); emitter_.CreateSelectionMerge(merge_block_, spv::SelectionControlMask::MaskNone); if (else_block_) { emitter_.CreateConditionalBranch(condition_, then_block_, else_block_); } else { emitter_.CreateConditionalBranch(condition_, then_block_, merge_block_); } // add the merge block to the function function_->push_block(merge_block_); emitter_.set_build_point(merge_block_); } void SpvEmitter::MakeSwitch(Id selector, int segment_count, std::vector case_values, std::vector value_index_to_segment, int default_segment, std::vector& segment_blocks) { Function& function = build_point_->parent(); // Make all the blocks. for (int s = 0; s < segment_count; ++s) { segment_blocks.push_back(new Block(AllocateUniqueId(), function)); } Block* merge_block = new Block(AllocateUniqueId(), function); // Make and insert the switch's selection-merge instruction. CreateSelectionMerge(merge_block, spv::SelectionControlMask::MaskNone); // Make the switch instruction. auto switchInst = new Instruction(NoResult, NoType, Op::OpSwitch); switchInst->AddIdOperand(selector); switchInst->AddIdOperand(default_segment >= 0 ? segment_blocks[default_segment]->id() : merge_block->id()); for (size_t i = 0; i < case_values.size(); ++i) { switchInst->AddImmediateOperand(case_values[i]); switchInst->AddIdOperand(segment_blocks[value_index_to_segment[i]]->id()); } build_point_->AddInstruction(switchInst); // Push the merge block. switch_merges_.push(merge_block); } void SpvEmitter::AddSwitchBreak() { // Branch to the top of the merge block stack. CreateBranch(switch_merges_.top()); CreateAndSetNoPredecessorBlock("post-switch-break"); } void SpvEmitter::NextSwitchSegment(std::vector& segment_block, int next_segment) { int last_segment = next_segment - 1; if (last_segment >= 0) { // Close out previous segment by jumping, if necessary, to next segment. if (!build_point_->is_terminated()) { CreateBranch(segment_block[next_segment]); } } Block* block = segment_block[next_segment]; block->parent().push_block(block); set_build_point(block); } void SpvEmitter::EndSwitch(std::vector& segment_block) { // Close out previous segment by jumping, if necessary, to next segment. if (!build_point_->is_terminated()) { AddSwitchBreak(); } switch_merges_.top()->parent().push_block(switch_merges_.top()); set_build_point(switch_merges_.top()); switch_merges_.pop(); } void SpvEmitter::MakeNewLoop(bool test_first) { loops_.push(Loop(*this, test_first)); const Loop& loop = loops_.top(); // The loop test is always emitted before the loop body. // But if the loop test executes at the bottom of the loop, then // execute the test only on the second and subsequent iterations. // Remember the block that branches to the loop header. This // is required for the test-after-body case. Block* preheader = build_point(); // Branch into the loop CreateBranch(loop.header); // Set ourselves inside the loop loop.function->push_block(loop.header); set_build_point(loop.header); if (!test_first) { // Generate code to defer the loop test until the second and // subsequent iterations. // It's always the first iteration when coming from the preheader. // All other branches to this loop header will need to indicate "false", // but we don't yet know where they will come from. loop.is_first_iteration->AddIdOperand(MakeBoolConstant(true)); loop.is_first_iteration->AddIdOperand(preheader->id()); build_point()->AddInstruction(loop.is_first_iteration); // Mark the end of the structured loop. This must exist in the loop header // block. CreateLoopMerge(loop.merge, loop.header, spv::LoopControlMask::MaskNone); // Generate code to see if this is the first iteration of the loop. // It needs to be in its own block, since the loop merge and // the selection merge instructions can't both be in the same // (header) block. Block* firstIterationCheck = new Block(AllocateUniqueId(), *loop.function); CreateBranch(firstIterationCheck); loop.function->push_block(firstIterationCheck); set_build_point(firstIterationCheck); // Control flow after this "if" normally reconverges at the loop body. // However, the loop test has a "break branch" out of this selection // construct because it can transfer control to the loop merge block. CreateSelectionMerge(loop.body, spv::SelectionControlMask::MaskNone); Block* loopTest = new Block(AllocateUniqueId(), *loop.function); CreateConditionalBranch(loop.is_first_iteration->result_id(), loop.body, loopTest); loop.function->push_block(loopTest); set_build_point(loopTest); } } void SpvEmitter::CreateLoopTestBranch(Id condition) { const Loop& loop = loops_.top(); // Generate the merge instruction. If the loop test executes before // the body, then this is a loop merge. Otherwise the loop merge // has already been generated and this is a conditional merge. if (loop.test_first) { CreateLoopMerge(loop.merge, loop.header, spv::LoopControlMask::MaskNone); // Branching to the "body" block will keep control inside // the loop. CreateConditionalBranch(condition, loop.body, loop.merge); loop.function->push_block(loop.body); set_build_point(loop.body); } else { // The branch to the loop merge block is the allowed exception // to the structured control flow. Otherwise, control flow will // continue to loop.body block. Since that is already the target // of a merge instruction, and a block can't be the target of more // than one merge instruction, we need to make an intermediate block. Block* stayInLoopBlock = new Block(AllocateUniqueId(), *loop.function); CreateSelectionMerge(stayInLoopBlock, spv::SelectionControlMask::MaskNone); // This is the loop test. CreateConditionalBranch(condition, stayInLoopBlock, loop.merge); // The dummy block just branches to the real loop body. loop.function->push_block(stayInLoopBlock); set_build_point(stayInLoopBlock); CreateBranchToBody(); } } void SpvEmitter::CreateBranchToBody() { const Loop& loop = loops_.top(); assert(loop.body); // This is a reconvergence of control flow, so no merge instruction // is required. CreateBranch(loop.body); loop.function->push_block(loop.body); set_build_point(loop.body); } void SpvEmitter::CreateLoopContinue() { CreateBranchToLoopHeaderFromInside(loops_.top()); // Set up a block for dead code. CreateAndSetNoPredecessorBlock("post-loop-continue"); } void SpvEmitter::CreateLoopExit() { CreateBranch(loops_.top().merge); // Set up a block for dead code. CreateAndSetNoPredecessorBlock("post-loop-break"); } void SpvEmitter::CloseLoop() { const Loop& loop = loops_.top(); // Branch back to the top. CreateBranchToLoopHeaderFromInside(loop); // Add the merge block and set the build point to it. loop.function->push_block(loop.merge); set_build_point(loop.merge); loops_.pop(); } void SpvEmitter::ClearAccessChain() { access_chain_.base = NoResult; access_chain_.index_chain.clear(); access_chain_.instr = NoResult; access_chain_.swizzle.clear(); access_chain_.component = NoResult; access_chain_.pre_swizzle_base_type = NoType; access_chain_.is_rvalue = false; } // Turn the described access chain in 'accessChain' into an instruction // computing its address. This *cannot* include complex swizzles, which must // be handled after this is called, but it does include swizzles that select // an individual element, as a single address of a scalar type can be // computed by an OpAccessChain instruction. Id SpvEmitter::CollapseAccessChain() { assert(access_chain_.is_rvalue == false); if (!access_chain_.index_chain.empty()) { if (!access_chain_.instr) { auto storage_class = module_.storage_class(GetTypeId(access_chain_.base)); access_chain_.instr = CreateAccessChain(storage_class, access_chain_.base, access_chain_.index_chain); } return access_chain_.instr; } else { return access_chain_.base; } // Note that non-trivial swizzling is left pending... } // Clear out swizzle if it is redundant, that is reselecting the same components // that would be present without the swizzle. void SpvEmitter::SimplifyAccessChainSwizzle() { // If the swizzle has fewer components than the vector, it is subsetting, and // must stay to preserve that fact. if (GetTypeComponentCount(access_chain_.pre_swizzle_base_type) > access_chain_.swizzle.size()) { return; } // If components are out of order, it is a swizzle. for (size_t i = 0; i < access_chain_.swizzle.size(); ++i) { if (i != access_chain_.swizzle[i]) { return; } } // Otherwise, there is no need to track this swizzle. access_chain_.swizzle.clear(); if (access_chain_.component == NoResult) { access_chain_.pre_swizzle_base_type = NoType; } } // To the extent any swizzling can become part of the chain // of accesses instead of a post operation, make it so. // If 'dynamic' is true, include transfering a non-static component index, // otherwise, only transfer static indexes. // // Also, Boolean vectors are likely to be special. While // for external storage, they should only be integer types, // function-local bool vectors could use sub-word indexing, // so keep that as a separate Insert/Extract on a loaded vector. void SpvEmitter::TransferAccessChainSwizzle(bool dynamic) { // too complex? if (access_chain_.swizzle.size() > 1) { return; } // non existent? if (access_chain_.swizzle.empty() && access_chain_.component == NoResult) { return; } // single component... // skip doing it for Boolean vectors if (IsBoolType(GetContainedTypeId(access_chain_.pre_swizzle_base_type))) { return; } if (access_chain_.swizzle.size() == 1) { // handle static component access_chain_.index_chain.push_back( MakeUintConstant(access_chain_.swizzle.front())); access_chain_.swizzle.clear(); // note, the only valid remaining dynamic access would be to this one // component, so don't bother even looking at access_chain_.component access_chain_.pre_swizzle_base_type = NoType; access_chain_.component = NoResult; } else if (dynamic && access_chain_.component != NoResult) { // handle dynamic component access_chain_.index_chain.push_back(access_chain_.component); access_chain_.pre_swizzle_base_type = NoType; access_chain_.component = NoResult; } } void SpvEmitter::PushAccessChainSwizzle(std::vector swizzle, Id pre_swizzle_base_type) { // Swizzles can be stacked in GLSL, but simplified to a single // one here; the base type doesn't change. if (access_chain_.pre_swizzle_base_type == NoType) { access_chain_.pre_swizzle_base_type = pre_swizzle_base_type; } // If needed, propagate the swizzle for the current access chain. if (access_chain_.swizzle.size()) { std::vector oldSwizzle = access_chain_.swizzle; access_chain_.swizzle.resize(0); for (unsigned int i = 0; i < swizzle.size(); ++i) { access_chain_.swizzle.push_back(oldSwizzle[swizzle[i]]); } } else { access_chain_.swizzle = swizzle; } // Determine if we need to track this swizzle anymore. SimplifyAccessChainSwizzle(); } void SpvEmitter::CreateAccessChainStore(Id rvalue) { assert(access_chain_.is_rvalue == false); TransferAccessChainSwizzle(true); Id base = CollapseAccessChain(); if (access_chain_.swizzle.size() && access_chain_.component != NoResult) { CheckNotImplemented( "simultaneous l-value swizzle and dynamic component selection"); return; } // If swizzle still exists, it is out-of-order or not full, we must load the // target vector, extract and insert elements to perform writeMask and/or // swizzle. Id source = NoResult; if (access_chain_.swizzle.size()) { Id temp_base_id = CreateLoad(base); source = CreateLvalueSwizzle(GetTypeId(temp_base_id), temp_base_id, rvalue, access_chain_.swizzle); } // Dynamic component selection. if (access_chain_.component != NoResult) { Id temp_base_id = (source == NoResult) ? CreateLoad(base) : source; source = CreateVectorInsertDynamic(temp_base_id, GetTypeId(temp_base_id), rvalue, access_chain_.component); } if (source == NoResult) { source = rvalue; } CreateStore(source, base); } Id SpvEmitter::CreateAccessChainLoad(Id result_type_id) { Id id; if (access_chain_.is_rvalue) { // Transfer access chain, but keep it static, so we can stay in registers. TransferAccessChainSwizzle(false); if (!access_chain_.index_chain.empty()) { Id swizzle_base_type_id = access_chain_.pre_swizzle_base_type != NoType ? access_chain_.pre_swizzle_base_type : result_type_id; // If all the accesses are constants we can use OpCompositeExtract. std::vector indexes; bool constant = true; for (auto index : access_chain_.index_chain) { if (IsConstantScalar(index)) { indexes.push_back(GetConstantScalar(index)); } else { constant = false; break; } } if (constant) { id = CreateCompositeExtract(access_chain_.base, swizzle_base_type_id, indexes); } else { // Make a new function variable for this r-value. Id lvalue = CreateVariable(spv::StorageClass::Function, GetTypeId(access_chain_.base), "indexable"); // Store into it. CreateStore(access_chain_.base, lvalue); // Move base to the new variable. access_chain_.base = lvalue; access_chain_.is_rvalue = false; // Load through the access chain. id = CreateLoad(CollapseAccessChain()); } } else { id = access_chain_.base; } } else { TransferAccessChainSwizzle(true); // Load through the access chain. id = CreateLoad(CollapseAccessChain()); } // Done, unless there are swizzles to do. if (access_chain_.swizzle.empty() && access_chain_.component == NoResult) { return id; } // Do remaining swizzling. // First, static swizzling. if (access_chain_.swizzle.size()) { // Static swizzle. Id swizzledType = GetScalarTypeId(GetTypeId(id)); if (access_chain_.swizzle.size() > 1) { swizzledType = MakeVectorType(swizzledType, (int)access_chain_.swizzle.size()); } id = CreateSwizzle(swizzledType, id, access_chain_.swizzle); } // Dynamic single-component selection. if (access_chain_.component != NoResult) { id = CreateVectorExtractDynamic(id, result_type_id, access_chain_.component); } return id; } Id SpvEmitter::CreateAccessChainLValue() { assert(access_chain_.is_rvalue == false); TransferAccessChainSwizzle(true); Id lvalue = CollapseAccessChain(); // If swizzle exists, it is out-of-order or not full, we must load the target // vector, extract and insert elements to perform writeMask and/or swizzle. // This does not go with getting a direct l-value pointer. assert(access_chain_.swizzle.empty()); assert(access_chain_.component == NoResult); return lvalue; } void SpvEmitter::Serialize(std::vector& out) const { // Header, before first instructions: out.push_back(spv::MagicNumber); out.push_back(spv::Version); out.push_back(builder_number_); out.push_back(unique_id_ + 1); out.push_back(0); for (auto capability : capabilities_) { Instruction capInst(0, 0, Op::OpCapability); capInst.AddImmediateOperand(capability); capInst.Serialize(out); } // TBD: OpExtension ... SerializeInstructions(out, imports_); Instruction memInst(0, 0, Op::OpMemoryModel); memInst.AddImmediateOperand(addressing_model_); memInst.AddImmediateOperand(memory_model_); memInst.Serialize(out); // Instructions saved up while building: SerializeInstructions(out, entry_points_); SerializeInstructions(out, execution_modes_); // Debug instructions: if (source_language_ != spv::SourceLanguage::Unknown) { Instruction sourceInst(0, 0, Op::OpSource); sourceInst.AddImmediateOperand(source_language_); sourceInst.AddImmediateOperand(source_version_); sourceInst.Serialize(out); } for (auto extension : source_extensions_) { Instruction extInst(0, 0, Op::OpSourceExtension); extInst.AddStringOperand(extension); extInst.Serialize(out); } SerializeInstructions(out, names_); SerializeInstructions(out, lines_); // Annotation instructions: SerializeInstructions(out, decorations_); SerializeInstructions(out, constants_types_globals_); SerializeInstructions(out, externals_); // The functions: module_.Serialize(out); } void SpvEmitter::SerializeInstructions( std::vector& out, const std::vector& instructions) const { for (auto instruction : instructions) { instruction->Serialize(out); } } // Utility method for creating a new block and setting the insert point to // be in it. This is useful for flow-control operations that need a "dummy" // block proceeding them (e.g. instructions after a discard, etc). void SpvEmitter::CreateAndSetNoPredecessorBlock(const char* name) { Block* block = new Block(AllocateUniqueId(), build_point_->parent()); block->set_unreachable(true); build_point_->parent().push_block(block); set_build_point(block); AddName(block->id(), name); } void SpvEmitter::CreateBranch(Block* block) { auto instr = new Instruction(Op::OpBranch); instr->AddIdOperand(block->id()); build_point_->AddInstruction(instr); block->AddPredecessor(build_point_); } void SpvEmitter::CreateSelectionMerge(Block* merge_block, spv::SelectionControlMask control) { auto instr = new Instruction(Op::OpSelectionMerge); instr->AddIdOperand(merge_block->id()); instr->AddImmediateOperand(control); build_point_->AddInstruction(instr); } void SpvEmitter::CreateLoopMerge(Block* merge_block, Block* continueBlock, spv::LoopControlMask control) { auto instr = new Instruction(Op::OpLoopMerge); instr->AddIdOperand(merge_block->id()); instr->AddIdOperand(continueBlock->id()); instr->AddImmediateOperand(control); build_point_->AddInstruction(instr); } void SpvEmitter::CreateConditionalBranch(Id condition, Block* then_block, Block* else_block) { auto instr = new Instruction(Op::OpBranchConditional); instr->AddIdOperand(condition); instr->AddIdOperand(then_block->id()); instr->AddIdOperand(else_block->id()); build_point_->AddInstruction(instr); then_block->AddPredecessor(build_point_); else_block->AddPredecessor(build_point_); } SpvEmitter::Loop::Loop(SpvEmitter& emitter, bool testFirstArg) : function(&emitter.build_point()->parent()), header(new Block(emitter.AllocateUniqueId(), *function)), merge(new Block(emitter.AllocateUniqueId(), *function)), body(new Block(emitter.AllocateUniqueId(), *function)), test_first(testFirstArg), is_first_iteration(nullptr) { if (!test_first) { // You may be tempted to rewrite this as // new Instruction(builder.getUniqueId(), builder.makeBoolType(), OpPhi); // This will cause subtle test failures because builder.getUniqueId(), // and builder.makeBoolType() can then get run in a compiler-specific // order making tests fail for certain configurations. Id instructionId = emitter.AllocateUniqueId(); is_first_iteration = new Instruction(instructionId, emitter.MakeBoolType(), Op::OpPhi); } } // Create a branch to the header of the given loop, from inside // the loop body. // Adjusts the phi node for the first-iteration value if needeed. void SpvEmitter::CreateBranchToLoopHeaderFromInside(const Loop& loop) { CreateBranch(loop.header); if (loop.is_first_iteration) { loop.is_first_iteration->AddIdOperand(MakeBoolConstant(false)); loop.is_first_iteration->AddIdOperand(build_point()->id()); } } void SpvEmitter::CheckNotImplemented(const char* message) { xe::FatalError("Missing functionality: %s", message); } } // namespace spirv } // namespace gpu } // namespace xe