mirror of
https://github.com/bkaradzic/bgfx.git
synced 2026-10-29 12:15:30 +00:00
Updated spirv-tools.
This commit is contained in:
committed by
Branimir Karadžić
parent
7147583ad7
commit
b9dff93496
@@ -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"
|
||||
|
||||
+8423
-8392
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
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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:
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
@@ -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
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user