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 <dneto@google.com>
diff --git a/source/val/validate.cpp b/source/val/validate.cpp
index a5f320b..ccf26fb 100644
--- a/source/val/validate.cpp
+++ b/source/val/validate.cpp
@@ -141,6 +141,10 @@
}
}
+ if (auto error = ValidateFloatControls2(_)) {
+ return error;
+ }
+
return SPV_SUCCESS;
}
diff --git a/source/val/validate.h b/source/val/validate.h
index 6b7d7cd..52267c8 100644
--- a/source/val/validate.h
+++ b/source/val/validate.h
@@ -82,6 +82,18 @@
/// @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 73d0285..106004d 100644
--- a/source/val/validate_annotation.cpp
+++ b/source/val/validate_annotation.cpp
@@ -267,6 +267,34 @@
}
}
+ 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<spv::FPFastMathModeMask>(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 8710ffa..5bc4d2c 100644
--- a/source/val/validate_instruction.cpp
+++ b/source/val/validate_instruction.cpp
@@ -470,7 +470,8 @@
}
_.set_addressing_model(inst->GetOperandAs<spv::AddressingModel>(0));
_.set_memory_model(inst->GetOperandAs<spv::MemoryModel>(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 d757d4f..10afa82 100644
--- a/source/val/validate_mode_setting.cpp
+++ b/source/val/validate_mode_setting.cpp
@@ -340,29 +340,92 @@
const auto mode = inst->GetOperandAs<spv::ExecutionMode>(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<uint32_t>(2);
+ const auto operand_id = inst->GetOperandAs<uint32_t>(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<uint32_t>(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<uint32_t>(
+ spv::FPFastMathModeMask::AllowTransform)) != 0 &&
+ ((value & static_cast<uint32_t>(reassoc_contract)) !=
+ static_cast<uint32_t>(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 @@
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 @@
} // namespace
+spv_result_t ValidateFloatControls2(ValidationState_t& _) {
+ std::unordered_set<uint32_t> 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<std::pair<const Instruction*, spv::Decoration>> worklist;
+ for (const auto& inst : _.ordered_instructions()) {
+ if (inst.opcode() != spv::Op::OpDecorate) {
+ continue;
+ }
+
+ const auto decoration = inst.GetOperandAs<spv::Decoration>(1);
+ const auto target_id = inst.GetOperandAs<uint32_t>(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<spv::FPFastMathModeMask>(2);
+ if ((mask & spv::FPFastMathModeMask::Fast) !=
+ spv::FPFastMathModeMask::MaskNone) {
+ worklist.push_back(std::make_pair(target, decoration));
+ }
+ }
+ }
+
+ std::unordered_set<const Instruction*> 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 6c4042d..6f2ac9f 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 @@
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<spv_target_env, EnumCapabilityCase>;
+
// Test fixture for testing EnumCapabilityCases.
using EnumCapabilityTest =
TestWithParam<std::tuple<spv_target_env, EnumCapabilityCase>>;
diff --git a/test/text_to_binary.extension_test.cpp b/test/text_to_binary.extension_test.cpp
index 17adc55..8e78312 100644
--- a/test/text_to_binary.extension_test.cpp
+++ b/test/text_to_binary.extension_test.cpp
@@ -1264,5 +1264,41 @@
{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<AssemblyCase>{
+ {"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 9f85a30..97dde2d 100644
--- a/test/val/val_annotation_test.cpp
+++ b/test/val/val_annotation_test.cpp
@@ -65,6 +65,171 @@
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<std::string>;
TEST_P(MemberOnlyDecorations, MemberDecoration) {
diff --git a/test/val/val_modes_test.cpp b/test/val/val_modes_test.cpp
index 97d8c39..f174977 100644
--- a/test/val/val_modes_test.cpp
+++ b/test/val/val_modes_test.cpp
@@ -1328,6 +1328,552 @@
"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