From ef2f4323640beddf3bab855c5535b0c3e8c7d878 Mon Sep 17 00:00:00 2001 From: alan-baker Date: Thu, 25 Jan 2024 10:22:09 -0500 Subject: [PATCH] Add support for SPV_KHR_float_controls2 (#5543) * Test asm/dis for SPV_KHR_float_controls2 * SPV_KHR_float_controls2 validation --------- Co-authored-by: David Neto --- source/val/validate.cpp | 4 + source/val/validate.h | 12 + source/val/validate_annotation.cpp | 28 ++ source/val/validate_instruction.cpp | 3 +- source/val/validate_mode_setting.cpp | 173 +++++++- test/operand_capabilities_test.cpp | 17 +- test/text_to_binary.extension_test.cpp | 36 ++ test/val/val_annotation_test.cpp | 165 ++++++++ test/val/val_modes_test.cpp | 546 +++++++++++++++++++++++++ 9 files changed, 961 insertions(+), 23 deletions(-) diff --git a/source/val/validate.cpp b/source/val/validate.cpp index a5f320b9f..ccf26fbf1 100644 --- a/source/val/validate.cpp +++ b/source/val/validate.cpp @@ -141,6 +141,10 @@ spv_result_t ValidateEntryPoints(ValidationState_t& _) { } } + if (auto error = ValidateFloatControls2(_)) { + return error; + } + return SPV_SUCCESS; } diff --git a/source/val/validate.h b/source/val/validate.h index 6b7d7cdac..52267c8ab 100644 --- a/source/val/validate.h +++ b/source/val/validate.h @@ -82,6 +82,18 @@ spv_result_t ValidateAdjacency(ValidationState_t& _); /// @return SPV_SUCCESS if no errors are found. spv_result_t ValidateInterfaces(ValidationState_t& _); +/// @brief Validates entry point call tree requirements of +/// SPV_KHR_float_controls2 +/// +/// Checks that no entry point using FPFastMathDefault uses: +/// * FPFastMathMode Fast +/// * NoContraction +/// +/// @param[in] _ the validation state of the module +/// +/// @return SPV_SUCCESS if no errors are found. +spv_result_t ValidateFloatControls2(ValidationState_t& _); + /// @brief Validates memory instructions /// /// @param[in] _ the validation state of the module diff --git a/source/val/validate_annotation.cpp b/source/val/validate_annotation.cpp index 73d0285a1..106004d06 100644 --- a/source/val/validate_annotation.cpp +++ b/source/val/validate_annotation.cpp @@ -267,6 +267,34 @@ spv_result_t ValidateDecorate(ValidationState_t& _, const Instruction* inst) { } } + if (decoration == spv::Decoration::FPFastMathMode) { + if (_.HasDecoration(target_id, spv::Decoration::NoContraction)) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "FPFastMathMode and NoContraction cannot decorate the same " + "target"; + } + auto mask = inst->GetOperandAs(2); + if ((mask & spv::FPFastMathModeMask::AllowTransform) != + spv::FPFastMathModeMask::MaskNone && + ((mask & (spv::FPFastMathModeMask::AllowContract | + spv::FPFastMathModeMask::AllowReassoc)) != + (spv::FPFastMathModeMask::AllowContract | + spv::FPFastMathModeMask::AllowReassoc))) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "AllowReassoc and AllowContract must be specified when " + "AllowTransform is specified"; + } + } + + // This is checked from both sides since we register decorations as we go. + if (decoration == spv::Decoration::NoContraction) { + if (_.HasDecoration(target_id, spv::Decoration::FPFastMathMode)) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "FPFastMathMode and NoContraction cannot decorate the same " + "target"; + } + } + if (DecorationTakesIdParameters(decoration)) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "Decorations taking ID parameters may not be used with " diff --git a/source/val/validate_instruction.cpp b/source/val/validate_instruction.cpp index 8710ffa44..5bc4d2cef 100644 --- a/source/val/validate_instruction.cpp +++ b/source/val/validate_instruction.cpp @@ -470,7 +470,8 @@ spv_result_t InstructionPass(ValidationState_t& _, const Instruction* inst) { } _.set_addressing_model(inst->GetOperandAs(0)); _.set_memory_model(inst->GetOperandAs(1)); - } else if (opcode == spv::Op::OpExecutionMode) { + } else if (opcode == spv::Op::OpExecutionMode || + opcode == spv::Op::OpExecutionModeId) { const uint32_t entry_point = inst->word(1); _.RegisterExecutionModeForEntryPoint(entry_point, spv::ExecutionMode(inst->word(2))); diff --git a/source/val/validate_mode_setting.cpp b/source/val/validate_mode_setting.cpp index d757d4f82..10afa8298 100644 --- a/source/val/validate_mode_setting.cpp +++ b/source/val/validate_mode_setting.cpp @@ -340,29 +340,92 @@ spv_result_t ValidateExecutionMode(ValidationState_t& _, const auto mode = inst->GetOperandAs(1); if (inst->opcode() == spv::Op::OpExecutionModeId) { + bool valid_mode = false; + switch (mode) { + case spv::ExecutionMode::SubgroupsPerWorkgroupId: + case spv::ExecutionMode::LocalSizeHintId: + case spv::ExecutionMode::LocalSizeId: + case spv::ExecutionMode::FPFastMathDefault: + valid_mode = true; + break; + default: + valid_mode = false; + break; + } + if (!valid_mode) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "OpExecutionModeId is only valid when the Mode operand is an " + "execution mode that takes Extra Operands that are id " + "operands."; + } + size_t operand_count = inst->operands().size(); for (size_t i = 2; i < operand_count; ++i) { - const auto operand_id = inst->GetOperandAs(2); + const auto operand_id = inst->GetOperandAs(i); const auto* operand_inst = _.FindDef(operand_id); - if (mode == spv::ExecutionMode::SubgroupsPerWorkgroupId || - mode == spv::ExecutionMode::LocalSizeHintId || - mode == spv::ExecutionMode::LocalSizeId) { - if (!spvOpcodeIsConstant(operand_inst->opcode())) { - return _.diag(SPV_ERROR_INVALID_ID, inst) - << "For OpExecutionModeId all Extra Operand ids must be " - "constant " - "instructions."; - } - } else { - return _.diag(SPV_ERROR_INVALID_ID, inst) - << "OpExecutionModeId is only valid when the Mode operand is an " - "execution mode that takes Extra Operands that are id " - "operands."; + switch (mode) { + case spv::ExecutionMode::SubgroupsPerWorkgroupId: + case spv::ExecutionMode::LocalSizeHintId: + case spv::ExecutionMode::LocalSizeId: + if (!spvOpcodeIsConstant(operand_inst->opcode())) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "For OpExecutionModeId all Extra Operand ids must be " + "constant instructions."; + } + break; + case spv::ExecutionMode::FPFastMathDefault: + if (i == 2) { + if (!_.IsFloatScalarType(operand_id)) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "The Target Type operand must be a floating-point " + "scalar type"; + } + } else { + bool is_int32 = false; + bool is_const = false; + uint32_t value = 0; + std::tie(is_int32, is_const, value) = + _.EvalInt32IfConst(operand_id); + if (is_int32 && is_const) { + // Valid values include up to 0x00040000 (AllowTransform). + uint32_t invalid_mask = 0xfff80000; + if ((invalid_mask & value) != 0) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "The Fast Math Default operand is an invalid bitmask " + "value"; + } + if (value & + static_cast(spv::FPFastMathModeMask::Fast)) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "The Fast Math Default operand must not include Fast"; + } + const auto reassoc_contract = + spv::FPFastMathModeMask::AllowContract | + spv::FPFastMathModeMask::AllowReassoc; + if ((value & static_cast( + spv::FPFastMathModeMask::AllowTransform)) != 0 && + ((value & static_cast(reassoc_contract)) != + static_cast(reassoc_contract))) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "The Fast Math Default operand must include " + "AllowContract and AllowReassoc when AllowTransform " + "is specified"; + } + } else { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "The Fast Math Default operand must be a " + "non-specialization constant"; + } + } + break; + default: + break; } } } else if (mode == spv::ExecutionMode::SubgroupsPerWorkgroupId || mode == spv::ExecutionMode::LocalSizeHintId || - mode == spv::ExecutionMode::LocalSizeId) { + mode == spv::ExecutionMode::LocalSizeId || + mode == spv::ExecutionMode::FPFastMathDefault) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "OpExecutionMode is only valid when the Mode operand is an " "execution mode that takes no Extra Operands, or takes Extra " @@ -579,6 +642,20 @@ spv_result_t ValidateExecutionMode(ValidationState_t& _, break; } + if (mode == spv::ExecutionMode::FPFastMathDefault) { + const auto* modes = _.GetExecutionModes(entry_point_id); + if (modes && modes->count(spv::ExecutionMode::ContractionOff)) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "FPFastMathDefault and ContractionOff execution modes cannot " + "be applied to the same entry point"; + } + if (modes && modes->count(spv::ExecutionMode::SignedZeroInfNanPreserve)) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "FPFastMathDefault and SignedZeroInfNanPreserve execution " + "modes cannot be applied to the same entry point"; + } + } + if (spvIsVulkanEnv(_.context()->target_env)) { if (mode == spv::ExecutionMode::OriginLowerLeft) { return _.diag(SPV_ERROR_INVALID_DATA, inst) @@ -636,6 +713,70 @@ spv_result_t ValidateMemoryModel(ValidationState_t& _, } // namespace +spv_result_t ValidateFloatControls2(ValidationState_t& _) { + std::unordered_set fp_fast_math_default_entry_points; + for (auto entry_point : _.entry_points()) { + const auto* exec_modes = _.GetExecutionModes(entry_point); + if (exec_modes && + exec_modes->count(spv::ExecutionMode::FPFastMathDefault)) { + fp_fast_math_default_entry_points.insert(entry_point); + } + } + + std::vector> worklist; + for (const auto& inst : _.ordered_instructions()) { + if (inst.opcode() != spv::Op::OpDecorate) { + continue; + } + + const auto decoration = inst.GetOperandAs(1); + const auto target_id = inst.GetOperandAs(0); + const auto target = _.FindDef(target_id); + if (decoration == spv::Decoration::NoContraction) { + worklist.push_back(std::make_pair(target, decoration)); + } else if (decoration == spv::Decoration::FPFastMathMode) { + auto mask = inst.GetOperandAs(2); + if ((mask & spv::FPFastMathModeMask::Fast) != + spv::FPFastMathModeMask::MaskNone) { + worklist.push_back(std::make_pair(target, decoration)); + } + } + } + + std::unordered_set visited; + while (!worklist.empty()) { + const auto inst = worklist.back().first; + const auto decoration = worklist.back().second; + worklist.pop_back(); + + if (!visited.insert(inst).second) { + continue; + } + + const auto function = inst->function(); + if (function) { + const auto& entry_points = _.FunctionEntryPoints(function->id()); + for (auto entry_point : entry_points) { + if (fp_fast_math_default_entry_points.count(entry_point)) { + const std::string dec = decoration == spv::Decoration::NoContraction + ? "NoContraction" + : "FPFastMathMode Fast"; + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << dec + << " cannot be used by an entry point with the " + "FPFastMathDefault execution mode"; + } + } + } else { + for (const auto& pair : inst->uses()) { + worklist.push_back(std::make_pair(pair.first, decoration)); + } + } + } + + return SPV_SUCCESS; +} + spv_result_t ModeSettingPass(ValidationState_t& _, const Instruction* inst) { switch (inst->opcode()) { case spv::Op::OpEntryPoint: diff --git a/test/operand_capabilities_test.cpp b/test/operand_capabilities_test.cpp index 6c4042dc2..6f2ac9f77 100644 --- a/test/operand_capabilities_test.cpp +++ b/test/operand_capabilities_test.cpp @@ -21,6 +21,9 @@ #include "source/assembly_grammar.h" #include "source/enum_set.h" #include "source/operand.h" +#include "source/spirv_target_env.h" +#include "source/table.h" +#include "spirv-tools/libspirv.h" #include "test/unit_spirv.h" namespace spvtools { @@ -58,15 +61,17 @@ struct EnumCapabilityCase { uint32_t value; CapabilitySet expected_capabilities; }; -// Emits an EnumCapabilityCase to the ostream, returning the ostream. -inline std::ostream& operator<<(std::ostream& out, - const EnumCapabilityCase& ecc) { - out << "EnumCapabilityCase{ " << spvOperandTypeStr(ecc.type) << "(" - << unsigned(ecc.type) << "), " << ecc.value << ", " - << ecc.expected_capabilities << "}"; + +// Emits an EnumCapabilityCase to the given output stream. This is used +// to emit failure cases when they occur, which helps debug tests. +inline std::ostream& operator<<(std::ostream& out, EnumCapabilityCase e) { + out << "{" << spvOperandTypeStr(e.type) << " " << e.value << " " + << e.expected_capabilities << " }"; return out; } +using EnvEnumCapabilityCase = std::tuple; + // Test fixture for testing EnumCapabilityCases. using EnumCapabilityTest = TestWithParam>; diff --git a/test/text_to_binary.extension_test.cpp b/test/text_to_binary.extension_test.cpp index 17adc557d..8e78312e6 100644 --- a/test/text_to_binary.extension_test.cpp +++ b/test/text_to_binary.extension_test.cpp @@ -1264,5 +1264,41 @@ INSTANTIATE_TEST_SUITE_P( {1, (uint32_t)spv::ExecutionMode::MaximallyReconvergesKHR})}, }))); +// SPV_KHR_float_controls2 + +INSTANTIATE_TEST_SUITE_P( + SPV_KHR_float_controls2, ExtensionRoundTripTest, + Combine( + Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_5, SPV_ENV_VULKAN_1_0, + SPV_ENV_VULKAN_1_1, SPV_ENV_VULKAN_1_2, SPV_ENV_VULKAN_1_3), + ValuesIn(std::vector{ + {"OpExtension \"SPV_KHR_float_controls2\"\n", + MakeInstruction(spv::Op::OpExtension, + MakeVector("SPV_KHR_float_controls2"))}, + {"OpCapability FloatControls2\n", + MakeInstruction(spv::Op::OpCapability, + {(uint32_t)spv::Capability::FloatControls2})}, + {"OpExecutionMode %1 FPFastMathDefault %2 %3\n", + // The operands are: target type, flags constant + MakeInstruction( + spv::Op::OpExecutionMode, + {1, (uint32_t)spv::ExecutionMode::FPFastMathDefault, 2, 3})}, + {"OpDecorate %1 FPFastMathMode AllowContract\n", + MakeInstruction( + spv::Op::OpDecorate, + {1, (uint32_t)spv::Decoration::FPFastMathMode, + (uint32_t)spv::FPFastMathModeMask::AllowContract})}, + {"OpDecorate %1 FPFastMathMode AllowReassoc\n", + MakeInstruction( + spv::Op::OpDecorate, + {1, (uint32_t)spv::Decoration::FPFastMathMode, + (uint32_t)spv::FPFastMathModeMask::AllowReassoc})}, + {"OpDecorate %1 FPFastMathMode AllowTransform\n", + MakeInstruction( + spv::Op::OpDecorate, + {1, (uint32_t)spv::Decoration::FPFastMathMode, + (uint32_t)spv::FPFastMathModeMask::AllowTransform})}, + }))); + } // namespace } // namespace spvtools diff --git a/test/val/val_annotation_test.cpp b/test/val/val_annotation_test.cpp index 9f85a30bb..97dde2df4 100644 --- a/test/val/val_annotation_test.cpp +++ b/test/val/val_annotation_test.cpp @@ -65,6 +65,171 @@ OpDecorate %var BuiltIn WorkgroupSize EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); } +TEST_F(DecorationTest, FPFastMathModeInvalidMask) { + const std::string text = R"( +OpCapability Shader +OpCapability FloatControls2 +OpExtension "SPV_KHR_float_controls2" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +OpDecorate %add FPFastMathMode !524288 +%void = OpTypeVoid +%float = OpTypeFloat 32 +%undef = OpUndef %float +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +%add = OpFAdd %float %undef %undef +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(text); + EXPECT_EQ(SPV_ERROR_INVALID_BINARY, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Invalid floating-point fast math mode operand")); +} + +TEST_F(DecorationTest, FPFastMathModeAllowTransformMissingAllowContract) { + const std::string text = R"( +OpCapability Shader +OpCapability FloatControls2 +OpExtension "SPV_KHR_float_controls2" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +OpDecorate %add FPFastMathMode AllowTransform|AllowReassoc +%void = OpTypeVoid +%float = OpTypeFloat 32 +%undef = OpUndef %float +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +%add = OpFAdd %float %undef %undef +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(text); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("AllowReassoc and AllowContract must be specified when " + "AllowTransform is specified")); +} + +TEST_F(DecorationTest, FPFastMathModeAllowTransformMissingAllowReassoc) { + const std::string text = R"( +OpCapability Shader +OpCapability FloatControls2 +OpExtension "SPV_KHR_float_controls2" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +OpDecorate %add FPFastMathMode AllowTransform|AllowContract +%void = OpTypeVoid +%float = OpTypeFloat 32 +%undef = OpUndef %float +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +%add = OpFAdd %float %undef %undef +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(text); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("AllowReassoc and AllowContract must be specified when " + "AllowTransform is specified")); +} + +TEST_F(DecorationTest, FPFastMathModeAllowTransformMissingContractAndReassoc) { + const std::string text = R"( +OpCapability Shader +OpCapability FloatControls2 +OpExtension "SPV_KHR_float_controls2" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +OpDecorate %add FPFastMathMode AllowTransform +%void = OpTypeVoid +%float = OpTypeFloat 32 +%undef = OpUndef %float +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +%add = OpFAdd %float %undef %undef +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(text); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("AllowReassoc and AllowContract must be specified when " + "AllowTransform is specified")); +} + +TEST_F(DecorationTest, FPFastMathModeAndNoContraction) { + const std::string text = R"( +OpCapability Shader +OpCapability FloatControls2 +OpExtension "SPV_KHR_float_controls2" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +OpDecorate %add FPFastMathMode None +OpDecorate %add NoContraction +%void = OpTypeVoid +%float = OpTypeFloat 32 +%undef = OpUndef %float +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +%add = OpFAdd %float %undef %undef +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(text); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "FPFastMathMode and NoContraction cannot decorate the same target")); +} + +TEST_F(DecorationTest, FPFastMathModeAndNoContraction2) { + const std::string text = R"( +OpCapability Shader +OpCapability FloatControls2 +OpExtension "SPV_KHR_float_controls2" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +OpDecorate %add NoContraction +OpDecorate %add FPFastMathMode None +%void = OpTypeVoid +%float = OpTypeFloat 32 +%undef = OpUndef %float +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +%add = OpFAdd %float %undef %undef +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(text); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "FPFastMathMode and NoContraction cannot decorate the same target")); +} + using MemberOnlyDecorations = spvtest::ValidateBase; TEST_P(MemberOnlyDecorations, MemberDecoration) { diff --git a/test/val/val_modes_test.cpp b/test/val/val_modes_test.cpp index 97d8c397e..f17497747 100644 --- a/test/val/val_modes_test.cpp +++ b/test/val/val_modes_test.cpp @@ -1328,6 +1328,552 @@ OpFunctionEnd "SPV_KHR_maximal_reconvergence ")); } +TEST_F(ValidateMode, FPFastMathDefaultNotExecutionModeId) { + const std::string spirv = R"( +OpCapability Shader +OpCapability FloatControls2 +OpExtension "SPV_KHR_float_controls2" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +OpExecutionMode %main FPFastMathDefault %int_0 %int_0 +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%int_0 = OpConstant %int 0 +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpExecutionMode is only valid when the Mode operand " + "is an execution mode that takes no Extra Operands, or " + "takes Extra Operands that are not id operands")); +} + +TEST_F(ValidateMode, FPFastMathDefaultNotAType) { + const std::string spirv = R"( +OpCapability Shader +OpCapability FloatControls2 +OpExtension "SPV_KHR_float_controls2" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +OpExecutionModeId %main FPFastMathDefault %int_0 %int_0 +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%int_0 = OpConstant %int 0 +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "The Target Type operand must be a floating-point scalar type")); +} + +TEST_F(ValidateMode, FPFastMathDefaultNotAFloatType) { + const std::string spirv = R"( +OpCapability Shader +OpCapability FloatControls2 +OpExtension "SPV_KHR_float_controls2" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +OpExecutionModeId %main FPFastMathDefault %int %int_0 +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%int_0 = OpConstant %int 0 +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "The Target Type operand must be a floating-point scalar type")); +} + +TEST_F(ValidateMode, FPFastMathDefaultNotAFloatScalarType) { + const std::string spirv = R"( +OpCapability Shader +OpCapability FloatControls2 +OpExtension "SPV_KHR_float_controls2" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +OpExecutionModeId %main FPFastMathDefault %float2 %int_0 +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%int_0 = OpConstant %int 0 +%float = OpTypeFloat 32 +%float2 = OpTypeVector %float 2 +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "The Target Type operand must be a floating-point scalar type")); +} + +TEST_F(ValidateMode, FPFastMathDefaultSpecConstant) { + const std::string spirv = R"( +OpCapability Shader +OpCapability FloatControls2 +OpExtension "SPV_KHR_float_controls2" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +OpExecutionModeId %main FPFastMathDefault %float %int_0 +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%int_0 = OpSpecConstant %int 0 +%float = OpTypeFloat 32 +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("The Fast Math Default operand must be a " + "non-specialization constant")); +} + +TEST_F(ValidateMode, FPFastMathDefaultInvalidMask) { + const std::string spirv = R"( +OpCapability Shader +OpCapability FloatControls2 +OpExtension "SPV_KHR_float_controls2" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +OpExecutionModeId %main FPFastMathDefault %float %constant +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%constant = OpConstant %int 524288 +%float = OpTypeFloat 32 +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("The Fast Math Default operand is an invalid bitmask value")); +} + +TEST_F(ValidateMode, FPFastMathDefaultContainsFast) { + const std::string spirv = R"( +OpCapability Shader +OpCapability FloatControls2 +OpExtension "SPV_KHR_float_controls2" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +OpExecutionModeId %main FPFastMathDefault %float %constant +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%constant = OpConstant %int 16 +%float = OpTypeFloat 32 +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("The Fast Math Default operand must not include Fast")); +} + +TEST_F(ValidateMode, FPFastMathDefaultAllowTransformMissingAllowReassoc) { + const std::string spirv = R"( +OpCapability Shader +OpCapability FloatControls2 +OpExtension "SPV_KHR_float_controls2" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +OpExecutionModeId %main FPFastMathDefault %float %constant +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%constant = OpConstant %int 327680 +%float = OpTypeFloat 32 +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("The Fast Math Default operand must include AllowContract and " + "AllowReassoc when AllowTransform is specified")); +} + +TEST_F(ValidateMode, FPFastMathDefaultAllowTransformMissingAllowContract) { + const std::string spirv = R"( +OpCapability Shader +OpCapability FloatControls2 +OpExtension "SPV_KHR_float_controls2" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +OpExecutionModeId %main FPFastMathDefault %float %constant +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%constant = OpConstant %int 393216 +%float = OpTypeFloat 32 +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("The Fast Math Default operand must include AllowContract and " + "AllowReassoc when AllowTransform is specified")); +} + +TEST_F(ValidateMode, FPFastMathDefaultAllowTransformMissingContractAndReassoc) { + const std::string spirv = R"( +OpCapability Shader +OpCapability FloatControls2 +OpExtension "SPV_KHR_float_controls2" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +OpExecutionModeId %main FPFastMathDefault %float %constant +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%constant = OpConstant %int 262144 +%float = OpTypeFloat 32 +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("The Fast Math Default operand must include AllowContract and " + "AllowReassoc when AllowTransform is specified")); +} + +TEST_F(ValidateMode, FPFastMathDefaultSignedZeroInfNanPreserve) { + const std::string spirv = R"( +OpCapability Shader +OpCapability FloatControls2 +OpCapability SignedZeroInfNanPreserve +OpExtension "SPV_KHR_float_controls2" +OpExtension "SPV_KHR_float_controls" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +OpExecutionModeId %main FPFastMathDefault %float %constant +OpExecutionMode %main SignedZeroInfNanPreserve 32 +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%constant = OpConstant %int 0 +%float = OpTypeFloat 32 +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("FPFastMathDefault and SignedZeroInfNanPreserve execution " + "modes cannot be applied to the same entry point")); +} + +TEST_F(ValidateMode, FPFastMathDefaultConractionOff) { + const std::string spirv = R"( +OpCapability Kernel +OpCapability Addresses +OpCapability FloatControls2 +OpCapability SignedZeroInfNanPreserve +OpExtension "SPV_KHR_float_controls2" +OpExtension "SPV_KHR_float_controls" +OpMemoryModel Physical64 OpenCL +OpEntryPoint Kernel %main "main" +OpExecutionModeId %main FPFastMathDefault %float %constant +OpExecutionMode %main ContractionOff +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%constant = OpConstant %int 0 +%float = OpTypeFloat 32 +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("FPFastMathDefault and ContractionOff execution modes " + "cannot be applied to the same entry point")); +} + +TEST_F(ValidateMode, FPFastMathDefaultNoContractionNotInCallTree) { + const std::string spirv = R"( +OpCapability Shader +OpCapability FloatControls2 +OpExtension "SPV_KHR_float_controls2" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionModeId %main FPFastMathDefault %float %constant +OpExecutionMode %main LocalSize 1 1 1 +OpDecorate %add NoContraction +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%constant = OpConstant %int 0 +%float = OpTypeFloat 32 +%zero = OpConstant %float 0 +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +%func = OpFunction %void None %void_fn +%func_entry = OpLabel +%add = OpFAdd %float %zero %zero +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); +} + +TEST_F(ValidateMode, FPFastMathDefaultNoContractionInCallTree) { + const std::string spirv = R"( +OpCapability Shader +OpCapability FloatControls2 +OpExtension "SPV_KHR_float_controls2" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionModeId %main FPFastMathDefault %float %constant +OpExecutionMode %main LocalSize 1 1 1 +OpDecorate %add NoContraction +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%constant = OpConstant %int 0 +%float = OpTypeFloat 32 +%zero = OpConstant %float 0 +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +%call = OpFunctionCall %void %func +OpReturn +OpFunctionEnd +%func = OpFunction %void None %void_fn +%func_entry = OpLabel +%add = OpFAdd %float %zero %zero +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("NoContraction cannot be used by an entry point with " + "the FPFastMathDefault execution mode")); +} + +TEST_F(ValidateMode, FPFastMathDefaultNoContractionInCallTree2) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Kernel +OpCapability Addresses +OpCapability FloatControls2 +OpExtension "SPV_KHR_float_controls2" +OpMemoryModel Physical64 OpenCL +OpEntryPoint Kernel %main "main" +OpExecutionModeId %main FPFastMathDefault %float %constant +OpDecorate %const NoContraction +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%constant = OpConstant %int 0 +%float = OpTypeFloat 32 +%zero = OpConstant %float 0 +%const = OpSpecConstantOp %float FAdd %zero %zero +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +%call = OpFunctionCall %void %func +OpReturn +OpFunctionEnd +%func = OpFunction %void None %void_fn +%func_entry = OpLabel +%add = OpFAdd %float %const %zero +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("NoContraction cannot be used by an entry point with " + "the FPFastMathDefault execution mode")); +} + +TEST_F(ValidateMode, FPFastMathDefaultFastMathFastNotInCallTree) { + const std::string spirv = R"( +OpCapability Shader +OpCapability FloatControls2 +OpExtension "SPV_KHR_float_controls2" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionModeId %main FPFastMathDefault %float %constant +OpExecutionMode %main LocalSize 1 1 1 +OpDecorate %add FPFastMathMode Fast +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%constant = OpConstant %int 0 +%float = OpTypeFloat 32 +%zero = OpConstant %float 0 +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +%func = OpFunction %void None %void_fn +%func_entry = OpLabel +%add = OpFAdd %float %zero %zero +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); +} + +TEST_F(ValidateMode, FPFastMathDefaultFastMathFastInCallTree) { + const std::string spirv = R"( +OpCapability Shader +OpCapability FloatControls2 +OpExtension "SPV_KHR_float_controls2" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionModeId %main FPFastMathDefault %float %constant +OpExecutionMode %main LocalSize 1 1 1 +OpDecorate %add FPFastMathMode Fast +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%constant = OpConstant %int 0 +%float = OpTypeFloat 32 +%zero = OpConstant %float 0 +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +%call = OpFunctionCall %void %func +OpReturn +OpFunctionEnd +%func = OpFunction %void None %void_fn +%func_entry = OpLabel +%add = OpFAdd %float %zero %zero +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("FPFastMathMode Fast cannot be used by an entry point " + "with the FPFastMathDefault execution mode")); +} + +TEST_F(ValidateMode, FPFastMathDefaultFastMathFastInCallTree2) { + const std::string spirv = R"( +OpCapability Kernel +OpCapability Addresses +OpCapability FloatControls2 +OpExtension "SPV_KHR_float_controls2" +OpMemoryModel Physical64 OpenCL +OpEntryPoint Kernel %main "main" +OpExecutionModeId %main FPFastMathDefault %float %constant +OpDecorate %const FPFastMathMode Fast +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%constant = OpConstant %int 0 +%float = OpTypeFloat 32 +%zero = OpConstant %float 0 +%const = OpSpecConstantOp %float FAdd %zero %zero +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +%call = OpFunctionCall %void %func +OpReturn +OpFunctionEnd +%func = OpFunction %void None %void_fn +%func_entry = OpLabel +%add = OpFAdd %float %const %zero +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("FPFastMathMode Fast cannot be used by an entry point " + "with the FPFastMathDefault execution mode")); +} + } // namespace } // namespace val } // namespace spvtools