Updated spirv-tools.

This commit is contained in:
Бранимир Караџић
2026-02-21 19:54:05 -08:00
committed by Branimir Karadžić
parent 7147583ad7
commit b9dff93496
25 changed files with 10144 additions and 8596 deletions
+1 -1
View File
@@ -1 +1 @@
"v2026.2-dev", "SPIRV-Tools v2026.2-dev v2026.1-9-g994cf90e"
"v2026.2-dev", "SPIRV-Tools v2026.2-dev v2026.1-29-gd4f2eb99"
File diff suppressed because it is too large Load Diff
@@ -217,4 +217,5 @@ enum Extension : uint32_t {
kSPV_QCOM_image_processing2,
kSPV_QCOM_tile_shading,
kSPV_VALIDATOR_ignore_type_decl_unique,
kSPV_VALVE_mixed_float_dot_product,
};
@@ -645,6 +645,11 @@ Optimizer::PassToken CreateLoopPeelingPass();
// Works best after LICM and local multi store elimination pass.
Optimizer::PassToken CreateLoopUnswitchPass();
// Creates a pass to legalize multidimensional arrays for Vulkan.
// This pass will replace multidimensional arrays of resources with a single
// dimensional array. Combine-access-chains should be run before this pass.
Optimizer::PassToken CreateLegalizeMultidimArrayPass();
// Create global value numbering pass.
// This pass will look for instructions where the same value is computed on all
// paths leading to the instruction. Those instructions are deleted.
@@ -1155,6 +1155,7 @@ void AggressiveDCEPass::InitExtensions() {
"SPV_NV_cluster_acceleration_structure",
"SPV_NV_linear_swept_spheres",
"SPV_KHR_maximal_reconvergence",
"SPV_NV_push_constant_bank",
});
}
+233 -27
View File
@@ -2603,6 +2603,138 @@ FoldingRule RedundantLogicalNot() {
};
}
// Cases handled:
// ((a ? C0 : C1) == C2) = ((a ? (C0 == C2) : (C1 == C2))
// ((a ? C0 : C1) != C2) = ((a ? (C0 != C2) : (C1 != C2))
// ((a ? C0 : C1) < C2) = ((a ? (C0 < C2) : (C1 < C2))
// ((a ? C0 : C1) <= C2) = ((a ? (C0 <= C2) : (C1 <= C2))
// ((a ? C0 : C1) > C2) = ((a ? (C0 > C2) : (C1 > C2))
// ((a ? C0 : C1) >= C2) = ((a ? (C0 >= C2) : (C1 >= C2))
// ((a ? C0 : C1) || C2) = ((a ? (C0 || C2) : (C1 || C2))
// ((a ? C0 : C1) && C2) = ((a ? (C0 && C2) : (C1 && C2))
// ((a ? C0 : C1) + C2) = ((a ? (C0 + C2) : (C1 + C2))
// ((a ? C0 : C1) - C2) = ((a ? (C0 - C2) : (C1 - C2))
// ((a ? C0 : C1) * C2) = ((a ? (C0 * C2) : (C1 * C2))
// ((a ? C0 : C1) / C2) = ((a ? (C0 / C2) : (C1 / C2))
// ((a ? C0 : C1) >> C2) = ((a ? (C0 >> C2) : (C1 >> C2))
// ((a ? C0 : C1) << C2) = ((a ? (C0 << C2) : (C1 << C2))
// ((a ? C0 : C1) ^ C2) = ((a ? (C0 ^ C2) : (C1 ^ C2))
// ((a ? C0 : C1) | C2) = ((a ? (C0 | C2) : (C1 | C2))
// ((a ? C0 : C1) & C2) = ((a ? (C0 & C2) : (C1 & C2))
static const constexpr spv::Op MergeBinaryOpSelectOps[] = {
spv::Op::OpLogicalEqual,
spv::Op::OpLogicalNotEqual,
spv::Op::OpLogicalAnd,
spv::Op::OpLogicalOr,
spv::Op::OpIEqual,
spv::Op::OpINotEqual,
spv::Op::OpUGreaterThan,
spv::Op::OpSGreaterThan,
spv::Op::OpUGreaterThanEqual,
spv::Op::OpSGreaterThanEqual,
spv::Op::OpULessThan,
spv::Op::OpSLessThan,
spv::Op::OpULessThanEqual,
spv::Op::OpSLessThanEqual,
spv::Op::OpFOrdEqual,
spv::Op::OpFUnordEqual,
spv::Op::OpFOrdNotEqual,
spv::Op::OpFUnordNotEqual,
spv::Op::OpFOrdLessThan,
spv::Op::OpFUnordLessThan,
spv::Op::OpFOrdGreaterThan,
spv::Op::OpFUnordGreaterThan,
spv::Op::OpFOrdLessThanEqual,
spv::Op::OpFUnordLessThanEqual,
spv::Op::OpFOrdGreaterThanEqual,
spv::Op::OpFUnordGreaterThanEqual,
spv::Op::OpIAdd,
spv::Op::OpFAdd,
spv::Op::OpISub,
spv::Op::OpFSub,
spv::Op::OpIMul,
spv::Op::OpFMul,
spv::Op::OpUDiv,
spv::Op::OpSDiv,
spv::Op::OpFDiv,
spv::Op::OpVectorTimesScalar,
spv::Op::OpShiftRightLogical,
spv::Op::OpShiftRightArithmetic,
spv::Op::OpShiftLeftLogical,
spv::Op::OpBitwiseXor,
spv::Op::OpBitwiseOr,
spv::Op::OpBitwiseAnd};
FoldingRule MergeBinaryOpSelect(spv::Op opcode) {
assert(std::find(std::begin(MergeBinaryOpSelectOps),
std::end(MergeBinaryOpSelectOps),
opcode) != std::end(MergeBinaryOpSelectOps) &&
"Wrong opcode.");
return [opcode](IRContext* context, Instruction* inst,
const std::vector<const analysis::Constant*>& constants) {
const analysis::Constant* const_input = ConstInput(constants);
if (!const_input) {
return false;
}
Instruction* non_const = NonConstInput(context, constants[0], inst);
if (non_const->opcode() != spv::Op::OpSelect) {
return false;
}
std::vector<const analysis::Constant*> select_constants =
context->get_constant_mgr()->GetOperandConstants(non_const);
if (!select_constants[1] || !select_constants[2]) {
return false;
}
InstructionBuilder ir_builder(
context, inst,
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
Instruction *lhs, *rhs;
if (constants[0]) {
lhs = ir_builder.AddBinaryOp(inst->type_id(), opcode,
inst->GetSingleWordInOperand(0),
non_const->GetSingleWordInOperand(1));
rhs = ir_builder.AddBinaryOp(inst->type_id(), opcode,
inst->GetSingleWordInOperand(0),
non_const->GetSingleWordInOperand(2));
} else {
lhs = ir_builder.AddBinaryOp(inst->type_id(), opcode,
non_const->GetSingleWordInOperand(1),
inst->GetSingleWordInOperand(1));
rhs = ir_builder.AddBinaryOp(inst->type_id(), opcode,
non_const->GetSingleWordInOperand(2),
inst->GetSingleWordInOperand(1));
}
if (!lhs || !rhs) {
return false;
}
if (context->get_instruction_folder().FoldInstruction(lhs)) {
context->AnalyzeDefUse(lhs);
while (lhs->opcode() == spv::Op::OpCopyObject) {
lhs =
context->get_def_use_mgr()->GetDef(lhs->GetSingleWordInOperand(0));
}
}
if (context->get_instruction_folder().FoldInstruction(rhs)) {
context->AnalyzeDefUse(rhs);
while (rhs->opcode() == spv::Op::OpCopyObject) {
rhs =
context->get_def_use_mgr()->GetDef(rhs->GetSingleWordInOperand(0));
}
}
inst->SetOpcode(spv::Op::OpSelect);
inst->SetInOperands(
{{SPV_OPERAND_TYPE_ID, {non_const->GetSingleWordInOperand(0)}},
{SPV_OPERAND_TYPE_ID, {lhs->result_id()}},
{SPV_OPERAND_TYPE_ID, {rhs->result_id()}}});
return true;
};
}
// Fold OpLogicalNot instructions that follow a comparison,
// if the comparison is only used by that instruction.
//
@@ -2721,6 +2853,45 @@ FoldingRule FoldLogicalNotComparison() {
};
}
// (a == true) = a
// (a == false) = !a
// (a != true) = !a
// (a != false) = a
FoldingRule RedundantLogicalEqual() {
return [](IRContext* context, Instruction* inst,
const std::vector<const analysis::Constant*>& constants) {
assert(inst->opcode() == spv::Op::OpLogicalEqual ||
inst->opcode() == spv::Op::OpLogicalNotEqual);
const analysis::Constant* const_input = ConstInput(constants);
if (!const_input) {
return false;
}
analysis::DefUseManager* def_mgr = context->get_def_use_mgr();
if (inst->type_id() !=
def_mgr->GetDef(inst->GetSingleWordInOperand(0))->type_id()) {
return false;
}
std::optional<bool> uniform_const = GetBoolConstantKind(const_input);
if (!uniform_const) {
return false;
}
bool direct_copy = inst->opcode() == spv::Op::OpLogicalEqual
? uniform_const.value()
: !uniform_const.value();
inst->SetOpcode(direct_copy ? spv::Op::OpCopyObject
: spv::Op::OpLogicalNot);
inst->SetInOperands(
{{SPV_OPERAND_TYPE_ID,
{NonConstInput(context, constants[0], inst)->result_id()}}});
return true;
};
}
enum class FloatConstantKind { Unknown, Zero, One };
FloatConstantKind getFloatConstantKind(const analysis::Constant* constant) {
@@ -3312,7 +3483,9 @@ FoldingRule RedundantAndShift() {
const analysis::Type* type =
context->get_type_mgr()->GetType(inst->type_id());
uint32_t width = ElementWidth(type);
if ((width != 32) && (width != 64)) return false;
if (width != 8 && width != 16 && width != 32 && width != 64) return false;
const uint64_t width_mask =
(width == 64) ? ~0ull : ((1ull << width) - 1ull);
analysis::ConstantManager* const_mgr = context->get_constant_mgr();
const analysis::Constant* const_input1 = ConstInput(constants);
@@ -3320,37 +3493,65 @@ FoldingRule RedundantAndShift() {
Instruction* other_inst = NonConstInput(context, constants[0], inst);
spv::Op other_op = other_inst->opcode();
if (other_op == spv::Op::OpShiftLeftLogical ||
other_op == spv::Op::OpShiftRightLogical) {
std::vector<const analysis::Constant*> other_constants =
const_mgr->GetOperandConstants(other_inst);
if (other_op != spv::Op::OpShiftLeftLogical &&
other_op != spv::Op::OpShiftRightLogical) {
return false;
}
// Only valid if const is on the right
if (other_constants[0]) {
std::vector<const analysis::Constant*> other_constants =
const_mgr->GetOperandConstants(other_inst);
// Only valid if const is on the right.
if (other_constants[0]) return false;
const analysis::Constant* const_input2 = other_constants[1];
if (!const_input2) return false;
auto get_value_u64 =
[](const analysis::Constant* c) -> std::optional<uint64_t> {
if (!c) return std::nullopt;
const analysis::Integer* int_t = c->type()->AsInteger();
if (!int_t) return std::nullopt;
return c->GetZeroExtendedValue();
};
auto can_fold_component =
[&](const analysis::Constant* mask_const,
const analysis::Constant* shift_const) -> std::optional<bool> {
auto lhs = get_value_u64(mask_const);
auto rhs = get_value_u64(shift_const);
if (!lhs || !rhs) return std::nullopt;
if (*rhs >= width) return false;
uint64_t lhs_masked = *lhs & width_mask;
if (other_op == spv::Op::OpShiftRightLogical) {
return ((lhs_masked << *rhs) & width_mask) == 0;
}
return ((lhs_masked >> *rhs) & width_mask) == 0;
};
if (const analysis::Vector* mask_vec = type->AsVector()) {
const analysis::Vector* shift_vec = const_input2->type()->AsVector();
if (!shift_vec ||
shift_vec->element_count() != mask_vec->element_count()) {
return false;
}
const analysis::Constant* const_input2 = other_constants[1];
if (!const_input2) return false;
bool can_convert_to_zero = true;
ForEachIntegerConstantPair(
const_mgr, const_input1, const_input2,
[&can_convert_to_zero, other_op](auto lhs, auto rhs) {
if (other_op == spv::Op::OpShiftRightLogical) {
can_convert_to_zero = can_convert_to_zero && (lhs << rhs) == 0;
} else {
can_convert_to_zero = can_convert_to_zero && (lhs >> rhs) == 0;
}
});
if (can_convert_to_zero) {
auto zero_id = context->get_constant_mgr()->GetNullConstId(type);
inst->SetOpcode(spv::Op::OpCopyObject);
inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {zero_id}}});
return true;
const auto mask_components = const_input1->GetVectorComponents(const_mgr);
const auto shift_components =
const_input2->GetVectorComponents(const_mgr);
for (uint32_t i = 0; i != mask_vec->element_count(); ++i) {
auto result =
can_fold_component(mask_components[i], shift_components[i]);
if (!result || !*result) return false;
}
} else {
if (const_input2->type()->AsVector()) return false;
auto result = can_fold_component(const_input1, const_input2);
if (!result || !*result) return false;
}
return false;
auto zero_id = context->get_constant_mgr()->GetNullConstId(type);
inst->SetOpcode(spv::Op::OpCopyObject);
inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {zero_id}}});
return true;
};
}
@@ -3699,6 +3900,8 @@ void FoldingRules::AddFoldingRules() {
rules_[op].push_back(RedundantBinaryLhs0To0(op));
for (auto op : ReassociateCommutiveBitwiseOps)
rules_[op].push_back(ReassociateCommutiveBitwise(op));
for (auto op : MergeBinaryOpSelectOps)
rules_[op].push_back(MergeBinaryOpSelect(op));
rules_[spv::Op::OpSDiv].push_back(RedundantSUDiv());
rules_[spv::Op::OpUDiv].push_back(RedundantSUDiv());
rules_[spv::Op::OpSMod].push_back(RedundantSUMod());
@@ -3797,6 +4000,9 @@ void FoldingRules::AddFoldingRules() {
rules_[spv::Op::OpLogicalNot].push_back(RedundantLogicalNot());
rules_[spv::Op::OpLogicalNot].push_back(FoldLogicalNotComparison());
rules_[spv::Op::OpLogicalEqual].push_back(RedundantLogicalEqual());
rules_[spv::Op::OpLogicalNotEqual].push_back(RedundantLogicalEqual());
rules_[spv::Op::OpStore].push_back(StoringUndef());
rules_[spv::Op::OpVectorShuffle].push_back(VectorShuffleFeedingShuffle());
@@ -0,0 +1,275 @@
// Copyright (c) 2026 Google LLC
//
// 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 "source/opt/legalize_multidim_array_pass.h"
#include "source/opt/constants.h"
#include "source/opt/desc_sroa_util.h"
#include "source/opt/ir_builder.h"
#include "source/opt/ir_context.h"
#include "source/opt/type_manager.h"
namespace spvtools {
namespace opt {
Pass::Status LegalizeMultidimArrayPass::Process() {
std::vector<Instruction*> vars_to_legalize;
for (auto& var : context()->types_values()) {
if (var.opcode() != spv::Op::OpVariable) continue;
if (!IsMultidimArrayOfResources(&var)) continue;
if (!CanLegalize(&var)) {
context()->EmitErrorMessage("Unable to legalize multidimensional array: ",
&var);
return Status::Failure;
}
vars_to_legalize.push_back(&var);
}
if (vars_to_legalize.empty()) return Status::SuccessWithoutChange;
for (auto* var : vars_to_legalize) {
uint32_t old_ptr_type_id = var->type_id();
uint32_t new_ptr_type_id = FlattenArrayType(var);
if (new_ptr_type_id == 0) return Status::Failure;
if (!RewriteAccessChains(var, old_ptr_type_id)) return Status::Failure;
}
return Status::SuccessWithChange;
}
bool LegalizeMultidimArrayPass::IsMultidimArrayOfResources(Instruction* var) {
if (!descsroautil::IsDescriptorArray(context(), var)) return false;
uint32_t type_id = var->type_id();
Instruction* type_inst = context()->get_def_use_mgr()->GetDef(type_id);
uint32_t pointee_type_id = type_inst->GetSingleWordInOperand(1);
std::vector<uint32_t> dims;
uint32_t element_type_id = 0;
GetArrayDimensions(pointee_type_id, &dims, &element_type_id);
return dims.size() > 1;
}
void LegalizeMultidimArrayPass::GetArrayDimensions(uint32_t type_id,
std::vector<uint32_t>* dims,
uint32_t* element_type_id) {
assert(dims != nullptr && "dims cannot be null.");
dims->clear();
Instruction* type_inst = context()->get_def_use_mgr()->GetDef(type_id);
while (type_inst->opcode() == spv::Op::OpTypeArray) {
uint32_t length_id = type_inst->GetSingleWordInOperand(1);
Instruction* length_inst = context()->get_def_use_mgr()->GetDef(length_id);
// Assume OpConstant. According to the spec the length could also be an
// OpSpecConstantOp. However, DXC will not generate that type of code. The
// code to handle spec constants will be much more complicated.
assert(length_inst->opcode() == spv::Op::OpConstant);
uint32_t length = length_inst->GetSingleWordInOperand(0);
dims->push_back(length);
type_id = type_inst->GetSingleWordInOperand(0);
type_inst = context()->get_def_use_mgr()->GetDef(type_id);
}
*element_type_id = type_id;
}
uint32_t LegalizeMultidimArrayPass::FlattenArrayType(Instruction* var) {
analysis::TypeManager* type_mgr = context()->get_type_mgr();
analysis::ConstantManager* constant_mgr = context()->get_constant_mgr();
uint32_t ptr_type_id = var->type_id();
Instruction* ptr_type_inst =
context()->get_def_use_mgr()->GetDef(ptr_type_id);
uint32_t pointee_type_id = ptr_type_inst->GetSingleWordInOperand(1);
std::vector<uint32_t> dims;
uint32_t element_type_id = 0;
GetArrayDimensions(pointee_type_id, &dims, &element_type_id);
uint32_t total_elements = 1;
for (uint32_t dim : dims) {
total_elements *= dim;
}
const analysis::Constant* total_elements_const =
constant_mgr->GetIntConst(total_elements, 32, false);
Instruction* total_elements_inst =
constant_mgr->GetDefiningInstruction(total_elements_const);
uint32_t total_elements_id = total_elements_inst->result_id();
// Create new OpTypeArray.
analysis::Type* element_type = type_mgr->GetType(element_type_id);
analysis::Array::LengthInfo length_info = {
total_elements_id,
{analysis::Array::LengthInfo::kConstant, total_elements}};
analysis::Array new_array_type(element_type, length_info);
uint32_t new_array_type_id = type_mgr->GetTypeInstruction(&new_array_type);
// Create new OpTypePointer.
spv::StorageClass sc =
static_cast<spv::StorageClass>(ptr_type_inst->GetSingleWordInOperand(0));
analysis::Pointer new_ptr_type(type_mgr->GetType(new_array_type_id), sc);
uint32_t new_ptr_type_id = type_mgr->GetTypeInstruction(&new_ptr_type);
var->SetResultType(new_ptr_type_id);
context()->UpdateDefUse(var);
// Move the var after the new pointer type to avoid a def-before-use.
var->InsertAfter(get_def_use_mgr()->GetDef(new_ptr_type_id));
return new_ptr_type_id;
}
bool LegalizeMultidimArrayPass::RewriteAccessChains(Instruction* var,
uint32_t old_ptr_type_id) {
uint32_t var_id = var->result_id();
std::vector<Instruction*> users;
// Use a worklist to handle transitive uses (e.g. through OpCopyObject)
std::vector<Instruction*> worklist;
context()->get_def_use_mgr()->ForEachUser(
var_id, [&worklist](Instruction* user) { worklist.push_back(user); });
Instruction* old_ptr_type_inst =
context()->get_def_use_mgr()->GetDef(old_ptr_type_id);
uint32_t old_pointee_type_id = old_ptr_type_inst->GetSingleWordInOperand(1);
std::vector<uint32_t> dims;
uint32_t element_type_id = 0;
GetArrayDimensions(old_pointee_type_id, &dims, &element_type_id);
assert(dims.size() != 0 &&
"This variable should have been rejected earlier.");
// Calculate strides once
std::vector<uint32_t> strides(dims.size());
strides[dims.size() - 1] = 1;
for (int i = static_cast<int>(dims.size()) - 2; i >= 0; --i) {
strides[i] = strides[i + 1] * dims[i + 1];
}
// Pre-calculate uint type id
uint32_t uint_type_id = context()->get_type_mgr()->GetUIntTypeId();
if (uint_type_id == 0) return false;
while (!worklist.empty()) {
Instruction* user = worklist.back();
worklist.pop_back();
if (user->opcode() == spv::Op::OpAccessChain ||
user->opcode() == spv::Op::OpInBoundsAccessChain) {
uint32_t num_indices = user->NumInOperands() - 1;
assert(num_indices >= dims.size());
InstructionBuilder builder(context(), user, IRContext::kAnalysisDefUse);
uint32_t linearized_idx_id = 0;
for (uint32_t i = 0; i < dims.size(); ++i) {
uint32_t idx_id = user->GetSingleWordInOperand(i + 1);
uint32_t term_id = idx_id;
if (strides[i] != 1) {
const analysis::Constant* stride_const =
context()->get_constant_mgr()->GetConstant(
context()->get_type_mgr()->GetType(uint_type_id),
{strides[i]});
Instruction* stride_inst =
context()->get_constant_mgr()->GetDefiningInstruction(
stride_const);
Instruction* mul_inst = builder.AddBinaryOp(
uint_type_id, spv::Op::OpIMul, idx_id, stride_inst->result_id());
if (mul_inst == nullptr) return false;
term_id = mul_inst->result_id();
}
if (linearized_idx_id == 0) {
linearized_idx_id = term_id;
} else {
Instruction* add_inst = builder.AddBinaryOp(
uint_type_id, spv::Op::OpIAdd, linearized_idx_id, term_id);
if (add_inst == nullptr) return false;
linearized_idx_id = add_inst->result_id();
}
}
// Create new AccessChain.
Instruction::OperandList new_operands;
new_operands.push_back(user->GetInOperand(0));
new_operands.push_back({SPV_OPERAND_TYPE_ID, {linearized_idx_id}});
for (uint32_t i = static_cast<uint32_t>(dims.size()); i < num_indices;
++i) {
new_operands.push_back(user->GetInOperand(i + 1));
}
user->SetInOperands(std::move(new_operands));
context()->UpdateDefUse(user);
} else if (user->opcode() == spv::Op::OpCopyObject) {
// The type of the variable has changed so the result type of the
// OpCopyObject will change as well.
uint32_t operand_id = user->GetSingleWordInOperand(0);
Instruction* operand_inst =
context()->get_def_use_mgr()->GetDef(operand_id);
user->SetResultType(operand_inst->type_id());
context()->UpdateDefUse(user);
// Add users of this copy to worklist
context()->get_def_use_mgr()->ForEachUser(
user->result_id(),
[&worklist](Instruction* u) { worklist.push_back(u); });
}
}
return true;
}
bool LegalizeMultidimArrayPass::CheckUse(Instruction* inst,
uint32_t max_depth) {
if (inst->opcode() == spv::Op::OpAccessChain ||
inst->opcode() == spv::Op::OpInBoundsAccessChain) {
uint32_t num_indices = inst->NumInOperands() - 1;
return num_indices >= max_depth;
} else if (inst->opcode() == spv::Op::OpCopyObject) {
bool ok = true;
return !context()->get_def_use_mgr()->WhileEachUser(
inst->result_id(),
[&](Instruction* u) { return !CheckUse(u, max_depth); });
return ok;
} else if (inst->IsDecoration() || inst->opcode() == spv::Op::OpName ||
inst->opcode() == spv::Op::OpMemberName) {
// Metadata is fine.
return true;
}
// Direct use of array or partial array without AccessChain is not allowed.
return false;
}
bool LegalizeMultidimArrayPass::CanLegalize(Instruction* var) {
bool ok = true;
uint32_t ptr_type_id = var->type_id();
Instruction* ptr_type_inst =
context()->get_def_use_mgr()->GetDef(ptr_type_id);
uint32_t pointee_type_id = ptr_type_inst->GetSingleWordInOperand(1);
std::vector<uint32_t> dims;
uint32_t element_type_id = 0;
GetArrayDimensions(pointee_type_id, &dims, &element_type_id);
context()->get_def_use_mgr()->ForEachUser(
var->result_id(), [&](Instruction* u) {
if (!CheckUse(u, static_cast<uint32_t>(dims.size()))) ok = false;
});
return ok;
}
} // namespace opt
} // namespace spvtools
@@ -0,0 +1,55 @@
// Copyright (c) 2026 Google LLC
//
// 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.
#ifndef SOURCE_OPT_LEGALIZE_MULTIDIM_ARRAY_PASS_H_
#define SOURCE_OPT_LEGALIZE_MULTIDIM_ARRAY_PASS_H_
#include "source/opt/pass.h"
namespace spvtools {
namespace opt {
// Pass to legalize multidimensional arrays of resources for Vulkan.
// It transforms multidimensional arrays into single-dimensional ones.
class LegalizeMultidimArrayPass : public Pass {
public:
const char* name() const override { return "legalize-multidim-array"; }
Status Process() override;
private:
// Returns true if |var| is a multidimensional array of resources.
bool IsMultidimArrayOfResources(Instruction* var);
// Flattens the multidimensional array type of |var| and returns the new type
// id.
uint32_t FlattenArrayType(Instruction* var);
// Rewrites all access chains that use |var|.
bool RewriteAccessChains(Instruction* var, uint32_t old_ptr_type_id);
// Returns true if all uses of |var| can be legalized.
bool CanLegalize(Instruction* var);
// Recursively checks if the uses of |inst| can be legalized.
bool CheckUse(Instruction* inst, uint32_t max_depth);
// Returns the dimensions of the array type |type_id|.
void GetArrayDimensions(uint32_t type_id, std::vector<uint32_t>* dims,
uint32_t* element_type_id);
};
} // namespace opt
} // namespace spvtools
#endif // SOURCE_OPT_LEGALIZE_MULTIDIM_ARRAY_PASS_H_
@@ -469,6 +469,7 @@ void LocalAccessChainConvertPass::InitExtensions() {
"SPV_NV_cluster_acceleration_structure",
"SPV_NV_linear_swept_spheres",
"SPV_KHR_maximal_reconvergence",
"SPV_NV_push_constant_bank",
});
}
@@ -306,6 +306,7 @@ void LocalSingleBlockLoadStoreElimPass::InitExtensions() {
"SPV_NV_cluster_acceleration_structure",
"SPV_NV_linear_swept_spheres",
"SPV_KHR_maximal_reconvergence",
"SPV_NV_push_constant_bank",
});
}
@@ -158,6 +158,7 @@ void LocalSingleStoreElimPass::InitExtensionAllowList() {
"SPV_NV_cluster_acceleration_structure",
"SPV_NV_linear_swept_spheres",
"SPV_KHR_maximal_reconvergence",
"SPV_NV_push_constant_bank",
});
}
bool LocalSingleStoreElimPass::ProcessVariable(Instruction* var_inst) {
+9
View File
@@ -149,7 +149,9 @@ Optimizer& Optimizer::RegisterLegalizationPasses(bool preserve_interface) {
.RegisterPass(CreateLocalSingleStoreElimPass())
.RegisterPass(CreateAggressiveDCEPass(preserve_interface))
.RegisterPass(CreateLocalMultiStoreElimPass())
.RegisterPass(CreateCombineAccessChainsPass())
.RegisterPass(CreateAggressiveDCEPass(preserve_interface))
.RegisterPass(CreateLegalizeMultidimArrayPass())
// Propagate constants to get as many constant conditions on branches
// as possible.
.RegisterPass(CreateCCPPass())
@@ -399,6 +401,8 @@ bool Optimizer::RegisterPassFromFlag(const std::string& flag,
RegisterPass(CreateFoldSpecConstantOpAndCompositePass());
} else if (pass_name == "loop-unswitch") {
RegisterPass(CreateLoopUnswitchPass());
} else if (pass_name == "legalize-multidim-array") {
RegisterPass(CreateLegalizeMultidimArrayPass());
} else if (pass_name == "scalar-replacement") {
if (pass_args.size() == 0) {
RegisterPass(CreateScalarReplacementPass(0));
@@ -961,6 +965,11 @@ Optimizer::PassToken CreateLoopUnswitchPass() {
MakeUnique<opt::LoopUnswitchPass>());
}
Optimizer::PassToken CreateLegalizeMultidimArrayPass() {
return MakeUnique<Optimizer::PassToken::Impl>(
MakeUnique<opt::LegalizeMultidimArrayPass>());
}
Optimizer::PassToken CreateRedundancyEliminationPass() {
return MakeUnique<Optimizer::PassToken::Impl>(
MakeUnique<opt::RedundancyEliminationPass>());
+1
View File
@@ -52,6 +52,7 @@
#include "source/opt/interface_var_sroa.h"
#include "source/opt/interp_fixup_pass.h"
#include "source/opt/invocation_interlock_placement_pass.h"
#include "source/opt/legalize_multidim_array_pass.h"
#include "source/opt/licm_pass.h"
#include "source/opt/local_access_chain_convert_pass.h"
#include "source/opt/local_redundancy_elimination.h"
+2 -1
View File
@@ -390,9 +390,10 @@ spv_result_t ValidateBinaryUsingContextAndValidationState(
if (auto error = AtomicsPass(*vstate, &instruction)) return error;
if (auto error = PrimitivesPass(*vstate, &instruction)) return error;
if (auto error = BarriersPass(*vstate, &instruction)) return error;
if (auto error = DotProductPass(*vstate, &instruction)) return error;
if (auto error = GroupPass(*vstate, &instruction)) return error;
// Device-Side Enqueue
// Pipe
if (auto error = PipePass(*vstate, &instruction)) return error;
if (auto error = NonUniformPass(*vstate, &instruction)) return error;
if (auto error = LiteralsPass(*vstate, &instruction)) return error;
+6
View File
@@ -180,6 +180,9 @@ spv_result_t AtomicsPass(ValidationState_t& _, const Instruction* inst);
/// Validates correctness of barrier instructions.
spv_result_t BarriersPass(ValidationState_t& _, const Instruction* inst);
/// Validates correctness of DotProduct instructions.
spv_result_t DotProductPass(ValidationState_t& _, const Instruction* inst);
/// Validates correctness of Group (Kernel) instructions.
spv_result_t GroupPass(ValidationState_t& _, const Instruction* inst);
@@ -192,6 +195,9 @@ spv_result_t ExtensionPass(ValidationState_t& _, const Instruction* inst);
/// Validates correctness of annotation instructions.
spv_result_t AnnotationPass(ValidationState_t& _, const Instruction* inst);
/// Validates correctness of pipe instructions.
spv_result_t PipePass(ValidationState_t& _, const Instruction* inst);
/// Validates correctness of non-uniform group instructions.
spv_result_t NonUniformPass(ValidationState_t& _, const Instruction* inst);
@@ -369,6 +369,15 @@ spv_result_t ValidateDecorateId(ValidationState_t& _, const Instruction* inst) {
"scalar type.";
}
// Even if spec constant, validation layers will test when frozen
uint64_t stride_value = 0;
if (_.EvalConstantValUint64(operand_id, &stride_value)) {
if (stride_value == 0) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "ArrayStrideIdEXT contains a stride of zero.";
}
}
// Strip array and should be the descriptor type
const uint32_t element_type =
_.FindDef(target_id)->GetOperandAs<uint32_t>(1);
+72 -16
View File
@@ -123,7 +123,7 @@ typedef enum VUIDError_ {
VUIDErrorMax,
} VUIDError;
const static uint32_t NumVUIDBuiltins = 41;
const static uint32_t NumVUIDBuiltins = 42;
typedef struct {
spv::BuiltIn builtIn;
@@ -175,6 +175,7 @@ std::array<BuiltinVUIDMapping, NumVUIDBuiltins> builtinVUIDInfo = {{
{spv::BuiltIn::PrimitiveLineIndicesEXT, {7047, 7049, 7050}},
{spv::BuiltIn::PrimitiveTriangleIndicesEXT, {7053, 7055, 7056}},
{spv::BuiltIn::CullPrimitiveEXT, {7034, 7035, 7036}},
{spv::BuiltIn::HitTriangleVertexPositionsKHR, {8747, 8748, 8749}},
// clang-format on
}};
@@ -196,6 +197,7 @@ bool IsExecutionModelValidForRtBuiltIn(spv::BuiltIn builtin,
switch (builtin) {
case spv::BuiltIn::HitKindKHR:
case spv::BuiltIn::HitTNV:
case spv::BuiltIn::HitTriangleVertexPositionsKHR:
if (stage == spv::ExecutionModel::AnyHitKHR ||
stage == spv::ExecutionModel::ClosestHitKHR) {
return true;
@@ -653,6 +655,10 @@ class BuiltInsValidator {
const Decoration& decoration, const Instruction& inst,
uint32_t num_components,
const std::function<spv_result_t(const std::string& message)>& diag);
spv_result_t ValidateArrayedF32Vec(
const Decoration& decoration, const Instruction& inst,
uint32_t num_components, uint32_t array_length,
const std::function<spv_result_t(const std::string& message)>& diag);
spv_result_t ValidateF32VecHelper(
const Decoration& decoration, const Instruction& inst,
uint32_t num_components,
@@ -1155,6 +1161,38 @@ spv_result_t BuiltInsValidator::ValidateOptionalArrayedF32Vec(
underlying_type);
}
spv_result_t BuiltInsValidator::ValidateArrayedF32Vec(
const Decoration& decoration, const Instruction& inst,
uint32_t num_components, uint32_t array_length,
const std::function<spv_result_t(const std::string& message)>& diag) {
uint32_t underlying_type = 0;
if (spv_result_t error =
GetUnderlyingType(_, decoration, inst, &underlying_type)) {
return error;
}
if (_.GetIdOpcode(underlying_type) != spv::Op::OpTypeArray) {
return diag(GetDefinitionDesc(decoration, inst) + " is not an array.");
}
const uint32_t length_id = _.FindDef(underlying_type)->word(3u);
uint64_t found_length = 0;
if (!_.EvalConstantValUint64(length_id, &found_length)) {
return diag(GetDefinitionDesc(decoration, inst) +
" array has a non constant length.");
}
if (array_length != found_length) {
return diag(GetDefinitionDesc(decoration, inst) + " array length must be " +
std::to_string(array_length));
}
underlying_type = _.FindDef(underlying_type)->word(2u);
return ValidateF32VecHelper(decoration, inst, num_components, diag,
underlying_type);
}
spv_result_t BuiltInsValidator::ValidateF32Vec(
const Decoration& decoration, const Instruction& inst,
uint32_t num_components,
@@ -4633,6 +4671,25 @@ spv_result_t BuiltInsValidator::ValidateRayTracingBuiltinsAtDefinition(
return error;
}
break;
case spv::BuiltIn::HitTriangleVertexPositionsKHR:
// array[3] of f32 vec3
if (spv_result_t error = ValidateArrayedF32Vec(
decoration, inst, 3, 3,
[this, &inst,
builtin](const std::string& message) -> spv_result_t {
uint32_t vuid = GetVUIDForBuiltin(builtin, VUIDErrorType);
return _.diag(SPV_ERROR_INVALID_DATA, &inst)
<< _.VkErrorID(vuid)
<< "According to the Vulkan spec BuiltIn "
<< _.grammar().lookupOperandName(
SPV_OPERAND_TYPE_BUILT_IN, uint32_t(builtin))
<< " variable needs to be an array of 3, 32-bit float "
"3-component vectors. "
<< message;
})) {
return error;
}
break;
default:
assert(0 && "Unexpected ray tracing builtin");
break;
@@ -5001,24 +5058,23 @@ spv_result_t BuiltInsValidator::ValidateSingleBuiltInAtDefinitionVulkan(
return ValidateFullyCoveredAtDefinition(decoration, inst);
}
// Ray tracing builtins
case spv::BuiltIn::HitKindKHR: // alias spv::BuiltIn::HitKindNV
case spv::BuiltIn::HitKindKHR: // alias HitKindNV
case spv::BuiltIn::HitTNV: // NOT present in KHR
case spv::BuiltIn::InstanceId:
case spv::BuiltIn::LaunchIdKHR: // alias spv::BuiltIn::LaunchIdNV
case spv::BuiltIn::LaunchSizeKHR: // alias spv::BuiltIn::LaunchSizeNV
case spv::BuiltIn::WorldRayOriginKHR: // alias spv::BuiltIn::WorldRayOriginNV
case spv::BuiltIn::WorldRayDirectionKHR: // alias spv::BuiltIn::WorldRayDirectionNV
case spv::BuiltIn::ObjectRayOriginKHR: // alias spv::BuiltIn::ObjectRayOriginNV
case spv::BuiltIn::ObjectRayDirectionKHR: // alias
// spv::BuiltIn::ObjectRayDirectionNV
case spv::BuiltIn::RayTminKHR: // alias spv::BuiltIn::RayTminNV
case spv::BuiltIn::RayTmaxKHR: // alias spv::BuiltIn::RayTmaxNV
case spv::BuiltIn::InstanceCustomIndexKHR: // alias
// spv::BuiltIn::InstanceCustomIndexNV
case spv::BuiltIn::ObjectToWorldKHR: // alias spv::BuiltIn::ObjectToWorldNV
case spv::BuiltIn::WorldToObjectKHR: // alias spv::BuiltIn::WorldToObjectNV
case spv::BuiltIn::IncomingRayFlagsKHR: // alias spv::BuiltIn::IncomingRayFlagsNV
case spv::BuiltIn::LaunchIdKHR: // alias LaunchIdNV
case spv::BuiltIn::LaunchSizeKHR: // alias LaunchSizeNV
case spv::BuiltIn::WorldRayOriginKHR: // alias WorldRayOriginNV
case spv::BuiltIn::WorldRayDirectionKHR: // alias WorldRayDirectionNV
case spv::BuiltIn::ObjectRayOriginKHR: // alias ObjectRayOriginNV
case spv::BuiltIn::ObjectRayDirectionKHR: // alias ObjectRayDirectionNV
case spv::BuiltIn::RayTminKHR: // alias RayTminNV
case spv::BuiltIn::RayTmaxKHR: // alias RayTmaxNV
case spv::BuiltIn::InstanceCustomIndexKHR: // alias InstanceCustomIndexNV
case spv::BuiltIn::ObjectToWorldKHR: // alias ObjectToWorldNV
case spv::BuiltIn::WorldToObjectKHR: // alias WorldToObjectNV
case spv::BuiltIn::IncomingRayFlagsKHR: // alias IncomingRayFlagsNV
case spv::BuiltIn::RayGeometryIndexKHR: // NOT present in NV
case spv::BuiltIn::HitTriangleVertexPositionsKHR:
case spv::BuiltIn::CullMaskKHR: {
return ValidateRayTracingBuiltinsAtDefinition(decoration, inst);
}
+17 -1
View File
@@ -1821,6 +1821,20 @@ spv_result_t CheckFPRoundingModeForShaders(ValidationState_t& vstate,
return SPV_SUCCESS;
}
spv_result_t CheckFPRoundingModeForKernels(ValidationState_t& vstate,
const Instruction& inst) {
// Validates conversion instruction to or from a floating-point type
const auto opcode = inst.opcode();
if (opcode != spv::Op::OpConvertFToU && opcode != spv::Op::OpConvertFToS &&
opcode != spv::Op::OpConvertSToF && opcode != spv::Op::OpConvertUToF &&
opcode != spv::Op::OpFConvert) {
return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
<< "FPRoundingMode decoration can be applied only to a conversion "
"instruction to or from a floating-point type.";
}
return SPV_SUCCESS;
}
// Returns SPV_SUCCESS if validation rules are satisfied for the NonReadable or
// NonWritable
// decoration. Otherwise emits a diagnostic and returns something other than
@@ -2156,8 +2170,8 @@ spv_result_t CheckRelaxPrecisionDecoration(ValidationState_t& vstate,
// than the decorated object. Assumes each decoration on a group have been
// propagated down to the group members.
spv_result_t CheckDecorationsFromDecoration(ValidationState_t& vstate) {
// Some rules are only checked for shaders.
const bool is_shader = vstate.HasCapability(spv::Capability::Shader);
const bool is_kernel = vstate.HasCapability(spv::Capability::Kernel);
for (const auto& kv : vstate.id_decorations()) {
const uint32_t id = kv.first;
@@ -2180,6 +2194,8 @@ spv_result_t CheckDecorationsFromDecoration(ValidationState_t& vstate) {
if (is_shader)
PASS_OR_BAIL(
CheckFPRoundingModeForShaders(vstate, *inst, decoration));
if (is_kernel)
PASS_OR_BAIL(CheckFPRoundingModeForKernels(vstate, *inst));
break;
case spv::Decoration::NonReadable:
case spv::Decoration::NonWritable:
+407
View File
@@ -0,0 +1,407 @@
// Copyright (c) 2026 LunarG Inc.
//
// 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 <cstdint>
#include "source/val/instruction.h"
#include "source/val/validate.h"
#include "source/val/validate_scopes.h"
#include "source/val/validation_state.h"
namespace spvtools {
namespace val {
namespace {
spv_result_t ValidateSameSignedDot(ValidationState_t& _,
const Instruction* inst) {
const uint32_t result_id = inst->type_id();
if (!_.IsIntScalarType(result_id)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Result must be an int scalar type.";
}
const spv::Op opcode = inst->opcode();
const bool has_accumulator = opcode == spv::Op::OpSDotAccSat ||
opcode == spv::Op::OpUDotAccSat ||
opcode == spv::Op::OpSUDotAccSat;
if (has_accumulator) {
const uint32_t accumulator_type = _.GetOperandTypeId(inst, 4);
if (accumulator_type != result_id) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Result must be the same as the Accumulator type.";
}
}
if (opcode == spv::Op::OpUDot || opcode == spv::Op::OpUDotAccSat) {
if (!_.IsIntScalarTypeWithSignedness(result_id, 0)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Result must be an unsigned int scalar type.";
}
}
const uint32_t vec_1_id = _.GetOperandTypeId(inst, 2);
const uint32_t vec_2_id = _.GetOperandTypeId(inst, 3);
const bool is_vec_1_scalar = _.IsIntScalarType(vec_1_id, 32);
const bool is_vec_2_scalar = _.IsIntScalarType(vec_2_id, 32);
if (is_vec_1_scalar != is_vec_2_scalar) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "'Vector 1' and 'Vector 2' must be the same type.";
} else if (is_vec_1_scalar && is_vec_2_scalar) {
if (!_.HasCapability(spv::Capability::DotProductInput4x8BitPacked)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "DotProductInput4x8BitPacked capability is required to use "
"scalar integers.";
}
// If both are scalar, spec doesn't say Signedness needs to match
const uint32_t vec_1_width = _.GetBitWidth(vec_1_id);
const uint32_t vec_2_width = _.GetBitWidth(vec_2_id);
if (vec_1_width != 32) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected 'Vector 1' to be 32-bit when a scalar.";
} else if (vec_2_width != 32) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected 'Vector 2' to be 32-bit when a scalar.";
}
// When packed, the result can be 8-bit
const uint32_t result_width = _.GetBitWidth(result_id);
if (result_width < 8) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Result width (" << result_width
<< ") must be greater than or equal to the packed vector width of "
"8";
}
// PackedVectorFormat4x8Bit is used when the "Vector" operand are really
// scalar
const uint32_t packed_operand = has_accumulator ? 6 : 5;
const bool has_packed_vec_format =
inst->operands().size() == packed_operand;
if (!has_packed_vec_format) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "'Vector 1' and 'Vector 2' are a 32-bit int scalar, but no "
"Packed Vector "
"Format was provided.";
}
} else {
// both should be vectors
if (!_.IsVectorType(vec_1_id)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected 'Vector 1' to be an int scalar or vector.";
} else if (!_.IsVectorType(vec_2_id)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected 'Vector 2' to be an int scalar or vector.";
}
const uint32_t vec_1_length = _.GetDimension(vec_1_id);
const uint32_t vec_2_length = _.GetDimension(vec_2_id);
// If using OpTypeVectorIdEXT with a spec constant, this can be evaluated
// when spec constants are frozen
if (vec_1_length != 0 && vec_2_length != 0 &&
vec_1_length != vec_2_length) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "'Vector 1' is " << vec_1_length
<< " components but 'Vector 2' is " << vec_2_length
<< " components";
}
const uint32_t vec_1_type = _.GetComponentType(vec_1_id);
const uint32_t vec_2_type = _.GetComponentType(vec_2_id);
if (!_.IsIntScalarType(vec_1_type)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected 'Vector 1' to be a vector of integers.";
} else if (!_.IsIntScalarType(vec_2_type)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected 'Vector 2' to be a vector of integers.";
}
const uint32_t vec_1_width = _.GetBitWidth(vec_1_type);
const uint32_t vec_2_width = _.GetBitWidth(vec_2_type);
if (vec_1_width != vec_2_width) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "'Vector 1' component is " << vec_1_width
<< "-bit but 'Vector 2' component is " << vec_2_width << "-bit";
}
const uint32_t result_width = _.GetBitWidth(result_id);
if (result_width < vec_1_width) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Result width (" << result_width
<< ") must be greater than or equal to the vectors width ("
<< vec_1_width << ").";
}
if (!_.HasCapability(spv::Capability::DotProductInputAll)) {
// 4-wide 8-bit ints are special exception that has its own capability
if (vec_1_length == 4 && vec_1_width == 8) {
if (!_.HasCapability(spv::Capability::DotProductInput4x8Bit)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "DotProductInput4x8Bit or DotProductInputAll capability is "
"required to use 4-component vectors of 8-bit integers.";
}
} else {
// provide a more helpful message what is going on if we are here
// reporting this error
if (_.HasCapability(spv::Capability::DotProductInput4x8BitPacked)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "DotProductInputAll capability is required use vectors. "
"(DotProductInput4x8BitPacked capability declared allows "
"for only 32-bit int scalars)";
} else {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "DotProductInputAll capability is additionally required to "
"the DotProduct capability to use vectors. (It is possible "
"to set DotProductInput4x8BitPacked to only use 32-bit "
"scalars packed as a 4-wide 8-byte vector)";
}
}
}
if (opcode == spv::Op::OpUDot || opcode == spv::Op::OpUDotAccSat) {
const bool vec_1_unsigned =
_.IsIntScalarTypeWithSignedness(vec_1_type, 0);
const bool vec_2_unsigned =
_.IsIntScalarTypeWithSignedness(vec_2_type, 0);
if (!vec_1_unsigned) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected 'Vector 1' to be an vector of unsigned integers.";
} else if (!vec_2_unsigned) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected 'Vector 2' to be an vector of unsigned integers.";
}
} else if (opcode == spv::Op::OpSUDot || opcode == spv::Op::OpSUDotAccSat) {
const bool vec_2_unsigned =
_.IsIntScalarTypeWithSignedness(vec_2_type, 0);
if (!vec_2_unsigned) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected 'Vector 2' to be an vector of unsigned integers.";
}
}
}
return SPV_SUCCESS;
}
spv_result_t ValidateFDotMixVectors(ValidationState_t& _,
const Instruction* inst, uint32_t vec_1_id,
uint32_t vec_2_id, uint32_t length) {
if (!_.IsVectorType(vec_1_id)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected 'Vector 1' to be an vector.";
} else if (!_.IsVectorType(vec_2_id)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected 'Vector 2' to be an vector.";
}
// If using OpTypeVectorIdEXT with a spec constant,
// this can be evaluated when spec constants are frozen
const uint32_t vec_1_length = _.GetDimension(vec_1_id);
const uint32_t vec_2_length = _.GetDimension(vec_2_id);
if (vec_1_length != 0 && vec_1_length != length && vec_2_length != 0 &&
vec_2_length != length) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "'Vector 1' is " << vec_1_length
<< " components and 'Vector 2' is " << vec_2_length
<< " components, but both need to be " << length << "-components";
}
return SPV_SUCCESS;
}
spv_result_t ValidateFDot2MixAcc32(ValidationState_t& _,
const Instruction* inst) {
const uint32_t result_id = inst->type_id();
if (!_.IsFloatScalarType(result_id, 32)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Result must be a 32-bit IEEE 754 float scalar type.";
}
const uint32_t vec_1_id = _.GetOperandTypeId(inst, 2);
const uint32_t vec_2_id = _.GetOperandTypeId(inst, 3);
if (auto error = ValidateFDotMixVectors(_, inst, vec_1_id, vec_2_id, 2))
return error;
const uint32_t vec_1_type = _.GetComponentType(vec_1_id);
const uint32_t vec_2_type = _.GetComponentType(vec_2_id);
if (!_.IsFloatScalarType(vec_1_type, 16)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected 'Vector 1' to be a vector of 16-bit floats.";
} else if (!_.IsFloatScalarType(vec_2_type, 16)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected 'Vector 2' to be a vector of 16-bit floats.";
}
// Currently 16-bit floats are only BFloat or IEEE 754
const bool is_vec_1_bfloat = _.IsBfloat16ScalarType(vec_1_type);
const bool is_vec_2_bfloat = _.IsBfloat16ScalarType(vec_2_type);
if (is_vec_1_bfloat != is_vec_2_bfloat) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "'Vector 1' and 'Vector 2' must be the same float encoding.";
}
if (is_vec_1_bfloat) {
if (!_.HasCapability(spv::Capability::DotProductBFloat16AccVALVE)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "DotProductBFloat16AccVALVE capability is required to use "
"BFloat16 encoded floats.";
}
} else {
if (!_.HasCapability(spv::Capability::DotProductFloat16AccFloat32VALVE)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "DotProductFloat16AccFloat32VALVE capability is required to "
"use "
"IEEE 754 encoded 16-bit floats.";
}
}
const uint32_t accumulator_type = _.GetOperandTypeId(inst, 4);
if (accumulator_type != result_id) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Accumulator Type must be the same as the Result Type.";
}
return SPV_SUCCESS;
}
spv_result_t ValidateFDot2MixAcc16(ValidationState_t& _,
const Instruction* inst) {
const uint32_t vec_1_id = _.GetOperandTypeId(inst, 2);
const uint32_t vec_2_id = _.GetOperandTypeId(inst, 3);
if (auto error = ValidateFDotMixVectors(_, inst, vec_1_id, vec_2_id, 2))
return error;
const uint32_t vec_1_type = _.GetComponentType(vec_1_id);
const uint32_t vec_2_type = _.GetComponentType(vec_2_id);
if (!_.IsFloatScalarType(vec_1_type, 16)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected 'Vector 1' to be a vector of 16-bit floats.";
} else if (!_.IsFloatScalarType(vec_2_type, 16)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected 'Vector 2' to be a vector of 16-bit floats.";
}
// Currently 16-bit floats are only BFloat or IEEE 754
const bool is_vec_1_bfloat = _.IsBfloat16ScalarType(vec_1_type);
const bool is_vec_2_bfloat = _.IsBfloat16ScalarType(vec_2_type);
if (is_vec_1_bfloat != is_vec_2_bfloat) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "'Vector 1' and 'Vector 2' must be the same float encoding.";
}
if (is_vec_1_bfloat) {
if (!_.HasCapability(spv::Capability::DotProductBFloat16AccVALVE)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "DotProductBFloat16AccVALVE capability is required to use "
"BFloat16 encoded floats.";
}
} else {
if (!_.HasCapability(spv::Capability::DotProductFloat16AccFloat16VALVE)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "DotProductFloat16AccFloat16VALVE capability is required to "
"use "
"IEEE 754 encoded 16-bit floats.";
}
}
const uint32_t result_id = inst->type_id();
if (!_.IsFloatScalarType(result_id, 16)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Result must be a 16-bit float scalar type.";
}
const bool is_result_bfloat = _.IsBfloat16ScalarType(result_id);
if (is_result_bfloat != is_vec_1_bfloat) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Result must have the same float encoding as 'Vector 1' and "
"'Vector 2'.";
}
const uint32_t accumulator_type = _.GetOperandTypeId(inst, 4);
if (accumulator_type != result_id) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Accumulator Type must be the same as the Result Type.";
}
return SPV_SUCCESS;
}
spv_result_t ValidateFDot4MixAcc32(ValidationState_t& _,
const Instruction* inst) {
const uint32_t result_id = inst->type_id();
if (!_.IsFloatScalarType(result_id, 32)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Result must be a 32-bit IEEE 754 float scalar type.";
}
const uint32_t vec_1_id = _.GetOperandTypeId(inst, 2);
const uint32_t vec_2_id = _.GetOperandTypeId(inst, 3);
if (auto error = ValidateFDotMixVectors(_, inst, vec_1_id, vec_2_id, 4))
return error;
// Currently 8-bit floats are only Float8E4M3/Float8E5M2
const uint32_t vec_1_type = _.GetComponentType(vec_1_id);
const uint32_t vec_2_type = _.GetComponentType(vec_2_id);
if (!_.IsFloatScalarType(vec_1_type, 8)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected 'Vector 1' to be a vector of 8-bit floats.";
} else if (!_.IsFloatScalarType(vec_2_type, 8)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected 'Vector 2' to be a vector of 8-bit floats.";
}
const uint32_t accumulator_type = _.GetOperandTypeId(inst, 4);
if (accumulator_type != result_id) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Accumulator Type must be the same as the Result Type.";
}
return SPV_SUCCESS;
}
} // namespace
spv_result_t DotProductPass(ValidationState_t& _, const Instruction* inst) {
const spv::Op opcode = inst->opcode();
switch (opcode) {
case spv::Op::OpSDot:
case spv::Op::OpUDot:
case spv::Op::OpSUDot:
case spv::Op::OpSDotAccSat:
case spv::Op::OpUDotAccSat:
case spv::Op::OpSUDotAccSat:
return ValidateSameSignedDot(_, inst);
// Tried combining these to a single validate function, but they are less
// similar than appeared at first glance
case spv::Op::OpFDot2MixAcc32VALVE:
return ValidateFDot2MixAcc32(_, inst);
case spv::Op::OpFDot2MixAcc16VALVE:
return ValidateFDot2MixAcc16(_, inst);
case spv::Op::OpFDot4MixAcc32VALVE:
return ValidateFDot4MixAcc32(_, inst);
default:
break;
}
return SPV_SUCCESS;
}
} // namespace val
} // namespace spvtools
+79
View File
@@ -2327,6 +2327,80 @@ spv_result_t ValidateImageProcessingQCOM(ValidationState_t& _,
return res;
}
spv_result_t ValidateTileImageEXT(ValidationState_t& _,
const Instruction* inst) {
const spv::Op opcode = inst->opcode();
const uint32_t result_type = inst->type_id();
const char* result_type_str = GetActualResultTypeStr(opcode);
if (opcode == spv::Op::OpColorAttachmentReadEXT) {
if (!_.IsFloatScalarOrVectorType(result_type) &&
!_.IsIntScalarOrVectorType(result_type)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected " << result_type_str
<< " to be int or float scalar or vector type";
}
const uint32_t attachment_type = _.GetOperandTypeId(inst, 2);
if (_.GetIdOpcode(attachment_type) != spv::Op::OpTypeImage) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected Image to be of type OpTypeImage";
}
ImageTypeInfo info;
if (!GetImageTypeInfo(_, attachment_type, &info)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Corrupt image type definition";
}
if (_.GetIdOpcode(info.sampled_type) != spv::Op::OpTypeVoid) {
const uint32_t result_component_type = _.GetComponentType(result_type);
if (result_component_type != info.sampled_type) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected Image 'Sampled Type' to be the same as "
<< GetActualResultTypeStr(opcode) << " components";
}
}
if (info.dim != spv::Dim::TileImageDataEXT) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Image 'Dim' must be TileImageDataEXT";
}
} else if (opcode == spv::Op::OpDepthAttachmentReadEXT) {
if (!_.IsFloatScalarType(result_type, 32)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected " << result_type_str
<< " to be a 32-bit floating-point type scalar";
}
} else {
if (!_.IsIntScalarType(result_type, 32)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected " << result_type_str
<< " to be a 32-bit integer type scalar";
}
}
size_t sample_word_index =
opcode == spv::Op::OpColorAttachmentReadEXT ? 4 : 3;
if (inst->words().size() == sample_word_index + 1) {
const uint32_t sample_id = inst->word(sample_word_index);
const uint32_t sample_type = _.GetTypeId(sample_id);
if (!_.IsIntScalarType(sample_type, 32)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected Sample to be a 32-bit int scalar";
}
}
_.function(inst->function()->id())
->RegisterExecutionModelLimitation(
spv::ExecutionModel::Fragment,
spvOpcodeString(opcode) +
std::string(" requires Fragment execution model"));
return SPV_SUCCESS;
}
} // namespace
// Validates correctness of image instructions.
@@ -2462,6 +2536,11 @@ spv_result_t ImagePass(ValidationState_t& _, const Instruction* inst) {
case spv::Op::OpImageBlockMatchGatherSSDQCOM:
return ValidateImageProcessingQCOM(_, inst);
case spv::Op::OpColorAttachmentReadEXT:
case spv::Op::OpDepthAttachmentReadEXT:
case spv::Op::OpStencilAttachmentReadEXT:
return ValidateTileImageEXT(_, inst);
default:
break;
}
@@ -810,6 +810,7 @@ spv_result_t ValidateExecutionMode(ValidationState_t& _,
case spv::ExecutionMode::DepthGreater:
case spv::ExecutionMode::DepthLess:
case spv::ExecutionMode::DepthUnchanged:
case spv::ExecutionMode::StencilRefReplacingEXT:
case spv::ExecutionMode::NonCoherentColorAttachmentReadEXT:
case spv::ExecutionMode::NonCoherentDepthAttachmentReadEXT:
case spv::ExecutionMode::NonCoherentStencilAttachmentReadEXT:
@@ -819,6 +820,7 @@ spv_result_t ValidateExecutionMode(ValidationState_t& _,
case spv::ExecutionMode::SampleInterlockUnorderedEXT:
case spv::ExecutionMode::ShadingRateInterlockOrderedEXT:
case spv::ExecutionMode::ShadingRateInterlockUnorderedEXT:
case spv::ExecutionMode::PostDepthCoverage:
case spv::ExecutionMode::EarlyAndLateFragmentTestsAMD:
case spv::ExecutionMode::StencilRefUnchangedFrontAMD:
case spv::ExecutionMode::StencilRefGreaterFrontAMD:
+353
View File
@@ -0,0 +1,353 @@
// Copyright (c) 2026 LunarG Inc.
//
// 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.
// Validates correctness of Pipe SPIR-V instructions.
#include "source/val/instruction.h"
#include "source/val/validate.h"
#include "source/val/validate_scopes.h"
#include "source/val/validation_state.h"
#include "spirv/unified1/spirv.hpp11"
namespace spvtools {
namespace val {
namespace {
enum class ValidPipeType {
READ_ONLY,
WRITE_ONLY,
READ_OR_WRITE, // still excludes Read AND Write
};
spv_result_t ValidatePipeType(ValidationState_t& _, const Instruction* inst,
uint32_t operand, ValidPipeType valid_pt) {
const Instruction* pipe_type = _.FindDef(_.GetOperandTypeId(inst, operand));
if (pipe_type->opcode() != spv::Op::OpTypePipe) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Pipe must be a type of OpTypePipe.";
}
const auto access_qualifier =
pipe_type->GetOperandAs<spv::AccessQualifier>(1);
if (valid_pt == ValidPipeType::READ_ONLY) {
if (access_qualifier != spv::AccessQualifier::ReadOnly) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Pipe must have a OpTypePipe with ReadOnly access qualifier.";
}
} else if (valid_pt == ValidPipeType::WRITE_ONLY) {
if (access_qualifier != spv::AccessQualifier::WriteOnly) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Pipe must have a OpTypePipe with WriteOnly access qualifier.";
}
} else if (valid_pt == ValidPipeType::READ_OR_WRITE) {
if (access_qualifier != spv::AccessQualifier::ReadOnly &&
access_qualifier != spv::AccessQualifier::WriteOnly) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Pipe must have a OpTypePipe with ReadOnly or WriteOnly access "
"qualifier.";
}
}
return SPV_SUCCESS;
}
spv_result_t ValidatePacketSizeAlign(ValidationState_t& _,
const Instruction* inst,
uint32_t size_operand,
uint32_t alignment_operand) {
const uint32_t packet_size_id = _.GetOperandTypeId(inst, size_operand);
if (!_.IsIntScalarType(packet_size_id, 32)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Packet Size must be a 32-bit scalar integer.";
}
const uint32_t packet_alignment_id =
_.GetOperandTypeId(inst, alignment_operand);
if (!_.IsIntScalarType(packet_alignment_id, 32)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Packet Alignment must be a 32-bit scalar integer.";
}
return SPV_SUCCESS;
}
spv_result_t ValidateReadWritePipe(ValidationState_t& _,
const Instruction* inst) {
const uint32_t result_type = inst->type_id();
if (!_.IsIntScalarType(result_type, 32)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Result Type must be a 32-bit int scalar.";
}
if (inst->opcode() == spv::Op::OpReadPipe) {
if (auto error = ValidatePipeType(_, inst, 2, ValidPipeType::READ_ONLY))
return error;
} else if (inst->opcode() == spv::Op::OpWritePipe) {
if (auto error = ValidatePipeType(_, inst, 2, ValidPipeType::WRITE_ONLY))
return error;
}
const Instruction* pointer_type = _.FindDef(_.GetOperandTypeId(inst, 3));
if (pointer_type->opcode() != spv::Op::OpTypePointer) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Pointer must be a type of OpTypePointer.";
}
if (pointer_type->GetOperandAs<spv::StorageClass>(1) !=
spv::StorageClass::Generic) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Pointer must be a OpTypePointer with a Generic storage class.";
}
if (auto error = ValidatePacketSizeAlign(_, inst, 4, 5)) return error;
return SPV_SUCCESS;
}
spv_result_t ValidateReservedReadWritePipe(ValidationState_t& _,
const Instruction* inst) {
const uint32_t result_type = inst->type_id();
if (!_.IsIntScalarType(result_type, 32)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Result Type must be a 32-bit int scalar.";
}
if (inst->opcode() == spv::Op::OpReservedReadPipe) {
if (auto error = ValidatePipeType(_, inst, 2, ValidPipeType::READ_ONLY))
return error;
} else if (inst->opcode() == spv::Op::OpReservedWritePipe) {
if (auto error = ValidatePipeType(_, inst, 2, ValidPipeType::WRITE_ONLY))
return error;
}
const Instruction* reserve_id = _.FindDef(_.GetOperandTypeId(inst, 3));
if (reserve_id->opcode() != spv::Op::OpTypeReserveId) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Reserve Id type must be OpTypeReserveId.";
}
const uint32_t index_id = _.GetOperandTypeId(inst, 4);
if (!_.IsIntScalarType(index_id, 32)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Index must be a 32-bit scalar integer.";
}
const Instruction* pointer_type = _.FindDef(_.GetOperandTypeId(inst, 5));
if (pointer_type->opcode() != spv::Op::OpTypePointer) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Pointer must be a type of OpTypePointer.";
}
if (pointer_type->GetOperandAs<spv::StorageClass>(1) !=
spv::StorageClass::Generic) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Pointer must be a OpTypePointer with a Generic storage class.";
}
if (auto error = ValidatePacketSizeAlign(_, inst, 6, 7)) return error;
return SPV_SUCCESS;
}
spv_result_t ValidateReservePackets(ValidationState_t& _,
const Instruction* inst) {
const Instruction* result_type = _.FindDef(inst->type_id());
if (result_type->opcode() != spv::Op::OpTypeReserveId) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Result Type must be OpTypeReserveId.";
}
if (inst->opcode() == spv::Op::OpReserveReadPipePackets) {
if (auto error = ValidatePipeType(_, inst, 2, ValidPipeType::READ_ONLY))
return error;
} else if (inst->opcode() == spv::Op::OpReserveWritePipePackets) {
if (auto error = ValidatePipeType(_, inst, 2, ValidPipeType::WRITE_ONLY))
return error;
}
const uint32_t num_packets_id = _.GetOperandTypeId(inst, 3);
if (!_.IsIntScalarType(num_packets_id, 32)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Num Packets must be a 32-bit scalar integer.";
}
if (auto error = ValidatePacketSizeAlign(_, inst, 4, 5)) return error;
return SPV_SUCCESS;
}
spv_result_t ValidateGroupReservePackets(ValidationState_t& _,
const Instruction* inst) {
const Instruction* result_type = _.FindDef(inst->type_id());
if (result_type->opcode() != spv::Op::OpTypeReserveId) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Result Type must be OpTypeReserveId.";
}
if (inst->opcode() == spv::Op::OpGroupReserveReadPipePackets) {
if (auto error = ValidatePipeType(_, inst, 3, ValidPipeType::READ_ONLY))
return error;
} else if (inst->opcode() == spv::Op::OpGroupReserveWritePipePackets) {
if (auto error = ValidatePipeType(_, inst, 3, ValidPipeType::WRITE_ONLY))
return error;
}
const uint32_t num_packets_id = _.GetOperandTypeId(inst, 4);
if (!_.IsIntScalarType(num_packets_id, 32)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Num Packets must be a 32-bit scalar integer.";
}
if (auto error = ValidatePacketSizeAlign(_, inst, 5, 6)) return error;
return SPV_SUCCESS;
}
spv_result_t ValidateCommitPipe(ValidationState_t& _, const Instruction* inst) {
if (inst->opcode() == spv::Op::OpCommitReadPipe) {
if (auto error = ValidatePipeType(_, inst, 0, ValidPipeType::READ_ONLY))
return error;
} else if (inst->opcode() == spv::Op::OpCommitWritePipe) {
if (auto error = ValidatePipeType(_, inst, 0, ValidPipeType::WRITE_ONLY))
return error;
}
const Instruction* reserve_id = _.FindDef(_.GetOperandTypeId(inst, 1));
if (reserve_id->opcode() != spv::Op::OpTypeReserveId) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Reserve Id type must be OpTypeReserveId.";
}
if (auto error = ValidatePacketSizeAlign(_, inst, 2, 3)) return error;
return SPV_SUCCESS;
}
spv_result_t ValidateGroupCommitPipe(ValidationState_t& _,
const Instruction* inst) {
if (inst->opcode() == spv::Op::OpGroupCommitReadPipe) {
if (auto error = ValidatePipeType(_, inst, 1, ValidPipeType::READ_ONLY))
return error;
} else if (inst->opcode() == spv::Op::OpGroupCommitWritePipe) {
if (auto error = ValidatePipeType(_, inst, 1, ValidPipeType::WRITE_ONLY))
return error;
}
const Instruction* reserve_id = _.FindDef(_.GetOperandTypeId(inst, 2));
if (reserve_id->opcode() != spv::Op::OpTypeReserveId) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Reserve Id type must be OpTypeReserveId.";
}
if (auto error = ValidatePacketSizeAlign(_, inst, 3, 4)) return error;
return SPV_SUCCESS;
}
spv_result_t ValidatePipePacketsQuery(ValidationState_t& _,
const Instruction* inst) {
const uint32_t result_type = inst->type_id();
if (!_.IsIntScalarType(result_type, 32)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Result Type must be a 32-bit int scalar.";
}
if (auto error = ValidatePipeType(_, inst, 2, ValidPipeType::READ_OR_WRITE))
return error;
if (auto error = ValidatePacketSizeAlign(_, inst, 3, 4)) return error;
return SPV_SUCCESS;
}
spv_result_t ValidateIsValidReserveId(ValidationState_t& _,
const Instruction* inst) {
if (!_.IsBoolScalarType(inst->type_id())) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Result Type must be a bool scalar";
}
const Instruction* reserve_id = _.FindDef(_.GetOperandTypeId(inst, 2));
if (reserve_id->opcode() != spv::Op::OpTypeReserveId) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Reserve Id type must be OpTypeReserveId.";
}
return SPV_SUCCESS;
}
spv_result_t ValidateCreatePipeFromPipeStorage(ValidationState_t& _,
const Instruction* inst) {
const Instruction* result_type = _.FindDef(inst->type_id());
if (result_type->opcode() != spv::Op::OpTypePipe) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Result Type must be OpTypePipe.";
}
// TODO - Need to check OpTypeStorage is from OpConstantPipeStorage
return SPV_SUCCESS;
}
spv_result_t ValidateConstantPipeStorage(ValidationState_t& _,
const Instruction* inst) {
const Instruction* result_type = _.FindDef(inst->type_id());
if (result_type->opcode() != spv::Op::OpTypePipeStorage) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Result Type must be OpTypePipeStorage.";
}
// TODO - Should we validate the literal values?
// https://gitlab.khronos.org/spirv/SPIR-V/-/issues/914
return SPV_SUCCESS;
}
} // namespace
// Validates correctness of pipe instructions.
spv_result_t PipePass(ValidationState_t& _, const Instruction* inst) {
switch (inst->opcode()) {
case spv::Op::OpReadPipe:
case spv::Op::OpWritePipe:
return ValidateReadWritePipe(_, inst);
case spv::Op::OpReservedReadPipe:
case spv::Op::OpReservedWritePipe:
return ValidateReservedReadWritePipe(_, inst);
case spv::Op::OpReserveReadPipePackets:
case spv::Op::OpReserveWritePipePackets:
return ValidateReservePackets(_, inst);
case spv::Op::OpGroupReserveReadPipePackets:
case spv::Op::OpGroupReserveWritePipePackets:
return ValidateGroupReservePackets(_, inst);
case spv::Op::OpCommitReadPipe:
case spv::Op::OpCommitWritePipe:
return ValidateCommitPipe(_, inst);
case spv::Op::OpGroupCommitReadPipe:
case spv::Op::OpGroupCommitWritePipe:
return ValidateGroupCommitPipe(_, inst);
case spv::Op::OpGetNumPipePackets:
case spv::Op::OpGetMaxPipePackets:
return ValidatePipePacketsQuery(_, inst);
case spv::Op::OpIsValidReserveId:
return ValidateIsValidReserveId(_, inst);
case spv::Op::OpCreatePipeFromPipeStorage:
return ValidateCreatePipeFromPipeStorage(_, inst);
case spv::Op::OpConstantPipeStorage:
return ValidateConstantPipeStorage(_, inst);
default:
break;
}
return SPV_SUCCESS;
}
} // namespace val
} // namespace spvtools
+20
View File
@@ -354,6 +354,26 @@ spv_result_t RayQueryPass(ValidationState_t& _, const Instruction* inst) {
break;
}
// SPV_KHR_ray_tracing_position_fetch
case spv::Op::OpRayQueryGetIntersectionTriangleVertexPositionsKHR: {
if (auto error = ValidateRayQueryPointer(_, inst, 2)) return error;
if (auto error = ValidateIntersectionId(_, inst, 3)) return error;
auto result_id = _.FindDef(result_type);
if ((result_id->opcode() != spv::Op::OpTypeArray) ||
(GetArrayLength(_, result_id) != 3) ||
!_.IsFloatVectorType(_.GetComponentType(result_type)) ||
_.GetDimension(_.GetComponentType(result_type)) != 3 ||
_.GetBitWidth(_.GetComponentType(result_type)) != 32) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected 3 element array of 32-bit 3 component float point "
"vector as Result Type: "
<< spvOpcodeString(opcode);
}
break;
}
default:
break;
}
+161 -156
View File
@@ -22,146 +22,143 @@
namespace spvtools {
namespace val {
spv_result_t RayTracingPass(ValidationState_t& _, const Instruction* inst) {
const spv::Op opcode = inst->opcode();
spv_result_t ValidateTraceRay(ValidationState_t& _, const Instruction* inst) {
_.function(inst->function()->id())
->RegisterExecutionModelLimitation(
[](spv::ExecutionModel model, std::string* message) {
if (model != spv::ExecutionModel::RayGenerationKHR &&
model != spv::ExecutionModel::ClosestHitKHR &&
model != spv::ExecutionModel::MissKHR) {
if (message) {
*message =
"OpTraceRayKHR requires RayGenerationKHR, "
"ClosestHitKHR and MissKHR execution models";
}
return false;
}
return true;
});
if (_.GetIdOpcode(_.GetOperandTypeId(inst, 0)) !=
spv::Op::OpTypeAccelerationStructureKHR) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected Acceleration Structure to be of type "
"OpTypeAccelerationStructureKHR";
}
const uint32_t ray_flags = _.GetOperandTypeId(inst, 1);
if (!_.IsIntScalarType(ray_flags, 32)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Ray Flags must be a 32-bit int scalar";
}
const uint32_t cull_mask = _.GetOperandTypeId(inst, 2);
if (!_.IsIntScalarType(cull_mask, 32)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Cull Mask must be a 32-bit int scalar";
}
const uint32_t sbt_offset = _.GetOperandTypeId(inst, 3);
if (!_.IsIntScalarType(sbt_offset, 32)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "SBT Offset must be a 32-bit int scalar";
}
const uint32_t sbt_stride = _.GetOperandTypeId(inst, 4);
if (!_.IsIntScalarType(sbt_stride, 32)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "SBT Stride must be a 32-bit int scalar";
}
const uint32_t miss_index = _.GetOperandTypeId(inst, 5);
if (!_.IsIntScalarType(miss_index, 32)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Miss Index must be a 32-bit int scalar";
}
const uint32_t ray_origin = _.GetOperandTypeId(inst, 6);
if (!_.IsFloatVectorType(ray_origin) || _.GetDimension(ray_origin) != 3 ||
_.GetBitWidth(ray_origin) != 32) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Ray Origin must be a 32-bit float 3-component vector";
}
const uint32_t ray_tmin = _.GetOperandTypeId(inst, 7);
if (!_.IsFloatScalarType(ray_tmin, 32)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Ray TMin must be a 32-bit float scalar";
}
const uint32_t ray_direction = _.GetOperandTypeId(inst, 8);
if (!_.IsFloatVectorType(ray_direction) ||
_.GetDimension(ray_direction) != 3 ||
_.GetBitWidth(ray_direction) != 32) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Ray Direction must be a 32-bit float 3-component vector";
}
const uint32_t ray_tmax = _.GetOperandTypeId(inst, 9);
if (!_.IsFloatScalarType(ray_tmax, 32)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Ray TMax must be a 32-bit float scalar";
}
const Instruction* payload = _.FindDef(inst->GetOperandAs<uint32_t>(10));
if (payload->opcode() != spv::Op::OpVariable) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Payload must be the result of a OpVariable";
} else if (payload->GetOperandAs<spv::StorageClass>(2) !=
spv::StorageClass::RayPayloadKHR &&
payload->GetOperandAs<spv::StorageClass>(2) !=
spv::StorageClass::IncomingRayPayloadKHR) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Payload must have storage class RayPayloadKHR or "
"IncomingRayPayloadKHR";
}
return SPV_SUCCESS;
}
spv_result_t ValidateReportIntersection(ValidationState_t& _,
const Instruction* inst) {
_.function(inst->function()->id())
->RegisterExecutionModelLimitation(
[](spv::ExecutionModel model, std::string* message) {
if (model != spv::ExecutionModel::IntersectionKHR) {
if (message) {
*message =
"OpReportIntersectionKHR requires IntersectionKHR "
"execution model";
}
return false;
}
return true;
});
const uint32_t result_type = inst->type_id();
if (!_.IsBoolScalarType(result_type)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "expected Result Type to be bool scalar type";
}
switch (opcode) {
case spv::Op::OpTraceRayKHR: {
_.function(inst->function()->id())
->RegisterExecutionModelLimitation(
[](spv::ExecutionModel model, std::string* message) {
if (model != spv::ExecutionModel::RayGenerationKHR &&
model != spv::ExecutionModel::ClosestHitKHR &&
model != spv::ExecutionModel::MissKHR) {
if (message) {
*message =
"OpTraceRayKHR requires RayGenerationKHR, "
"ClosestHitKHR and MissKHR execution models";
}
return false;
}
return true;
});
const uint32_t hit = _.GetOperandTypeId(inst, 2);
if (!_.IsFloatScalarType(hit, 32)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Hit must be a 32-bit int scalar";
}
if (_.GetIdOpcode(_.GetOperandTypeId(inst, 0)) !=
spv::Op::OpTypeAccelerationStructureKHR) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected Acceleration Structure to be of type "
"OpTypeAccelerationStructureKHR";
}
const uint32_t hit_kind = _.GetOperandTypeId(inst, 3);
if (!_.IsUnsignedIntScalarType(hit_kind) || _.GetBitWidth(hit_kind) != 32) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Hit Kind must be a 32-bit unsigned int scalar";
}
return SPV_SUCCESS;
}
const uint32_t ray_flags = _.GetOperandTypeId(inst, 1);
if (!_.IsIntScalarType(ray_flags, 32)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Ray Flags must be a 32-bit int scalar";
}
const uint32_t cull_mask = _.GetOperandTypeId(inst, 2);
if (!_.IsIntScalarType(cull_mask, 32)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Cull Mask must be a 32-bit int scalar";
}
const uint32_t sbt_offset = _.GetOperandTypeId(inst, 3);
if (!_.IsIntScalarType(sbt_offset, 32)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "SBT Offset must be a 32-bit int scalar";
}
const uint32_t sbt_stride = _.GetOperandTypeId(inst, 4);
if (!_.IsIntScalarType(sbt_stride, 32)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "SBT Stride must be a 32-bit int scalar";
}
const uint32_t miss_index = _.GetOperandTypeId(inst, 5);
if (!_.IsIntScalarType(miss_index, 32)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Miss Index must be a 32-bit int scalar";
}
const uint32_t ray_origin = _.GetOperandTypeId(inst, 6);
if (!_.IsFloatVectorType(ray_origin) || _.GetDimension(ray_origin) != 3 ||
_.GetBitWidth(ray_origin) != 32) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Ray Origin must be a 32-bit float 3-component vector";
}
const uint32_t ray_tmin = _.GetOperandTypeId(inst, 7);
if (!_.IsFloatScalarType(ray_tmin, 32)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Ray TMin must be a 32-bit float scalar";
}
const uint32_t ray_direction = _.GetOperandTypeId(inst, 8);
if (!_.IsFloatVectorType(ray_direction) ||
_.GetDimension(ray_direction) != 3 ||
_.GetBitWidth(ray_direction) != 32) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Ray Direction must be a 32-bit float 3-component vector";
}
const uint32_t ray_tmax = _.GetOperandTypeId(inst, 9);
if (!_.IsFloatScalarType(ray_tmax, 32)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Ray TMax must be a 32-bit float scalar";
}
const Instruction* payload = _.FindDef(inst->GetOperandAs<uint32_t>(10));
if (payload->opcode() != spv::Op::OpVariable) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Payload must be the result of a OpVariable";
} else if (payload->GetOperandAs<spv::StorageClass>(2) !=
spv::StorageClass::RayPayloadKHR &&
payload->GetOperandAs<spv::StorageClass>(2) !=
spv::StorageClass::IncomingRayPayloadKHR) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Payload must have storage class RayPayloadKHR or "
"IncomingRayPayloadKHR";
}
break;
}
case spv::Op::OpReportIntersectionKHR: {
_.function(inst->function()->id())
->RegisterExecutionModelLimitation(
[](spv::ExecutionModel model, std::string* message) {
if (model != spv::ExecutionModel::IntersectionKHR) {
if (message) {
*message =
"OpReportIntersectionKHR requires IntersectionKHR "
"execution model";
}
return false;
}
return true;
});
if (!_.IsBoolScalarType(result_type)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "expected Result Type to be bool scalar type";
}
const uint32_t hit = _.GetOperandTypeId(inst, 2);
if (!_.IsFloatScalarType(hit, 32)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Hit must be a 32-bit int scalar";
}
const uint32_t hit_kind = _.GetOperandTypeId(inst, 3);
if (!_.IsUnsignedIntScalarType(hit_kind) ||
_.GetBitWidth(hit_kind) != 32) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Hit Kind must be a 32-bit unsigned int scalar";
}
break;
}
case spv::Op::OpExecuteCallableKHR: {
_.function(inst->function()->id())
->RegisterExecutionModelLimitation([](spv::ExecutionModel model,
std::string* message) {
spv_result_t ValidateExecuteCallable(ValidationState_t& _,
const Instruction* inst) {
_.function(inst->function()->id())
->RegisterExecutionModelLimitation(
[](spv::ExecutionModel model, std::string* message) {
if (model != spv::ExecutionModel::RayGenerationKHR &&
model != spv::ExecutionModel::ClosestHitKHR &&
model != spv::ExecutionModel::MissKHR &&
@@ -176,29 +173,37 @@ spv_result_t RayTracingPass(ValidationState_t& _, const Instruction* inst) {
return true;
});
const uint32_t sbt_index = _.GetOperandTypeId(inst, 0);
if (!_.IsUnsignedIntScalarType(sbt_index) ||
_.GetBitWidth(sbt_index) != 32) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "SBT Index must be a 32-bit unsigned int scalar";
}
const uint32_t sbt_index = _.GetOperandTypeId(inst, 0);
if (!_.IsUnsignedIntScalarType(sbt_index) || _.GetBitWidth(sbt_index) != 32) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "SBT Index must be a 32-bit unsigned int scalar";
}
const auto callable_data = _.FindDef(inst->GetOperandAs<uint32_t>(1));
if (callable_data->opcode() != spv::Op::OpVariable) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Callable Data must be the result of a OpVariable";
} else if (callable_data->GetOperandAs<spv::StorageClass>(2) !=
spv::StorageClass::CallableDataKHR &&
callable_data->GetOperandAs<spv::StorageClass>(2) !=
spv::StorageClass::IncomingCallableDataKHR) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Callable Data must have storage class CallableDataKHR or "
"IncomingCallableDataKHR";
}
const auto callable_data = _.FindDef(inst->GetOperandAs<uint32_t>(1));
if (callable_data->opcode() != spv::Op::OpVariable) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Callable Data must be the result of a OpVariable";
} else if (callable_data->GetOperandAs<spv::StorageClass>(2) !=
spv::StorageClass::CallableDataKHR &&
callable_data->GetOperandAs<spv::StorageClass>(2) !=
spv::StorageClass::IncomingCallableDataKHR) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Callable Data must have storage class CallableDataKHR or "
"IncomingCallableDataKHR";
}
return SPV_SUCCESS;
}
break;
}
spv_result_t RayTracingPass(ValidationState_t& _, const Instruction* inst) {
const spv::Op opcode = inst->opcode();
switch (opcode) {
case spv::Op::OpTraceRayKHR:
return ValidateTraceRay(_, inst);
case spv::Op::OpReportIntersectionKHR:
return ValidateReportIntersection(_, inst);
case spv::Op::OpExecuteCallableKHR:
return ValidateExecuteCallable(_, inst);
default:
break;
}
+9 -2
View File
@@ -1801,13 +1801,14 @@ bool ValidationState_t::EvalConstantValInt64(uint32_t id, int64_t* val) const {
return true;
}
// <is_int32, is_const_int32, value>
std::tuple<bool, bool, uint32_t> ValidationState_t::EvalInt32IfConst(
uint32_t id) const {
const Instruction* const inst = FindDef(id);
assert(inst);
const uint32_t type = inst->type_id();
if (type == 0 || !IsIntScalarType(type) || GetBitWidth(type) != 32) {
if (type == 0 || !IsIntScalarType(type, 32)) {
return std::make_tuple(false, false, 0);
}
@@ -2837,10 +2838,16 @@ std::string ValidationState_t::VkErrorID(uint32_t id,
return VUID_WRAP(VUID-StandaloneSpirv-OpEntryPoint-08721);
case 8722:
return VUID_WRAP(VUID-StandaloneSpirv-OpEntryPoint-08722);
case 8747:
return VUID_WRAP(VUID-HitTriangleVertexPositionsKHR-HitTriangleVertexPositionsKHR-08747);
case 8748:
return VUID_WRAP(VUID-HitTriangleVertexPositionsKHR-HitTriangleVertexPositionsKHR-08748);
case 8749:
return VUID_WRAP(VUID-HitTriangleVertexPositionsKHR-HitTriangleVertexPositionsKHR-08749);
case 8973:
return VUID_WRAP(VUID-StandaloneSpirv-Pointer-08973);
case 9557:
return VUID_WRAP(VUID-StandaloneSpirv-Input-09557);
return VUID_WRAP(VUID-StandaloneSpirv-Input-09557);
case 9638:
return VUID_WRAP(VUID-StandaloneSpirv-OpTypeImage-09638);
case 9658: