Skip to content

Commit

Permalink
Disallow duplicate execution modes
Browse files Browse the repository at this point in the history
* Exceptions for float controls, float controls2 and some intel
  execution modes
  • Loading branch information
alan-baker committed Apr 11, 2024
1 parent 09edc76 commit 565d2db
Show file tree
Hide file tree
Showing 4 changed files with 226 additions and 0 deletions.
3 changes: 3 additions & 0 deletions source/val/validate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@ spv_result_t ValidateEntryPoints(ValidationState_t& _) {
if (auto error = ValidateFloatControls2(_)) {
return error;
}
if (auto error = ValidateDuplicateExecutionModes(_)) {
return error;
}

return SPV_SUCCESS;
}
Expand Down
7 changes: 7 additions & 0 deletions source/val/validate.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,13 @@ spv_result_t ValidateInterfaces(ValidationState_t& _);
/// @return SPV_SUCCESS if no errors are found.
spv_result_t ValidateFloatControls2(ValidationState_t& _);

/// @brief Validates duplicated execution modes for each entry point.
///
/// @param[in] _ the validation state of the module
///
/// @return SPV_SUCCESS if no errors are found.
spv_result_t ValidateDuplicateExecutionModes(ValidationState_t& _);

/// @brief Validates memory instructions
///
/// @param[in] _ the validation state of the module
Expand Down
66 changes: 66 additions & 0 deletions source/val/validate_mode_setting.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -724,6 +724,25 @@ spv_result_t ValidateMemoryModel(ValidationState_t& _,
return SPV_SUCCESS;
}

bool PerEntryExecutionMode(spv::ExecutionMode mode) {
switch (mode) {
// These execution modes can be specified multiple times per entry point.
case spv::ExecutionMode::DenormPreserve:
case spv::ExecutionMode::DenormFlushToZero:
case spv::ExecutionMode::SignedZeroInfNanPreserve:
case spv::ExecutionMode::RoundingModeRTE:
case spv::ExecutionMode::RoundingModeRTZ:
case spv::ExecutionMode::FPFastMathDefault:
case spv::ExecutionMode::RoundingModeRTPINTEL:
case spv::ExecutionMode::RoundingModeRTNINTEL:
case spv::ExecutionMode::FloatingPointModeALTINTEL:
case spv::ExecutionMode::FloatingPointModeIEEEINTEL:
return false;
default:
return true;
}
}

} // namespace

spv_result_t ValidateFloatControls2(ValidationState_t& _) {
Expand Down Expand Up @@ -808,5 +827,52 @@ spv_result_t ModeSettingPass(ValidationState_t& _, const Instruction* inst) {
return SPV_SUCCESS;
}

spv_result_t ValidateDuplicateExecutionModes(ValidationState_t& _) {
using PerEntryKey = std::tuple<spv::ExecutionMode, uint32_t>;
using PerOperandKey = std::tuple<spv::ExecutionMode, uint32_t, uint32_t>;
std::set<PerEntryKey> seen_per_entry;
std::set<PerOperandKey> seen_per_operand;

const auto lookupMode = [&_](spv::ExecutionMode mode) -> std::string {
spv_operand_desc desc = nullptr;
if (_.grammar().lookupOperand(SPV_OPERAND_TYPE_EXECUTION_MODE,
static_cast<uint32_t>(mode),
&desc) == SPV_SUCCESS) {
return std::string(desc->name);
}
return "Unknown";
};

for (const auto& inst : _.ordered_instructions()) {
if (inst.opcode() != spv::Op::OpExecutionMode &&
inst.opcode() != spv::Op::OpExecutionModeId) {
continue;
}

const auto entry = inst.GetOperandAs<uint32_t>(0);
const auto mode = inst.GetOperandAs<spv::ExecutionMode>(1);
if (PerEntryExecutionMode(mode)) {
if (!seen_per_entry.insert(std::make_tuple(mode, entry)).second) {
return _.diag(SPV_ERROR_INVALID_ID, &inst)
<< lookupMode(mode)
<< " execution mode must not be specified multiple times per "
"entry point";
}
} else {
// Execution modes allowed multiple times all take a single operand.
const auto operand = inst.GetOperandAs<uint32_t>(2);
if (!seen_per_operand.insert(std::make_tuple(mode, entry, operand))
.second) {
return _.diag(SPV_ERROR_INVALID_ID, &inst)
<< lookupMode(mode)
<< " execution mode must not be specified multiple times for "
"the same entry point and operands";
}
}
}

return SPV_SUCCESS;
}

} // namespace val
} // namespace spvtools
150 changes: 150 additions & 0 deletions test/val/val_modes_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1027,6 +1027,156 @@ OpExecutionModeId %main LocalSizeId %int_1 %int_1 %int_1
"constant instructions."));
}

