Files
Xenia-Canary/src/xenia/gpu/spirv/spv_emitter.cc
2015-11-23 21:20:59 -08:00

2229 lines
72 KiB
C++

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