Validate NonWritable decoration (#2263)
Also permit NonWritable on members of structs used for UBO and SSBO.
(That seems inadvertently removed in recent revisions of the spec.)
diff --git a/source/val/validate_decorations.cpp b/source/val/validate_decorations.cpp
index 164a5dd..f0ad177 100644
--- a/source/val/validate_decorations.cpp
+++ b/source/val/validate_decorations.cpp
@@ -950,6 +950,14 @@
const bool bufferRules =
(uniform && bufferDeco) || (push_constant && blockDeco) ||
((storage_buffer || phys_storage_buffer) && blockDeco);
+ if (uniform && blockDeco) {
+ vstate.RegisterPointerToUniformBlock(ptrInst->id());
+ vstate.RegisterStructForUniformBlock(id);
+ }
+ if ((uniform && bufferDeco) || (storage_buffer && blockDeco)) {
+ vstate.RegisterPointerToStorageBuffer(ptrInst->id());
+ vstate.RegisterStructForStorageBuffer(id);
+ }
if (blockRules || bufferRules) {
const char* deco_str = blockDeco ? "Block" : "BufferBlock";
spv_result_t recursive_status = SPV_SUCCESS;
@@ -1219,6 +1227,49 @@
return SPV_SUCCESS;
}
+// Returns SPV_SUCCESS if validation rules are satisfied for the NonWritable
+// decoration. Otherwise emits a diagnostic and returns something other than
+// SPV_SUCCESS. The |inst| parameter is the object being decorated. This must
+// be called after TypePass and AnnotateCheckDecorationsOfBuffers are called.
+spv_result_t CheckNonWritableDecoration(ValidationState_t& vstate,
+ const Instruction& inst,
+ const Decoration& decoration) {
+ assert(inst.id() && "Parser ensures the target of the decoration has an ID");
+
+ if (decoration.struct_member_index() == Decoration::kInvalidMember) {
+ // The target must be a memory object declaration.
+ // First, it must be a variable or function parameter.
+ if (inst.opcode() != SpvOpVariable &&
+ inst.opcode() != SpvOpFunctionParameter) {
+ return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
+ << "Target of NonWritable decoration must be a memory object "
+ "declaration (a variable or a function parameter)";
+ }
+ // Second, it must point to a UBO, SSBO, or storage image.
+ const auto type_id = inst.type_id();
+ if (!vstate.IsPointerToUniformBlock(type_id) &&
+ !vstate.IsPointerToStorageBuffer(type_id) &&
+ !vstate.IsPointerToStorageImage(type_id)) {
+ return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
+ << "Target of NonWritable decoration is invalid: must point to a "
+ "storage image, uniform block, or storage buffer";
+ }
+ } else {
+ // The target is a struct member. The annotations pass already checks that
+ // it is a structure. So now fall through to ensure that the structure is
+ // used as a UBO or SSBO.
+ const auto type_id = inst.id();
+ if (!vstate.IsStructForUniformBlock(type_id) &&
+ !vstate.IsStructForStorageBuffer(type_id)) {
+ return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
+ << "Target of NonWritable member decoration is invalid: must be "
+ "the struct type of a uniform block or storage buffer";
+ }
+ }
+
+ return SPV_SUCCESS;
+}
+
// Returns SPV_SUCCESS if validation rules are satisfied for Uniform
// decorations. Otherwise emits a diagnostic and returns something other than
// SPV_SUCCESS. Assumes each decoration on a group has been propagated down to
@@ -1314,6 +1365,9 @@
if (is_shader)
PASS_OR_BAIL(CheckFPRoundingModeForShaders(vstate, *inst));
break;
+ case SpvDecorationNonWritable:
+ PASS_OR_BAIL(CheckNonWritableDecoration(vstate, *inst, decoration));
+ break;
case SpvDecorationUniform:
PASS_OR_BAIL(CheckUniformDecoration(vstate, *inst, decoration));
break;
diff --git a/source/val/validate_type.cpp b/source/val/validate_type.cpp
index 7669f72..30fc2fb 100644
--- a/source/val/validate_type.cpp
+++ b/source/val/validate_type.cpp
@@ -269,6 +269,16 @@
<< "OpTypePointer Type <id> '" << _.getIdName(type_id)
<< "' is not a type.";
}
+ // See if this points to a storage image.
+ if (type->opcode() == SpvOpTypeImage) {
+ const auto storage_class = inst->GetOperandAs<uint32_t>(1);
+ if (storage_class == SpvStorageClassUniformConstant) {
+ const auto sampled = type->GetOperandAs<uint32_t>(6);
+ // 2 indicates this image is known to be be used without a sampler, i.e.
+ // a storage image.
+ if (sampled == 2) _.RegisterPointerToStorageImage(inst->id());
+ }
+ }
return SPV_SUCCESS;
}
diff --git a/source/val/validation_state.h b/source/val/validation_state.h
index 2c94906..676b423 100644
--- a/source/val/validation_state.h
+++ b/source/val/validation_state.h
@@ -562,6 +562,63 @@
bool GetPointerTypeInfo(uint32_t id, uint32_t* data_type,
uint32_t* storage_class) const;
+ // Is the ID the type of a pointer to a uniform block: Block-decorated struct
+ // in uniform storage class? The result is only valid after internal method
+ // CheckDecorationsOfBuffers has been called.
+ bool IsPointerToUniformBlock(uint32_t type_id) const {
+ return pointer_to_uniform_block_.find(type_id) !=
+ pointer_to_uniform_block_.cend();
+ }
+ // Save the ID of a pointer to uniform block.
+ void RegisterPointerToUniformBlock(uint32_t type_id) {
+ pointer_to_uniform_block_.insert(type_id);
+ }
+ // Is the ID the type of a struct used as a uniform block?
+ // The result is only valid after internal method CheckDecorationsOfBuffers
+ // has been called.
+ bool IsStructForUniformBlock(uint32_t type_id) const {
+ return struct_for_uniform_block_.find(type_id) !=
+ struct_for_uniform_block_.cend();
+ }
+ // Save the ID of a struct of a uniform block.
+ void RegisterStructForUniformBlock(uint32_t type_id) {
+ struct_for_uniform_block_.insert(type_id);
+ }
+ // Is the ID the type of a pointer to a storage buffer: BufferBlock-decorated
+ // struct in uniform storage class, or Block-decorated struct in StorageBuffer
+ // storage class? The result is only valid after internal method
+ // CheckDecorationsOfBuffers has been called.
+ bool IsPointerToStorageBuffer(uint32_t type_id) const {
+ return pointer_to_storage_buffer_.find(type_id) !=
+ pointer_to_storage_buffer_.cend();
+ }
+ // Save the ID of a pointer to a storage buffer.
+ void RegisterPointerToStorageBuffer(uint32_t type_id) {
+ pointer_to_storage_buffer_.insert(type_id);
+ }
+ // Is the ID the type of a struct for storage buffer?
+ // The result is only valid after internal method CheckDecorationsOfBuffers
+ // has been called.
+ bool IsStructForStorageBuffer(uint32_t type_id) const {
+ return struct_for_storage_buffer_.find(type_id) !=
+ struct_for_storage_buffer_.cend();
+ }
+ // Save the ID of a struct of a storage buffer.
+ void RegisterStructForStorageBuffer(uint32_t type_id) {
+ struct_for_storage_buffer_.insert(type_id);
+ }
+
+ // Is the ID the type of a pointer to a storage image? That is, the pointee
+ // type is an image type which is known to not use a sampler.
+ bool IsPointerToStorageImage(uint32_t type_id) const {
+ return pointer_to_storage_image_.find(type_id) !=
+ pointer_to_storage_image_.cend();
+ }
+ // Save the ID of a pointer to a storage image.
+ void RegisterPointerToStorageImage(uint32_t type_id) {
+ pointer_to_storage_image_.insert(type_id);
+ }
+
// Tries to evaluate a 32-bit signed or unsigned scalar integer constant.
// Returns tuple <is_int32, is_const_int32, value>.
// OpSpecConstant* return |is_const_int32| as false since their values cannot
@@ -701,6 +758,23 @@
std::unordered_map<uint32_t, std::vector<uint32_t>> function_to_entry_points_;
const std::vector<uint32_t> empty_ids_;
+ // The IDs of types of pointers to Block-decorated structs in Uniform storage
+ // class. This is populated at the start of ValidateDecorations.
+ std::unordered_set<uint32_t> pointer_to_uniform_block_;
+ // The IDs of struct types for uniform blocks.
+ // This is populated at the start of ValidateDecorations.
+ std::unordered_set<uint32_t> struct_for_uniform_block_;
+ // The IDs of types of pointers to BufferBlock-decorated structs in Uniform
+ // storage class, or Block-decorated structs in StorageBuffer storage class.
+ // This is populated at the start of ValidateDecorations.
+ std::unordered_set<uint32_t> pointer_to_storage_buffer_;
+ // The IDs of struct types for storage buffers.
+ // This is populated at the start of ValidateDecorations.
+ std::unordered_set<uint32_t> struct_for_storage_buffer_;
+ // The IDs of types of pointers to storage images. This is populated in the
+ // TypePass.
+ std::unordered_set<uint32_t> pointer_to_storage_image_;
+
/// Maps ids to friendly names.
std::unique_ptr<spvtools::FriendlyNameMapper> friendly_mapper_;
spvtools::NameMapper name_mapper_;
diff --git a/test/val/val_capability_test.cpp b/test/val/val_capability_test.cpp
index 488e957..37da4ea 100644
--- a/test/val/val_capability_test.cpp
+++ b/test/val/val_capability_test.cpp
@@ -1129,9 +1129,14 @@
"%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
AllCapabilities()),
std::make_pair(std::string(kOpenCLMemoryModel) +
+ // NonWritable must target something valid, such as a storage image.
"OpEntryPoint Kernel %func \"compute\" \n"
- "OpDecorate %intt NonWritable\n"
- "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
+ "OpDecorate %var NonWritable "
+ "%float = OpTypeFloat 32 "
+ "%imstor = OpTypeImage %float 2D 0 0 0 2 Unknown "
+ "%ptr = OpTypePointer UniformConstant %imstor "
+ "%var = OpVariable %ptr UniformConstant "
+ + std::string(kVoidFVoid),
AllCapabilities()),
std::make_pair(std::string(kOpenCLMemoryModel) +
"OpEntryPoint Kernel %func \"compute\" \n"
diff --git a/test/val/val_decoration_test.cpp b/test/val/val_decoration_test.cpp
index 1506a29..8f32ee0 100644
--- a/test/val/val_decoration_test.cpp
+++ b/test/val/val_decoration_test.cpp
@@ -115,7 +115,7 @@
OpCapability Linkage
OpMemoryModel Logical GLSL450
OpDecorate %1 DescriptorSet 0
- OpDecorate %1 NonWritable
+ OpDecorate %1 RelaxedPrecision
OpDecorate %1 Restrict
%1 = OpDecorationGroup
OpGroupDecorate %1 %2 %3
@@ -136,9 +136,10 @@
EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState());
// Decoration group has 3 decorations.
- auto expected_decorations = std::vector<Decoration>{
- Decoration(SpvDecorationDescriptorSet, {0}),
- Decoration(SpvDecorationNonWritable), Decoration(SpvDecorationRestrict)};
+ auto expected_decorations =
+ std::vector<Decoration>{Decoration(SpvDecorationDescriptorSet, {0}),
+ Decoration(SpvDecorationRelaxedPrecision),
+ Decoration(SpvDecorationRestrict)};
// Decoration group is applied to id 1, 2, 3, and 4. Note that id 1 (which is
// the decoration group id) also has all the decorations.
@@ -5491,6 +5492,255 @@
"improperly straddling vector at offset 28"));
}
+// NonWritable
+
+// Returns a SPIR-V shader module with variables in various storage classes,
+// parameterizable by which ID should be decorated as NonWritable.
+std::string ShaderWithNonWritableTarget(const std::string& target,
+ bool member_decorate = false) {
+ const std::string decoration_inst =
+ std::string(member_decorate ? "OpMemberDecorate " : "OpDecorate ") +
+ target + (member_decorate ? " 0" : "");
+
+ return std::string(R"(
+ OpCapability Shader
+ OpExtension "SPV_KHR_storage_buffer_storage_class"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %main "main"
+ OpName %label "label"
+ OpName %param_f "param_f"
+ OpName %param_p "param_p"
+ OpName %_ptr_imstor "_ptr_imstor"
+ OpName %_ptr_imsam "_ptr_imsam"
+ OpName %var_wg "var_wg"
+ OpName %var_imsam "var_imsam"
+ OpName %var_priv "var_priv"
+ OpName %var_func "var_func"
+ OpName %struct_bad "struct_bad"
+
+ OpDecorate %struct_b Block
+ OpDecorate %struct_bb BufferBlock
+ OpDecorate %struct_b_rtarr Block
+ OpMemberDecorate %struct_b 0 Offset 0
+ OpMemberDecorate %struct_bb 0 Offset 0
+ OpMemberDecorate %struct_b_rtarr 0 Offset 0
+ OpDecorate %rtarr ArrayStride 4
+)") + decoration_inst +
+
+ R"( NonWritable
+
+ %void = OpTypeVoid
+ %void_fn = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %float_0 = OpConstant %float 0
+ %struct_b = OpTypeStruct %float
+ %struct_bb = OpTypeStruct %float
+ %rtarr = OpTypeRuntimeArray %float
+%struct_b_rtarr = OpTypeStruct %rtarr
+%struct_bad = OpTypeStruct %float
+ ; storage image
+ %imstor = OpTypeImage %float 2D 0 0 0 2 R32f
+ ; sampled image
+ %imsam = OpTypeImage %float 2D 0 0 0 1 R32f
+
+%_ptr_Uniform_stb = OpTypePointer Uniform %struct_b
+%_ptr_Uniform_stbb = OpTypePointer Uniform %struct_bb
+%_ptr_StorageBuffer_stb = OpTypePointer StorageBuffer %struct_b
+%_ptr_StorageBuffer_stb_rtarr = OpTypePointer StorageBuffer %struct_b_rtarr
+%_ptr_Workgroup = OpTypePointer Workgroup %float
+%_ptr_Private = OpTypePointer Private %float
+%_ptr_Function = OpTypePointer Function %float
+%_ptr_imstor = OpTypePointer UniformConstant %imstor
+%_ptr_imsam = OpTypePointer UniformConstant %imsam
+
+%extra_fn = OpTypeFunction %void %float %_ptr_Private %_ptr_imstor
+
+%var_ubo = OpVariable %_ptr_Uniform_stb Uniform
+%var_ssbo_u = OpVariable %_ptr_Uniform_stbb Uniform
+%var_ssbo_sb = OpVariable %_ptr_StorageBuffer_stb StorageBuffer
+%var_ssbo_sb_rtarr = OpVariable %_ptr_StorageBuffer_stb_rtarr StorageBuffer
+%var_wg = OpVariable %_ptr_Workgroup Workgroup
+%var_priv = OpVariable %_ptr_Private Private
+%var_imstor = OpVariable %_ptr_imstor UniformConstant
+%var_imsam = OpVariable %_ptr_imsam UniformConstant
+
+ %helper = OpFunction %void None %extra_fn
+ %param_f = OpFunctionParameter %float
+ %param_p = OpFunctionParameter %_ptr_Private
+ %param_pimstor = OpFunctionParameter %_ptr_imstor
+%helper_label = OpLabel
+%helper_func_var = OpVariable %_ptr_Function Function
+ OpReturn
+ OpFunctionEnd
+
+ %main = OpFunction %void None %void_fn
+ %label = OpLabel
+%var_func = OpVariable %_ptr_Function Function
+ OpReturn
+ OpFunctionEnd
+)";
+}
+
+TEST_F(ValidateDecorations, NonWritableLabelTargetBad) {
+ std::string spirv = ShaderWithNonWritableTarget("%label");
+
+ CompileSuccessfully(spirv);
+ EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Target of NonWritable decoration must be a "
+ "memory object declaration (a variable or a function "
+ "parameter)\n %label = OpLabel"));
+}
+
+TEST_F(ValidateDecorations, NonWritableTypeTargetBad) {
+ std::string spirv = ShaderWithNonWritableTarget("%void");
+
+ CompileSuccessfully(spirv);
+ EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Target of NonWritable decoration must be a "
+ "memory object declaration (a variable or a function "
+ "parameter)\n %void = OpTypeVoid"));
+}
+
+TEST_F(ValidateDecorations, NonWritableValueTargetBad) {
+ std::string spirv = ShaderWithNonWritableTarget("%float_0");
+
+ CompileSuccessfully(spirv);
+ EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Target of NonWritable decoration must be a "
+ "memory object declaration (a variable or a function "
+ "parameter)\n %float_0 = OpConstant %float 0"));
+}
+
+TEST_F(ValidateDecorations, NonWritableValueParamBad) {
+ std::string spirv = ShaderWithNonWritableTarget("%param_f");
+
+ CompileSuccessfully(spirv);
+ EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Target of NonWritable decoration is invalid: must "
+ "point to a storage image, uniform block, or storage "
+ "buffer\n %param_f = OpFunctionParameter %float"));
+}
+
+TEST_F(ValidateDecorations, NonWritablePointerParamButWrongTypeBad) {
+ std::string spirv = ShaderWithNonWritableTarget("%param_p");
+
+ CompileSuccessfully(spirv);
+ EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ EXPECT_THAT(
+ getDiagnosticString(),
+ HasSubstr(
+ "Target of NonWritable decoration is invalid: must "
+ "point to a storage image, uniform block, or storage "
+ "buffer\n %param_p = OpFunctionParameter %_ptr_Private_float"));
+}
+
+TEST_F(ValidateDecorations, NonWritablePointerParamStorageImageGood) {
+ std::string spirv = ShaderWithNonWritableTarget("%param_pimstor");
+
+ CompileSuccessfully(spirv);
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+ EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, NonWritableVarStorageImageGood) {
+ std::string spirv = ShaderWithNonWritableTarget("%var_imstor");
+
+ CompileSuccessfully(spirv);
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+ EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, NonWritableVarSampledImageBad) {
+ std::string spirv = ShaderWithNonWritableTarget("%var_imsam");
+
+ CompileSuccessfully(spirv);
+ EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Target of NonWritable decoration is invalid: must "
+ "point to a storage image, uniform block, or storage "
+ "buffer\n %var_imsam"));
+}
+
+TEST_F(ValidateDecorations, NonWritableVarUboGood) {
+ std::string spirv = ShaderWithNonWritableTarget("%var_ubo");
+
+ CompileSuccessfully(spirv);
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+ EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, NonWritableVarSsboInUniformGood) {
+ std::string spirv = ShaderWithNonWritableTarget("%var_ssbo_u");
+
+ CompileSuccessfully(spirv);
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+ EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, NonWritableVarSsboInStorageBufferGood) {
+ std::string spirv = ShaderWithNonWritableTarget("%var_ssbo_sb");
+
+ CompileSuccessfully(spirv);
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+ EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, NonWritableMemberOfSsboInStorageBufferGood) {
+ std::string spirv = ShaderWithNonWritableTarget("%struct_b_rtarr", true);
+
+ CompileSuccessfully(spirv);
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+ EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, NonWritableMemberOfNonBlockStructBad) {
+ std::string spirv = ShaderWithNonWritableTarget("%struct_bad", true);
+
+ CompileSuccessfully(spirv);
+ EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ EXPECT_THAT(
+ getDiagnosticString(),
+ HasSubstr("Target of NonWritable member decoration is invalid: must be "
+ "the struct type of a uniform block or storage buffer"));
+}
+
+TEST_F(ValidateDecorations, NonWritableVarWorkgroupBad) {
+ std::string spirv = ShaderWithNonWritableTarget("%var_wg");
+
+ CompileSuccessfully(spirv);
+ EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Target of NonWritable decoration is invalid: must "
+ "point to a storage image, uniform block, or storage "
+ "buffer\n %var_wg"));
+}
+
+TEST_F(ValidateDecorations, NonWritableVarPrivateBad) {
+ std::string spirv = ShaderWithNonWritableTarget("%var_priv");
+
+ CompileSuccessfully(spirv);
+ EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Target of NonWritable decoration is invalid: must "
+ "point to a storage image, uniform block, or storage "
+ "buffer\n %var_priv"));
+}
+
+TEST_F(ValidateDecorations, NonWritableVarFunctionBad) {
+ std::string spirv = ShaderWithNonWritableTarget("%var_func");
+
+ CompileSuccessfully(spirv);
+ EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Target of NonWritable decoration is invalid: must "
+ "point to a storage image, uniform block, or storage "
+ "buffer\n %var_func"));
+}
+
} // namespace
} // namespace val
} // namespace spvtools