blob: ee6a2c03b4da8f30896cd0380193388fa948f20c [file] [log] [blame]
// Copyright 2019 The Shaderc Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "spvcir_pass.h"
#include "source/opt/ir_context.h"
namespace spvtools {
namespace opt {
static bool decoration_is_string(spv::Decoration decoration) {
switch (decoration) {
case spv::DecorationHlslSemanticGOOGLE:
return true;
default:
return false;
}
}
void SpvcIrPass::make_constant_null(uint32_t id, uint32_t type) {
// TODO(sarahM0): Add more tests
auto &constant_type = get<spirv_cross::SPIRType>(type);
if (constant_type.pointer) {
auto &constant = set<spirv_cross::SPIRConstant>(id, type);
constant.make_null(constant_type);
} else if (!constant_type.array.empty()) {
if (!CheckConditionAndSetErrorMessage(
constant_type.parent_type,
"constant type parent shouldn't be empty"))
return;
uint32_t parent_id = ir_->increase_bound_by(1);
make_constant_null(parent_id, constant_type.parent_type);
if (!CheckConditionAndSetErrorMessage(
constant_type.array_size_literal.back(),
"Array size of OpConstantNull must be a literal."))
return;
spirv_cross::SmallVector<uint32_t> elements(constant_type.array.back());
for (uint32_t i = 0; i < constant_type.array.back(); i++)
elements[i] = parent_id;
set<spirv_cross::SPIRConstant>(id, type, elements.data(),
uint32_t(elements.size()), false);
} else if (!constant_type.member_types.empty()) {
uint32_t member_ids =
ir_->increase_bound_by(uint32_t(constant_type.member_types.size()));
spirv_cross::SmallVector<uint32_t> elements(
constant_type.member_types.size());
for (uint32_t i = 0; i < constant_type.member_types.size(); i++) {
make_constant_null(member_ids + i, constant_type.member_types[i]);
elements[i] = member_ids + i;
}
set<spirv_cross::SPIRConstant>(id, type, elements.data(),
uint32_t(elements.size()), false);
} else {
auto &constant = set<spirv_cross::SPIRConstant>(id, type);
constant.make_null(constant_type);
}
}
bool SpvcIrPass::types_are_logically_equivalent(
const spirv_cross::SPIRType &a, const spirv_cross::SPIRType &b) const {
if (a.basetype != b.basetype) return false;
if (a.width != b.width) return false;
if (a.vecsize != b.vecsize) return false;
if (a.columns != b.columns) return false;
if (a.array.size() != b.array.size()) return false;
size_t array_count = a.array.size();
if (array_count && memcmp(a.array.data(), b.array.data(),
array_count * sizeof(uint32_t)) != 0)
return false;
if (a.basetype == spirv_cross::SPIRType::Image ||
a.basetype == spirv_cross::SPIRType::SampledImage) {
if (memcmp(&a.image, &b.image, sizeof(spirv_cross::SPIRType::Image)) != 0)
return false;
}
if (a.member_types.size() != b.member_types.size()) return false;
size_t member_types = a.member_types.size();
for (size_t i = 0; i < member_types; i++) {
if (!types_are_logically_equivalent(
get<spirv_cross::SPIRType>(a.member_types[i]),
get<spirv_cross::SPIRType>(b.member_types[i])))
return false;
}
return true;
}
// Returns the string representation of the data starting at the index of the
// given instruction, assuming data is null terminated.
inline std::string GetWordsAsString(uint32_t index, Instruction *inst) {
return reinterpret_cast<const char *>(inst->GetInOperand(index).words.data());
}
SpvcIrPass::SpvcIrPass(spirv_cross::ParsedIR *ir) {
ir_ = ir;
current_function_ = nullptr;
current_block_ = nullptr;
status_ = Status::SuccessWithoutChange;
}
bool SpvcIrPass::CheckConditionAndSetErrorMessage(bool condition,
std::string message) {
if (!condition) {
status_ = Status::Failure;
if (consumer()) {
consumer()(SPV_MSG_ERROR, 0, {0, 0, 0}, message.c_str());
} else {
assert(condition && message.c_str());
}
}
return condition;
}
Pass::Status SpvcIrPass::Process() {
auto s = ir_->spirv.data();
if (!CheckConditionAndSetErrorMessage(ir_->spirv.size() > 3,
"SpvcIrPass: spirv data is too small"))
return Status::Failure;
uint32_t bound = s[3];
ir_->set_id_bounds(bound);
get_module()->ForEachInst(
[this](Instruction *inst) {
if (status_ != Status::SuccessWithoutChange) return;
GenerateSpirvCrossIR(inst);
},
true);
if (!CheckConditionAndSetErrorMessage(
!current_block_,
"SpvcIrPass: Error at the end of parsing, block was not terminated."))
return status_;
if (!CheckConditionAndSetErrorMessage(
!current_function_,
"SpvcIrPass: Error at the end of parsing, function was not "
"terminated."))
return status_;
return status_;
}
void SpvcIrPass::GenerateSpirvCrossIR(Instruction *inst) {
switch (inst->opcode()) {
case SpvOpSourceContinued:
case SpvOpSourceExtension:
case SpvOpNop:
case SpvOpModuleProcessed:
break;
// opcode: 1
case SpvOpUndef: {
uint32_t result_type = inst->type_id();
uint32_t id = inst->result_id();
set<spirv_cross::SPIRUndef>(id, result_type);
if (current_block_) {
spirv_cross::Instruction instr = {};
instr.op = inst->opcode();
instr.count = inst->NumOperandWords() + 1;
instr.offset = offset_ + 1;
instr.length = instr.count - 1;
current_block_->ops.push_back(instr);
}
break;
}
case SpvOpSource: {
auto lang =
static_cast<spv::SourceLanguage>(inst->GetSingleWordInOperand(0u));
switch (lang) {
case spv::SourceLanguageESSL:
ir_->source.es = true;
ir_->source.version = inst->GetSingleWordInOperand(1u);
ir_->source.known = true;
ir_->source.hlsl = false;
break;
case spv::SourceLanguageGLSL:
ir_->source.es = false;
ir_->source.version = inst->GetSingleWordInOperand(1u);
ir_->source.known = true;
ir_->source.hlsl = false;
break;
case spv::SourceLanguageHLSL:
// For purposes of cross-compiling, this is GLSL 450.
ir_->source.es = false;
ir_->source.version = 450;
ir_->source.known = true;
ir_->source.hlsl = true;
break;
default:
ir_->source.known = false;
break;
}
break;
}
case SpvOpCapability: {
auto cap = inst->GetSingleWordInOperand(0u);
if (!CheckConditionAndSetErrorMessage(
cap != spv::CapabilityKernel,
"SpvcIrPass: Error while parsing OpCapability, "
"kernel capability not supported."))
return;
ir_->declared_capabilities.push_back(static_cast<spv::Capability>(cap));
break;
}
case SpvOpExtension: {
const std::string extension_name = GetWordsAsString(0u, inst);
ir_->declared_extensions.push_back(extension_name);
break;
}
// opcode: 5
case SpvOpName: {
uint32_t id = inst->GetSingleWordInOperand(0u);
ir_->set_name(id, GetWordsAsString(1u, inst));
break;
}
// opcode:6
case SpvOpMemberName: {
uint32_t id = inst->GetSingleWordInOperand(0u);
uint32_t member = inst->GetSingleWordInOperand(1u);
ir_->set_member_name(id, member, GetWordsAsString(2u, inst));
break;
}
// opcode: 7
case SpvOpString: {
set<spirv_cross::SPIRString>(inst->result_id(),
GetWordsAsString(0u, inst));
break;
}
// opcode:11
case SpvOpExtInstImport: {
uint32_t id = inst->result_id();
std::string ext(
reinterpret_cast<const char *>(inst->GetInOperand(0u).words.data()));
if (ext == "GLSL.std.450")
set<spirv_cross::SPIRExtension>(id, spirv_cross::SPIRExtension::GLSL);
else if (ext == "DebugInfo")
set<spirv_cross::SPIRExtension>(
id, spirv_cross::SPIRExtension::SPV_debug_info);
else if (ext == "SPV_AMD_shader_ballot")
set<spirv_cross::SPIRExtension>(
id, spirv_cross::SPIRExtension::SPV_AMD_shader_ballot);
else if (ext == "SPV_AMD_shader_explicit_vertex_parameter")
set<spirv_cross::SPIRExtension>(
id, spirv_cross::SPIRExtension::
SPV_AMD_shader_explicit_vertex_parameter);
else if (ext == "SPV_AMD_shader_trinary_minmax")
set<spirv_cross::SPIRExtension>(
id, spirv_cross::SPIRExtension::SPV_AMD_shader_trinary_minmax);
else if (ext == "SPV_AMD_gcn_shader")
set<spirv_cross::SPIRExtension>(
id, spirv_cross::SPIRExtension::SPV_AMD_gcn_shader);
else {
// spirv-cross comment:
// Other SPIR-V extensions which have ExtInstrs are currently not
// supported.
// TODO(sarahM0): figure out which ones are not supported and try to
// add them.
if (!CheckConditionAndSetErrorMessage(
false,
"SpvcIrPass: Error while parsing "
"OpExtInstImport, SPIRV extension "
"not supported: " +
ext))
return;
}
break;
}
// opcode: 12
// TODO(sarahM0): Figure out how to test this.
case SpvOpExtInst: {
// spirv_cross comment:
// The SPIR-V debug information extended instructions might come at
// global scope.
spirv_cross::Instruction instruction = {};
instruction.op = inst->opcode();
instruction.count = inst->NumOperandWords() + 1;
instruction.offset = offset_ + 1;
instruction.length = instruction.count - 1;
if (current_block_) current_block_->ops.push_back(instruction);
break;
}
// opcode: 14
case SpvOpMemoryModel: {
ir_->addressing_model =
static_cast<spv::AddressingModel>(inst->GetSingleWordInOperand(0u));
ir_->memory_model =
static_cast<spv::MemoryModel>(inst->GetSingleWordInOperand(1u));
break;
}
// opcode:15
case SpvOpEntryPoint: {
auto function_id = inst->GetSingleWordInOperand(1u);
auto execution_mode =
static_cast<spv::ExecutionModel>(inst->GetSingleWordInOperand(0u));
const char *entry_name =
reinterpret_cast<const char *>(inst->GetInOperand(2u).words.data());
auto itr = ir_->entry_points.insert(std::make_pair(
function_id, spirv_cross::SPIREntryPoint(function_id, execution_mode,
entry_name)));
auto &e = itr.first->second;
ir_->set_name(function_id, e.name);
for (uint32_t i = 3; i < inst->NumInOperands(); ++i) {
e.interface_variables.push_back(inst->GetSingleWordInOperand(i));
}
if (!ir_->default_entry_point) ir_->default_entry_point = function_id;
break;
}
// opcode: 16
case SpvOpExecutionMode: {
auto &execution = ir_->entry_points[inst->GetSingleWordInOperand(0u)];
auto mode =
static_cast<SpvExecutionMode>(inst->GetSingleWordInOperand(1u));
execution.flags.set(mode);
switch (mode) {
case SpvExecutionModeInvocations: {
execution.invocations = inst->GetSingleWordInOperand(2u);
break;
}
case SpvExecutionModeLocalSize: {
execution.workgroup_size.x = inst->GetSingleWordInOperand(2u);
execution.workgroup_size.y = inst->GetSingleWordInOperand(3u);
execution.workgroup_size.z = inst->GetSingleWordInOperand(4u);
break;
}
case SpvExecutionModeOutputVertices: {
execution.output_vertices = inst->GetSingleWordInOperand(2u);
break;
}
default: {
break;
// Other executions modes are set as part of "Bitset flags" member
// of struct SPIREntryPoint in the compiler
}
}
break;
}
// opcode: 71
case SpvOpDecorate:
// opcode: 332
case SpvOpDecorateId: {
// spirv-cross comment:
// OpDecorateId technically supports an array of arguments, but our
// only supported decorations are single uint, so merge decorate and
// decorate-id here.
// TODO(sarahM0): investigate if this limitation is acceptable.
uint32_t id = inst->GetSingleWordInOperand(0u);
auto decoration =
static_cast<spv::Decoration>(inst->GetSingleWordInOperand(1u));
if (inst->NumInOperands() > 2) {
// instruction offset + length = offset_ + 1 +
// inst->NumOpreandWords()
if (!CheckConditionAndSetErrorMessage(
offset_ + 1 + inst->NumOperandWords() < ir_->spirv.size(),
"SpvcIrPass: Error while parsing OpDecorate/OpDecorateId, "
"reading out of spirv.data() bound"))
return;
// The extra operand of the decoration (Literal, Literal, …) are at
// instruction offset + 2 (skipping <id>Target, Decoration)
ir_->meta[id].decoration_word_offset[decoration] =
uint32_t((&ir_->spirv[offset_ + 1] + 2) - ir_->spirv.data());
ir_->set_decoration(id, decoration, inst->GetSingleWordInOperand(2u));
} else {
ir_->set_decoration(id, decoration);
}
break;
}
// opcode: 72
case SpvOpMemberDecorate: {
uint32_t id = inst->GetSingleWordInOperand(0u);
uint32_t member = inst->GetSingleWordInOperand(1u);
auto decoration =
static_cast<spv::Decoration>(inst->GetSingleWordInOperand(2u));
if (inst->NumInOperands() >= 4)
ir_->set_member_decoration(id, member, decoration,
inst->GetSingleWordInOperand(3u));
else
ir_->set_member_decoration(id, member, decoration);
break;
}
// opcode: 73
case SpvOpDecorationGroup: {
// spirv-cross comment:
// Noop, this simply means an ID should be a collector of decorations.
// The meta array is already a flat array of decorations which will
// contain the relevant decorations.
break;
}
// opcode: 74
case SpvOpGroupDecorate: {
uint32_t group_id = inst->GetSingleWordInOperand(0u);
auto &decorations = ir_->meta[group_id].decoration;
auto &flags = decorations.decoration_flags;
// Copies decorations from one ID to another. Only copy decorations which
// are set in the group, i.e., we cannot just copy the meta structure
// directly.
for (uint32_t i = 1; i < inst->NumInOperands(); i++) {
uint32_t target = inst->GetSingleWordInOperand(i);
flags.for_each_bit([&](uint32_t bit) {
auto decoration = static_cast<spv::Decoration>(bit);
if (decoration_is_string(decoration)) {
ir_->set_decoration_string(
target, decoration,
ir_->get_decoration_string(group_id, decoration));
} else {
ir_->meta[target].decoration_word_offset[decoration] =
ir_->meta[group_id].decoration_word_offset[decoration];
ir_->set_decoration(target, decoration,
ir_->get_decoration(group_id, decoration));
}
});
}
break;
}
// opcode: 75
case SpvOpGroupMemberDecorate: {
uint32_t group_id = inst->GetSingleWordInOperand(0u);
auto &flags = ir_->meta[group_id].decoration.decoration_flags;
// Copies decorations from one ID to another. Only copy decorations which
// are set in the group, i.e., we cannot just copy the meta structure
// directly.
for (uint32_t i = 1; i + 1 < inst->NumInOperands(); i += 2) {
uint32_t target = inst->GetSingleWordInOperand(i + 0u);
uint32_t index = inst->GetSingleWordInOperand(i + 1u);
flags.for_each_bit([&](uint32_t bit) {
auto decoration = static_cast<spv::Decoration>(bit);
if (decoration_is_string(decoration))
ir_->set_member_decoration_string(
target, index, decoration,
ir_->get_decoration_string(group_id, decoration));
else
ir_->set_member_decoration(
target, index, decoration,
ir_->get_decoration(group_id, decoration));
});
}
break;
}
// opcode: 5632
case SpvOpDecorateStringGOOGLE: {
uint32_t id = inst->GetSingleWordInOperand(0u);
auto decoration =
static_cast<spv::Decoration>(inst->GetSingleWordInOperand(1u));
ir_->set_decoration_string(id, decoration, GetWordsAsString(2u, inst));
break;
}
// opcode: 5633
case SpvOpMemberDecorateStringGOOGLE: {
uint32_t id = inst->GetSingleWordInOperand(0u);
uint32_t member = inst->GetSingleWordInOperand(1u);
auto decoration =
static_cast<spv::Decoration>(inst->GetSingleWordInOperand(2u));
ir_->set_member_decoration_string(id, member, decoration,
GetWordsAsString(3u, inst));
break;
}
// Basic type cases
// opcode: 19
case SpvOpTypeVoid: {
auto id = inst->result_id();
auto &type = set<spirv_cross::SPIRType>(id);
type.basetype = spirv_cross::SPIRType::Void;
break;
}
// opcode: 20
case SpvOpTypeBool: {
uint32_t id = inst->result_id();
auto &type = set<spirv_cross::SPIRType>(id);
type.basetype = spirv_cross::SPIRType::Boolean;
type.width = 1;
break;
}
// opcode: 21
case SpvOpTypeInt: {
uint32_t id = inst->result_id();
uint32_t width = inst->GetSingleWordInOperand(0u);
bool signedness = inst->GetSingleWordInOperand(1u) != 0;
auto &type = set<spirv_cross::SPIRType>(id);
type.basetype = signedness ? spirv_cross::to_signed_basetype(width)
: spirv_cross::to_unsigned_basetype(width);
type.width = width;
break;
}
// opcode: 22
case SpvOpTypeFloat: {
uint32_t id = inst->result_id();
auto &type = set<spirv_cross::SPIRType>(id);
uint32_t width = inst->GetSingleWordInOperand(0u);
if (width == 64)
type.basetype = spirv_cross::SPIRType::Double;
else if (width == 32)
type.basetype = spirv_cross::SPIRType::Float;
else if (width == 16)
type.basetype = spirv_cross::SPIRType::Half;
else
CheckConditionAndSetErrorMessage(
false, "Unrecognized bit-width of floating point type.");
type.width = width;
break;
}
// opcode: 23
// spirv-cross comment:
// Build composite types by "inheriting".
// NOTE: The self member is also copied! For pointers and array modifiers
// this is a good thing since we can refer to decorations on pointee
// classes which is needed for UBO/SSBO, I/O blocks in geometry/tess etc.
case SpvOpTypeVector: {
uint32_t id = inst->result_id();
// TODO(sarahM0): ask if Column Count, in operand one, can be
// double-word.
uint32_t vecsize = inst->GetSingleWordInOperand(1u);
auto &base = get<spirv_cross::SPIRType>(inst->GetSingleWordInOperand(0u));
auto &vecbase = set<spirv_cross::SPIRType>(id);
vecbase = base;
vecbase.vecsize = vecsize;
vecbase.self = id;
vecbase.parent_type = inst->GetSingleWordInOperand(0u);
break;
}
// opcode: 24
case SpvOpTypeMatrix: {
uint32_t id = inst->result_id();
uint32_t colcount = inst->GetSingleWordInOperand(1u);
auto &base = get<spirv_cross::SPIRType>(inst->GetSingleWordInOperand(0u));
auto &matrixbase = set<spirv_cross::SPIRType>(id);
matrixbase = base;
matrixbase.columns = colcount;
matrixbase.self = id;
matrixbase.parent_type = inst->GetSingleWordInOperand(0u);
break;
}
// opcode: 25
case SpvOpTypeImage: {
uint32_t id = inst->result_id();
auto &type = set<spirv_cross::SPIRType>(id);
type.basetype = spirv_cross::SPIRType::Image;
type.image.type = inst->GetSingleWordInOperand(0u);
type.image.dim = static_cast<spv::Dim>(inst->GetSingleWordInOperand(1u));
type.image.depth = inst->GetSingleWordInOperand(2u) == 1;
type.image.arrayed = inst->GetSingleWordInOperand(3u) != 0;
type.image.ms = inst->GetSingleWordInOperand(4u) != 0;
type.image.sampled = inst->GetSingleWordInOperand(5u);
type.image.format =
static_cast<spv::ImageFormat>(inst->GetSingleWordInOperand(6u));
type.image.access = (inst->NumInOperands() > 7)
? static_cast<spv::AccessQualifier>(
inst->GetSingleWordInOperand(7u))
: spv::AccessQualifierMax;
break;
}
// opcode: 26
case SpvOpTypeSampler: {
uint32_t id = inst->result_id();
auto &type = set<spirv_cross::SPIRType>(id);
type.basetype = spirv_cross::SPIRType::Sampler;
break;
}
// opcode: 27
case SpvOpTypeSampledImage: {
uint32_t id = inst->result_id();
uint32_t imagetype = inst->GetSingleWordInOperand(0u);
auto &type = set<spirv_cross::SPIRType>(id);
type = get<spirv_cross::SPIRType>(imagetype);
type.basetype = spirv_cross::SPIRType::SampledImage;
type.self = id;
break;
}
// opcode: 28
case SpvOpTypeArray: {
uint32_t id = inst->result_id();
auto &arraybase = set<spirv_cross::SPIRType>(id);
uint32_t tid = inst->GetSingleWordInOperand(0u);
auto &base = get<spirv_cross::SPIRType>(tid);
arraybase = base;
arraybase.parent_type = tid;
uint32_t cid = inst->GetSingleWordInOperand(1u);
ir_->mark_used_as_array_length(cid);
auto *c = maybe_get<spirv_cross::SPIRConstant>(cid);
bool literal = c && !c->specialization;
arraybase.array_size_literal.push_back(literal);
arraybase.array.push_back(literal ? c->scalar() : cid);
// spirv-cross comment:
// Do NOT set arraybase.self!
break;
}
// opcode: 29
case SpvOpTypeRuntimeArray: {
auto id = inst->result_id();
auto tid = inst->GetSingleWordInOperand(0u);
auto &base = get<spirv_cross::SPIRType>(tid);
auto &arraybase = set<spirv_cross::SPIRType>(id);
arraybase = base;
arraybase.array.push_back(0);
arraybase.array_size_literal.push_back(true);
arraybase.parent_type = tid;
break;
}
// opcode: 32
case SpvOpTypePointer: {
uint32_t id = inst->result_id();
auto &base = get<spirv_cross::SPIRType>(inst->GetSingleWordInOperand(1u));
auto &ptrbase = set<spirv_cross::SPIRType>(id);
ptrbase = base;
ptrbase.pointer = true;
ptrbase.pointer_depth++;
ptrbase.storage =
static_cast<spv::StorageClass>(inst->GetSingleWordInOperand(0u));
if (ptrbase.storage == spv::StorageClassAtomicCounter)
ptrbase.basetype = spirv_cross::SPIRType::AtomicCounter;
ptrbase.parent_type = inst->GetSingleWordInOperand(1u);
// spirv-cross comment:
// Do NOT set ptrbase.self!
break;
}
// opcode: 33
case SpvOpTypeFunction: {
auto id = inst->result_id();
auto return_type = inst->GetSingleWordInOperand(0u);
// Reading Parameter 0 Type, Parameter 1 Type, ...
// Starting i at 1 to skip over the function return type parameter.
auto &func = set<spirv_cross::SPIRFunctionPrototype>(id, return_type);
for (uint32_t i = 1; i < inst->NumInOperands(); ++i) {
func.parameter_types.push_back(inst->GetSingleWordInOperand(i));
}
break;
}
// opcode: 39
case SpvOpTypeForwardPointer: {
uint32_t id = inst->GetSingleWordInOperand(0u);
auto &ptrbase = set<spirv_cross::SPIRType>(id);
ptrbase.pointer = true;
ptrbase.pointer_depth++;
ptrbase.storage =
static_cast<spv::StorageClass>(inst->GetSingleWordInOperand(1u));
// TODO(sarahM0): add test for with AtomicCounter storage class
if (ptrbase.storage == spv::StorageClassAtomicCounter)
ptrbase.basetype = spirv_cross::SPIRType::AtomicCounter;
break;
}
// opcode: 5341
case SpvOpTypeAccelerationStructureKHR: {
uint32_t id = inst->result_id();
auto &type = set<spirv_cross::SPIRType>(id);
type.basetype = spirv_cross::SPIRType::AccelerationStructure;
break;
}
// Variable declaration
// opcode: 59
// spirv-cross comment:
// All variables are essentially pointers with a storage qualifier.
case SpvOpVariable: {
uint32_t type = inst->type_id();
uint32_t id = inst->result_id();
auto storage =
static_cast<spv::StorageClass>(inst->GetSingleWordInOperand(0u));
uint32_t initializer =
inst->NumInOperands() == 2 ? inst->GetSingleWordInOperand(1u) : 0;
if (storage == spv::StorageClassFunction) {
if (!CheckConditionAndSetErrorMessage(
current_function_,
"SpvcIrPass: Error while parsing OpVariable, "
"no function currently in scope"))
return;
current_function_->add_local_variable(id);
}
set<spirv_cross::SPIRVariable>(id, type, storage, initializer);
// spirv-cross comment:
// hlsl based shaders don't have those decorations. force them and
// then reset when reading/writing images
auto &ttype = get<spirv_cross::SPIRType>(type);
if (ttype.basetype == spirv_cross::SPIRType::BaseType::Image) {
ir_->set_decoration(id, spv::DecorationNonWritable);
ir_->set_decoration(id, spv::DecorationNonReadable);
}
break;
}
// opcode: 30
case SpvOpTypeStruct: {
uint32_t id = inst->result_id();
auto &type = set<spirv_cross::SPIRType>(id);
type.basetype = spirv_cross::SPIRType::Struct;
for (uint32_t i = 0; i < inst->NumInOperands(); i++) {
type.member_types.push_back(inst->GetSingleWordInOperand(i));
}
// TODO(sarahM0): ask about aliasing? figure out what is happening in
// this loop.
bool consider_aliasing = !ir_->get_name(type.self).empty();
if (consider_aliasing) {
for (auto &other : global_struct_cache_) {
if (ir_->get_name(type.self) == ir_->get_name(other) &&
types_are_logically_equivalent(
type, get<spirv_cross::SPIRType>(other))) {
type.type_alias = other;
break;
}
}
if (type.type_alias == spirv_cross::TypeID(0))
global_struct_cache_.push_back(id);
}
break;
}
// Constant Cases
// opcode: 50
case SpvOpSpecConstant:
// opcode: 43
case SpvOpConstant: {
uint32_t id = inst->result_id();
auto &type = get<spirv_cross::SPIRType>(inst->type_id());
// TODO(sarahM0): floating-point numbers of IEEE 754 are of length 16
// bits, 32 bits, 64 bits, and any multiple of 32 bits up to 128 bits
// Ask whether we need to support longer than 64 bits
if (type.width > 32) {
uint64_t combined_words = inst->GetInOperand(0u).words[1];
combined_words = combined_words << 32;
combined_words |= inst->GetInOperand(0u).words[0];
set<spirv_cross::SPIRConstant>(id, inst->type_id(), combined_words,
inst->opcode() == SpvOpSpecConstant);
} else {
set<spirv_cross::SPIRConstant>(id, inst->type_id(),
inst->GetSingleWordInOperand(0u),
inst->opcode() == SpvOpSpecConstant);
}
break;
}
// opcode: 49
case SpvOpSpecConstantFalse:
// opcode: 42
case SpvOpConstantFalse: {
uint32_t id = inst->result_id();
set<spirv_cross::SPIRConstant>(id, inst->type_id(), 0u,
inst->opcode() == SpvOpSpecConstantFalse);
break;
}
// opcode: 48
case SpvOpSpecConstantTrue:
// opcode: 41
case SpvOpConstantTrue: {
uint32_t id = inst->result_id();
set<spirv_cross::SPIRConstant>(id, inst->type_id(), 1u,
inst->opcode() == SpvOpSpecConstantTrue);
break;
}
// opcode: 46
case SpvOpConstantNull: {
uint32_t id = inst->result_id();
uint32_t type = inst->type_id();
make_constant_null(id, type);
break;
}
// opcode: 51
case SpvOpSpecConstantComposite:
// opcode: 44
case SpvOpConstantComposite: {
uint32_t id = inst->result_id();
uint32_t type = inst->type_id();
auto &ctype = get<spirv_cross::SPIRType>(type);
// spirv-cross comment:
// We can have constants which are structs and arrays.
// In this case, our SPIRConstant will be a list of other
// SPIRConstant ids which we can refer to.
if (ctype.basetype == spirv_cross::SPIRType::Struct ||
!ctype.array.empty()) {
set<spirv_cross::SPIRConstant>(
id, type, (&ir_->spirv[offset_ + 1u] + 2u), inst->NumInOperands(),
inst->opcode() == SpvOpSpecConstantComposite);
} else {
if (!CheckConditionAndSetErrorMessage(
inst->NumInOperands() < 5,
"OpConstantComposite only supports 1, 2, 3 and 4 elements."))
return;
spirv_cross::SPIRConstant remapped_constant_ops[4];
const spirv_cross::SPIRConstant *c[4];
for (uint32_t i = 0; i < inst->NumInOperands(); i++) {
// spirv-cross comment:
// Specialization constants operations can also be part of this.
// We do not know their value, so any attempt to query
// SPIRConstant later will fail. We can only propagate the ID of the
// expression and use to_expression on it.
auto *constant_op = maybe_get<spirv_cross::SPIRConstantOp>(
inst->GetSingleWordInOperand(i));
auto *undef_op = maybe_get<spirv_cross::SPIRUndef>(
inst->GetSingleWordInOperand(i));
if (constant_op) {
if (!CheckConditionAndSetErrorMessage(
inst->opcode() != SpvOpConstantComposite,
"Specialization constant operation used in "
"OpConstantComposite."))
return;
remapped_constant_ops[i].make_null(
get<spirv_cross::SPIRType>(constant_op->basetype));
remapped_constant_ops[i].self = constant_op->self;
remapped_constant_ops[i].constant_type = constant_op->basetype;
remapped_constant_ops[i].specialization = true;
c[i] = &remapped_constant_ops[i];
} else if (undef_op) {
// spirv-cross comment:
// Undefined, just pick 0.
remapped_constant_ops[i].make_null(
get<spirv_cross::SPIRType>(undef_op->basetype));
remapped_constant_ops[i].constant_type = undef_op->basetype;
c[i] = &remapped_constant_ops[i];
} else {
c[i] = &get<spirv_cross::SPIRConstant>(
inst->GetSingleWordInOperand(i));
}
}
set<spirv_cross::SPIRConstant>(
id, type, c, inst->NumInOperands(),
inst->opcode() == SpvOpSpecConstantComposite);
}
break;
}
case SpvOpSpecConstantOp: {
uint32_t result_type = inst->type_id();
uint32_t id = inst->result_id();
auto spec_op = static_cast<spv::Op>(inst->GetSingleWordInOperand(0u));
set<spirv_cross::SPIRConstantOp>(id, result_type, spec_op,
(&ir_->spirv[offset_ + 1u] + 3u),
inst->NumInOperands() - 1);
break;
}
// Control Flow cases
// opcode: 245
// spirv-cross comment:
// OpPhi is a fairly magical opcode.
// It selects temporary variables based on which parent block we *came
// from*. In high-level languages we can "de-SSA" by creating a function
// local, and flush out temporaries to this function-local variable to
// emulate SSA Phi.
case SpvOpPhi: {
if (!CheckConditionAndSetErrorMessage(
current_function_,
"SpvcIrPass: Trying to end a non-existing block."))
return;
if (!CheckConditionAndSetErrorMessage(
current_block_, "SpvcIrPass: No block currently in scope"))
return;
uint32_t result_type = inst->type_id();
uint32_t id = inst->result_id();
// spirv-cross comment:
// Instead of a temporary, create a new function-wide temporary with
// this ID instead.
auto &var = set<spirv_cross::SPIRVariable>(id, result_type,
spv::StorageClassFunction);
var.phi_variable = true;
current_function_->add_local_variable(id);
for (uint32_t i = 0; i + 1 < inst->NumInOperands(); i += 2)
current_block_->phi_variables.push_back(
{inst->GetSingleWordInOperand(i),
inst->GetSingleWordInOperand(i + 1), id});
break;
}
// opcode: 246
case SpvOpLoopMerge: {
if (!CheckConditionAndSetErrorMessage(
current_block_,
"SpvcIrPass: Trying to end a non-existing block."))
return;
current_block_->merge_block = inst->GetSingleWordInOperand(0u);
current_block_->continue_block = inst->GetSingleWordInOperand(1u);
current_block_->merge = spirv_cross::SPIRBlock::MergeLoop;
ir_->block_meta[current_block_->self] |=
spirv_cross::ParsedIR::BLOCK_META_LOOP_HEADER_BIT;
ir_->block_meta[current_block_->merge_block] |=
spirv_cross::ParsedIR::BLOCK_META_LOOP_MERGE_BIT;
ir_->continue_block_to_loop_header[current_block_->continue_block] =
spirv_cross::BlockID(current_block_->self);
// spirv-cross comment:
// Don't add loop headers to continue blocks,
// which would make it impossible branch into the loop header since
// they are treated as continues.
if (current_block_->continue_block !=
spirv_cross::BlockID(current_block_->self))
ir_->block_meta[current_block_->continue_block] |=
spirv_cross::ParsedIR::BLOCK_META_CONTINUE_BIT;
if (inst->NumInOperands() > 3) {
if (inst->GetSingleWordInOperand(2u) & spv::LoopControlUnrollMask)
current_block_->hint = spirv_cross::SPIRBlock::HintUnroll;
else if (inst->GetSingleWordInOperand(2u) &
spv::LoopControlDontUnrollMask)
current_block_->hint = spirv_cross::SPIRBlock::HintDontUnroll;
}
break;
}
// opcode 247
case SpvOpSelectionMerge: {
if (!CheckConditionAndSetErrorMessage(
current_block_,
"SpvcIrPass: Trying to end a non-existing block."))
return;
current_block_->next_block = inst->GetSingleWordInOperand(0u);
current_block_->merge = spirv_cross::SPIRBlock::MergeSelection;
ir_->block_meta[current_block_->next_block] |=
spirv_cross::ParsedIR::BLOCK_META_SELECTION_MERGE_BIT;
if (inst->NumInOperands() - 1 >= 2) {
if (inst->GetSingleWordInOperand(1u) & spv::SelectionControlFlattenMask)
current_block_->hint = spirv_cross::SPIRBlock::HintFlatten;
else if (inst->GetSingleWordInOperand(1u) &
spv::SelectionControlDontFlattenMask)
current_block_->hint = spirv_cross::SPIRBlock::HintDontFlatten;
}
break;
}
// opcode: 248
case SpvOpLabel: {
// OpLabel always starts a block.
if (!CheckConditionAndSetErrorMessage(
current_function_,
"SpvcIrPass: Error while parsing OpLable, blocks "
"cannot exist outside functions!"))
return;
uint32_t id = inst->result_id();
current_function_->blocks.push_back(id);
if (!current_function_->entry_block) {
current_function_->entry_block = id;
}
if (!CheckConditionAndSetErrorMessage(
!current_block_,
"SpvcIrPass: Error while parsing OpLable, cannot "
"start a block before ending the current block."))
return;
current_block_ = &set<spirv_cross::SPIRBlock>(id);
break;
}
// opcode: 249
case SpvOpBranch: {
if (!CheckConditionAndSetErrorMessage(
current_block_,
"SpvcIrPass: Trying to end a non-existing block."))
return;
uint32_t target = inst->GetSingleWordInOperand(0u);
current_block_->terminator = spirv_cross::SPIRBlock::Direct;
current_block_->next_block = target;
current_block_ = nullptr;
break;
}
// opcode: 250
case SpvOpBranchConditional: {
if (!CheckConditionAndSetErrorMessage(
current_block_,
"SpvcIrPass: Trying to end a non-existing block."))
return;
current_block_->condition = inst->GetSingleWordInOperand(0u);
current_block_->true_block = inst->GetSingleWordInOperand(1u);
current_block_->false_block = inst->GetSingleWordInOperand(2u);
current_block_->terminator = spirv_cross::SPIRBlock::Select;
current_block_ = nullptr;
break;
}
// opcode: 251
case SpvOpSwitch: {
if (!CheckConditionAndSetErrorMessage(
current_block_,
"SpvcIrPass: Trying to end a non-existing block."))
return;
current_block_->terminator = spirv_cross::SPIRBlock::MultiSelect;
current_block_->condition = inst->GetSingleWordInOperand(0u);
current_block_->default_block = inst->GetSingleWordInOperand(1u);
for (uint32_t i = 2; i + 1 < inst->NumInOperands(); i += 2)
current_block_->cases.push_back({inst->GetSingleWordInOperand(i),
inst->GetSingleWordInOperand(i + 1)});
// spirv-cross comment:
// If we jump to next block, make it break instead since we're inside a
// switch case block at that point.
ir_->block_meta[current_block_->next_block] |=
spirv_cross::ParsedIR::BLOCK_META_MULTISELECT_MERGE_BIT;
current_block_ = nullptr;
break;
}
// opcode: 252
case SpvOpKill: {
if (!CheckConditionAndSetErrorMessage(
current_block_,
"SpvcIrPass: Trying to end a non-existing block."))
return;
current_block_->terminator = spirv_cross::SPIRBlock::Kill;
current_block_ = nullptr;
break;
}
// opcode: 253
case SpvOpReturn: {
if (!CheckConditionAndSetErrorMessage(
current_block_,
"SpvcIrPass: Error while parsing OpReturn, "
"trying to end a non-existing block."))
return;
current_block_->terminator = spirv_cross::SPIRBlock::Return;
current_block_ = nullptr;
break;
}
// opcode: 254
case SpvOpReturnValue: {
if (!CheckConditionAndSetErrorMessage(
current_block_,
"SpvcIrPass: Trying to end a non-existing block."))
return;
current_block_->terminator = spirv_cross::SPIRBlock::Return;
current_block_->return_value = inst->GetSingleWordInOperand(0u);
current_block_ = nullptr;
break;
}
// opcode: 255
case SpvOpUnreachable: {
if (!CheckConditionAndSetErrorMessage(
current_block_,
"SpvcIrPass: Trying to end a non-existing block. Opcode: " +
std::to_string(inst->opcode())))
return;
current_block_->terminator = spirv_cross::SPIRBlock::Unreachable;
current_block_ = nullptr;
break;
}
// Function cases:
// opcode: 54
case SpvOpFunction: {
auto result = inst->type_id();
auto id = inst->result_id();
auto type = inst->GetSingleWordInOperand(1u);
if (!CheckConditionAndSetErrorMessage(
!current_function_,
"SpvcIrPass: Must end a function "
"before starting a new one. Opcode: " +
std::to_string(inst->opcode())))
return;
current_function_ = &set<spirv_cross::SPIRFunction>(id, result, type);
break;
}
// opcode: 55
case SpvOpFunctionParameter: {
auto type = inst->type_id();
auto id = inst->result_id();
if (!CheckConditionAndSetErrorMessage(
current_function_,
"SpvcIrPass: OpFunctionParameter must be in a function!"))
return;
current_function_->add_parameter(type, id);
set<spirv_cross::SPIRVariable>(id, type, spv::StorageClassFunction);
break;
}
// opcode: 56
case SpvOpFunctionEnd: {
// Very specific error message, but seems to come up quite often.
if (!CheckConditionAndSetErrorMessage(
!current_block_,
"SpvcIrPass: Error while parsing OpFunctionEnd, cannot end a "
"function before ending the current block.\n"
"Likely cause: If this SPIR-V was created from glslang HLSL, "
"make sure the entry point is valid."))
return;
current_function_ = nullptr;
break;
}
// opcode: 8
case SpvOpLine: {
// OpLine might come at global scope, but we don't care about those since
// they will not be declared in any meaningful correct order. Ignore all
// OpLine directives which live outside a function.
if (current_block_) {
spirv_cross::Instruction instr = {};
instr.op = inst->opcode();
instr.count = inst->NumOperandWords() + 1;
instr.offset = offset_ + 1;
instr.length = instr.count - 1;
current_block_->ops.push_back(instr);
}
// Line directives may arrive before first OpLabel.
// Treat this as the line of the function declaration,
// so warnings for arguments can propagate properly.
if (current_function_) {
// Store the first one we find and emit it before creating the function
// prototype.
if (current_function_->entry_line.file_id == 0) {
current_function_->entry_line.file_id =
inst->GetSingleWordInOperand(0u);
current_function_->entry_line.line_literal =
inst->GetSingleWordInOperand(1u);
}
}
break;
}
// opcode: 317
case SpvOpNoLine: {
// spirv_cross comment: OpNoLine might come at global scope.
if (current_block_) {
spirv_cross::Instruction instr = {};
instr.op = inst->opcode();
instr.count = inst->NumOperandWords() + 1;
instr.offset = offset_ + 1;
instr.length = instr.count - 1;
current_block_->ops.push_back(instr);
}
break;
}
default: {
if (!CheckConditionAndSetErrorMessage(
current_block_,
"SpvcIrPass: Currently no block to insert opcode."))
return;
spirv_cross::Instruction instr = {};
instr.op = inst->opcode();
instr.count = inst->NumOperandWords() + 1;
instr.offset = offset_ + 1;
instr.length = instr.count - 1;
if (!CheckConditionAndSetErrorMessage(
instr.count != 0,
"SpvcIrPass: SPIR-V instructions cannot consume "
"0 words. Invalid SPIR-V file."))
return;
if (!CheckConditionAndSetErrorMessage(
offset_ <= ir_->spirv.size(),
"SpvcIrPass: SPIR-V instruction goes out of bounds."))
return;
current_block_->ops.push_back(instr);
break;
}
}
offset_ += inst->NumOperandWords() + 1;
}
} // namespace opt
} // namespace spvtools