using AllowMultipleExecutionModes = spvtest::ValidateBase<std::string>;

TEST_P(AllowMultipleExecutionModes, DifferentOperand) {
const std::string mode = GetParam();
const std::string spirv = R"(
OpCapability Shader
OpCapability DenormPreserve
OpCapability DenormFlushToZero
OpCapability SignedZeroInfNanPreserve
OpCapability RoundingModeRTE
OpCapability RoundingModeRTZ
OpExtension "SPV_KHR_float_controls"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpExecutionMode %main )" + mode + R"( 16
OpExecutionMode %main )" + mode + R"( 32
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";

CompileSuccessfully(spirv);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}

TEST_P(AllowMultipleExecutionModes, SameOperand) {
const std::string mode = GetParam();
const std::string spirv = R"(
OpCapability Shader
OpCapability DenormPreserve
OpCapability DenormFlushToZero
OpCapability SignedZeroInfNanPreserve
OpCapability RoundingModeRTE
OpCapability RoundingModeRTZ
OpExtension "SPV_KHR_float_controls"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpExecutionMode %main )" + mode + R"( 32
OpExecutionMode %main )" + mode + R"( 32
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";

CompileSuccessfully(spirv);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("execution mode must not be specified multiple times "
"for the same entry point and operands"));
}

INSTANTIATE_TEST_SUITE_P(MultipleFloatControlsExecModes,
AllowMultipleExecutionModes,
Values("DenormPreserve", "DenormFlushToZero",
"SignedZeroInfNanPreserve", "RoundingModeRTE",
"RoundingModeRTZ"));

using MultipleExecModes = spvtest::ValidateBase<std::string>;

TEST_P(MultipleExecModes, DuplicateMode) {
const std::string mode = GetParam();
const std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
OpExecutionMode %main )" + mode + R"(
OpExecutionMode %main )" + mode + R"(
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";

CompileSuccessfully(spirv);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("execution mode must not be specified multiple times "
"per entry point"));
}

INSTANTIATE_TEST_SUITE_P(MultipleFragmentExecMode, MultipleExecModes,
Values("DepthReplacing", "DepthGreater", "DepthLess",
"DepthUnchanged"));

TEST_F(ValidateMode, FloatControls2FPFastMathDefaultSameOperand) {
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 %none
OpExecutionModeId %main FPFastMathDefault %float %none
%void = OpTypeVoid
%float = OpTypeFloat 32
%int = OpTypeInt 32 0
%none = OpConstant %int 0
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";

CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_2);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_2));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("execution mode must not be specified multiple times "
"for the same entry point and operands"));
}

TEST_F(ValidateMode, FloatControls2FPFastMathDefaultDifferentOperand) {
const std::string spirv = R"(
OpCapability Shader
OpCapability Float16
OpCapability FloatControls2
OpExtension "SPV_KHR_float_controls2"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpExecutionModeId %main FPFastMathDefault %float %none
OpExecutionModeId %main FPFastMathDefault %half %none
%void = OpTypeVoid
%float = OpTypeFloat 32
%int = OpTypeInt 32 0
%none = OpConstant %int 0
%half = OpTypeFloat 16
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";

CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_2);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_2));
}

TEST_F(ValidateMode, FragmentShaderInterlockVertexBad) {
const std::string spirv = R"(
OpCapability Shader
Expand Down

0 comments on commit 565d2db

Please sign in to comment.