#include <algorithm>
#include <vector>
#include "source/spirv_constant.h"
#include "source/spirv_target_env.h"
#include "source/val/function.h"
#include "source/val/instruction.h"
#include "source/val/validate.h"
#include "source/val/validation_state.h"
namespace spvtools {
namespace val {
namespace {
const uint32_t kMaxLocations = 4096 * 4;
bool is_interface_variable(const Instruction* inst, bool is_spv_1_4) {
if (is_spv_1_4) {
return inst->opcode() == spv::Op::OpVariable &&
inst->GetOperandAs<spv::StorageClass>(2u) !=
spv::StorageClass::Function;
} else {
return inst->opcode() == spv::Op::OpVariable &&
(inst->GetOperandAs<spv::StorageClass>(2u) ==
spv::StorageClass::Input ||
inst->GetOperandAs<spv::StorageClass>(2u) ==
spv::StorageClass::Output);
}
}
spv_result_t check_interface_variable(ValidationState_t& _,
const Instruction* var) {
std::vector<const Function*> functions;
std::vector<const Instruction*> uses;
for (auto use : var->uses()) {
uses.push_back(use.first);
}
for (uint32_t i = 0; i < uses.size(); ++i) {
const auto user = uses[i];
if (const Function* func = user->function()) {
functions.push_back(func);
} else {
for (auto use : user->uses()) {
uses.push_back(use.first);
}
}
}
std::sort(functions.begin(), functions.end(),
[](const Function* lhs, const Function* rhs) {
return lhs->id() < rhs->id();
});
functions.erase(std::unique(functions.begin(), functions.end()),
functions.end());
std::vector<uint32_t> entry_points;
for (const auto func : functions) {
for (auto id : _.FunctionEntryPoints(func->id())) {
entry_points.push_back(id);
}
}
std::sort(entry_points.begin(), entry_points.end());
entry_points.erase(std::unique(entry_points.begin(), entry_points.end()),
entry_points.end());
for (auto id : entry_points) {
for (const auto& desc : _.entry_point_descriptions(id)) {
bool found = false;
for (auto interface : desc.interfaces) {
if (var->id() == interface) {
found = true;
break;
}
}
if (!found) {
return _.diag(SPV_ERROR_INVALID_ID, var)
<< "Interface variable id <" << var->id()
<< "> is used by entry point '" << desc.name << "' id <" << id
<< ">, but is not listed as an interface";
}
}
}
return SPV_SUCCESS;
}
spv_result_t NumConsumedLocations(ValidationState_t& _, const Instruction* type,
uint32_t* num_locations) {
*num_locations = 0;
switch (type->opcode()) {
case spv::Op::OpTypeInt:
case spv::Op::OpTypeFloat:
*num_locations = 1;
break;
case spv::Op::OpTypeVector:
if ((_.ContainsSizedIntOrFloatType(type->id(), spv::Op::OpTypeInt, 64) ||
_.ContainsSizedIntOrFloatType(type->id(), spv::Op::OpTypeFloat,
64)) &&
(type->GetOperandAs<uint32_t>(2) > 2)) {
*num_locations = 2;
} else {
*num_locations = 1;
}
break;
case spv::Op::OpTypeMatrix:
NumConsumedLocations(_, _.FindDef(type->GetOperandAs<uint32_t>(1)),
num_locations);
*num_locations *= type->GetOperandAs<uint32_t>(2);
break;
case spv::Op::OpTypeArray: {
NumConsumedLocations(_, _.FindDef(type->GetOperandAs<uint32_t>(1)),
num_locations);
bool is_int = false;
bool is_const = false;
uint32_t value = 0;
std::tie(is_int, is_const, value) =
_.EvalInt32IfConst(type->GetOperandAs<uint32_t>(2));
if (is_int && is_const) *num_locations *= value;
break;
}
case spv::Op::OpTypeStruct: {
if (_.HasDecoration(type->id(), spv::Decoration::Location)) {
return _.diag(SPV_ERROR_INVALID_DATA, type)
<< _.VkErrorID(4918) << "Members cannot be assigned a location";
}
for (uint32_t i = 1; i < type->operands().size(); ++i) {
uint32_t member_locations = 0;
if (auto error = NumConsumedLocations(
_, _.FindDef(type->GetOperandAs<uint32_t>(i)),
&member_locations)) {
return error;
}
*num_locations += member_locations;
}
break;
}
case spv::Op::OpTypePointer: {
if (_.addressing_model() ==
spv::AddressingModel::PhysicalStorageBuffer64 &&
type->GetOperandAs<spv::StorageClass>(1) ==
spv::StorageClass::PhysicalStorageBuffer) {
*num_locations = 1;
break;
}
[[fallthrough]];
}
default:
return _.diag(SPV_ERROR_INVALID_DATA, type)
<< "Invalid type to assign a location";
}
return SPV_SUCCESS;
}
uint32_t NumConsumedComponents(ValidationState_t& _, const Instruction* type) {
uint32_t num_components = 0;
switch (type->opcode()) {
case spv::Op::OpTypeInt:
case spv::Op::OpTypeFloat:
if (type->GetOperandAs<uint32_t>(1) == 64) {
num_components = 2;
} else {
num_components = 1;
}
break;
case spv::Op::OpTypeVector:
num_components =
NumConsumedComponents(_, _.FindDef(type->GetOperandAs<uint32_t>(1)));
num_components *= type->GetOperandAs<uint32_t>(2);
break;
case spv::Op::OpTypeArray:
return NumConsumedComponents(_,
_.FindDef(type->GetOperandAs<uint32_t>(1)));
case spv::Op::OpTypePointer:
if (_.addressing_model() ==
spv::AddressingModel::PhysicalStorageBuffer64 &&
type->GetOperandAs<spv::StorageClass>(1) ==
spv::StorageClass::PhysicalStorageBuffer) {
return 2;
}
break;
default:
break;
}
return num_components;
}
spv_result_t GetLocationsForVariable(
ValidationState_t& _, const Instruction* entry_point,
const Instruction* variable, std::unordered_set<uint32_t>* locations,
std::unordered_set<uint32_t>* output_index1_locations) {
const bool is_fragment = entry_point->GetOperandAs<spv::ExecutionModel>(0) ==
spv::ExecutionModel::Fragment;
const bool is_output =
variable->GetOperandAs<spv::StorageClass>(2) == spv::StorageClass::Output;
auto ptr_type_id = variable->GetOperandAs<uint32_t>(0);
auto ptr_type = _.FindDef(ptr_type_id);
auto type_id = ptr_type->GetOperandAs<uint32_t>(2);
auto type = _.FindDef(type_id);
bool has_location = false;
uint32_t location = 0;
bool has_component = false;
uint32_t component = 0;
bool has_index = false;
uint32_t index = 0;
bool has_patch = false;
bool has_per_task_nv = false;
bool has_per_vertex_khr = false;
for (auto& dec : _.id_decorations(variable->id())) {
if (dec.dec_type() == spv::Decoration::Location) {
if (has_location && dec.params()[0] != location) {
return _.diag(SPV_ERROR_INVALID_DATA, variable)
<< "Variable has conflicting location decorations";
}
has_location = true;
location = dec.params()[0];
} else if (dec.dec_type() == spv::Decoration::Component) {
if (has_component && dec.params()[0] != component) {
return _.diag(SPV_ERROR_INVALID_DATA, variable)
<< "Variable has conflicting component decorations";
}
has_component = true;
component = dec.params()[0];
} else if (dec.dec_type() == spv::Decoration::Index) {
if (!is_output || !is_fragment) {
return _.diag(SPV_ERROR_INVALID_DATA, variable)
<< "Index can only be applied to Fragment output variables";
}
if (has_index && dec.params()[0] != index) {
return _.diag(SPV_ERROR_INVALID_DATA, variable)
<< "Variable has conflicting index decorations";
}
has_index = true;
index = dec.params()[0];
} else if (dec.dec_type() == spv::Decoration::BuiltIn) {
return SPV_SUCCESS;
} else if (dec.dec_type() == spv::Decoration::Patch) {
has_patch = true;
} else if (dec.dec_type() == spv::Decoration::PerTaskNV) {
has_per_task_nv = true;
} else if (dec.dec_type() == spv::Decoration::PerVertexKHR) {
if (!is_fragment) {
return _.diag(SPV_ERROR_INVALID_DATA, variable)
<< _.VkErrorID(6777)
<< "PerVertexKHR can only be applied to Fragment Execution "
"Models";
}
if (type->opcode() != spv::Op::OpTypeArray &&
type->opcode() != spv::Op::OpTypeRuntimeArray) {
return _.diag(SPV_ERROR_INVALID_DATA, variable)
<< _.VkErrorID(6778)
<< "PerVertexKHR must be declared as arrays";
}
has_per_vertex_khr = true;
}
}
bool is_arrayed = false;
switch (entry_point->GetOperandAs<spv::ExecutionModel>(0)) {
case spv::ExecutionModel::TessellationControl:
if (!has_patch) {
is_arrayed = true;
}
break;
case spv::ExecutionModel::TessellationEvaluation:
if (!is_output && !has_patch) {
is_arrayed = true;
}
break;
case spv::ExecutionModel::Geometry:
if (!is_output) {
is_arrayed = true;
}
break;
case spv::ExecutionModel::Fragment:
if (!is_output && has_per_vertex_khr) {
is_arrayed = true;
}
break;
case spv::ExecutionModel::MeshNV:
if (is_output && !has_per_task_nv) {
is_arrayed = true;
}
break;
default:
break;
}
if (is_arrayed && (type->opcode() == spv::Op::OpTypeArray ||
type->opcode() == spv::Op::OpTypeRuntimeArray)) {
type_id = type->GetOperandAs<uint32_t>(1);
type = _.FindDef(type_id);
}
if (type->opcode() == spv::Op::OpTypeStruct) {
if (_.HasDecoration(type_id, spv::Decoration::BuiltIn)) return SPV_SUCCESS;
}
const bool is_block = _.HasDecoration(type_id, spv::Decoration::Block);
if (!has_location && !is_block) {
const auto vuid = (type->opcode() == spv::Op::OpTypeStruct) ? 4917 : 4916;
return _.diag(SPV_ERROR_INVALID_DATA, variable)
<< _.VkErrorID(vuid) << "Variable must be decorated with a location";
}
const std::string storage_class = is_output ? "output" : "input";
if (has_location) {
auto sub_type = type;
bool is_int = false;
bool is_const = false;
uint32_t array_size = 1;
if (type->opcode() == spv::Op::OpTypeArray) {
std::tie(is_int, is_const, array_size) =
_.EvalInt32IfConst(type->GetOperandAs<uint32_t>(2));
if (!is_int || !is_const) array_size = 1;
auto sub_type_id = type->GetOperandAs<uint32_t>(1);
sub_type = _.FindDef(sub_type_id);
}
uint32_t num_locations = 0;
if (auto error = NumConsumedLocations(_, sub_type, &num_locations))
return error;
uint32_t num_components = NumConsumedComponents(_, sub_type);
for (uint32_t array_idx = 0; array_idx < array_size; ++array_idx) {
uint32_t array_location = location + (num_locations * array_idx);
uint32_t start = array_location * 4;
if (kMaxLocations <= start) {
break;
}
uint32_t end = (array_location + num_locations) * 4;
if (num_components != 0) {
start += component;
end = array_location * 4 + component + num_components;
}
auto locs = locations;
if (has_index && index == 1) locs = output_index1_locations;
for (uint32_t i = start; i < end; ++i) {
if (!locs->insert(i).second) {
return _.diag(SPV_ERROR_INVALID_DATA, entry_point)
<< (is_output ? _.VkErrorID(8722) : _.VkErrorID(8721))
<< "Entry-point has conflicting " << storage_class
<< " location assignment at location " << i / 4
<< ", component " << i % 4;
}
}
}
} else {
std::unordered_map<uint32_t, uint32_t> member_locations;
std::unordered_map<uint32_t, uint32_t> member_components;
for (auto& dec : _.id_decorations(type_id)) {
if (dec.dec_type() == spv::Decoration::Location) {
auto where = member_locations.find(dec.struct_member_index());
if (where == member_locations.end()) {
member_locations[dec.struct_member_index()] = dec.params()[0];
} else if (where->second != dec.params()[0]) {
return _.diag(SPV_ERROR_INVALID_DATA, type)
<< "Member index " << dec.struct_member_index()
<< " has conflicting location assignments";
}
} else if (dec.dec_type() == spv::Decoration::Component) {
auto where = member_components.find(dec.struct_member_index());
if (where == member_components.end()) {
member_components[dec.struct_member_index()] = dec.params()[0];
} else if (where->second != dec.params()[0]) {
return _.diag(SPV_ERROR_INVALID_DATA, type)
<< "Member index " << dec.struct_member_index()
<< " has conflicting component assignments";
}
}
}
for (uint32_t i = 1; i < type->operands().size(); ++i) {
auto where = member_locations.find(i - 1);
if (where == member_locations.end()) {
return _.diag(SPV_ERROR_INVALID_DATA, type)
<< _.VkErrorID(4919) << "Member index " << i - 1
<< " is missing a location assignment";
}
location = where->second;
auto member = _.FindDef(type->GetOperandAs<uint32_t>(i));
uint32_t num_locations = 0;
if (auto error = NumConsumedLocations(_, member, &num_locations))
return error;
uint32_t num_components = NumConsumedComponents(_, member);
component = 0;
if (member_components.count(i - 1)) {
component = member_components[i - 1];
}
uint32_t start = location * 4;
if (kMaxLocations <= start) {
continue;
}
if (member->opcode() == spv::Op::OpTypeArray && num_components >= 1 &&
num_components < 4) {
for (uint32_t l = location; l < num_locations + location; ++l) {
for (uint32_t c = component; c < component + num_components; ++c) {
uint32_t check = 4 * l + c;
if (!locations->insert(check).second) {
return _.diag(SPV_ERROR_INVALID_DATA, entry_point)
<< (is_output ? _.VkErrorID(8722) : _.VkErrorID(8721))
<< "Entry-point has conflicting " << storage_class
<< " location assignment at location " << l
<< ", component " << c;
}
}
}
} else {
uint32_t end = (location + num_locations) * 4;
if (num_components != 0) {
start += component;
end = location * 4 + component + num_components;
}
for (uint32_t l = start; l < end; ++l) {
if (!locations->insert(l).second) {
return _.diag(SPV_ERROR_INVALID_DATA, entry_point)
<< (is_output ? _.VkErrorID(8722) : _.VkErrorID(8721))
<< "Entry-point has conflicting " << storage_class
<< " location assignment at location " << l / 4
<< ", component " << l % 4;
}
}
}
}
}
return SPV_SUCCESS;
}
spv_result_t ValidateLocations(ValidationState_t& _,
const Instruction* entry_point) {
switch (entry_point->GetOperandAs<spv::ExecutionModel>(0)) {
case spv::ExecutionModel::Vertex:
case spv::ExecutionModel::TessellationControl:
case spv::ExecutionModel::TessellationEvaluation:
case spv::ExecutionModel::Geometry:
case spv::ExecutionModel::Fragment:
break;
default:
return SPV_SUCCESS;
}
std::unordered_set<uint32_t> input_locations;
std::unordered_set<uint32_t> output_locations_index0;
std::unordered_set<uint32_t> output_locations_index1;
std::unordered_set<uint32_t> seen;
for (uint32_t i = 3; i < entry_point->operands().size(); ++i) {
auto interface_id = entry_point->GetOperandAs<uint32_t>(i);
auto interface_var = _.FindDef(interface_id);
auto storage_class = interface_var->GetOperandAs<spv::StorageClass>(2);
if (storage_class != spv::StorageClass::Input &&
storage_class != spv::StorageClass::Output) {
continue;
}
if (!seen.insert(interface_id).second) {
continue;
}
auto locations = (storage_class == spv::StorageClass::Input)
? &input_locations
: &output_locations_index0;
if (auto error = GetLocationsForVariable(
_, entry_point, interface_var, locations, &output_locations_index1))
return error;
}
return SPV_SUCCESS;
}
}
spv_result_t ValidateInterfaces(ValidationState_t& _) {
bool is_spv_1_4 = _.version() >= SPV_SPIRV_VERSION_WORD(1, 4);
for (auto& inst : _.ordered_instructions()) {
if (is_interface_variable(&inst, is_spv_1_4)) {
if (auto error = check_interface_variable(_, &inst)) {
return error;
}
}
}
if (spvIsVulkanEnv(_.context()->target_env)) {
for (auto& inst : _.ordered_instructions()) {
if (inst.opcode() == spv::Op::OpEntryPoint) {
if (auto error = ValidateLocations(_, &inst)) {
return error;
}
}
if (inst.opcode() == spv::Op::OpTypeVoid) break;
}
}
return SPV_SUCCESS;
}
}
}