Updated spirv-tools.

This commit is contained in:
Бранимир Караџић
2025-11-20 09:34:27 -08:00
committed by Branimir Karadžić
parent c02ebda27d
commit a3df232816
40 changed files with 12013 additions and 8949 deletions

View File

@@ -1 +1 @@
"v2025.4", "SPIRV-Tools v2025.4 v2025.4-28-g8b4ee452"
"v2025.5", "SPIRV-Tools v2025.5 v2025.4-64-gd2a11ec9"

File diff suppressed because it is too large Load Diff

View File

@@ -30,6 +30,26 @@ enum class PrintingClass : uint32_t {
};
enum Extension : uint32_t {
kSPV_ALTERA_arbitrary_precision_fixed_point,
kSPV_ALTERA_arbitrary_precision_floating_point,
kSPV_ALTERA_arbitrary_precision_integers,
kSPV_ALTERA_blocking_pipes,
kSPV_ALTERA_fpga_argument_interfaces,
kSPV_ALTERA_fpga_buffer_location,
kSPV_ALTERA_fpga_cluster_attributes,
kSPV_ALTERA_fpga_dsp_control,
kSPV_ALTERA_fpga_invocation_pipelining_attributes,
kSPV_ALTERA_fpga_latency_control,
kSPV_ALTERA_fpga_loop_controls,
kSPV_ALTERA_fpga_memory_accesses,
kSPV_ALTERA_fpga_memory_attributes,
kSPV_ALTERA_fpga_reg,
kSPV_ALTERA_global_variable_fpga_decorations,
kSPV_ALTERA_io_pipes,
kSPV_ALTERA_loop_fuse,
kSPV_ALTERA_runtime_aligned,
kSPV_ALTERA_task_sequence,
kSPV_ALTERA_usm_storage_classes,
kSPV_AMDX_shader_enqueue,
kSPV_AMD_gcn_shader,
kSPV_AMD_gpu_shader_half_float,
@@ -59,10 +79,12 @@ enum Extension : uint32_t {
kSPV_EXT_physical_storage_buffer,
kSPV_EXT_relaxed_printf_string_address_space,
kSPV_EXT_replicated_composites,
kSPV_EXT_shader_64bit_indexing,
kSPV_EXT_shader_atomic_float16_add,
kSPV_EXT_shader_atomic_float_add,
kSPV_EXT_shader_atomic_float_min_max,
kSPV_EXT_shader_image_int64,
kSPV_EXT_shader_invocation_reorder,
kSPV_EXT_shader_stencil_export,
kSPV_EXT_shader_tile_image,
kSPV_EXT_shader_viewport_index_layer,

View File

@@ -267,6 +267,7 @@ int32_t spvOpcodeGeneratesType(spv::Op op) {
// spv::Op::OpTypeAccelerationStructureNV
case spv::Op::OpTypeRayQueryKHR:
case spv::Op::OpTypeHitObjectNV:
case spv::Op::OpTypeHitObjectEXT:
case spv::Op::OpTypeUntypedPointerKHR:
case spv::Op::OpTypeNodePayloadArrayAMDX:
case spv::Op::OpTypeTensorLayoutNV:

View File

@@ -305,7 +305,7 @@ Pass::Status AggressiveDCEPass::ProcessDebugInformation(
NonSemanticShaderDebugInfo100DebugValue) {
uint32_t id = inst->GetSingleWordInOperand(kDebugValueValue);
auto def = get_def_use_mgr()->GetDef(id);
if (!live_insts_.Set(def->unique_id())) {
if (!IsLive(def)) {
AddToWorklist(inst);
uint32_t undef_id = Type2Undef(def->type_id());
if (undef_id == 0) {
@@ -1123,6 +1123,7 @@ void AggressiveDCEPass::InitExtensions() {
"SPV_NV_shader_invocation_reorder",
"SPV_NV_cluster_acceleration_structure",
"SPV_NV_linear_swept_spheres",
"SPV_KHR_maximal_reconvergence",
});
}

View File

@@ -76,6 +76,7 @@ bool ReplaceTrinaryMinMax(IRContext* ctx, Instruction* inst,
Instruction* temp = ir_builder.AddNaryExtendedInstruction(
inst->type_id(), glsl405_ext_inst_id, opcode, {op1, op2});
if (temp == nullptr) return false;
Instruction::OperandList new_operands;
new_operands.push_back({SPV_OPERAND_TYPE_ID, {glsl405_ext_inst_id}});
@@ -114,9 +115,12 @@ bool ReplaceTrinaryMid(IRContext* ctx, Instruction* inst,
Instruction* min = ir_builder.AddNaryExtendedInstruction(
inst->type_id(), glsl405_ext_inst_id, static_cast<uint32_t>(min_opcode),
{op2, op3});
if (min == nullptr) return false;
Instruction* max = ir_builder.AddNaryExtendedInstruction(
inst->type_id(), glsl405_ext_inst_id, static_cast<uint32_t>(max_opcode),
{op2, op3});
if (max == nullptr) return false;
Instruction::OperandList new_operands;
new_operands.push_back({SPV_OPERAND_TYPE_ID, {glsl405_ext_inst_id}});
@@ -226,16 +230,20 @@ bool ReplaceSwizzleInvocations(IRContext* ctx, Instruction* inst,
// Get the subgroup invocation id.
uint32_t var_id = ctx->GetBuiltinInputVarId(
uint32_t(spv::BuiltIn::SubgroupLocalInvocationId));
assert(var_id != 0 && "Could not get SubgroupLocalInvocationId variable.");
if (var_id == 0) return false;
Instruction* var_inst = ctx->get_def_use_mgr()->GetDef(var_id);
if (var_inst == nullptr) return false;
Instruction* var_ptr_type =
ctx->get_def_use_mgr()->GetDef(var_inst->type_id());
if (var_ptr_type == nullptr) return false;
uint32_t uint_type_id = var_ptr_type->GetSingleWordInOperand(1);
if (uint_type_id == 0) return false;
// TODO(1841): Handle id overflow.
Instruction* id = ir_builder.AddLoad(uint_type_id, var_id);
if (id == nullptr) return false;
uint32_t quad_mask = ir_builder.GetUintConstantId(3);
if (quad_mask == 0) return false;
// This gives the offset in the group of 4 of this invocation.
Instruction* quad_idx = ir_builder.AddBinaryOp(
@@ -262,26 +270,41 @@ bool ReplaceSwizzleInvocations(IRContext* ctx, Instruction* inst,
// Do the group operations
uint32_t uint_max_id = ir_builder.GetUintConstantId(0xFFFFFFFF);
if (uint_max_id == 0) return false;
uint32_t subgroup_scope =
ir_builder.GetUintConstantId(uint32_t(spv::Scope::Subgroup));
if (subgroup_scope == 0) return false;
const auto* vec_type = type_mgr->GetUIntVectorType(4);
if (vec_type == nullptr) return false;
const auto* ballot_value_const = const_mgr->GetConstant(
type_mgr->GetUIntVectorType(4),
{uint_max_id, uint_max_id, uint_max_id, uint_max_id});
vec_type, {uint_max_id, uint_max_id, uint_max_id, uint_max_id});
if (ballot_value_const == nullptr) return false;
Instruction* ballot_value =
const_mgr->GetDefiningInstruction(ballot_value_const);
// TODO(1841): Handle id overflow.
if (ballot_value == nullptr) return false;
uint32_t bool_type_id = type_mgr->GetBoolTypeId();
if (bool_type_id == 0) return false;
Instruction* is_active = ir_builder.AddNaryOp(
type_mgr->GetBoolTypeId(), spv::Op::OpGroupNonUniformBallotBitExtract,
bool_type_id, spv::Op::OpGroupNonUniformBallotBitExtract,
{subgroup_scope, ballot_value->result_id(), target_inv->result_id()});
// TODO(1841): Handle id overflow.
if (is_active == nullptr) return false;
Instruction* shuffle =
ir_builder.AddNaryOp(inst->type_id(), spv::Op::OpGroupNonUniformShuffle,
{subgroup_scope, data_id, target_inv->result_id()});
if (shuffle == nullptr) return false;
// Create the null constant to use in the select.
const auto* null = const_mgr->GetConstant(type_mgr->GetType(inst->type_id()),
std::vector<uint32_t>());
const auto* result_type = type_mgr->GetType(inst->type_id());
if (result_type == nullptr) return false;
const auto* null =
const_mgr->GetConstant(result_type, std::vector<uint32_t>());
if (null == nullptr) {
return false;
}
Instruction* null_inst = const_mgr->GetDefiningInstruction(null);
if (null_inst == nullptr) {
return false;
}
// Build the select.
inst->SetOpcode(spv::Op::OpSelect);
@@ -346,30 +369,38 @@ bool ReplaceSwizzleInvocationsMasked(
uint32_t data_id = inst->GetSingleWordInOperand(2);
Instruction* mask_inst = def_use_mgr->GetDef(inst->GetSingleWordInOperand(3));
if (mask_inst == nullptr) return false;
assert(mask_inst->opcode() == spv::Op::OpConstantComposite &&
"The mask is suppose to be a vector constant.");
assert(mask_inst->NumInOperands() == 3 &&
"The mask is suppose to have 3 components.");
uint32_t uint_x = mask_inst->GetSingleWordInOperand(0);
if (uint_x == 0) return false;
uint32_t uint_y = mask_inst->GetSingleWordInOperand(1);
if (uint_y == 0) return false;
uint32_t uint_z = mask_inst->GetSingleWordInOperand(2);
if (uint_z == 0) return false;
// Get the subgroup invocation id.
uint32_t var_id = ctx->GetBuiltinInputVarId(
uint32_t(spv::BuiltIn::SubgroupLocalInvocationId));
if (var_id == 0) return false;
ctx->AddExtension("SPV_KHR_shader_ballot");
assert(var_id != 0 && "Could not get SubgroupLocalInvocationId variable.");
Instruction* var_inst = ctx->get_def_use_mgr()->GetDef(var_id);
if (var_inst == nullptr) return false;
Instruction* var_ptr_type =
ctx->get_def_use_mgr()->GetDef(var_inst->type_id());
if (var_ptr_type == nullptr) return false;
uint32_t uint_type_id = var_ptr_type->GetSingleWordInOperand(1);
if (uint_type_id == 0) return false;
// TODO(1841): Handle id overflow.
Instruction* id = ir_builder.AddLoad(uint_type_id, var_id);
if (id == nullptr) return false;
// Do the bitwise operations.
uint32_t mask_extended = ir_builder.GetUintConstantId(0xFFFFFFE0);
if (mask_extended == 0) return false;
Instruction* and_mask = ir_builder.AddBinaryOp(
uint_type_id, spv::Op::OpBitwiseOr, uint_x, mask_extended);
if (and_mask == nullptr) return false;
@@ -386,26 +417,37 @@ bool ReplaceSwizzleInvocationsMasked(
// Do the group operations
uint32_t uint_max_id = ir_builder.GetUintConstantId(0xFFFFFFFF);
if (uint_max_id == 0) return false;
uint32_t subgroup_scope =
ir_builder.GetUintConstantId(uint32_t(spv::Scope::Subgroup));
if (subgroup_scope == 0) return false;
const auto* vec_type = type_mgr->GetUIntVectorType(4);
if (vec_type == nullptr) return false;
const auto* ballot_value_const = const_mgr->GetConstant(
type_mgr->GetUIntVectorType(4),
{uint_max_id, uint_max_id, uint_max_id, uint_max_id});
vec_type, {uint_max_id, uint_max_id, uint_max_id, uint_max_id});
if (ballot_value_const == nullptr) return false;
Instruction* ballot_value =
const_mgr->GetDefiningInstruction(ballot_value_const);
// TODO(1841): Handle id overflow.
if (ballot_value == nullptr) return false;
uint32_t bool_type_id = type_mgr->GetBoolTypeId();
if (bool_type_id == 0) return false;
Instruction* is_active = ir_builder.AddNaryOp(
type_mgr->GetBoolTypeId(), spv::Op::OpGroupNonUniformBallotBitExtract,
bool_type_id, spv::Op::OpGroupNonUniformBallotBitExtract,
{subgroup_scope, ballot_value->result_id(), target_inv->result_id()});
// TODO(1841): Handle id overflow.
if (is_active == nullptr) return false;
Instruction* shuffle =
ir_builder.AddNaryOp(inst->type_id(), spv::Op::OpGroupNonUniformShuffle,
{subgroup_scope, data_id, target_inv->result_id()});
if (shuffle == nullptr) return false;
// Create the null constant to use in the select.
const auto* null = const_mgr->GetConstant(type_mgr->GetType(inst->type_id()),
std::vector<uint32_t>());
const auto* result_type = type_mgr->GetType(inst->type_id());
if (result_type == nullptr) return false;
const auto* null =
const_mgr->GetConstant(result_type, std::vector<uint32_t>());
if (null == nullptr) return false;
Instruction* null_inst = const_mgr->GetDefiningInstruction(null);
if (null_inst == nullptr) return false;
// Build the select.
inst->SetOpcode(spv::Op::OpSelect);
@@ -439,21 +481,24 @@ bool ReplaceWriteInvocation(IRContext* ctx, Instruction* inst,
const std::vector<const analysis::Constant*>&) {
uint32_t var_id = ctx->GetBuiltinInputVarId(
uint32_t(spv::BuiltIn::SubgroupLocalInvocationId));
ctx->AddCapability(spv::Capability::SubgroupBallotKHR);
ctx->AddExtension("SPV_KHR_shader_ballot");
assert(var_id != 0 && "Could not get SubgroupLocalInvocationId variable.");
if (var_id == 0) return false;
Instruction* var_inst = ctx->get_def_use_mgr()->GetDef(var_id);
if (var_inst == nullptr) return false;
Instruction* var_ptr_type =
ctx->get_def_use_mgr()->GetDef(var_inst->type_id());
if (var_ptr_type == nullptr) return false;
ctx->AddCapability(spv::Capability::SubgroupBallotKHR);
ctx->AddExtension("SPV_KHR_shader_ballot");
InstructionBuilder ir_builder(
ctx, inst,
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
// TODO(1841): Handle id overflow.
Instruction* t =
ir_builder.AddLoad(var_ptr_type->GetSingleWordInOperand(1), var_id);
if (t == nullptr) return false;
analysis::Bool bool_type;
uint32_t bool_type_id = ctx->get_type_mgr()->GetTypeInstruction(&bool_type);
if (bool_type_id == 0) return false;
Instruction* cmp =
ir_builder.AddBinaryOp(bool_type_id, spv::Op::OpIEqual, t->result_id(),
inst->GetSingleWordInOperand(4));
@@ -500,7 +545,8 @@ bool ReplaceMbcnt(IRContext* context, Instruction* inst,
uint32_t var_id =
context->GetBuiltinInputVarId(uint32_t(spv::BuiltIn::SubgroupLtMask));
assert(var_id != 0 && "Could not get SubgroupLtMask variable.");
if (var_id == 0) return false;
context->AddCapability(spv::Capability::GroupNonUniformBallot);
Instruction* var_inst = def_use_mgr->GetDef(var_id);
Instruction* var_ptr_type = def_use_mgr->GetDef(var_inst->type_id());
@@ -513,6 +559,7 @@ bool ReplaceMbcnt(IRContext* context, Instruction* inst,
analysis::Vector temp_type(GetUIntType(context), 2);
const analysis::Type* shuffle_type =
context->get_type_mgr()->GetRegisteredType(&temp_type);
if (shuffle_type == nullptr) return false;
uint32_t shuffle_type_id = type_mgr->GetTypeInstruction(shuffle_type);
uint32_t mask_id = inst->GetSingleWordInOperand(2);
@@ -526,10 +573,13 @@ bool ReplaceMbcnt(IRContext* context, Instruction* inst,
context, inst,
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
Instruction* load = ir_builder.AddLoad(var_type->result_id(), var_id);
if (load == nullptr) return false;
Instruction* shuffle = ir_builder.AddVectorShuffle(
shuffle_type_id, load->result_id(), load->result_id(), {0, 1});
if (shuffle == nullptr) return false;
Instruction* bitcast = ir_builder.AddUnaryOp(
mask_inst->type_id(), spv::Op::OpBitcast, shuffle->result_id());
if (bitcast == nullptr) return false;
Instruction* t =
ir_builder.AddBinaryOp(mask_inst->type_id(), spv::Op::OpBitwiseAnd,
bitcast->result_id(), mask_id);
@@ -588,9 +638,13 @@ bool ReplaceCubeFaceCoord(IRContext* ctx, Instruction* inst,
analysis::ConstantManager* const_mgr = ctx->get_constant_mgr();
uint32_t float_type_id = type_mgr->GetFloatTypeId();
if (float_type_id == 0) return false;
const analysis::Type* v2_float_type = type_mgr->GetFloatVectorType(2);
if (v2_float_type == nullptr) return false;
uint32_t v2_float_type_id = type_mgr->GetId(v2_float_type);
if (v2_float_type_id == 0) return false;
uint32_t bool_id = type_mgr->GetBoolTypeId();
if (bool_id == 0) return false;
InstructionBuilder ir_builder(
ctx, inst,
@@ -604,23 +658,29 @@ bool ReplaceCubeFaceCoord(IRContext* ctx, Instruction* inst,
glsl405_ext_inst_id =
ctx->get_feature_mgr()->GetExtInstImportId_GLSLstd450();
}
if (glsl405_ext_inst_id == 0) return false;
// Get the constants that will be used.
uint32_t f0_const_id = const_mgr->GetFloatConstId(0.0);
if (f0_const_id == 0) return false;
uint32_t f2_const_id = const_mgr->GetFloatConstId(2.0);
if (f2_const_id == 0) return false;
uint32_t f0_5_const_id = const_mgr->GetFloatConstId(0.5);
if (f0_5_const_id == 0) return false;
const analysis::Constant* vec_const =
const_mgr->GetConstant(v2_float_type, {f0_5_const_id, f0_5_const_id});
uint32_t vec_const_id =
const_mgr->GetDefiningInstruction(vec_const)->result_id();
if (vec_const == nullptr) return false;
Instruction* vec_const_inst = const_mgr->GetDefiningInstruction(vec_const);
if (vec_const_inst == nullptr) return false;
uint32_t vec_const_id = vec_const_inst->result_id();
// Extract the input values.
// TODO(1841): Handle id overflow.
Instruction* x = ir_builder.AddCompositeExtract(float_type_id, input_id, {0});
// TODO(1841): Handle id overflow.
if (x == nullptr) return false;
Instruction* y = ir_builder.AddCompositeExtract(float_type_id, input_id, {1});
// TODO(1-841): Handle id overflow.
if (y == nullptr) return false;
Instruction* z = ir_builder.AddCompositeExtract(float_type_id, input_id, {2});
if (z == nullptr) return false;
// Negate the input values.
Instruction* nx =
@@ -629,14 +689,20 @@ bool ReplaceCubeFaceCoord(IRContext* ctx, Instruction* inst,
ir_builder.AddUnaryOp(float_type_id, spv::Op::OpFNegate, y->result_id());
Instruction* nz =
ir_builder.AddUnaryOp(float_type_id, spv::Op::OpFNegate, z->result_id());
if (nx == nullptr) return false;
if (ny == nullptr) return false;
if (nz == nullptr) return false;
// Get the abolsute values of the inputs.
Instruction* ax = ir_builder.AddNaryExtendedInstruction(
float_type_id, glsl405_ext_inst_id, GLSLstd450FAbs, {x->result_id()});
if (ax == nullptr) return false;
Instruction* ay = ir_builder.AddNaryExtendedInstruction(
float_type_id, glsl405_ext_inst_id, GLSLstd450FAbs, {y->result_id()});
if (ay == nullptr) return false;
Instruction* az = ir_builder.AddNaryExtendedInstruction(
float_type_id, glsl405_ext_inst_id, GLSLstd450FAbs, {z->result_id()});
if (az == nullptr) return false;
// Find which values are negative. Used in later computations.
Instruction* is_z_neg = ir_builder.AddBinaryOp(
@@ -653,9 +719,11 @@ bool ReplaceCubeFaceCoord(IRContext* ctx, Instruction* inst,
Instruction* amax_x_y = ir_builder.AddNaryExtendedInstruction(
float_type_id, glsl405_ext_inst_id, GLSLstd450FMax,
{ax->result_id(), ay->result_id()});
if (amax_x_y == nullptr) return false;
Instruction* amax = ir_builder.AddNaryExtendedInstruction(
float_type_id, glsl405_ext_inst_id, GLSLstd450FMax,
{az->result_id(), amax_x_y->result_id()});
if (amax == nullptr) return false;
Instruction* cubema = ir_builder.AddBinaryOp(float_type_id, spv::Op::OpFMul,
f2_const_id, amax->result_id());
if (cubema == nullptr) return false;
@@ -667,6 +735,7 @@ bool ReplaceCubeFaceCoord(IRContext* ctx, Instruction* inst,
if (is_z_max == nullptr) return false;
Instruction* not_is_z_max = ir_builder.AddUnaryOp(
bool_id, spv::Op::OpLogicalNot, is_z_max->result_id());
if (not_is_z_max == nullptr) return false;
Instruction* y_gr_x =
ir_builder.AddBinaryOp(bool_id, spv::Op::OpFOrdGreaterThanEqual,
ay->result_id(), ax->result_id());
@@ -677,35 +746,37 @@ bool ReplaceCubeFaceCoord(IRContext* ctx, Instruction* inst,
if (is_y_max == nullptr) return false;
// Select the correct value for cubesc.
// TODO(1841): Handle id overflow.
Instruction* cubesc_case_1 = ir_builder.AddSelect(
float_type_id, is_z_neg->result_id(), nx->result_id(), x->result_id());
// TODO(1841): Handle id overflow.
if (cubesc_case_1 == nullptr) return false;
Instruction* cubesc_case_2 = ir_builder.AddSelect(
float_type_id, is_x_neg->result_id(), z->result_id(), nz->result_id());
if (cubesc_case_2 == nullptr) return false;
Instruction* sel =
// TODO(1841): Handle id overflow.
ir_builder.AddSelect(float_type_id, is_y_max->result_id(), x->result_id(),
cubesc_case_2->result_id());
if (sel == nullptr) return false;
Instruction* cubesc =
// TODO(1841): Handle id overflow.
ir_builder.AddSelect(float_type_id, is_z_max->result_id(),
cubesc_case_1->result_id(), sel->result_id());
if (cubesc == nullptr) return false;
// Select the correct value for cubetc.
// TODO(1841): Handle id overflow.
Instruction* cubetc_case_1 = ir_builder.AddSelect(
float_type_id, is_y_neg->result_id(), nz->result_id(), z->result_id());
if (cubetc_case_1 == nullptr) return false;
Instruction* cubetc =
// TODO(1841): Handle id overflow.
ir_builder.AddSelect(float_type_id, is_y_max->result_id(),
cubetc_case_1->result_id(), ny->result_id());
if (cubetc == nullptr) return false;
// Do the division
Instruction* cube = ir_builder.AddCompositeConstruct(
v2_float_type_id, {cubesc->result_id(), cubetc->result_id()});
if (cube == nullptr) return false;
Instruction* denom = ir_builder.AddCompositeConstruct(
v2_float_type_id, {cubema->result_id(), cubema->result_id()});
if (denom == nullptr) return false;
Instruction* div = ir_builder.AddBinaryOp(
v2_float_type_id, spv::Op::OpFDiv, cube->result_id(), denom->result_id());
if (div == nullptr) return false;
@@ -756,7 +827,9 @@ bool ReplaceCubeFaceIndex(IRContext* ctx, Instruction* inst,
analysis::ConstantManager* const_mgr = ctx->get_constant_mgr();
uint32_t float_type_id = type_mgr->GetFloatTypeId();
if (float_type_id == 0) return false;
uint32_t bool_id = type_mgr->GetBoolTypeId();
if (bool_id == 0) return false;
InstructionBuilder ir_builder(
ctx, inst,
@@ -773,27 +846,37 @@ bool ReplaceCubeFaceIndex(IRContext* ctx, Instruction* inst,
// Get the constants that will be used.
uint32_t f0_const_id = const_mgr->GetFloatConstId(0.0);
if (f0_const_id == 0) return false;
uint32_t f1_const_id = const_mgr->GetFloatConstId(1.0);
if (f1_const_id == 0) return false;
uint32_t f2_const_id = const_mgr->GetFloatConstId(2.0);
if (f2_const_id == 0) return false;
uint32_t f3_const_id = const_mgr->GetFloatConstId(3.0);
if (f3_const_id == 0) return false;
uint32_t f4_const_id = const_mgr->GetFloatConstId(4.0);
if (f4_const_id == 0) return false;
uint32_t f5_const_id = const_mgr->GetFloatConstId(5.0);
if (f5_const_id == 0) return false;
// Extract the input values.
// TODO(1841): Handle id overflow.
Instruction* x = ir_builder.AddCompositeExtract(float_type_id, input_id, {0});
// TODO(1841): Handle id overflow.
if (x == nullptr) return false;
Instruction* y = ir_builder.AddCompositeExtract(float_type_id, input_id, {1});
if (y == nullptr) return false;
// TODO(1-841): Handle id overflow.
Instruction* z = ir_builder.AddCompositeExtract(float_type_id, input_id, {2});
if (z == nullptr) return false;
// Get the absolute values of the inputs.
Instruction* ax = ir_builder.AddNaryExtendedInstruction(
float_type_id, glsl405_ext_inst_id, GLSLstd450FAbs, {x->result_id()});
if (ax == nullptr) return false;
Instruction* ay = ir_builder.AddNaryExtendedInstruction(
float_type_id, glsl405_ext_inst_id, GLSLstd450FAbs, {y->result_id()});
if (ay == nullptr) return false;
Instruction* az = ir_builder.AddNaryExtendedInstruction(
float_type_id, glsl405_ext_inst_id, GLSLstd450FAbs, {z->result_id()});
if (az == nullptr) return false;
// Find which values are negative. Used in later computations.
Instruction* is_z_neg = ir_builder.AddBinaryOp(
@@ -810,6 +893,7 @@ bool ReplaceCubeFaceIndex(IRContext* ctx, Instruction* inst,
Instruction* amax_x_y = ir_builder.AddNaryExtendedInstruction(
float_type_id, glsl405_ext_inst_id, GLSLstd450FMax,
{ax->result_id(), ay->result_id()});
if (amax_x_y == nullptr) return false;
Instruction* is_z_max =
ir_builder.AddBinaryOp(bool_id, spv::Op::OpFOrdGreaterThanEqual,
az->result_id(), amax_x_y->result_id());
@@ -820,21 +904,21 @@ bool ReplaceCubeFaceIndex(IRContext* ctx, Instruction* inst,
if (y_gr_x == nullptr) return false;
// Get the value for each case.
// TODO(1841): Handle id overflow.
Instruction* case_z = ir_builder.AddSelect(
float_type_id, is_z_neg->result_id(), f5_const_id, f4_const_id);
// TODO(1841): Handle id overflow.
if (case_z == nullptr) return false;
Instruction* case_y = ir_builder.AddSelect(
float_type_id, is_y_neg->result_id(), f3_const_id, f2_const_id);
// TODO(1841): Handle id overflow.
if (case_y == nullptr) return false;
Instruction* case_x = ir_builder.AddSelect(
float_type_id, is_x_neg->result_id(), f1_const_id, f0_const_id);
if (case_x == nullptr) return false;
// Select the correct case.
Instruction* sel =
// TODO(1841): Handle id overflow.
ir_builder.AddSelect(float_type_id, y_gr_x->result_id(),
case_y->result_id(), case_x->result_id());
if (sel == nullptr) return false;
// Get the final result by adding 0.5 to |div|.
inst->SetOpcode(spv::Op::OpSelect);
@@ -974,11 +1058,18 @@ Pass::Status AmdExtensionToKhrPass::Process() {
std::unique_ptr<AmdExtFoldingRules>(new AmdExtFoldingRules(context())),
MakeUnique<AmdExtConstFoldingRules>(context()));
for (Function& func : *get_module()) {
func.ForEachInst([&changed, &folder](Instruction* inst) {
bool failed =
!func.WhileEachInst([&changed, &folder, this](Instruction* inst) {
if (folder.FoldInstruction(inst)) {
changed = true;
return true;
} else if (context()->id_overflow()) {
return false;
}
return true;
});
if (failed) return Status::Failure;
}
// Now that instruction that require the extensions have been removed, we can

View File

@@ -462,7 +462,9 @@ const Constant* ConstantManager::GetNumericVectorConstantWithWords(
uint32_t ConstantManager::GetFloatConstId(float val) {
const Constant* c = GetFloatConst(val);
return GetDefiningInstruction(c)->result_id();
Instruction* inst = GetDefiningInstruction(c);
if (inst == nullptr) return 0;
return inst->result_id();
}
const Constant* ConstantManager::GetFloatConst(float val) {
@@ -474,7 +476,9 @@ const Constant* ConstantManager::GetFloatConst(float val) {
uint32_t ConstantManager::GetDoubleConstId(double val) {
const Constant* c = GetDoubleConst(val);
return GetDefiningInstruction(c)->result_id();
Instruction* inst = GetDefiningInstruction(c);
if (inst == nullptr) return 0;
return inst->result_id();
}
const Constant* ConstantManager::GetDoubleConst(double val) {

View File

@@ -194,7 +194,7 @@ std::vector<uint32_t> GetWordsFromNumericScalarOrVectorConstant(
uint32_t compacted_word = 0;
for (int32_t i = static_cast<int32_t>(words.size()) - 1; i >= 0; --i) {
compacted_word <<= 8;
compacted_word |= words[i];
compacted_word |= (words[i] & 0xFF);
}
return {compacted_word};
} else if (ElementWidth(c->type()) == 16) {
@@ -205,7 +205,7 @@ std::vector<uint32_t> GetWordsFromNumericScalarOrVectorConstant(
for (uint32_t i = 0; i < words.size(); i += 2) {
uint32_t word1 = words[i];
uint32_t word2 = (i + 1 < words.size()) ? words[i + 1] : 0;
uint32_t compacted_word = (word2 << 16) | word1;
uint32_t compacted_word = (word2 << 16) | (word1 & 0xFFFF);
compacted_words.push_back(compacted_word);
}
return compacted_words;
@@ -2819,6 +2819,230 @@ FoldingRule RedundantSUMod() {
};
}
// Utility function for applying |callback| to |input1| and |input2|.
// If they are vectors it applies element wise.
// The constants |input1| and |input2| must be integers or a vector of integers.
template <typename Callback>
void ForEachIntegerConstantPair(analysis::ConstantManager* const_mgr,
const analysis::Constant* input1,
const analysis::Constant* input2,
Callback&& callback) {
assert(input1 && input2);
auto Dispatch = [&callback](const analysis::Constant* lhs,
const analysis::Constant* rhs) {
assert(lhs->type()->AsInteger());
const analysis::Integer* type = lhs->type()->AsInteger();
uint32_t width = type->AsInteger()->width();
assert(width == 32 || width == 64);
if (width == 32) {
callback(lhs->GetU32(), rhs->GetU32());
} else {
callback(lhs->GetU64(), rhs->GetU64());
}
};
const analysis::Type* type = input1->type();
if (const analysis::Vector* vector_type = type->AsVector()) {
const analysis::Type* ele_type = vector_type->element_type();
assert(ele_type->AsInteger());
for (uint32_t i = 0; i != vector_type->element_count(); ++i) {
const analysis::Constant* input1_comp = nullptr;
if (const analysis::VectorConstant* input1_vector =
input1->AsVectorConstant()) {
input1_comp = input1_vector->GetComponents()[i];
} else {
assert(input1->AsNullConstant());
input1_comp = const_mgr->GetConstant(ele_type, {});
}
const analysis::Constant* input2_comp = nullptr;
if (const analysis::VectorConstant* input2_vector =
input2->AsVectorConstant()) {
input2_comp = input2_vector->GetComponents()[i];
} else {
assert(input2->AsNullConstant());
input2_comp = const_mgr->GetConstant(ele_type, {});
}
assert(ele_type->AsInteger());
Dispatch(input1_comp, input2_comp);
}
} else {
assert(type->AsInteger());
Dispatch(input1, input2);
}
}
// Folds redundant xor and or ops that are part of an and.
// Cases handled:
// 0b1110 & (a | 0b0001) = a & 0b1110
// 0b1110 & (a ^ 0b0001) = a & 0b1110
// 0b0110 & (a | 0b1110) = 0b0110
FoldingRule RedundantAndOrXor() {
return [](IRContext* context, Instruction* inst,
const std::vector<const analysis::Constant*>& constants) {
assert(inst->opcode() == spv::Op::OpBitwiseAnd && "Wrong opcode.");
const analysis::Type* type =
context->get_type_mgr()->GetType(inst->type_id());
uint32_t width = ElementWidth(type);
if ((width != 32) && (width != 64)) return false;
analysis::ConstantManager* const_mgr = context->get_constant_mgr();
const analysis::Constant* const_input1 = ConstInput(constants);
if (!const_input1) return false;
Instruction* other_inst = NonConstInput(context, constants[0], inst);
if (other_inst->opcode() == spv::Op::OpBitwiseOr ||
other_inst->opcode() == spv::Op::OpBitwiseXor) {
std::vector<const analysis::Constant*> other_constants =
const_mgr->GetOperandConstants(other_inst);
const analysis::Constant* const_input2 = ConstInput(other_constants);
if (!const_input2) return false;
bool can_convert_to_const = other_inst->opcode() == spv::Op::OpBitwiseOr;
bool can_remove_inner = true;
ForEachIntegerConstantPair(
const_mgr, const_input1, const_input2,
[&can_remove_inner, &can_convert_to_const](auto lhs, auto rhs) {
// Only convert to const if 'and' is a subset of 'or'
can_convert_to_const = can_convert_to_const && ((lhs & rhs) == lhs);
// Only remove 'xor'/'or' if no bits intersect with 'and'
can_remove_inner = can_remove_inner && ((lhs & rhs) == 0);
});
if (can_convert_to_const) {
Instruction* const_inst =
const_mgr->GetDefiningInstruction(const_input1);
inst->SetOpcode(spv::Op::OpCopyObject);
inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {const_inst->result_id()}}});
return true;
} else if (can_remove_inner) {
Instruction* non_const_input =
NonConstInput(context, other_constants[0], other_inst);
Instruction* const_inst =
const_mgr->GetDefiningInstruction(const_input1);
inst->SetInOperands(
{{SPV_OPERAND_TYPE_ID, {non_const_input->result_id()}},
{SPV_OPERAND_TYPE_ID, {const_inst->result_id()}}});
return true;
}
}
return false;
};
}
// Folds redundant add and sub ops that are part of an and.
// Cases handled:
// 1 & (b + 2) = b & 1
// 1 & (b - 2) = b & 1
FoldingRule RedundantAndAddSub() {
return [](IRContext* context, Instruction* inst,
const std::vector<const analysis::Constant*>& constants) {
assert(inst->opcode() == spv::Op::OpBitwiseAnd && "Wrong opcode.");
const analysis::Type* type =
context->get_type_mgr()->GetType(inst->type_id());
uint32_t width = ElementWidth(type);
if ((width != 32) && (width != 64)) return false;
analysis::ConstantManager* const_mgr = context->get_constant_mgr();
const analysis::Constant* const_input1 = ConstInput(constants);
if (!const_input1) return false;
Instruction* other_inst = NonConstInput(context, constants[0], inst);
if (other_inst->opcode() != spv::Op::OpIAdd &&
other_inst->opcode() != spv::Op::OpISub) {
return false;
}
std::vector<const analysis::Constant*> other_constants =
const_mgr->GetOperandConstants(other_inst);
const analysis::Constant* const_input2 = ConstInput(other_constants);
if (!const_input2) return false;
// Only valid for subtraction if const is on the right
if ((other_inst->opcode() == spv::Op::OpISub) && other_constants[0]) {
return false;
}
bool can_remove_inner = true;
ForEachIntegerConstantPair(const_mgr, const_input1, const_input2,
[&can_remove_inner](auto and_op, auto add_op) {
if (can_remove_inner) {
// Only valid if no bits from the +/- could
// affect bits from the & operation.
can_remove_inner =
utils::LSB(add_op) > and_op;
}
});
if (can_remove_inner) {
Instruction* non_const_input =
NonConstInput(context, other_constants[0], other_inst);
Instruction* const_inst = const_mgr->GetDefiningInstruction(const_input1);
inst->SetInOperands(
{{SPV_OPERAND_TYPE_ID, {non_const_input->result_id()}},
{SPV_OPERAND_TYPE_ID, {const_inst->result_id()}}});
return true;
}
return false;
};
}
// Folds redundant shift ops that are part of an and.
// Cases handled:
// 1 & (b << 1) = 0
// 0x80000000 & (b >> 1) = 0
FoldingRule RedundantAndShift() {
return [](IRContext* context, Instruction* inst,
const std::vector<const analysis::Constant*>& constants) {
assert(inst->opcode() == spv::Op::OpBitwiseAnd && "Wrong opcode.");
const analysis::Type* type =
context->get_type_mgr()->GetType(inst->type_id());
uint32_t width = ElementWidth(type);
if ((width != 32) && (width != 64)) return false;
analysis::ConstantManager* const_mgr = context->get_constant_mgr();
const analysis::Constant* const_input1 = ConstInput(constants);
if (!const_input1) return false;
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);
// 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;
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;
}
}
return false;
};
}
// This rule look for a dot with a constant vector containing a single 1 and
// the rest 0s. This is the same as doing an extract.
FoldingRule DotProductDoingExtract() {
@@ -3231,6 +3455,10 @@ void FoldingRules::AddFoldingRules() {
rules_[spv::Op::OpISub].push_back(MergeSubAddArithmetic());
rules_[spv::Op::OpISub].push_back(MergeSubSubArithmetic());
rules_[spv::Op::OpBitwiseAnd].push_back(RedundantAndOrXor());
rules_[spv::Op::OpBitwiseAnd].push_back(RedundantAndAddSub());
rules_[spv::Op::OpBitwiseAnd].push_back(RedundantAndShift());
rules_[spv::Op::OpPhi].push_back(RedundantPhi());
rules_[spv::Op::OpSNegate].push_back(MergeNegateArithmetic());

View File

@@ -557,6 +557,7 @@ void IRContext::AddCombinatorsForCapability(uint32_t capability) {
(uint32_t)spv::Op::OpTypeAccelerationStructureKHR,
(uint32_t)spv::Op::OpTypeRayQueryKHR,
(uint32_t)spv::Op::OpTypeHitObjectNV,
(uint32_t)spv::Op::OpTypeHitObjectEXT,
(uint32_t)spv::Op::OpTypeArray,
(uint32_t)spv::Op::OpTypeRuntimeArray,
(uint32_t)spv::Op::OpTypeNodePayloadArrayAMDX,
@@ -927,11 +928,13 @@ uint32_t IRContext::GetBuiltinInputVarId(uint32_t builtin) {
return 0;
}
}
if (reg_type == nullptr) return 0; // Error
uint32_t type_id = type_mgr->GetTypeInstruction(reg_type);
uint32_t varTyPtrId =
type_mgr->FindPointerToType(type_id, spv::StorageClass::Input);
// TODO(1841): Handle id overflow.
var_id = TakeNextId();
if (var_id == 0) return 0; // Error
std::unique_ptr<Instruction> newVarOp(
new Instruction(this, spv::Op::OpVariable, varTyPtrId, var_id,
{{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,

View File

@@ -467,6 +467,7 @@ void LocalAccessChainConvertPass::InitExtensions() {
"SPV_NV_shader_invocation_reorder",
"SPV_NV_cluster_acceleration_structure",
"SPV_NV_linear_swept_spheres",
"SPV_KHR_maximal_reconvergence",
});
}

View File

@@ -304,6 +304,7 @@ void LocalSingleBlockLoadStoreElimPass::InitExtensions() {
"SPV_NV_shader_invocation_reorder",
"SPV_NV_cluster_acceleration_structure",
"SPV_NV_linear_swept_spheres",
"SPV_KHR_maximal_reconvergence",
});
}

View File

@@ -156,6 +156,7 @@ void LocalSingleStoreElimPass::InitExtensionAllowList() {
"SPV_NV_shader_invocation_reorder",
"SPV_NV_cluster_acceleration_structure",
"SPV_NV_linear_swept_spheres",
"SPV_KHR_maximal_reconvergence",
});
}
bool LocalSingleStoreElimPass::ProcessVariable(Instruction* var_inst) {

View File

@@ -930,13 +930,13 @@ LoopDescriptor::Status LoopDescriptor::CreatePreHeaderBlocksIfMissing() {
for (auto& loop : *this) {
if (!loop.GetPreHeaderBlock()) {
if (!loop.GetOrCreatePreHeaderBlock()) {
return Status::kFailure;
return Status::Failure;
}
modified = true;
}
}
return modified ? Status::kSuccessWithChange : Status::kSuccessWithoutChange;
return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
}
// Add and remove loops which have been marked for addition and removal to

View File

@@ -28,6 +28,7 @@
#include "source/opt/dominator_analysis.h"
#include "source/opt/module.h"
#include "source/opt/tree_iterator.h"
#include "source/util/status.h"
namespace spvtools {
namespace opt {
@@ -426,7 +427,7 @@ class LoopDescriptor {
using const_pre_iterator = TreeDFIterator<const Loop>;
// The status of processing a module.
enum class Status { kSuccessWithChange, kSuccessWithoutChange, kFailure };
using Status = utils::Status;
// Creates a loop object for all loops found in |f|.
LoopDescriptor(IRContext* context, const Function* f);

View File

@@ -41,8 +41,8 @@ Pass::Status LoopFusionPass::ProcessFunction(Function* function) {
// sure to return Status::SuccessWithChange in that case.
bool modified = false;
auto status = ld.CreatePreHeaderBlocksIfMissing();
if (status == LoopDescriptor::Status::kFailure) return Status::Failure;
modified = status == LoopDescriptor::Status::kSuccessWithChange;
if (status == LoopDescriptor::Status::Failure) return Status::Failure;
modified = status == LoopDescriptor::Status::SuccessWithChange;
// TODO(tremmelg): Could the only loop that |loop| could possibly be fused be
// picked out so don't have to check every loop

View File

@@ -938,6 +938,11 @@ bool LoopUnrollerUtilsImpl::AssignNewResultIds(BasicBlock* basic_block) {
inst.SetResultId(new_id);
def_use_mgr->AnalyzeInstDef(&inst);
// All decorations that can apply to an instruction in a function body
// modify the behaviour of the instruction, and should be on the
// new instruction to keep the same results.
context_->get_decoration_mgr()->CloneDecorations(old_id, new_id);
// Save the mapping of old_id -> new_id.
state_.new_inst[old_id] = inst.result_id();
// Check if this instruction is the induction variable.

View File

@@ -25,6 +25,7 @@
#include "source/opt/def_use_manager.h"
#include "source/opt/ir_context.h"
#include "source/opt/module.h"
#include "source/util/status.h"
#include "spirv-tools/libspirv.hpp"
#include "types.h"
@@ -46,11 +47,7 @@ class Pass {
//
// The numbers for the cases are assigned to make sure that Failure & anything
// is Failure, SuccessWithChange & any success is SuccessWithChange.
enum class Status {
Failure = 0x00,
SuccessWithChange = 0x10,
SuccessWithoutChange = 0x11,
};
using Status = utils::Status;
using ProcessFunction = std::function<bool(Function*)>;

View File

@@ -142,6 +142,7 @@ Instruction* SplitCombinedImageSamplerPass::GetSamplerType() {
analysis::Sampler s;
uint32_t sampler_type_id = type_mgr_->GetTypeInstruction(&s);
sampler_type_ = def_use_mgr_->GetDef(sampler_type_id);
if (sampler_type_ == nullptr) return nullptr;
assert(first_sampled_image_type_);
sampler_type_->InsertBefore(first_sampled_image_type_);
RegisterNewGlobal(sampler_type_->result_id());
@@ -169,6 +170,7 @@ std::pair<Instruction*, Instruction*> SplitCombinedImageSamplerPass::SplitType(
auto* image_type =
def_use_mgr_->GetDef(combined_kind_type.GetSingleWordInOperand(0));
auto* sampler_type = GetSamplerType();
if (!sampler_type) return {nullptr, nullptr};
type_remap_[combined_kind_type.result_id()] = {&combined_kind_type,
image_type, sampler_type};
return {image_type, sampler_type};
@@ -187,7 +189,9 @@ std::pair<Instruction*, Instruction*> SplitCombinedImageSamplerPass::SplitType(
// this defensively.
if (image_pointee && sampler_pointee) {
auto* ptr_image = MakeUniformConstantPointer(image_pointee);
if (!ptr_image) return {nullptr, nullptr};
auto* ptr_sampler = MakeUniformConstantPointer(sampler_pointee);
if (!ptr_sampler) return {nullptr, nullptr};
type_remap_[combined_kind_type.result_id()] = {
&combined_kind_type, ptr_image, ptr_sampler};
return {ptr_image, ptr_sampler};
@@ -207,6 +211,7 @@ std::pair<Instruction*, Instruction*> SplitCombinedImageSamplerPass::SplitType(
analysis::Array array_image_ty(image_ty, array_ty->length_info());
const uint32_t array_image_ty_id =
type_mgr_->GetTypeInstruction(&array_image_ty);
if (array_image_ty_id == 0) return {nullptr, nullptr};
auto* array_image_ty_inst = def_use_mgr_->GetDef(array_image_ty_id);
if (!IsKnownGlobal(array_image_ty_id)) {
array_image_ty_inst->InsertBefore(&combined_kind_type);
@@ -214,11 +219,14 @@ std::pair<Instruction*, Instruction*> SplitCombinedImageSamplerPass::SplitType(
// GetTypeInstruction also updated the def-use manager.
}
auto* sampler_ty_inst = GetSamplerType();
if (!sampler_ty_inst) return {nullptr, nullptr};
analysis::Array sampler_array_ty(
type_mgr_->GetType(GetSamplerType()->result_id()),
type_mgr_->GetType(sampler_ty_inst->result_id()),
array_ty->length_info());
const uint32_t array_sampler_ty_id =
type_mgr_->GetTypeInstruction(&sampler_array_ty);
if (array_sampler_ty_id == 0) return {nullptr, nullptr};
auto* array_sampler_ty_inst = def_use_mgr_->GetDef(array_sampler_ty_id);
if (!IsKnownGlobal(array_sampler_ty_id)) {
array_sampler_ty_inst->InsertBefore(&combined_kind_type);
@@ -240,6 +248,7 @@ std::pair<Instruction*, Instruction*> SplitCombinedImageSamplerPass::SplitType(
analysis::RuntimeArray array_image_ty(image_ty);
const uint32_t array_image_ty_id =
type_mgr_->GetTypeInstruction(&array_image_ty);
if (array_image_ty_id == 0) return {nullptr, nullptr};
auto* array_image_ty_inst = def_use_mgr_->GetDef(array_image_ty_id);
if (!IsKnownGlobal(array_image_ty_id)) {
array_image_ty_inst->InsertBefore(&combined_kind_type);
@@ -247,10 +256,13 @@ std::pair<Instruction*, Instruction*> SplitCombinedImageSamplerPass::SplitType(
// GetTypeInstruction also updated the def-use manager.
}
auto* sampler_ty_inst = GetSamplerType();
if (!sampler_ty_inst) return {nullptr, nullptr};
analysis::RuntimeArray sampler_array_ty(
type_mgr_->GetType(GetSamplerType()->result_id()));
type_mgr_->GetType(sampler_ty_inst->result_id()));
const uint32_t array_sampler_ty_id =
type_mgr_->GetTypeInstruction(&sampler_array_ty);
if (array_sampler_ty_id == 0) return {nullptr, nullptr};
auto* array_sampler_ty_inst = def_use_mgr_->GetDef(array_sampler_ty_id);
if (!IsKnownGlobal(array_sampler_ty_id)) {
array_sampler_ty_inst->InsertBefore(&combined_kind_type);
@@ -273,14 +285,14 @@ spv_result_t SplitCombinedImageSamplerPass::RemapVar(
// Create an image variable, and a sampler variable.
auto* combined_var_type = def_use_mgr_->GetDef(combined_var->type_id());
auto [ptr_image_ty, ptr_sampler_ty] = SplitType(*combined_var_type);
assert(ptr_image_ty);
assert(ptr_sampler_ty);
// TODO(1841): Handle id overflow.
if (!ptr_image_ty || !ptr_sampler_ty) return SPV_ERROR_INTERNAL;
Instruction* sampler_var = builder.AddVariable(
ptr_sampler_ty->result_id(), SpvStorageClassUniformConstant);
// TODO(1841): Handle id overflow.
if (sampler_var == nullptr) return SPV_ERROR_INTERNAL;
Instruction* image_var = builder.AddVariable(ptr_image_ty->result_id(),
SpvStorageClassUniformConstant);
if (image_var == nullptr) return SPV_ERROR_INTERNAL;
modified_ = true;
return RemapUses(combined_var, image_var, sampler_var);
}
@@ -356,12 +368,12 @@ spv_result_t SplitCombinedImageSamplerPass::RemapUses(
// Create loads for the image part and sampler part.
builder.SetInsertPoint(load);
// TODO(1841): Handle id overflow.
auto* image = builder.AddLoad(PointeeTypeId(use.image_part),
use.image_part->result_id());
// TODO(1841): Handle id overflow.
if (!image) return SPV_ERROR_INTERNAL;
auto* sampler = builder.AddLoad(PointeeTypeId(use.sampler_part),
use.sampler_part->result_id());
if (!sampler) return SPV_ERROR_INTERNAL;
// Move decorations, such as RelaxedPrecision.
auto* deco_mgr = context()->get_decoration_mgr();
@@ -372,6 +384,7 @@ spv_result_t SplitCombinedImageSamplerPass::RemapUses(
// Create a sampled image from the loads of the two parts.
auto* sampled_image = builder.AddSampledImage(
load->type_id(), image->result_id(), sampler->result_id());
if (!sampled_image) return SPV_ERROR_INTERNAL;
// Replace the original sampled image value with the new one.
std::unordered_set<Instruction*> users;
def_use_mgr_->ForEachUse(
@@ -463,14 +476,18 @@ spv_result_t SplitCombinedImageSamplerPass::RemapUses(
auto [result_image_part_ty, result_sampler_part_ty] =
SplitType(*def_use_mgr_->GetDef(original_access_chain->type_id()));
// TODO(1841): Handle id overflow.
if (!result_image_part_ty || !result_sampler_part_ty)
return Fail() << "failed to split type for access chain";
auto* result_image_part = builder.AddOpcodeAccessChain(
use.user->opcode(), result_image_part_ty->result_id(),
use.image_part->result_id(), indices);
// TODO(1841): Handle id overflow.
if (!result_image_part)
return Fail() << "failed to create access chain for image part";
auto* result_sampler_part = builder.AddOpcodeAccessChain(
use.user->opcode(), result_sampler_part_ty->result_id(),
use.sampler_part->result_id(), indices);
if (!result_sampler_part)
return Fail() << "failed to create access chain for sampler part";
// Remap uses of the original access chain.
add_remap(original_access_chain, result_image_part,
@@ -521,8 +538,7 @@ spv_result_t SplitCombinedImageSamplerPass::RemapFunctions() {
if (combined_types_.find(param_ty_id) != combined_types_.end()) {
auto* param_type = def_use_mgr_->GetDef(param_ty_id);
auto [image_type, sampler_type] = SplitType(*param_type);
assert(image_type);
assert(sampler_type);
if (!image_type || !sampler_type) return SPV_ERROR_INTERNAL;
// The image and sampler types must already exist, so there is no
// need to move them to the right spot.
new_params.push_back(type_mgr_->GetType(image_type->result_id()));
@@ -579,6 +595,11 @@ spv_result_t SplitCombinedImageSamplerPass::RemapFunctions() {
auto* combined_inst = param.release();
auto* combined_type = def_use_mgr_->GetDef(combined_inst->type_id());
auto [image_type, sampler_type] = SplitType(*combined_type);
if (!image_type || !sampler_type) {
error = true;
return;
}
uint32_t image_param_id = context()->TakeNextId();
if (image_param_id == 0) {
error = true;
@@ -621,6 +642,7 @@ Instruction* SplitCombinedImageSamplerPass::MakeUniformConstantPointer(
Instruction* pointee) {
uint32_t ptr_id = type_mgr_->FindPointerToType(
pointee->result_id(), spv::StorageClass::UniformConstant);
if (ptr_id == 0) return nullptr;
auto* ptr = def_use_mgr_->GetDef(ptr_id);
if (!IsKnownGlobal(ptr_id)) {
// The pointer type was created at the end. Put it right after the

View File

@@ -237,6 +237,7 @@ uint32_t TypeManager::GetTypeInstruction(const Type* type) {
DefineParameterlessCase(AccelerationStructureNV);
DefineParameterlessCase(RayQueryKHR);
DefineParameterlessCase(HitObjectNV);
DefineParameterlessCase(HitObjectEXT);
#undef DefineParameterlessCase
case Type::kInteger:
typeInst = MakeUnique<Instruction>(
@@ -654,6 +655,7 @@ Type* TypeManager::RebuildType(uint32_t type_id, const Type& type) {
DefineNoSubtypeCase(AccelerationStructureNV);
DefineNoSubtypeCase(RayQueryKHR);
DefineNoSubtypeCase(HitObjectNV);
DefineNoSubtypeCase(HitObjectEXT);
#undef DefineNoSubtypeCase
case Type::kVector: {
const Vector* vec_ty = type.AsVector();
@@ -1082,6 +1084,9 @@ Type* TypeManager::RecordIfTypeDefinition(const Instruction& inst) {
case spv::Op::OpTypeHitObjectNV:
type = new HitObjectNV();
break;
case spv::Op::OpTypeHitObjectEXT:
type = new HitObjectEXT();
break;
case spv::Op::OpTypeTensorLayoutNV:
type = new TensorLayoutNV(inst.GetSingleWordInOperand(0),
inst.GetSingleWordInOperand(1));

View File

@@ -203,7 +203,11 @@ class TypeManager {
return GetRegisteredType(&bool_type);
}
uint32_t GetBoolTypeId() { return GetTypeInstruction(GetBoolType()); }
uint32_t GetBoolTypeId() {
Type* bool_type = GetBoolType();
if (bool_type == nullptr) return 0;
return GetTypeInstruction(bool_type);
}
Type* GetVoidType() {
Void void_type;

View File

@@ -135,6 +135,7 @@ std::unique_ptr<Type> Type::Clone() const {
DeclareKindCase(CooperativeVectorNV);
DeclareKindCase(RayQueryKHR);
DeclareKindCase(HitObjectNV);
DeclareKindCase(HitObjectEXT);
DeclareKindCase(TensorARM);
DeclareKindCase(GraphARM);
#undef DeclareKindCase
@@ -187,6 +188,7 @@ bool Type::operator==(const Type& other) const {
DeclareKindCase(CooperativeVectorNV);
DeclareKindCase(RayQueryKHR);
DeclareKindCase(HitObjectNV);
DeclareKindCase(HitObjectEXT);
DeclareKindCase(TensorLayoutNV);
DeclareKindCase(TensorViewNV);
DeclareKindCase(TensorARM);
@@ -249,6 +251,7 @@ size_t Type::ComputeHashValue(size_t hash, SeenTypes* seen) const {
DeclareKindCase(CooperativeVectorNV);
DeclareKindCase(RayQueryKHR);
DeclareKindCase(HitObjectNV);
DeclareKindCase(HitObjectEXT);
DeclareKindCase(TensorLayoutNV);
DeclareKindCase(TensorViewNV);
DeclareKindCase(TensorARM);

View File

@@ -67,6 +67,7 @@ class CooperativeMatrixKHR;
class CooperativeVectorNV;
class RayQueryKHR;
class HitObjectNV;
class HitObjectEXT;
class TensorLayoutNV;
class TensorViewNV;
class TensorARM;
@@ -114,6 +115,7 @@ class Type {
kCooperativeVectorNV,
kRayQueryKHR,
kHitObjectNV,
kHitObjectEXT,
kTensorLayoutNV,
kTensorViewNV,
kTensorARM,
@@ -222,6 +224,7 @@ class Type {
DeclareCastMethod(CooperativeVectorNV)
DeclareCastMethod(RayQueryKHR)
DeclareCastMethod(HitObjectNV)
DeclareCastMethod(HitObjectEXT)
DeclareCastMethod(TensorLayoutNV)
DeclareCastMethod(TensorViewNV)
DeclareCastMethod(TensorARM)
@@ -862,6 +865,7 @@ DefineParameterlessType(NamedBarrier, named_barrier);
DefineParameterlessType(AccelerationStructureNV, accelerationStructureNV);
DefineParameterlessType(RayQueryKHR, rayQueryKHR);
DefineParameterlessType(HitObjectNV, hitObjectNV);
DefineParameterlessType(HitObjectEXT, hitObjectEXT);
#undef DefineParameterlessType
} // namespace analysis

View File

@@ -38,6 +38,45 @@ uint32_t ValueNumberTable::GetValueNumber(uint32_t id) const {
return GetValueNumber(context()->get_def_use_mgr()->GetDef(id));
}
bool ValueNumberTable::IsReadOnlyLoad(Instruction* inst) {
if (!inst->IsLoad()) {
return false;
}
Instruction* address_def = inst->GetBaseAddress();
if (!address_def) {
return false;
}
auto cached_result = read_only_variable_cache_.find(address_def->result_id());
if (cached_result != read_only_variable_cache_.end()) {
return cached_result->second;
}
bool is_read_only = IsReadOnlyVariable(address_def);
read_only_variable_cache_[address_def->result_id()] = is_read_only;
return is_read_only;
}
bool ValueNumberTable::IsReadOnlyVariable(Instruction* address_def) {
if (address_def->opcode() == spv::Op::OpVariable) {
if (address_def->IsReadOnlyPointer()) {
return true;
}
}
if (address_def->opcode() == spv::Op::OpLoad) {
const analysis::Type* address_type =
context()->get_type_mgr()->GetType(address_def->type_id());
if (address_type->AsSampledImage() != nullptr) {
const auto* image_type =
address_type->AsSampledImage()->image_type()->AsImage();
return image_type->sampled() == 1;
}
}
return false;
}
uint32_t ValueNumberTable::AssignValueNumber(Instruction* inst) {
// If it already has a value return that.
uint32_t value = GetValueNumber(inst);
@@ -88,7 +127,7 @@ uint32_t ValueNumberTable::AssignValueNumber(Instruction* inst) {
// Note that this test will also handle volatile loads because they are not
// read only. However, if this is ever relaxed because we analyze stores, we
// will have to add a new case for volatile loads.
if (inst->IsLoad() && !inst->IsReadOnlyLoad()) {
if (inst->IsLoad() && !IsReadOnlyLoad(inst)) {
return assign_new_number(inst);
}

View File

@@ -70,6 +70,14 @@ class ValueNumberTable {
// Assigns a value number to every result id in the module.
void BuildDominatorTreeValueNumberTable();
// Returns true if |inst| is a load from read-only memory. This is a cached
// version of |Instruction::IsReadOnlyLoad| that is local to this pass.
bool IsReadOnlyLoad(Instruction* inst);
// Returns true if the variable pointed to by |address_def| is read-only.
// This is the part of |IsReadOnlyLoad| that is cached.
bool IsReadOnlyVariable(Instruction* address_def);
// Returns the new value number.
uint32_t TakeNextValueNumber() { return next_value_number_++; }
@@ -81,6 +89,10 @@ class ValueNumberTable {
std::unordered_map<Instruction, uint32_t, ValueTableHash, ComputeSameValue>
instruction_to_value_;
std::unordered_map<uint32_t, uint32_t> id_to_value_;
// A cache for the results of |IsReadOnlyVariable|. The key is the base
// variable of a load.
std::unordered_map<uint32_t, bool> read_only_variable_cache_;
IRContext* context_;
uint32_t next_value_number_;
};

View File

@@ -206,6 +206,23 @@ T ZeroExtendValue(T value, uint32_t number_of_bits) {
return utils::ClearHighBits(value, bit_width - number_of_bits);
}
// Returns the the least significant bit from |value|.
template <typename T>
constexpr T LSB(T value) {
static_assert(std::is_integral<T>::value, "LSB requires integer type");
if constexpr (std::is_unsigned_v<T>) {
// Prevent warnings about doing a -x on unsigned values.
return value & (~value + 1);
} else {
return value & -value;
}
}
static_assert(LSB<uint32_t>(UINT32_MAX) == uint32_t(0x00000001), "LSB failed");
static_assert(LSB<uint32_t>(0x10001000) == uint32_t(0x00001000), "LSB failed");
static_assert(LSB<uint32_t>(0x10000000) == uint32_t(0x10000000), "LSB failed");
static_assert(LSB<int32_t>(-1) == int32_t(0x00000001), "LSB failed");
} // namespace utils
} // namespace spvtools

View File

@@ -0,0 +1,31 @@
// Copyright (c) 2025 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_UTIL_STATUS_H_
#define SOURCE_UTIL_STATUS_H_
namespace spvtools {
namespace utils {
// The result of processing a module.
enum class Status {
Failure = 0x0,
SuccessWithChange = 0x10,
SuccessWithoutChange = 0x11
};
} // namespace utils
} // namespace spvtools
#endif // SOURCE_UTIL_STATUS_H_

View File

@@ -399,6 +399,7 @@ spv_result_t ValidateBinaryUsingContextAndValidationState(
if (auto error = RayQueryPass(*vstate, &instruction)) return error;
if (auto error = RayTracingPass(*vstate, &instruction)) return error;
if (auto error = RayReorderNVPass(*vstate, &instruction)) return error;
if (auto error = RayReorderEXTPass(*vstate, &instruction)) return error;
if (auto error = MeshShadingPass(*vstate, &instruction)) return error;
if (auto error = TensorLayoutPass(*vstate, &instruction)) return error;
if (auto error = TensorPass(*vstate, &instruction)) return error;
@@ -430,6 +431,7 @@ spv_result_t ValidateBinaryUsingContextAndValidationState(
if (auto error = ValidateQCOMImageProcessingTextureUsages(*vstate, &inst))
return error;
}
if (auto error = ValidateLogicalPointers(*vstate)) return error;
return SPV_SUCCESS;
}

View File

@@ -220,6 +220,9 @@ spv_result_t RayTracingPass(ValidationState_t& _, const Instruction* inst);
/// Validates correctness of shader execution reorder instructions.
spv_result_t RayReorderNVPass(ValidationState_t& _, const Instruction* inst);
/// Validates correctness of shader execution reorder EXT instructions.
spv_result_t RayReorderEXTPass(ValidationState_t& _, const Instruction* inst);
/// Validates correctness of mesh shading instructions.
spv_result_t MeshShadingPass(ValidationState_t& _, const Instruction* inst);
@@ -259,6 +262,9 @@ spv_result_t ValidateSmallTypeUses(ValidationState_t& _,
spv_result_t ValidateQCOMImageProcessingTextureUsages(ValidationState_t& _,
const Instruction* inst);
/// Validates logical pointer restrictions.
spv_result_t ValidateLogicalPointers(ValidationState_t& _);
/// @brief Validate the ID's within a SPIR-V binary
///
/// @param[in] pInstructions array of instructions

View File

@@ -217,6 +217,7 @@ spv_result_t ValidateDecorationTarget(ValidationState_t& _, spv::Decoration dec,
sc != spv::StorageClass::IncomingCallableDataKHR &&
sc != spv::StorageClass::ShaderRecordBufferKHR &&
sc != spv::StorageClass::HitObjectAttributeNV &&
sc != spv::StorageClass::HitObjectAttributeEXT &&
sc != spv::StorageClass::TileImageEXT) {
return _.diag(SPV_ERROR_INVALID_ID, target)
<< _.VkErrorID(6672) << _.SpvDecorationString(dec)

View File

@@ -36,7 +36,8 @@ spv_result_t ArithmeticsPass(ValidationState_t& _, const Instruction* inst) {
case spv::Op::OpFDiv:
case spv::Op::OpFRem:
case spv::Op::OpFMod:
case spv::Op::OpFNegate: {
case spv::Op::OpFNegate:
case spv::Op::OpFmaKHR: {
bool supportsCoopMat =
(opcode != spv::Op::OpFMul && opcode != spv::Op::OpFRem &&
opcode != spv::Op::OpFMod);

View File

@@ -123,7 +123,7 @@ typedef enum VUIDError_ {
VUIDErrorMax,
} VUIDError;
const static uint32_t NumVUIDBuiltins = 40;
const static uint32_t NumVUIDBuiltins = 41;
typedef struct {
spv::BuiltIn builtIn;
@@ -170,6 +170,7 @@ std::array<BuiltinVUIDMapping, NumVUIDBuiltins> builtinVUIDInfo = {{
{spv::BuiltIn::CullMaskKHR, {6735, 6736, 6737}},
{spv::BuiltIn::BaryCoordKHR, {4154, 4155, 4156}},
{spv::BuiltIn::BaryCoordNoPerspKHR, {4160, 4161, 4162}},
{spv::BuiltIn::LocalInvocationIndex, {4284, 4285, 4286}},
{spv::BuiltIn::PrimitivePointIndicesEXT, {7041, 7043, 7044}},
{spv::BuiltIn::PrimitiveLineIndicesEXT, {7047, 7049, 7050}},
{spv::BuiltIn::PrimitiveTriangleIndicesEXT, {7053, 7055, 7056}},
@@ -2829,14 +2830,69 @@ spv_result_t BuiltInsValidator::ValidateVertexIdAtDefinition(
spv_result_t BuiltInsValidator::ValidateLocalInvocationIndexAtDefinition(
const Decoration& decoration, const Instruction& inst) {
if (spvIsVulkanEnv(_.context()->target_env)) {
if (spv_result_t error = ValidateI32(
decoration, inst,
[this, &inst](const std::string& message) -> spv_result_t {
uint32_t vuid = GetVUIDForBuiltin(
spv::BuiltIn::LocalInvocationIndex, VUIDErrorType);
return _.diag(SPV_ERROR_INVALID_DATA, &inst)
<< _.VkErrorID(vuid)
<< "According to the Vulkan spec BuiltIn "
"LocalInvocationIndex variable needs to be a 32-bit "
"int scalar. "
<< message;
})) {
return error;
}
}
// Seed at reference checks with this built-in.
return ValidateLocalInvocationIndexAtReference(decoration, inst, inst, inst);
}
spv_result_t BuiltInsValidator::ValidateLocalInvocationIndexAtReference(
const Decoration& decoration, const Instruction& built_in_inst,
const Instruction&,
const Instruction& referenced_inst,
const Instruction& referenced_from_inst) {
if (spvIsVulkanEnv(_.context()->target_env)) {
const spv::StorageClass storage_class =
GetStorageClass(referenced_from_inst);
if (storage_class != spv::StorageClass::Max &&
storage_class != spv::StorageClass::Input) {
uint32_t vuid = GetVUIDForBuiltin(spv::BuiltIn::LocalInvocationIndex,
VUIDErrorStorageClass);
return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
<< _.VkErrorID(vuid)
<< "Vulkan spec allows BuiltIn LocalInvocationIndex to be only "
"used for variables with Input storage class. "
<< GetReferenceDesc(decoration, built_in_inst, referenced_inst,
referenced_from_inst)
<< " " << GetStorageClassDesc(referenced_from_inst);
}
for (const spv::ExecutionModel execution_model : execution_models_) {
bool has_vulkan_model =
execution_model == spv::ExecutionModel::GLCompute ||
execution_model == spv::ExecutionModel::TaskNV ||
execution_model == spv::ExecutionModel::MeshNV ||
execution_model == spv::ExecutionModel::TaskEXT ||
execution_model == spv::ExecutionModel::MeshEXT;
if (spvIsVulkanEnv(_.context()->target_env) && !has_vulkan_model) {
uint32_t vuid = GetVUIDForBuiltin(spv::BuiltIn::LocalInvocationIndex,
VUIDErrorExecutionModel);
return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
<< _.VkErrorID(vuid)
<< "Vulkan spec allows BuiltIn LocalInvocationIndex to be used "
"only with GLCompute, MeshNV, TaskNV, MeshEXT or"
<< " TaskEXT execution model. "
<< GetReferenceDesc(decoration, built_in_inst, referenced_inst,
referenced_from_inst, execution_model);
}
}
}
if (function_id_ == 0) {
// Propagate this rule to all dependant ids in the global scope.
id_to_at_reference_checks_[referenced_from_inst.id()].push_back(

View File

@@ -532,8 +532,7 @@ spv_result_t ValidateVectorShuffle(ValidationState_t& _,
if (!resultType || resultType->opcode() != spv::Op::OpTypeVector) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "The Result Type of OpVectorShuffle must be"
<< " OpTypeVector. Found Op"
<< spvOpcodeString(static_cast<spv::Op>(resultType->opcode()))
<< " OpTypeVector. Found Op" << spvOpcodeString(resultType->opcode())
<< ".";
}

View File

@@ -13,6 +13,7 @@
// limitations under the License.
// Validates correctness of extension SPIR-V instructions.
#include <algorithm>
#include <cstdlib>
#include <sstream>
#include <string>
@@ -1087,6 +1088,7 @@ spv_result_t ValidateExtension(ValidationState_t& _, const Instruction* inst) {
ExtensionToString(kSPV_KHR_workgroup_memory_explicit_layout) ||
extension == ExtensionToString(kSPV_EXT_mesh_shader) ||
extension == ExtensionToString(kSPV_NV_shader_invocation_reorder) ||
extension == ExtensionToString(kSPV_EXT_shader_invocation_reorder) ||
extension ==
ExtensionToString(kSPV_NV_cluster_acceleration_structure) ||
extension == ExtensionToString(kSPV_NV_linear_swept_spheres) ||
@@ -3753,12 +3755,26 @@ spv_result_t ValidateExtInstDebugInfo100(ValidationState_t& _,
},
inst, 12)) {
auto* operand = _.FindDef(inst->word(12));
if (operand->opcode() != spv::Op::OpVariable &&
operand->opcode() != spv::Op::OpConstant) {
std::initializer_list<spv::Op> allowed_opcodes = {
spv::Op::OpVariable,
spv::Op::OpConstantTrue,
spv::Op::OpConstantFalse,
spv::Op::OpConstant,
spv::Op::OpConstantComposite,
spv::Op::OpConstantSampler,
spv::Op::OpConstantNull,
spv::Op::OpSpecConstantTrue,
spv::Op::OpSpecConstantFalse,
spv::Op::OpSpecConstant,
spv::Op::OpSpecConstantComposite,
spv::Op::OpSpecConstantOp};
if (std::find(allowed_opcodes.begin(), allowed_opcodes.end(),
operand->opcode()) == allowed_opcodes.end()) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< GetExtInstName(_, inst) << ": "
<< "expected operand Variable must be a result id of "
"OpVariable or OpConstant or DebugInfoNone";
"OpVariable, OpConstant variant, OpSpecConstant variant "
"or DebugInfoNone";
}
}
if (num_words == 15) {

View File

@@ -170,6 +170,34 @@ spv_result_t ValidateFunctionCall(ValidationState_t& _,
<< "s type does not match Function <id> "
<< _.getIdName(return_type->id()) << "s return type.";
}
if (!_.options()->relax_logical_pointer &&
(_.addressing_model() == spv::AddressingModel::Logical ||
_.addressing_model() == spv::AddressingModel::PhysicalStorageBuffer64)) {
if (return_type->opcode() == spv::Op::OpTypePointer ||
return_type->opcode() == spv::Op::OpTypeUntypedPointerKHR) {
const auto sc = return_type->GetOperandAs<spv::StorageClass>(1);
if (sc != spv::StorageClass::PhysicalStorageBuffer) {
if (!_.HasCapability(spv::Capability::VariablePointersStorageBuffer) &&
sc == spv::StorageClass::StorageBuffer) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "In Logical addressing, functions may only return a "
"storage buffer pointer if the "
"VariablePointersStorageBuffer capability is declared";
} else if (!_.HasCapability(spv::Capability::VariablePointers) &&
sc == spv::StorageClass::Workgroup) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "In Logical addressing, functions may only return a "
"workgroup pointer if the VariablePointers capability is "
"declared";
} else if (sc != spv::StorageClass::StorageBuffer &&
sc != spv::StorageClass::Workgroup) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "In Logical addressing, functions may not return a pointer "
"in this storage class";
}
}
}
}
const auto function_type_id = function->GetOperandAs<uint32_t>(3);
const auto function_type = _.FindDef(function_type_id);
@@ -216,12 +244,14 @@ spv_result_t ValidateFunctionCall(ValidationState_t& _,
}
}
if (_.addressing_model() == spv::AddressingModel::Logical) {
if (_.addressing_model() == spv::AddressingModel::Logical ||
_.addressing_model() == spv::AddressingModel::PhysicalStorageBuffer64) {
if ((parameter_type->opcode() == spv::Op::OpTypePointer ||
parameter_type->opcode() == spv::Op::OpTypeUntypedPointerKHR) &&
!_.options()->relax_logical_pointer) {
spv::StorageClass sc =
parameter_type->GetOperandAs<spv::StorageClass>(1u);
if (sc != spv::StorageClass::PhysicalStorageBuffer) {
// Validate which storage classes can be pointer operands.
switch (sc) {
case spv::StorageClass::UniformConstant:
@@ -229,6 +259,10 @@ spv_result_t ValidateFunctionCall(ValidationState_t& _,
case spv::StorageClass::Private:
case spv::StorageClass::Workgroup:
case spv::StorageClass::AtomicCounter:
// SPV_EXT_tile_image
case spv::StorageClass::TileImageEXT:
// SPV_KHR_ray_tracing
case spv::StorageClass::ShaderRecordBufferKHR:
// These are always allowed.
break;
case spv::StorageClass::StorageBuffer:
@@ -250,7 +284,8 @@ spv_result_t ValidateFunctionCall(ValidationState_t& _,
argument->opcode() != spv::Op::OpUntypedVariableKHR &&
argument->opcode() != spv::Op::OpFunctionParameter) {
const bool ssbo_vptr =
_.HasCapability(spv::Capability::VariablePointersStorageBuffer) &&
_.HasCapability(
spv::Capability::VariablePointersStorageBuffer) &&
sc == spv::StorageClass::StorageBuffer;
const bool wg_vptr =
_.HasCapability(spv::Capability::VariablePointers) &&
@@ -266,6 +301,7 @@ spv_result_t ValidateFunctionCall(ValidationState_t& _,
}
}
}
}
return SPV_SUCCESS;
}

View File

@@ -1100,7 +1100,7 @@ spv_result_t ValidateSampledImage(ValidationState_t& _,
<< "Result <id> from OpSampledImage instruction must not appear "
"as "
"operands of Op"
<< spvOpcodeString(static_cast<spv::Op>(consumer_opcode)) << "."
<< spvOpcodeString(consumer_opcode) << "."
<< " Found result <id> " << _.getIdName(inst->id())
<< " as an operand of <id> " << _.getIdName(consumer_instr->id())
<< ".";
@@ -1110,12 +1110,11 @@ spv_result_t ValidateSampledImage(ValidationState_t& _,
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Result <id> from OpSampledImage instruction must not appear "
"as operand for Op"
<< spvOpcodeString(static_cast<spv::Op>(consumer_opcode))
<< spvOpcodeString(consumer_opcode)
<< ", since it is not specified as taking an "
<< "OpTypeSampledImage."
<< " Found result <id> " << _.getIdName(inst->id())
<< " as an operand of <id> " << _.getIdName(consumer_instr->id())
<< ".";
<< "OpTypeSampledImage." << " Found result <id> "
<< _.getIdName(inst->id()) << " as an operand of <id> "
<< _.getIdName(consumer_instr->id()) << ".";
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -515,6 +515,7 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) {
storage_class != spv::StorageClass::IncomingCallableDataKHR &&
storage_class != spv::StorageClass::TaskPayloadWorkgroupEXT &&
storage_class != spv::StorageClass::HitObjectAttributeNV &&
storage_class != spv::StorageClass::HitObjectAttributeEXT &&
storage_class != spv::StorageClass::NodePayloadAMDX) {
bool storage_input_or_output = storage_class == spv::StorageClass::Input ||
storage_class == spv::StorageClass::Output;
@@ -586,14 +587,31 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) {
const auto pointee = untyped_pointer
? value_id == 0 ? nullptr : _.FindDef(value_id)
: _.FindDef(result_type->word(3));
if (_.addressing_model() == spv::AddressingModel::Logical &&
if ((_.addressing_model() == spv::AddressingModel::Logical ||
_.addressing_model() == spv::AddressingModel::PhysicalStorageBuffer64) &&
!_.options()->relax_logical_pointer) {
// VariablePointersStorageBuffer is implied by VariablePointers.
if (pointee && pointee->opcode() == spv::Op::OpTypePointer) {
if (!_.HasCapability(spv::Capability::VariablePointersStorageBuffer)) {
if (pointee && (pointee->opcode() == spv::Op::OpTypePointer ||
pointee->opcode() == spv::Op::OpTypeUntypedPointerKHR)) {
const auto sc = pointee->GetOperandAs<spv::StorageClass>(1u);
if (sc != spv::StorageClass::PhysicalStorageBuffer) {
if (sc != spv::StorageClass::StorageBuffer &&
sc != spv::StorageClass::Workgroup) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "In Logical addressing, variables may not allocate a pointer "
<< "type";
<< "In Logical addressing, variables can only allocate a "
"pointer to the StorageBuffer or Workgroup storage classes";
} else if (!_.HasCapability(
spv::Capability::VariablePointersStorageBuffer) &&
sc == spv::StorageClass::StorageBuffer) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "In Logical addressing, variables can only allocate a "
"storage buffer pointer if the "
"VariablePointersStorageBuffer capability is declared";
} else if (!_.HasCapability(spv::Capability::VariablePointers) &&
sc == spv::StorageClass::Workgroup) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "In Logical addressing, variables can only allocate a "
"workgroup pointer if the VariablePointers capability is "
"declared";
} else if (storage_class != spv::StorageClass::Function &&
storage_class != spv::StorageClass::Private) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
@@ -603,6 +621,7 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) {
}
}
}
}
if (spvIsVulkanEnv(_.context()->target_env)) {
// Vulkan Push Constant Interface section: Check type of PushConstant
@@ -738,6 +757,11 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) {
<< "OpVariable, <id> " << _.getIdName(inst->id())
<< ", initializer are not allowed for HitObjectAttributeNV";
}
if (storage_class == spv::StorageClass::HitObjectAttributeEXT) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "OpVariable, <id> " << _.getIdName(inst->id())
<< ", initializer are not allowed for HitObjectAttributeEXT";
}
}
if (storage_class == spv::StorageClass::PhysicalStorageBuffer) {
@@ -776,8 +800,7 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) {
// If an OpStruct has an OpTypeRuntimeArray somewhere within it, then it
// must either have the storage class StorageBuffer and be decorated
// with Block, or it must be in the Uniform storage class and be decorated
// as BufferBlock.
// with Block, or it must be in the Uniform storage class
if (value_type && value_type->opcode() == spv::Op::OpTypeStruct) {
if (DoesStructContainRTA(_, value_type)) {
if (storage_class == spv::StorageClass::StorageBuffer ||
@@ -791,13 +814,14 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) {
"PhysicalStorageBuffer.";
}
} else if (storage_class == spv::StorageClass::Uniform) {
if (!_.HasDecoration(value_id, spv::Decoration::BufferBlock)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< _.VkErrorID(4680)
<< "For Vulkan, an OpTypeStruct variable containing an "
<< "OpTypeRuntimeArray must be decorated with BufferBlock "
<< "if it has storage class Uniform.";
}
// BufferBlock Uniform were always allowed.
//
// Block Uniform use to be invalid, but Vulkan added
// VK_EXT_shader_uniform_buffer_unsized_array and now this is
// validated at runtime
//
// The uniform must have either the Block or BufferBlock decoration
// (see VUID-StandaloneSpirv-Uniform-06676)
} else {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< _.VkErrorID(4680)
@@ -1299,7 +1323,7 @@ spv_result_t ValidateCopyMemoryMemoryAccess(ValidationState_t& _,
}
} else {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< spvOpcodeString(static_cast<spv::Op>(inst->opcode()))
<< spvOpcodeString(inst->opcode())
<< " with two memory access operands requires SPIR-V 1.4 or "
"later";
}
@@ -1551,9 +1575,7 @@ spv_result_t ValidateCopyMemory(ValidationState_t& _, const Instruction* inst) {
spv_result_t ValidateAccessChain(ValidationState_t& _,
const Instruction* inst) {
std::string instr_name =
"Op" + std::string(spvOpcodeString(static_cast<spv::Op>(inst->opcode())));
const spv::Op opcode = inst->opcode();
const bool untyped_pointer = spvOpcodeGeneratesUntypedPointer(inst->opcode());
// The result type must be OpTypePointer for regular access chains and an
@@ -1563,19 +1585,17 @@ spv_result_t ValidateAccessChain(ValidationState_t& _,
if (!result_type ||
spv::Op::OpTypeUntypedPointerKHR != result_type->opcode()) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "The Result Type of " << instr_name << " <id> "
<< "The Result Type of Op" << spvOpcodeString(opcode) << " <id> "
<< _.getIdName(inst->id())
<< " must be OpTypeUntypedPointerKHR. Found Op"
<< spvOpcodeString(static_cast<spv::Op>(result_type->opcode()))
<< ".";
<< spvOpcodeString(result_type->opcode()) << ".";
}
} else {
if (!result_type || spv::Op::OpTypePointer != result_type->opcode()) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "The Result Type of " << instr_name << " <id> "
<< "The Result Type of Op" << spvOpcodeString(opcode) << " <id> "
<< _.getIdName(inst->id()) << " must be OpTypePointer. Found Op"
<< spvOpcodeString(static_cast<spv::Op>(result_type->opcode()))
<< ".";
<< spvOpcodeString(result_type->opcode()) << ".";
}
}
@@ -1653,8 +1673,8 @@ spv_result_t ValidateAccessChain(ValidationState_t& _,
(untyped_pointer && spv::Op::OpTypeUntypedPointerKHR ==
base_type->opcode()))) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "The Base <id> " << _.getIdName(base_id) << " in " << instr_name
<< " instruction must be a pointer.";
<< "The Base <id> " << _.getIdName(base_id) << " in Op"
<< spvOpcodeString(opcode) << " instruction must be a pointer.";
}
// The result pointer storage class and base pointer storage class must match.
@@ -1664,8 +1684,8 @@ spv_result_t ValidateAccessChain(ValidationState_t& _,
if (result_type_storage_class != base_type_storage_class) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "The result pointer storage class and base "
"pointer storage class in "
<< instr_name << " do not match.";
"pointer storage class in Op"
<< spvOpcodeString(opcode) << " do not match.";
}
// The type pointed to by OpTypePointer (word 3) must be a composite type.
@@ -1689,8 +1709,9 @@ spv_result_t ValidateAccessChain(ValidationState_t& _,
_.options()->universal_limits_.max_access_chain_indexes;
if (num_indexes > num_indexes_limit) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "The number of indexes in " << instr_name << " may not exceed "
<< num_indexes_limit << ". Found " << num_indexes << " indexes.";
<< "The number of indexes in Op" << spvOpcodeString(opcode)
<< " may not exceed " << num_indexes_limit << ". Found "
<< num_indexes << " indexes.";
}
// Indexes walk the type hierarchy to the desired depth, potentially down to
// scalar granularity. The first index in Indexes will select the top-level
@@ -1714,9 +1735,29 @@ spv_result_t ValidateAccessChain(ValidationState_t& _,
auto index_type = _.FindDef(cur_word_instr->type_id());
if (!index_type || spv::Op::OpTypeInt != index_type->opcode()) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Indexes passed to " << instr_name
<< "Indexes passed to Op" << spvOpcodeString(opcode)
<< " must be of type integer.";
}
// Logical pointer restrictions: any constant index with a signed integer
// type must not have its sign bit set.
if (!_.options()->relax_logical_pointer &&
(_.addressing_model() == spv::AddressingModel::Logical ||
_.addressing_model() ==
spv::AddressingModel::PhysicalStorageBuffer64) &&
result_type_storage_class !=
static_cast<uint32_t>(spv::StorageClass::PhysicalStorageBuffer)) {
if (index_type->GetOperandAs<uint32_t>(2) == 1) {
int64_t val = 0;
if (_.EvalConstantValInt64(cur_word, &val)) {
if (val < 0) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Index at word " << i << " may not have a negative value";
}
}
}
}
switch (type_pointee->opcode()) {
case spv::Op::OpTypeMatrix:
case spv::Op::OpTypeVector:
@@ -1738,8 +1779,8 @@ spv_result_t ValidateAccessChain(ValidationState_t& _,
int64_t cur_index;
if (!_.EvalConstantValInt64(cur_word, &cur_index)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "The <id> passed to " << instr_name << " to index "
<< _.getIdName(cur_word)
<< "The <id> passed to Op" << spvOpcodeString(opcode)
<< " to index " << _.getIdName(cur_word)
<< " into a "
"structure must be an OpConstant.";
}
@@ -1750,8 +1791,8 @@ spv_result_t ValidateAccessChain(ValidationState_t& _,
static_cast<int64_t>(type_pointee->words().size() - 2);
if (cur_index >= num_struct_members || cur_index < 0) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Index " << _.getIdName(cur_word)
<< " is out of bounds: " << instr_name << " cannot find index "
<< "Index " << _.getIdName(cur_word) << " is out of bounds: Op"
<< spvOpcodeString(opcode) << " cannot find index "
<< cur_index << " into the structure <id> "
<< _.getIdName(type_pointee->id()) << ". This structure has "
<< num_struct_members << " members. Largest valid index is "
@@ -1766,7 +1807,7 @@ spv_result_t ValidateAccessChain(ValidationState_t& _,
default: {
// Give an error. reached non-composite type while indexes still remain.
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< instr_name
<< "Op" << spvOpcodeString(opcode)
<< " reached non-composite type while indexes "
"still remain to be traversed.";
}
@@ -1782,14 +1823,12 @@ spv_result_t ValidateAccessChain(ValidationState_t& _,
// The type being pointed to should be the same as the result type.
if (type_pointee->id() != result_type_pointee->id()) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< instr_name << " result type (Op"
<< spvOpcodeString(
static_cast<spv::Op>(result_type_pointee->opcode()))
<< "Op" << spvOpcodeString(opcode) << " result type (Op"
<< spvOpcodeString(result_type_pointee->opcode())
<< ") does not match the type that results from indexing into the "
"base "
"<id> (Op"
<< spvOpcodeString(static_cast<spv::Op>(type_pointee->opcode()))
<< ").";
<< spvOpcodeString(type_pointee->opcode()) << ").";
}
}
@@ -1798,13 +1837,12 @@ spv_result_t ValidateAccessChain(ValidationState_t& _,
spv_result_t ValidateRawAccessChain(ValidationState_t& _,
const Instruction* inst) {
std::string instr_name = "Op" + std::string(spvOpcodeString(inst->opcode()));
const spv::Op opcode = inst->opcode();
// The result type must be OpTypePointer.
const auto result_type = _.FindDef(inst->type_id());
if (spv::Op::OpTypePointer != result_type->opcode()) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "The Result Type of " << instr_name << " <id> "
<< "The Result Type of Op" << spvOpcodeString(opcode) << " <id> "
<< _.getIdName(inst->id()) << " must be OpTypePointer. Found Op"
<< spvOpcodeString(result_type->opcode()) << '.';
}
@@ -1815,7 +1853,7 @@ spv_result_t ValidateRawAccessChain(ValidationState_t& _,
storage_class != spv::StorageClass::PhysicalStorageBuffer &&
storage_class != spv::StorageClass::Uniform) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "The Result Type of " << instr_name << " <id> "
<< "The Result Type of Op" << spvOpcodeString(opcode) << " <id> "
<< _.getIdName(inst->id())
<< " must point to a storage class of "
"StorageBuffer, PhysicalStorageBuffer, or Uniform.";
@@ -1828,7 +1866,7 @@ spv_result_t ValidateRawAccessChain(ValidationState_t& _,
result_type_pointee->opcode() == spv::Op::OpTypeMatrix ||
result_type_pointee->opcode() == spv::Op::OpTypeStruct) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "The Result Type of " << instr_name << " <id> "
<< "The Result Type of Op" << spvOpcodeString(opcode) << " <id> "
<< _.getIdName(inst->id())
<< " must not point to "
"OpTypeArray, OpTypeMatrix, or OpTypeStruct.";
@@ -1838,7 +1876,7 @@ spv_result_t ValidateRawAccessChain(ValidationState_t& _,
const auto stride = _.FindDef(inst->GetOperandAs<uint32_t>(3));
if (stride->opcode() != spv::Op::OpConstant) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "The Stride of " << instr_name << " <id> "
<< "The Stride of Op" << spvOpcodeString(opcode) << " <id> "
<< _.getIdName(inst->id()) << " must be OpConstant. Found Op"
<< spvOpcodeString(stride->opcode()) << '.';
}
@@ -1846,7 +1884,7 @@ spv_result_t ValidateRawAccessChain(ValidationState_t& _,
const auto stride_type = _.FindDef(stride->type_id());
if (stride_type->opcode() != spv::Op::OpTypeInt) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "The type of Stride of " << instr_name << " <id> "
<< "The type of Stride of Op" << spvOpcodeString(opcode) << " <id> "
<< _.getIdName(inst->id()) << " must be OpTypeInt. Found Op"
<< spvOpcodeString(stride_type->opcode()) << '.';
}
@@ -1858,16 +1896,17 @@ spv_result_t ValidateRawAccessChain(ValidationState_t& _,
const auto value_type = _.FindDef(value->type_id());
if (value_type->opcode() != spv::Op::OpTypeInt) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "The type of " << name << " of " << instr_name << " <id> "
<< _.getIdName(inst->id()) << " must be OpTypeInt. Found Op"
<< "The type of " << name << " of Op" << spvOpcodeString(opcode)
<< " <id> " << _.getIdName(inst->id())
<< " must be OpTypeInt. Found Op"
<< spvOpcodeString(value_type->opcode()) << '.';
}
const auto width = value_type->GetOperandAs<uint32_t>(1);
if (width != 32) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "The integer width of " << name << " of " << instr_name
<< " <id> " << _.getIdName(inst->id()) << " must be 32. Found "
<< width << '.';
<< "The integer width of " << name << " of Op"
<< spvOpcodeString(opcode) << " <id> " << _.getIdName(inst->id())
<< " must be 32. Found " << width << '.';
}
return SPV_SUCCESS;
};
@@ -1918,15 +1957,6 @@ spv_result_t ValidateRawAccessChain(ValidationState_t& _,
spv_result_t ValidatePtrAccessChain(ValidationState_t& _,
const Instruction* inst) {
if (_.addressing_model() == spv::AddressingModel::Logical &&
inst->opcode() == spv::Op::OpPtrAccessChain) {
if (!_.features().variable_pointers) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Generating variable pointers requires capability "
<< "VariablePointers or VariablePointersStorageBuffer";
}
}
// Need to call first, will make sure Base is a valid ID
if (auto error = ValidateAccessChain(_, inst)) return error;
@@ -2006,18 +2036,20 @@ spv_result_t ValidatePtrAccessChain(ValidationState_t& _,
spv_result_t ValidateArrayLength(ValidationState_t& state,
const Instruction* inst) {
std::string instr_name =
"Op" + std::string(spvOpcodeString(static_cast<spv::Op>(inst->opcode())));
const spv::Op opcode = inst->opcode();
// Result type must be a 32-bit unsigned int.
// Result type must be a 32- or 64-bit unsigned int.
// 64-bit requires CapabilityShader64BitIndexingEXT or a pipeline/shader
// flag and is validated in VVL.
auto result_type = state.FindDef(inst->type_id());
if (result_type->opcode() != spv::Op::OpTypeInt ||
result_type->GetOperandAs<uint32_t>(1) != 32 ||
!(result_type->GetOperandAs<uint32_t>(1) == 32 ||
result_type->GetOperandAs<uint32_t>(1) == 64) ||
result_type->GetOperandAs<uint32_t>(2) != 0) {
return state.diag(SPV_ERROR_INVALID_ID, inst)
<< "The Result Type of " << instr_name << " <id> "
<< "The Result Type of Op" << spvOpcodeString(opcode) << " <id> "
<< state.getIdName(inst->id())
<< " must be OpTypeInt with width 32 and signedness 0.";
<< " must be OpTypeInt with width 32 or 64 and signedness 0.";
}
const bool untyped = inst->opcode() == spv::Op::OpUntypedArrayLengthKHR;
@@ -2030,8 +2062,8 @@ spv_result_t ValidateArrayLength(ValidationState_t& state,
}
} else if (pointer_ty->opcode() != spv::Op::OpTypePointer) {
return state.diag(SPV_ERROR_INVALID_ID, inst)
<< "The Structure's type in " << instr_name << " <id> "
<< state.getIdName(inst->id())
<< "The Structure's type in Op" << spvOpcodeString(opcode)
<< " <id> " << state.getIdName(inst->id())
<< " must be a pointer to an OpTypeStruct.";
}
@@ -2044,8 +2076,8 @@ spv_result_t ValidateArrayLength(ValidationState_t& state,
if (structure_type->opcode() != spv::Op::OpTypeStruct) {
return state.diag(SPV_ERROR_INVALID_ID, inst)
<< "The Structure's type in " << instr_name << " <id> "
<< state.getIdName(inst->id())
<< "The Structure's type in Op" << spvOpcodeString(opcode)
<< " <id> " << state.getIdName(inst->id())
<< " must be a pointer to an OpTypeStruct.";
}
@@ -2054,8 +2086,9 @@ spv_result_t ValidateArrayLength(ValidationState_t& state,
state.FindDef(structure_type->GetOperandAs<uint32_t>(num_of_members));
if (last_member->opcode() != spv::Op::OpTypeRuntimeArray) {
return state.diag(SPV_ERROR_INVALID_ID, inst)
<< "The Structure's last member in " << instr_name << " <id> "
<< state.getIdName(inst->id()) << " must be an OpTypeRuntimeArray.";
<< "The Structure's last member in Op" << spvOpcodeString(opcode)
<< " <id> " << state.getIdName(inst->id())
<< " must be an OpTypeRuntimeArray.";
}
// The array member must the index of the last element (the run time
@@ -2063,25 +2096,35 @@ spv_result_t ValidateArrayLength(ValidationState_t& state,
const auto index = untyped ? 4 : 3;
if (inst->GetOperandAs<uint32_t>(index) != num_of_members - 1) {
return state.diag(SPV_ERROR_INVALID_ID, inst)
<< "The array member in " << instr_name << " <id> "
<< "The array member in Op" << spvOpcodeString(opcode) << " <id> "
<< state.getIdName(inst->id())
<< " must be the last member of the struct.";
}
if (spvIsVulkanEnv(state.context()->target_env)) {
const auto storage_class = pointer_ty->GetOperandAs<spv::StorageClass>(1);
if (storage_class == spv::StorageClass::Uniform &&
state.HasDecoration(structure_type->id(), spv::Decoration::Block)) {
return state.diag(SPV_ERROR_INVALID_ID, inst)
<< state.VkErrorID(11805) << "Op" << spvOpcodeString(opcode)
<< " must not be used on the OpTypeRuntimeArray inside a Uniform "
"block";
}
}
return SPV_SUCCESS;
}
spv_result_t ValidateCooperativeMatrixLengthNV(ValidationState_t& state,
const Instruction* inst) {
std::string instr_name =
"Op" + std::string(spvOpcodeString(static_cast<spv::Op>(inst->opcode())));
const spv::Op opcode = inst->opcode();
// Result type must be a 32-bit unsigned int.
auto result_type = state.FindDef(inst->type_id());
if (result_type->opcode() != spv::Op::OpTypeInt ||
result_type->GetOperandAs<uint32_t>(1) != 32 ||
result_type->GetOperandAs<uint32_t>(2) != 0) {
return state.diag(SPV_ERROR_INVALID_ID, inst)
<< "The Result Type of " << instr_name << " <id> "
<< "The Result Type of Op" << spvOpcodeString(opcode) << " <id> "
<< state.getIdName(inst->id())
<< " must be OpTypeInt with width 32 and signedness 0.";
}
@@ -2091,12 +2134,12 @@ spv_result_t ValidateCooperativeMatrixLengthNV(ValidationState_t& state,
auto type = state.FindDef(type_id);
if (isKhr && type->opcode() != spv::Op::OpTypeCooperativeMatrixKHR) {
return state.diag(SPV_ERROR_INVALID_ID, inst)
<< "The type in " << instr_name << " <id> "
<< "The type in Op" << spvOpcodeString(opcode) << " <id> "
<< state.getIdName(type_id)
<< " must be OpTypeCooperativeMatrixKHR.";
} else if (!isKhr && type->opcode() != spv::Op::OpTypeCooperativeMatrixNV) {
return state.diag(SPV_ERROR_INVALID_ID, inst)
<< "The type in " << instr_name << " <id> "
<< "The type in Op" << spvOpcodeString(opcode) << " <id> "
<< state.getIdName(type_id) << " must be OpTypeCooperativeMatrixNV.";
}
return SPV_SUCCESS;
@@ -2299,23 +2342,96 @@ spv_result_t ValidateCooperativeMatrixLoadStoreKHR(ValidationState_t& _,
}
bool stride_required = false;
bool layout_requires_constant_stride = false;
uint64_t layout;
if (_.EvalConstantValUint64(layout_id, &layout)) {
const bool is_arm_layout =
(layout ==
(uint64_t)spv::CooperativeMatrixLayout::RowBlockedInterleavedARM) ||
(layout ==
(uint64_t)spv::CooperativeMatrixLayout::ColumnBlockedInterleavedARM);
if (is_arm_layout) {
if (!_.HasCapability(spv::Capability::CooperativeMatrixLayoutsARM)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Using the RowBlockedInterleavedARM or "
"ColumnBlockedInterleavedARM MemoryLayout requires the "
"CooperativeMatrixLayoutsARM capability be declared";
}
}
stride_required =
(layout == (uint64_t)spv::CooperativeMatrixLayout::RowMajorKHR) ||
(layout == (uint64_t)spv::CooperativeMatrixLayout::ColumnMajorKHR);
(layout == (uint64_t)spv::CooperativeMatrixLayout::ColumnMajorKHR) ||
is_arm_layout;
layout_requires_constant_stride = is_arm_layout;
}
const auto stride_index =
(inst->opcode() == spv::Op::OpCooperativeMatrixLoadKHR) ? 4u : 3u;
if (inst->operands().size() > stride_index) {
const auto stride_id = inst->GetOperandAs<uint32_t>(stride_index);
const auto stride = _.FindDef(stride_id);
if (!stride || !_.IsIntScalarType(stride->type_id())) {
const auto stride_inst = _.FindDef(stride_id);
if (!stride_inst || !_.IsIntScalarType(stride_inst->type_id())) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Stride operand <id> " << _.getIdName(stride_id)
<< " must be a scalar integer type.";
}
// Check SPV_ARM_cooperative_matrix_layouts constraints
if (layout_requires_constant_stride &&
!spvOpcodeIsConstant(stride_inst->opcode())) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "MemoryLayout " << layout
<< " requires Stride come from a constant instruction.";
}
if (layout_requires_constant_stride) {
uint64_t stride;
if (_.EvalConstantValUint64(stride_id, &stride)) {
if ((layout ==
(uint64_t)
spv::CooperativeMatrixLayout::RowBlockedInterleavedARM) ||
(layout ==
(uint64_t)
spv::CooperativeMatrixLayout::ColumnBlockedInterleavedARM)) {
if ((stride != 1) && (stride != 2) && (stride != 4)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "MemoryLayout " << layout
<< " requires Stride be 1, 2, or 4.";
}
}
const uint32_t elty_id = matrix_type->GetOperandAs<uint32_t>(1);
const uint32_t rows_id = matrix_type->GetOperandAs<uint32_t>(3);
const uint32_t cols_id = matrix_type->GetOperandAs<uint32_t>(4);
uint64_t rows = 0, cols = 0;
_.EvalConstantValUint64(rows_id, &rows);
_.EvalConstantValUint64(cols_id, &cols);
uint32_t sizeof_component_in_bytes = _.GetBitWidth(elty_id) / 8;
uint64_t rows_required_multiple = 4;
uint64_t cols_required_multiple = 16 / sizeof_component_in_bytes;
if (layout ==
(uint64_t)spv::CooperativeMatrixLayout::RowBlockedInterleavedARM) {
cols_required_multiple *= stride;
}
if (layout ==
(uint64_t)
spv::CooperativeMatrixLayout::ColumnBlockedInterleavedARM) {
rows_required_multiple *= stride;
}
if ((rows != 0) && (rows % rows_required_multiple != 0)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "MemoryLayout " << layout << " with a Stride of " << stride
<< " requires that the number of rows be a multiple of "
<< rows_required_multiple;
}
if ((cols != 0) && (cols % cols_required_multiple != 0)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "MemoryLayout " << layout << " with a Stride of " << stride
<< " requires that the number of columns be a multiple of "
<< cols_required_multiple;
}
}
}
} else if (stride_required) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "MemoryLayout " << layout << " requires a Stride.";
@@ -2561,6 +2677,23 @@ spv_result_t ValidateInt32Operand(ValidationState_t& _, const Instruction* inst,
return SPV_SUCCESS;
}
spv_result_t ValidateInt32Or64Operand(ValidationState_t& _,
const Instruction* inst,
uint32_t operand_index,
const char* opcode_name,
const char* operand_name) {
const auto type_id =
_.FindDef(inst->GetOperandAs<uint32_t>(operand_index))->type_id();
if (!_.IsIntScalarType(type_id) ||
!(_.GetBitWidth(type_id) == 32 || _.GetBitWidth(type_id) == 64)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< opcode_name << " " << operand_name << " type <id> "
<< _.getIdName(type_id) << " is not a 32 or 64 bit integer.";
}
return SPV_SUCCESS;
}
spv_result_t ValidateCooperativeVectorPointer(ValidationState_t& _,
const Instruction* inst,
const char* opname,
@@ -2651,11 +2784,19 @@ spv_result_t ValidateCooperativeVectorLoadStoreNV(ValidationState_t& _,
const auto pointer_index =
(inst->opcode() == spv::Op::OpCooperativeVectorLoadNV) ? 2u : 0u;
const auto offset_index =
(inst->opcode() == spv::Op::OpCooperativeVectorLoadNV) ? 3u : 1u;
if (auto error =
ValidateCooperativeVectorPointer(_, inst, opname, pointer_index)) {
return error;
}
if (auto error =
ValidateInt32Or64Operand(_, inst, offset_index, opname, "Offset")) {
return error;
}
const auto memory_access_index =
(inst->opcode() == spv::Op::OpCooperativeVectorLoadNV) ? 4u : 3u;
if (inst->operands().size() > memory_access_index) {
@@ -2705,7 +2846,8 @@ spv_result_t ValidateCooperativeVectorOuterProductNV(ValidationState_t& _,
<< _.getIdName(b_component_type_id) << " do not match.";
}
if (auto error = ValidateInt32Operand(_, inst, 1, opcode_name, "Offset")) {
if (auto error =
ValidateInt32Or64Operand(_, inst, 1, opcode_name, "Offset")) {
return error;
}
@@ -2748,7 +2890,8 @@ spv_result_t ValidateCooperativeVectorReduceSumNV(ValidationState_t& _,
<< " is not a cooperative vector type.";
}
if (auto error = ValidateInt32Operand(_, inst, 1, opcode_name, "Offset")) {
if (auto error =
ValidateInt32Or64Operand(_, inst, 1, opcode_name, "Offset")) {
return error;
}
@@ -2781,8 +2924,10 @@ spv_result_t ValidateCooperativeVectorMatrixMulNV(ValidationState_t& _,
const auto input_index = 2u;
const auto input_interpretation_index = 3u;
const auto matrix_index = 4u;
const auto matrix_offset_index = 5u;
const auto matrix_interpretation_index = 6u;
const auto bias_index = 7u;
const auto bias_offset_index = 8u;
const auto bias_interpretation_index = 9u;
const auto m_index = 7u + bias_offset;
const auto k_index = 8u + bias_offset;
@@ -2930,15 +3075,33 @@ spv_result_t ValidateCooperativeVectorMatrixMulNV(ValidationState_t& _,
return error;
}
if (auto error = ValidateInt32Or64Operand(_, inst, matrix_offset_index,
opcode_name, "MatrixOffset")) {
return error;
}
if (has_bias) {
if (auto error = ValidateInt32Or64Operand(_, inst, bias_offset_index,
opcode_name, "BiasOffset")) {
return error;
}
}
return SPV_SUCCESS;
}
spv_result_t ValidatePtrComparison(ValidationState_t& _,
const Instruction* inst) {
if (_.addressing_model() == spv::AddressingModel::Logical &&
const auto op1 = _.FindDef(inst->GetOperandAs<uint32_t>(2u));
const auto op2 = _.FindDef(inst->GetOperandAs<uint32_t>(3u));
const auto op1_type = _.FindDef(op1->type_id());
const auto op2_type = _.FindDef(op2->type_id());
spv::StorageClass sc = op1_type->GetOperandAs<spv::StorageClass>(1u);
if ((_.addressing_model() == spv::AddressingModel::Logical ||
_.addressing_model() == spv::AddressingModel::PhysicalStorageBuffer64) &&
sc != spv::StorageClass::PhysicalStorageBuffer &&
!_.features().variable_pointers) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Instruction cannot for logical addressing model be used without "
<< "Instruction on logical pointers cannot be used without "
"a variable pointers capability";
}
@@ -2955,10 +3118,6 @@ spv_result_t ValidatePtrComparison(ValidationState_t& _,
}
}
const auto op1 = _.FindDef(inst->GetOperandAs<uint32_t>(2u));
const auto op2 = _.FindDef(inst->GetOperandAs<uint32_t>(3u));
const auto op1_type = _.FindDef(op1->type_id());
const auto op2_type = _.FindDef(op2->type_id());
if (!op1_type || (op1_type->opcode() != spv::Op::OpTypePointer &&
op1_type->opcode() != spv::Op::OpTypeUntypedPointerKHR)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
@@ -2993,7 +3152,6 @@ spv_result_t ValidatePtrComparison(ValidationState_t& _,
}
}
spv::StorageClass sc = op1_type->GetOperandAs<spv::StorageClass>(1u);
if (_.addressing_model() == spv::AddressingModel::Logical) {
if (sc != spv::StorageClass::Workgroup &&
sc != spv::StorageClass::StorageBuffer) {

View File

@@ -315,9 +315,7 @@ spv_result_t ValidateEntryPoint(ValidationState_t& _, const Instruction* inst) {
}
if (!ok) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< (_.HasCapability(spv::Capability::TileShadingQCOM)
? _.VkErrorID(10685)
: _.VkErrorID(6426))
<< _.VkErrorID(10685)
<< "In the Vulkan environment, GLCompute execution model "
"entry points require either the "
<< (_.HasCapability(spv::Capability::TileShadingQCOM)

View File

@@ -12,7 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// Validates ray tracing instructions from SPV_NV_shader_execution_reorder
// Validates ray tracing instructions from SPV_NV_shader_invocation_reorder and
// SPV_EXT_shader_invocation_reorder
#include "source/opcode.h"
#include "source/val/instruction.h"
@@ -37,18 +38,29 @@ uint32_t GetArrayLength(ValidationState_t& _, const Instruction* array_type) {
return array_length;
}
spv_result_t ValidateRayQueryPointer(ValidationState_t& _,
const Instruction* inst,
uint32_t ray_query_index) {
const uint32_t ray_query_id = inst->GetOperandAs<uint32_t>(ray_query_index);
auto variable = _.FindDef(ray_query_id);
auto pointer = _.FindDef(variable->GetOperandAs<uint32_t>(0));
if (!pointer || pointer->opcode() != spv::Op::OpTypePointer) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Ray Query must be a pointer";
}
auto type = _.FindDef(pointer->GetOperandAs<uint32_t>(2));
if (!type || type->opcode() != spv::Op::OpTypeRayQueryKHR) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Ray Query must be a pointer to OpTypeRayQueryKHR";
}
return SPV_SUCCESS;
}
spv_result_t ValidateHitObjectPointer(ValidationState_t& _,
const Instruction* inst,
uint32_t hit_object_index) {
const uint32_t hit_object_id = inst->GetOperandAs<uint32_t>(hit_object_index);
auto variable = _.FindDef(hit_object_id);
const auto var_opcode = variable->opcode();
if (!variable || (var_opcode != spv::Op::OpVariable &&
var_opcode != spv::Op::OpFunctionParameter &&
var_opcode != spv::Op::OpAccessChain)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Hit Object must be a memory object declaration";
}
auto pointer = _.FindDef(variable->GetOperandAs<uint32_t>(0));
if (!pointer || pointer->opcode() != spv::Op::OpTypePointer) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
@@ -62,6 +74,24 @@ spv_result_t ValidateHitObjectPointer(ValidationState_t& _,
return SPV_SUCCESS;
}
spv_result_t ValidateHitObjectPointerEXT(ValidationState_t& _,
const Instruction* inst,
uint32_t hit_object_index) {
const uint32_t hit_object_id = inst->GetOperandAs<uint32_t>(hit_object_index);
auto variable = _.FindDef(hit_object_id);
auto pointer = _.FindDef(variable->GetOperandAs<uint32_t>(0));
if (!pointer || pointer->opcode() != spv::Op::OpTypePointer) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Hit Object must be a pointer";
}
auto type = _.FindDef(pointer->GetOperandAs<uint32_t>(2));
if (!type || type->opcode() != spv::Op::OpTypeHitObjectEXT) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Type must be OpTypeHitObjectEXT";
}
return SPV_SUCCESS;
}
spv_result_t ValidateHitObjectInstructionCommonParameters(
ValidationState_t& _, const Instruction* inst,
uint32_t acceleration_struct_index, uint32_t instance_id_index,
@@ -247,8 +277,10 @@ spv_result_t ValidateHitObjectInstructionCommonParameters(
auto variable = _.FindDef(hit_object_attr_id);
const auto var_opcode = variable->opcode();
if (!variable || var_opcode != spv::Op::OpVariable ||
(variable->GetOperandAs<spv::StorageClass>(2)) !=
spv::StorageClass::HitObjectAttributeNV) {
!((variable->GetOperandAs<spv::StorageClass>(2) ==
spv::StorageClass::HitObjectAttributeNV) ||
(variable->GetOperandAs<spv::StorageClass>(2) ==
spv::StorageClass::HitObjectAttributeEXT))) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Hit Object Attributes id must be a OpVariable of storage "
"class HitObjectAttributeNV";
@@ -728,5 +760,651 @@ spv_result_t RayReorderNVPass(ValidationState_t& _, const Instruction* inst) {
}
return SPV_SUCCESS;
}
spv_result_t RayReorderEXTPass(ValidationState_t& _, const Instruction* inst) {
const spv::Op opcode = inst->opcode();
const uint32_t result_type = inst->type_id();
auto RegisterOpcodeForValidModel = [](ValidationState_t& vs,
const Instruction* rtinst) {
std::string opcode_name = spvOpcodeString(rtinst->opcode());
vs.function(rtinst->function()->id())
->RegisterExecutionModelLimitation(
[opcode_name](spv::ExecutionModel model, std::string* message) {
if (model != spv::ExecutionModel::RayGenerationKHR &&
model != spv::ExecutionModel::ClosestHitKHR &&
model != spv::ExecutionModel::MissKHR) {
if (message) {
*message = opcode_name +
" requires RayGenerationKHR, ClosestHitKHR and "
"MissKHR execution models";
}
return false;
}
return true;
});
return;
};
switch (opcode) {
case spv::Op::OpHitObjectIsMissEXT:
case spv::Op::OpHitObjectIsHitEXT:
case spv::Op::OpHitObjectIsEmptyEXT: {
RegisterOpcodeForValidModel(_, inst);
if (!_.IsBoolScalarType(result_type)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "expected Result Type to be bool scalar type";
}
if (auto error = ValidateHitObjectPointerEXT(_, inst, 2)) return error;
break;
}
case spv::Op::OpHitObjectGetShaderRecordBufferHandleEXT: {
RegisterOpcodeForValidModel(_, inst);
if (auto error = ValidateHitObjectPointerEXT(_, inst, 2)) return error;
if (!_.IsIntVectorType(result_type) ||
(_.GetDimension(result_type) != 2) ||
(_.GetBitWidth(result_type) != 32))
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected 32-bit integer type 2-component vector as Result "
"Type: "
<< spvOpcodeString(opcode);
break;
}
case spv::Op::OpHitObjectGetHitKindEXT:
case spv::Op::OpHitObjectGetPrimitiveIndexEXT:
case spv::Op::OpHitObjectGetGeometryIndexEXT:
case spv::Op::OpHitObjectGetInstanceIdEXT:
case spv::Op::OpHitObjectGetInstanceCustomIndexEXT:
case spv::Op::OpHitObjectGetShaderBindingTableRecordIndexEXT:
case spv::Op::OpHitObjectGetRayFlagsEXT: {
RegisterOpcodeForValidModel(_, inst);
if (auto error = ValidateHitObjectPointerEXT(_, inst, 2)) return error;
if (!_.IsIntScalarType(result_type) || _.GetBitWidth(result_type) != 32)
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected 32-bit integer type scalar as Result Type: "
<< spvOpcodeString(opcode);
break;
}
case spv::Op::OpHitObjectGetCurrentTimeEXT:
case spv::Op::OpHitObjectGetRayTMaxEXT:
case spv::Op::OpHitObjectGetRayTMinEXT: {
RegisterOpcodeForValidModel(_, inst);
if (auto error = ValidateHitObjectPointerEXT(_, inst, 2)) return error;
if (!_.IsFloatScalarType(result_type) || _.GetBitWidth(result_type) != 32)
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected 32-bit floating-point type scalar as Result Type: "
<< spvOpcodeString(opcode);
break;
}
case spv::Op::OpHitObjectGetObjectToWorldEXT:
case spv::Op::OpHitObjectGetWorldToObjectEXT: {
RegisterOpcodeForValidModel(_, inst);
if (auto error = ValidateHitObjectPointerEXT(_, inst, 2)) return error;
uint32_t num_rows = 0;
uint32_t num_cols = 0;
uint32_t col_type = 0;
uint32_t component_type = 0;
if (!_.GetMatrixTypeInfo(result_type, &num_rows, &num_cols, &col_type,
&component_type)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "expected matrix type as Result Type: "
<< spvOpcodeString(opcode);
}
if (num_cols != 4) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "expected Result Type matrix to have a Column Count of 4"
<< spvOpcodeString(opcode);
}
if (!_.IsFloatScalarType(component_type) ||
_.GetBitWidth(result_type) != 32 || num_rows != 3) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "expected Result Type matrix to have a Column Type of "
"3-component 32-bit float vectors: "
<< spvOpcodeString(opcode);
}
break;
}
case spv::Op::OpHitObjectGetObjectRayOriginEXT:
case spv::Op::OpHitObjectGetObjectRayDirectionEXT:
case spv::Op::OpHitObjectGetWorldRayDirectionEXT:
case spv::Op::OpHitObjectGetWorldRayOriginEXT: {
RegisterOpcodeForValidModel(_, inst);
if (auto error = ValidateHitObjectPointerEXT(_, inst, 2)) return error;
if (!_.IsFloatVectorType(result_type) ||
(_.GetDimension(result_type) != 3) ||
(_.GetBitWidth(result_type) != 32))
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected 32-bit floating-point type 3-component vector as "
"Result Type: "
<< spvOpcodeString(opcode);
break;
}
case spv::Op::OpHitObjectGetIntersectionTriangleVertexPositionsEXT: {
RegisterOpcodeForValidModel(_, inst);
if (auto error = ValidateHitObjectPointerEXT(_, inst, 2)) 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 "
"vectors as Result Type: "
<< spvOpcodeString(opcode);
}
break;
}
case spv::Op::OpHitObjectGetAttributesEXT: {
RegisterOpcodeForValidModel(_, inst);
if (auto error = ValidateHitObjectPointerEXT(_, inst, 0)) return error;
const uint32_t hit_object_attr_id = inst->GetOperandAs<uint32_t>(1);
auto variable = _.FindDef(hit_object_attr_id);
const auto var_opcode = variable->opcode();
if (!variable || var_opcode != spv::Op::OpVariable ||
variable->GetOperandAs<spv::StorageClass>(2) !=
spv::StorageClass::HitObjectAttributeEXT) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Hit Object Attributes id must be a OpVariable of storage "
"class HitObjectAttributeEXT";
}
break;
}
case spv::Op::OpHitObjectSetShaderBindingTableRecordIndexEXT: {
RegisterOpcodeForValidModel(_, inst);
if (auto error = ValidateHitObjectPointerEXT(_, inst, 0)) return error;
const uint32_t sbt_index_id = _.GetOperandTypeId(inst, 1);
if (!_.IsIntScalarType(sbt_index_id) ||
_.GetBitWidth(sbt_index_id) != 32) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "SBT Index must be a 32-bit integer scalar";
}
break;
}
case spv::Op::OpHitObjectExecuteShaderEXT: {
RegisterOpcodeForValidModel(_, inst);
if (auto error = ValidateHitObjectPointerEXT(_, inst, 0)) return error;
const uint32_t payload_id = inst->GetOperandAs<uint32_t>(1);
auto variable = _.FindDef(payload_id);
const auto var_opcode = variable->opcode();
if (!variable || var_opcode != spv::Op::OpVariable ||
(variable->GetOperandAs<spv::StorageClass>(2) !=
spv::StorageClass::RayPayloadKHR &&
variable->GetOperandAs<spv::StorageClass>(2) !=
spv::StorageClass::IncomingRayPayloadKHR)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Payload must be a OpVariable of storage "
"class RayPayloadKHR or IncomingRayPayloadKHR";
}
break;
}
case spv::Op::OpHitObjectRecordEmptyEXT: {
RegisterOpcodeForValidModel(_, inst);
if (auto error = ValidateHitObjectPointerEXT(_, inst, 0)) return error;
break;
}
case spv::Op::OpHitObjectRecordFromQueryEXT: {
RegisterOpcodeForValidModel(_, inst);
if (auto error = ValidateHitObjectPointerEXT(_, inst, 0)) return error;
if (auto error = ValidateRayQueryPointer(_, inst, 1)) return error;
if (!_.HasCapability(spv::Capability::RayQueryKHR))
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< spvOpcodeString(opcode)
<< ": requires RayQueryKHR capability";
// Validate SBT Record Index (operand 2)
const uint32_t sbt_record_index_id = _.GetOperandTypeId(inst, 2);
if (!_.IsIntScalarType(sbt_record_index_id) ||
_.GetBitWidth(sbt_record_index_id) != 32) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "SBT Record Index must be a 32-bit integer scalar";
}
// Validate Hit Object Attributes (operand 3)
const uint32_t hit_object_attr_id = inst->GetOperandAs<uint32_t>(3);
auto attr_variable = _.FindDef(hit_object_attr_id);
const auto attr_var_opcode = attr_variable->opcode();
if (!attr_variable || attr_var_opcode != spv::Op::OpVariable ||
attr_variable->GetOperandAs<spv::StorageClass>(2) !=
spv::StorageClass::HitObjectAttributeEXT) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Hit Object Attributes id must be a OpVariable of storage "
"class HitObjectAttributeEXT";
}
break;
}
case spv::Op::OpHitObjectRecordMissEXT: {
RegisterOpcodeForValidModel(_, inst);
if (auto error = ValidateHitObjectPointerEXT(_, inst, 0)) return error;
// Ray Flags (operand 1)
const uint32_t ray_flags_id = _.GetOperandTypeId(inst, 1);
if (!_.IsIntScalarType(ray_flags_id) ||
_.GetBitWidth(ray_flags_id) != 32) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Ray Flags must be a 32-bit int scalar";
}
// Miss Index (operand 2)
const uint32_t miss_index = _.GetOperandTypeId(inst, 2);
if (!_.IsUnsignedIntScalarType(miss_index) ||
_.GetBitWidth(miss_index) != 32) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Miss Index must be a 32-bit unsigned int scalar";
}
// Ray Origin (operand 3)
const uint32_t ray_origin = _.GetOperandTypeId(inst, 3);
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";
}
// Ray TMin (operand 4)
const uint32_t ray_tmin = _.GetOperandTypeId(inst, 4);
if (!_.IsFloatScalarType(ray_tmin) || _.GetBitWidth(ray_tmin) != 32) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Ray TMin must be a 32-bit float scalar";
}
// Ray Direction (operand 5)
const uint32_t ray_direction = _.GetOperandTypeId(inst, 5);
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";
}
// Ray TMax (operand 6)
const uint32_t ray_tmax = _.GetOperandTypeId(inst, 6);
if (!_.IsFloatScalarType(ray_tmax) || _.GetBitWidth(ray_tmax) != 32) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Ray TMax must be a 32-bit float scalar";
}
break;
}
case spv::Op::OpHitObjectRecordMissMotionEXT: {
RegisterOpcodeForValidModel(_, inst);
if (auto error = ValidateHitObjectPointerEXT(_, inst, 0)) return error;
// Ray Flags (operand 1)
const uint32_t ray_flags_id = _.GetOperandTypeId(inst, 1);
if (!_.IsIntScalarType(ray_flags_id) ||
_.GetBitWidth(ray_flags_id) != 32) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Ray Flags must be a 32-bit int scalar";
}
// Miss Index (operand 2)
const uint32_t miss_index = _.GetOperandTypeId(inst, 2);
if (!_.IsUnsignedIntScalarType(miss_index) ||
_.GetBitWidth(miss_index) != 32) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Miss Index must be a 32-bit unsigned int scalar";
}
// Ray Origin (operand 3)
const uint32_t ray_origin = _.GetOperandTypeId(inst, 3);
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";
}
// Ray TMin (operand 4)
const uint32_t ray_tmin = _.GetOperandTypeId(inst, 4);
if (!_.IsFloatScalarType(ray_tmin) || _.GetBitWidth(ray_tmin) != 32) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Ray TMin must be a 32-bit float scalar";
}
// Ray Direction (operand 5)
const uint32_t ray_direction = _.GetOperandTypeId(inst, 5);
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";
}
// Ray TMax (operand 6)
const uint32_t ray_tmax = _.GetOperandTypeId(inst, 6);
if (!_.IsFloatScalarType(ray_tmax) || _.GetBitWidth(ray_tmax) != 32) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Ray TMax must be a 32-bit float scalar";
}
// Current Time (operand 7)
const uint32_t current_time_id = _.GetOperandTypeId(inst, 7);
if (!_.IsFloatScalarType(current_time_id) ||
_.GetBitWidth(current_time_id) != 32) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Current Time must be a 32-bit float scalar";
}
break;
}
case spv::Op::OpReorderThreadWithHintEXT: {
std::string opcode_name = spvOpcodeString(inst->opcode());
_.function(inst->function()->id())
->RegisterExecutionModelLimitation(
[opcode_name](spv::ExecutionModel model, std::string* message) {
if (model != spv::ExecutionModel::RayGenerationKHR) {
if (message) {
*message = opcode_name +
" requires RayGenerationKHR execution model";
}
return false;
}
return true;
});
const uint32_t hint_id = _.GetOperandTypeId(inst, 0);
if (!_.IsIntScalarType(hint_id) || _.GetBitWidth(hint_id) != 32) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Hint must be a 32-bit int scalar";
}
const uint32_t bits_id = _.GetOperandTypeId(inst, 1);
if (!_.IsIntScalarType(bits_id) || _.GetBitWidth(bits_id) != 32) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Bits must be a 32-bit int scalar";
}
break;
}
case spv::Op::OpReorderThreadWithHitObjectEXT: {
std::string opcode_name = spvOpcodeString(inst->opcode());
_.function(inst->function()->id())
->RegisterExecutionModelLimitation(
[opcode_name](spv::ExecutionModel model, std::string* message) {
if (model != spv::ExecutionModel::RayGenerationKHR) {
if (message) {
*message = opcode_name +
" requires RayGenerationKHR execution model";
}
return false;
}
return true;
});
if (auto error = ValidateHitObjectPointerEXT(_, inst, 0)) return error;
if (inst->operands().size() > 1) {
if (inst->operands().size() != 3) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Hint and Bits are optional together i.e "
<< " Either both Hint and Bits should be provided or neither.";
}
// Validate the optional operands Hint and Bits
const uint32_t hint_id = _.GetOperandTypeId(inst, 1);
if (!_.IsIntScalarType(hint_id) || _.GetBitWidth(hint_id) != 32) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Hint must be a 32-bit int scalar";
}
const uint32_t bits_id = _.GetOperandTypeId(inst, 2);
if (!_.IsIntScalarType(bits_id) || _.GetBitWidth(bits_id) != 32) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Bits must be a 32-bit int scalar";
}
}
break;
}
case spv::Op::OpHitObjectTraceRayEXT: {
RegisterOpcodeForValidModel(_, inst);
if (auto error = ValidateHitObjectPointerEXT(_, inst, 0)) return error;
if (auto error = ValidateHitObjectInstructionCommonParameters(
_, inst, 1 /* Acceleration Struct */,
KRayParamInvalidId /* Instance Id */,
KRayParamInvalidId /* Primitive Id */,
KRayParamInvalidId /* Geometry Index */, 2 /* Ray Flags */,
3 /* Cull Mask */, KRayParamInvalidId /* Hit Kind*/,
KRayParamInvalidId /* SBT index */, 4 /* SBT Offset */,
5 /* SBT Stride */, KRayParamInvalidId /* SBT Record Offset */,
KRayParamInvalidId /* SBT Record Stride */, 6 /* Miss Index */,
7 /* Ray Origin */, 8 /* Ray TMin */, 9 /* Ray Direction */,
10 /* Ray TMax */, 11 /* Payload */,
KRayParamInvalidId /* Hit Object Attribute */))
return error;
break;
}
case spv::Op::OpHitObjectTraceRayMotionEXT: {
RegisterOpcodeForValidModel(_, inst);
if (auto error = ValidateHitObjectPointerEXT(_, inst, 0)) return error;
if (auto error = ValidateHitObjectInstructionCommonParameters(
_, inst, 1 /* Acceleration Struct */,
KRayParamInvalidId /* Instance Id */,
KRayParamInvalidId /* Primitive Id */,
KRayParamInvalidId /* Geometry Index */, 2 /* Ray Flags */,
3 /* Cull Mask */, KRayParamInvalidId /* Hit Kind*/,
KRayParamInvalidId /* SBT index */, 4 /* SBT Offset */,
5 /* SBT Stride */, KRayParamInvalidId /* SBT Record Offset */,
KRayParamInvalidId /* SBT Record Stride */, 6 /* Miss Index */,
7 /* Ray Origin */, 8 /* Ray TMin */, 9 /* Ray Direction */,
10 /* Ray TMax */, 12 /* Payload */,
KRayParamInvalidId /* Hit Object Attribute */))
return error;
// Current Time (operand 11)
const uint32_t current_time_id = _.GetOperandTypeId(inst, 11);
if (!_.IsFloatScalarType(current_time_id) ||
_.GetBitWidth(current_time_id) != 32) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Current Time must be a 32-bit float scalar";
}
break;
}
case spv::Op::OpHitObjectReorderExecuteShaderEXT: {
std::string opcode_name = spvOpcodeString(inst->opcode());
_.function(inst->function()->id())
->RegisterExecutionModelLimitation(
[opcode_name](spv::ExecutionModel model, std::string* message) {
if (model != spv::ExecutionModel::RayGenerationKHR) {
if (message) {
*message = opcode_name +
" requires RayGenerationKHR execution model";
}
return false;
}
return true;
});
if (auto error = ValidateHitObjectPointerEXT(_, inst, 0)) return error;
// Validate Payload (operand 1)
const uint32_t payload_id = inst->GetOperandAs<uint32_t>(1);
auto variable = _.FindDef(payload_id);
const auto var_opcode = variable->opcode();
if (!variable || var_opcode != spv::Op::OpVariable ||
(variable->GetOperandAs<spv::StorageClass>(2) !=
spv::StorageClass::RayPayloadKHR &&
variable->GetOperandAs<spv::StorageClass>(2) !=
spv::StorageClass::IncomingRayPayloadKHR)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Payload must be a OpVariable of storage "
"class RayPayloadKHR or IncomingRayPayloadKHR";
}
// Check for optional Hint and Bits (operands 2 and 3)
if (inst->operands().size() > 2) {
if (inst->operands().size() != 4) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Hint and Bits are optional together i.e "
<< " Either both Hint and Bits should be provided or neither.";
}
// Validate optional Hint and Bits
const uint32_t hint_id = _.GetOperandTypeId(inst, 2);
if (!_.IsIntScalarType(hint_id) || _.GetBitWidth(hint_id) != 32) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Hint must be a 32-bit int scalar";
}
const uint32_t bits_id = _.GetOperandTypeId(inst, 3);
if (!_.IsIntScalarType(bits_id) || _.GetBitWidth(bits_id) != 32) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Bits must be a 32-bit int scalar";
}
}
break;
}
case spv::Op::OpHitObjectTraceReorderExecuteEXT: {
std::string opcode_name = spvOpcodeString(inst->opcode());
_.function(inst->function()->id())
->RegisterExecutionModelLimitation(
[opcode_name](spv::ExecutionModel model, std::string* message) {
if (model != spv::ExecutionModel::RayGenerationKHR) {
if (message) {
*message = opcode_name +
" requires RayGenerationKHR execution model";
}
return false;
}
return true;
});
if (auto error = ValidateHitObjectPointerEXT(_, inst, 0)) return error;
// Validate base trace ray parameters (operands 1-11)
if (auto error = ValidateHitObjectInstructionCommonParameters(
_, inst, 1 /* Acceleration Struct */,
KRayParamInvalidId /* Instance Id */,
KRayParamInvalidId /* Primitive Id */,
KRayParamInvalidId /* Geometry Index */, 2 /* Ray Flags */,
3 /* Cull Mask */, KRayParamInvalidId /* Hit Kind*/,
KRayParamInvalidId /* SBT index */, 4 /* SBT Offset */,
5 /* SBT Stride */, KRayParamInvalidId /* SBT Record Offset */,
KRayParamInvalidId /* SBT Record Stride */, 6 /* Miss Index */,
7 /* Ray Origin */, 8 /* Ray TMin */, 9 /* Ray Direction */,
10 /* Ray TMax */, 11 /* Payload */,
KRayParamInvalidId /* Hit Object Attribute */))
return error;
// Check for optional Hint and Bits (operands 12 and 13)
if (inst->operands().size() > 12) {
if (inst->operands().size() != 14) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Hint and Bits are optional together i.e "
<< " Either both Hint and Bits should be provided or neither.";
}
// Validate optional Hint and Bits
const uint32_t hint_id = _.GetOperandTypeId(inst, 12);
if (!_.IsIntScalarType(hint_id) || _.GetBitWidth(hint_id) != 32) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Hint must be a 32-bit int scalar";
}
const uint32_t bits_id = _.GetOperandTypeId(inst, 13);
if (!_.IsIntScalarType(bits_id) || _.GetBitWidth(bits_id) != 32) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Bits must be a 32-bit int scalar";
}
}
break;
}
case spv::Op::OpHitObjectTraceMotionReorderExecuteEXT: {
std::string opcode_name = spvOpcodeString(inst->opcode());
_.function(inst->function()->id())
->RegisterExecutionModelLimitation(
[opcode_name](spv::ExecutionModel model, std::string* message) {
if (model != spv::ExecutionModel::RayGenerationKHR) {
if (message) {
*message = opcode_name +
" requires RayGenerationKHR execution model";
}
return false;
}
return true;
});
if (auto error = ValidateHitObjectPointerEXT(_, inst, 0)) return error;
// Validate base trace ray parameters (operands 1-12)
if (auto error = ValidateHitObjectInstructionCommonParameters(
_, inst, 1 /* Acceleration Struct */,
KRayParamInvalidId /* Instance Id */,
KRayParamInvalidId /* Primitive Id */,
KRayParamInvalidId /* Geometry Index */, 2 /* Ray Flags */,
3 /* Cull Mask */, KRayParamInvalidId /* Hit Kind*/,
KRayParamInvalidId /* SBT index */, 4 /* SBT Offset */,
5 /* SBT Stride */, KRayParamInvalidId /* SBT Record Offset */,
KRayParamInvalidId /* SBT Record Stride */, 6 /* Miss Index */,
7 /* Ray Origin */, 8 /* Ray TMin */, 9 /* Ray Direction */,
10 /* Ray TMax */, 12 /* Payload */,
KRayParamInvalidId /* Hit Object Attribute */))
return error;
// Current Time (operand 11)
const uint32_t current_time_id = _.GetOperandTypeId(inst, 11);
if (!_.IsFloatScalarType(current_time_id) ||
_.GetBitWidth(current_time_id) != 32) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Current Time must be a 32-bit float scalar";
}
// Check for optional Hint and Bits (operands 13 and 14)
if (inst->operands().size() > 13) {
if (inst->operands().size() != 15) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Hint and Bits are optional together i.e "
<< " Either both Hint and Bits should be provided or neither.";
}
// Validate optional Hint and Bits
const uint32_t hint_id = _.GetOperandTypeId(inst, 13);
if (!_.IsIntScalarType(hint_id) || _.GetBitWidth(hint_id) != 32) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Hint must be a 32-bit int scalar";
}
const uint32_t bits_id = _.GetOperandTypeId(inst, 14);
if (!_.IsIntScalarType(bits_id) || _.GetBitWidth(bits_id) != 32) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Bits must be a 32-bit int scalar";
}
}
break;
}
default:
break;
}
return SPV_SUCCESS;
}
} // namespace val
} // namespace spvtools

View File

@@ -859,6 +859,22 @@ void ValidationState_t::RegisterStorageClassConsumer(
}
return true;
});
} else if (storage_class == spv::StorageClass::HitObjectAttributeEXT) {
function(consumer->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 =
"HitObjectAttributeEXT Storage Class is limited to "
"RayGenerationKHR, ClosestHitKHR or MissKHR execution model";
}
return false;
}
return true;
});
}
}
@@ -2032,6 +2048,7 @@ bool ValidationState_t::IsValidStorageClass(
case spv::StorageClass::ShaderRecordBufferKHR:
case spv::StorageClass::TaskPayloadWorkgroupEXT:
case spv::StorageClass::HitObjectAttributeNV:
case spv::StorageClass::HitObjectAttributeEXT:
case spv::StorageClass::TileImageEXT:
case spv::StorageClass::NodePayloadAMDX:
case spv::StorageClass::TileAttachmentQCOM:
@@ -2254,6 +2271,12 @@ std::string ValidationState_t::VkErrorID(uint32_t id,
return VUID_WRAP(VUID-LocalInvocationId-LocalInvocationId-04282);
case 4283:
return VUID_WRAP(VUID-LocalInvocationId-LocalInvocationId-04283);
case 4284:
return VUID_WRAP(VUID-LocalInvocationIndex-LocalInvocationIndex-04284);
case 4285:
return VUID_WRAP(VUID-LocalInvocationIndex-LocalInvocationIndex-04285);
case 4286:
return VUID_WRAP(VUID-LocalInvocationIndex-LocalInvocationIndex-04286);
case 4293:
return VUID_WRAP(VUID-NumSubgroups-NumSubgroups-04293);
case 4294:
@@ -2538,8 +2561,6 @@ std::string ValidationState_t::VkErrorID(uint32_t id,
return VUID_WRAP(VUID-StandaloneSpirv-OpTypeRuntimeArray-04680);
case 4682:
return VUID_WRAP(VUID-StandaloneSpirv-OpControlBarrier-04682);
case 6426:
return VUID_WRAP(VUID-StandaloneSpirv-LocalSize-06426); // formally 04683
case 4685:
return VUID_WRAP(VUID-StandaloneSpirv-OpGroupNonUniformBallotBitCount-04685);
case 4686:
@@ -2752,7 +2773,7 @@ std::string ValidationState_t::VkErrorID(uint32_t id,
case 10684:
return VUID_WRAP(VUID-StandaloneSpirv-None-10684);
case 10685:
return VUID_WRAP(VUID-StandaloneSpirv-None-10685);
return VUID_WRAP(VUID-StandaloneSpirv-None-10685); // formally 04683/06426
case 10824:
// This use to be a standalone, but maintenance9 will set allow_vulkan_32_bit_bitwise now
return VUID_WRAP(VUID-RuntimeSpirv-None-10824);
@@ -2790,6 +2811,8 @@ std::string ValidationState_t::VkErrorID(uint32_t id,
return VUID_WRAP(VUID-StandaloneSpirv-TessLevelInner-10880);
case 11167:
return VUID_WRAP(VUID-StandaloneSpirv-OpUntypedVariableKHR-11167);
case 11805:
return VUID_WRAP(VUID-StandaloneSpirv-OpArrayLength-11805);
default:
return ""; // unknown id
}