diff --git a/src/xenia/gpu/spirv/spirv_compiler.cc b/src/xenia/gpu/spirv/spirv_compiler.cc index c68a005b2..e4a1bde6d 100644 --- a/src/xenia/gpu/spirv/spirv_compiler.cc +++ b/src/xenia/gpu/spirv/spirv_compiler.cc @@ -9,11 +9,53 @@ #include "xenia/gpu/spirv/spirv_compiler.h" +#include "third_party/spirv-tools/include/libspirv/libspirv.h" +#include "xenia/gpu/spirv/spv_assembler.h" +#include "xenia/gpu/spirv/spv_disassembler.h" +#include "xenia/gpu/spirv/spv_emitter.h" + namespace xe { namespace gpu { namespace spirv { -// +SpirvCompiler::SpirvCompiler() { + // HACK(benvanik): in-progress test code just to make sure things compile. + const std::string spirv = R"( +OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical Simple +OpEntryPoint Vertex %2 "main" +%3 = OpTypeVoid +%4 = OpTypeFunction %3 +%2 = OpFunction %3 None %4 +%5 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SpvAssembler spv_asm; + auto asm_result = spv_asm.Assemble(spirv); + + SpvDisassembler spv_disasm; + auto disasm_result = + spv_disasm.Disassemble(asm_result->words(), asm_result->word_count()); + + SpvEmitter e; + auto glsl_std_450 = e.import("GLSL.std.450"); + auto fn = e.makeMain(); + auto float_1_0 = e.makeFloatConstant(1.0f); + auto acos = e.createBuiltinCall( + spv::Decoration::Invariant, e.makeFloatType(32), glsl_std_450, + static_cast(spv::GLSLstd450::Acos), {{float_1_0}}); + e.makeReturn(false); + + std::vector words; + e.dump(words); + + auto disasm_result2 = spv_disasm.Disassemble(words.data(), words.size()); + + return; +} } // namespace spirv } // namespace gpu diff --git a/src/xenia/gpu/spirv/spirv_compiler.h b/src/xenia/gpu/spirv/spirv_compiler.h index e903b5b03..a14bba99f 100644 --- a/src/xenia/gpu/spirv/spirv_compiler.h +++ b/src/xenia/gpu/spirv/spirv_compiler.h @@ -10,11 +10,18 @@ #ifndef XENIA_GPU_SPIRV_SPIRV_COMPILER_H_ #define XENIA_GPU_SPIRV_SPIRV_COMPILER_H_ +#include "xenia/gpu/spirv/spirv_util.h" + namespace xe { namespace gpu { namespace spirv { -// +class SpirvCompiler { + public: + SpirvCompiler(); + + private: +}; } // namespace spirv } // namespace gpu diff --git a/src/xenia/gpu/spirv/spirv_compiler_main.cc b/src/xenia/gpu/spirv/spirv_compiler_main.cc index 1769af1a6..d87bd4aba 100644 --- a/src/xenia/gpu/spirv/spirv_compiler_main.cc +++ b/src/xenia/gpu/spirv/spirv_compiler_main.cc @@ -22,7 +22,10 @@ namespace xe { namespace gpu { namespace spirv { -int compiler_main(const std::vector& args) { return 0; } +int compiler_main(const std::vector& args) { + SpirvCompiler c; + return 0; +} } // namespace spirv } // namespace gpu diff --git a/src/xenia/gpu/spirv/spv_emitter.cc b/src/xenia/gpu/spirv/spv_emitter.cc new file mode 100644 index 000000000..9a3b0a039 --- /dev/null +++ b/src/xenia/gpu/spirv/spv_emitter.cc @@ -0,0 +1,2257 @@ +/** + ****************************************************************************** + * 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::import(const char* name) { + auto import = new Instruction(getUniqueId(), 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(getUniqueId(), 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(getUniqueId(), 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(getUniqueId(), 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(getUniqueId(), 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 width, bool hasSign) { + // try to find it + auto& grouped_type = grouped_types_[static_cast(Op::OpTypeInt)]; + for (auto& type : grouped_type) { + if (type->immediate_operand(0) == (unsigned)width && + type->immediate_operand(1) == (hasSign ? 1u : 0u)) { + return type->result_id(); + } + } + + // not found, make it + auto type = new Instruction(getUniqueId(), NoType, Op::OpTypeInt); + type->addImmediateOperand(width); + type->addImmediateOperand(hasSign ? 1 : 0); + grouped_type.push_back(type); + constants_types_globals_.push_back(type); + module_.mapInstruction(type); + + return type->result_id(); +} + +Id SpvEmitter::makeFloatType(int 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)width) { + return type->result_id(); + } + } + + // not found, make it + auto type = new Instruction(getUniqueId(), NoType, Op::OpTypeFloat); + type->addImmediateOperand(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::vector& 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(getUniqueId(), 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::makeStructResultType(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 + std::vector members; + members.push_back(type0); + members.push_back(type1); + + return makeStructType(members, "ResType"); +} + +Id SpvEmitter::makeVectorType(Id component, int size) { + // 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->immediate_operand(1) == (unsigned)size) { + return type->result_id(); + } + } + + // not found, make it + auto type = new Instruction(getUniqueId(), NoType, Op::OpTypeVector); + type->addIdOperand(component); + type->addImmediateOperand(size); + grouped_type.push_back(type); + constants_types_globals_.push_back(type); + module_.mapInstruction(type); + + return type->result_id(); +} + +Id SpvEmitter::makeMatrixType(Id component, int cols, int rows) { + assert(cols <= kMaxMatrixSize && rows <= kMaxMatrixSize); + + Id column = makeVectorType(component, 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(getUniqueId(), 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, unsigned size) { + // First, we need a constant instruction for the size + Id sizeId = makeUintConstant(size); + + // 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->id_operand(1) == sizeId) { + return type->result_id(); + } + } + + // not found, make it + auto type = new Instruction(getUniqueId(), NoType, Op::OpTypeArray); + type->addIdOperand(element); + type->addIdOperand(sizeId); + grouped_type.push_back(type); + constants_types_globals_.push_back(type); + module_.mapInstruction(type); + + return type->result_id(); +} + +Id SpvEmitter::makeRuntimeArray(Id element) { + Instruction* type = + new Instruction(getUniqueId(), NoType, Op::OpTypeRuntimeArray); + type->addIdOperand(element); + constants_types_globals_.push_back(type); + module_.mapInstruction(type); + + return type->result_id(); +} + +Id SpvEmitter::makeFunctionType(Id return_type, std::vector& 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 || + (int)param_types.size() != type->operand_count() - 1) { + continue; + } + bool mismatch = false; + for (int p = 0; p < (int)param_types.size(); ++p) { + if (param_types[p] != type->id_operand(p + 1)) { + mismatch = true; + break; + } + } + if (!mismatch) { + return type->result_id(); + } + } + + // not found, make it + auto type = new Instruction(getUniqueId(), 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 sampledType, spv::Dim dim, bool depth, + bool arrayed, bool ms, unsigned 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) == sampledType && + type->immediate_operand(1) == (unsigned int)dim && + type->immediate_operand(2) == (depth ? 1u : 0u) && + type->immediate_operand(3) == (arrayed ? 1u : 0u) && + type->immediate_operand(4) == (ms ? 1u : 0u) && + type->immediate_operand(5) == sampled && + type->immediate_operand(6) == (unsigned int)format) { + return type->result_id(); + } + } + + // not found, make it + auto type = new Instruction(getUniqueId(), NoType, Op::OpTypeImage); + type->addIdOperand(sampledType); + type->addImmediateOperand(dim); + type->addImmediateOperand(depth ? 1 : 0); + type->addImmediateOperand(arrayed ? 1 : 0); + type->addImmediateOperand(ms ? 1 : 0); + type->addImmediateOperand(sampled); + type->addImmediateOperand((unsigned int)format); + + grouped_type.push_back(type); + constants_types_globals_.push_back(type); + module_.mapInstruction(type); + + return type->result_id(); +} + +Id SpvEmitter::makeSampledImageType(Id imageType) { + // try to find it + auto& grouped_type = grouped_types_[static_cast(Op::OpTypeSampledImage)]; + for (auto& type : grouped_type) { + if (type->id_operand(0) == imageType) { + return type->result_id(); + } + } + + // not found, make it + auto type = new Instruction(getUniqueId(), NoType, Op::OpTypeSampledImage); + type->addIdOperand(imageType); + + 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_.getInstruction(type_id)->immediate_operand(1); +} + +Op SpvEmitter::getMostBasicTypeClass(Id type_id) const { + auto instr = module_.getInstruction(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::getNumTypeComponents(Id type_id) const { + auto instr = module_.getInstruction(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_.getInstruction(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_.getInstruction(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, + unsigned 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, + unsigned v1, unsigned 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 b, bool is_spec_constant) { + Id type_id = makeBoolType(); + Op opcode = is_spec_constant + ? (b ? Op::OpSpecConstantTrue : Op::OpSpecConstantFalse) + : (b ? 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) { + existing = constant->result_id(); + } + } + if (existing) { + return existing; + } + + // Make it + auto c = new Instruction(getUniqueId(), 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::makeIntConstant(Id type_id, unsigned 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(getUniqueId(), 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 f, bool is_spec_constant) { + Op opcode = is_spec_constant ? Op::OpSpecConstant : Op::OpConstant; + Id type_id = makeFloatType(32); + unsigned value = *(unsigned int*)&f; + Id existing = findScalarConstant(Op::OpTypeFloat, opcode, type_id, value); + if (existing) { + return existing; + } + + auto c = new Instruction(getUniqueId(), type_id, opcode); + c->addImmediateOperand(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 d, bool is_spec_constant) { + Op opcode = is_spec_constant ? Op::OpSpecConstant : Op::OpConstant; + Id type_id = makeFloatType(64); + unsigned long long value = *(unsigned long long*)&d; + unsigned op1 = value & 0xFFFFFFFF; + unsigned op2 = value >> 32; + Id existing = findScalarConstant(Op::OpTypeFloat, opcode, type_id, op1, op2); + if (existing) { + return existing; + } + + auto c = new Instruction(getUniqueId(), 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::vector& comps) const { + Instruction* constant = nullptr; + bool found = false; + auto& grouped_constant = grouped_constants_[static_cast(type_class)]; + for (auto& constant : grouped_constant) { + // same shape? + if (constant->operand_count() != (int)comps.size()) { + continue; + } + + // same contents? + bool mismatch = false; + for (int op = 0; op < constant->operand_count(); ++op) { + if (constant->id_operand(op) != comps[op]) { + mismatch = true; + break; + } + } + if (!mismatch) { + found = true; + break; + } + } + + return found ? constant->result_id() : NoResult; +} + +Id SpvEmitter::makeCompositeConstant(Id type_id, std::vector& members) { + 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, members); + if (existing) { + return existing; + } + + auto c = new Instruction(getUniqueId(), type_id, Op::OpConstantComposite); + c->addIdOperands(members); + 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 model, + Function* function, const char* name) { + auto entry_point = new Instruction(Op::OpEntryPoint); + entry_point->addImmediateOperand(model); + entry_point->addIdOperand(function->id()); + entry_point->addStringOperand(name); + + entry_points_.push_back(entry_point); + + return entry_point; +} + +// Currently relying on the fact that all 'value' of interest are small +// non-negative values. +void SpvEmitter::addExecutionMode(Function* entry_point, + spv::ExecutionMode mode, int value1, + int value2, int value3) { + auto instr = new Instruction(Op::OpExecutionMode); + instr->addIdOperand(entry_point->id()); + instr->addImmediateOperand(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 id, const char* string) { + auto name = new Instruction(Op::OpName); + name->addIdOperand(id); + name->addStringOperand(string); + + names_.push_back(name); +} + +void SpvEmitter::addMemberName(Id id, int memberNumber, const char* string) { + auto name = new Instruction(Op::OpMemberName); + name->addIdOperand(id); + name->addImmediateOperand(memberNumber); + name->addStringOperand(string); + + names_.push_back(name); +} + +void SpvEmitter::addLine(Id target, Id file_name, int lineNum, int column) { + auto line = new Instruction(Op::OpLine); + line->addIdOperand(target); + line->addIdOperand(file_name); + line->addImmediateOperand(lineNum); + line->addImmediateOperand(column); + + lines_.push_back(line); +} + +void SpvEmitter::addDecoration(Id id, spv::Decoration decoration, int num) { + if (decoration == (spv::Decoration)BadValue) { + return; + } + auto dec = new Instruction(Op::OpDecorate); + dec->addIdOperand(id); + dec->addImmediateOperand(decoration); + if (num >= 0) { + dec->addImmediateOperand(num); + } + + decorations_.push_back(dec); +} + +void SpvEmitter::addMemberDecoration(Id id, unsigned int member, + spv::Decoration decoration, int num) { + auto dec = new Instruction(Op::OpMemberDecorate); + dec->addIdOperand(id); + dec->addImmediateOperand(member); + dec->addImmediateOperand(decoration); + if (num >= 0) { + dec->addImmediateOperand(num); + } + + decorations_.push_back(dec); +} + +Function* SpvEmitter::makeMain() { + assert(!main_function_); + + Block* entry; + std::vector params; + + main_function_ = makeFunctionEntry(makeVoidType(), "main", params, &entry); + + return main_function_; +} + +Function* SpvEmitter::makeFunctionEntry(Id return_type, const char* name, + std::vector& param_types, + Block** entry) { + Id type_id = makeFunctionType(return_type, param_types); + Id firstParamId = + param_types.empty() ? 0 : getUniqueIds((int)param_types.size()); + auto function = + new Function(getUniqueId(), return_type, type_id, firstParamId, module_); + + if (entry) { + *entry = new Block(getUniqueId(), *function); + function->push_block(*entry); + set_build_point(*entry); + } + + if (name) { + addName(function->id(), name); + } + + return function; +} + +void SpvEmitter::makeReturn(bool implicit, Id retVal) { + if (retVal) { + auto inst = new Instruction(NoResult, NoType, Op::OpReturnValue); + inst->addIdOperand(retVal); + build_point_->push_instruction(inst); + } else { + build_point_->push_instruction( + 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() == 0; + + 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_->push_instruction(new Instruction(Op::OpKill)); + createAndSetNoPredecessorBlock("post-discard"); +} + +Id SpvEmitter::createVariable(spv::StorageClass storage_class, Id type, + const char* name) { + Id pointerType = makePointer(storage_class, type); + auto inst = new Instruction(getUniqueId(), pointerType, Op::OpVariable); + inst->addImmediateOperand(storage_class); + + switch (storage_class) { + case spv::StorageClass::Function: + // Validation rules require the declaration in the entry block + build_point_->parent().push_local_variable(inst); + break; + + default: + constants_types_globals_.push_back(inst); + module_.mapInstruction(inst); + break; + } + + if (name) { + addName(inst->result_id(), name); + } + + return inst->result_id(); +} + +Id SpvEmitter::createUndefined(Id type) { + auto inst = new Instruction(getUniqueId(), type, Op::OpUndef); + build_point_->push_instruction(inst); + return inst->result_id(); +} + +void SpvEmitter::createStore(Id rvalue, Id lvalue) { + auto store = new Instruction(Op::OpStore); + store->addIdOperand(lvalue); + store->addIdOperand(rvalue); + build_point_->push_instruction(store); +} + +Id SpvEmitter::createLoad(Id lvalue) { + auto load = + new Instruction(getUniqueId(), getDerefTypeId(lvalue), Op::OpLoad); + load->addIdOperand(lvalue); + build_point_->push_instruction(load); + + return load->result_id(); +} + +Id SpvEmitter::createAccessChain(spv::StorageClass storage_class, Id base, + std::vector& offsets) { + // Figure out the final resulting type. + spv::Id type_id = getTypeId(base); + assert(isPointerType(type_id) && !offsets.empty()); + type_id = getContainedTypeId(type_id); + for (size_t i = 0; i < offsets.size(); ++i) { + if (isStructType(type_id)) { + assert(isConstantScalar(offsets[i])); + type_id = getContainedTypeId(type_id, getConstantScalar(offsets[i])); + } else { + type_id = getContainedTypeId(type_id, offsets[i]); + } + } + type_id = makePointer(storage_class, type_id); + + // Make the instruction + auto chain = new Instruction(getUniqueId(), type_id, Op::OpAccessChain); + chain->addIdOperand(base); + chain->addIdOperands(offsets); + build_point_->push_instruction(chain); + + return chain->result_id(); +} + +Id SpvEmitter::createArrayLength(Id base, unsigned int member) { + auto length = + new Instruction(getUniqueId(), makeIntType(32), Op::OpArrayLength); + length->addIdOperand(base); + length->addImmediateOperand(member); + build_point_->push_instruction(length); + + return length->result_id(); +} + +Id SpvEmitter::createCompositeExtract(Id composite, Id type_id, + unsigned index) { + auto extract = + new Instruction(getUniqueId(), type_id, Op::OpCompositeExtract); + extract->addIdOperand(composite); + extract->addImmediateOperand(index); + build_point_->push_instruction(extract); + + return extract->result_id(); +} + +Id SpvEmitter::createCompositeExtract(Id composite, Id type_id, + std::vector& indexes) { + auto extract = + new Instruction(getUniqueId(), type_id, Op::OpCompositeExtract); + extract->addIdOperand(composite); + extract->addImmediateOperands(indexes); + build_point_->push_instruction(extract); + + return extract->result_id(); +} + +Id SpvEmitter::createCompositeInsert(Id object, Id composite, Id type_id, + unsigned index) { + auto insert = new Instruction(getUniqueId(), type_id, Op::OpCompositeInsert); + insert->addIdOperand(object); + insert->addIdOperand(composite); + insert->addImmediateOperand(index); + build_point_->push_instruction(insert); + + return insert->result_id(); +} + +Id SpvEmitter::createCompositeInsert(Id object, Id composite, Id type_id, + std::vector& indexes) { + auto insert = new Instruction(getUniqueId(), type_id, Op::OpCompositeInsert); + insert->addIdOperand(object); + insert->addIdOperand(composite); + for (size_t i = 0; i < indexes.size(); ++i) { + insert->addImmediateOperand(indexes[i]); + } + build_point_->push_instruction(insert); + + return insert->result_id(); +} + +Id SpvEmitter::createVectorExtractDynamic(Id vector, Id type_id, + Id component_index) { + auto extract = + new Instruction(getUniqueId(), type_id, Op::OpVectorExtractDynamic); + extract->addIdOperand(vector); + extract->addIdOperand(component_index); + build_point_->push_instruction(extract); + + return extract->result_id(); +} + +Id SpvEmitter::createVectorInsertDynamic(Id vector, Id type_id, Id component, + Id component_index) { + auto insert = + new Instruction(getUniqueId(), type_id, Op::OpVectorInsertDynamic); + insert->addIdOperand(vector); + insert->addIdOperand(component); + insert->addIdOperand(component_index); + build_point_->push_instruction(insert); + + return insert->result_id(); +} + +// An opcode that has no operands, no result id, and no type +void SpvEmitter::createNoResultOp(Op opcode) { + auto op = new Instruction(opcode); + build_point_->push_instruction(op); +} + +// An opcode that has one operand, no result id, and no type +void SpvEmitter::createNoResultOp(Op opcode, Id operand) { + auto op = new Instruction(opcode); + op->addIdOperand(operand); + build_point_->push_instruction(op); +} + +// An opcode that has one operand, no result id, and no type +void SpvEmitter::createNoResultOp(Op opcode, const std::vector& operands) { + auto op = new Instruction(opcode); + op->addIdOperands(operands); + build_point_->push_instruction(op); +} + +void SpvEmitter::createControlBarrier(spv::Scope execution, spv::Scope memory, + spv::MemorySemanticsMask semantics) { + auto op = new Instruction(Op::OpControlBarrier); + op->addImmediateOperand(makeUintConstant(execution)); + op->addImmediateOperand(makeUintConstant(memory)); + op->addImmediateOperand(makeUintConstant(semantics)); + build_point_->push_instruction(op); +} + +void SpvEmitter::createMemoryBarrier(unsigned execution_scope, + unsigned memory_semantics) { + auto op = new Instruction(Op::OpMemoryBarrier); + op->addImmediateOperand(makeUintConstant(execution_scope)); + op->addImmediateOperand(makeUintConstant(memory_semantics)); + build_point_->push_instruction(op); +} + +// An opcode that has one operands, a result id, and a type +Id SpvEmitter::createUnaryOp(Op opcode, Id type_id, Id operand) { + auto op = new Instruction(getUniqueId(), type_id, opcode); + op->addIdOperand(operand); + build_point_->push_instruction(op); + + return op->result_id(); +} + +Id SpvEmitter::createBinOp(Op opcode, Id type_id, Id left, Id right) { + auto op = new Instruction(getUniqueId(), type_id, opcode); + op->addIdOperand(left); + op->addIdOperand(right); + build_point_->push_instruction(op); + + return op->result_id(); +} + +Id SpvEmitter::createTriOp(Op opcode, Id type_id, Id op1, Id op2, Id op3) { + auto op = new Instruction(getUniqueId(), type_id, opcode); + op->addIdOperand(op1); + op->addIdOperand(op2); + op->addIdOperand(op3); + build_point_->push_instruction(op); + + return op->result_id(); +} + +Id SpvEmitter::createOp(Op opcode, Id type_id, + const std::vector& operands) { + auto op = new Instruction(getUniqueId(), type_id, opcode); + op->addIdOperands(operands); + build_point_->push_instruction(op); + + return op->result_id(); +} + +Id SpvEmitter::createFunctionCall(Function* function, + std::vector& args) { + auto op = new Instruction(getUniqueId(), function->return_type(), + Op::OpFunctionCall); + op->addIdOperand(function->id()); + op->addIdOperands(args); + build_point_->push_instruction(op); + + return op->result_id(); +} + +Id SpvEmitter::createRvalueSwizzle(Id type_id, Id source, + std::vector& channels) { + if (channels.size() == 1) + return createCompositeExtract(source, type_id, channels.front()); + + auto swizzle = new Instruction(getUniqueId(), type_id, Op::OpVectorShuffle); + assert(isVector(source)); + swizzle->addIdOperand(source); + swizzle->addIdOperand(source); + swizzle->addImmediateOperands(channels); + build_point_->push_instruction(swizzle); + + return swizzle->result_id(); +} + +Id SpvEmitter::createLvalueSwizzle(Id type_id, Id target, Id source, + std::vector& channels) { + assert(getNumComponents(source) == (int)channels.size()); + if (channels.size() == 1 && getNumComponents(source) == 1) { + return createCompositeInsert(source, target, type_id, channels.front()); + } + + auto swizzle = new Instruction(getUniqueId(), type_id, Op::OpVectorShuffle); + assert(isVector(source)); + assert(isVector(target)); + swizzle->addIdOperand(target); + swizzle->addIdOperand(source); + + // Set up an identity shuffle from the base value to the result value + unsigned int components[4]; + int numTargetComponents = getNumComponents(target); + for (int i = 0; i < numTargetComponents; ++i) { + components[i] = i; + } + + // Punch in the l-value swizzle + for (int i = 0; i < (int)channels.size(); ++i) { + components[channels[i]] = numTargetComponents + i; + } + + // finish the instruction with these components selectors + for (int i = 0; i < numTargetComponents; ++i) { + swizzle->addImmediateOperand(components[i]); + } + build_point_->push_instruction(swizzle); + + return swizzle->result_id(); +} + +void SpvEmitter::promoteScalar(spv::Decoration precision, Id& left, Id& right) { + int direction = getNumComponents(right) - getNumComponents(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, + Id vectorType) { + assert(getNumComponents(scalar) == 1); + + int numComponents = getNumTypeComponents(vectorType); + if (numComponents == 1) { + return scalar; + } + + auto smear = + new Instruction(getUniqueId(), vectorType, Op::OpCompositeConstruct); + for (int c = 0; c < numComponents; ++c) { + smear->addIdOperand(scalar); + } + build_point_->push_instruction(smear); + + return smear->result_id(); +} + +Id SpvEmitter::createBuiltinCall(spv::Decoration precision, Id result_type, + Id builtins, int entry_point, + std::initializer_list args) { + auto inst = new Instruction(getUniqueId(), result_type, Op::OpExtInst); + inst->addIdOperand(builtins); + inst->addImmediateOperand(entry_point); + inst->addIdOperands(args); + + build_point_->push_instruction(inst); + return inst->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 maxTextureArgs = 10; + Id texArgs[maxTextureArgs] = {}; + + // Set up the fixed arguments + int numArgs = 0; + bool xplicit = false; + texArgs[numArgs++] = parameters.sampler; + texArgs[numArgs++] = parameters.coords; + if (parameters.Dref) { + texArgs[numArgs++] = parameters.Dref; + } + if (parameters.comp) { + texArgs[numArgs++] = parameters.comp; + } + + // Set up the optional arguments + int optArgNum = numArgs; // track which operand, if it exists, is the mask of + // optional arguments + ++numArgs; // speculatively make room for the mask operand + auto mask = spv::ImageOperandsMask::MaskNone; // the mask operand + if (parameters.bias) { + mask = mask | spv::ImageOperandsMask::Bias; + texArgs[numArgs++] = parameters.bias; + } + if (parameters.lod) { + mask = mask | spv::ImageOperandsMask::Lod; + texArgs[numArgs++] = parameters.lod; + xplicit = true; + } + if (parameters.gradX) { + mask = mask | spv::ImageOperandsMask::Grad; + texArgs[numArgs++] = parameters.gradX; + texArgs[numArgs++] = parameters.gradY; + xplicit = true; + } + if (parameters.offset) { + if (isConstant(parameters.offset)) { + mask = mask | spv::ImageOperandsMask::ConstOffset; + } else { + mask = mask | spv::ImageOperandsMask::Offset; + } + texArgs[numArgs++] = parameters.offset; + } + if (parameters.offsets) { + mask = mask | spv::ImageOperandsMask::ConstOffsets; + texArgs[numArgs++] = parameters.offsets; + } + if (parameters.sample) { + mask = mask | spv::ImageOperandsMask::Sample; + texArgs[numArgs++] = parameters.sample; + } + if (mask == spv::ImageOperandsMask::MaskNone) { + --numArgs; // undo speculative reservation for the mask argument + } else { + texArgs[optArgNum] = static_cast(mask); + } + + // Set up the instruction + Op opcode; + opcode = Op::OpImageSampleImplicitLod; + if (fetch) { + opcode = Op::OpImageFetch; + } else if (gather) { + if (parameters.Dref) + opcode = Op::OpImageDrefGather; + else + opcode = Op::OpImageGather; + } else if (xplicit) { + if (parameters.Dref) { + if (proj) + opcode = Op::OpImageSampleProjDrefExplicitLod; + else + opcode = Op::OpImageSampleDrefExplicitLod; + } else { + if (proj) + opcode = Op::OpImageSampleProjExplicitLod; + else + opcode = Op::OpImageSampleExplicitLod; + } + } else { + if (parameters.Dref) { + 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 smearedType = 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 textureInst = new Instruction(getUniqueId(), result_type, opcode); + for (int op = 0; op < optArgNum; ++op) { + textureInst->addIdOperand(texArgs[op]); + } + if (optArgNum < numArgs) textureInst->addImmediateOperand(texArgs[optArgNum]); + for (int op = optArgNum + 1; op < numArgs; ++op) { + textureInst->addIdOperand(texArgs[op]); + } + setPrecision(textureInst->result_id(), precision); + build_point_->push_instruction(textureInst); + + Id result_id = textureInst->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 != smearedType) { + result_id = smearScalar(precision, result_id, smearedType); + } + + 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 numComponents; + switch (getTypeDimensionality(getImageType(parameters.sampler))) { + case spv::Dim::Dim1D: + case spv::Dim::Buffer: + numComponents = 1; + break; + case spv::Dim::Dim2D: + case spv::Dim::Cube: + case spv::Dim::Rect: + numComponents = 2; + break; + case spv::Dim::Dim3D: + numComponents = 3; + break; + case spv::Dim::SubpassData: + CheckNotImplemented("input-attachment dim"); + break; + + default: + assert(0); + break; + } + if (isArrayedImageType(getImageType(parameters.sampler))) { + ++numComponents; + } + if (numComponents == 1) { + result_type = makeIntType(32); + } else { + result_type = makeVectorType(makeIntType(32), numComponents); + } + 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 query = new Instruction(getUniqueId(), result_type, opcode); + query->addIdOperand(parameters.sampler); + if (parameters.coords) { + query->addIdOperand(parameters.coords); + } + if (parameters.lod) { + query->addIdOperand(parameters.lod); + } + build_point_->push_instruction(query); + + return query->result_id(); +} + +Id SpvEmitter::createCompare(spv::Decoration precision, Id value1, Id value2, + bool equal) { + Id boolType = makeBoolType(); + Id valueType = getTypeId(value1); + + assert(valueType == getTypeId(value2)); + assert(!isScalar(value1)); + + // Vectors + + if (isVectorType(valueType)) { + Op op; + if (getMostBasicTypeClass(valueType) == Op::OpTypeFloat) { + op = equal ? Op::OpFOrdEqual : Op::OpFOrdNotEqual; + } else { + op = equal ? Op::OpIEqual : Op::OpINotEqual; + } + + Id boolVectorType = + makeVectorType(boolType, getNumTypeComponents(valueType)); + Id boolVector = createBinOp(op, boolVectorType, value1, value2); + setPrecision(boolVector, precision); + + // Reduce vector compares with any() and all(). + + op = equal ? Op::OpAll : Op::OpAny; + + return createUnaryOp(op, boolType, boolVector); + } + + 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& constituents) { + assert(isAggregateType(type_id) || + (getNumTypeComponents(type_id) > 1 && + getNumTypeComponents(type_id) == (int)constituents.size())); + + auto op = new Instruction(getUniqueId(), type_id, Op::OpCompositeConstruct); + op->addIdOperands(constituents); + build_point_->push_instruction(op); + + return op->result_id(); +} + +// Vector or scalar constructor +Id SpvEmitter::createConstructor(spv::Decoration precision, + const std::vector& sources, + Id result_type_id) { + Id result = 0; + unsigned int numTargetComponents = getNumTypeComponents(result_type_id); + unsigned int targetComponent = 0; + + // Special case: when calling a vector constructor with a single scalar + // argument, smear the scalar + if (sources.size() == 1 && isScalar(sources[0]) && numTargetComponents > 1) { + return smearScalar(precision, sources[0], result_type_id); + } + + Id scalarTypeId = getScalarTypeId(result_type_id); + std::vector + constituents; // accumulate the arguments for OpCompositeConstruct + for (auto source : sources) { + assert(!isAggregate(source)); + unsigned int sourceSize = getNumComponents(source); + unsigned int sourcesToUse = sourceSize; + if (sourcesToUse + targetComponent > numTargetComponents) { + sourcesToUse = numTargetComponents - targetComponent; + } + + for (unsigned int s = 0; s < sourcesToUse; ++s) { + Id arg = source; + if (sourceSize > 1) { + std::vector swiz; + swiz.push_back(s); + arg = createRvalueSwizzle(scalarTypeId, arg, swiz); + } + + if (numTargetComponents > 1) { + constituents.push_back(arg); + } else { + result = arg; + } + ++targetComponent; + } + + if (targetComponent >= numTargetComponents) { + break; + } + } + + if (!constituents.empty()) { + result = createCompositeConstruct(result_type_id, constituents); + } + + setPrecision(result, precision); + + return result; +} + +Id SpvEmitter::createMatrixConstructor(spv::Decoration precision, + const std::vector& sources, + Id result_type_id) { + Id componentTypeId = getScalarTypeId(result_type_id); + int numCols = getTypeNumColumns(result_type_id); + int numRows = getTypeNumRows(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 < 4; ++col) { + for (int row = 0; row < 4; ++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 < 4; ++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 minCols = std::min(numCols, getNumColumns(matrix)); + int minRows = std::min(numRows, getNumRows(matrix)); + for (int col = 0; col < minCols; ++col) { + std::vector indexes; + indexes.push_back(col); + for (int row = 0; row < minRows; ++row) { + indexes.push_back(row); + ids[col][row] = + createCompositeExtract(matrix, componentTypeId, 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 argComp = source; + for (int comp = 0; comp < getNumComponents(source); ++comp) { + if (getNumComponents(source) > 1) { + argComp = createCompositeExtract(source, componentTypeId, comp); + setPrecision(argComp, precision); + } + ids[col][row++] = argComp; + if (row == numRows) { + row = 0; + col++; + } + } + } + } + + // Step 2: Construct a matrix from that array. + // First make the column vectors, then make the matrix. + + // make the column vectors + Id columnTypeId = getContainedTypeId(result_type_id); + std::vector matrixColumns; + for (int col = 0; col < numCols; ++col) { + std::vector vectorComponents; + for (int row = 0; row < numRows; ++row) { + vectorComponents.push_back(ids[col][row]); + } + matrixColumns.push_back( + createCompositeConstruct(columnTypeId, vectorComponents)); + } + + // make the matrix + return createCompositeConstruct(result_type_id, matrixColumns); +} + +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_.getUniqueId(), *function_); + merge_block_ = new Block(emitter_.getUniqueId(), *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_.getUniqueId(), *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 numSegments, + std::vector& caseValues, + std::vector& valueIndexToSegment, + int defaultSegment, + std::vector& segmentBlocks) { + Function& function = build_point_->parent(); + + // make all the blocks + for (int s = 0; s < numSegments; ++s) { + segmentBlocks.push_back(new Block(getUniqueId(), function)); + } + + Block* merge_block = new Block(getUniqueId(), 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(defaultSegment >= 0 + ? segmentBlocks[defaultSegment]->id() + : merge_block->id()); + for (int i = 0; i < (int)caseValues.size(); ++i) { + switchInst->addImmediateOperand(caseValues[i]); + switchInst->addIdOperand(segmentBlocks[valueIndexToSegment[i]]->id()); + } + build_point_->push_instruction(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& segmentBlock, + int nextSegment) { + int lastSegment = nextSegment - 1; + if (lastSegment >= 0) { + // Close out previous segment by jumping, if necessary, to next segment + if (!build_point_->is_terminated()) { + createBranch(segmentBlock[nextSegment]); + } + } + Block* block = segmentBlock[nextSegment]; + block->parent().push_block(block); + set_build_point(block); +} + +void SpvEmitter::endSwitch(std::vector& /*segmentBlock*/) { + // 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 loopTestFirst) { + loops_.push(Loop(*this, loopTestFirst)); + 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 (!loopTestFirst) { + // 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()->push_instruction(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(getUniqueId(), *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(getUniqueId(), *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(getUniqueId(), *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"); +} + +// Add an exit (e.g. "break") for the innermost loop that you're in +void SpvEmitter::createLoopExit() { + createBranch(loops_.top().merge); + // Set up a block for dead code. + createAndSetNoPredecessorBlock("post-loop-break"); +} + +// Close the innermost loop +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(); +} + +// 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::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; +} + +void SpvEmitter::accessChainPushSwizzle(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::accessChainStore(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"); + } + + // 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 tempBaseId = createLoad(base); + source = createLvalueSwizzle(getTypeId(tempBaseId), tempBaseId, rvalue, + access_chain_.swizzle); + } + + // dynamic component selection + if (access_chain_.component != NoResult) { + Id tempBaseId = (source == NoResult) ? createLoad(base) : source; + source = createVectorInsertDynamic(tempBaseId, getTypeId(tempBaseId), + rvalue, access_chain_.component); + } + + if (source == NoResult) { + source = rvalue; + } + + createStore(source, base); +} + +Id SpvEmitter::accessChainLoad(Id result_type) { + 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 swizzleBase = access_chain_.pre_swizzle_base_type != NoType + ? access_chain_.pre_swizzle_base_type + : result_type; + + // 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, swizzleBase, 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 = createRvalueSwizzle(swizzledType, id, access_chain_.swizzle); + } + + // dynamic single-component selection + if (access_chain_.component != NoResult) { + id = createVectorExtractDynamic(id, result_type, access_chain_.component); + } + + return id; +} + +Id SpvEmitter::accessChainGetLValue() { + 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::dump(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.dump(out); + } + + // TBD: OpExtension ... + + dumpInstructions(out, imports_); + Instruction memInst(0, 0, Op::OpMemoryModel); + memInst.addImmediateOperand(addressing_model_); + memInst.addImmediateOperand(memory_model_); + memInst.dump(out); + + // Instructions saved up while building: + dumpInstructions(out, entry_points_); + dumpInstructions(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.dump(out); + } + for (auto extension : extensions_) { + Instruction extInst(0, 0, Op::OpSourceExtension); + extInst.addStringOperand(extension); + extInst.dump(out); + } + dumpInstructions(out, names_); + dumpInstructions(out, lines_); + + // Annotation instructions + dumpInstructions(out, decorations_); + + dumpInstructions(out, constants_types_globals_); + dumpInstructions(out, externals_); + + // The functions + module_.dump(out); +} + +// 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_.getStorageClass(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 (getNumTypeComponents(access_chain_.pre_swizzle_base_type) > + (int)access_chain_.swizzle.size()) { + return; + } + + // if components are out of order, it is a swizzle + for (unsigned int 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; + } +} + +// 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(getUniqueId(), build_point_->parent()); + block->set_unreachable(true); + build_point_->parent().push_block(block); + set_build_point(block); + + // if (name) + // addName(block->id(), name); +} + +void SpvEmitter::createBranch(Block* block) { + auto branch = new Instruction(Op::OpBranch); + branch->addIdOperand(block->id()); + build_point_->push_instruction(branch); + block->push_predecessor(build_point_); +} + +void SpvEmitter::createSelectionMerge(Block* merge_block, + spv::SelectionControlMask control) { + auto merge = new Instruction(Op::OpSelectionMerge); + merge->addIdOperand(merge_block->id()); + merge->addImmediateOperand(control); + build_point_->push_instruction(merge); +} + +void SpvEmitter::createLoopMerge(Block* merge_block, Block* continueBlock, + spv::LoopControlMask control) { + auto merge = new Instruction(Op::OpLoopMerge); + merge->addIdOperand(merge_block->id()); + merge->addIdOperand(continueBlock->id()); + merge->addImmediateOperand(control); + build_point_->push_instruction(merge); +} + +void SpvEmitter::createConditionalBranch(Id condition, Block* then_block, + Block* else_block) { + auto branch = new Instruction(Op::OpBranchConditional); + branch->addIdOperand(condition); + branch->addIdOperand(then_block->id()); + branch->addIdOperand(else_block->id()); + build_point_->push_instruction(branch); + then_block->push_predecessor(build_point_); + else_block->push_predecessor(build_point_); +} + +void SpvEmitter::dumpInstructions( + std::vector& out, + const std::vector& instructions) const { + for (int i = 0; i < (int)instructions.size(); ++i) { + instructions[i]->dump(out); + } +} + +void SpvEmitter::CheckNotImplemented(const char* message) { + xe::FatalError("Missing functionality: %s", message); +} + +SpvEmitter::Loop::Loop(SpvEmitter& emitter, bool testFirstArg) + : function(&emitter.build_point()->parent()), + header(new Block(emitter.getUniqueId(), *function)), + merge(new Block(emitter.getUniqueId(), *function)), + body(new Block(emitter.getUniqueId(), *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.getUniqueId(); + is_first_iteration = + new Instruction(instructionId, emitter.makeBoolType(), Op::OpPhi); + } +} + +} // namespace spirv +} // namespace gpu +} // namespace xe diff --git a/src/xenia/gpu/spirv/spv_emitter.h b/src/xenia/gpu/spirv/spv_emitter.h new file mode 100644 index 000000000..6c13e0732 --- /dev/null +++ b/src/xenia/gpu/spirv/spv_emitter.h @@ -0,0 +1,705 @@ +/** + ****************************************************************************** + * 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. + +#ifndef XENIA_GPU_SPIRV_SPV_EMITTER_H_ +#define XENIA_GPU_SPIRV_SPV_EMITTER_H_ + +#include +#include +#include +#include + +#include "xenia/base/assert.h" +#include "xenia/gpu/spirv/spirv_util.h" +#include "xenia/gpu/spirv/spv_ir.h" + +namespace xe { +namespace gpu { +namespace spirv { + +class SpvEmitter { + public: + SpvEmitter(); + ~SpvEmitter(); + + void setSource(spv::SourceLanguage language, int version) { + source_language_ = language; + source_version_ = version; + } + + void addSourceExtension(const char* ext) { extensions_.push_back(ext); } + + Id import(const char* name); + + void setMemoryModel(spv::AddressingModel addressing_model, + spv::MemoryModel memory_model) { + addressing_model_ = addressing_model; + memory_model_ = memory_model; + } + + void addCapability(spv::Capability cap) { capabilities_.push_back(cap); } + + // To get a new for anything needing a new one. + Id getUniqueId() { return ++unique_id_; } + + // To get a set of new s, e.g., for a set of function parameters + Id getUniqueIds(int numIds) { + Id id = unique_id_ + 1; + unique_id_ += numIds; + return id; + } + + // For creating new types (will return old type if the requested one was + // already made). + Id makeVoidType(); + Id makeBoolType(); + Id makePointer(spv::StorageClass, Id type); + Id makeIntegerType(int width, bool hasSign); // generic + Id makeIntType(int width) { return makeIntegerType(width, true); } + Id makeUintType(int width) { return makeIntegerType(width, false); } + Id makeFloatType(int width); + Id makeStructType(std::vector& members, const char*); + Id makeStructResultType(Id type0, Id type1); + Id makeVectorType(Id component, int size); + Id makeMatrixType(Id component, int cols, int rows); + Id makeArrayType(Id element, unsigned size); + Id makeRuntimeArray(Id element); + Id makeFunctionType(Id return_type, std::vector& param_types); + Id makeImageType(Id sampledType, spv::Dim, bool depth, bool arrayed, bool ms, + unsigned sampled, spv::ImageFormat format); + Id makeSamplerType(); + Id makeSampledImageType(Id imageType); + + // For querying about types. + Id getTypeId(Id result_id) const { return module_.getTypeId(result_id); } + Id getDerefTypeId(Id result_id) const; + Op getOpCode(Id id) const { return module_.getInstruction(id)->opcode(); } + Op getTypeClass(Id type_id) const { return getOpCode(type_id); } + Op getMostBasicTypeClass(Id type_id) const; + int getNumComponents(Id result_id) const { + return getNumTypeComponents(getTypeId(result_id)); + } + int getNumTypeComponents(Id type_id) const; + Id getScalarTypeId(Id type_id) const; + Id getContainedTypeId(Id type_id) const; + Id getContainedTypeId(Id type_id, int) const; + spv::StorageClass getTypeStorageClass(Id type_id) const { + return module_.getStorageClass(type_id); + } + + bool isPointer(Id result_id) const { + return isPointerType(getTypeId(result_id)); + } + bool isScalar(Id result_id) const { + return isScalarType(getTypeId(result_id)); + } + bool isVector(Id result_id) const { + return isVectorType(getTypeId(result_id)); + } + bool isMatrix(Id result_id) const { + return isMatrixType(getTypeId(result_id)); + } + bool isAggregate(Id result_id) const { + return isAggregateType(getTypeId(result_id)); + } + bool isBoolType(Id type_id) const { + return grouped_types_[static_cast(spv::Op::OpTypeBool)].size() > 0 && + type_id == + grouped_types_[static_cast(spv::Op::OpTypeBool)] + .back() + ->result_id(); + } + + bool isPointerType(Id type_id) const { + return getTypeClass(type_id) == spv::Op::OpTypePointer; + } + bool isScalarType(Id type_id) const { + return getTypeClass(type_id) == spv::Op::OpTypeFloat || + getTypeClass(type_id) == spv::Op::OpTypeInt || + getTypeClass(type_id) == spv::Op::OpTypeBool; + } + bool isVectorType(Id type_id) const { + return getTypeClass(type_id) == spv::Op::OpTypeVector; + } + bool isMatrixType(Id type_id) const { + return getTypeClass(type_id) == spv::Op::OpTypeMatrix; + } + bool isStructType(Id type_id) const { + return getTypeClass(type_id) == spv::Op::OpTypeStruct; + } + bool isArrayType(Id type_id) const { + return getTypeClass(type_id) == spv::Op::OpTypeArray; + } + bool isAggregateType(Id type_id) const { + return isArrayType(type_id) || isStructType(type_id); + } + bool isImageType(Id type_id) const { + return getTypeClass(type_id) == spv::Op::OpTypeImage; + } + bool isSamplerType(Id type_id) const { + return getTypeClass(type_id) == spv::Op::OpTypeSampler; + } + bool isSampledImageType(Id type_id) const { + return getTypeClass(type_id) == spv::Op::OpTypeSampledImage; + } + + bool isConstantOpCode(Op opcode) const; + bool isConstant(Id result_id) const { + return isConstantOpCode(getOpCode(result_id)); + } + bool isConstantScalar(Id result_id) const { + return getOpCode(result_id) == spv::Op::OpConstant; + } + unsigned int getConstantScalar(Id result_id) const { + return module_.getInstruction(result_id)->immediate_operand(0); + } + spv::StorageClass getStorageClass(Id result_id) const { + return getTypeStorageClass(getTypeId(result_id)); + } + + int getTypeNumColumns(Id type_id) const { + assert(isMatrixType(type_id)); + return getNumTypeComponents(type_id); + } + int getNumColumns(Id result_id) const { + return getTypeNumColumns(getTypeId(result_id)); + } + int getTypeNumRows(Id type_id) const { + assert(isMatrixType(type_id)); + return getNumTypeComponents(getContainedTypeId(type_id)); + } + int getNumRows(Id result_id) const { + return getTypeNumRows(getTypeId(result_id)); + } + + spv::Dim getTypeDimensionality(Id type_id) const { + assert(isImageType(type_id)); + return static_cast( + module_.getInstruction(type_id)->immediate_operand(1)); + } + Id getImageType(Id result_id) const { + Id type_id = getTypeId(result_id); + assert(isImageType(type_id) || isSampledImageType(type_id)); + return isSampledImageType(type_id) + ? module_.getInstruction(type_id)->id_operand(0) + : type_id; + } + bool isArrayedImageType(Id type_id) const { + assert(isImageType(type_id)); + return module_.getInstruction(type_id)->immediate_operand(3) != 0; + } + + // For making new constants (will return old constant if the requested one was + // already made). + Id makeBoolConstant(bool b, bool is_spec_constant = false); + Id makeIntConstant(int i, bool is_spec_constant = false) { + return makeIntConstant(makeIntType(32), (unsigned)i, is_spec_constant); + } + Id makeUintConstant(uint32_t u, bool is_spec_constant = false) { + return makeIntConstant(makeUintType(32), u, is_spec_constant); + } + template + Id makeUintConstant(T u, bool is_spec_constant = false) { + static_assert(sizeof(T) == sizeof(uint32_t), "Invalid type"); + return makeIntConstant(makeUintType(32), static_cast(u), + is_spec_constant); + } + Id makeFloatConstant(float f, bool is_spec_constant = false); + Id makeDoubleConstant(double d, bool is_spec_constant = false); + + // Turn the array of constants into a proper spv constant of the requested + // type. + Id makeCompositeConstant(Id type, std::vector& comps); + + // Methods for adding information outside the CFG. + Instruction* addEntryPoint(spv::ExecutionModel, Function*, const char* name); + void addExecutionMode(Function*, spv::ExecutionMode mode, int value1 = -1, + int value2 = -1, int value3 = -1); + void addName(Id, const char* name); + void addMemberName(Id, int member, const char* name); + void addLine(Id target, Id file_name, int line, int column); + void addDecoration(Id, spv::Decoration, int num = -1); + void addMemberDecoration(Id, unsigned int member, spv::Decoration, + int num = -1); + + // At the end of what block do the next create*() instructions go? + Block* build_point() const { return build_point_; } + void set_build_point(Block* build_point) { build_point_ = build_point; } + + // Make the main function. + Function* makeMain(); + + // Make a shader-style function, and create its entry block if entry is + // non-zero. + // Return the function, pass back the entry. + Function* makeFunctionEntry(Id return_type, const char* name, + std::vector& param_types, Block** entry = 0); + + // Create a return. An 'implicit' return is one not appearing in the source + // code. In the case of an implicit return, no post-return block is inserted. + void makeReturn(bool implicit, Id retVal = 0); + + // Generate all the code needed to finish up a function. + void leaveFunction(); + + // Create a discard. + void makeDiscard(); + + // Create a global or function local or IO variable. + Id createVariable(spv::StorageClass storage_class, Id type, + const char* name = 0); + + // Create an imtermediate with an undefined value. + Id createUndefined(Id type); + + // Store into an Id and return the l-value + void createStore(Id rvalue, Id lvalue); + + // Load from an Id and return it + Id createLoad(Id lvalue); + + // Create an OpAccessChain instruction + Id createAccessChain(spv::StorageClass storage_class, Id base, + std::vector& offsets); + + // Create an OpArrayLength instruction + Id createArrayLength(Id base, unsigned int member); + + // Create an OpCompositeExtract instruction + Id createCompositeExtract(Id composite, Id type_id, unsigned index); + Id createCompositeExtract(Id composite, Id type_id, + std::vector& indexes); + Id createCompositeInsert(Id object, Id composite, Id type_id, unsigned index); + Id createCompositeInsert(Id object, Id composite, Id type_id, + std::vector& indexes); + + Id createVectorExtractDynamic(Id vector, Id type_id, Id component_index); + Id createVectorInsertDynamic(Id vector, Id type_id, Id component, + Id component_index); + + void createNoResultOp(Op); + void createNoResultOp(Op, Id operand); + void createNoResultOp(Op, const std::vector& operands); + void createControlBarrier(spv::Scope execution, spv::Scope memory, + spv::MemorySemanticsMask); + void createMemoryBarrier(unsigned execution_scope, unsigned memory_semantics); + Id createUnaryOp(Op, Id type_id, Id operand); + Id createBinOp(Op, Id type_id, Id operand1, Id operand2); + Id createTriOp(Op, Id type_id, Id operand1, Id operand2, Id operand3); + Id createOp(Op, Id type_id, const std::vector& operands); + Id createFunctionCall(Function*, std::vector&); + + // Take an rvalue (source) and a set of channels to extract from it to + // make a new rvalue, which is returned. + Id createRvalueSwizzle(Id type_id, Id source, + std::vector& channels); + + // Take a copy of an lvalue (target) and a source of components, and set the + // source components into the lvalue where the 'channels' say to put them. + // An updated version of the target is returned. + // (No true lvalue or stores are used.) + Id createLvalueSwizzle(Id type_id, Id target, Id source, + std::vector& channels); + + // If the value passed in is an instruction and the precision is not EMpNone, + // it gets tagged with the requested precision. + void setPrecision(Id value, spv::Decoration precision) { + CheckNotImplemented("setPrecision"); + } + + // Can smear a scalar to a vector for the following forms: + // - promoteScalar(scalar, vector) // smear scalar to width of vector + // - promoteScalar(vector, scalar) // smear scalar to width of vector + // - promoteScalar(pointer, scalar) // smear scalar to width of what pointer + // points to + // - promoteScalar(scalar, scalar) // do nothing + // Other forms are not allowed. + // + // Note: One of the arguments will change, with the result coming back that + // way rather than + // through the return value. + void promoteScalar(spv::Decoration precision, Id& left, Id& right); + + // make a value by smearing the scalar to fill the type + Id smearScalar(spv::Decoration precision, Id scalarVal, Id); + + // Create a call to a built-in function. + Id createBuiltinCall(spv::Decoration precision, Id result_type, Id builtins, + int entry_point, std::initializer_list args); + + // List of parameters used to create a texture operation + struct TextureParameters { + Id sampler; + Id coords; + Id bias; + Id lod; + Id Dref; + Id offset; + Id offsets; + Id gradX; + Id gradY; + Id sample; + Id comp; + }; + + // Select the correct texture operation based on all inputs, and emit the + // correct instruction + Id createTextureCall(spv::Decoration precision, Id result_type, bool fetch, + bool proj, bool gather, const TextureParameters&); + + // Emit the OpTextureQuery* instruction that was passed in. + // Figure out the right return value and type, and return it. + Id createTextureQueryCall(Op, const TextureParameters&); + + Id createSamplePositionCall(spv::Decoration precision, Id, Id); + + Id createBitFieldExtractCall(spv::Decoration precision, Id, Id, Id, + bool isSigned); + Id createBitFieldInsertCall(spv::Decoration precision, Id, Id, Id, Id); + + // Reduction comparision for composites: For equal and not-equal resulting in + // a scalar. + Id createCompare(spv::Decoration precision, Id, Id, + bool /* true if for equal, fales if for not-equal */); + + // OpCompositeConstruct + Id createCompositeConstruct(Id type_id, std::vector& constituents); + + // vector or scalar constructor + Id createConstructor(spv::Decoration precision, + const std::vector& sources, Id result_type_id); + + // matrix constructor + Id createMatrixConstructor(spv::Decoration precision, + const std::vector& sources, Id constructee); + + // Helper to use for building nested control flow with if-then-else. + class If { + public: + If(SpvEmitter& emitter, Id condition); + ~If() = default; + + void makeBeginElse(); + void makeEndIf(); + + private: + If(const If&) = delete; + If& operator=(If&) = delete; + + SpvEmitter& emitter_; + Id condition_; + Function* function_ = nullptr; + Block* header_block_ = nullptr; + Block* then_block_ = nullptr; + Block* else_block_ = nullptr; + Block* merge_block_ = nullptr; + }; + + // Make a switch statement. + // A switch has 'numSegments' of pieces of code, not containing any + // case/default labels, all separated by one or more case/default labels. + // Each possible case value v is a jump to the caseValues[v] segment. The + // defaultSegment is also in this number space. How to compute the value is + // given by 'condition', as in switch(condition). + // + // The SPIR-V Builder will maintain the stack of post-switch merge blocks for + // nested switches. + // + // Use a defaultSegment < 0 if there is no default segment (to branch to post + // switch). + // + // Returns the right set of basic blocks to start each code segment with, so + // that the caller's recursion stack can hold the memory for it. + void makeSwitch(Id condition, int numSegments, std::vector& caseValues, + std::vector& valueToSegment, int defaultSegment, + std::vector& segmentBB); // return argument + + // Add a branch to the innermost switch's merge block. + void addSwitchBreak(); + + // Move to the next code segment, passing in the return argument in + // makeSwitch() + void nextSwitchSegment(std::vector& segmentBB, int segment); + + // Finish off the innermost switch. + void endSwitch(std::vector& segmentBB); + + // Start the beginning of a new loop, and prepare the builder to + // generate code for the loop test. + // The loopTestFirst parameter is true when the loop test executes before + // the body. (It is false for do-while loops.) + void makeNewLoop(bool loopTestFirst); + + // Add the branch for the loop test, based on the given condition. + // The true branch goes to the first block in the loop body, and + // the false branch goes to the loop's merge block. The builder insertion + // point will be placed at the start of the body. + void createLoopTestBranch(Id condition); + + // Generate an unconditional branch to the loop body. The builder insertion + // point will be placed at the start of the body. Use this when there is + // no loop test. + void createBranchToBody(); + + // Add a branch to the test of the current (innermost) loop. + // The way we generate code, that's also the loop header. + void createLoopContinue(); + + // Add an exit (e.g. "break") for the innermost loop that you're in + void createLoopExit(); + + // Close the innermost loop that you're in + void closeLoop(); + + // Access chain design for an R-Value vs. L-Value: + // + // There is a single access chain the builder is building at + // any particular time. Such a chain can be used to either to a load or + // a store, when desired. + // + // Expressions can be r-values, l-values, or both, or only r-values: + // a[b.c].d = .... // l-value + // ... = a[b.c].d; // r-value, that also looks like an l-value + // ++a[b.c].d; // r-value and l-value + // (x + y)[2]; // r-value only, can't possibly be l-value + // + // Computing an r-value means generating code. Hence, + // r-values should only be computed when they are needed, not speculatively. + // + // Computing an l-value means saving away information for later use in the + // compiler, + // no code is generated until the l-value is later dereferenced. It is okay + // to speculatively generate an l-value, just not okay to speculatively + // dereference it. + // + // The base of the access chain (the left-most variable or expression + // from which everything is based) can be set either as an l-value + // or as an r-value. Most efficient would be to set an l-value if one + // is available. If an expression was evaluated, the resulting r-value + // can be set as the chain base. + // + // The users of this single access chain can save and restore if they + // want to nest or manage multiple chains. + // + struct AccessChain { + Id base; // for l-values, pointer to the base object, for r-values, the + // base object + std::vector index_chain; + Id instr; // cache the instruction that generates this access chain + std::vector swizzle; // each std::vector element selects the next + // GLSL component number + Id component; // a dynamic component index, can coexist with a swizzle, + // done after the swizzle, NoResult if not present + Id pre_swizzle_base_type; // dereferenced type, before swizzle or component + // is + // applied; NoType unless a swizzle or component is + // present + bool is_rvalue; // true if 'base' is an r-value, otherwise, base is an + // l-value + }; + + // + // the SPIR-V builder maintains a single active chain that + // the following methods operated on + // + + // for external save and restore + AccessChain access_chain() { return access_chain_; } + void set_access_chain(AccessChain new_chain) { access_chain_ = new_chain; } + + // clear accessChain + void clearAccessChain(); + + // set new base as an l-value base + void setAccessChainLValue(Id lvalue) { + assert(isPointer(lvalue)); + access_chain_.base = lvalue; + } + + // set new base value as an r-value + void setAccessChainRValue(Id rvalue) { + access_chain_.is_rvalue = true; + access_chain_.base = rvalue; + } + + // push offset onto the end of the chain + void accessChainPush(Id offset) { + access_chain_.index_chain.push_back(offset); + } + + // push new swizzle onto the end of any existing swizzle, merging into a + // single swizzle + void accessChainPushSwizzle(std::vector& swizzle, + Id pre_swizzle_base_type); + + // push a variable component selection onto the access chain; supporting only + // one, so unsided + void accessChainPushComponent(Id component, Id pre_swizzle_base_type) { + access_chain_.component = component; + if (access_chain_.pre_swizzle_base_type == NoType) + access_chain_.pre_swizzle_base_type = pre_swizzle_base_type; + } + + // use accessChain and swizzle to store value + void accessChainStore(Id rvalue); + + // use accessChain and swizzle to load an r-value + Id accessChainLoad(Id result_type); + + // get the direct pointer for an l-value + Id accessChainGetLValue(); + + void dump(std::vector&) const; + + private: + // Maximum dimension for column/row in a matrix. + static const int kMaxMatrixSize = 4; + + // Asserts on unimplemnted functionality. + void CheckNotImplemented(const char* message); + + Id makeIntConstant(Id type_id, unsigned value, bool is_spec_constant); + Id findScalarConstant(Op type_class, Op opcode, Id type_id, + unsigned value) const; + Id findScalarConstant(Op type_class, Op opcode, Id type_id, unsigned v1, + unsigned v2) const; + Id findCompositeConstant(Op type_class, std::vector& comps) const; + Id collapseAccessChain(); + void transferAccessChainSwizzle(bool dynamic); + void simplifyAccessChainSwizzle(); + void createAndSetNoPredecessorBlock(const char*); + void createBranch(Block* block); + void createSelectionMerge(Block* merge_block, + spv::SelectionControlMask control); + void createLoopMerge(Block* merge_block, Block* continueBlock, + spv::LoopControlMask control); + void createConditionalBranch(Id condition, Block* then_block, + Block* else_block); + void dumpInstructions(std::vector&, + const std::vector&) const; + + struct Loop; // Defined below. + void createBranchToLoopHeaderFromInside(const Loop& loop); + + spv::SourceLanguage source_language_ = spv::SourceLanguage::Unknown; + int source_version_ = 0; + std::vector extensions_; + spv::AddressingModel addressing_model_ = spv::AddressingModel::Logical; + spv::MemoryModel memory_model_ = spv::MemoryModel::GLSL450; + std::vector capabilities_; + int builder_number_ = 0; + Module module_; + Block* build_point_ = nullptr; + Id unique_id_ = 0; + Function* main_function_ = nullptr; + AccessChain access_chain_; + + // special blocks of instructions for output + std::vector imports_; + std::vector entry_points_; + std::vector execution_modes_; + std::vector names_; + std::vector lines_; + std::vector decorations_; + std::vector constants_types_globals_; + std::vector externals_; + + // not output, internally used for quick & dirty canonical (unique) creation + std::vector grouped_constants_[static_cast( + spv::Op::OpConstant)]; // all types appear before OpConstant + std::vector + grouped_types_[static_cast(spv::Op::OpConstant)]; + + // stack of switches + std::stack switch_merges_; + + // Data that needs to be kept in order to properly handle loops. + struct Loop { + // Constructs a default Loop structure containing new header, merge, and + // body blocks for the current function. + // The test_first argument indicates whether the loop test executes at + // the top of the loop rather than at the bottom. In the latter case, + // also create a phi instruction whose value indicates whether we're on + // the first iteration of the loop. The phi instruction is initialized + // with no values or predecessor operands. + Loop(SpvEmitter& emitter, bool test_first); + + // The function containing the loop. + Function* const function; + // The header is the first block generated for the loop. + // It dominates all the blocks in the loop, i.e. it is always + // executed before any others. + // If the loop test is executed before the body (as in "while" and + // "for" loops), then the header begins with the test code. + // Otherwise, the loop is a "do-while" loop and the header contains the + // start of the body of the loop (if the body exists). + Block* const header; + // The merge block marks the end of the loop. Control is transferred + // to the merge block when either the loop test fails, or when a + // nested "break" is encountered. + Block* const merge; + // The body block is the first basic block in the body of the loop, i.e. + // the code that is to be repeatedly executed, aside from loop control. + // This member is null until we generate code that references the loop + // body block. + Block* const body; + // True when the loop test executes before the body. + const bool test_first; + // When the test executes after the body, this is defined as the phi + // instruction that tells us whether we are on the first iteration of + // the loop. Otherwise this is null. This is non-const because + // it has to be initialized outside of the initializer-list. + Instruction* is_first_iteration; + }; + + // Our loop stack. + std::stack loops_; +}; + +} // namespace spirv +} // namespace gpu +} // namespace xe + +#endif // XENIA_GPU_SPIRV_SPV_EMITTER_H_ diff --git a/src/xenia/gpu/spirv/spv_ir.h b/src/xenia/gpu/spirv/spv_ir.h new file mode 100644 index 000000000..47a17a872 --- /dev/null +++ b/src/xenia/gpu/spirv/spv_ir.h @@ -0,0 +1,403 @@ +/** + ****************************************************************************** + * 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. + +// SPIRV-IR +// +// Simple in-memory representation (IR) of SPIRV. Just for holding +// Each function's CFG of blocks. Has this hierarchy: +// - Module, which is a list of +// - Function, which is a list of +// - Block, which is a list of +// - Instruction +// + +#ifndef XENIA_GPU_SPIRV_SPV_IR_H_ +#define XENIA_GPU_SPIRV_SPV_IR_H_ + +#include +#include + +#include "xenia/gpu/spirv/spirv_util.h" + +namespace xe { +namespace gpu { +namespace spirv { + +using spv::Id; +using spv::Op; + +class Function; +class Module; + +const Id NoResult = 0; +const Id NoType = 0; + +const uint32_t BadValue = 0xFFFFFFFF; +const spv::Decoration NoPrecision = static_cast(BadValue); +const spv::MemorySemanticsMask MemorySemanticsAllMemory = + static_cast(0x3FF); + +class Instruction { + public: + Instruction(Id result_id, Id type_id, Op opcode) + : result_id_(result_id), type_id_(type_id), opcode_(opcode) {} + explicit Instruction(Op opcode) : opcode_(opcode) {} + ~Instruction() = default; + + void addIdOperand(Id id) { operands_.push_back(id); } + + void addIdOperands(const std::vector& ids) { + for (auto id : ids) { + operands_.push_back(id); + } + } + + void addImmediateOperand(uint32_t immediate) { + operands_.push_back(immediate); + } + + template + void addImmediateOperand(T immediate) { + static_assert(sizeof(T) == sizeof(uint32_t), "Invalid operand size"); + operands_.push_back(static_cast(immediate)); + } + + void addImmediateOperands(const std::vector& immediates) { + for (auto immediate : immediates) { + operands_.push_back(immediate); + } + } + + void addStringOperand(const char* str) { + original_string_ = str; + uint32_t word; + char* wordString = (char*)&word; + char* wordPtr = wordString; + int charCount = 0; + char c; + do { + c = *(str++); + *(wordPtr++) = c; + ++charCount; + if (charCount == 4) { + addImmediateOperand(word); + wordPtr = wordString; + charCount = 0; + } + } while (c != 0); + + // deal with partial last word + if (charCount > 0) { + // pad with 0s + for (; charCount < 4; ++charCount) { + *(wordPtr++) = 0; + } + addImmediateOperand(word); + } + } + + Op opcode() const { return opcode_; } + int operand_count() const { return static_cast(operands_.size()); } + Id result_id() const { return result_id_; } + Id type_id() const { return type_id_; } + Id id_operand(int op) const { return operands_[op]; } + uint32_t immediate_operand(int op) const { return operands_[op]; } + const char* string_operand() const { return original_string_.c_str(); } + + // Write out the binary form. + void dump(std::vector& out) const { + uint32_t wordCount = 1; + if (type_id_) { + ++wordCount; + } + if (result_id_) { + ++wordCount; + } + wordCount += static_cast(operands_.size()); + + out.push_back((wordCount << spv::WordCountShift) | + static_cast(opcode_)); + if (type_id_) { + out.push_back(type_id_); + } + if (result_id_) { + out.push_back(result_id_); + } + for (auto operand : operands_) { + out.push_back(operand); + } + } + + private: + Instruction(const Instruction&) = delete; + + Id result_id_ = NoResult; + Id type_id_ = NoType; + Op opcode_; + std::vector operands_; + std::string original_string_; // could be optimized away; convenience for + // getting string operand +}; + +class Block { + public: + Block(Id id, Function& parent); + ~Block() { + // TODO: free instructions + } + + Id id() { return instructions_.front()->result_id(); } + + Function& parent() const { return parent_; } + + void push_instruction(Instruction* inst); + void push_local_variable(Instruction* inst) { + local_variables_.push_back(inst); + } + + void push_predecessor(Block* predecessor) { + predecessors_.push_back(predecessor); + } + + int predecessor_count() const { + return static_cast(predecessors_.size()); + } + + bool is_unreachable() const { return unreachable_; } + void set_unreachable(bool value) { unreachable_ = value; } + + bool is_terminated() const { + switch (instructions_.back()->opcode()) { + case spv::Op::OpBranch: + case spv::Op::OpBranchConditional: + case spv::Op::OpSwitch: + case spv::Op::OpKill: + case spv::Op::OpReturn: + case spv::Op::OpReturnValue: + return true; + default: + return false; + } + } + + void dump(std::vector& out) const { + // skip the degenerate unreachable blocks + // TODO: code gen: skip all unreachable blocks (transitive closure) + // (but, until that's done safer to keep non-degenerate + // unreachable blocks, in case others depend on something) + if (unreachable_ && instructions_.size() <= 2) { + return; + } + + instructions_[0]->dump(out); + for (auto variable : local_variables_) { + variable->dump(out); + } + for (int i = 1; i < instructions_.size(); ++i) { + instructions_[i]->dump(out); + } + } + + private: + Block(const Block&) = delete; + Block& operator=(Block&) = delete; + + // To enforce keeping parent and ownership in sync: + friend Function; + + std::vector instructions_; + std::vector predecessors_; + std::vector local_variables_; + Function& parent_; + + // track whether this block is known to be uncreachable (not necessarily + // true for all unreachable blocks, but should be set at least + // for the extraneous ones introduced by the builder). + bool unreachable_; +}; + +class Function { + public: + Function(Id id, Id resultType, Id functionType, Id firstParam, + Module& parent); + ~Function() { + for (size_t i = 0; i < parameter_instructions_.size(); ++i) { + delete parameter_instructions_[i]; + } + for (size_t i = 0; i < blocks_.size(); ++i) { + delete blocks_[i]; + } + } + + Id id() const { return function_instruction_.result_id(); } + Id param_id(int p) { return parameter_instructions_[p]->result_id(); } + + void push_block(Block* block) { blocks_.push_back(block); } + void pop_block(Block*) { blocks_.pop_back(); } + + Module& parent() const { return parent_; } + Block* entry_block() const { return blocks_.front(); } + Block* last_block() const { return blocks_.back(); } + + void push_local_variable(Instruction* inst); + + Id return_type() const { return function_instruction_.type_id(); } + + void dump(std::vector& out) const { + // OpFunction + function_instruction_.dump(out); + + // OpFunctionParameter + for (auto instruction : parameter_instructions_) { + instruction->dump(out); + } + + // Blocks + for (auto block : blocks_) { + block->dump(out); + } + + Instruction end(0, 0, spv::Op::OpFunctionEnd); + end.dump(out); + } + + private: + Function(const Function&) = delete; + Function& operator=(Function&) = delete; + + Module& parent_; + Instruction function_instruction_; + std::vector parameter_instructions_; + std::vector blocks_; +}; + +class Module { + public: + Module() = default; + ~Module() { + // TODO delete things + } + + void push_function(Function* function) { functions_.push_back(function); } + + void mapInstruction(Instruction* instruction) { + spv::Id result_id = instruction->result_id(); + // map the instruction's result id + if (result_id >= id_to_instruction_.size()) + id_to_instruction_.resize(result_id + 16); + id_to_instruction_[result_id] = instruction; + } + + Instruction* getInstruction(Id id) const { return id_to_instruction_[id]; } + + spv::Id getTypeId(Id result_id) const { + return id_to_instruction_[result_id]->type_id(); + } + + spv::StorageClass getStorageClass(Id type_id) const { + return (spv::StorageClass)id_to_instruction_[type_id]->immediate_operand(0); + } + + void dump(std::vector& out) const { + for (auto function : functions_) { + function->dump(out); + } + } + + private: + Module(const Module&) = delete; + + std::vector functions_; + + // map from result id to instruction having that result id + std::vector id_to_instruction_; + + // map from a result id to its type id +}; + +inline Function::Function(Id id, Id resultType, Id functionType, + Id firstParamId, Module& parent) + : parent_(parent), + function_instruction_(id, resultType, spv::Op::OpFunction) { + // OpFunction + function_instruction_.addImmediateOperand( + static_cast(spv::FunctionControlMask::MaskNone)); + function_instruction_.addIdOperand(functionType); + parent.mapInstruction(&function_instruction_); + parent.push_function(this); + + // OpFunctionParameter + Instruction* typeInst = parent.getInstruction(functionType); + int numParams = typeInst->operand_count() - 1; + for (int p = 0; p < numParams; ++p) { + auto param = new Instruction(firstParamId + p, typeInst->id_operand(p + 1), + spv::Op::OpFunctionParameter); + parent.mapInstruction(param); + parameter_instructions_.push_back(param); + } +} + +inline void Function::push_local_variable(Instruction* inst) { + blocks_[0]->push_local_variable(inst); + parent_.mapInstruction(inst); +} + +inline Block::Block(Id id, Function& parent) + : parent_(parent), unreachable_(false) { + instructions_.push_back(new Instruction(id, NoType, spv::Op::OpLabel)); +} + +inline void Block::push_instruction(Instruction* inst) { + instructions_.push_back(inst); + if (inst->result_id()) { + parent_.parent().mapInstruction(inst); + } +} + +} // namespace spirv +} // namespace gpu +} // namespace xe + +#endif // XENIA_GPU_SPIRV_SPV_IR_H_ diff --git a/src/xenia/gpu/spirv/spv_optimizer.cc b/src/xenia/gpu/spirv/spv_optimizer.cc new file mode 100644 index 000000000..09fc4b054 --- /dev/null +++ b/src/xenia/gpu/spirv/spv_optimizer.cc @@ -0,0 +1,22 @@ +/** + ****************************************************************************** + * 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. * + ****************************************************************************** + */ + +#include "xenia/gpu/spirv/spv_optimizer.h" + +namespace xe { +namespace gpu { +namespace spirv { + +SpvOptimizer::SpvOptimizer() = default; + +SpvOptimizer::~SpvOptimizer() = default; + +} // namespace spirv +} // namespace gpu +} // namespace xe diff --git a/src/xenia/gpu/spirv/spv_optimizer.h b/src/xenia/gpu/spirv/spv_optimizer.h new file mode 100644 index 000000000..6de4557ca --- /dev/null +++ b/src/xenia/gpu/spirv/spv_optimizer.h @@ -0,0 +1,31 @@ +/** + ****************************************************************************** + * 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. * + ****************************************************************************** + */ + +#ifndef XENIA_GPU_SPIRV_SPV_OPTIMIZER_H_ +#define XENIA_GPU_SPIRV_SPV_OPTIMIZER_H_ + +#include "xenia/gpu/spirv/spirv_util.h" + +namespace xe { +namespace gpu { +namespace spirv { + +class SpvOptimizer { + public: + SpvOptimizer(); + ~SpvOptimizer(); + + private: +}; + +} // namespace spirv +} // namespace gpu +} // namespace xe + +#endif // XENIA_GPU_SPIRV_SPV_OPTIMIZER_H_